AI辅助自动化测试实战:基于Python+Pytest+Playwright框架搭建
开始前先说明一点:网上标着“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。常见流程:
- 代码提交到 Git。
- CI 拉取代码,安装依赖。
- 启动被测应用(或部署测试环境)。
- 运行自动化测试。
- 生成 Allure 报告,上传到报告服务。
- 测试失败时发送通知。
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 开始的学习路径
如果你目前是零基础,建议按下面的顺序学习:
- Python 基础:变量、流程控制、函数、类、文件操作。
- Pytest 基础:断言、fixture、参数化、hook。
- Playwright 基础:定位、操作、等待、截图。
- 页面对象模型:理解分层思想,重构自己的脚本。
- 接口测试:使用 requests + pytest 覆盖接口层。
- AI 辅助:学习调用 LLM API,并把生成结果接入测试框架。
- 平台化:把测试报告、用例管理集成到公司内部平台。
不建议一上来就追求“全平台自动化”“关键字驱动框架”这些大词,先把一条完整链路跑通,再逐步扩展。
运行完本文这个项目后,建议你尝试做三件事:
- 给极简书店增加一个“订单提交”页面,并补充对应的自动化测试用例。
- 用 LLM 生成新增需求对应的测试步骤,并检查生成结果是否覆盖了异常场景。
- 把项目接入 CI,模拟一次提交代码后自动触发测试的完整流程。
真正跑完这三步,你对 AI + 自动化测试的理解会从“看过概念”变成“能写进简历的项目经验”。如果这篇文章对你有帮助,可以先收藏起来,照着步骤把环境搭起来再往下看,动手实践永远是学习自动化测试最有效的方式。
更多推荐

所有评论(0)