AI 自动化测试是噱头还是真能干?从用例生成到自愈脚本,从视觉验证到日志分析------一把拆透大模型在测试全链路上的落地姿势(附实战落地方案)
【摘要】 本文从 AI 自动化测试的落地全链路出发,拆解 LLM 在测试用例生成(需求→Gherkin→Python 用例)、Selenium/Cypress 测试脚本自动编写、失败归因分析三阶段中的实际应用。深入 AI 视觉验证(Playwright + 截图对比替代传统断言)、自愈定位器(基于语义理解动态锁定元素,避免 XPath 失效)的底层原理。包含 LLM 本地部署测试方案选型(Ollama + LLaMA 3 / Qwen 2.5)、Prompt 工程在测试中的三种调试技巧、用例准确性验证策略,以及大模型幻觉导致无效用例、Token 成本控制等踩坑记录。
目录
- [一、AI 自动化测试到底在测什么](#一、AI 自动化测试到底在测什么)
- [二、LLM 在测试三阶段的角色](#二、LLM 在测试三阶段的角色)
- [三、AI 生成测试用例:从需求到可执行脚本](#三、AI 生成测试用例:从需求到可执行脚本)
- [四、自愈定位器:XPath 坏了我自己修](#四、自愈定位器:XPath 坏了我自己修)
- [五、AI 视觉验证:截图对比替代手工断言](#五、AI 视觉验证:截图对比替代手工断言)
- [六、失败归因分析:AI 读日志找根因](#六、失败归因分析:AI 读日志找根因)
- [七、本地 LLM 部署方案选型](#七、本地 LLM 部署方案选型)
- [八、Prompt 工程在测试中的三种调试技巧](#八、Prompt 工程在测试中的三种调试技巧)
- [九、实战:Selenium 脚本 AI 生成全流程](#九、实战:Selenium 脚本 AI 生成全流程)
- [十、实战:Playwright + AI 视觉回归测试](#十、实战:Playwright + AI 视觉回归测试)
- [十一、AI 测试的误区和边界](#十一、AI 测试的误区和边界)
- 十二、总结
一、AI 自动化测试到底在测什么
传统的自动化测试长这样:
#mermaid-svg-oNNcOzKsyVCVOQEl{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-oNNcOzKsyVCVOQEl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-oNNcOzKsyVCVOQEl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-oNNcOzKsyVCVOQEl .error-icon{fill:#552222;}#mermaid-svg-oNNcOzKsyVCVOQEl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oNNcOzKsyVCVOQEl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-oNNcOzKsyVCVOQEl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oNNcOzKsyVCVOQEl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oNNcOzKsyVCVOQEl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-oNNcOzKsyVCVOQEl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oNNcOzKsyVCVOQEl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oNNcOzKsyVCVOQEl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oNNcOzKsyVCVOQEl .marker.cross{stroke:#333333;}#mermaid-svg-oNNcOzKsyVCVOQEl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oNNcOzKsyVCVOQEl p{margin:0;}#mermaid-svg-oNNcOzKsyVCVOQEl .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-oNNcOzKsyVCVOQEl .cluster-label text{fill:#333;}#mermaid-svg-oNNcOzKsyVCVOQEl .cluster-label span{color:#333;}#mermaid-svg-oNNcOzKsyVCVOQEl .cluster-label span p{background-color:transparent;}#mermaid-svg-oNNcOzKsyVCVOQEl .label text,#mermaid-svg-oNNcOzKsyVCVOQEl span{fill:#333;color:#333;}#mermaid-svg-oNNcOzKsyVCVOQEl .node rect,#mermaid-svg-oNNcOzKsyVCVOQEl .node circle,#mermaid-svg-oNNcOzKsyVCVOQEl .node ellipse,#mermaid-svg-oNNcOzKsyVCVOQEl .node polygon,#mermaid-svg-oNNcOzKsyVCVOQEl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oNNcOzKsyVCVOQEl .rough-node .label text,#mermaid-svg-oNNcOzKsyVCVOQEl .node .label text,#mermaid-svg-oNNcOzKsyVCVOQEl .image-shape .label,#mermaid-svg-oNNcOzKsyVCVOQEl .icon-shape .label{text-anchor:middle;}#mermaid-svg-oNNcOzKsyVCVOQEl .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-oNNcOzKsyVCVOQEl .rough-node .label,#mermaid-svg-oNNcOzKsyVCVOQEl .node .label,#mermaid-svg-oNNcOzKsyVCVOQEl .image-shape .label,#mermaid-svg-oNNcOzKsyVCVOQEl .icon-shape .label{text-align:center;}#mermaid-svg-oNNcOzKsyVCVOQEl .node.clickable{cursor:pointer;}#mermaid-svg-oNNcOzKsyVCVOQEl .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-oNNcOzKsyVCVOQEl .arrowheadPath{fill:#333333;}#mermaid-svg-oNNcOzKsyVCVOQEl .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-oNNcOzKsyVCVOQEl .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-oNNcOzKsyVCVOQEl .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oNNcOzKsyVCVOQEl .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-oNNcOzKsyVCVOQEl .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oNNcOzKsyVCVOQEl .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-oNNcOzKsyVCVOQEl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-oNNcOzKsyVCVOQEl .cluster text{fill:#333;}#mermaid-svg-oNNcOzKsyVCVOQEl .cluster span{color:#333;}#mermaid-svg-oNNcOzKsyVCVOQEl div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-oNNcOzKsyVCVOQEl .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-oNNcOzKsyVCVOQEl rect.text{fill:none;stroke-width:0;}#mermaid-svg-oNNcOzKsyVCVOQEl .icon-shape,#mermaid-svg-oNNcOzKsyVCVOQEl .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oNNcOzKsyVCVOQEl .icon-shape p,#mermaid-svg-oNNcOzKsyVCVOQEl .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-oNNcOzKsyVCVOQEl .icon-shape .label rect,#mermaid-svg-oNNcOzKsyVCVOQEl .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oNNcOzKsyVCVOQEl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-oNNcOzKsyVCVOQEl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-oNNcOzKsyVCVOQEl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 人看需求
人写用例
人写脚本
Selenium / Appium
定时跑
人看报告
判断是不是 Bug
AI 进来之后,哪些环节变了?
#mermaid-svg-6NmYsudzoH6XsbfU{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6NmYsudzoH6XsbfU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6NmYsudzoH6XsbfU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6NmYsudzoH6XsbfU .error-icon{fill:#552222;}#mermaid-svg-6NmYsudzoH6XsbfU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6NmYsudzoH6XsbfU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6NmYsudzoH6XsbfU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6NmYsudzoH6XsbfU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6NmYsudzoH6XsbfU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6NmYsudzoH6XsbfU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6NmYsudzoH6XsbfU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6NmYsudzoH6XsbfU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6NmYsudzoH6XsbfU .marker.cross{stroke:#333333;}#mermaid-svg-6NmYsudzoH6XsbfU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6NmYsudzoH6XsbfU p{margin:0;}#mermaid-svg-6NmYsudzoH6XsbfU .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6NmYsudzoH6XsbfU .cluster-label text{fill:#333;}#mermaid-svg-6NmYsudzoH6XsbfU .cluster-label span{color:#333;}#mermaid-svg-6NmYsudzoH6XsbfU .cluster-label span p{background-color:transparent;}#mermaid-svg-6NmYsudzoH6XsbfU .label text,#mermaid-svg-6NmYsudzoH6XsbfU span{fill:#333;color:#333;}#mermaid-svg-6NmYsudzoH6XsbfU .node rect,#mermaid-svg-6NmYsudzoH6XsbfU .node circle,#mermaid-svg-6NmYsudzoH6XsbfU .node ellipse,#mermaid-svg-6NmYsudzoH6XsbfU .node polygon,#mermaid-svg-6NmYsudzoH6XsbfU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6NmYsudzoH6XsbfU .rough-node .label text,#mermaid-svg-6NmYsudzoH6XsbfU .node .label text,#mermaid-svg-6NmYsudzoH6XsbfU .image-shape .label,#mermaid-svg-6NmYsudzoH6XsbfU .icon-shape .label{text-anchor:middle;}#mermaid-svg-6NmYsudzoH6XsbfU .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6NmYsudzoH6XsbfU .rough-node .label,#mermaid-svg-6NmYsudzoH6XsbfU .node .label,#mermaid-svg-6NmYsudzoH6XsbfU .image-shape .label,#mermaid-svg-6NmYsudzoH6XsbfU .icon-shape .label{text-align:center;}#mermaid-svg-6NmYsudzoH6XsbfU .node.clickable{cursor:pointer;}#mermaid-svg-6NmYsudzoH6XsbfU .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6NmYsudzoH6XsbfU .arrowheadPath{fill:#333333;}#mermaid-svg-6NmYsudzoH6XsbfU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6NmYsudzoH6XsbfU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6NmYsudzoH6XsbfU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6NmYsudzoH6XsbfU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6NmYsudzoH6XsbfU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6NmYsudzoH6XsbfU .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6NmYsudzoH6XsbfU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6NmYsudzoH6XsbfU .cluster text{fill:#333;}#mermaid-svg-6NmYsudzoH6XsbfU .cluster span{color:#333;}#mermaid-svg-6NmYsudzoH6XsbfU div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6NmYsudzoH6XsbfU .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6NmYsudzoH6XsbfU rect.text{fill:none;stroke-width:0;}#mermaid-svg-6NmYsudzoH6XsbfU .icon-shape,#mermaid-svg-6NmYsudzoH6XsbfU .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6NmYsudzoH6XsbfU .icon-shape p,#mermaid-svg-6NmYsudzoH6XsbfU .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6NmYsudzoH6XsbfU .icon-shape .label rect,#mermaid-svg-6NmYsudzoH6XsbfU .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6NmYsudzoH6XsbfU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6NmYsudzoH6XsbfU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6NmYsudzoH6XsbfU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 需求文档
★ LLM 生成测试用例
★ LLM 生成脚本骨架
-
人审改
定时跑
★ AI 视觉验证 -
失败归因
人只看 AI 筛选过的
高价值告警
一句话: AI 不是替代整个测试流程,而是把"写用例""写脚本""看报告"这三个最吃人力、最重复的环节自动化了。
| 传统做法 | AI 做法 | 效果 |
|---|---|---|
| 手写 100 条用例 | LLM 从需求生成 100 条,人再审 20 分钟 | 节省 60% 时间 |
| XPath 变化后手动修 | 自愈定位器自动找新位置 | 维护成本降 ~50% |
| 人工对比截图找 UI 差异 | AI 视觉模型自动对比 + 标红 | 回归测试效率翻倍 |
| 失败日志一条条看 | AI 读日志给推断根因 | 定位时间从小时到分钟 |
二、LLM 在测试三阶段的角色
#mermaid-svg-knNdvOhzwCUObXuM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-knNdvOhzwCUObXuM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-knNdvOhzwCUObXuM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-knNdvOhzwCUObXuM .error-icon{fill:#552222;}#mermaid-svg-knNdvOhzwCUObXuM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-knNdvOhzwCUObXuM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-knNdvOhzwCUObXuM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-knNdvOhzwCUObXuM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-knNdvOhzwCUObXuM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-knNdvOhzwCUObXuM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-knNdvOhzwCUObXuM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-knNdvOhzwCUObXuM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-knNdvOhzwCUObXuM .marker.cross{stroke:#333333;}#mermaid-svg-knNdvOhzwCUObXuM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-knNdvOhzwCUObXuM p{margin:0;}#mermaid-svg-knNdvOhzwCUObXuM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-knNdvOhzwCUObXuM .cluster-label text{fill:#333;}#mermaid-svg-knNdvOhzwCUObXuM .cluster-label span{color:#333;}#mermaid-svg-knNdvOhzwCUObXuM .cluster-label span p{background-color:transparent;}#mermaid-svg-knNdvOhzwCUObXuM .label text,#mermaid-svg-knNdvOhzwCUObXuM span{fill:#333;color:#333;}#mermaid-svg-knNdvOhzwCUObXuM .node rect,#mermaid-svg-knNdvOhzwCUObXuM .node circle,#mermaid-svg-knNdvOhzwCUObXuM .node ellipse,#mermaid-svg-knNdvOhzwCUObXuM .node polygon,#mermaid-svg-knNdvOhzwCUObXuM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-knNdvOhzwCUObXuM .rough-node .label text,#mermaid-svg-knNdvOhzwCUObXuM .node .label text,#mermaid-svg-knNdvOhzwCUObXuM .image-shape .label,#mermaid-svg-knNdvOhzwCUObXuM .icon-shape .label{text-anchor:middle;}#mermaid-svg-knNdvOhzwCUObXuM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-knNdvOhzwCUObXuM .rough-node .label,#mermaid-svg-knNdvOhzwCUObXuM .node .label,#mermaid-svg-knNdvOhzwCUObXuM .image-shape .label,#mermaid-svg-knNdvOhzwCUObXuM .icon-shape .label{text-align:center;}#mermaid-svg-knNdvOhzwCUObXuM .node.clickable{cursor:pointer;}#mermaid-svg-knNdvOhzwCUObXuM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-knNdvOhzwCUObXuM .arrowheadPath{fill:#333333;}#mermaid-svg-knNdvOhzwCUObXuM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-knNdvOhzwCUObXuM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-knNdvOhzwCUObXuM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-knNdvOhzwCUObXuM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-knNdvOhzwCUObXuM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-knNdvOhzwCUObXuM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-knNdvOhzwCUObXuM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-knNdvOhzwCUObXuM .cluster text{fill:#333;}#mermaid-svg-knNdvOhzwCUObXuM .cluster span{color:#333;}#mermaid-svg-knNdvOhzwCUObXuM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-knNdvOhzwCUObXuM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-knNdvOhzwCUObXuM rect.text{fill:none;stroke-width:0;}#mermaid-svg-knNdvOhzwCUObXuM .icon-shape,#mermaid-svg-knNdvOhzwCUObXuM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-knNdvOhzwCUObXuM .icon-shape p,#mermaid-svg-knNdvOhzwCUObXuM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-knNdvOhzwCUObXuM .icon-shape .label rect,#mermaid-svg-knNdvOhzwCUObXuM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-knNdvOhzwCUObXuM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-knNdvOhzwCUObXuM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-knNdvOhzwCUObXuM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 阶段三:结果分析
失败截图 + 日志
LLM 分析根因
归类 → 报告
阶段二:测试执行
脚本跑挂了?
自愈定位器尝试修复
重跑
阶段一:测试生成
需求 / PRD
LLM 提取测试点
生成 Gherkin 用例
生成 Python / Java 脚本
三、AI 生成测试用例:从需求到可执行脚本
原始需求示例(一段手写的 PRD 描述):
用户可以在搜索框输入关键词,点击搜索按钮后,页面展示匹配结果列表。如果无匹配结果,显示"未找到相关内容"提示。搜索框为空时按钮置灰不可点击。
Step 1:让 LLM 生成 Gherkin(BDD 风格用例):
gherkin
Feature: 搜索功能
Scenario: 正常搜索有结果
Given 用户打开搜索页面
When 用户在搜索框中输入 "Python"
And 用户点击搜索按钮
Then 页面展示包含 "Python" 的结果列表
Scenario: 搜索无结果时显示提示
Given 用户打开搜索页面
When 用户在搜索框中输入 "xyz不存在的词"
And 用户点击搜索按钮
Then 页面显示 "未找到相关内容"
Scenario: 搜索框为空时按钮禁用
Given 用户打开搜索页面
When 搜索框为空
Then 搜索按钮处于禁用状态,不可点击
Step 2:LLM 直接把 Gherkin 转成 Python + Playwright 脚本:
python
from playwright.sync_api import sync_playwright, expect
def test_search_has_results():
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://example.com/search")
# 输入关键词
page.fill("#search-input", "Python")
# 点击搜索
page.click("#search-btn")
# 验证结果列表包含关键字
expect(page.locator(".result-item").first).to_contain_text("Python")
browser.close()
def test_empty_search_disables_button():
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://example.com/search")
# 验证搜索框为空时按钮 disabled
expect(page.locator("#search-btn")).to_be_disabled()
browser.close()
手写这两条用例大约 10-15 分钟,AI 从需求到可执行脚本 20 秒。剩下的时间人只需要审一遍逻辑。
生成用例的质量保障策略:
| 验证层 | 做法 |
|---|---|
| 格式检查 | 脚本能不能跑起来(语法错误 LLM 自己很少犯) |
| 逻辑审查 | 人看断言对不对(AI 可能把"包含"写成"等于") |
| 数据真实化 | 把 LLM 编的假数据换成业务真实数据 |
| 边界补充 | LLM 一般给正常流程,你补边界:空串、超长、注入 |
四、自愈定位器:XPath 坏了我自己修
传统自动化测试里,维护成本最高的就是定位器。前端改个 CSS 类名,你的 //div[@class='search-wrapper'] 就挂了。
自愈定位器的思路:
#mermaid-svg-amkHIRkJfPIgV1Og{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-amkHIRkJfPIgV1Og .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-amkHIRkJfPIgV1Og .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-amkHIRkJfPIgV1Og .error-icon{fill:#552222;}#mermaid-svg-amkHIRkJfPIgV1Og .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-amkHIRkJfPIgV1Og .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-amkHIRkJfPIgV1Og .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-amkHIRkJfPIgV1Og .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-amkHIRkJfPIgV1Og .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-amkHIRkJfPIgV1Og .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-amkHIRkJfPIgV1Og .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-amkHIRkJfPIgV1Og .marker{fill:#333333;stroke:#333333;}#mermaid-svg-amkHIRkJfPIgV1Og .marker.cross{stroke:#333333;}#mermaid-svg-amkHIRkJfPIgV1Og svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-amkHIRkJfPIgV1Og p{margin:0;}#mermaid-svg-amkHIRkJfPIgV1Og .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-amkHIRkJfPIgV1Og .cluster-label text{fill:#333;}#mermaid-svg-amkHIRkJfPIgV1Og .cluster-label span{color:#333;}#mermaid-svg-amkHIRkJfPIgV1Og .cluster-label span p{background-color:transparent;}#mermaid-svg-amkHIRkJfPIgV1Og .label text,#mermaid-svg-amkHIRkJfPIgV1Og span{fill:#333;color:#333;}#mermaid-svg-amkHIRkJfPIgV1Og .node rect,#mermaid-svg-amkHIRkJfPIgV1Og .node circle,#mermaid-svg-amkHIRkJfPIgV1Og .node ellipse,#mermaid-svg-amkHIRkJfPIgV1Og .node polygon,#mermaid-svg-amkHIRkJfPIgV1Og .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-amkHIRkJfPIgV1Og .rough-node .label text,#mermaid-svg-amkHIRkJfPIgV1Og .node .label text,#mermaid-svg-amkHIRkJfPIgV1Og .image-shape .label,#mermaid-svg-amkHIRkJfPIgV1Og .icon-shape .label{text-anchor:middle;}#mermaid-svg-amkHIRkJfPIgV1Og .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-amkHIRkJfPIgV1Og .rough-node .label,#mermaid-svg-amkHIRkJfPIgV1Og .node .label,#mermaid-svg-amkHIRkJfPIgV1Og .image-shape .label,#mermaid-svg-amkHIRkJfPIgV1Og .icon-shape .label{text-align:center;}#mermaid-svg-amkHIRkJfPIgV1Og .node.clickable{cursor:pointer;}#mermaid-svg-amkHIRkJfPIgV1Og .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-amkHIRkJfPIgV1Og .arrowheadPath{fill:#333333;}#mermaid-svg-amkHIRkJfPIgV1Og .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-amkHIRkJfPIgV1Og .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-amkHIRkJfPIgV1Og .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-amkHIRkJfPIgV1Og .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-amkHIRkJfPIgV1Og .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-amkHIRkJfPIgV1Og .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-amkHIRkJfPIgV1Og .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-amkHIRkJfPIgV1Og .cluster text{fill:#333;}#mermaid-svg-amkHIRkJfPIgV1Og .cluster span{color:#333;}#mermaid-svg-amkHIRkJfPIgV1Og div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-amkHIRkJfPIgV1Og .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-amkHIRkJfPIgV1Og rect.text{fill:none;stroke-width:0;}#mermaid-svg-amkHIRkJfPIgV1Og .icon-shape,#mermaid-svg-amkHIRkJfPIgV1Og .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-amkHIRkJfPIgV1Og .icon-shape p,#mermaid-svg-amkHIRkJfPIgV1Og .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-amkHIRkJfPIgV1Og .icon-shape .label rect,#mermaid-svg-amkHIRkJfPIgV1Og .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-amkHIRkJfPIgV1Og .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-amkHIRkJfPIgV1Og .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-amkHIRkJfPIgV1Og :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 找到了
没找到
脚本执行
元素找到了?
继续执行
★ 收集当前页面 DOM 快照
传给 LLM
给原始定位器 + 当前 DOM
LLM 分析语义
找出最可能的替代元素
生成新定位器
自动更新测试脚本
重试执行
LLM 自愈定位器的 Prompt 示例:
原始定位器:#search-btn 未找到。
当前页面 DOM 摘要:
<button class="search-submit" id="search-submit-btn" data-testid="search-trigger">
搜索
</button>
请找出对应的新定位器,返回 JSON:
{"strategy": "css", "value": "..."}
LLM 返回:
json
{"strategy": "css", "value": "button[data-testid='search-trigger']"}
脚本自动把定位器换成新的,重新执行------整个过程不需要人介入。
自愈成功率(实测数据参考):
| 场景 | 成功率 | 说明 |
|---|---|---|
| 单纯换 CSS 类名 | ~95% | LLM 语义匹配很强 |
| 整个 DOM 结构重构 | ~40% | 变化太大,定位器没语义锚点 |
| 加个 data-testid | ~100% | 最稳定的方式 |
五、AI 视觉验证:截图对比替代手工断言
有些东西不适合用定位器断言------比如"这个按钮的 icon 有没有被截断""这个页面的整体 UI 有没有变形"。
传统做法: 人工截图对比,肉眼找差异。
AI 做法:
#mermaid-svg-9u2andP4jUiPXOxq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9u2andP4jUiPXOxq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9u2andP4jUiPXOxq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9u2andP4jUiPXOxq .error-icon{fill:#552222;}#mermaid-svg-9u2andP4jUiPXOxq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9u2andP4jUiPXOxq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9u2andP4jUiPXOxq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9u2andP4jUiPXOxq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9u2andP4jUiPXOxq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9u2andP4jUiPXOxq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9u2andP4jUiPXOxq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9u2andP4jUiPXOxq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9u2andP4jUiPXOxq .marker.cross{stroke:#333333;}#mermaid-svg-9u2andP4jUiPXOxq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9u2andP4jUiPXOxq p{margin:0;}#mermaid-svg-9u2andP4jUiPXOxq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9u2andP4jUiPXOxq .cluster-label text{fill:#333;}#mermaid-svg-9u2andP4jUiPXOxq .cluster-label span{color:#333;}#mermaid-svg-9u2andP4jUiPXOxq .cluster-label span p{background-color:transparent;}#mermaid-svg-9u2andP4jUiPXOxq .label text,#mermaid-svg-9u2andP4jUiPXOxq span{fill:#333;color:#333;}#mermaid-svg-9u2andP4jUiPXOxq .node rect,#mermaid-svg-9u2andP4jUiPXOxq .node circle,#mermaid-svg-9u2andP4jUiPXOxq .node ellipse,#mermaid-svg-9u2andP4jUiPXOxq .node polygon,#mermaid-svg-9u2andP4jUiPXOxq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9u2andP4jUiPXOxq .rough-node .label text,#mermaid-svg-9u2andP4jUiPXOxq .node .label text,#mermaid-svg-9u2andP4jUiPXOxq .image-shape .label,#mermaid-svg-9u2andP4jUiPXOxq .icon-shape .label{text-anchor:middle;}#mermaid-svg-9u2andP4jUiPXOxq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9u2andP4jUiPXOxq .rough-node .label,#mermaid-svg-9u2andP4jUiPXOxq .node .label,#mermaid-svg-9u2andP4jUiPXOxq .image-shape .label,#mermaid-svg-9u2andP4jUiPXOxq .icon-shape .label{text-align:center;}#mermaid-svg-9u2andP4jUiPXOxq .node.clickable{cursor:pointer;}#mermaid-svg-9u2andP4jUiPXOxq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9u2andP4jUiPXOxq .arrowheadPath{fill:#333333;}#mermaid-svg-9u2andP4jUiPXOxq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9u2andP4jUiPXOxq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9u2andP4jUiPXOxq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9u2andP4jUiPXOxq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9u2andP4jUiPXOxq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9u2andP4jUiPXOxq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9u2andP4jUiPXOxq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9u2andP4jUiPXOxq .cluster text{fill:#333;}#mermaid-svg-9u2andP4jUiPXOxq .cluster span{color:#333;}#mermaid-svg-9u2andP4jUiPXOxq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9u2andP4jUiPXOxq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9u2andP4jUiPXOxq rect.text{fill:none;stroke-width:0;}#mermaid-svg-9u2andP4jUiPXOxq .icon-shape,#mermaid-svg-9u2andP4jUiPXOxq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9u2andP4jUiPXOxq .icon-shape p,#mermaid-svg-9u2andP4jUiPXOxq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9u2andP4jUiPXOxq .icon-shape .label rect,#mermaid-svg-9u2andP4jUiPXOxq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9u2andP4jUiPXOxq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9u2andP4jUiPXOxq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9u2andP4jUiPXOxq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 基线截图
(正确 UI)
多模态 LLM
GPT-4V / Qwen-VL
当前截图
(被测 UI)
返回差异报告
文字描述 + 差异区域坐标
自动判定
Pass / Fail
Playwright + AI 视觉验证代码示例:
python
from playwright.sync_api import sync_playwright
import base64
def ai_visual_check(page, baseline_path, current_path, model):
# 1. 截图当前页面
page.screenshot(path=current_path, full_page=True)
# 2. 把两张图编码成 base64 喂给多模态 LLM
with open(baseline_path, 'rb') as f:
baseline_b64 = base64.b64encode(f.read()).decode()
with open(current_path, 'rb') as f:
current_b64 = base64.b64encode(f.read()).decode()
# 3. 调多模态 API 比较
prompt = """
比较基线截图和当前截图,检查:
1. 布局有没有位移或变形
2. 文字有没有截断或重叠
3. 颜色/间距有没有明显差异
4. 按钮/输入框有没有缺失
返回 JSON:
{"passed": bool, "issues": [{"desc": "...", "severity": "high|medium|low"}]}
"""
# response = call_vision_llm(prompt, baseline_b64, current_b64)
# return response
适用场景 vs 不适用场景:
| 适用 AI 视觉验证 | 不适用(用传统断言) |
|---|---|
| UI 整体布局回归 | 数值精确校验 |
| icon / 图片渲染 | API 返回数据断言 |
| 多分辨率适配 | 数据库一致性检查 |
| 暗黑模式切换 | 性能指标(响应时间) |
六、失败归因分析:AI 读日志找根因
跑了几百条用例挂了 10 条,传统做法是一条条点开报告、看截图、翻日志------半小时起步。
AI 做法------把失败日志和截图丢给 LLM,让它做第一轮归因:
python
def analyze_failure(test_name, error_log, screenshot_b64):
prompt = f"""
测试用例「{test_name}」失败。
错误日志:
{error_log}
请分析根因,输出 JSON:
{{
"root_cause": "元素未找到 | 超时 | API 错误 | 数据问题 | 环境问题 | 脚本错误",
"confidence": 0.0~1.0,
"explanation": "中文解释",
"suggested_fix": "修复建议"
}}
"""
# response = call_llm(prompt)
# return response
AI 归因分类长这样:
| 根因分类 | 示例 |
|---|---|
| 元素未找到 | 前端改了 DOM,定位器失效 |
| 超时 | 页面加载变慢,timeout 不够 |
| API 错误 | 后端返回 500,前端没兜住 |
| 数据问题 | 测试账号过期 / 订单数据被清 |
| 环境问题 | 测试环境挂了 / 网络不通 |
| 脚本错误 | 参数传错 / 断言写错 |
七、本地 LLM 部署方案选型
敏感数据不能上 OpenAI------那就本地跑:
| 方案 | 模型 | 硬件需求 | 适用 |
|---|---|---|---|
| Ollama + Qwen 2.5 (7B) | 通义千问 | 16GB 内存 / 无 GPU 也能跑 | 用例生成 |
| Ollama + LLaMA 3.1 (8B) | Meta | 同上 | 脚本生成 |
| vLLM + Qwen 2.5 (32B) | 通义千问 | 24GB+ 显存 | 视觉验证 |
| OpenAI API (GPT-4o) | OpenAI | 无本地硬件需求 | 效果最好但数据可能泄露 |
本地部署命令(Ollama 一行搞定):
bash
# 安装 Ollama
curl -fsSL https://ollama.com/install.sh | sh
# 拉 Qwen 2.5 7B
ollama pull qwen2.5:7b
# 本地跑起来
ollama run qwen2.5:7b
八、Prompt 工程在测试中的三种调试技巧
技巧一:给出正例 + 反例
text
生成测试用例时:
- 正例:正常登录成功
- 反例:密码错误登录失败
- 边界:密码为空点击登录按钮
技巧二:约束输出格式
输出格式要求:
每条用例一行,格式为 [模块]-[场景]-[预期结果]
不要输出 Markdown 表格,直接输出纯文本列表。
技巧三:让 LLM 自己先审一遍
先根据需求生成 5 条测试用例,然后你自己检查一遍:
1. 有没有重复的用例?
2. 有没有遗漏的需求点?
3. 有没有不合理的预期?
检查完成后,把修正后的用例单独输出在"---"分隔线下方。
九、实战:Selenium 脚本 AI 生成全流程
完整流程------从需求到可跑脚本:
python
# 模拟一条完整的 AI 生成链路
# 输入:一段自然语言需求
# 输出:可执行的 Selenium 脚本
import openai
requirement = """
登录页面:
1. 正确用户名 + 正确密码 → 跳转到首页
2. 正确用户名 + 错误密码 → 提示"密码错误",停留在登录页
3. 连续 3 次密码错误 → 锁定账户 15 分钟,提示"账户已锁定"
"""
prompt = f"""
根据以下需求生成 Python Selenium 测试脚本:
{requirement}
要求:
- 使用 pytest 框架
- 显式等待用 WebDriverWait + expected_conditions
- 每个场景一个 test_ 函数
- 代码注释用中文
"""
# response = openai.ChatCompletion.create(
# model="gpt-4o",
# messages=[{"role": "user", "content": prompt}]
# )
AI 生成的脚本(审改后):
python
import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
@pytest.fixture
def driver():
d = webdriver.Chrome()
d.get("http://localhost:3000/login")
yield d
d.quit()
def test_login_success(driver):
# 输入正确的用户名和密码
driver.find_element(By.ID, "username").send_keys("admin")
driver.find_element(By.ID, "password").send_keys("pass123")
driver.find_element(By.ID, "login-btn").click()
# 验证跳转到首页
WebDriverWait(driver, 10).until(
EC.url_contains("/home")
)
def test_login_wrong_password(driver):
driver.find_element(By.ID, "username").send_keys("admin")
driver.find_element(By.ID, "password").send_keys("wrong123")
driver.find_element(By.ID, "login-btn").click()
# 验证错误提示
error_msg = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.CLASS_NAME, "error-msg"))
)
assert "密码错误" in error_msg.text
# 验证没有跳转
assert "/login" in driver.current_url
def test_account_lockout(driver):
# 连续 3 次输入错误密码
for _ in range(3):
driver.find_element(By.ID, "username").clear()
driver.find_element(By.ID, "password").clear()
driver.find_element(By.ID, "username").send_keys("admin")
driver.find_element(By.ID, "password").send_keys("wrong123")
driver.find_element(By.ID, "login-btn").click()
# 验证锁定提示
lock_msg = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.CLASS_NAME, "error-msg"))
)
assert "账户已锁定" in lock_msg.text
这段脚本 AI 生成 20 秒,人审改 2 分钟。审改主要做的:把 AI 编的假元素 ID 换成真实 DOM 中的 ID。
十、实战:Playwright + AI 视觉回归测试
python
from playwright.sync_api import sync_playwright
import json
def visual_regression_with_ai():
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("http://localhost:3000/dashboard")
# 截图基线(第一次跑时存下来)
baseline = "baseline_dashboard.png"
current = "current_dashboard.png"
# page.screenshot(path=baseline, full_page=True) # 第一次
page.screenshot(path=current, full_page=True)
# 传给 AI 视觉模型对比
# result = ai_visual_diff(baseline, current)
# 预期返回:
# {
# "passed": false,
# "differences": [
# {"area": "右上角用户头像",
# "desc": "头像从圆形变成了方形,圆角丢失",
# "severity": "low"},
# {"area": "左侧导航栏",
# "desc": "导航栏整体宽度增加了约 15px",
# "severity": "medium"}
# ]
# }
browser.close()
十一、AI 测试的误区和边界
误区一:AI 可以完全替代测试人员
不能。AI 生成用例的质量取决于需求写得多清楚。需求里没写的边界条件,AI 不会主动补。而且 AI 不知道业务上下文------它不知道哪些字段跟钱相关,哪些场景出了问题会赔钱。
误区二:用例越多越好
LLM 可以一口气吐出 200 条用例------但里面有 30% 是废话或重复。审改成本不能省。
误区三:API 调一次就完事了
#mermaid-svg-SQ1BIwSvSxJyJmUu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SQ1BIwSvSxJyJmUu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SQ1BIwSvSxJyJmUu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SQ1BIwSvSxJyJmUu .error-icon{fill:#552222;}#mermaid-svg-SQ1BIwSvSxJyJmUu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SQ1BIwSvSxJyJmUu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SQ1BIwSvSxJyJmUu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SQ1BIwSvSxJyJmUu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SQ1BIwSvSxJyJmUu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SQ1BIwSvSxJyJmUu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SQ1BIwSvSxJyJmUu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SQ1BIwSvSxJyJmUu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SQ1BIwSvSxJyJmUu .marker.cross{stroke:#333333;}#mermaid-svg-SQ1BIwSvSxJyJmUu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SQ1BIwSvSxJyJmUu p{margin:0;}#mermaid-svg-SQ1BIwSvSxJyJmUu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-SQ1BIwSvSxJyJmUu .cluster-label text{fill:#333;}#mermaid-svg-SQ1BIwSvSxJyJmUu .cluster-label span{color:#333;}#mermaid-svg-SQ1BIwSvSxJyJmUu .cluster-label span p{background-color:transparent;}#mermaid-svg-SQ1BIwSvSxJyJmUu .label text,#mermaid-svg-SQ1BIwSvSxJyJmUu span{fill:#333;color:#333;}#mermaid-svg-SQ1BIwSvSxJyJmUu .node rect,#mermaid-svg-SQ1BIwSvSxJyJmUu .node circle,#mermaid-svg-SQ1BIwSvSxJyJmUu .node ellipse,#mermaid-svg-SQ1BIwSvSxJyJmUu .node polygon,#mermaid-svg-SQ1BIwSvSxJyJmUu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SQ1BIwSvSxJyJmUu .rough-node .label text,#mermaid-svg-SQ1BIwSvSxJyJmUu .node .label text,#mermaid-svg-SQ1BIwSvSxJyJmUu .image-shape .label,#mermaid-svg-SQ1BIwSvSxJyJmUu .icon-shape .label{text-anchor:middle;}#mermaid-svg-SQ1BIwSvSxJyJmUu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-SQ1BIwSvSxJyJmUu .rough-node .label,#mermaid-svg-SQ1BIwSvSxJyJmUu .node .label,#mermaid-svg-SQ1BIwSvSxJyJmUu .image-shape .label,#mermaid-svg-SQ1BIwSvSxJyJmUu .icon-shape .label{text-align:center;}#mermaid-svg-SQ1BIwSvSxJyJmUu .node.clickable{cursor:pointer;}#mermaid-svg-SQ1BIwSvSxJyJmUu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-SQ1BIwSvSxJyJmUu .arrowheadPath{fill:#333333;}#mermaid-svg-SQ1BIwSvSxJyJmUu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-SQ1BIwSvSxJyJmUu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-SQ1BIwSvSxJyJmUu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SQ1BIwSvSxJyJmUu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-SQ1BIwSvSxJyJmUu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SQ1BIwSvSxJyJmUu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-SQ1BIwSvSxJyJmUu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-SQ1BIwSvSxJyJmUu .cluster text{fill:#333;}#mermaid-svg-SQ1BIwSvSxJyJmUu .cluster span{color:#333;}#mermaid-svg-SQ1BIwSvSxJyJmUu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-SQ1BIwSvSxJyJmUu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-SQ1BIwSvSxJyJmUu rect.text{fill:none;stroke-width:0;}#mermaid-svg-SQ1BIwSvSxJyJmUu .icon-shape,#mermaid-svg-SQ1BIwSvSxJyJmUu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SQ1BIwSvSxJyJmUu .icon-shape p,#mermaid-svg-SQ1BIwSvSxJyJmUu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-SQ1BIwSvSxJyJmUu .icon-shape .label rect,#mermaid-svg-SQ1BIwSvSxJyJmUu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SQ1BIwSvSxJyJmUu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-SQ1BIwSvSxJyJmUu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-SQ1BIwSvSxJyJmUu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 没
全了
需求 → LLM → 用例
人审改
跑一遍
看覆盖率
覆盖全了?
补需求 → 再生成
入库
成本边界------自己心里有个数:
| 调用方式 | 成本估算 | 适合 |
|---|---|---|
| OpenAI GPT-4o API | ~$0.0025 / 用例 | 小批量用 |
| 本地 Qwen 2.5 7B | 电费,几乎免费 | 大批量日常跑 |
| 开源视觉模型 | GPU 占用 ~8GB 显存 | 视觉回归 |
十二、总结
AI 测试全链路一览:
#mermaid-svg-m6n3sqGZCDRqstfk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-m6n3sqGZCDRqstfk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-m6n3sqGZCDRqstfk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-m6n3sqGZCDRqstfk .error-icon{fill:#552222;}#mermaid-svg-m6n3sqGZCDRqstfk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-m6n3sqGZCDRqstfk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-m6n3sqGZCDRqstfk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-m6n3sqGZCDRqstfk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-m6n3sqGZCDRqstfk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-m6n3sqGZCDRqstfk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-m6n3sqGZCDRqstfk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-m6n3sqGZCDRqstfk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-m6n3sqGZCDRqstfk .marker.cross{stroke:#333333;}#mermaid-svg-m6n3sqGZCDRqstfk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-m6n3sqGZCDRqstfk p{margin:0;}#mermaid-svg-m6n3sqGZCDRqstfk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-m6n3sqGZCDRqstfk .cluster-label text{fill:#333;}#mermaid-svg-m6n3sqGZCDRqstfk .cluster-label span{color:#333;}#mermaid-svg-m6n3sqGZCDRqstfk .cluster-label span p{background-color:transparent;}#mermaid-svg-m6n3sqGZCDRqstfk .label text,#mermaid-svg-m6n3sqGZCDRqstfk span{fill:#333;color:#333;}#mermaid-svg-m6n3sqGZCDRqstfk .node rect,#mermaid-svg-m6n3sqGZCDRqstfk .node circle,#mermaid-svg-m6n3sqGZCDRqstfk .node ellipse,#mermaid-svg-m6n3sqGZCDRqstfk .node polygon,#mermaid-svg-m6n3sqGZCDRqstfk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-m6n3sqGZCDRqstfk .rough-node .label text,#mermaid-svg-m6n3sqGZCDRqstfk .node .label text,#mermaid-svg-m6n3sqGZCDRqstfk .image-shape .label,#mermaid-svg-m6n3sqGZCDRqstfk .icon-shape .label{text-anchor:middle;}#mermaid-svg-m6n3sqGZCDRqstfk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-m6n3sqGZCDRqstfk .rough-node .label,#mermaid-svg-m6n3sqGZCDRqstfk .node .label,#mermaid-svg-m6n3sqGZCDRqstfk .image-shape .label,#mermaid-svg-m6n3sqGZCDRqstfk .icon-shape .label{text-align:center;}#mermaid-svg-m6n3sqGZCDRqstfk .node.clickable{cursor:pointer;}#mermaid-svg-m6n3sqGZCDRqstfk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-m6n3sqGZCDRqstfk .arrowheadPath{fill:#333333;}#mermaid-svg-m6n3sqGZCDRqstfk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-m6n3sqGZCDRqstfk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-m6n3sqGZCDRqstfk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-m6n3sqGZCDRqstfk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-m6n3sqGZCDRqstfk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-m6n3sqGZCDRqstfk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-m6n3sqGZCDRqstfk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-m6n3sqGZCDRqstfk .cluster text{fill:#333;}#mermaid-svg-m6n3sqGZCDRqstfk .cluster span{color:#333;}#mermaid-svg-m6n3sqGZCDRqstfk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-m6n3sqGZCDRqstfk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-m6n3sqGZCDRqstfk rect.text{fill:none;stroke-width:0;}#mermaid-svg-m6n3sqGZCDRqstfk .icon-shape,#mermaid-svg-m6n3sqGZCDRqstfk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-m6n3sqGZCDRqstfk .icon-shape p,#mermaid-svg-m6n3sqGZCDRqstfk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-m6n3sqGZCDRqstfk .icon-shape .label rect,#mermaid-svg-m6n3sqGZCDRqstfk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-m6n3sqGZCDRqstfk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-m6n3sqGZCDRqstfk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-m6n3sqGZCDRqstfk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 分析
执行
生成
需求 → LLM → 用例
用例 → LLM → 脚本
定时跑脚本
自愈定位器兜底
失败截图 + 日志
LLM 归因
分类成报告
核心速查:
| 你要做什么 | 推荐工具 / 方案 |
|---|---|
| 从需求生成用例 | GPT-4o / 本地 Qwen 2.5 |
| 用例生成脚本 | LLM + Playwright / Selenium |
| 定位器自愈 | LLM 语义匹配 DOM |
| 视觉回归 | GPT-4V / Qwen-VL 截图对比 |
| 失败归因 | LLM 读日志 + 截图 |
| 本地部署 | Ollama + Qwen 2.5 7B |
一句话总结: AI 在测试里的正确使用姿势是------它写初稿,你来审改。用例生成、脚本骨架、日志分析这些最耗时间的脏活给 AI,业务判断、边界补充、质量把关留给人。现阶段别指望 AI 全自动------但它帮你省掉 50%~60% 的重复劳动是完全做得到的。