开始前先说明一点:网上标着“AI 自动化测试”的资料很多,但多数要么只讲概念,要么贴几个 Selenium 函数就当实战。真正能把 AI 辅助能力用进自动化测试框架,并且完整跑通接口、UI、报告、排查全流程的内容反而很少。这篇文章不聊虚的,直接从一个可运行的“AI + 自动化测试”项目入手,带你搭建一套基于 Python + Pytest + Playwright + Allure 的测试框架,同时加入 LLM 辅助生成用例、自动修复元素定位的模块。整个教程会覆盖环境搭建、被测应用编写、页面对象模型设计、AI 辅助模块实现、运行与报告生成,以及常见问题排查。新手照着敲也能跑通,有基础的可以直接拿框架去扩展。

1. AI+自动化测试到底是什么

1.1 自动化测试的痛点:为什么脚本写多了反而累

很多测试同学刚接触自动化时,心里想的是“写一次脚本,以后自动执行,彻底解放双手”。但实际进入项目后会碰到几个现实问题:

  • 页面改动频繁,元素定位一改就是一片红。
  • 用例维护成本高,一个业务场景涉及登录、搜索、下单、支付,脚本越来越长。
  • 断言写不准,界面上元素存在但状态不对,测试结果不可信。
  • 弹窗、网络延迟、加载顺序等不稳定因素导致用例间歇性失败。

这些问题在传统自动化测试框架里,往往靠人肉补强:加等待、写异常捕获、不断 micro 调整定位器。但人肉投入有限,这时候 AI 的介入就有了实际意义。

1.2 AI 在自动化测试中的三种落地形态

AI 不是魔法,它解决的是自动化测试中“重复决策”和“模式识别”的问题。目前工程里最常见的落地方式有三种:

落地形态 解决什么问题 典型实现
测试用例生成 从需求描述或接口文档直接生成用例 调用 LLM 接口,输入需求文本,输出用例步骤和断言
选择器自动修复 页面元素属性变化导致定位失败 LLM 分析 HTML 片段,推荐新的稳定定位方式
测试结果分析 失败日志多、人工排查慢 对错误信息分类、聚类,辅助定位根因

本文的实战项目会覆盖前两类,它们最容易在现有框架中落地,也最能体现“AI 辅助”的价值。

1.3 本文技术栈说明

为了让整套方案真正能在本地跑起来,并且不依赖商业平台,我选用了下面这套技术组合:

  • Python 3.10+:测试脚本语言。
  • Pytest:测试框架,负责用例组织、断言、夹具管理。
  • Playwright:浏览器自动化库,支持 Chromium、Firefox、WebKit,自带自动等待和截图能力。
  • Allure:测试报告框架,生成美观的 HTML 报告。
  • Flask:本地被测应用,模拟一个极简书店商城,用来承载自动化测试场景。
  • OpenAI 兼容接口:用于实现 AI 辅助生成用例和选择器修复。没有 Key 也能运行,框架会自动切换到规则兜底模式。

这套组合兼顾了易上手和工程可扩展性。学完之后,把被测应用替换成公司项目,框架本身可以直接复用。

2. 环境准备与项目结构

2.1 Python 环境准备

建议使用 Python 3.10 或更高版本。在开始之前,先确认你的环境:

python --version
pip --version

如果还没有安装 Python,进入官网下载对应系统安装包,安装时勾选“Add Python to PATH”。

接下来创建项目目录并初始化虚拟环境:

mkdir ai-auto-test
cd ai-auto-test
python -m venv venv

激活虚拟环境:

  • Windows:
venv\Scripts\activate
  • macOS / Linux:
source venv/bin/activate

2.2 安装依赖

创建 requirements.txt ,内容如下:

pytest==8.2.2
playwright==1.45.1
pytest-playwright==0.4.3
allure-pytest==2.13.5
flask==3.0.3
requests==2.32.3
openai==1.35.7

然后执行:

pip install -r requirements.txt

安装 Playwright 浏览器内核:

playwright install chromium

这里要注意,Playwright 的浏览器内核默认下载到用户目录,不需要额外配置系统 Chrome。如果下载慢,可以设置国内镜像源,但不推荐用不稳定渠道,建议保持官方源或配置可用的代理策略。

2.3 项目结构规划

强烈建议从一开始就按分层思想组织项目,不要把所有脚本都堆在同一个目录下。下面是本项目使用的结构:

ai-auto-test/
├── app/                        # 被测应用(Flask 极简书店)
│   ├── __init__.py
│   ├── app.py
│   └── templates/
│       ├── base.html
│       ├── index.html
│       ├── login.html
│       └── cart.html
├── ai_assistant/               # AI 辅助模块
│   ├── __init__.py
│   ├── llm_client.py           # LLM 调用客户端
│   └── locator_healer.py       # 选择器修复器
├── tests/                      # 自动化测试目录
│   ├── __init__.py
│   ├── conftest.py             # Pytest 夹具
│   ├── pages/                  # 页面对象层
│   │   ├── __init__.py
│   │   ├── base_page.py
│   │   ├── login_page.py
│   │   └── home_page.py
│   └── test_cases/
│       ├── __init__.py
│       ├── test_login.py
│       └── test_shopping.py
├── config.py                   # 全局配置
├── requirements.txt
└── pytest.ini

页面对象层(Page Object Model,简称 POM)是自动化测试中非常经典的设计模式,后面会单独解释。

2.4 pytest.ini 配置文件

创建 pytest.ini ,内容如下:

[pytest]
testpaths = tests/test_cases
timeout = 30
addopts = -v -s --alluredir=reports/allure-results

配置说明:

  • testpaths :指定用例目录,避免误收集。
  • timeout :单条用例最长执行时间,防止卡死。
  • addopts :默认携带的 Pytest 参数, --alluredir 指定 Allure 结果目录。

配置完成后,运行:

pytest --version

确认没有报错,说明基础环境已经准备好。

3. 核心原理:Playwright + Pytest + AI 的协作方式

3.1 Playwright 的核心能力

Playwright 相比老的 Selenium,最大的优势是“自动等待”。它内置了 actionability 检查,执行点击、输入之前会等元素可见、稳定、可操作,不再需要到处写 time.sleep()

看一个最简单的例子:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()
    page.goto("https://example.com")
    page.fill("#search-input", "自动化测试")
    page.click("button[type='submit']")
    print(page.title())
    browser.close()

这段代码演示了三个核心动作:

  • page.goto() :跳转页面。
  • page.fill() :输入框填充。
  • page.click() :点击元素。

Playwright 在执行 fill click 前会自动等待目标元素满足可操作条件,不用额外写显式等待。

3.2 同步 API 与异步 API

Playwright 提供了同步和异步两套 API:

  • 同步 API:适合大多数测试场景,写起来直观,可读性好。
  • 异步 API:适合高频并发场景,需要 async/await

本教程使用同步 API,工程上已经足够。

3.3 页面对象模型(POM)设计

POM 的核心思想是:把页面元素定位和页面操作封装到独立的类中,测试用例只关注业务步骤和断言。这样做的好处是页面一旦变化,只需要改对应 Page 类,不需要逐个修改测试用例。

以一个登录页为例:

# tests/pages/login_page.py
from tests.pages.base_page import BasePage


class LoginPage(BasePage):
    def __init__(self, page):
        super().__init__(page)
        self.username_input = "#username"
        self.password_input = "#password"
        self.login_button = "button[type='submit']"
        self.error_msg = ".error-message"

    def login(self, username, password):
        self.page.fill(self.username_input, username)
        self.page.fill(self.password_input, password)
        self.page.click(self.login_button)

    def get_error_message(self):
        return self.page.text_content(self.error_msg)

