DEV Community

Atlas Whoff
Atlas Whoff

Posted on

Playwright End-to-End Testing: Test Your App Like a Real User

Playwright End-to-End Testing: Test Your App Like a Real User

Unit tests verify logic. E2E tests verify the entire flow — signup, payment, dashboard — works together. Playwright runs in real browsers and catches what unit tests never see.

Install

npm init playwright@latest
# Installs browsers, creates playwright.config.ts, example tests
Enter fullscreen mode Exit fullscreen mode

Config

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

export default defineConfig({
  testDir: './e2e',
  fullyParallel: true,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'Mobile Safari', use: { ...devices['iPhone 14'] } },
  ],
  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
  },
});
Enter fullscreen mode Exit fullscreen mode

Your First Test

// e2e/auth.spec.ts
import { test, expect } from '@playwright/test';

test('user can sign up and access dashboard', async ({ page }) => {
  await page.goto('/signup');

  await page.fill('[name=email]', 'test@example.com');
  await page.fill('[name=password]', 'SecurePass123!');
  await page.click('button[type=submit]');

  await expect(page).toHaveURL('/dashboard');
  await expect(page.getByText('Welcome')).toBeVisible();
});
Enter fullscreen mode Exit fullscreen mode

Page Object Model

// e2e/pages/LoginPage.ts
import { Page, Locator } from '@playwright/test';

export class LoginPage {
  readonly emailInput: Locator;
  readonly passwordInput: Locator;
  readonly submitButton: Locator;

  constructor(private page: Page) {
    this.emailInput = page.getByLabel('Email');
    this.passwordInput = page.getByLabel('Password');
    this.submitButton = page.getByRole('button', { name: 'Sign in' });
  }

  async login(email: string, password: string) {
    await this.emailInput.fill(email);
    await this.passwordInput.fill(password);
    await this.submitButton.click();
  }
}

// In tests
const loginPage = new LoginPage(page);
await loginPage.login('user@example.com', 'password123');
Enter fullscreen mode Exit fullscreen mode

Authentication State (Reuse Login)

// e2e/auth.setup.ts — run once, save session
import { test as setup } from '@playwright/test';

setup('authenticate', async ({ page }) => {
  await page.goto('/login');
  await page.fill('[name=email]', process.env.TEST_USER_EMAIL!);
  await page.fill('[name=password]', process.env.TEST_USER_PASSWORD!);
  await page.click('button[type=submit]');
  await page.waitForURL('/dashboard');
  await page.context().storageState({ path: 'e2e/.auth/user.json' });
});

// playwright.config.ts — use saved state in all tests
projects: [
  { name: 'setup', testMatch: /auth\.setup\.ts/ },
  {
    name: 'chromium',
    use: { storageState: 'e2e/.auth/user.json' },
    dependencies: ['setup'],
  },
]
Enter fullscreen mode Exit fullscreen mode

API Mocking

test('shows error on failed payment', async ({ page }) => {
  await page.route('**/api/checkout', route => {
    route.fulfill({ status: 400, body: JSON.stringify({ error: 'Card declined' }) });
  });

  await page.goto('/checkout');
  await page.click('button[type=submit]');
  await expect(page.getByText('Card declined')).toBeVisible();
});
Enter fullscreen mode Exit fullscreen mode

E2E test infrastructure ships in the Ship Fast Skill Pack/test skill generates Playwright specs for critical user flows. $49 at whoffagents.com.

Top comments (0)