Complete Claude Code plugin with: - 9 skills (/pw:init, generate, review, fix, migrate, coverage, testrail, browserstack, report) - 3 specialized agents (test-architect, test-debugger, migration-planner) - 55 test case templates across 11 categories (auth, CRUD, checkout, search, forms, dashboard, settings, onboarding, notifications, API, accessibility) - TestRail MCP server (TypeScript) — 8 tools for bidirectional sync - BrowserStack MCP server (TypeScript) — 7 tools for cross-browser testing - Smart hooks (auto-validate tests, auto-detect Playwright projects) - 6 curated reference docs (golden rules, locators, assertions, fixtures, pitfalls, flaky tests) - Leverages Claude Code built-ins (/batch, /debug, Explore subagent) - Zero-config for core features; TestRail/BrowserStack via env vars - Both TypeScript and JavaScript support throughout Co-authored-by: Leo <leo@openclaw.ai>
4.2 KiB
Playwright Anti-Patterns Reference
1. Using waitForTimeout()
Bad:
await page.click('.submit');
await page.waitForTimeout(3000);
await expect(page.locator('.result')).toBeVisible();
Good:
await page.getByRole('button', { name: 'Submit' }).click();
await expect(page.getByTestId('result')).toBeVisible();
Why: Arbitrary waits slow tests and cause flakiness. Web-first assertions auto-retry.
2. Non-Web-First Assertions
Bad:
const text = await page.textContent('.message');
expect(text).toBe('Success');
Good:
await expect(page.getByText('Success')).toBeVisible();
Why: expect(locator) auto-retries until timeout. expect(value) checks once and fails.
3. Hardcoded URLs
Bad:
await page.goto('http://localhost:3000/login');
Good:
await page.goto('/login');
Why: baseURL in config handles the host. Tests break across environments with hardcoded URLs.
4. CSS/XPath When Role-Based Exists
Bad:
await page.click('#submit-btn');
await page.locator('.nav-link.active').click();
Good:
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByRole('link', { name: 'Dashboard' }).click();
Why: Role-based locators survive CSS renames, class refactors, and component library changes.
5. Missing await
Bad:
page.goto('/dashboard');
expect(page.getByText('Welcome')).toBeVisible();
Good:
await page.goto('/dashboard');
await expect(page.getByText('Welcome')).toBeVisible();
Why: Missing await causes race conditions. Tests pass sometimes, fail others.
6. Shared Mutable State
Bad:
let userId: string;
test('create user', async ({ page }) => {
// ... creates user, sets userId
userId = '123';
});
test('edit user', async ({ page }) => {
await page.goto(`/users/${userId}`); // depends on previous test
});
Good:
test('edit user', async ({ page }) => {
// Create user via API in this test's setup
const userId = await createUserViaAPI();
await page.goto(`/users/${userId}`);
});
Why: Tests must be independent. Shared state causes order-dependent failures.
7. Execution Order Dependencies
Bad:
test('step 1: fill form', async ({ page }) => { ... });
test('step 2: submit form', async ({ page }) => { ... });
test('step 3: verify result', async ({ page }) => { ... });
Good:
test('should fill and submit form successfully', async ({ page }) => {
// All steps in one test
});
Why: Playwright runs tests in parallel by default. Order-dependent tests fail randomly.
8. Tests Over 50 Lines
Split into focused tests. Each test should verify one behavior.
9. Magic Strings
Bad:
await page.getByLabel('Email').fill('admin@test.com');
Good:
const TEST_USER = { email: 'admin@test.com', password: 'Test123!' };
await page.getByLabel('Email').fill(TEST_USER.email);
10. Missing Error Cases
If you test the happy path, also test:
- Invalid input
- Empty state
- Network error
- Permission denied
- Timeout/loading state
11. Using page.evaluate() Unnecessarily
Bad:
const text = await page.evaluate(() => document.querySelector('.title')?.textContent);
Good:
await expect(page.getByRole('heading')).toHaveText('Expected Title');
12. Deep Nesting
Keep test.describe() to max 2 levels. More makes tests hard to find and maintain.
13. Generic Test Names
Bad: test('test 1'), test('should work'), test('login test')
Good: test('should show error when email is invalid'), test('should redirect to dashboard after successful login')
14-20. Style Issues
- No page objects for complex pages → create them
- Inline data → use factories or fixtures
- Missing a11y assertions → add
toHaveAttribute('role', ...) - No visual regression → add
toHaveScreenshot()for key pages - Not checking console errors → add
page.on('console', ...) - Using
networkidle→ use specific assertions instead - No
test.describe()→ group related tests