AI自动化测试入门:Python+Playwright+Pytest实战路线
AI自动化测试并不是让AI完全替代测试人员,而是把测试中最耗时、最容易重复的劳动交给AI辅助完成。真正有价值的能力是:知道哪些用例需要自动化、如何让脚本稳定运行、如何在页面变化时快速修复定位器、如何把测试结果接入工程体系。如果只有7小时,你需要一条比“从零学Selenium”更短的学习路径,因为传统自动化往往卡在元素定位、脚本维护和等待策略上。这篇文章围绕一个最小可落地的技术栈:Python + Playwright + Pytest + AI辅助工具,拆解从环境准备到持续集成的完整过程。
1. 先搞清楚AI自动化测试到底改了什么
1.1 传统自动化测试为什么“学会容易,维护难”
传统自动化测试的核心流程并不复杂:打开浏览器,定位页面元素,执行点击和输入,再对结果加断言。这条链路运行一次并不难,难的是当项目进入持续迭代之后,脚本为什么频繁失败、失败之后要花多长时间去修。
真实项目里,最常见的维护成本来自几个固定场景。页面结构调整后,基于CSS或XPath写死的定位器失效;前端加了异步渲染后,固定sleep等待不可靠;系统弹窗、iframe、新窗口、shadow DOM这些特殊情况需要大量样板代码;断言写得太简单,页面没报错但结果不对,脚本照样通过。这些问题的本质不是“代码写错了”,而是“脚本和页面结构绑得太紧”。
传统工具并不是没有价值。Selenium仍是许多企业的存量基础,Appium仍是移动端自动化的常见选择。但从学习效率来看,7小时里如果从传统Selenium脚本的录制回放一路学起,很可能还没有接触到测试设计和稳定性处理,时间就已经用完。更好的策略是:先选择一个自动等待更完善、API更现代的工具,跑通一个最小闭环,然后再回头理解传统方案。
1.2 AI介入自动化测试的三种模式
AI在自动化测试里的落地方式,并不等同于“AI自动生成测试用例然后替你把活都干了”。目前比较成熟且具备工程实用价值的有三种模式。
第一种是辅助生成代码。测试人员用自然语言描述目标操作,AI生成Playwright或Pytest代码。这个模式适合编写页面对象、定位器、数据驱动用例的初稿,节省的是“从需求到代码”的翻译成本。但生成的代码必须经过人工确认和真实环境验证。
第二种是智能定位。传统定位器依赖DOM结构,页面一改就崩。AI可以把一段HTML片段和自然语言目标交给大模型,让它推荐更稳定的定位方式,比如优先使用语义化的role、name、>python -m venv .venv
macOS或Linux执行:
python3 -m venv .venv然后激活虚拟环境。Windows下:
.venv\Scripts\activatemacOS或Linux下:
source .venv/bin/activate激活后命令行前缀会出现(.venv),说明当前已经进入虚拟环境。此时再执行:
python --version确认Python版本符合预期。这里要注意,如果激活虚拟环境后python仍然指向全局版本,说明激活命令没有生效,或者当前终端缓存了旧的命令路径。
注意:不要跳过虚拟环境这一步。很多“环境装不上”的问题,根源都是项目依赖和系统依赖混在了一起。
2.3 安装依赖和浏览器
在虚拟环境激活状态下,先升级pip,再安装核心依赖:
python -m pip install --upgrade pip pip install playwright pytest pytest-html requestsplaywright是浏览器自动化核心,pytest是测试组织框架,pytest-html用于生成HTML报告,requests用于接口测试和调用AI服务。
安装完Python包之后,还需要下载Chromium浏览器内核:
playwright install chromium这一步会在用户目录下载浏览器文件。如果在公司网络或受限环境中下载失败,需要检查网络权限,或者配置团队内部的镜像源。下载完成后,可以运行一条简单的验证命令:
python -c "from playwright.sync_api import sync_playwright; print('playwright ok')"输出playwright ok,说明Python包可以正常导入。
2.4 初始化项目和pytest配置
先建立项目目录结构。建议从第一天就按照工程化方式组织,后面扩展才不会乱:
auto_test_project/ ├── config/ │ └── settings.py ├── pages/ │ └── base_page.py ├── tests/ │ ├── conftest.py │ └── test_first.py ├── utils/ │ ├── ai_client.py │ └── screenshot.py ├── reports/ ├── requirements.txt └── pytest.iniconfig放URL、账号、超时时间等配置;pages放页面对象;tests放测试用例;utils放AI客户端、截图工具等公共能力;reports保存测试报告和失败截图。
在pytest.ini中做基础配置:
[pytest] python_files = test_*.py python_classes = Test* python_functions = test_* addopts = -q --disable-warnings testpaths = tests这里指定了测试文件、类、函数的命名规则,并让pytest只扫描tests目录。运行pytest时如果找不到用例,优先检查命名是否匹配这些规则。
最后创建一个最简单的空测试文件,例如tests/test_first.py:
def test_example(): assert 1 + 1 == 2运行:
pytest看到测试通过后,环境准备这一步才算真正完成。
3. 用Playwright跑通第一个Web自动化用例
3.1 第一个端到端用例
有了环境后,先写一个不依赖任何被测系统的用例,验证Playwright本身是否正常。这里以https://example.com为例,这个页面结构稳定,适合做环境验证。实际项目里应该把URL替换为被测系统地址。
创建tests/test_first.py,写入:
from playwright.sync_api import sync_playwright def test_open_demo_page(): with sync_playwright() as p: browser = p.chromium.launch(headless=False) page = browser.new_page() page.goto("https://example.com") assert page.title() == "Example Domain" browser.close()代码里with sync_playwright() as p负责启动和清理Playwright运行时。headless=False会弹出浏览器窗口,便于新手观察操作过程。如果是在CI或服务器上运行,需要改成headless=True。
运行用例:
pytest tests/test_first.py如果浏览器没有自动关闭,检查是否在断言失败时抛出了异常。更健壮的写法是使用try/finally或后置fixture释放资源,而不是只靠脚本正常结束时关闭。
3.2 用pytest fixture封装浏览器生命周期
手写browser.close()在遇到断言失败时容易漏执行。推荐的方案是把浏览器和页面的创建放在pytest的fixture里,pytest会保证清理。
在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() page = context.new_page() yield page context.close()这里browser是session级别,整个测试会话只创建一次浏览器实例;page是function级别,每个用例都有独立的浏览器上下文,避免用例之间的登录态和Cookie相互污染。
修改测试用例,让模板自动管理页面:
def test_demo_title(page): page.goto("https://example.com") assert page.title() == "Example Domain"这样即使有10个用例,每个用例也会拿到干净的新页面。这个设计对web自动化非常重要,因为页面状态共享往往是“偶发失败”的来源。
3.3 用AI辅助生成定位器和用例
当页面结构复杂时,人工写定位器很容易写出非常脆弱的表达式。AI在这里可以帮上忙,但不是直接让它给你一段选择器就算完。
提供一个通用提问模板:
页面地址:<这里填被测页面URL> 目标操作:<例如:点击登录按钮> 页面HTML片段:<打开开发者工具,复制目标元素附近的一小段HTML> 请生成Playwright定位器,要求优先使用get_by_role,其次data-testid,最后才是文本。只输出代码,不要解释。假设页面HTML是:
<button class="btn btn-primary login-button" type="submit">登录</button>AI可能返回:
page.get_by_role("button", name="登录")对比page.locator("button.btn.btn-primary.login-button"),get_by_role的语义更稳定,页面class调整后它仍然能工作。AI生成的结果会不会出错?会。所以必须拿到真实页面上去运行一遍,确认能找到唯一元素。
注意:不要直接复制AI生成的选择器到测试代码里。先运行一次,再检查是否匹配到了预期元素,最后再固化到页面对象中。
3.4 参数化与数据驱动
同一个业务流程往往要用多组数据验证,比如登录时要验证正常账号、错误密码、空用户名。把这些场景复制成多条用例会非常啰嗦,pytest的parametrize可以解决。
示例写法:
import pytest @pytest.mark.parametrize("keyword, expected_title", [ ("python", "python"), ("playwright", "playwright"), ]) def test_search(page, keyword, expected_title): page.goto("https://example-custom-site.com") search_box = page.get_by_role("textbox", name="搜索") search_box.fill(keyword) search_box.press("Enter") assert expected_title in page.title()这里的URL和业务字段只是示例,落地时要结合真实被测系统调整。参数化的好处是,新增一组测试数据只需要增加一行元组,不需要复制用例函数。但也要注意,不是所有数据都适合参数化。如果每一组数据都对应不同的断言逻辑,强行参数化反而会降低代码可读性。
4. 把AI能力接进测试:从“生成代码”到“自适应维护”
4.1 明确AI在测试项目里的边界
在把AI接入项目代码之前,先定清楚哪些环节可以交给AI,哪些不能。
| 适合AI辅助的场景 | 不适合完全交给AI的场景 |
|---|---|
| 从HTML生成定位器 | 对高风险的支付、删除等操作决策 |
| 生成参数化测试数据初稿 | 断言业务结果是否符合复杂规则 |
| 汇总失败日志和截图,生成分析摘要 | 对测试覆盖率的最终评估 |
| 补充常规边界值测试数据 | 判断产品需求是否发生了变化 |
在测试项目里,AI更适合做“翻译”和“归纳”,不太适合做“决策”。这一点要在团队协作时讲清楚,否则会出现AI生成的脚本破坏了业务流程的严重问题。
4.2 用Python封装一个AI客户端
为了让AI能力可以在多个用例里复用,建议封装一个统一的客户端。下面这个示例假设你已经有可访问的大模型服务,API格式为常见的chat/completions格式,实际项目以服务方文档为准。
创建utils/ai_client.py:
import os import requests LLM_API_URL = os.getenv("LLM_API_URL", "http://your-llm-endpoint/v1/chat/completions") LLM_API_KEY = os.getenv("LLM_API_KEY", "") def chat(prompt: str, system: str = "你是一名自动化测试专家。") -> str: payload = { "model": os.getenv("LLM_MODEL", "local-model"), "messages": [ {"role": "system", "content": system}, {"role": "user", "content": prompt}, ], "temperature": 0.2, } headers = {"Authorization": f"Bearer {LLM_API_KEY}"} resp = requests.post(LLM_API_URL, json=payload, headers=headers, timeout=30) resp.raise_for_status() return resp.json()["choices"][0]["message"]["content"]这段代码的关键点有三个。temperature设置为0.2,目的是让AI输出尽量稳定,不要每次都生成不同写法;超时设置为30秒,避免因为模型响应慢拖挂测试;API地址和密钥通过环境变量注入,不要写死在代码里。
4.3 用AI辅助定位元素,并在常规定位失败时兜底
AI定位不能作为默认主路径,否则每次跑测试都要等待模型响应,测试速度会慢到无法接受。合理做法是:常规定位失败后,记下日志,再使用AI辅助定位作为兜底。
先让AI返回结构化描述,不要直接返回代码字符串。创建一个定位器生成函数:
import json def generate_locator(html_snippet: str, target: str) -> dict: prompt = f""" 请分析下面的HTML片段,找出目标元素:{target} HTML: {html_snippet} 请返回JSON,格式为: {{"type": "role", "role": "button", "name": "登录"}} 或 {{"type": "test_id", "test_id": "login-btn"}} 或 {{"type": "text", "text": "登录"}} 或 {{"type": "selector", "selector": "button.login-btn"}} 只输出JSON。 """ content = chat(prompt) return json.loads(content) def locator_from_desc(page, desc: dict): locator_type = desc["type"] if locator_type == "role": return page.get_by_role(desc["role"], name=desc.get("name")) if locator_type == "test_id": return page.get_by_test_id(desc["test_id"]) if locator_type == "text": return page.get_by_text(desc["text"]) return page.locator(desc["selector"])在用例里这样使用:
from playwright.sync_api import expect from utils.ai_client import generate_locator, locator_from_desc def test_login_with_ai_fallback(page): page.goto("https://your-test-site.com/login") try: page.get_by_role("button", name="登录").click(timeout=3000) except Exception as exc: html_snippet = page.locator("body").inner_html()[:3000] desc = generate_locator(html_snippet, "登录按钮") locator = locator_from_desc(page, desc) expect(locator).to_be_visible() locator.click()这里的兜底逻辑要有两个限制。第一,body的HTML可能非常大,传给模型前先截断,避免超长文本;第二,AI调用失败时要继续抛出原始异常,不能让测试因为AI服务波动而误判。
4.4 非预期弹窗导致失败的解决方案
接口自动化之外,Web自动化最常遇到的稳定性问题就是非预期弹窗。弹窗可能来自业务通知、广告、新版本提示,也可能来自浏览器原生弹窗。处理的原则是:不要一看到弹窗就关,而是先判断弹窗类型,再用对应的策略处理。
原生对话框由浏览器控制,例如alert、confirm、prompt。Playwright可以通过监听dialog事件处理:
page.on("dialog", lambda dialog: dialog.accept())页面内弹窗则是普通DOM元素,常见做法是在点击目标元素前,尝试关闭已知的弹窗按钮:
def close_known_popups(page, texts=("知道了", "我知道了", "关闭", "确定")): for text in texts: button = page.get_by_role("button", name=text) try: if button.is_visible(timeout=500): button.click(timeout=500) except Exception: pass这个方案适用于弹窗文案已知且稳定的系统。如果弹窗变化非常频繁,建议保留每次失败的截图,再交给AI分析弹窗类型,逐步整理成可维护的弹窗处理列表。
4.5 用截图和AI做失败分析
用例失败时,最不希望看到的是测试报告里只有一句红色的报错。可以结合Pytest的hook,在失败时自动截图并保存当前页面HTML。
在conftest.py中添加:
import pytest @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: page.screenshot(path=f"reports/{item.name}.png") with open(f"reports/{item.name}.html", "w", encoding="utf-8") as f: f.write(page.content())有了截图和HTML快照,AI才能做进一步分析。可以写一个分析Prompt:
这是测试用例失败时的HTML内容。 请分析页面是否出现了非预期弹窗、元素未加载、页面跳转失败等情况。 给出可能的失败原因和下一步检查建议。失败分析的意义不在于AI直接给出“某某代码错了”的结论,而是帮助测试人员从大量信息里快速定位到“弹窗”“加载慢”“选择器过时”这几类最常见原因。
5. 从浏览器测试扩到接口测试,再接入持续集成
5.1 接口自动化测试的最小闭环
UI自动化适合验证关键用户流程,但接口层才是测试覆盖率的重要来源。接口测试不需要打开浏览器,运行速度快,定位问题也更直接。
创建一个简单的接口测试示例,假设被测系统有一个登录接口和一个获取用户信息的接口:
import requests BASE_URL = "http://your-service-address" def test_login_and_get_profile(): login_resp = requests.post( f"{BASE_URL}/api/login", json={"username": "admin", "password": "123456"}, timeout=10, ) assert login_resp.status_code == 200, f"登录失败: {login_resp.text}" token = login_resp.json()["data"]["token"] assert token, "登录响应中没有token" profile_resp = requests.get( f"{BASE_URL}/api/profile", headers={"Authorization": f"Bearer {token}"}, timeout=10, ) assert profile_resp.status_code == 200 assert profile_resp.json()["data"]["username"] == "admin"这里断言了三个关键点:登录接口的HTTP状态码、token是否返回、token能否正常访问用户信息。实际项目的接口字段可能不同,但这条“登录 → 拿token → 带token访问资源”的链路非常常见。接口测试报告里要区分“服务未启动”“接口返回500”“断言失败”三种情况。
5.2 用AI生成接口测试用例的工作流
接口测试用例的重复性很高,适合用AI辅助生成初稿。可以整理一个接口文档模板给AI:
接口路径:POST /api/login 请求体:{"username": "string", "password": "string"} 成功响应:{"code": 0, "data": {"token": "string"}} 失败响应:{"code": 1001, "message": "用户名或密码错误"} 请生成pytest参数化用例,覆盖正常登录、错误密码、缺少用户名、空密码四种情况。AI生成的用例可能很完整,但也可能生成假设性的字段或断言,所以必须做两件事:第一,根据真实接口文档修正字段;第二,在本地至少运行一次所有生成用例,确认不是“纸面代码”。接口用例可以放在tests/test_api_demo.py里,和UI用例共存。
5.3 生成HTML测试报告
本地跑通之后,需要让测试结果看起来直观。pytest-html在上一步已经安装,只需在运行时加上参数:
pytest --html=reports/report.html --self-contained-html--self-contained-html会把CSS和JS嵌入HTML文件,单文件可以直接发送给同事查看。报告里会显示每个用例的状态、耗时和失败信息。如果配合了失败截图,就可以在上报时定位问题。
执行后再打开reports/report.html,确认没有缺失样式,用例状态正常。
5.4 把测试放进CI,让每次提交都自动跑
本地能跑通只是第一步,自动化测试的真正价值在于持续运行。下面以GitHub Actions为例,提供一个最小CI配置。如果团队使用Jenkins、GitLab CI或自建平台,逻辑类似。
name: Automated Test on: push: pull_request: jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install dependencies run: | pip install -r requirements.txt playwright install --with-deps chromium - name: Run pytest run: pytest --html=reports/report.html --self-contained-html - name: Upload report if: always() uses: actions/upload-artifact@v4 with: name: test-report path: reports/这里有一个关键点:CI环境没有显示器,Playwright必须以无头模式运行。如果用例里写死了headless=False,在CI中会报错。建议在fixture里通过环境变量控制:
import os @pytest.fixture(scope="session") def browser(): is_headless = os.getenv("HEADLESS", "true").lower() == "true" with sync_playwright() as p: browser = p.chromium.launch(headless=is_headless) yield browser browser.close()5.5 全部跑通后的验收清单
一个可以拿给别人演示的自动化测试项目,至少要满足这几点:
- 新环境按
requirements.txt安装依赖后,能通过pytest一键运行。 - 测试用例有清晰的文件和函数命名,别人能看懂每个用例在验证什么。
- 失败用例能自动生成截图和页面HTML快照。
- 生成的HTML测试报告可以离线打开。
- README里写清了如何启动被测服务、如何运行测试、如何查看报告。
如果上面任意一项缺失,建议不要急着继续加测试用例,先把工程基础补齐。
6. 常见问题排查:从现象到根因,十分钟定位
6.1 环境装不上或版本不匹配
不少初学者在最开始就卡住。常见现象是pip install playwright成功,但运行测试时报ModuleNotFoundError,或者playwright install下载浏览器失败。
这可能是因为pip安装了Python包,但Playwright浏览器没有下载成功;也可能是激活的虚拟环境和执行命令的终端不一致。先检查:
python -m playwright --version which python pip show playwright如果which python显示的不是项目虚拟环境路径,说明终端没有激活虚拟环境。如果playwright包存在,但浏览器下载失败,需要重新运行:
playwright install chromium6.2 浏览器启动失败
在服务器或Docker容器中运行,经常遇到缺少系统依赖的错误。Playwright官方提供了自动安装依赖的命令:
playwright install-deps chromium这个命令需要管理员权限。如果运行后仍然失败,检查操作系统版本是否受支持,以及是否有安全策略限制了用户目录下载文件。
6.3 定位器找不到元素
定位器报错是最常见的自动化测试问题。不要急着改定位器,先按顺序排查:
- 页面是否已经加载到目标元素。如果页面有异步渲染,先确认已经触发等待,例如
expect(page).to_have_title()或page.wait_for_selector()。 - 元素是否在iframe里。Playwright需要使用
frame_locator进入iframe再定位。 - 元素是否在shadow DOM中。Playwright可以穿透shadow DOM,但必须先确保定位的是正确的宿主节点。
- 页面是否发生了跳转或弹窗遮挡。结合上一步的截图判断。
6.4 AI生成的代码不可靠
AI生成的定位器可能在本地能跑,但过一天就失效,尤其是依赖文本、CSS class这些变化频繁的属性。AI输出的建议必须经过人工审查,核心判断标准是:这个定位器是否表达了元素的语义,而不是表面样式。
如果AI返回的是一个超长CSS路径,大概率不可维护。建议继续向AI追加提示,要求改用get_by_role或>
