|
| 1 | +--- |
| 2 | +description: Create a Playwright E2E test with explicit success criteria |
| 3 | +argument-hint: <feature-or-page-name> |
| 4 | +allowed-tools: Read, Write, Grep, Glob, Bash, AskUserQuestion |
| 5 | +--- |
| 6 | + |
| 7 | +# Create E2E Test |
| 8 | + |
| 9 | +Create a Playwright E2E test for: **$ARGUMENTS** |
| 10 | + |
| 11 | +## ABSOLUTE RULES — Read Before Writing a Single Line |
| 12 | + |
| 13 | +### 1. Every test MUST have explicit success criteria |
| 14 | + |
| 15 | +"Page loads" is NOT a test. Every `test()` block MUST assert: |
| 16 | +- **URL** — verify the page navigated to the correct URL |
| 17 | +- **Visible elements** — verify key elements are present and visible |
| 18 | +- **Correct data** — verify the right content is displayed |
| 19 | +- **Error states** — verify error messages show when expected |
| 20 | + |
| 21 | +```typescript |
| 22 | +// CORRECT — explicit success criteria |
| 23 | +test('dashboard shows user data after login', async ({ page }) => { |
| 24 | + await page.goto('/login'); |
| 25 | + await page.fill('[name="email"]', 'test@example.com'); |
| 26 | + await page.fill('[name="password"]', 'password123'); |
| 27 | + await page.click('button[type="submit"]'); |
| 28 | + |
| 29 | + // ✅ Verify URL changed |
| 30 | + await expect(page).toHaveURL('/dashboard'); |
| 31 | + // ✅ Verify key element visible |
| 32 | + await expect(page.locator('h1')).toContainText('Welcome'); |
| 33 | + // ✅ Verify correct data displayed |
| 34 | + await expect(page.locator('[data-testid="user-email"]')).toContainText('test@example.com'); |
| 35 | + // ✅ Verify sidebar loaded |
| 36 | + await expect(page.locator('nav.sidebar')).toBeVisible(); |
| 37 | +}); |
| 38 | + |
| 39 | +// WRONG — this passes even when completely broken |
| 40 | +test('dashboard loads', async ({ page }) => { |
| 41 | + await page.goto('/dashboard'); |
| 42 | + // no assertions! |
| 43 | +}); |
| 44 | +``` |
| 45 | + |
| 46 | +### 2. A test is FINISHED when it has ALL of these |
| 47 | + |
| 48 | +A test is NOT done until it has: |
| 49 | +- [ ] At least one `await expect(page).toHaveURL()` assertion |
| 50 | +- [ ] At least one `await expect(locator).toBeVisible()` assertion |
| 51 | +- [ ] At least one content/data verification (`toContainText`, `toHaveValue`, etc.) |
| 52 | +- [ ] Error case coverage (what happens when it fails?) |
| 53 | +- [ ] No `// TODO` or placeholder assertions |
| 54 | + |
| 55 | +If you cannot check ALL of these, the test is incomplete. Say so and explain what's missing. |
| 56 | + |
| 57 | +### 3. Test structure — ALWAYS follow this pattern |
| 58 | + |
| 59 | +```typescript |
| 60 | +import { test, expect } from '@playwright/test'; |
| 61 | + |
| 62 | +test.describe('[Feature Name]', () => { |
| 63 | + test.describe('happy path', () => { |
| 64 | + test('should [specific behavior] when [specific condition]', async ({ page }) => { |
| 65 | + // ARRANGE — navigate, set up state |
| 66 | + // ACT — perform the user action |
| 67 | + // ASSERT — verify SPECIFIC outcomes (URL, elements, data) |
| 68 | + }); |
| 69 | + }); |
| 70 | + |
| 71 | + test.describe('error handling', () => { |
| 72 | + test('should show error when [invalid input]', async ({ page }) => { |
| 73 | + // Test the failure mode |
| 74 | + }); |
| 75 | + }); |
| 76 | + |
| 77 | + test.describe('edge cases', () => { |
| 78 | + test('should handle [empty state / max values / etc]', async ({ page }) => { |
| 79 | + // Test boundaries |
| 80 | + }); |
| 81 | + }); |
| 82 | +}); |
| 83 | +``` |
| 84 | + |
| 85 | +### 4. Port configuration — ALWAYS use test ports |
| 86 | + |
| 87 | +E2E tests run on TEST ports, never dev ports: |
| 88 | + |
| 89 | +| Service | Test Port | Base URL | |
| 90 | +|-----------|-----------|----------------------------| |
| 91 | +| Website | 4000 | http://localhost:4000 | |
| 92 | +| API | 4010 | http://localhost:4010 | |
| 93 | +| Dashboard | 4020 | http://localhost:4020 | |
| 94 | + |
| 95 | +The `baseURL` is already set in `playwright.config.ts`. Use relative paths: |
| 96 | + |
| 97 | +```typescript |
| 98 | +// CORRECT — uses baseURL from config |
| 99 | +await page.goto('/dashboard'); |
| 100 | + |
| 101 | +// WRONG — hardcoded URL |
| 102 | +await page.goto('http://localhost:3000/dashboard'); |
| 103 | +``` |
| 104 | + |
| 105 | +### 5. What to test for each page type |
| 106 | + |
| 107 | +**For a page/route:** |
| 108 | +- URL is correct after navigation |
| 109 | +- Page title / heading is present |
| 110 | +- Key UI elements are visible (nav, sidebar, footer, etc.) |
| 111 | +- Dynamic data is loaded and displayed |
| 112 | +- Links navigate to correct destinations |
| 113 | +- Responsive behavior (if applicable) |
| 114 | + |
| 115 | +**For a form:** |
| 116 | +- Empty form shows proper validation messages |
| 117 | +- Valid submission succeeds (verify success state) |
| 118 | +- Invalid input shows specific error messages |
| 119 | +- Submit button disabled during processing |
| 120 | +- Form clears or redirects after success |
| 121 | + |
| 122 | +**For an API endpoint:** |
| 123 | +- Correct response status code |
| 124 | +- Response body matches expected shape |
| 125 | +- Error responses have proper status codes and messages |
| 126 | +- Authentication/authorization is enforced |
| 127 | + |
| 128 | +**For authentication:** |
| 129 | +- Login with valid credentials succeeds |
| 130 | +- Login with invalid credentials shows error |
| 131 | +- Protected pages redirect to login |
| 132 | +- Logout clears session |
| 133 | +- Token expiry is handled |
| 134 | + |
| 135 | +### 6. Naming convention |
| 136 | + |
| 137 | +File: `tests/e2e/[feature-name].spec.ts` |
| 138 | + |
| 139 | +Examples: |
| 140 | +- `tests/e2e/auth-login.spec.ts` |
| 141 | +- `tests/e2e/dashboard-overview.spec.ts` |
| 142 | +- `tests/e2e/api-users.spec.ts` |
| 143 | +- `tests/e2e/settings-profile.spec.ts` |
| 144 | + |
| 145 | +## Step 0 — Branch Safety Check |
| 146 | + |
| 147 | +Before creating any files, check the current branch: |
| 148 | + |
| 149 | +```bash |
| 150 | +git branch --show-current |
| 151 | +``` |
| 152 | + |
| 153 | +- If on `main` or `master`: **STOP.** Warn the user and suggest: |
| 154 | + - `/worktree test-<feature>` — creates isolated branch + directory (recommended) |
| 155 | + - `git checkout -b test/<feature>` — creates a branch in the current directory |
| 156 | +- If on a feature branch: proceed |
| 157 | +- If not a git repo: proceed (skip this check) |
| 158 | + |
| 159 | +## Step 1 — Gather Information |
| 160 | + |
| 161 | +Before writing the test: |
| 162 | + |
| 163 | +1. **Read the source code** for the feature/page being tested |
| 164 | +2. **Identify all assertions** — what URLs, elements, and data should be verified? |
| 165 | +3. **Identify error states** — what can go wrong? What should the user see? |
| 166 | +4. **Check for test data** — does the test need seeded data? Mock API responses? |
| 167 | + |
| 168 | +## Step 2 — Ask What to Verify (if not obvious) |
| 169 | + |
| 170 | +If the feature has multiple possible success criteria, ask the user: |
| 171 | + |
| 172 | +Use AskUserQuestion to clarify: |
| 173 | +- "What specific elements should be visible on this page?" |
| 174 | +- "What data should be displayed after this action?" |
| 175 | +- "What error message should appear for invalid input?" |
| 176 | + |
| 177 | +## Step 3 — Write the Test |
| 178 | + |
| 179 | +Create the test file at `tests/e2e/[feature-name].spec.ts` following ALL rules above. |
| 180 | + |
| 181 | +Every test file MUST include: |
| 182 | +1. At least one happy-path test |
| 183 | +2. At least one error-case test |
| 184 | +3. Explicit assertions in every `test()` block |
| 185 | + |
| 186 | +## Step 4 — Verification Checklist |
| 187 | + |
| 188 | +After writing, verify: |
| 189 | + |
| 190 | +- [ ] File is at `tests/e2e/[name].spec.ts` |
| 191 | +- [ ] Every `test()` has at least 3 assertions (URL, element, data) |
| 192 | +- [ ] Error cases are covered |
| 193 | +- [ ] No hardcoded ports (uses baseURL from config) |
| 194 | +- [ ] No `// TODO` placeholders |
| 195 | +- [ ] Test names describe behavior: "should [verb] when [condition]" |
| 196 | +- [ ] No `any` types |
| 197 | +- [ ] No `.only` left in the code |
| 198 | + |
| 199 | +## Running Tests |
| 200 | + |
| 201 | +```bash |
| 202 | +# Run all E2E tests (spawns servers automatically on test ports) |
| 203 | +pnpm test:e2e |
| 204 | + |
| 205 | +# Run a specific test file |
| 206 | +pnpm test:e2e tests/e2e/auth-login.spec.ts |
| 207 | + |
| 208 | +# Run with UI mode (debug) |
| 209 | +pnpm test:e2e:ui |
| 210 | + |
| 211 | +# Run headed (see the browser) |
| 212 | +pnpm test:e2e:headed |
| 213 | + |
| 214 | +# View the last test report |
| 215 | +pnpm test:e2e:report |
| 216 | +``` |
| 217 | + |
| 218 | +## RuleCatch Report |
| 219 | + |
| 220 | +After the test file is created and verified, check RuleCatch: |
| 221 | + |
| 222 | +- If the RuleCatch MCP server is available: query for violations in the new test file |
| 223 | +- Report any violations found (missing assertions, TypeScript issues, etc.) |
| 224 | +- If no MCP: suggest checking the RuleCatch dashboard |
0 commit comments