关于 playwright-bdd
这项 Playwright BDD 技能用于判断是否需要更新功能场景,并指导编写与审批。它强调以简洁的端到端 Gherkin 场景作为实施规划的依据。
主要功能
- 01判断是否需要更新 BDD 场景
- 02查找功能文件与步骤配置
- 03编写端到端 Gherkin 场景
- 04实施规划前要求审批
- 05按功能范围组织步骤定义
适用场景
- 01判断变更是否需要 BDD 场景
- 02补充或修订用户流程覆盖
- 03按功能区域组织步骤定义
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ vitalets/ playwright-bdd --skill playwright-bdd安装后在 Claude Code 中输入/playwright-bdd确认已加载。
详细介绍
Playwright BDD
阶段 0:BDD 必要性检查
对于用户请求的每项任务,首先判断是否需要通过新增或更新 BDD 场景来说明预期结果。应根据可观察到的产品行为变化作出判断,而不是根据代码改动规模或涉及的文件数量。
在以下情况下,需要修改 BDD feature 文件:
- 明确要求新增、更新或删除 BDD 场景。
- 新增、删除或更改用户可观察到的行为或端到端用户结果。
- 更改用户流程、验收标准、验证规则、权限或用户可见的错误行为。
- 修复的 bug,其预期行为尚未在现有场景中体现,或现有场景对其描述不准确。
在以下情况下,不需要修改 BDD feature 文件:
- 重构或重新组织实现,同时保持现有可观察行为不变。
- 更改文档、注释、格式、工具或其他开发基础设施,但不改变产品行为。
- 只调整视觉呈现或几何布局,不改变用户交互或含义。
- 实现或修复的行为已在现有场景中准确说明,且无需更改场景措辞或覆盖范围。
- 只更改步骤定义或测试基础设施,同时保持已说明的产品行为不变。
完成此评估后:
- 如果需要修改 BDD feature 文件,直接继续阶段 1,无需征求确认。
- 如果不需要修改 BDD feature 文件,立即继续处理用户的任务,不要创建或修改任何
.feature文件,也不要停下来征求确认。 - 如果确实无法确定,且仅询问:“是否需要为此更改更新 BDD 场景?” 然后等待用户回答。不要解释提问原因,也不要附带任何详细信息、推理或建议。
- 如果用户回答“是”,继续阶段 1。如果用户回答“否”,继续处理用户的任务,不要修改
.feature文件。
阶段 1:规划
- 发现项目配置 — 搜索
playwright.config.ts(或playwright.config.js),查找defineBddConfig(...)调用。这会揭示 features 目录,以及指向步骤定义文件的stepsglob 模式。- 如果存在多个
defineBddConfig调用,请根据上下文选择最合适的一个(例如,让目录名称与所描述的功能区域相匹配)。只有在确实无法判断时,才询问用户以澄清。
- 如果存在多个
- 编写 BDD 场景 — 检查现有的 feature 文件,并根据输入创建或更新 BDD 场景。严格遵循“场景编写规则”部分。
- 向用户展示 feature 文件变更,并使用 question 工具提问:"Approve the proposed feature-file changes?" — 向用户展示确切的新 Gherkin 内容或变更后的内容,以便协商:
- 展示现有场景的变更时,使用统一差异格式(```diff),清晰显示新增和删除的内容。对于全新的场景,以纯 Gherkin 格式展示。
- 始终显示目标 feature 文件的路径,以便明确场景将添加或修改的位置。
- 在 question 工具中使用简洁的批准问题:"Approve the proposed feature-file changes?",不要在问题中展示完整差异。
- 如果用户要求提供计划,在展示这些 feature 文件变更并获得明确批准之前,不要敲定计划。
- 不要用摘要、清单或实现计划代替 feature 文件差异/Gherkin。用户必须看到拟议更新的
.feature文件内容本身。 - 如果用户要求修改,请修订拟议场景文本并重新展示。
- 持续迭代,直到用户明确批准场景。在用户确认场景正确之前,不要继续进行实现规划或实现。
- 仅在场景获批后规划实现 — 用户批准 Gherkin 后,根据要求制定或执行实现计划。计划应以获批场景为事实依据。
阶段 2:实现
- 实现功能 — 编写实际功能实现代码,并遵循项目指南,而非本技能。
- 实现步骤定义 — 为场景中使用的步骤编写或更新步骤定义。遵循现有的步骤编写模式。根据现有文件命名方式,建议最适合添加新步骤的文件。
阶段 3:验证
执行 npx bddgen && npx playwright test,从 features 生成测试文件,并使用 Playwright 运行测试。
只运行相关的测试子集:将生成的 spec 文件路径传递给 Playwright CLI。生成目录由 playwright.config.ts 中的 defineBddConfig() 定义(即 testDir 值)。
示例:
npx bddgen && npx playwright test .features-gen/@homepage/homepage.feature.spec.js
场景编写规则
默认应用这些规则。如果用户明确要求采用不同的措辞、结构或覆盖范围,则针对相关场景遵循其要求。
-
场景必须涵盖完整的端到端用户流程,并产生有意义的结果。 场景应描述面向用户的行为或结果,而非检查中间状态。
-
尽量减少场景数量。 仅使用覆盖该功能主要用户流程所需的最少场景。
-
编写场景时复用现有步骤。 在为新场景编写新措辞前,先查找现有步骤定义和功能文件,看看是否有可复用的步骤。使用
npx bddgen export或文件搜索工具列出所有已注册的步骤定义。 -
通过交互确认元素可用。 如果场景会点击、填写、选择或以其他方式使用某个元素,则不要另外添加步骤来断言该元素是否存在、可见或已启用。仅当元素的可用性本身是有意义的前置条件或结果时,才添加可用性断言。
-
优先使用贴合业务的步骤名称,避免技术性强、参数过多的名称。 不佳:
When('I click {string} on {string}', ...)良好:When('I click the "Add" button in the product list', ...)\ -
对于多个相似操作,优先使用带数据表的单个步骤,而不是多个步骤。 当场景需要提供多个同类型的值(例如填写表单字段、添加列表项)时,应将其合并为一个带数据表的步骤,而不是为每个值重复一个步骤。 不佳:
When I fill "Name" with "Alice" And I fill "Email" with "[email protected]" And I fill "Role" with "Admin"良好:
When I fill the form with: | Name | Alice | | Email | [email protected] | | Role | Admin | -
为每个功能添加简短描述。 在每个
Feature:行的正下方,添加一到两句缩进的描述,说明该功能面向用户的主要用途。使用简单、具体且符合项目现有领域术语的表述。描述整个功能,不要列出场景、边缘情况或实现细节。在描述前后各留一个空行。 -
将新场景追加到末尾。 向现有功能文件添加场景时,应将其放在所有现有场景之后。除非用户明确要求特定位置,否则不要将其插入开头或现有场景之间。
作用域限定的步骤定义
优先使用以 @ 开头的目录,为特定功能域限定步骤定义的作用域。这可以避免常见步骤名称(例如 I should see {string} text)需要根据上下文使用不同实现时发生冲突。
有关作用域限定步骤的更多详情:https://vitalets.github.io/playwright-bdd/#/writing-steps/scoped?id=tags-from-path
作用域限定步骤示例结构
features/
├── fixtures.ts
├── @homepage/
│ ├── homepage.feature
│ └── steps.ts
├── @profile/
│ ├── profile.feature
│ └── steps.ts
└── shared-steps.ts
在 features/@homepage/steps.ts 中定义的步骤会自动限定到同一目录下的功能文件,无需显式添加 { tags: '@homepage' }。
功能文件示例
Feature: Shopping cart
Customers can collect products they intend to buy and review the current cart contents.
Scenario: Add item to cart
Given I am on a product page
And the cart is empty
When I add the product "banana" to the cart
Then the cart contains "banana"
And the cart badge shows 1
步骤定义示例
import { Given, When, Then } from './fixtures';
Given('I am on a product page', async ({ page }) => {
await page.goto('/product');
});
When('I add the product {string} to the cart', async ({ page }, name: string) => {
await page.getByRole('button', { name: `Add ${name}` }).click();
});
Then('the cart badge should show {int}', async ({ page }, count: number) => {
await expect(page.locator('.cart-badge')).toHaveText(String(count));
});