본문으로 건너뛰기 Playwright Complete Guide | E2E Testing

Playwright Complete Guide | E2E Testing

Playwright Complete Guide | E2E Testing

이 글의 핵심

Playwright is Microsoft’s cross-browser test runner for Chromium, Firefox, and WebKit. Auto-waiting locators, network interception, traces, and first-class parallelism make it a strong choice for modern E2E suites and CI pipelines.

What This Guide Covers

Playwright runs tests in real browsers with a single API across Chromium, Firefox, and WebKit. Unlike older WebDriver stacks, it uses the browser’s own protocols where possible, keeps tests fast, and gives excellent artifacts when things go wrong.

You will learn how to:

  • Install and configure @playwright/test
  • Write stable tests with locators and assertions
  • Mock APIs with page.route()
  • Use traces, screenshots, and video
  • Run multi-project (browser) matrices and parallel workers
  • Wire everything into GitHub Actions

1. Why Playwright?

Strengths

  • Cross-browser: One codebase exercises Chromium, Firefox, and WebKit (Safari engine).
  • Auto-waiting: Locators retry until timeouts — fewer sleep() hacks.
  • Network control: Intercept and fulfill requests for deterministic tests.
  • Parallelism: Workers and sharding are first-class for CI throughput.
  • Tooling: Trace viewer, codegen (npx playwright codegen), HTML report.

Trade-offs

  • Learning curve vs Cypress for teams used to Cypress’s dashboard-centric workflow.
  • Very dynamic SPAs still need discipline: stable data-testid or role-based selectors.

2. Installation & Setup

Scaffold a project

npm create playwright@latest

Pick TypeScript, add a GitHub Actions workflow when prompted, and install browsers:

npx playwright install --with-deps

playwright.config.ts

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

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 2 : undefined,
  reporter: [['html', { open: 'never' }], ['list']],
  use: {
    baseURL: 'http://127.0.0.1:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'webkit', use: { ...devices['Desktop Safari'] } },
  ],
  webServer: {
    command: 'npm run dev',
    url: 'http://127.0.0.1:3000',
    reuseExistingServer: !process.env.CI,
    timeout: 120_000,
  },
});

webServer boots your app automatically in CI and locally (unless a server is already running).


3. First Tests

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

test.describe('Login', () => {
  test('redirects to dashboard on success', async ({ page }) => {
    await page.goto('/login');
    await page.getByLabel('Email').fill('user@example.com');
    await page.getByLabel('Password').fill('correct-horse-battery');
    await page.getByRole('button', { name: 'Sign in' }).click();
    await expect(page).toHaveURL(/\/dashboard/);
    await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
  });

  test('shows validation errors', async ({ page }) => {
    await page.goto('/login');
    await page.getByRole('button', { name: 'Sign in' }).click();
    await expect(page.getByText('Email is required')).toBeVisible();
  });
});

Prefer getByRole, getByLabel, and getByTestId over brittle CSS. They mirror how assistive tech sees the page and survive refactors better than deep selectors.


4. Locators & Assertions

// Chaining and filtering
const card = page.locator('.card').filter({ hasText: 'Pro plan' });
await card.getByRole('button', { name: 'Upgrade' }).click();

// Soft assertions (collect failures, then report)
await expect.soft(page.getByText('Welcome')).toBeVisible();
await expect.soft(page.getByText('Notifications')).toBeVisible();

Use expect.poll for asynchronous UI state that updates after network calls.


5. API Mocking with page.route

test('lists users from mocked API', async ({ page }) => {
  await page.route('**/api/users', async (route) => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify([{ id: '1', name: 'Ada' }]),
    });
  });

  await page.goto('/users');
  await expect(page.getByText('Ada')).toBeVisible();
});

For GraphQL, match on URL and POST body, or stub at the service worker layer if your app uses one.


6. Authentication State

Reuse login by saving storage state once:

// global-setup.ts (simplified)
import { chromium, type FullConfig } from '@playwright/test';

async function globalSetup(config: FullConfig) {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto(config.projects[0].use.baseURL + '/login');
  // perform login...
  await page.context().storageState({ path: 'storage/state.json' });
  await browser.close();
}

export default globalSetup;

Point use.storageState at that file in projects that need an authenticated session.


7. Screenshots, Video, and Traces

  • Screenshots: await page.screenshot({ path: 'shot.png', fullPage: true })
  • Video: set video: 'on' in use for debugging (disable in high-volume CI if needed).
  • Trace: keep trace: 'on-first-retry' — open playwright show-trace trace.zip after failures.

8. Parallelism & Sharding

# Four parallel workers locally
npx playwright test --workers=4

# CI matrix: shard 1 of 3
npx playwright test --shard=1/3

Combine sharding with multiple job runners to keep PR feedback under a few minutes.


9. GitHub Actions

# .github/workflows/playwright.yml
name: Playwright
on: [push, pull_request]
jobs:
  test:
    timeout-minutes: 30
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report/

Upload the HTML report so reviewers can open failures without reproducing locally.


10. Playwright vs Cypress (Quick Comparison)

TopicPlaywrightCypress
BrowsersChromium, Firefox, WebKit in one runnerChrome, Edge, Firefox (WebKit limited)
Multi-tab / multi-originStrongMore constraints
ArchitectureOut-of-process driverIn-browser (with trade-offs)
Parallel CIBuilt-in shardingOften Cypress Cloud for scale

11. Best Practices

Do

  • Stabilize selectors with roles and accessible names.
  • Mock third-party APIs you do not own.
  • Keep tests independent — create data in beforeEach or use isolated tenants.
  • Run the smallest project set locally; full matrix in CI.

Avoid

  • Arbitrary page.waitForTimeout(ms) except when simulating real delays in demos.
  • Sharing mutable global state between tests.
  • Coupling tests to pixel-perfect CSS class names.

Summary & Checklist

Core ideas

  • Playwright Test is the official runner — use @playwright/test, not raw playwright for assertions and fixtures.
  • page.route controls the network layer for fast, deterministic suites.
  • Projects map to browsers/devices; sharding scales CI.
  • Traces are your best friend for debugging intermittent failures.

Checklist

  • Add playwright.config.ts with baseURL, traces, and webServer
  • Convert critical user journeys into spec files
  • Introduce API mocks for flaky dependencies
  • Turn on HTML + trace artifacts in CI
  • Add npm run test:e2e to package scripts and document it in README

Browser automation & runner internals (brief)

Processes: @playwright/test launches browsers (Chromium/Firefox/WebKit) and isolates work in BrowserContexts—think incognito profiles with separate cookies/storage. Commands reach Chromium through Chrome DevTools Protocol (and equivalent channels for other engines).

Mocking: page.route fulfills or modifies HTTP before it hits your app; page.waitForResponse ties assertions to network completion—prefer this over page.waitForTimeout.

Coverage: E2E rarely replaces unit coverage; if needed, serve an instrumented build and merge reports separately from Vitest/Jest numbers.

Production: Target staging and preview deploys; add synthetic checks for uptime. For a shorter, internals-heavy companion, see Playwright E2E Testing Guide.


More career guides (Korean on pkglog.com)

Deep-dive posts in Korean pair well with this English overview: job posting channels, resume and interview delivery, weekly habits, and practical job-hunting tips. Use them if you read Korean or run pages through translation.

Related posts:


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. A practical guide to Playwright: installation, locators, API mocking, traces, parallel projects, and GitHub Actions. Com… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

Playwright, E2E, Testing, Automation, CI/CD, TypeScript, Browser 등으로 검색하시면 이 글이 도움이 됩니다.