ECC e2e-runner 实战指南:用 Agent Browser + Playwright 守住关键用户旅程的最后一关
E2E 测试是发布前最后一道防线,能捕获单元测试遗漏的集成问题。本文以 ECC(The agent harness performance optimization system)仓库中的 e2e-runner Agent 定义 为骨架,完整讲解如何让 Agent 承担"创建、维护、执行 E2E 测试"的职责:以 Vercel Agent Browser 为第一选择、Playwright 兜底,覆盖测试旅程管理、不稳定测试隔离与产物(截图/视频/轨迹)上传,并配合仓库内 e2e-testing 技能 沉淀 Page Object Model、CI/CD 配置与防抖动策略。读完你将得到一套可复制、可落地的 Agent 化端到端测试工作流与配套量化指标。
一、e2e-runner 在 ECC 中的定位
agents/e2e-runner.md 是 ECC 仓库中 60 余个 Agent 定义之一。其 YAML frontmatter 声明了该 Agent 的基本契约:
- name:
e2e-runner - description: 使用 Vercel Agent Browser(首选)与 Playwright(备选)进行端到端测试的专家;应主动用于生成、维护和运行 E2E 测试;
- tools: Read、Write、Edit、Bash、Grep、Glob(即不直接编排浏览器驱动,而是通过 Bash 调起 CLI);
- model:
sonnet
从编排角度看,e2e-runner 被设计为"直接调用型" Agent。仓库的 命令 → Agent 映射文档 明确标注了 /e2e 命令对应 e2e-runner(注释为 "Playwright E2E tests");在 Agent 编排规则 的 Agent 一览表中,它的使用时机被概括为 "Critical user flows"——即仅在涉及关键用户流时动用,而非每个改动都触发。
安装侧同样可以佐证其存在:ECC 的组件清单 manifests/install-components.json 中包含 "id": "agent:e2e-runner",描述为 "Playwright E2E testing agent.",归属于 agents-core 模块。换言之,该 Agent 随核心模块安装,不是可选增值插件。
二、核心职责:六件事
Agent 的核心使命是确保关键用户旅程正确工作——创建、维护并执行全面的 E2E 测试,同时做好产物管理与抖动(flaky)测试处理。具体分解为六项职责:
- Test Journey Creation(测试旅程创建)——为用户流程编写测试,首选 Agent Browser,备选 Playwright;
- Test Maintenance(测试维护)——UI 变化后及时同步更新既有测试;
- Flaky Test Management(不稳定测试管理)——识别并隔离不稳定的测试用例;
- Artifact Management(产物管理)——捕获截图、视频、trace 轨迹;
- CI/CD Integration(流水线集成)——确保测试在流水线中稳定执行;
- Test Reporting(测试报告)——产出 HTML 报告与 JUnit XML,供 CI 与其他工具消费。
三、首选执行工具:Agent Browser
为什么优先于裸 Playwright
Agent Browser 构建在 Playwright 之上,但面向 Agent 与 LLM 做了三项关键优化:语义选择器(可直接面向元素语义定位)、AI 优化(snapshot 输出带 ref 的元素树,天然适合模型理解页面)、自动等待(内置 auto-wait 机制)。因此规则非常明确——优先 Agent Browser,而非裸 Playwright。
安装与核心工作流
# Setup
npm install -g agent-browser && agent-browser install
安装完成后,Agent 的核心循环如下:
# Core workflow
agent-browser open https://example.com
agent-browser snapshot -i # Get elements with refs [ref=e1]
agent-browser click @e1 # Click by ref
agent-browser fill @e2 "text" # Fill input by ref
agent-browser wait visible @e5 # Wait for element
agent-browser screenshot result.png
逐条拆解其语义:
| 命令 | 作用 | 关键点 |
|---|---|---|
open <url> |
打开目标页面 | E2E 旅程的起点 |
snapshot -i |
输出带交互元素 ref 的页面快照 | -i(interactive)只列出可交互元素,每个元素形如 [ref=e1] |
click @e1 |
按 ref 点击元素 | 引用来自 snapshot 输出的 ref 编号 |
fill @e2 "text" |
向输入框填充文本 | ref 定位而非 CSS/XPath |
wait visible @e5 |
等待元素可见 | 用"条件就绪"代替固定 sleep |
screenshot result.png |
截图保存产物 | 在关键步骤落盘,作为故障证据 |
ref 的驱动循环是 Agent Browser 的核心心智模型:snapshot -i 获得"当前页面长什么样、哪些东西可点可填",随后用 @ref 精确操作。这比让模型自行编写 CSS 选择器再猜测页面状态要可靠得多,也天然规避了选择器失效与"元素尚未出现"两类经典问题。
四、兜底方案:直接使用 Playwright
当 Agent Browser 不可用(未安装、受网络限制、或需要精细控制浏览器底层能力)时,回退到 Playwright CLI 直接执行:
npx playwright test # Run all E2E tests
npx playwright test tests/auth.spec.ts # Run specific file
npx playwright test --headed # See browser
npx playwright test --debug # Debug with inspector
npx playwright test --trace on # Run with trace
npx playwright show-report # View HTML report
各命令的使用场景:
npx playwright test:无参数运行整个测试套件;- 指定
tests/auth.spec.ts:只跑单文件,用于聚焦回归某条用户流; --headed:以有头模式启动浏览器,便于人工观察执行过程;--debug:打开 Playwright Inspector 逐步调试;--trace on:全量开启 trace 录制,事后可在 trace viewer 中重放每一步 DOM 与网络;show-report:本地起服务查看最新 HTML 报告(对应配置中的 html reporter)。
五、三阶段工作流:Plan → Create → Execute
阶段 1:Plan(规划)
识别关键用户旅程——典型候选是 auth(认证)、核心功能、支付(payments)、CRUD;随后定义每种旅程的场景矩阵:happy path(正常路径)、edge cases(边界)、error cases(错误路径);最后按风险分级排优先级:
- HIGH(高风险):金融交易、认证授权——任何闪失都是事故级;
- MEDIUM(中风险):搜索、导航——影响面大但可降级;
- LOW(低风险):纯 UI 润色——视觉细节,不阻断发布。
风险分级直接决定测试投入顺序与 CI 门禁策略:高风险旅程必须有断言、有产物、有 100% 通过要求;低风险项允许后续补齐。
阶段 2:Create(创建)
写测试时遵守五条铁律:
- 使用 Page Object Model(POM)模式:把页面结构与操作封装为类,避免用例里散落裸选择器;
- 优先
data-testid定位器:选择器优先级是data-testid> CSS 选择器 > XPath——testid 是给测试的稳定锚点,不受样式/结构重构影响; - 在关键步骤加断言:每一步状态转变都用
expect()验证,实现"快速失败"; - 关键节点截图:捕获页面证据,失败时一眼定位到哪一步崩了;
- 使用规范等待,绝不用
waitForTimeout:固定 sleep 是最典型的抖动来源。
POM 的参考实现可直接复用 e2e-testing 技能 中的模式(详见第八节)。
阶段 3:Execute(执行)
- 本地重复执行 3–5 次,用多次运行暴露间歇性失败;
- 确认抖动后,用
test.fixme()或test.skip()隔离该用例,并关联 issue 编号; - 将产物(报告、截图、视频、trace)上传到 CI,保证失败可审计、可回放。
六、让测试稳定下来的六条关键原则
原文给出六条足以决定 E2E 套件生死的原则,值得逐条强调:
- Use semantic locators:
[data-testid="..."]> CSS selectors > XPath。语义定位器与 UI 意图绑定,而非与实现细节绑定; - Wait for conditions, not time:
waitForResponse()>waitForTimeout()。等待"发生的事情",而不是"流逝的时间"; - Auto-wait built in:
page.locator().click()自带自动等待(等待可操作),而裸page.click()没有——前者能消化大多数竞态; - Isolate tests(测试隔离):每个测试相互独立、不共享状态,可任意排序与并行;
- Fail fast:在每一个关键步骤用
expect()断言,尽早暴露回归而非等用例走完; - Trace on retry:配置
trace: 'on-first-retry'——首次失败时自动录 trace,省去为每次运行付费录制的成本,失败现场却不缺失。
七、Flaky 测试处理:识别、隔离、根因修复
隔离(Quarantine)代码范式
// Quarantine
test('flaky: market search', async ({ page }) => {
test.fixme(true, 'Flaky - Issue #123')
})
在测试体首行调用 test.fixme(true, 'Flaky - Issue #123'),会让该用例在报告中显式标记为"已知问题待修",而非混入失败数污染通过率;Issue #123 保留追踪线索。更精细的控制是条件跳过:
test('conditional skip', async ({ page }) => {
test.skip(process.env.CI, 'Flaky in CI - Issue #123')
})
量化识别抖动
# 重复执行以暴露间歇性失败
npx playwright test tests/search.spec.ts --repeat-each=10
# 或加重试压力观察失败模式
npx playwright test tests/search.spec.ts --retries=3
--repeat-each=10 让同一用例连跑 10 遍,是定位竞态型抖动的利器——稳定用例会 10/10 通过,抖动用例则暴露非确定性。
三类常见根因与修复
竞态条件(Race conditions)——假定元素已就绪:
// Bad: assumes element is ready
await page.click('[data-testid="button"]')
// Good: auto-wait locator
await page.locator('[data-testid="button"]').click()
网络时序(Network timing)——等待任意时长而非具体响应:
// Bad: arbitrary timeout
await page.waitForTimeout(5000)
// Good: wait for specific condition
await page.waitForResponse(resp => resp.url().includes('/api/data'))
动画时序(Animation timing)——动画进行中触发交互:
// Bad: click during animation
await page.click('[data-testid="menu-item"]')
// Good: wait for stability
await page.locator('[data-testid="menu-item"]').waitFor({ state: 'visible' })
await page.waitForLoadState('networkidle')
await page.locator('[data-testid="menu-item"]').click()
八、成功指标:用数字定义"可靠"
E2E 套件是否合格,用五条硬指标衡量:
- 关键旅程通过率 100%:HIGH 级流程零容忍;
- 整体通过率 > 95%:留出灰度空间但守住质量底线;
- Flaky 率 < 5%:抖动用例占比过高说明测试基建本身有问题;
- 单次执行时长 < 10 分钟:超时即拖垮 CI 反馈速度,需拆分或并行化;
- 产物可上传、可访问:失败无证据等于未失败。
九、纵深参考:e2e-testing 技能中的落地模式
e2e-runner.md 末尾将细节指向技能 e2e-testing/SKILL.md。该技能文件(元数据标注 origin: ECC)提供了可直接复用的实现,是实现上述六条原则与三阶段工作流的最佳范本。
目录组织与 POM 骨架
技能推荐的测试目录将 spec(用例)与 fixtures、配置分离:
tests/
├── e2e/
│ ├── auth/ # login / logout / register
│ ├── features/ # browse / search / create
│ └── api/ # endpoints
├── fixtures/
│ ├── auth.ts
│ └── data.ts
└── playwright.config.ts
POM 类把定位器提升为构造期的只读字段,并在方法内封装等待逻辑:
import { Page, Locator } from '@playwright/test'
export class ItemsPage {
readonly page: Page
readonly searchInput: Locator
readonly itemCards: Locator
constructor(page: Page) {
this.page = page
this.searchInput = page.locator('[data-testid="search-input"]')
this.itemCards = page.locator('[data-testid="item-card"]')
}
async search(query: string) {
await this.searchInput.fill(query)
await this.page.waitForResponse(resp => resp.url().includes('/api/search'))
await this.page.waitForLoadState('networkidle')
}
}
可以看到第五节"Create"阶段的全部要求在此落地:data-testid 定位器、waitForResponse 代替固定 sleep、networkidle 等待动画与请求稳定。
测试结构(describe + beforeEach + 断言 + 截图)
import { test, expect } from '@playwright/test'
import { ItemsPage } from '../../pages/ItemsPage'
test.describe('Item Search', () => {
let itemsPage: ItemsPage
test.beforeEach(async ({ page }) => {
itemsPage = new ItemsPage(page)
await itemsPage.goto()
})
test('should search by keyword', async ({ page }) => {
await itemsPage.search('test')
const count = await itemsPage.getItemCount()
expect(count).toBeGreaterThan(0)
await expect(itemsPage.itemCards.first()).toContainText(/test/i)
await page.screenshot({ path: 'artifacts/search-results.png' })
})
})
该结构示范了 happy path(关键词命中并断言结果存在与内容匹配)与 error case(不存在的结果返回空态并断言 no-results 可见)两种场景如何写。
Playwright 配置模板(多浏览器 + 三 reporter + webServer)
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI, // CI 下禁止 only
retries: process.env.CI ? 2 : 0, // CI 重试 2 次
workers: process.env.CI ? 1 : undefined, // CI 下单 worker,降低抖动
reporter: [
['html', { outputFolder: 'playwright-report' }],
['junit', { outputFile: 'playwright-results.xml' }],
['json', { outputFile: 'playwright-results.json' }]
],
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry', // 首次失败录制 trace
screenshot: 'only-on-failure',
video: 'retain-on-failure',
actionTimeout: 10000,
navigationTimeout: 30000,
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
],
webServer: {
command: 'npm run dev', // 自动起被测服务
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 120000,
},
})
值得注意的配置决策都直接呼应第六、七节的稳定性原则:fullyParallel + 测试隔离配合;retries: CI ? 2 : 0 与 trace: 'on-first-retry' 是 CI 抖动治理的标准组合(retry 只发生在首次失败记录 trace 之后);forbidOnly 阻止 test.only 泄漏进流水线;JUnit XML 与 JSON 双格式输出对接 CI 平台与看板。
CI/CD 流水线(GitHub Actions)
name: E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
env:
BASE_URL: ${{ vars.STAGING_URL }}
- uses: actions/upload-artifact@v4
if: always() # 失败也要上传报告
with:
name: playwright-report
path: playwright-report/
retention-days: 30
要点:playwright install --with-deps 补齐系统级浏览器依赖;BASE_URL 指向 staging 而非本地;if: always() 保证即使测试失败,playwright-report 也能作为 artifact 保留 30 天用于复盘。
产物管理三件套
- 截图:
page.screenshot({ path: 'artifacts/after-login.png' });fullPage: true截整页;定位器级.locator(...).screenshot()只截目标组件; - Trace:
browser.startTracing(page, { path, screenshots: true, snapshots: true })手动录制,或依赖配置级trace: 'on-first-retry'自动产出 zip 供 Trace Viewer 重放; - Video:配置
use: { video: 'retain-on-failure', videosPath: 'artifacts/videos/' },仅失败保留.webm。
报告模板
技能内置了一份可复用的 E2E 报告 Markdown 模板,结构为:时间/时长/状态头部 → Summary(Total / Passed / Failed / Flaky / Skipped)→ 失败用例明细(文件行号 + 错误摘要 + 截图路径 + 推荐修复)→ 产物清单(HTML/Screenshots/Videos/Traces)。把它接到 Agent 的最终输出上,即可形成"失败可定位、证据可追溯"的交付闭环。
高风险旅程专项:Web3 与金融流
针对钱包类应用,用 context.addInitScript 注入 mock 的 window.ethereum(实现 eth_requestAccounts / eth_chainId),在无真实链上环境时完成连接流程验证:
test('wallet connection', async ({ page, context }) => {
await context.addInitScript(() => {
window.ethereum = {
isMetaMask: true,
request: async ({ method }) => {
if (method === 'eth_requestAccounts')
return ['0x1234567890123456789012345678901234567890']
if (method === 'eth_chainId') return '0x1'
}
}
})
await page.goto('/')
await page.locator('[data-testid="connect-wallet"]').click()
await expect(page.locator('[data-testid="wallet-address"]')).toContainText('0x1234')
})
金融交易类用例则遵循"HIGH 风险 + 生产豁免"双保险——断言下单预览、等待包含 /api/trade 且返回 200 的响应,同时用 test.skip(process.env.NODE_ENV === 'production', ...) 杜绝真实资金风险。
十、在 ECC 中编排 e2e-runner 的补充说明
若要在 ECC 生态中更系统地使用该 Agent,可继续查阅以下仓库资源:
- Agent 编排规则:说明 e2e-runner 与 planner、tdd-guide、code-reviewer 的分工时序,以及并行 Task 执行与委派完成契约(被委派 Agent 的结果必须回收整合,禁止 fire-and-forget);
- 命令 → Agent 映射:
/e2e命令将调度至 e2e-runner,方便在会话中直接触发; - 组件安装清单:e2e-runner 随
agents-core模块分发; - 中文翻译版 Agent 定义:仓库提供了该文档的中文翻译,便于中文团队对照阅读;
- e2e-testing 技能:第五节至第九节所有代码模板的完整出处与延伸。
最后,请记住 e2e-runner 的定位本身:E2E 测试是生产环境前的最后一道防线,它专门捕捉单元测试覆盖不到的集成问题。把精力投在稳定性(自动等待代替 sleep)、速度(并行 + 超时上限)与覆盖率(风险分级补全)上,你的关键用户旅程才会真正"可发布、可回退、可追溯"。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00