关于 playwright
playwright 是一项用于创建端到端测试的技能,面向使用 Playwright 的测试编写者。它强调先检查页面和实际交互,再编写测试,并规范页面对象复用、选择器和测试文档。
主要功能
- 01创建端到端测试
- 02先探索页面再编写测试
- 03优先使用角色和标签选择器
- 04复用页面对象与共享辅助函数
- 05按规范组织测试和文档
适用场景
- 01为登录或注册流程编写测试
- 02核实表单交互与页面跳转
- 03为现有页面对象补充测试
- 04整理测试辅助函数和用例文档
MCP 相关
若有 Playwright MCP 工具,此技能要求先用其导航、查看页面快照、进行交互并截图,再编写测试;若不可用,则依据文档和代码分析。
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ gentleman-programming/ gentleman-skills --skill playwright安装后在 Claude Code 中输入/playwright确认已加载。
详细介绍
MCP 工作流(如可用则必须遵循)
⚠️ 如果你有 Playwright MCP 工具,创建任何测试之前都必须先使用这些工具:
- 导航到目标页面
- 拍摄快照,查看页面结构和元素
- 与表单和元素交互,验证确切的用户流程
- 截取屏幕截图,记录预期状态
- 验证页面跳转,覆盖完整流程(加载、成功、错误)
- 记录实际选择器(来自快照,使用真实的引用和标签)
- 只有探索完毕后,才能使用已验证的选择器编写测试代码
如果无法使用 MCP: 根据文档和代码分析创建测试。
这样做的重要性:
- ✅ 测试精准——准确覆盖所需步骤,不做假设
- ✅ 选择器准确——基于真实 DOM 结构,而非想象
- ✅ 验证真实流程——确认整个流程确实可用
- ✅ 避免过度设计——只为现有功能编写必要测试
- ✅ 避免不稳定的测试——实际探索能让测试更稳定
- ❌ 切勿假设 UI「应该」如何运行
文件结构
tests/
├── base-page.ts # Parent class for ALL pages
├── helpers.ts # Shared utilities
└── {page-name}/
├── {page-name}-page.ts # Page Object Model
├── {page-name}.spec.ts # ALL tests here (NO separate files!)
└── {page-name}.md # Test documentation
文件命名:
- ✅
sign-up.spec.ts(所有注册测试) - ✅
sign-up-page.ts(页面对象) - ✅
sign-up.md(文档) - ❌
sign-up-critical-path.spec.ts(错误——不要创建单独的文件) - ❌
sign-up-validation.spec.ts(错误)
选择器优先级(必须遵循)
// 1. BEST - getByRole for interactive elements
this.submitButton = page.getByRole("button", { name: "Submit" });
this.navLink = page.getByRole("link", { name: "Dashboard" });
// 2. BEST - getByLabel for form controls
this.emailInput = page.getByLabel("Email");
this.passwordInput = page.getByLabel("Password");
// 3. SPARINGLY - getByText for static content only
this.errorMessage = page.getByText("Invalid credentials");
this.pageTitle = page.getByText("Welcome");
// 4. LAST RESORT - getByTestId when above fail
this.customWidget = page.getByTestId("date-picker");
// ❌ AVOID fragile selectors
this.button = page.locator(".btn-primary"); // NO
this.input = page.locator("#email"); // NO
范围判断(存在歧义时先询问)
| 用户的说法 | 操作 |
|---|---|
| 「一个测试」「新增一个测试」「新测试」「添加测试」 | 在现有 spec 中创建一个 test() |
| 「全面测试」「所有测试」「测试套件」「生成测试」 | 创建完整测试套件 |
示例:
- 「为用户注册创建一个测试」→ 仅创建一个测试
- 「为登录页面生成端到端测试」→ 创建完整测试套件
- 「添加一个测试以验证表单验证」→ 在现有 spec 中添加一个测试
页面对象模式
import { Page, Locator, expect } from "@playwright/test";
// BasePage - ALL pages extend this
export class BasePage {
constructor(protected page: Page) {}
async goto(path: string): Promise<void> {
await this.page.goto(path);
await this.page.waitForLoadState("networkidle");
}
// Common methods go here (see Refactoring Guidelines)
async waitForNotification(): Promise<void> {
await this.page.waitForSelector('[role="status"]');
}
async verifyNotificationMessage(message: string): Promise<void> {
const notification = this.page.locator('[role="status"]');
await expect(notification).toContainText(message);
}
}
// Page-specific implementation
export interface LoginData {
email: string;
password: string;
}
export class LoginPage extends BasePage {
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
constructor(page: Page) {
super(page);
this.emailInput = page.getByLabel("Email");
this.passwordInput = page.getByLabel("Password");
this.submitButton = page.getByRole("button", { name: "Sign in" });
}
async goto(): Promise<void> {
await super.goto("/login");
}
async login(data: LoginData): Promise<void> {
await this.emailInput.fill(data.email);
await this.passwordInput.fill(data.password);
await this.submitButton.click();
}
async verifyCriticalOutcome(): Promise<void> {
await expect(this.page).toHaveURL("/dashboard");
}
}
复用页面对象(至关重要)
创建新的页面对象之前,务必检查现有页面对象!
// ✅ GOOD: Reuse existing page objects
import { SignInPage } from "../sign-in/sign-in-page";
import { HomePage } from "../home/home-page";
test("User can sign up and login", async ({ page }) => {
const signUpPage = new SignUpPage(page);
const signInPage = new SignInPage(page); // REUSE
const homePage = new HomePage(page); // REUSE
await signUpPage.signUp(userData);
await homePage.verifyPageLoaded(); // REUSE method
await homePage.signOut(); // REUSE method
await signInPage.login(credentials); // REUSE method
});
// ❌ BAD: Recreating existing functionality
export class SignUpPage extends BasePage {
async logout() { /* ... */ } // ❌ HomePage already has this
async login() { /* ... */ } // ❌ SignInPage already has this
}
指南:
- 首先检查
tests/中是否已有页面对象 - 导入并复用现有页面
- 仅在页面对象不存在时创建新的
- 如果测试需要多个页面,请确保所有页面对象都存在(如有需要则创建)
重构指南
在以下情况下移至 BasePage:
- ✅ 多个页面共用的导航辅助方法(
waitForPageLoad()、getCurrentUrl()) - ✅ 常见 UI 交互(通知、模态框、主题切换)
- ✅ 多个页面重复使用的验证模式(
isVisible()、waitForVisible()) - ✅ 适用于所有页面的错误处理
- ✅ 用于调试的屏幕截图工具
在以下情况下移至 helpers.ts:
- ✅ 测试数据生成(
generateUniqueEmail()、generateTestUser()) - ✅ 设置和清理辅助工具(
createTestUser()、cleanupTestData()) - ✅ 自定义断言(
expectNotificationToContain()) - ✅ 用于测试设置的 API 辅助工具(
seedDatabase()、resetState()) - ✅ 时间辅助工具(
waitForCondition()、retryAction())
重构前(不佳):
// Repeated in multiple page objects
export class SignUpPage extends BasePage {
async waitForNotification(): Promise<void> {
await this.page.waitForSelector('[role="status"]');
}
}
export class SignInPage extends BasePage {
async waitForNotification(): Promise<void> {
await this.page.waitForSelector('[role="status"]'); // DUPLICATED!
}
}
重构后(良好):
// BasePage - shared across all pages
export class BasePage {
async waitForNotification(): Promise<void> {
await this.page.waitForSelector('[role="status"]');
}
}
// helpers.ts - data generation
export function generateUniqueEmail(): string {
return `test.${Date.now()}@example.com`;
}
export function generateTestUser() {
return {
name: "Test User",
email: generateUniqueEmail(),
password: "TestPassword123!",
};
}
带标签的测试模式
import { test, expect } from "@playwright/test";
import { LoginPage } from "./login-page";
test.describe("Login", () => {
test("User can login successfully",
{ tag: ["@critical", "@e2e", "@login", "@LOGIN-E2E-001"] },
async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login({ email: "[email protected]", password: "pass123" });
await expect(page).toHaveURL("/dashboard");
}
);
});
标签类别:
- 优先级:
@critical、@high、@medium、@low - 类型:
@e2e - 功能:
@signup、@signin、@dashboard - 测试 ID:
@SIGNUP-E2E-001、@LOGIN-E2E-002
测试文档格式({page-name}.md)
### E2E Tests: {Feature Name}
**Suite ID:** `{SUITE-ID}`
**Feature:** {Feature description}
---
## Test Case: `{TEST-ID}` - {Test case title}
**Priority:** `{critical|high|medium|low}`
**Tags:**
- type → @e2e
- feature → @{feature-name}
**Description/Objective:** {Brief description}
**Preconditions:**
- {Prerequisites for test to run}
- {Required data or state}
### Flow Steps:
1. {Step 1}
2. {Step 2}
3. {Step 3}
### Expected Result:
- {Expected outcome 1}
- {Expected outcome 2}
### Key verification points:
- {Assertion 1}
- {Assertion 2}
### Notes:
- {Additional considerations}
文档规则:
- ❌ 不要包含通用的测试运行说明
- ❌ 不要解释文件结构
- ❌ 不要提供代码示例或教程
- ❌ 不要包含故障排除章节
- ✅ 只关注具体的测试用例
- ✅ 尽可能控制在 60 行以内
命令
npx playwright test # Run all
npx playwright test --grep "login" # Filter by name
npx playwright test --ui # Interactive UI
npx playwright test --debug # Debug mode
npx playwright test tests/login/ # Run specific folder
关键词
playwright,端到端,测试,页面对象模型,选择器,端到端,mcp
评论
支持 Markdown