测试用例调用时,只需要关心业务逻辑:

def test_login_failed(login_page):
    login_page.login("admin", "wrong_password")
    assert login_page.get_error_message() == "用户名或密码错误"

页面结构变化时,只需要修改 LoginPage 里的定位器,测试用例不需要改动。

3.4 Pytest fixture 如何管理浏览器

Pytest 的 fixture 机制非常适合用来管理浏览器生命周期。在 conftest.py 中定义一个 page fixture,每个测试用例执行前创建浏览器页面,执行结束后自动关闭:

# tests/conftest.py
import pytest
from playwright.sync_api import sync_playwright


@pytest.fixture(scope="session")
def browser():
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        yield browser
        browser.close()


@pytest.fixture()
def page(browser):
    context = browser.new_context()
    current_page = context.new_page()
    yield current_page
    context.close()

这里的 scope="session" 表示整个测试会话只启动一次浏览器, page fixture 每个用例独立,避免用例之间数据相互影响。

3.5 AI 辅助模块的设计思路

AI 在本项目中的角色不是替代 Pytest 和 Playwright,而是给它们增加“智能决策”能力。具体体现在两个模块:

第一个是 LLM 客户端。 它负责把需求描述或 HTML 片段发送给大模型,并返回结构化的测试用例或定位建议。考虑到不是所有人都有 LLM API Key,客户端内部做了降级:没有配置 Key 时,使用内置规则模板。

第二个是自然语言选择器 。传统选择器依赖 id class xpath ,一旦页面结构调整就会失效。通过 LLM 分析元素上下文和可见文本,可以返回更稳定的定位策略,例如根据角色定位或文本内容定位。

简单来说,框架还是以 Playwright 定位为主,AI 负责在失败时提供“备选方案”和“新用例补充”,这样既稳定又灵活。

4. 完整实战:AI 辅助的极简书店自动化测试框架

这一节我们从零开始,把一个完整可运行的测试框架搭起来。

4.1 被测应用:极简书店

为了不依赖外部网站,我们先用 Flask 写一个极简书店,功能包括登录、商品列表、加入购物车、查看购物车。这段代码虽然简单,但覆盖了 Web 自动化测试中最典型的登录、跳转、渲染、点击、文本断言等场景。

创建 app/app.py

from flask import Flask, render_template, request, redirect, session
import uuid

app = Flask(__name__)
app.secret_key = "test-secret-key"

BOOKS = [
    {"id": 1, "name": "Python 自动化测试实战", "price": 79.0},
    {"id": 2, "name": "深入理解 Playwright", "price": 69.0},
    {"id": 3, "name": "AI 时代的软件测试", "price": 59.0},
]

USERS = {
    "admin": "123456",
    "tester": "abc1234",
}


@app.route("/")
def index():
    return render_template("index.html", books=BOOKS)


@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        username = request.form.get("username")
        password = request.form.get("password")
        if USERS.get(username) == password:
            session["user"] = username
            return redirect("/")
        return render_template("login.html", error="用户名或密码错误")
    return render_template("login.html")


@app.route("/add_to_cart/<int:book_id>")
def add_to_cart(book_id):
    cart = session.get("cart", [])
    cart.append(book_id)
    session["cart"] = cart
    return redirect("/cart")


@app.route("/cart")
def cart():
    cart_ids = session.get("cart", [])
    items = []
    total = 0.0
    for book_id in cart_ids:
        book = next((b for b in BOOKS if b["id"] == book_id), None)
        if book:
            items.append(book)
            total += book["price"]
    return render_template("cart.html", items=items, total=total)


@app.route("/logout")
def logout():
    session.clear()
    return redirect("/")


if __name__ == "__main__":
    app.run(host="127.0.0.1", port=5000, debug=True)

