Files
2nd/10_Wiki/Topics/Coding/Testing_Playwright_Advanced.md
T
2026-05-09 21:08:02 +09:00

5.9 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
testing-playwright-advanced Playwright 심화 — Trace / Auth / Parallel Coding draft B conceptual 2026-05-09 2026-05-09
testing
playwright
e2e
vibe-coding
language applicable_to
TS / Playwright
Frontend
Playwright
trace viewer
storage state
fixtures
page object
auto-wait

Playwright 심화

Modern E2E. Auto-wait + trace viewer + parallel + auth state reuse. Cypress 보다 빠르고 multi-browser. CI 핵심: trace + screenshot + storage state.

📖 핵심 개념

  • Auto-wait: 자동으로 element 대기.
  • Trace: 단계별 screenshot + network + console.
  • Storage state: 한 번 로그인 → JSON 저장 → 재사용.
  • Fixtures: test 별 setup/teardown.

💻 코드 패턴

기본

import { test, expect } from '@playwright/test';

test('login', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill('a@b.com');
  await page.getByLabel('Password').fill('pw');
  await page.getByRole('button', { name: 'Sign in' }).click();
  
  await expect(page).toHaveURL('/dashboard');
  await expect(page.getByText('Welcome')).toBeVisible();
});

Locator (modern, role-based)

page.getByRole('button', { name: 'Save' });
page.getByLabel('Email');
page.getByPlaceholder('Search…');
page.getByText('Welcome');
page.getByTestId('user-card'); // data-testid

→ 위에서 아래 우선. testId 는 마지막.

Storage state (auth reuse)

// global-setup.ts
import { chromium } from '@playwright/test';

async function globalSetup() {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('http://localhost:3000/login');
  await page.getByLabel('Email').fill('a@b.com');
  await page.getByLabel('Password').fill('pw');
  await page.getByRole('button', { name: 'Sign in' }).click();
  await page.waitForURL('/dashboard');
  await page.context().storageState({ path: 'state.json' });
  await browser.close();
}

export default globalSetup;
// playwright.config.ts
export default defineConfig({
  globalSetup: './global-setup',
  use: { storageState: 'state.json' },
});

→ 모든 test 가 이미 로그인 상태로 시작.

다양한 user (worker storage)

// fixtures.ts
type Auths = { adminPage: Page; userPage: Page };

export const test = base.extend<Auths>({
  adminPage: async ({ browser }, use) => {
    const ctx = await browser.newContext({ storageState: 'admin-state.json' });
    await use(await ctx.newPage());
    await ctx.close();
  },
  userPage: async ({ browser }, use) => {
    const ctx = await browser.newContext({ storageState: 'user-state.json' });
    await use(await ctx.newPage());
    await ctx.close();
  },
});

test('admin sees panel', async ({ adminPage }) => {
  await adminPage.goto('/admin');
  await expect(adminPage.getByText('Admin Panel')).toBeVisible();
});

Trace

// playwright.config.ts
use: { trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure' }
# 실패 후 trace 보기
npx playwright show-trace trace.zip

Network mocking

test('handles api error', async ({ page }) => {
  await page.route('**/api/users', (route) => route.fulfill({
    status: 500,
    body: JSON.stringify({ error: 'server error' }),
  }));

  await page.goto('/users');
  await expect(page.getByText('Failed to load')).toBeVisible();
});

API testing

test('create user via API', async ({ request }) => {
  const r = await request.post('/api/users', {
    data: { email: 'a@b.com' },
  });
  expect(r.status()).toBe(201);
  const body = await r.json();
  expect(body.id).toBeTruthy();
});

Component testing (CT)

import { test, expect } from '@playwright/experimental-ct-react';
import { Button } from './Button';

test('Button click', async ({ mount }) => {
  let clicked = false;
  const btn = await mount(<Button onClick={() => { clicked = true; }}>Click</Button>);
  await btn.click();
  expect(clicked).toBe(true);
});

Visual regression

await expect(page).toHaveScreenshot('home.png', {
  maxDiffPixelRatio: 0.01,
});

→ 첫 run 시 저장, 이후 비교.

Parallel + sharding

npx playwright test --workers=4
npx playwright test --shard=1/4   # CI 분산

Retry on flake

// config
retries: process.env.CI ? 2 : 0,

Page object (재사용)

class LoginPage {
  constructor(private page: Page) {}
  async goto() { await this.page.goto('/login'); }
  async login(email: string, pw: string) {
    await this.page.getByLabel('Email').fill(email);
    await this.page.getByLabel('Password').fill(pw);
    await this.page.getByRole('button', { name: 'Sign in' }).click();
  }
}

test('login', async ({ page }) => {
  const p = new LoginPage(page);
  await p.goto();
  await p.login('a@b.com', 'pw');
});

🤔 의사결정 기준

상황 추천
Modern E2E web Playwright
Multi-browser (Chrome / FF / Safari) Playwright
Cypress 마이그 Playwright (속도 + 안정성)
Component test Playwright CT 또는 Vitest + RTL
Mobile E2E Maestro / Detox (Playwright X)
API + UI Playwright (둘 다)

안티패턴

  • waitForTimeout(2000): flake. auto-wait + expect.
  • CSS selector 의존 (.btn-primary): 변경 시 깨짐. role / label.
  • Real DB / external API: flake. mock / seed.
  • Trace off prod: 실패 분석 못 함.
  • Storage state 안 씀: 매 test 로그인 — 느림.
  • Test 간 상태 의존: serial. 독립 setup.
  • Sleep 으로 animation 기다림: visible() 또는 Promise.race.

🤖 LLM 활용 힌트

  • getByRole > getByLabel > getByTestId.
  • Storage state + fixtures = 빠름.
  • Trace on-first-retry + screenshot + video retain.

🔗 관련 문서