playwright

📁 poletron/custom-rules 📅 Jan 26, 2026
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:

  1. Navigate to target page
  2. Take snapshot to see page structure and elements
  3. Interact with forms/elements to verify exact user flow
  4. Take screenshots to document expected states
  5. Verify page transitions through complete flow
  6. Document actual selectors from snapshots
  7. 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