Skip to main content

e2e-tester

Playwright E2E testing patterns. Trigger: When writing Playwright E2E tests (Page Object Model, selectors, MCP exploration workflow).

소스 정보

저장소
PageAI-Pro/ralph-loop
최근 소스 활동
2026년 2월 22일 16:31
감지된 SKILL.md 언어
영어
스타
311
포크
46

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
e2e-tester
description
Playwright E2E testing patterns. Trigger: When writing Playwright E2E tests (Page Object Model, selectors, MCP exploration workflow).
metadata
{"scope":["root","ui"],"auto_invoke":"Writing Playwright E2E tests"}
allowed-tools
Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task
Before you start: - check if any existing tests cover the functionality you are testing - check if a unit tests can cover the feature better - analyze if a end to end test brings any value over a unit test If the answer is yes to any of the above, proceed with the end to end test creation. ## 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 (loading, success, error) 6. **Document actual selectors** from snapshots (use real refs and labels) 7. **Only after exploring** create test code with verified selectors **If MCP NOT available:** Proceed with test creation based on docs and code analysis. **Why This Matters:** - ✅ Precise tests - exact steps needed, no assumptions - ✅ Accurate selectors - real DOM structure, not imagined - ✅ Real flow validation - verify journey actually works - ✅ Avoid over-engineering - minimal tests for what exists - ✅ Prevent flaky tests - real exploration = stable tests - ❌ Never assume how UI "should" work ## Waiting Strategies (CRITICAL) ```typescript // ❌ NEVER use fixed waits await page.waitForTimeout(2000); // ✅ Wait for specific conditions await expect(element).toBeVisible(); await page.waitForResponse(resp => resp.url().includes('/api/data')); await page.waitForURL('**/dashboard'); await expect(page.getByText('Success')).toBeVisible({ timeout: 10000 }); // ✅ For SPAs, prefer explicit waits over networkidle await page.goto('/app', { waitUntil: 'domcontentloaded' }); await expect(page.getByRole('main')).toBeVisible(); ``` ## File Structure ``` 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-page.ts` (page object) - ✅ `sign-up.md` (documentation) - ❌ `sign-up-critical-path.spec.ts` (WRONG - no separate files) - ❌ `sign-up-validation.spec.ts` (WRONG) ## Selector Priority (REQUIRED) ```typescript // 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 ``` ## Scope Detection (ASK IF AMBIGUOUS) | User Says | Action | | ------------------------------------------------------------------ | ---------------------------------- | | "a test", "one test", "new test", "add test" | Create ONE test() in existing spec | | "comprehensive tests", "all tests", "test suite", "generate tests" | Create full suite | **Examples:** - "Create a test for user sign-up" → ONE test only - "Generate E2E tests for login page" → Full suite - "Add a test to verify form validation" → ONE test to existing spec ## Page Object Pattern ```typescript 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"); } } ``` ## Page Object Reuse (CRITICAL) **Always check existing page objects before creating new ones!** ```typescript // ✅ 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 } ``` **Guidelines:** - Check `tests/` for existing page objects first - Import and reuse existing pages - Create page objects only when page doesn't exist - If test requires multiple pages, ensure all page objects exist (create if needed) ## Refactoring Guidelines ### Move to `BasePage` when: - ✅ Navigation helpers used by multiple pages (`waitForPageLoad()`, `getCurrentUrl()`) - ✅ Common UI interactions (notifications, modals, theme toggles) - ✅ Verification patterns repeated across pages (`isVisible()`, `waitForVisible()`) - ✅ Error handling that applies to all pages - ✅ Screenshot utilities for debugging ### Move to `helpers.ts` when: - ✅ Test data generation (`generateUniqueEmail()`, `generateTestUser()`) - ✅ Setup/teardown utilities (`createTestUser()`, `cleanupTestData()`) - ✅ Custom assertions (`expectNotificationToContain()`) - ✅ API helpers for test setup (`seedDatabase()`, `resetState()`) - ✅ Time utilities (`waitForCondition()`, `retryAction()`) **Before (BAD):** ```typescript // 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! } } ``` **After (GOOD):** ```typescript // 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!", }; } ``` ## Test Pattern with Tags ```typescript 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"); } ); }); ``` **Tag Categories:** - Priority: `@critical`, `@high`, `@medium`, `@low` - Type: `@e2e` - Feature: `@signup`, `@signin`, `@dashboard` - Test ID: `@SIGNUP-E2E-001`, `@LOGIN-E2E-002` ## Test Documentation Format ({page-name}.md) ```markdown ### 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} ``` **Documentation Rules:** - ❌ NO general test running instructions - ❌ NO file structure explanations - ❌ NO code examples or tutorials - ❌ NO troubleshooting sections - ✅ Focus ONLY on specific test case - ✅ Keep under 60 lines when possible ## Authentication State Reuse ```typescript // auth.setup.ts - Run once, reuse across tests import { test as setup } from "@playwright/test"; setup("authenticate", async ({ page }) => { await page.goto("/login"); await page.getByLabel("Email").fill("user@test.com"); await page.getByLabel("Password").fill("password"); await page.getByRole("button", { name: "Sign in" }).click(); await page.waitForURL("/dashboard"); await page.context().storageState({ path: ".auth/user.json" }); }); // playwright.config.ts projects: [ { name: "auth", testMatch: /auth\.setup\.ts/ }, { name: "logged-in", dependencies: ["auth"], use: { storageState: ".auth/user.json" } }, { name: "logged-out", use: { storageState: { cookies: [], origins: [] } } } ] ``` ## API Mocking ```typescript // Mock API responses for isolated tests await page.route("**/api/users", route => route.fulfill({ json: { users: [{ id: 1, name: "Test" }] } }) ); // Mock error states
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기