Loading...
Loading...
End-to-end testing and click-verification of web apps with Playwright — install, write specs, drive authenticated pages, take screenshots, and run in CI. Use when verifying a web change actually works in a real browser, testing user flows, debugging a UI bug live, or confirming an authed/gated page before shipping. Keywords Playwright, e2e, browser test, click test, screenshot, headless.
npx skill4agent add 5dive-ai/skills playwright-e2enextjs-appCore principle: a green build is not a working feature. Before calling an interactive change done, click through the actual rendered page in a browser. Especially for authed routes, modals, and forms — those are exactly where "looks fine in code" silently breaks.
npm install -D @playwright/test
npx playwright install chromium # or: --with-deps on CI / fresh boxes// playwright.config.ts
import { defineConfig } from '@playwright/test'
export default defineConfig({
testDir: './e2e',
use: { baseURL: 'http://localhost:3000', trace: 'on-first-retry' },
webServer: { command: 'npm run dev', url: 'http://localhost:3000', reuseExistingServer: true },
})// e2e/home.spec.ts
import { test, expect } from '@playwright/test'
test('home renders and CTA navigates', async ({ page }) => {
await page.goto('/')
await expect(page.getByRole('heading', { name: /welcome/i })).toBeVisible()
await page.getByRole('link', { name: 'Get started' }).click()
await expect(page).toHaveURL(/.*dashboard/)
})npx playwright test # headless, all specs
npx playwright test --headed # watch it
npx playwright test home.spec.ts -g CTA # filter
npx playwright show-report # HTML report after a runpage.getByRole('button', { name: 'Save' })page.getByLabel('Email')page.getByPlaceholder(…)page.getByText(…)page.getByTestId('submit')data-testidwaitForTimeoutawait expect(locator).toBeVisible()page.waitForURL(…)// e2e/auth.setup.ts (run as a setup project)
import { test as setup } from '@playwright/test'
setup('authenticate', async ({ page }) => {
await page.goto('/sign-in')
await page.getByLabel('Email').fill(process.env.TEST_EMAIL!)
await page.getByLabel('Password').fill(process.env.TEST_PASSWORD!)
await page.getByRole('button', { name: 'Sign in' }).click()
await page.waitForURL('**/dashboard')
await page.context().storageState({ path: 'e2e/.auth/user.json' })
})// playwright.config.ts — wire the setup + reuse the session
projects: [
{ name: 'setup', testMatch: /auth\.setup\.ts/ },
{ name: 'app', use: { storageState: 'e2e/.auth/user.json' }, dependencies: ['setup'] },
]// Mint a short-lived sign-in token server-side, then exchange it in the browser.
// Exact call depends on the provider; the shape is: get a ticket → land on a URL that
// consumes it → save storageState.
await page.goto(`/sign-in#__token=${signInToken}`) // provider-specific consume step
await page.waitForURL('**/dashboard')
await page.context().storageState({ path: 'e2e/.auth/user.json' })// verify.mjs — node verify.mjs
import { chromium } from 'playwright'
const b = await chromium.launch()
const p = await b.newPage()
await p.goto('https://your-app.example.com/', { waitUntil: 'domcontentloaded' })
await p.screenshot({ path: 'shot.png', fullPage: true })
console.log('title:', await p.title())
await b.close()networkidlewaitUntil: 'domcontentloaded'page.getByRole('button', { name: /accept/i }).click().catch(() => {})# .github/workflows/e2e.yml
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npx playwright test
- uses: actions/upload-artifact@v4
if: failure()
with: { name: playwright-report, path: playwright-report }npx playwright test --debug # step through with the inspector
npx playwright codegen localhost:3000 # record clicks → generated spec
npx playwright show-trace trace.zip # time-travel a failed run