엔드투엔드 테스트
엔드투엔드 테스트는 실제 애플리케이션을 사용자가 사용하는 방식 그대로 빌드하고 시작한 후, 외부에서 구동합니다. 브라우저가 페이지를 클릭하고, 셸이 CLI를 실행하거나, 클라이언트가 API를 호출하는 방식입니다. 이 테스트는 단위 테스트가 포착하지 못하는 와이어링, 구성, 라우팅, 그리고 부분 간의 연결 지점을 찾아냅니다. 또한 가장 느리고 가장 취약한 테스트 유형이므로, 모든 테스트는 그 자리를 정당화해야 하며 매번 동일한 결과를 반환하도록 작성되어야 합니다.
관련 스킬 unit-testing은 단일 단위에 대한 테스트를 다룹니다. 동작을 두 수준 중 하나에서 확인할 수 있다면 단위 테스트로 확인하세요.
저장소 규칙이 우선입니다.
저장소의 e2e 프레임워크, 구성, 폴더, fixture 및 스크립트를 사용하세요. 테스트를 작성하기 전에 기존 테스트와 구성(playwright.config.*, cypress.config.* 등)을 읽고 일치시켜야 합니다. AGENTS.md, CONTRIBUTING.md 및 패키지 스크립트에는 e2e 테스트 실행 방법과 대상을 명시합니다. 이 스킬과 충돌하는 경우 저장소 규칙이 우선합니다. 두 번째 e2e 프레임워크를 추가하지 마세요.
기본 도구
저장소에 아직 e2e 도구가 없는 경우, 브라우저가 필요한 모든 작업에는 Playwright(@playwright/test)를 사용하세요. 백엔드가 어떤 언어로 작성되었든 모든 웹 애플리케이션에서 작동합니다. 여기 예제는 Playwright를 사용합니다. 규칙은 모든 도구에 적용됩니다.
e2e 테스트가 필요한 경우
- 중요한 여정: 회원가입, 로그인, 제품이 존재하는 핵심 작업, 결제, 그리고 이전에 프로덕션에서 문제가 발생했던 모든 것.
- 통합 지점: 실제 백엔드, 인증 또는 라우팅이 작동해야 하는 페이지.
- 여정당 하나의 해피 패스, 그리고 사용자가 실제로 마주칠 실패 시나리오(잘못된 비밀번호, 거부된 결제).
다른 모든 것은 단위 테스트로 내립니다. 모든 유효성 검사 규칙, 모든 형식 지정 케이스, 계산의 모든 분기입니다. 모든 경우를 다루려는 e2e 스위트는 실행 속도가 너무 느리고 신뢰할 수 없을 정도로 취약해집니다.
로케이터
사용자가 요소를 찾는 방식으로 요소를 찾으세요. 그러면 테스트가 마크업 및 스타일 변경에도 견딜 수 있습니다.
- 우선 순위: role과 접근 가능한 이름(
getByRole('button', { name: 'Save' })), 레이블(getByLabel('Email')), 표시 텍스트, 그 다음으로 사용자에게 표시되는 안정적인 것이 없을 때 테스트 ID(getByTestId). - 피해야 할 것: CSS 클래스, XPath,
nth-child, 요소 위치. 레이아웃이 변경되면 깨지고 사용자에게 보이는 것에 대해 아무것도 알려주지 않습니다. - role이나 레이블로 요소를 찾을 수 없다면 종종 접근성 버그입니다. 취약한 선택자에 의존하기보다 그렇게 말하세요.
대기
절대 고정된 시간을 기다리지 마세요. sleep, waitForTimeout, setTimeout은 느린 CI 머신에서는 너무 짧고 빠른 머신에서는 시간을 낭비합니다.
- 통과하거나 시간 초과될 때까지 재시도하는 assertion을 사용하세요.
await expect(page.getByRole('alert')).toHaveText('Saved'). - 사용자가 기다릴 조건을 기다리세요. 요소가 보일 때, URL이 변경될 때, 응답을 받았을 때(
page.waitForResponse), 파일이 작성될 때. - 실패를 수정하기 위해 시간 제한을 늘리지 마세요. 합법적으로 오래 걸리는 경우 해당 단계에 대해서만 시간 제한을 늘리고 이유를 설명하세요.
- 프레임워크의 검사를 무시하는 강제 액션(
{ force: true })을 사용하지 마세요. 클릭을 강제해야 한다면 무언가 요소를 가리거나 준비되지 않은 것이며, 실제 사용자도 같은 문제를 겪을 것입니다.
테스트 데이터 및 격리
- 각 테스트는 자체 상태를 설정하며 단독으로, 어떤 순서로든, 다른 테스트와 병렬로 실행됩니다. 어떤 테스트도 다른 테스트가 먼저 실행되었다고 가정하지 않습니다.
- 테스트마다 고유한 데이터를 만드세요(생성된 이메일 또는 ID). 그러면 병렬 작업자와 재시도가 충돌하지 않습니다.
- 백도어를 통해 설정하세요. UI를 클릭하는 대신 API, 시드 스크립트 또는 fixture를 통해 사용자와 레코드를 만드세요. 테스트 중인 여정만 UI를 통해 진행됩니다.
- 한 번 로그인하고 재사용하세요. 모든 테스트에서 폼을 통해 로그인하는 대신 설정 단계(Playwright의
storageState)에서 인증된 상태를 저장하세요. - 소유하지 않은 것만 가짜로 대체하세요. 네트워크 경계에서
page.route()또는 로컬 가짜를 사용하여 타사 서비스(결제, 이메일, 지도)를 스텁하세요. 자체 프론트엔드, 백엔드, 데이터베이스는 실제로 유지하세요. 그렇지 않으면 엔드투엔드 테스트가 아닙니다.
CLI 및 API 엔드투엔드 테스트
브라우저 없이도 동일한 규칙이 적용됩니다. 저장소의 단위 테스트 러너(JavaScript 및 TypeScript의 경우 Vitest)를 사용하여 CLI 테스트를 실행하되, 단위 스위트 속도를 늦추지 않도록 별도의 구성이나 프로젝트에서 실행하세요. API 테스트는 Playwright의 request fixture 또는 동일한 러너를 사용할 수 있습니다.
- 사용자가 실행하는 방식과 동일하게, 테스트 전용 진입점이 아닌 빌드된 아티팩트를 실행하세요.
- 각 테스트에 작업 디렉토리로 사용할 새로운 임시 디렉토리를 제공하고,
HOME, 구성 경로, 캐시를 그곳으로 지정하여 테스트가 개발자의 실제 파일을 읽거나 변경하지 않도록 하세요. - 종료 코드, stdout, stderr 및 생성된 파일을 별도로 단언하세요. "예외가 발생하지 않았음"만 확인하는 테스트는 잘못된 출력을 놓칩니다.
- 출력을 비교하기 전에 실행 간에 변경되는 내용(타임스탬프, 임시 경로, 기간)을 제거하거나 고정시키세요.
테스트 실패 시
무엇이든 변경하기 전에 증거를 읽으세요.
- 아티팩트를 확보하세요. 실패 시 트레이스, 스크린샷 및 비디오를 활성화하고(Playwright:
trace: 'on-first-retry',screenshot: 'only-on-failure'), CI 실행에서 트레이스를 여세요(npx playwright show-trace <trace.zip>). 트레이스는 각 단계에서 페이지, 네트워크 및 콘솔을 보여줍니다. - 누구의 버그인지 결정하세요. 앱이 잘못되었다면 앱을 수정하세요. 테스트가 제 역할을 한 것입니다. 테스트가 잘못되었다면 테스트를 수정하세요. 환경이 잘못되었다면(서비스 중단, 포트 사용 중) 설정을 수정하세요.
- CI와 동일한 명령, 브라우저 및 설정(헤드리스 모드 및 작업자 수 포함)으로 재현하세요.
- 불안정한 테스트의 경우 경합 조건을 찾으세요. 일반적인 원인: 고정된 대기, 페이지가 준비되기 전에 작업 수행, 다른 테스트와 데이터 공유, 요소를 가리는 애니메이션 또는 토스트. 그 원인을 수정한 다음, 수정되었다고 말하기 전에 테스트를 여러 번 연속 실행하세요(
npx playwright test <file> --repeat-each=20 --retries=0).
CI 재시도는 파이프라인을 녹색으로 유지할 수 있지만, 재시도에서만 통과한 테스트는 여전히 실패하는 것입니다. 보고서에서 "불안정"을 상태가 아닌 수정해야 할 버그로 취급하세요. 사용자의 동의 없이 녹색을 얻기 위해 테스트를 건너뛰거나, 삭제하거나, 격리하지 마세요. 통과를 위해 assertion을 느슨하게 하지 마세요.
체크리스트
e2e 테스트를 인계하거나 검토할 때:
- 해당 여정에 e2e 테스트가 필요합니다. 단위 테스트로 확인할 수 있는 경우는 단위 테스트입니다.
- 로케이터는 role, 레이블, 텍스트 또는 테스트 ID를 사용합니다. CSS 체인 또는 XPath가 없습니다.
- 고정된 대기, 강제 액션 또는 전역 시간 제한 증가가 없습니다.
- 각 테스트는 자체 고유한 데이터를 생성하고 단독 및 병렬로 통과합니다.
- 타사 서비스만 스텁되었습니다.
- 실패 시 CI에 트레이스 또는 스크린샷이 남습니다.
- 새로 추가되거나 수정된 테스트가 재시도 없이 여러 번 연속 통과했습니다.