본문으로 건너뛰기

4.3 Hook과 Test data

Hook은 같은 describe scope의 공통 동작에 사용하지만 test 의존성을 만들지 않아야 합니다.

test.beforeEach(async ({ page }) => {
await page.goto('/');
});

test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status !== testInfo.expectedStatus) {
await testInfo.attach('url', {
body: page.url(),
contentType: 'text/plain',
});
}
});

beforeAll은 worker마다 실행될 수 있고 failure 뒤 worker가 재시작되면 다시 실행됩니다. 한 번만 실행될 것이라는 가정으로 external state를 만들지 않습니다.

Test data 준비 방식

  1. UI로 생성: 가장 현실적이지만 느립니다.
  2. API로 생성: 빠르고 제품의 public contract를 사용합니다.
  3. Database helper: 빠르지만 schema와 강하게 결합됩니다.
  4. Seed snapshot: 재현성이 좋지만 version 관리가 필요합니다.

Test 목적이 “주문 취소 UI”라면 주문 생성까지 UI로 반복할 필요는 없습니다. API fixture로 주문을 만들고 취소 flow에 집중합니다.

Cleanup 실패가 본래 assertion error를 가리지 않도록 attachment와 log를 남깁니다. Random data는 seed를 기록해 실패를 재현할 수 있게 합니다.

Hook 사용 기준

  • beforeEach: 모든 test가 실제로 필요하고 비교적 싼 준비
  • afterEach: test 결과에 따라 artifact나 제한된 cleanup
  • beforeAll: serial group 또는 immutable shared resource에만 신중히 사용
  • afterAll: worker/process 종료 전에 shared resource 정리

Hook이 길어지면 test가 보이지 않는 전제에 의존합니다. Domain setup은 fixture나 helper로 이름을 부여합니다.

Deterministic data

const title = `pw-${testInfo.parallelIndex}-${testInfo.retry}`;

Retry가 동일 record를 재사용해야 하는지 새 record를 가져야 하는지는 제품 idempotency와 cleanup 정책에 따라 결정합니다.

실패 후 cleanup

try {
await use(order);
} finally {
await api.delete(`/orders/${order.id}`).catch(async error => {
await test.info().attach('cleanup-error', {
body: String(error),
contentType: 'text/plain',
});
});
}

실습

  1. UI setup을 API setup으로 옮기고 실행 시간을 비교합니다.
  2. Random seed와 생성된 business ID를 attachment로 남깁니다.
  3. Cleanup endpoint 실패를 주입해 본래 failure가 보존되는지 확인합니다.

참고: Fixtures, Playwright Test hooks