Skip to content
Testing
Skill

/e2e-playwright

Battle-tested Playwright E2E testing patterns for Next.js/React apps. Use when writing, running, debugging, or fixing Playwright tests. Also triggers on 'e2e', 'end-to-end', 'playwright', 'browser test', 'UI test', 'integration test with browser', 'flaky test', 'test keeps

From plugin
e2e-testing
159 skills2 hooks
Install
$ npx -y skills add burhankhatri/e2e-testing --skill e2e-playwright --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/e2e-playwright

Context preview

The summary Claude sees to decide when to auto-load this skill.

Battle-tested Playwright E2E testing patterns for Next.js/React apps. Use when writing, running, debugging, or fixing Playwright tests. Also triggers on 'e2e', 'end-to-end', 'playwright', 'browser test', 'UI test', 'integration test with browser', 'flaky test', 'test keeps

SKILL.md

e2e-playwright.SKILL.md
name: e2e-playwright
description: "Battle-tested Playwright E2E testing patterns for Next.js/React apps. Use when writing, running, debugging, or fixing Playwright tests. Also triggers on 'e2e', 'end-to-end', 'playwright', 'browser test', 'UI test', 'integration test with browser', 'flaky test', 'test keeps failing'. Covers locators, assertions, fixtures, auth, network mocking, flaky test diagnosis, Next.js-specific patterns, and debugging workflows."

Playwright E2E Testing

> Production-tested patterns from the TestDino Playwright Skill. Every pattern includes when (and when *not*) to use it.

Golden Rules

1. **`getByRole()` over CSS/XPath** — resilient to markup changes, mirrors how users see the page 2. **Never `page.waitForTimeout()`** — use `expect(locator).toBeVisible()` or `page.waitForURL()` 3. **Web-first assertions** — `expect(locator)` auto-retries; `expect(await locator.textContent())` does NOT 4. **Isolate every test** — no shared state, no execution-order dependencies 5. **`baseURL` in config** — zero hardcoded URLs in tests 6. **Retries: `2` in CI, `0` locally** — surface flakiness where it matters 7. **Traces: `'on-first-retry'`** — rich debugging artifacts without CI slowdown 8. **Fixtures over globals** — share state via `test.extend()`, not module-level variables 9. **One behavior per test** — multiple related `expect()` calls are fine 10. **Mock external services only** — never mock your own app; mock third-party APIs, payment gateways, email 11. **Real auth or stop** — never `test.skip(true, ...)` around missing login, never `.or(signIn)` assertions that pass on the auth wall. If auth setup doesn't exist, STOP and set up storage state with the user (one-time). A test that passes signed-out is not a feature test.

**Deep dives available in `references/` directory — read them when working on the relevant topic.**

---

Feature Tests vs Smoke Tests

Not all E2E tests are equal. Know what tier you're writing.

| Tier | What it tests | Example | Sufficient for feature coverage? | |------|--------------|---------|----------------------------------| | **Smoke** | Page loads, no 404, no crash | `goto('/canvas'); expect(heading).toBeVisible()` | **NO** — baseline only | | **Feature** | User completes a real workflow | Drag entry to project → rule created → future entries auto-link | **YES** — this is the goal | | **Navigation** | Links route correctly, active states work | Click "Canvas" in sidebar → URL is /canvas → heading visible | **Required when nav changes** |

**The rule:** Every feature shipped MUST have at least one tier-2 (feature) E2E test. Smoke tests are free but DO NOT count toward feature coverage.

**Ask yourself:** "If someone broke this feature tomorrow, would my E2E tests catch it?" If the answer is "only if they deleted the entire page" — you wrote smoke tests, not feature tests.

Navigation Tests — Required When Nav Changes

When you add or modify navigation (sidebar items, mobile tab bar, header links, route changes), you MUST write tests that verify:

1. Nav item is visible at the correct viewport (desktop sidebar, mobile tab bar) 2. Clicking it navigates to the correct URL 3. Destination page renders its primary content (not just "no 404") 4. Active/selected state highlights correctly

**Desktop + Mobile navigation test template:**

import { test, expect } from '@playwright/test';

test.describe('Navigation — Desktop', () => {
  test.use({ viewport: { width: 1280, height: 800 } });

  test('sidebar contains Canvas link and navigates correctly', async ({ page }) => {
    await page.goto('/');
    const sidebar = page.getByRole('navigation');
    const canvasLink = sidebar.getByRole('link', { name: 'Canvas' });
    await expect(canvasLink).toBeVisible();
    await canvasLink.click();
    await page.waitForURL('/canvas');
    await expect(page.getByRole('heading', { name: 'Canvas' })).toBeVisible();
  });
});

test.describe('Navigation — Mobile', () => {
  test.use({ viewport: { width: 375, height: 812 } });

  test('mobile tab bar contains Canvas and navigates correctly', async ({ page }) => {
    await page.goto('/');
    const tabBar = page.getByRole('navigation', { name: /mobile|tab/i });
    const canvasTab = tabBar.getByRole('link', { name: 'Canvas' });
    await expect(canvasTab).toBeVisible();
    await canvasTab.click();
    await page.waitForURL('/canvas');
    await expect(page.getByRole('heading', { name: 'Canvas' })).toBeVisible();
  });
});

Adapt names/selectors to the actual app. The structure is: find nav → find link → click → verify URL → verify content.

---

Next.js Config (App Router + Pages Router)

// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests/e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? '50%' : undefined,
  reporter: process.env.CI ? 'html' : 'list',

  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'on',
    video: 'retain-on-failure',
  },

  expect: {
    toHaveScreenshot: {
      maxDiffPixelRatio: 0.01,
      animations: 'disabled',
    },
  },

  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'mobile', use: { ...devices['iPhone 14'] } },
  ],

  webServer: {
    command: process.env.CI
      ? 'npm run build && npm run start'  // production build in CI
      : 'npm run dev',                    // dev server locally
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
    timeout: 120_000,
    env: {
      NODE_ENV: process.env.CI ? 'production' : 'test',
    },
  },
});

**Environment variables:** Next.js loads `.env.test` automatically when `NODE_ENV=test`. Use `.env.test` for non-secret test config (committed), `.env.test.local` for secrets (gitignored).

**Gitignore additions:**

.env*.local
Read more
Ships withe2e-testing

A set of 8 global skills for Claude Code that enforce disciplined, test-driven agentic development. Install once, use in any project.

Get the whole plugin

Other skills on e2e-testing.