创建 app/templates/base.html

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>极简书店</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 40px; background: #f9f9f9; }
        .container { max-width: 800px; margin: 0 auto; background: #fff; padding: 20px; border-radius: 8px; }
        .book { padding: 12px; border-bottom: 1px solid #eee; }
        .nav { margin-bottom: 20px; }
        .nav a { margin-right: 16px; }
        .error { color: red; }
    </style>
</head>
<body>
<div class="container">
    <div class="nav">
        <a href="/">首页</a>
        {% if session.get("user") %}
            <span>欢迎,{{ session["user"] }}</span>
            <a href="/cart">购物车</a>
            <a href="/logout">退出</a>
        {% else %}
            <a href="/login">登录</a>
        {% endif %}
    </div>
    {% block content %}{% endblock %}
</div>
</body>
</html>

创建 app/templates/index.html

{% extends "base.html" %}
{% block content %}
<h1>极简书店</h1>
{% for book in books %}
<div class="book">
    <span>{{ book.name }}</span>
    <span>¥{{ book.price }}</span>
    {% if session.get("user") %}
    <a href="/add_to_cart/{{ book.id }}">加入购物车</a>
    {% else %}
    <a href="/login">请先登录</a>
    {% endif %}
</div>
{% endfor %}
{% endblock %}

创建 app/templates/login.html

{% extends "base.html" %}
{% block content %}
<h1>登录</h1>
{% if error %}
<p class="error">{{ error }}</p>
{% endif %}
<form method="post">
    <p><label>用户名:<input type="text" name="username" id="username"></label></p>
    <p><label>密码:<input type="password" name="password" id="password"></label></p>
    <p><button type="submit" id="login-btn">登录</button></p>
</form>
{% endblock %}

创建 app/templates/cart.html

{% extends "base.html" %}
{% block content %}
<h1>购物车</h1>
{% if items %}
    {% for item in items %}
    <div class="book">{{ item.name }} - ¥{{ item.price }}</div>
    {% endfor %}
    <p><strong>总计:¥{{ total }}</strong></p>
{% else %}
    <p>购物车还是空的</p>
{% endif %}
{% endblock %}

启动应用:

python app/app.py

浏览器访问 http://127.0.0.1:5000 ,就可以看到书店首页了。

4.2 配置管理

创建 config.py

# config.py
BASE_URL = "http://127.0.0.1:5000"

# LLM 配置,不填也可以运行,会自动使用规则兜底
LLM_API_KEY = ""
LLM_BASE_URL = "https://api.openai.com/v1"
LLM_MODEL = "gpt-4o-mini"

如果后续配置了自己的 LLM Key,AI 模块会启用真实模型调用;没有 Key 时,框架降级为规则模式,同样能跑完整流程。

4.3 编写页面对象层

创建 tests/pages/base_page.py

class BasePage:
    def __init__(self, page):
        self.page = page

    def goto(self, url):
        self.page.goto(url)

    def click(self, selector):
        self.page.click(selector)

    def fill(self, selector, text):
        self.page.fill(selector, text)

    def get_text(self, selector):
        return self.page.text_content(selector)

    def wait_for(self, selector):
        self.page.wait_for_selector(selector)

创建 tests/pages/login_page.py

from tests.pages.base_page import BasePage


class LoginPage(BasePage):
    def __init__(self, page):
        super().__init__(page)
        self.username_input = "#username"
        self.password_input = "#password"
        self.login_button = "#login-btn"
        self.error_msg = ".error"

    def goto_login(self):
        self.goto("http://127.0.0.1:5000/login")

    def login(self, username, password):
        self.fill(self.username_input, username)
        self.fill(self.password_input, password)
        self.click(self.login_button)

    def get_error_message(self):
        return self.get_text(self.error_msg)

创建 tests/pages/home_page.py

from tests.pages.base_page import BasePage


class HomePage(BasePage):
    def __init__(self, page):
        super().__init__(page)
        self.book_items = ".book"

    def goto_home(self):
        self.goto("http://127.0.0.1:5000/")

    def get_book_count(self):
        return len(self.page.locator(self.book_items).all())

    def get_first_book_name(self):
        return self.page.locator(self.book_items).first.text_content()

    def add_first_book_to_cart(self):
        self.page.locator(self.book_items).first.locator("a").click()

4.4 编写 AI 辅助模块

这是整个项目中最能体现“AI + 自动化测试”的部分。我们先实现一个 LLM 客户端,它既能真实调用大模型,也能在无 Key 时降级为规则生成。

创建 ai_assistant/llm_client.py

import json
import os
from openai import OpenAI


class LLMClient:
    def __init__(self, api_key=None, base_url=None, model=None):
        self.api_key = api_key or os.getenv("LLM_API_KEY", "")
        self.base_url = base_url or os.getenv("LLM_BASE_URL", "https://api.openai.com/v1")
        self.model = model or os.getenv("LLM_MODEL", "gpt-4o-mini")
        self.enabled = bool(self.api_key)
        if self.enabled:
            self.client = OpenAI(api_key=self.api_key, base_url=self.base_url)

    def generate_test_cases(self, requirement: str):
        """根据需求描述生成测试用例列表"""
        if not self.enabled:
            return self._rule_based_cases(requirement)

        prompt = f"""
        你是一名资深测试工程师,请根据下面的需求描述生成测试用例。
        输出 JSON 数组,每个元素包含 title、steps、expected 三个字段。
        需求描述:
        {requirement}
        
        只输出 JSON,不要输出多余文本。
        """
        try:
            response = self.client.chat.completions.create(
                model=self.model,
                messages=[{"role": "user", "content": prompt}],
                temperature=0.2,
            )
            content = response.choices[0].message.content.strip()
            # 兼容模型可能输出 ```json 包裹的情况
            if content.startswith("```"):
                content = content.strip("`")
                if content.startswith("json"):
                    content = content[4:]
            return json.loads(content)
        except Exception as e:
            print(f"LLM 调用失败,降级为规则生成:{e}")
            return self._rule_based_cases(requirement)

    def suggest_locator(self, html_snippet: str, fallback_text: str):
        """根据 HTML 片段推荐稳定的选择器"""
        if not self.enabled:
            return self._rule_based_locator(fallback_text)

        prompt = f"""
        下面是一段 HTML 片段,请帮我推荐一个最稳定的 Playwright 定位表达式。
        要求优先级:data-testid > id > 语义化文本关联 > 稳定的 class。
        HTML 片段:
        {html_snippet}
        
        只输出定位表达式,不要解释。
        """
        try:
            response = self.client.chat.completions.create(
                model=self.model,
                messages=[{"role": "user", "content": prompt}],
                temperature=0.2,
            )
            return response.choices[0].message.content.strip()
        except Exception:
            return self._rule_based_locator(fallback_text)

    @staticmethod
    def _rule_based_cases(requirement: str):
        """规则兜底:根据关键词返回常用用例模板"""
        cases = []
        if "登录" in requirement:
            cases.append({
                "title": "登录成功",
                "steps": ["打开登录页", "输入正确用户名密码", "点击登录"],
                "expected": "跳转到首页并显示用户昵称"
            })
            cases.append({
                "title": "登录失败",
                "steps": ["打开登录页", "输入错误密码", "点击登录"],
                "expected": "页面显示错误提示"
            })
        if "购物车" in requirement:
            cases.append({
                "title": "添加商品到购物车",
                "steps": ["登录商城", "点击第一本书的加入购物车", "进入购物车页面"],
                "expected": "购物车显示对应商品"
            })
        if not cases:
            cases.append({
                "title": "通用冒烟用例",
                "steps": ["打开首页", "检查页面标题"],
                "expected": "页面正常加载,无报错"
            })
        return cases

    @staticmethod
    def _rule_based_locator(text: str):
        """规则兜底:优先用 text 定位"""
        return f"text={text}"

创建 ai_assistant/locator_healer.py

from playwright.sync_api import Page


class LocatorHealer:
    """定位失败时的自动修复器"""

    def __init__(self, llm_client):
        self.llm_client = llm_client

    def heal(self, page: Page, failed_selector: str, fallback_text: str):
        """当 failed_selector 定位失败时,尝试用 LLM 推荐新定位器"""
        # 获取页面关键 HTML 片段,用于给 LLM 分析
        html_snippet = page.content()[:2000]
        suggested = self.llm_client.suggest_locator(html_snippet, fallback_text)

        # 验证建议的定位器是否有效
        try:
            locator = page.locator(suggested)
            if locator.count() > 0:
                print(f"选择器修复成功:{failed_selector} -> {suggested}")
                return suggested
        except Exception:
            pass

        # 如果建议无效,回退到文本定位
        fallback = f"text={fallback_text}"
        try:
            if page.locator(fallback).count() > 0:
                return fallback
        except Exception:
            pass

        raise Exception(f"无法自动修复选择器:{failed_selector}")

4.5 修改页面对象,接入 AI 修复能力

为了让修复器实际生效,在 BasePage 中加入一个带自动修复的点击方法:

from ai_assistant.locator_healer import LocatorHealer
from ai_assistant.llm_client import LLMClient

_llm_client = LLMClient()
_healer = LocatorHealer(_llm_client)


class BasePage:
    def __init__(self, page):
        self.page = page

    def goto(self, url):
        self.page.goto(url)

    def click_with_heal(self, selector, fallback_text):
        try:
            self.page.click(selector)
        except Exception:
            new_selector = _healer.heal(self.page, selector, fallback_text)
            self.page.click(new_selector)

    def fill(self, selector, text):
        self.page.fill(selector, text)

    def get_text(self, selector):
        return self.page.text_content(selector)

    def wait_for(self, selector):
        self.page.wait_for_selector(selector)

这里需要注意, click_with_heal 里的 heal 方法会读取页面 HTML 片段并调用 LLM。如果配置了 Key,这是一个真实的智能修复过程;如果没有 Key,则走规则兜底,尝试用可见文本定位。

4.6 编写测试用例

创建 tests/test_cases/test_login.py

from tests.pages.login_page import LoginPage


def test_login_success(page):
    login_page = LoginPage(page)
    login_page.goto_login()
    login_page.login("admin", "123456")
    page.wait_for_url("http://127.0.0.1:5000/")
    assert page.title() == "极简书店"


def test_login_failed(page):
    login_page = LoginPage(page)
    login_page.goto_login()
    login_page.login("admin", "wrong-password")
    error = login_page.get_error_message()
    assert error == "用户名或密码错误"

创建 tests/test_cases/test_shopping.py

from tests.pages.login_page import LoginPage
from tests.pages.home_page import HomePage


def test_add_to_cart(page):
    # 登录
    login_page = LoginPage(page)
    login_page.goto_login()
    login_page.login("admin", "123456")

    # 进入首页,添加第一本书到购物车
    home_page = HomePage(page)
    home_page.goto_home()
    home_page.add_first_book_to_cart()

    # 验证购物车
    page.wait_for_url("**/cart")
    cart_text = page.text_content("body")
    assert "Python 自动化测试实战" in cart_text
    assert "总计" in cart_text


def test_ai_generate_cases(page):
    """演示 LLM 生成用例,并输出到控制台"""
    from ai_assistant.llm_client import LLMClient
    client = LLMClient()
    cases = client.generate_test_cases("用户登录和购物车功能")
    assert len(cases) > 0
    for case in cases:
        print(f"\n用例:{case['title']}")
        print(f"步骤:{case['steps']}")
        print(f"预期:{case['expected']}")

4.7 编写 conftest.py 完善浏览器管理

更新 tests/conftest.py ,加入截图失败自动保存逻辑:

import os
import pytest
from playwright.sync_api import sync_playwright


@pytest.fixture(scope="session")
def browser():
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        yield browser
        browser.close()


@pytest.fixture()
def page(browser):
    context = browser.new_context(viewport={"width": 1280, "height": 720})
    current_page = context.new_page()
    yield current_page
    context.close()


@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    report = outcome.get_result()
    if report.when == "call" and report.failed:
        page = item.funcargs.get("page")
        if page:
            os.makedirs("reports/screenshots", exist_ok=True)
            screenshot_path = f"reports/screenshots/{item.name}.png"
            page.screenshot(path=screenshot_path)
            print(f"\n截图已保存:{screenshot_path}")

4.8 运行测试与生成报告

启动 Flask 应用后,新开一个终端执行:

pytest

预期输出类似:

collected 4 items

tests/test_cases/test_login.py::test_login_success PASSED
tests/test_cases/test_login.py::test_login_failed PASSED
tests/test_cases/test_shopping.py::test_add_to_cart PASSED
tests/test_cases/test_shopping.py::test_ai_generate_cases PASSED

然后生成 Allure 报告:

allure generate reports/allure-results -o reports/allure-report --clean
allure open reports/allure-report

浏览器会打开一份结构化测试报告,包含每个用例的执行状态、耗时、失败截图等信息。

5. 常见问题与排查思路

5.1 非预期弹窗导致用例失败

现象:点击按钮后,页面弹出优惠券弹窗、广告遮罩或确认弹窗,导致后续元素被遮挡,Playwright 点击超时。

原因:被测系统在特定时机弹出非预期组件,自动化脚本没有处理弹窗逻辑。

解决方案:

# 在 conftest.py 的 page fixture 中统一处理 dialog 弹窗
def handle_dialog(dialog):
    print(f"捕获到弹窗:{dialog.message}")
    dialog.dismiss()

current_page.on("dialog", handle_dialog)

对于页面内渲染的遮罩层,建议封装一个“关闭弹窗”方法:

def close_if_present(page, close_selector):
    locator = page.locator(close_selector)
    if locator.count() > 0:
        locator.first.click()

预防措施:

  • 用例数据尽量选择不会触发弹窗的场景。
  • 对于遮罩层,使用 page.locator(selector).first.click(timeout=3000) 并捕获异常。
  • 所有弹窗处理逻辑统一封装,不要散落在用例中。

5.2 元素定位超时或定位失败

问题现象 常见原因 解决思路
TimeoutError 页面加载慢,元素未出现 使用 Playwright 自动等待,调整全局 timeout
strict mode violation 选择器匹配到多个元素 使用 .first .nth() 或更精确的定位表达式
Element is not attached 页面发生重绘,元素被替换 使用 wait_for_selector 重新获取元素
AI 推荐定位器无效 HTML 片段过长,模型分析不准 截取元素局部 HTML,或降低 temperature 参数

排查顺序建议:先看失败截图,确认页面实际状态;再看控制台日志,检查是否触发弹窗或跳转;最后用 Playwright 的 locator.count() 验证选择器是否匹配多个元素。

5.3 Playwright 启动时下载浏览器失败

现象:执行 playwright install chromium 时长时间无响应或报 SSL 错误。

可能原因:网络不稳定、镜像源不可达、磁盘空间不足。

解决思路:

  • 先检查网络。
  • 清理 pip 缓存后重装。
  • 如果下载包本身损坏,删除 ~/AppData/Local/ms-playwright (Windows)或 ~/Library/Caches/ms-playwright (macOS)下的残留文件,再执行安装。

5.4 AI 生成的测试用例不稳定

现象:LLM 返回的用例有时多有时少,偶尔输出不是 JSON。

原因:模型输出随机性,以及 prompt 约束不够严格。

解决办法:

  • 增加 prompt 中的格式示例,要求只输出 JSON。
  • 设置 temperature=0.2 降低随机性。
  • 在解析时增加 json.loads 失败后的容错逻辑,比如截取 [ ] 之间的内容。
  • 生产环境考虑把 LLM 输出保存到文件中,人工审核后转成 pytest 用例,而不是直接执行。

6. 最佳实践与工程建议

6.1 测试分层,控制用例粒度

自动化用例不要做成“一锅端”,尽量按三层拆分:

  • 冒烟层:核心链路,登录、主页面打开、关键功能冒烟。
  • 业务层:按业务模块编写,比如登录、购物车、订单。
  • 回归层:全量功能用例,可以放在夜间执行。

每一条用例尽量独立,不依赖其他用例的执行顺序。用例之间共享状态越少,越稳定。

6.2 页面对象层的维护规范

  • 页面元素定位器统一放在 Page 类顶部,不要散落在方法内部。
  • 优先使用 data-testid id 、可见文本这些稳定的定位方式,避免使用复杂 XPath。
  • 一个元素被多个方法使用时,定义为类属性,方便统一修改。
  • 方法命名尽量体现业务动作,例如 login() add_to_cart() ,避免 click_btn_1() 这类无意义命名。

6.3 AI 辅助的正确边界

AI 在自动化测试里适合做“辅助决策”,不适合做“全自动无人值守”。推荐的做法:

  • 用例生成后必须人工 review,再纳入测试套件。
  • 定位器修复时,自动修复结果要输出日志,方便定位问题。
  • LLM 调用要设置超时和失败降级机制,不能因为 AI 服务不可用拖垮整个测试。
  • 涉及用户数据、密钥、内部系统的提示词中,不要传入敏感信息。
  • 调用 AI 接口时,注意数据合规要求,尤其是公司内部业务数据,需要先做脱敏。

6.4 与 CI/CD 集成

本地跑通后,下一步是接入 CI。常见流程:

  1. 代码提交到 Git。
  2. CI 拉取代码,安装依赖。
  3. 启动被测应用(或部署测试环境)。
  4. 运行自动化测试。
  5. 生成 Allure 报告,上传到报告服务。
  6. 测试失败时发送通知。

Jenkins 中的流水线片段示例:

stage('自动化测试') {
    steps {
        sh '''
            python -m venv venv
            source venv/bin/activate
            pip install -r requirements.txt
            playwright install chromium
            nohup python app/app.py > app.log 2>&1 &
            sleep 3
            pytest
            deactivate
        '''
    }
}

接入 CI 之后,测试的价值才能真正体现:每次提交代码,自动回归,尽早发现问题。

6.5 从 0 开始的学习路径

如果你目前是零基础,建议按下面的顺序学习:

  1. Python 基础:变量、流程控制、函数、类、文件操作。
  2. Pytest 基础:断言、fixture、参数化、hook。
  3. Playwright 基础:定位、操作、等待、截图。
  4. 页面对象模型:理解分层思想,重构自己的脚本。
  5. 接口测试:使用 requests + pytest 覆盖接口层。
  6. AI 辅助:学习调用 LLM API,并把生成结果接入测试框架。
  7. 平台化:把测试报告、用例管理集成到公司内部平台。

不建议一上来就追求“全平台自动化”“关键字驱动框架”这些大词,先把一条完整链路跑通,再逐步扩展。

运行完本文这个项目后,建议你尝试做三件事:

  • 给极简书店增加一个“订单提交”页面,并补充对应的自动化测试用例。
  • 用 LLM 生成新增需求对应的测试步骤,并检查生成结果是否覆盖了异常场景。
  • 把项目接入 CI,模拟一次提交代码后自动触发测试的完整流程。

真正跑完这三步,你对 AI + 自动化测试的理解会从“看过概念”变成“能写进简历的项目经验”。如果这篇文章对你有帮助,可以先收藏起来,照着步骤把环境搭起来再往下看,动手实践永远是学习自动化测试最有效的方式。

更多推荐