☰
OMP 与 Playwright 进行 Web 自动化测试的完整流程
2026/10/23 2:52:11 网站建设 项目流程

本文基于 LLM Proxy 项目(FastAPI 单进程单端口 + Vue3 管理台)的真实实践:从零搭建 Playwright E2E 工程,
按 OpenSpec 需求规格驱动编写 66 个用例,全部自动化运行、失败自动取证、与规格逐条对账判定产品行为一致性,
最终 66/66 通过并归档。文中所有命令、坑位与修复均来自实际过程。

1. 背景与目标

被测试系统:

  • 后端:FastAPI + uvicorn,单进程单端口(3002),网关面/v1/*+ 管理 API/api/v1/admin/*+ 托管 Vue3 管理台(SPA 兜底)
  • 前端:Vue3 + TS + Element Plus + ECharts
  • 需求基线:OpenSpec 规格(openspec/specs/下 8 个 capability,每个 capability 由若干 Requirement + Scenario 组成,MUST/MUST NOT 措辞)

目标:

  1. 可视化:能自动打开 Playwright 自带的 Chromium,看到真实的鼠标点击、输入、页面跳转
  2. 自动化:一条命令完成「拉起服务 → 启动浏览器 → 登录 → 逐页测试 → 失败取证 → 回收进程」
  3. 规格对账:测试用例与 OpenSpec 的 Requirement/Scenario 一一对应,测试结果能回答"产品行为是否符合需求规格"

2. OMP 中需要安装什么

2.1 Node.js 运行时

Playwright 测试运行器要求 Node 18+(本机实测 Node v24 正常)。OMP 的 bash/eval 工具链与 Node 无额外耦合。

2.2 Playwright 测试框架 + 浏览器

在仓库根目录(而不是web/前端工程里)建独立 e2e 工程,与前端构建解耦:

# 根目录 package.json(若无则新建),添加:npminstall-D@playwright/test--registry=https://registry.npmmirror.com# 安装 Chromium 浏览器(国内镜像,避免官方 CDN 不可达)# Windows PowerShell:$env:PLAYWRIGHT_DOWNLOAD_HOST="https://cdn.npmmirror.com/binaries/playwright"npx playwrightinstallchromium

镜像参数只在本机网络受限时需要;网络正常时npm i -D @playwright/test && npx playwright install chromium即可。
node_modules/已被 .gitignore 覆盖,浏览器二进制在用户目录,均不入库。

package.json的 scripts(本次工程的全部命令面):

{"scripts":{"test:e2e":"playwright test","test:e2e:headed":"playwright test --headed","test:e2e:ui":"playwright test --ui","test:e2e:report":"playwright show-report","test:e2e:install":"playwright install chromium"}}

2.3 OMP 侧的能力(无需安装,内置)

OMP 提供三类互补能力,Playwright 是主力,其余用于辅助:

能力用途
browser 设备(内置 Chromium 驱动)手工走查、抓页面 a11y 快照、截图取证;用于"先人工看一遍"与失败后定位(如本次用tab.observe()拿到收起态菜单的真实 DOM 结构再决定定位器)
e2e-testing 技能 / e2e-runner 代理页面导航、断言、失败隔离的通用模式参考
task 子代理 / eval 内核并行读规格文件、跑对比脚本(如 delta 规格与主规格的需求/场景计数比对)

2.4 关键结论

OMP 本身"自带浏览器"(browser 设备),但成体系的回归测试必须落在 Playwright 上:
browser 设备适合交互式取证(单次会话、即时观察),Playwright 适合可重复、可 CI、可留证(截图/trace/视频)的正式测试资产。
两者分工:browser 设备做探索与定位,Playwright 做回归与验收。

3. 工程结构与配置

llmproxy/ ├── playwright.config.ts # 全局配置 ├── package.json # 仅 e2e 依赖与脚本(与 web/ 解耦) └── tests/e2e/ ├── auth.ts # admin fixture:API 登录取 JWT → addInitScript 注入 localStorage ├── helpers.ts # API helper:建/删用户、建/删 key、热设置读改恢复、实时查询窗口 ├── login.spec.ts # 登录 UI 真实点击(成功/错误密码/限流 429) ├── admin-pages.spec.ts # 10 个管理页渲染冒烟 ├── api.spec.ts # 网关面/管理面接口契约冒烟 ├── gateway-e2e.spec.ts # 真实上游全链路(含全量审计归档验证) └── specs/ # 8 个 OpenSpec capability 的规格驱动 spec ├── user-auth.spec.ts ├── user-management.spec.ts ├── traffic-stats.spec.ts ├── dashboard-user-usage.spec.ts ├── request-audit.spec.ts ├── full-client-audit.spec.ts ├── dark-mode.spec.ts └── menu-collapse.spec.ts

playwright.config.ts核心(决定"一条命令全自动"的关键):

exportdefaultdefineConfig({testDir:'./tests/e2e',timeout:30_000,retries:2,workers:1,// 串行:用例共享真实 DB/限流桶/热设置,保证确定性reporter:[['list'],['html',{open:'never'}]],use:{baseURL:'http://127.0.0.1:3002',trace:'on-first-retry',// 首次失败后重试自动录 tracescreenshot:'on',},webServer:{// 服务未运行则自动拉起;跑完自动回收;已运行则复用command:process.platform==='win32'?'venv\\Scripts\\uvicorn.exe main:app --port 3002':'venv/bin/uvicorn main:app --port 3002',url:'http://127.0.0.1:3002',reuseExistingServer:true,timeout:60_000,},})

两个实践要点:

  1. webServer 平台条件命令:Windows 的 cmd 不认正斜杠相对路径(venv/bin/uvicorn直接报
    'venv' is not recognized)。之前该问题被reuseExistingServer掩盖(服务一直在跑),
    一旦服务未起、Playwright 自己拉起就暴露——webServer 的拉起路径必须单独验证过。
  2. workers: 1是规格对账的前提:登录限流桶、热设置、审计归档目标都是全局单例状态,
    并发用例会互相污染(本次实测:限流用例把 429 桶灌满后,后续同文件用例全部 429)。

4. 怎么自动启动 Chromium(三种模式 + 服务联动)

模式命令效果
无头(CI/默认)npm run test:e2e不显示窗口,速度最快
有头(看操作)npm run test:e2e:headed自动打开 Chromium 窗口,全程可见鼠标点击/输入/跳转
UI 模式(交互式调试)npm run test:e2e:uiPlaywright 可视化面板:单步执行、断点、逐用例重跑、查看每步的网络请求/DOM/截图
手动录制npx playwright codegen http://127.0.0.1:3002打开受控 Chromium,你手工点,它实时生成定位器代码

"自动"的完整链条(一条npm run test:e2e:headed触发):

npm script └─ playwright test ├─ webServer: 探测 3002 端口 │ ├─ 未运行 → spawn uvicorn(平台条件命令)→ 等待 URL 可达 │ └─ 已运行 → 复用(reuseExistingServer) ├─ 按 workers:1 串行执行 spec │ └─ 每个 test 自动分配一个 Chromium 页面(有头模式下是同一窗口内的 tab) │ ├─ goto baseURL → 路由守卫 → /login │ ├─ fixture 注入会话 或 真实 UI 填密码点登录 │ └─ 逐条断言(失败 → 截图 + 重试 → 仍失败 → trace.zip) └─ 全部结束 → 回收自己拉起的 uvicorn 进程

登录会话的双通道设计(既覆盖 UI 登录流程,又避免每个用例重复点登录):

// tests/e2e/auth.ts —— fixture 通道:API 登录取 JWT,注入 localStorageexportconsttest=base.extend<{page:Page}>({page:async({page},use)=>{consttoken=awaitapiLogin()// POST /api/v1/admin/login,密码读 .envawaitpage.addInitScript(([t,r])=>{localStorage.setItem('llmproxy_token',t)localStorage.setItem('llmproxy_role',r)},[token,'admin'])awaituse(page)},})
  • login.spec.ts走真实 UI 点击(填密码 → 点「登录」→ 断言跳转/dashboard,含错误密码/限流场景)
  • 其余 60+ 用例走fixture 注入,直接进入目标页
  • 密码绝不硬编码:adminPassword()从环境变量或项目根.env的ADMIN_PASSWORD读取

5. 自动化测试的完整流程(八步)

产品 bug

测试 bug

不一致

一致

① 需求基线:OpenSpec 规格

② 测试计划:Requirement/Scenario 1:1 映射

③ 编写测试脚本:规格驱动 spec 文件

④ 执行:npm run test:e2e
webServer 自动拉起服务+Chromium

⑤ 取证:失败自动截图/trace
error-context a11y 快照
OMP browser 手工补拍

⑥ 根因分析:产品 bug vs 测试 bug

修产品代码 → 回到 ④

修测试 → 回到 ④

⑦ 与 OpenSpec 对账判定

⑧ 全量回归 + 文档 + 提交

① 需求基线:OpenSpec 规格是唯一验收标准

openspec/specs/<capability>/spec.md中每条 Requirement 用 MUST/MUST NOT 描述行为,
每条至少一个#### Scenario(WHEN/THEN/AND)。例如 full-client-audit:

### Requirement: 仅目标客户端被全量归档 ... 系统 MUST 对目标客户端的每次请求保存完整请求/应答 ... #### Scenario: 目标客户端产生归档 - **WHEN** 对目标客户端发起一次请求 - **THEN** 归档列表中出现该请求的元数据,且请求内容逐字可查

规格里没有写过的行为,测试不应该断言;规格里 MUST 写过的行为,测试必须覆盖或显式声明归其他层。

② 测试计划:映射 + 边界声明

把每个 capability 的 Requirement/Scenario 映射成 spec 文件与用例,用例标题直接引用规格:

test('[full-client-audit] 仅目标客户端被全量归档:请求逐字 + 信封打码 + 可下载',async()=>{...})

同时显式声明 E2E 边界(哪些场景不属于 E2E,归 pytest 集成测试),写进文件头注释:

// 说明:DB 明细对账、缓存口径、流式逐块事件流等深度场景归 pytest 集成测试// 上游服务不可用时整文件跳过(本地开发环境依赖,非产品缺陷)

边界声明是"判定一致性"的前提——只有明确了每个 Scenario 的归属层,才能说"该层内全部覆盖"。

③ 编写测试脚本

约定(本项目执行下来的纪律):

  1. 可抛数据e2e_前缀 +afterAll强制清理(含 key 删除保护场景的兜底清理函数),不污染真实注册表
  2. 热设置读改恢复:改全局参数(如full_audit_client_id)一律走withSetting(),异常路径也恢复原值
  3. API 断言走 APIRequestContext:不依赖页面渲染的内部状态,直接对接口契约断言
  4. UI 断言优先可访问名称/角色(getByRole/getByLabel),其次 CSS 类,最后才是位置索引(并注释原因)
  5. 外部依赖降级:全链路依赖真实上游(vLLM/Ollama),不健康时test.skip整文件,不产生假失败

④ 执行

npmrun test:e2e# CI:无头npmrun test:e2e:headed# 日常:有头窗口看全程npmrun test:e2e:ui# 调试:UI 模式单步

本次全量 66 用例,真实上游在环,约 64 秒。

⑤ 取证(截图 / trace / 快照)

失败时 Playwright 自动产出(test-results/):

  • test-failed-*.png:失败瞬间截图
  • trace.zip(首次重试起自动录):npx playwright show-trace回放每一步的 DOM/网络/控制台
  • error-context.md:a11y 树快照——本次的关键证据之一。收起态菜单定位超时,
    从快照里直接看到 10 个menuitem都没有可访问名、且 Element Plus 的index是 prop 不落 DOM 属性,
    据此决定按 DOM 顺序.nth(n)定位(注释标明顺序来源)

此外 OMP browser 设备可做主动补拍:打开真实页面、执行 JS 取outerHTML/boundingBox、
tab.screenshot()存 webp。本次用它实测了 el-aside 宽度(220↔64px)、localStorage 键名、
菜单折叠后的真实 DOM,把"猜选择器"变成"量选择器"。

⑥ 根因分析:先分诊「产品 bug」还是「测试 bug」

本次 66 个用例跑绿之前共出现 11 个失败,没有一个是产品 bug,全部是测试侧问题——
这个分诊过程本身就是流程价值:

现象分诊结论修复
后续用例批量 429限流用例污染了 60s 失败桶(共享状态)限流用例移到文件末尾(串行下确定不污染前置用例)
密码框找不到输入用户名后 placeholder 动态变化改用可访问名称(getByRole('textbox', {name:'密码'}))
chat 返回content: ''疑似网关丢内容 → 实测对照:thinking 模型max_tokens=16被思考过程吃光(finish_reason=length),改 512 正常 →网关透传正确,测试参数问题测试max_tokens512
归档列表恒 0 行疑似归档器失效 → 读代码:created_at为 UTCYYYY-MM-DD HH:MM:SS,测试只传日期,字典序比较恒假 →测试参数格式问题查询窗口改完整 UTC 时间戳,且每次查询实时计算(模块加载时冻结的窗口在 60s 后就过期)
宽度断言抖动CSS 过渡动画竞态(点击后立即读 boundingBox 可能还在 220)expect.poll(...).toBeCloseTo(64)轮询等稳定

分诊手段排序:看 error-context 快照 → 读产品代码确认契约 → 用独立脚本直连 API/上游对照实测(本次用一次性 node 脚本对比"网关 vs 上游直连",2.6 秒定案)。

⑦ 与 OpenSpec 需求规格对比,判定功能是否正常

对账是三步:

  1. 覆盖对账(规格 → 测试):逐条 Requirement/Scenario 问"谁覆盖它?"
    • 被 E2E 用例覆盖 → 该用例必须绿
    • 被声明归 pytest 层 → 查 pytest 套件
    • 两层都没有 →缺口,补用例或补声明
  2. 断言对账(用例 → 规格):用例断言必须落在规格的 WHEN/THEN/AND 上,
    且规格中每条 MUST/MUST NOT 至少被一个断言守住;反向也查——用例断言了规格没有的行为是过度测试,应删除
  3. 反向路径对账:MUST NOT(“厂商 key 不可用”、“路径穿越被拒”、“key 明文不回显”)
    必须用反向用例验证"该发生的不发生",只有正向通过不算覆盖

一致性判定标准(全部满足才判定"该 capability 功能与需求一致"):

#判据反例(本次实际踩过)
1该 capability 所有 E2E 归属 Scenario 的用例全绿且无 flaky(重试后通过要按未通过处理,查根因)限流桶污染导致 1 个用例"重试后通过",实际是顺序侥幸
2断言的是规格措辞本身(状态码、error.code、字段存在/打码/逐字、可访问性、无溢出),不是实现细节断言toBeTypeOf('number')(Jest API)——断言对象错位,Playwright expect 根本没有该方法
3正向路径用真实数据/真实上游验证,不 mock 被测系统本身用假模型名跑 chat,永远测不到网关-上游翻译层
4数据可对账:写后能读回且一致(归档内容逐字含 marker、打码处不含明文 key、下载文件与详情一致)只断言"列表有 1 行"而不验内容,无法证明"逐字归档"
5用例可重跑:跑两遍结果相同(可抛数据已清理、全局状态已恢复、时间窗口实时计算)模块级冻结的查询窗口导致第二遍必挂

⑧ 回归与交付

  • 全量绿 → 把"怎么跑"写进 README(命令、映射表、边界声明)
  • 提交时按仓库惯例引用 OpenSpec 变更名;规格随/opsx-sync同步入主规格、/opsx-archive归档
  • 产物(test-results/、playwright-report/、node_modules/、浏览器二进制)全部 gitignore,不入库

6. OMP 在该流程中的角色(人机分工)

阶段OMP 做了什么人做什么
测试计划读 8 个 capability 规格,产出映射表与边界声明确认边界划分(什么归 E2E、什么归 pytest)
编写生成全部 spec/helpers/fixture/config审阅选择器策略与断言强度
执行npm run test:e2e:headed一条命令;browser 设备手工补拍取证看窗口里的真实操作(有头模式的价值)
分析读 error-context 快照、读产品代码、跑对照探测脚本分诊 11 个失败裁定"测试 bug / 产品 bug"的争议项
对账逐 Requirement 核对覆盖与断言,产出判定结论拍板"功能与需求一致"的结论
交付README 章节、提交(引用变更名)、归档确认提交与推送时机

核心原则:判定权在人,取证与对账的体力活给 OMP。
“功能是否正常"不是"测试绿了”,而是"规格里的每条 MUST 都有对应用例守住、且全部通过"。

7. 常见坑清单(Windows + Vue/Element Plus + 网关类系统)

  1. Windows 下 webServer 命令:cmd 不认正斜杠相对路径 → 平台条件命令;reuseExistingServer会掩盖拉起失败,必须服务未起时验证一次
  2. 限流/计数器类共享状态:会失败的用例(429 桶、预算)放文件末尾,workers: 1
  3. 动态 placeholder/文案:表单字段文案随输入变化时,用可访问名称(label/role)而非 placeholder
  4. CSS 过渡动画:宽度/位移类断言用expect.poll等稳定值,不要点击后立即读
  5. Element Plus 折叠菜单:index是 prop 不落 DOM;收起态文字标签不可访问 → 按 DOM 顺序.nth(n)并注释顺序来源
  6. 时间窗口与时区:先读产品代码确认存储格式(本项目created_at= UTCYYYY-MM-DD HH:MM:SS),查询参数用完整时间戳且实时计算,不要只传日期、不要模块加载时冻结
  7. thinking 模型的max_tokens:短预算会被思考过程吃光(finish_reason=length、content 空)——E2E 用 512 起步;这也是"网关丢没丢内容"分诊的第一步
  8. 归档/异步落盘:非阻塞写入后查询要留缓冲,且查询窗口要包含缓冲
  9. 测试 API 方言:Playwright expect 是 Chai 系(无toBeTypeOf);断言 JSON 对象内容先JSON.stringify
  10. npm 源:国内网络--registry=https://registry.npmmirror.com+PLAYWRIGHT_DOWNLOAD_HOST=https://cdn.npmmirror.com/binaries/playwright

8. 命令速查

npmrun test:e2e:headed# 有头自动跑(看鼠标操作)npmrun test:e2e:ui# UI 模式单步调试npmrun test:e2e:report# HTML 报告npx playwright show-trace test-results/<case>/trace.zip# 回放失败 tracenpx playwright codegen http://127.0.0.1:3002# 录制openspec list--json# 活跃变更(对账基线来源)

9. 总结

OMP + Playwright 的 Web 自动化测试,本质是把「OpenSpec 规格 → 测试计划 → 用例 → 执行 → 取证 → 对账」
做成一条可重复的流水线:

  • 装:@playwright/test+ chromium(国内加镜像参数),OMP 侧用内置 browser 设备做探索与补拍
  • 启:playwright.config.ts的webServer让"服务 + Chromium"随测试命令自动起停;headed/UI 模式按需选
  • 测:登录双通道(UI 点击 + JWT 注入)、e2e_数据自清理、串行保证共享状态确定性
  • 流程:规格基线 → 映射计划(含边界声明)→ 规格驱动脚本 → 自动执行 → 截图/trace/a11y 快照取证 → 产品/测试分诊 → 与规格三向对账 → 回归交付
  • 判定:功能与需求一致 = 每条 MUST 都有用例守住 + 全绿无 flaky + 断言落在规格措辞上 + 真实数据可对账 + 可重跑

测试绿了只是必要条件;"规格里写的每件事都被验证过"才是交付标准。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询