Playwright
Playwright E2E Testing
Playwright E2E Testing Setup & Configuration // playwright.config.ts import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ tes…
Playwright E2E Testing
Setup & Configuration
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
timeout: 30_000,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [['html', { open: 'never' }]],
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry', // record trace on retry
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'Mobile Chrome', use: { ...devices['Pixel 5'] } },
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
});
// CLI commands
// npx playwright test — run all tests
// npx playwright test --ui — interactive UI mode
// npx playwright test --headed — show browser
// npx playwright test -g "login" — grep specific test
// npx playwright test --debug — debug mode (step through)
// npx playwright show-report — open HTML report
// npx playwright codegen localhost:3000 — record testWriting Tests
import { test, expect } from '@playwright/test';
// Basic test
test('homepage has title', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/DevRecall/);
await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible();
});
// User flow test
test('user can sign up and create a note', async ({ page }) => {
// Navigate & fill form
await page.goto('/signup');
await page.getByLabel('Email').fill('test@example.com');
await page.getByLabel('Password').fill('Password123!');
await page.getByRole('button', { name: 'Sign up' }).click();
// Wait for navigation
await page.waitForURL('/dashboard');
await expect(page.getByText('Welcome')).toBeVisible();
// Interact with UI
await page.getByRole('button', { name: 'New folder' }).click();
await page.getByPlaceholder('Folder name').fill('My Notes');
await page.keyboard.press('Enter');
await expect(page.getByText('My Notes')).toBeVisible();
});
// Locator strategies (prefer accessible selectors)
page.getByRole('button', { name: 'Submit' })
page.getByLabel('Email address')
page.getByPlaceholder('Search...')
page.getByText('Welcome back')
page.getByTestId('user-menu') // data-testid attribute
page.locator('.submit-btn') // CSS (avoid if possible)
page.locator('[data-state="open"]') // attribute
// Assertions
await expect(page).toHaveURL('/dashboard');
await expect(element).toBeVisible();
await expect(element).toBeHidden();
await expect(element).toBeEnabled();
await expect(element).toBeDisabled();
await expect(element).toHaveText('Hello');
await expect(element).toContainText('Hell');
await expect(element).toHaveValue('alice@example.com');
await expect(element).toHaveAttribute('href', '/about');
await expect(element).toHaveCount(5);
await expect(page).toHaveScreenshot(); // visual regressionAuth & Fixtures
// Save auth state — avoid re-logging in every test
// e2e/auth.setup.ts
import { test as setup } from '@playwright/test';
const authFile = 'playwright/.auth/user.json';
setup('authenticate', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('test@example.com');
await page.getByLabel('Password').fill('Password123!');
await page.getByRole('button', { name: 'Login' }).click();
await page.waitForURL('/dashboard');
await page.context().storageState({ path: authFile }); // save cookies/localStorage
});
// playwright.config.ts — use saved state
projects: [
{ name: 'setup', testMatch: /auth.setup/ },
{
name: 'authenticated',
use: { storageState: 'playwright/.auth/user.json' },
dependencies: ['setup'],
},
],
// Custom fixtures
import { test as base } from '@playwright/test';
type Fixtures = { loggedInPage: Page };
export const test = base.extend<Fixtures>({
loggedInPage: async ({ page }, use) => {
await page.goto('/login');
await page.getByLabel('Email').fill('test@example.com');
await page.getByLabel('Password').fill('Password123!');
await page.getByRole('button', { name: 'Login' }).click();
await page.waitForURL('/dashboard');
await use(page); // provide fixture to test
},
});
// Mock API responses
await page.route('**/api/users', route => {
route.fulfill({
status: 200,
body: JSON.stringify([{ id: '1', name: 'Alice' }]),
});
});