All topics
Testing · Learning hub

Playwright notes for developers

Master Playwright with a curated set of 1 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore Testing notes
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 test

Writing 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 regression

Auth & 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' }]),
  });
});

Keep your Playwright knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever