关于 create_e2e_tests
create_e2e_tests 是用于创建本地 YAML 端到端测试的分阶段技能,面向需要先规划再编写 Web 应用测试的开发者。它通过策略、场景规格和实施计划等文档衔接测试编写与覆盖核验,并使用 Shiplight 在本地运行测试。
主要功能
- 01按阶段生成测试规划文档
- 02用 Given/When/Then 描述场景
- 03通过浏览器采集真实定位器
- 04校验 YAML 测试文件
- 05对照规格核验测试覆盖
适用场景
- 01从零创建本地测试项目
- 02为 Web 应用添加 YAML 测试
- 03规划登录与关键用户流程测试
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ shiplightai/ agent-skills --skill create_e2e_tests安装后在 Claude Code 中输入/create_e2e_tests确认已加载。
详细介绍
创建本地 YAML 测试
一种由规范驱动的工作流,通过在编写任何测试之前进行结构化规划,提前引入测试专业知识。使用 npx shiplight test --headed 运行测试——无需云基础设施。
适用场景
当用户希望进行以下操作时,使用 /create_e2e_tests:
- 从头创建新的本地测试项目
- 为 Web 应用添加 YAML 测试
- 为测试项目设置身份验证
- 在编写测试前规划测试内容
原则
-
始终生成产物。 每个阶段都会写入一个 Markdown 文件。产物有助于厘清你的思路,为用户提供可供审阅的内容,并指导后续阶段。用户提供了详细需求时,将其用作素材——跳过已回答的问题,但仍要生成产物。
-
实现前先确认。 在投入时间进行浏览器探索和编写测试前,先呈现规范(第 2 阶段的检查点)供用户确认。将你的理解整理成结构化场景复述给用户,以便及早发现不一致之处。
-
每个阶段都读取上一阶段的产物。 Discover 为 Specify 提供依据,Specify 为 Plan 提供依据,Plan 为 Implement 提供依据,Implement 为 Verify 提供依据。如果之前运行时已生成产物,可询问是否复用。
-
遇到问题时升级处理,不要循环重试。 出现失败或含糊不清的情况时,向用户说明并询问,而不是悄悄重试。
阶段概览
Phase 1: Discover → test-strategy.md (understand the app & user goals)
Phase 2: Specify → test-spec.md (define what to test in Given/When/Then)
Phase 3: Plan → test-plan.md (prioritize, structure, per-test guidance)
Phase 4: Implement → *.test.yaml files (setup project, write tests, run them)
Phase 5: Verify → updated spec files (coverage check, reconcile spec ↔ tests)
快速通道
开始前检查是否已有产物。只有用户明确提出时,才可以跳过产物生成。
| 情况 | 处理方式 |
|---|---|
| 用户明确说“跳到实现”或“直接编写测试” | 仅执行第 4 阶段 |
已存在 test-specs/test-strategy.md | 询问是否复用,并跳过第 1 阶段 |
已存在 test-specs/test-spec.md | 询问是否复用,并跳过第 1–2 阶段 |
已存在 test-specs/test-plan.md | 询问是否复用,并跳到第 4 阶段 |
第 1 阶段:探索
目标: 了解应用、用户的角色,以及测试中最重要的内容。
输出: <project>/test-specs/test-strategy.md
步骤
-
获取项目路径 — 询问要在哪里创建测试项目(例如
./my-tests)。所有产物和测试都会放在这里。创建test-specs/目录。如果云 MCP 工具可用(已设置
SHIPLIGHT_API_TOKEN),使用/cloud技能获取环境和测试账号——这有助于预先填写目标 URL 和凭据。 -
静默扫描 — 提问前,先从现有信息中收集上下文:
- 代码库:路由、组件、
package.json、框架 - Git 分支差异(近期有哪些变更)
- 现有测试(已覆盖的内容)
- PRD、文档、README 文件
- 云环境(如果云 MCP 工具可用)
- 代码库:路由、组件、
-
了解测试需求 — 询问用户想测试什么,然后一次提出一个有针对性的后续问题,并根据扫描结果给出建议,以补齐遗漏:风险区域、用户角色、身份验证、数据策略、关键用户旅程。跳过用户已经回答的问题。
-
编写
test-strategy.md,包含:- 应用概况:名称、URL、框架、关键页面/功能
- 风险概况:最重要的内容、脆弱之处
- 测试范围:包含/不包含的内容、要覆盖的用户角色
- 数据策略:如何创建和清理测试数据
- 环境:目标 URL、身份验证方式、任何特殊设置
第 2 阶段:编写规范
目标: 以结构化的 Given/When/Then 格式定义具体测试场景,并按风险排序。找出可能导致测试不稳定或不完整的模糊之处。
输入: 读取 test-specs/test-strategy.md
输出: <project>/test-specs/test-spec.md
步骤
-
阅读
test-strategy.md,了解范围和优先级。 -
生成用户旅程规范 —— 为每个关键旅程编写:
- 标题:描述性名称(例如,“新用户通过电子邮件验证注册”)
- 优先级:P0(必需)、P1(应该具备)、P2(锦上添花)
- 前置条件:测试开始前必须满足的条件(Given)
- 顺利流程:逐步列出操作和预期结果(When/Then)
- 边缘情况:每个旅程至少 2 种(例如,无效输入、超时、空状态)
- 数据要求:所需的测试数据
-
检查测试风险 —— 检查每个旅程中可能导致测试不稳定或不完整的问题:数据依赖、计时/异步行为、动态内容、身份验证边界、第三方服务、状态隔离、环境差异。在每个旅程中添加 测试注意事项 部分,列出已识别的风险和缓解措施。如果存在任何不明确之处,请一次询问用户一个问题,并提供建议答案和影响说明。
-
编写包含所有旅程规范的
test-spec.md。 -
检查点 —— 提交汇总表供用户审核:
# 旅程 优先级 步骤数 边缘情况 风险 1 用户注册 P0 5 3 计时 2 ... ... ... ... ... 询问:“这样看起来正确吗?需要添加、删除或调整优先级的旅程吗?”
等待用户确认后再继续。
第 3 阶段:规划
目标: 制定可执行的实施计划,并为每项测试提供指导。
**输入:**读取 test-specs/test-spec.md
输出:<project>/test-specs/test-plan.md
步骤
-
阅读
test-spec.md。 -
定义测试文件结构 —— 将旅程映射到测试文件:
tests/ ├── auth.setup.ts (if auth needed) ├── signup.test.yaml (Journey 1) ├── checkout.test.yaml (Journey 2) └── ... -
确定实施顺序 —— 按以下顺序排列:
- 优先处理依赖项(先设置身份验证,再进行需要身份验证的测试)
- 然后按优先级排序(P0 优先于 P1)
- 最后按风险排序(风险最高的优先)
-
逐项测试指导 —— 为每个测试文件指定:
- 数据策略:要创建/使用的数据、清理方法
- 等待策略:何处使用 WAIT_UNTIL 与 WAIT、预期的加载节点
- 不稳定性风险:此测试中需要注意的具体事项
-
编写
test-plan.md。 -
检查点 —— 提交汇总:
已准备好实施 N 个测试文件。要继续吗?
第 4 阶段:实施
目标: 设置项目并根据计划编写所有 YAML 测试。
**输入:**读取 test-specs/test-plan.md
设置
跳过已完成的步骤(项目已存在、依赖项已安装、身份验证已配置)。
-
配置 AI 提供商 — 检查测试项目是否已有包含 AI API 密钥的
.env。如果没有,请询问用户选择哪个提供商:要运行 YAML 测试,我需要一个 AI 提供商来解析测试步骤。您想使用哪个提供商?
A) Google AI —
GOOGLE_API_KEY(获取密钥)— 默认模型:gemini-3.1-flash-lite-previewB) Anthropic —ANTHROPIC_API_KEY(获取密钥)— 默认模型:claude-haiku-4-5C) OpenAI —OPENAI_API_KEY(获取密钥)— 默认模型:gpt-5.4-miniD) Azure OpenAI — 需要AZURE_OPENAI_API_KEY+AZURE_OPENAI_ENDPOINT— 设置WEB_AGENT_MODEL=azure:<deployment>E) AWS Bedrock — 使用 AWS 凭证链 — 设置WEB_AGENT_MODEL=bedrock:<model_id>F) Google Vertex AI — 使用 GCP 应用默认凭证 — 设置WEB_AGENT_MODEL=vertex:<model>G) 我已经配置好了用户选择后,请索取其 API 密钥并将其保存到测试项目的
.env文件中。对于 A/B/C,会根据密钥自动检测模型。对于 D/E/F,还需使用相应的provider:model前缀保存WEB_AGENT_MODEL。用户也可以选择设置WEB_AGENT_MODEL来覆盖默认模型(例如,WEB_AGENT_MODEL=claude-sonnet-4-6)。 -
搭建项目框架 — 使用绝对项目路径调用
scaffold_project。这会创建package.json、playwright.config.ts、.env.example、.gitignore和tests/。将 API 密钥保存到.env。 -
安装依赖项:
npm install npx playwright install chromium -
设置身份验证(如有需要) — 遵循标准的 Playwright 身份验证模式。
在
playwright.config.ts中将凭证添加为变量:{ name: 'my-app', testDir: './tests/my-app', dependencies: ['my-app-setup'], use: { baseURL: 'https://app.example.com', storageState: 'tests/my-app/.auth/storage-state.json', variables: { username: process.env.MY_APP_EMAIL, password: { value: process.env.MY_APP_PASSWORD, sensitive: true }, // otp_secret_key: { value: process.env.MY_APP_TOTP_SECRET, sensitive: true }, }, }, },标准变量名:
username、password、otp_secret_key。对于机密信息,使用{ value, sensitive: true }。将值添加到.env。使用标准的 Playwright 登录代码编写
auth.setup.ts。对于 TOTP,使用node:crypto实现 RFC 6238(HMAC-SHA1 + base32 解码)——无需第三方依赖。继续之前先验证身份验证。 运行
npx shiplight test --headed执行身份验证设置,并确认其保存了storage-state.json。如果失败,请升级给用户处理——身份验证是其他所有操作的前提。如果测试计划涉及特殊身份验证要求(例如,每个测试使用一个账户、多个角色),请在继续之前与用户确认身份验证策略。
编写测试
对于计划中的每项测试(或用户需要的每项测试):
- 打开浏览器会话 — 使用应用的
starting_url调用new_session。 - 逐步体验流程 — 使用
inspect_page查看页面,然后使用act执行每项操作。这样可以从响应中捕获定位器。 - 捕获定位器 — 需要更多元素信息时,使用
get_locators。 - 构建 YAML — 按照下方的最佳实践编写
.test.yaml内容。 - 保存并验证 — 写入
.test.yaml文件,然后使用文件路径调用validate_yaml_test,检查定位器覆盖率(至少需要 50%)。 - 关闭会话 — 完成后调用
close_session。
重要: 请勿凭空编写 YAML 测试。务必先在浏览器会话中体验应用,以捕获真实的定位器。没有定位器的测试会被 validate_yaml_test 拒绝。
使用 test-plan.md 指导时:
- 在加载节点应用指定的等待策略
- 覆盖规范中定义的边界情况和断言
运行测试
编写完所有测试后,运行测试:
npx shiplight test --headed
测试失败时:
- 报告 — 用一句话告诉用户哪个测试失败以及失败原因。
- 分类失败:
- 实现修复(定位器错误、缺少等待、时序问题)→ 修复并重试。
- 规范不匹配(应用行为与规范不同)→ 询问用户是更新规范还是跳过该场景。
- 升级处理修复无效的情况——不要反复尝试相同的方法。
阶段 5:验证
目标: 根据规范验证测试覆盖情况,并协调任何偏差。
**输入:**读取 test-specs/test-spec.md、test-specs/test-plan.md 和所有 .test.yaml 文件
仅当规范文档存在时才运行此阶段。
覆盖检查
对于规范中的每个用户旅程,确认测试覆盖了正常路径和列出的所有边缘情况。
提供覆盖情况摘要:
| 规范中的用户旅程 | 优先级 | 规定的场景 | 编写的测试 | 覆盖情况 |
|---|---|---|---|---|
| 用户注册 | P0 | 4 | 4 | ✓ |
| 结账 | P0 | 3 | 2 | ✗ — 未覆盖边缘情况“购物车为空” |
标记缺口和额外内容(规范中未列出的测试步骤)。
协调
更新规范文档,使其与实际实现保持一致:
- 更新
test-spec.md— 标记跳过的场景并说明原因,添加实现过程中出现的场景,更新边缘情况以反映实际测试内容 - 更新
test-plan.md— 修正文件结构,记录与原计划的偏差 - 显示差异摘要 — 告诉用户变更了什么以及原因
这样可确保文档准确,便于今后维护和扩展测试。
YAML 格式参考
阅读 MCP 资源 shiplight://yaml-test-spec-v1.3.0,了解完整的语言规范(语句类型、模板、变量、测试套件、钩子、参数化测试)。
阅读 MCP 资源 shiplight://schemas/action-entity,了解所有可用操作及其参数。
YAML 编写最佳实践
这些最佳实践结合 YAML 语言规范和操作目录,帮助你编写快速、可靠的测试。
语句类型选择
- ACTION 是默认选项。 在浏览器会话期间,通过 MCP 工具(
act、get_locators)捕获定位器,然后编写 ACTION 语句。ACTION 可确定性地重放(约 1 秒)。 - DRAFT 是最后的选择。 仅当编写时确实无法确定定位器时才使用 DRAFT。DRAFT 速度较慢(每个约 5–10 秒,运行时由 AI 解析)。过多使用 DRAFT 的测试会被
validate_yaml_test拒绝。 - 使用 VERIFY 编写断言。 所有断言都使用
VERIFY:。不要编写类似"Check that the button is visible"的断言 DRAFT。 - 使用 URL 进行导航。 使用
URL: /path导航,而不是action: go_to_url。 - 使用 CODE 编写脚本。 使用
CODE:进行网络模拟、localStorage 操作和页面级脚本编写。不要用它来执行点击、断言或导航。
intent 字段
intent 是步骤的意图——它定义了该步骤应完成_什么_。action/locator 或 js 字段是实现_方式_的缓存。缓存失效时(定位器过期、DOM 发生变化),AI Agent 会利用 intent 重新检查页面,并从头重新生成操作。
由于 intent 用于驱动自我修复,因此必须具体到足以让 Agent 在没有其他上下文的情况下执行。描述用户目标,而非 DOM 元素——避免使用元素索引、CSS 选择器或位置引用,因为这些内容会在 UI 变化时失效:
# BAD: vague, agent can't re-derive the action
- intent: Click button
# BAD: tied to DOM structure that can change
- intent: Click the 3rd button in the form
- intent: Click element at index 42
# GOOD: describes the user goal, stable across UI changes
- intent: Click the Submit button to save the new project
action: click
locator: "getByRole('button', { name: 'Submit' })"
ACTION:结构化格式与 js: 简写
默认情况下,所有受支持的操作都使用结构化格式。 阅读 MCP 资源 shiplight://schemas/action-entity,了解所有可用操作及其参数。
仅当操作无法映射到受支持的操作时才使用 js:——例如,复杂的多步交互、自定义 Playwright API 调用或链式操作:
- intent: Drag slider to 50% position
js: "await page.getByRole('slider').first().fill('50')"
- intent: Wait for network idle after form submit
js: "await page.waitForLoadState('networkidle')"
js: 编码规则
- 始终将定位器解析为单个元素(例如,
.first()、.nth(1)),以避免 Playwright 严格模式错误 - 执行操作时始终指定
{ timeout: 5000 },以确保时序可预测 intent至关重要——当js失败时,它是自我修复的输入page、agent和expect在当前作用域中可用
VERIFY 最佳实践
- 对带有 AI 回退的
js:断言,始终设置较短的超时时间(例如,{ timeout: 2000 }),这样失效的定位器会快速回退到 AI,而不是等待默认的 5 秒 - 始终使用
VERIFY:简写——不要直接使用action: verify - 注意
js:断言可能出现误报阴性。 只有当js抛出错误(未找到元素、超时)时,才会触发 AI 回退。如果js针对错误元素执行成功(失效的选择器匹配了其他元素),断言会静默通过——不会触发回退。请保持js:断言简单且具体,以尽量降低此类风险。
IF/WHILE js: 条件最佳实践
- 对基于 DOM 的检查(元素是否可见、文本是否存在、页面状态)使用自然语言(AI)条件。AI 条件可以根据 DOM 变化自我修复;
js:条件则很脆弱,无法自动修复。 js:条件仅用于计数器/状态逻辑——例如,js: counter++ < 10、js: retryCount < 3。切勿使用js:检查 DOM,例如js: document.querySelector('.modal') !== null。- 如果需要基于 JavaScript 检查 DOM,请使用
CODE:执行检查并存储结果,或使用带有js:的VERIFY:(至少失败时会触发 AI 回退)。
等待语法
WAIT_UNTIL:——AI 会反复检查条件,直到条件满足或超时。默认超时时间为 60 秒。每次 AI 检查需要 5–10 秒,因此请将timeout_seconds设为至少 15。WAIT:——固定时长的暂停。使用seconds:设置时长。
何时使用每种语法,请参阅 E2E 测试设计中的智能等待。
通用约定
- 为提高可读性,在 ACTION 语句中将
intent放在首位 - 仅当 ACTION 既没有
locator也没有js时,才需要使用xpath。 - 单测试文件、套件与参数:
- 单测试文件——一个独立测试,不共享状态
- 套件——存在顺序依赖的测试(例如,测试 A 创建文件,测试 B 使用该文件)。套件中的每个测试仍只覆盖一个用户流程;套件仅保证执行顺序并共享浏览器状态。请勿使用套件打包互不相关的测试。
- 参数——相同的测试结构,不同的数据输入
E2E 测试设计最佳实践
这些原则规定了测试的内容和测试的结构,与 YAML 格式无关。请在第 2 阶段(指定)和第 4 阶段(实现)中应用这些原则。
测试隔离
每个测试都必须能够独立运行——绝不能依赖其他测试的副作用、执行顺序或遗留状态。如果测试需要数据,就由测试自行创建。
# BAD: depends on a previous test having created "My Project"
test: Delete a project
steps:
- URL: /projects
- intent: Click on "My Project"
action: click
locator: "getByText('My Project')"
- intent: Click the Delete button
action: click
locator: "getByRole('button', { name: 'Delete' })"
# GOOD: creates its own data, then tests the behavior
test: Delete a project
steps:
- CODE:
js: |
const res = await page.request.post('/api/projects', {
data: { name: 'Delete-Test-' + Date.now() }
});
const project = await res.json();
save_variable('projectName', project.name);
- URL: /projects
- WAIT_UNTIL: The project list has loaded
- intent: Click on the project we just created
action: click
js: "await page.getByText('{{projectName}}').click()"
- intent: Click the Delete button
action: click
locator: "getByRole('button', { name: 'Delete' })"
- VERIFY: The project is no longer visible in the list
每个测试对应一个用户流程
每个测试都应验证一个逻辑上的用户流程。如果 8 个步骤中的第 3 步失败,第 4 到第 8 步就无法提供任何信息。请将较长的流程拆分为聚焦的测试。
**例外:**套件允许测试之间存在顺序依赖(例如,测试 A 上传文件,测试 B 下载文件)。套件中的每个测试仍只覆盖一个用户流程;套件仅保证执行顺序并共享浏览器状态。
# BAD: tests login, settings change, AND deletion in one test
test: Full user lifecycle
steps:
- intent: Log in
- intent: Navigate to settings
- intent: Change display name
- VERIFY: Name updated
- intent: Navigate to account
- intent: Delete account
- VERIFY: Account deleted
# GOOD: separate tests, each verifiable in isolation
# File: update-display-name.test.yaml
test: Update display name from settings
steps:
- URL: /settings
- intent: Clear the display name field and type "New Name"
action: fill
locator: "getByLabel('Display name')"
value: "New Name"
- intent: Click Save
action: click
locator: "getByRole('button', { name: 'Save' })"
- VERIFY: Success message "Settings saved" is visible
# File: delete-account.test.yaml (separate test)
test: Delete account from account page
steps:
- URL: /account
# ... focused on deletion only
断言用户看到的内容,而非实现细节
测试可见结果——文本、导航、启用/禁用状态。绝不要断言 CSS 类、数据属性、内部状态或 DOM 结构。
# BAD: asserts implementation details
- VERIFY:
js: |
const el = await page.locator('.btn-primary');
await expect(el).toHaveClass(/disabled/);
await expect(el).toHaveAttribute('data-state', 'submitted');
# GOOD: asserts what a user would observe
- VERIFY: The Submit button is disabled
js: |
await expect(page.getByRole('button', { name: 'Submit' }))
.toBeDisabled({ timeout: 2000 });
聚焦的断言
验证能够证明功能正常的一件事。过多的断言会让测试变得脆弱——与被测行为无关的外观变化也会导致测试失败。
# BAD: asserts every field on the page — breaks when any label changes
- VERIFY: Page title is "Dashboard"
- VERIFY: Welcome message shows username
- VERIFY: Sidebar has 5 menu items
- VERIFY: Footer shows current year
- VERIFY: Avatar image is loaded
- VERIFY: Notification bell is visible
# GOOD: asserts the one thing that proves the user landed on the dashboard
- VERIFY: Dashboard page shows the welcome message with the user's name
绝不测试第三方服务
不要断言 Stripe 的结账流程、Google OAuth 的授权页面或 Twilio 的短信发送是否正常。应在网络边界模拟外部服务。测试你自己的集成,而非第三方的 UI。
# BAD: tests Stripe's UI (will break when Stripe updates their page)
- intent: Enter card number in Stripe iframe
- intent: Click Stripe's pay button
- VERIFY: Stripe shows success checkmark
# GOOD: mock the payment API, test your success handling
- CODE:
js: |
await page.route('**/api/payments', route =>
route.fulfill({ status: 200, json: { status: 'succeeded', id: 'pi_mock' } })
);
- intent: Click the Pay button
action: click
locator: "getByRole('button', { name: 'Pay' })"
- VERIFY: Order confirmation page shows "Payment successful"
确定性测试数据
每次测试运行都使用唯一标识符,避免冲突。切勿依赖其他测试或用户可能修改的硬编码数据。
# BAD: hardcoded name — collides if tests run in parallel or data persists
- intent: Type "Test User" into the name field
action: fill
locator: "getByLabel('Name')"
value: "Test User"
# GOOD: unique per run — no collisions
- CODE:
js: "save_variable('testName', 'Test-User-' + Date.now());"
- intent: Type the generated name into the name field
action: fill
locator: "getByLabel('Name')"
text: "{{testName}}"
优先通过 API 初始化,而不是通过 UI 设置
如果测试需要满足前置条件(例如用户已存在、项目已创建),请通过 API 调用完成设置,而不是通过 UI 点击操作。通过 UI 设置速度慢、容易出错,而且测试的也不是这部分内容。
# BAD: 10 UI steps just to set up data before the real test
- URL: /projects/new
- intent: Type project name
- intent: Select team
- intent: Click Create
- WAIT_UNTIL: Project page loads
# ... now the actual test starts
# GOOD: API seed in one step, then test the real behavior
- CODE:
js: |
const res = await page.request.post('/api/projects', {
data: { name: 'Seed-' + Date.now(), team: 'engineering' }
});
const { slug } = await res.json();
save_variable('projectSlug', slug);
- URL: /projects/{{projectSlug}}/settings
- WAIT_UNTIL: Settings page has loaded
# ... test starts immediately at the point that matters
合理等待
根据具体情况选择合适的等待方式。WAIT_UNTIL: 每次检查需要 5-10s(AI 解析),因此不适合用于短暂、可预测的延迟。延迟短且已知时,使用 WAIT: 没问题。反面做法是在延迟不可预测时,用 WAIT: 代替基于条件的等待。
# BAD: guessing how long a data fetch takes — too short in CI, too long locally
- WAIT: Wait for data to load
seconds: 5
- VERIFY: The table shows results
# GOOD: condition-based wait for unpredictable operations
- WAIT_UNTIL: The data table has at least one row visible
timeout_seconds: 30
# ALSO GOOD: short WAIT for known, fast delays (animations, transitions, debounce)
- intent: Type search query
action: fill
locator: "getByRole('searchbox')"
value: "test"
- WAIT: Wait for debounce to fire
seconds: 1
- VERIFY: Search suggestions are visible
经验法则:如果延迟可预测且少于 5s(动画、防抖、过渡),使用 WAIT:。如果延迟不可预测(API 调用、数据加载、文件处理),使用 WAIT_UNTIL:。
测试错误状态,而不只是正常流程
真实用户会遇到错误。只覆盖正常流程的测试套件会带来虚假的信心。对于每个关键用户旅程,至少加入一个错误或边界情况测试。
# Covers: empty state, invalid input, network failure
test: Search handles no results gracefully
steps:
- URL: /search
- intent: Type a query that returns no results
action: fill
locator: "getByRole('searchbox')"
value: "zzz_no_match_zzz"
- intent: Submit the search
action: click
locator: "getByRole('button', { name: 'Search' })"
- VERIFY: Empty state message "No results found" is displayed
- VERIFY: The search box still contains the query (user can refine)
为并行执行做好设计
修改共享全局状态(例如全站设置、唯一的管理员账户)的测试无法安全地并行运行。请按以下方式设计:
- 使用每个测试专属的唯一数据,而不是共享固定数据
- 避免更改全局配置的测试
- 如果测试必须修改共享状态,请记录此情况,并标记为串行执行
不稳定测试处理策略
重试后通过的测试仍然有问题。切勿通过添加重试来掩盖不稳定性——请找到并修复根本原因:
- 时序不稳定? → 针对正确的条件添加恰当的
WAIT_UNTIL: - 数据不稳定? → 使用唯一测试数据,并添加恰当的清理操作
- 顺序不稳定? → 测试隐式依赖了另一个测试——让其能够独立运行
- 环境不稳定? → 模拟不稳定的外部服务
项目结构
my-tests/
├── test-specs/ # Spec artifacts (version-controlled)
│ ├── test-strategy.md # Phase 1: app & risk profile
│ ├── test-spec.md # Phase 2: Given/When/Then scenarios
│ └── test-plan.md # Phase 3: implementation plan
│
├── playwright.config.ts
├── package.json
├── .env # API keys + credentials (gitignored)
├── .gitignore
│
├── tests/
│ ├── public-app/ # No login needed
│ │ ├── search.test.yaml
│ │ └── filter.test.yaml
│ │
│ └── my-saas-app/ # Requires login
│ ├── auth.setup.ts # Playwright login setup — you write this
│ ├── dashboard.test.yaml
│ └── settings.test.yaml
test-specs/ 目录包含人类可读、可进行版本控制的 Markdown 产物。不要将 test-specs/ 添加到 .gitignore。
提示
- 带定位器的 ACTION 语句回放速度约为 DRAFT 的 10 倍。始终优先使用 ACTION。
- 使用
inspect_page了解页面状态。务必先读取 DOM 文件——其中提供了act所需的元素索引,而且消耗的令牌要少得多。只有在确实需要视觉信息(布局、颜色、图像)时才查看屏幕截图,因为截图消耗的令牌远多于 DOM。 - 使用以下命令运行指定项目的测试:
npx shiplight test --headed my-saas-app/ shiplightConfig()会自动发现.env文件——无需手动设置 dotenv。