Playwright로 완성하는 2026 E2E 테스트 자동화 병렬 실행과 플래키 테스트 잡기
플레이라이트로 엔드투엔드 테스트를 안정적으로 자동화하는 방법을 정리했습니다. 병렬 실행, 자동 대기, 플래키 테스트 원인 진단과 트레이스 뷰어 활용법을 실무 관점에서 다룹니다.
Playwright로 완성하는 2026 E2E 테스트 자동화 병렬 실행과 플래키 테스트 잡기
엔드투엔드 테스트는 개발자에게 애증의 대상이다. 실제 사용자 흐름을 검증해주지만, 조금만 잘못 짜면 불안정하게 깜빡이는 플래키 테스트가 되어 신뢰를 잃는다. 플레이라이트(Playwright)는 이런 고질적 문제를 정면으로 겨냥해 설계됐다. 2026년 현재 플레이라이트는 크로미움, 파이어폭스, 웹킷을 하나의 API로 제어하고, 자동 대기와 강력한 트레이스 도구로 안정성과 디버깅 편의를 동시에 잡으며 프런트엔드 테스트의 표준이 됐다. 이 글은 실무에서 흔들리지 않는 E2E 스위트를 만드는 방법을 다룬다.
자동 대기가 플래키를 줄이는 원리
플래키 테스트의 가장 흔한 원인은 타이밍이다. 버튼이 화면에 나타나기 전에 클릭을 시도하거나, 데이터가 로드되기 전에 텍스트를 확인하면 테스트는 무작위로 실패한다. 예전 도구들은 이를 임의의 sleep으로 때웠지만, 이는 느리면서도 불안정한 최악의 조합이었다. 플레이라이트는 모든 액션 전에 요소가 실제로 상호작용 가능한 상태인지 자동으로 확인한다. 요소가 보이고, 화면에 붙어 있고, 가려지지 않고, 클릭을 받을 수 있을 때까지 기다린 뒤에야 동작을 수행한다.
이 자동 대기 덕분에 개발자는 명시적 대기 코드를 거의 쓰지 않아도 된다. expect 단언 역시 조건이 충족될 때까지 재시도하는 리트라이 기반으로 동작한다. 아래 코드는 로그인 흐름을 검증하는데, 어디에도 수동 대기가 없지만 각 단계에서 요소 준비 상태를 자동으로 기다린다.
import { test, expect } from '@playwright/test';
test('사용자가 로그인하면 대시보드로 이동한다', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('이메일').fill('user@example.com');
await page.getByLabel('비밀번호').fill('secret1234');
await page.getByRole('button', { name: '로그인' }).click();
await expect(page.getByRole('heading', { name: '대시보드' })).toBeVisible();
});
여기서 getByRole이나 getByLabel 같은 접근성 기반 로케이터를 쓴 점에 주목할 만하다. CSS 클래스나 복잡한 XPath 대신 사용자가 실제로 인지하는 방식으로 요소를 찾으므로, 마크업이 리팩터링돼도 테스트가 잘 깨지지 않는다.
병렬 실행으로 스위트 속도 높이기
E2E 테스트가 느리면 아무도 돌리지 않는다. 플레이라이트는 기본적으로 테스트 파일을 여러 워커 프로세스에 분산해 병렬로 실행한다. 워커 수는 CPU 코어에 맞춰 자동 조정되며, 설정으로 직접 지정할 수도 있다. 각 워커는 독립된 브라우저 컨텍스트에서 실행되므로 쿠키나 로컬 스토리지가 서로 간섭하지 않는다.
import { defineConfig } from '@playwright/test';
export default defineConfig({
fullyParallel: true,
workers: process.env.CI ? 4 : undefined,
retries: process.env.CI ? 2 : 0,
use: { trace: 'on-first-retry' },
});
병렬 실행에서 주의할 점은 테스트 간 상태 공유다. 여러 테스트가 같은 계정으로 동시에 로그인해 데이터를 바꾸면 서로 간섭한다. 이를 피하려면 각 테스트가 자기만의 데이터를 만들어 쓰거나, 스토리지 상태를 미리 저장해두고 재사용하는 방식으로 인증 오버헤드를 줄인다. CI 환경에서 retries를 2로 둔 것은 진짜 플래키를 한 번 더 걸러내되, 실패가 반복되면 확실한 버그로 판단하기 위함이다.
트레이스 뷰어로 실패 원인 추적하기
테스트가 CI에서만 실패하고 로컬에서는 재현되지 않을 때만큼 답답한 상황도 없다. 플레이라이트의 트레이스 뷰어는 이 문제를 해결한다. trace: 'on-first-retry'를 켜두면 실패한 테스트를 재시도할 때 전체 실행 과정을 기록한다. 트레이스에는 각 단계의 DOM 스냅숏, 네트워크 요청, 콘솔 로그, 실행 전후 스크린샷이 타임라인으로 담긴다.
트레이스 파일을 열면 마치 녹화 영상을 되감듯 각 액션 시점의 페이지 상태를 그대로 볼 수 있다. 어떤 요소가 왜 클릭되지 못했는지, 어떤 네트워크 응답이 늦었는지가 한눈에 드러난다. 실제 운영에서 플래키를 줄이는 왕도는 없다. 트레이스로 근본 원인을 찾고, 취약한 로케이터를 접근성 기반으로 교체하고, 테스트 격리를 강화하는 지루한 반복이 답이다. 여기에 시각적 회귀 테스트를 위한 스크린샷 비교와 API 모킹을 곁들이면, 실제 사용자 경험을 폭넓게 지키면서도 흔들리지 않는 스위트를 완성할 수 있다. 견고한 E2E 스위트는 배포 직전 마지막 안전망으로서 팀 전체의 배포 자신감을 끌어올린다.
네트워크 모킹과 인증 상태 재사용
E2E 테스트가 느리고 불안정한 또 다른 원인은 외부 의존성이다. 결제 게이트웨이나 외부 API에 실제로 요청을 보내면 테스트가 그 서비스의 상태에 좌우된다. 플레이라이트는 네트워크 요청을 가로채는 라우팅 기능을 제공해, 특정 엔드포인트의 응답을 원하는 값으로 대체할 수 있다. 이렇게 하면 결제 성공과 실패, 서버 오류, 느린 응답 같은 다양한 시나리오를 실제 외부 서비스 없이 결정적으로 재현할 수 있다. 특히 오류 처리 로직은 실제 환경에서 재현하기 어려우므로 모킹이 거의 유일한 검증 수단이다.
인증도 테스트 속도에 큰 영향을 준다. 모든 테스트가 매번 로그인 폼을 거치면 시간 낭비가 심하다. 플레이라이트는 로그인 후의 브라우저 저장 상태를 파일로 저장해두고, 다른 테스트에서 이 상태를 불러와 로그인 과정을 건너뛰는 기법을 지원한다. 전역 설정에서 한 번만 로그인해 상태를 저장하고, 각 테스트는 그 상태에서 시작하면 인증 오버헤드가 사라진다. 서로 다른 권한을 가진 사용자를 테스트해야 한다면 역할별로 상태 파일을 여러 개 만들어두면 된다.
테스트 조직화도 유지보수성을 좌우한다. 페이지 객체 모델처럼 화면별 상호작용을 하나의 클래스로 캡슐화하는 패턴을 쓰면, UI가 바뀌어도 수정할 곳이 한군데로 모인다. 또 CI 파이프라인에서 브라우저별로 테스트를 나눠 병렬로 돌리고, 실패 시 트레이스와 스크린샷을 아티팩트로 저장해 원격에서도 원인을 분석할 수 있게 해야 한다. 이렇게 모킹, 인증 재사용, 좋은 조직화가 결합되면 수백 개의 E2E 테스트도 몇 분 안에 안정적으로 돌아가는 스위트가 된다. 결국 좋은 E2E 테스트는 사용자의 실제 여정을 충실히 재현하되, 불필요한 외부 변수는 통제하는 균형 감각에서 나온다.