使用说明
🚀 QA自动驾驶仪——剧作家测试失败的人工智能诊断
pytest插件——Playwright测试失败的实时AI诊断
多LLM(OpenAI·Anthropic·Ollama·DeepSeek)·默认GDPR·开源
🎯 的问题
剧作家测试不及格。错误消息显示:
TimeoutError: Page.click: Timeout 5000ms exceeded.
waiting for locator("a[href='/international/']")
选择器正确。元素存在。 那么,为什么它不起作用呢?
因为饼干横幅覆盖了一切。或者元素位于iframe内部。或者DOM是通过AJAX重新加载的。或者按钮是 disabled。或者你用过 click() 而不是 dblclick().
QA Autopilot告诉你真正的原因。
📊 记分卡
一套搜索结果 7个陷阱测试 专门设计用于欺骗诊断工具:
| 测试 | 陷阱 | 人工智能诊断 | 类别 | 信心 |
|---|---|---|---|---|
| 🫣 Cookie覆盖 | 横幅覆盖的元素 | ✅ Cookie横幅阻止点击 | element_obscured |
🟢 95% |
| 🖼️ 不可见的iframe | iframe内的元素,在主框架中搜索 | ✅ 缺少iframe上下文 | iframe_context |
🟢 95% |
| 👻 陈旧的AJAX | DOM重新加载前捕获的定位器 | ✅ AJAX后的过时引用 | stale_reference |
🟢 95% |
| ↪️ 无声重定向 | URL重定向301/302 | ✅ 测试通过(检测到陷阱) | -- | ✅ |
| 🚫 禁用按钮 | 元素可见但已禁用 | ✅ 检测到禁用属性 | element_disabled |
🟢 95% |
| 🔤 Unicode正则表达式 | Zinedine 对比 Zinédine |
✅ 正则表达式重音不匹配 | encoding_mismatch |
🟢 95% |
| 🫣 双击 | 同意管理器拦截点击 | ✅ 检测到重叠 | element_obscured |
🟢 95% |
在95%的置信度下,有6/6个正确诊断 --第7次测试通过(无需诊断)。
⚠️ 局限性
[!小心] 测试200多条线路: 发送到AI的上下文被有意截断。 E2E测试应该保持简短——一个场景,一个责任,不超过50行。 除此之外,这是一个设计问题,而不是诊断问题。 在查找失败原因之前,请重新构建测试。
📦 安装
QA Autopilot是一个原生Python模块,可以直接在PyPI上使用:
pip install qa-autopilot
就是这样。没有配置,没有服务器,没有帐户。一条线。
自动 .env 加载:
pip install qa-autopilot[dotenv]
或来源:
git clone https://github.com/julienmerconsulting/qa-autopilot.git
cd qa-autopilot
pip install -e .
先决条件
playwright install chromium
.env 配置
创建 .env 项目根目录下的文件:
# OpenAI (default)
OPENAI_API_KEY=sk-...
# Or DeepSeek
BASE_URL=https://api.deepseek.com
API_KEY=sk-...
QA_MODEL=deepseek-chat
# Or local Ollama (zero cost)
BASE_URL=http://localhost:11434/v1
API_KEY=ollama
QA_MODEL=llama3
⚡ 快速开始
pytest模式(推荐)
在pytest命令中添加一个标志:
pytest tests/ --qa-autopilot -v
就是这样。每次失败的测试都会得到自动的人工智能诊断。
使用HTML报告
pytest tests/ --qa-autopilot --html=qa-reports/report.html --self-contained-html -v
单机模式
python -m qa_autopilot tests/test_checkout.py
python -m qa_autopilot tests/test_login.py::test_auth
python -m qa_autopilot tests/ -k "checkout" --headed
直接导入模式
from qa_autopilot import QAInterceptor
# Inside your test
interceptor = QAInterceptor(page)
interceptor.start()
# ... your test ...
# On failure
diagnosis = interceptor.diagnose(error_message, "test_file.py")
print(diagnosis["root_cause"])
print(diagnosis["category"])
🔍 运作原理
┌─────────────────────────────────────────────────────┐
│ YOUR PLAYWRIGHT TEST │
│ │
│ page.goto("https://example.com") │
│ page.click("#submit") ← FAIL │
│ expect(page).to_have_url(...) │
└──────────────────────┬──────────────────────────────┘
│
┌─────────────▼─────────────┐
│ QA AUTOPILOT HOOK │
│ (listens in parallel) │
└─────────────┬─────────────┘
│
┌──────────────────┼──────────────────┐
▼ ▼ ▼
┌────────┐ ┌──────────┐ ┌───────────┐
│ DOM │ │ NETWORK │ │ CONSOLE │
│Listener│ │ Capture │ │ Capture │
│ (JS) │ │ req/res │ │ err/warn │
└───┬────┘ └────┬─────┘ └─────┬─────┘
│ │ │
└────────────────┼───────────────────┘
│
┌───────────▼───────────┐
│ CONTEXT BUNDLE │
│ code + error + DOM │
│ + network + console │
│ + screenshot (opt) │
└───────────┬───────────┘
│
┌───────────▼───────────┐
│ ONE PROMPT → AI │
│ (12 categories) │
│ diagnosis + fix │
└───────────┬───────────┘
│
┌────────────────┼────────────────┐
▼ ▼ ▼
┌────────┐ ┌───────────┐ ┌──────────┐
│Terminal│ │ JSON │ │ Jira │
│ Output │ │ Report │ │ (if bug) │
└────────┘ └───────────┘ └──────────┘
5级管道
- 透明挂钩 --通过本地事件插入剧作家页面
- 并行捕获 --DOM(注入JS监听器)、网络、控制台、截图
- 故障检测 --pytest钩子拦截
FAILED状态 - 捆绑+提示 --所有上下文都在一次人工智能通话中消失
- 诊断 --根本原因+类别+具体修复+JSON报告
🏷️ 12个诊断类别
| 图标 | 类别 | 描述 |
|---|---|---|
| 🎯 | wrong_selector |
选择器损坏、缺失或太宽 |
| ⏭️ | missing_step |
缺少步骤(Cookie、goto、下拉菜单) |
| ⏱️ | timing |
比赛状态,元素尚未准备就绪 |
| 🫣 | element_obscured |
覆盖层/模态/横幅覆盖的元素 |
| 🚫 | element_disabled |
元素已找到但已禁用 |
| 🔀 | wrong_action |
错误的方法(点击vs dblclick,填充vs类型) |
| 🖼️ | iframe_context |
在错误的帧中搜索元素 |
| 🔤 | encoding_mismatch |
Unicode/重音/正则表达式问题 |
| 👻 | stale_reference |
DOM更改后定位器失效 |
| 📊 | test_data |
预期值错误的断言 |
| 🐛 | app_bug |
应用程序错误(不是测试)→ 生成Jira票证 |
| 🌐 | network |
失败的网络请求(4xx/5xx) |
⚙️ 配置
环境变量
| 变量 | 默认值 | 描述 |
|---|---|---|
OPENAI_API_KEY |
(如果没有API_KEY,则为必填项) | OpenAI API密钥 |
API_KEY |
(可选) | 替代提供商的密钥(DeepSeek、Ollama…) |
BASE_URL |
None (原生OpenAI) |
LLM提供者基本URL |
QA_MODEL |
gpt-4.1-mini |
要使用的AI模型 |
QA_SCREENSHOT |
0 |
1 在提示中包含屏幕截图 |
QA_REPORT_DIR |
qa-reports/ |
报告目录 |
QA_REDACT_INPUTS |
1 |
自动编辑敏感字段(密码、信用卡、令牌、IBAN…)。 0 禁用(不推荐)。 |
pytest参数
pytest tests/ --qa-autopilot # Enable AI diagnosis
pytest tests/ --qa-autopilot --headed # With visible browser
pytest tests/ --qa-autopilot -k "login" # Filter by keyword
📁 间引结构
qa-reports/
├── summary_20260223_014751.json # Consolidated run report
├── diag_test_broken_20260223_014713.json # Individual diagnosis
├── diag_test_broken_20260223_014659.json
├── jira_test_broken_20260223_014659.md # Jira ticket (if app_bug)
└── report.html # pytest HTML report
合并报表示例
[
{
"test": "test_element_covered_by_overlay[chromium]",
"category": "element_obscured",
"confidence": 0.95,
"root_cause": "The target element is covered by the cookie banner",
"suggested_fix": "Close the cookie banner before clicking"
}
]
🔒 安全与GDPR
QA自动驾驶仪 自动地 在任何LLM调用之前编辑敏感数据。这种保护是 默认启用 (QA_REDACT_INPUTS=1)并在 两条战线:
1.浏览器端编校(DOM监听器)
在敏感字段中键入的值被拦截 浏览器内部,在 saveEntry() JS监听器的功能,并替换为 [REDACTED] 之前 任何存储。真正的价值永远不会离开浏览器。
| 检测标准 | 示例 |
|---|---|
HTML type |
password, email, tel |
name/id 包含 |
password, passwd, pwd, secret, token, cvv, card, ssn, auth, pin, api_key, credit, iban, bic, swift, client_secret |
placeholder / aria-label 包含 |
相同的模式 |
autocomplete |
current-password, new-password, cc-* (信用卡) |
2.源代码编辑(LLM调用前)
测试 .py 文件也会被扫描,硬编码的凭据也会被编辑:
page.fill("#password", "...")/.type()/.press_sequentially()/.input_value()关于敏感选择器- Python变量:
password = "...",token = "...",api_key = "...",client_secret = "...",access_token = "..."等等。 os.environ["PASSWORD"] = "..."(直接分配)
当应用编辑时,qa autopilot会打印一条警告:
⚠️ Hardcoded credentials detected in test_login.py, redacted before LLM call.
Best practice: use os.environ or pytest fixtures for secrets.
LLM看到了什么?
CAPTURED DOM ACTIONS (3 total)
1. INPUT ✅ #username = 'john.doe@example.com'
2. INPUT ✅ #password = [REDACTED — sensitive field]
3. CLICK ✅ button[type="submit"] (text: 'Login')
TEST CODE
def test_login(page):
page.fill("#username", "john.doe@example.com")
page.fill("#password", "[REDACTED]")
page.click("button[type='submit']")
LLM在提示中明确获悉 [REDACTED] 做 不 表示空白或损坏的字段,但受GDPR保护。在不知道实际值的情况下进行诊断。
如何验证编辑是否有效?
# Run a test that types a password
pytest tests/test_login.py --qa-autopilot
# Check the JSON report — you should see [REDACTED] everywhere
grep -i "password\|REDACTED" qa-reports/diag_*.json
对于超级偏执狂:用拦截传出流量 mitmproxy 并验证请求 api.openai.com 永远不要包含你真正的敏感价值。
发送到LLM的数据
| 数据 | 已发送 | 可修改 |
|---|---|---|
| 测试源代码(最多3000个字符) | ✅ | ✅ 默认情况下 (fill/assign/env上的正则表达式) |
| 剧作家错误消息 | ✅ | ❌ |
| 元素选择器 | ✅ | ❌ |
| 输入值(DOM监听器) | ✅ | ✅ 默认情况下 (6个标准级联) |
| 页面URL | ✅ | ❌ |
| 控制台错误 | ✅ | ❌ |
| 4xx/5xx请求体(截断) | ✅ | ❌ |
| 截图 | 仅当 QA_SCREENSHOT=1 |
无 |
禁用编辑(不建议)
对于极少数情况,“敏感”字段的内容是合法有用的(对包含 auth 但实际上与认证无关):
QA_REDACT_INPUTS=0 pytest tests/ --qa-autopilot
⚠️ 仅适用于虚构的测试数据。 如有疑问,请保持启用编辑。Redaction不是硬编码凭据的借口:它不能捕捉到每一个奇异的情况(具有发明名称、连接值等的变量)。黄金法则依然存在: 永远不要硬编码秘密.
已知限制——记录的剩余风险
浏览器端侦听器捕获 在实字段中键入的运行时值 无论测试如何处理它们(类型/名称/占位符/aria标签/自动补全)。源端正则表达式捕获了最常见的模式(page.fill("#password", ...), PASSWORD = "...", get_by_label("Password").fill(...), get_by_placeholder("IBAN").fill(...)).然而,有两种特定的模式 测试源中的硬编码凭据 目前尚未被捕获:
| 模式 | 示例 | 状态 |
|---|---|---|
| 来自剧作家codegen的嵌套引用 | page.fill("[name='cvv']", "123") |
跟踪为 xfail 在 tests/test_redaction.py |
页面对象模型——声明为类属性的定位器, .fill() 呼叫其他地方 |
PASSWORD_INPUT = "#password" ... page.fill(PASSWORD_INPUT, secret) |
跟踪为 xfail 在 tests/test_redaction.py |
在这两种情况下 在浏览器中键入的运行时值仍然受到保护 通过浏览器监听器。泄漏仅影响 源代码中硬编码的值 (无论如何,这都是不好的做法——使用 os.environ 或pytest夹具)。这 xfail 测试使剩余风险 可见和跟踪 因此,任何未来的修复都将得到验证。
🏗️ 建筑
qa-autopilot/
├── qa_autopilot/
│ ├── __init__.py # Public exports
│ ├── core.py # QAInterceptor + capture
│ ├── prompt.py # Prompt v2 (12 categories)
│ ├── diagnose.py # AI call + retry + JSON mode
│ ├── reporter.py # JSON + Jira markdown reports
│ ├── listener.js # DOM listener (browser injection)
│ └── plugin.py # pytest hooks
├── tests/
│ ├── test_traps.py # Trap test suite
│ └── conftest.py
├── examples/
│ └── standalone.py # Direct usage example
├── pyproject.toml
├── LICENSE
└── README.md
注: 当前版本是一个整体
qa_autopilot.py文件(约1250行)。 上面的结构是v2的目标。
🆚 为什么没有其他选择?
| QA自动驾驶仪 | 剧作家MCP(23K行) | SaaS(Testim、Mabl…) | |
|---|---|---|---|
| 代码行数 | ~1250 | 23000+ | 已关闭 |
| 安装 | pip install |
MCP服务器+配置 | 帐户+许可证 |
| 配置 | 1个标志 | 32个MCP工具 | 仪表板+集成 |
| 价格 | 免费+OpenAI密钥 | 免费 | 200-500美元/月/用户 |
| 诊断 | 12个类别,95% | 基本 | 可变 |
| 厂商锁定 | 无 | MCP协议 | 总计 |
🛠️ DOM监听器——6层级联
注入浏览器的JavaScript监听器使用6级选择器级联,从最稳定到最不稳定:
| 层级 | 策略 | 示例 |
|---|---|---|
| 1 | data-testid / id / name |
[data-testid="submit-btn"] |
| 2 | aria-label / placeholder / title |
[aria-label="Close"] |
| 3 | href (链接) |
a[href="/checkout"] |
| 4 | 具有稳定属性的父对象 | [data-testid="form"] button |
| 5 | 相关标签(输入) | //label[contains(text(),"Email")]//input |
| 6 | 短CSS+ nth-of-type |
button.primary:nth-of-type(2) |
每个选择器在DOM中都经过唯一性验证。包括Shadow DOM支持。
🤝 贡献者
| 贡献者 | 贡献 |
|---|---|
| 朱利安·梅尔 | 原作者 |
| @ 施瓦巴 | 多提供商法学硕士支持(DeepSeek、Ollama)+中文翻译 |
欢迎投稿——问题、错误报告、拉取请求。
标签: pytest插件·剧作家·剧作家python·ai测试·llm·openai·anthropic·deepseek·ollama·mistral·groq·自愈测试·根本原因分析·测试调试·qa自动化·gdpr·rgpd·数据保护·多llm·开源qa
📄 许可证
麻省理工学院——随心所欲。
由...创建 朱利安·梅尔 --JMer咨询
QA架构师·20多年经验·Katalon欧洲顶级合作伙伴
