playwright
4
总安装量
3
周安装量
#53717
全站排名
安装命令
npx skills add https://github.com/poletron/custom-rules --skill playwright
Agent 安装分布
github-copilot
3
mcpjam
2
droid
2
gemini-cli
2
windsurf
2
zencoder
2
Skill 文档
When to Use
Use this skill when:
- Writing E2E tests with Playwright
- Creating Page Object Models
- Setting up selectors and assertions
- Using MCP for test exploration
MCP Workflow (MANDATORY If Available)
â ï¸ If you have Playwright MCP tools, ALWAYS use them BEFORE creating any test:
- Navigate to target page
- Take snapshot to see page structure and elements
- Interact with forms/elements to verify exact user flow
- Take screenshots to document expected states
- Verify page transitions through complete flow
- Document actual selectors from snapshots
- Only after exploring create test code with verified selectors
Why This Matters:
- â Precise tests – exact steps needed, no assumptions
- â Accurate selectors – real DOM structure
- â Real flow validation
- â Avoid over-engineering
- â Prevent flaky tests
Critical Patterns
File Structure (REQUIRED)
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
File Naming:
- â
sign-up.spec.ts(all sign-up tests) - â
sign-up-critical-path.spec.ts(WRONG – no separate files)
Selector Priority (REQUIRED)
// 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");
// 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
Page Object Pattern (REQUIRED)
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");
}
async waitForNotification(): Promise<void> {
await this.page.waitForSelector('[role="status"]');
}
}
// 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();
}
}
Decision Tree
Need to click? â Use getByRole or getByTestId
Need to type? â Use getByLabel + fill()
Need to assert? â Use expect() with locator
Need to wait? â Auto-wait is built-in
Need reusable logic? â Create Page Object
Need shared utilities? â Add to helpers.ts
Scope Detection (ASK IF AMBIGUOUS)
| User Says | Action |
|---|---|
| “a test”, “one test”, “new test” | Create ONE test() in existing spec |
| “comprehensive tests”, “all tests”, “test suite” | Create full suite |
Code Examples
Test Pattern with Tags
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: "user@test.com", password: "pass123" });
await expect(page).toHaveURL("/dashboard");
}
);
});
Page Object Reuse
// â
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.signOut();
await signInPage.login(credentials);
});
Refactoring to BasePage
// Move to BasePage when used by multiple pages:
export class BasePage {
async waitForNotification(): Promise<void> {
await this.page.waitForSelector('[role="status"]');
}
}
// Move to helpers.ts for test data:
export function generateUniqueEmail(): string {
return `test.${Date.now()}@example.com`;
}
Commands
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 codegen # Generate tests
npx playwright show-report # View report
Test Documentation Format
### E2E Tests: {Feature Name}
**Suite ID:** `{SUITE-ID}`
**Priority:** `{critical|high|medium|low}`
**Tags:** @e2e, @{feature-name}
**Preconditions:**
- {Prerequisites}
### Flow Steps:
1. {Step 1}
2. {Step 2}
### Expected Result:
- {Expected outcome}
Resources
- Best Practices: best-practices.md