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
169 skills2 hooks

Claude Code

$ npx -y skills add burhankhatri/e2e-testing --skill e2e-playwright --agent claude-code

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
@burhankhatri
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.

Stars on GitHub

Momentum

stars in total
16
Star history of burhankhatri/e2e-testing on GitHub, a chart by star-history.com

Other skills on e2e-testing.

code-review

code-review

Use when a major project step has been completed and needs review against the plan and coding standards. Also use when someone…

start

start

Master orchestrator skill that kicks off the full development pipeline. Routes tasks through the correct skill chain (brainstorm,…

tdd

tdd

Enforces strict test-driven development. Use when implementing ANY feature, bugfix, or refactor — before writing implementation…

More skills from other plugins.

api-review

api-review

Produce the "Playwright 1.xx API Review" artifact for a range of commits - the public API additions and the breaking or behavior…

@microsoft@microsoftInstall Skill
add-e2e-selectors

add-e2e-selectors

Add reliable @grafana/e2e-selectors to interactive elements and key containers in the Grafana frontend. Use when adding e2e…

grafana5d78k
@grafana@grafanaInstall Skill
promptfoo-evals

promptfoo-evals

Write, refine, run, and QA promptfoo evaluation suites: promptfooconfig.yaml, prompts, providers, vars, tests, assertions,…

promptfoo13d27k
@promptfoo@promptfooInstall Skill
deepeval

deepeval

DeepEval evaluation workflow for AI agents and LLM applications. TRIGGER when the user wants to evaluate or improve an AI agent,…

@confident-ai@confident-aiInstall Skill

Questions about e2e-playwright.

Which coding agents can use e2e-playwright?
e2e-playwright installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.