E2E 테스트를 실행할 때마다 성공과 실패를 오가는 간헐적 테스트 실패(Flaky Test)를 경험해봤을까?
로컬 개발 환경에서는 100% 통과하던 테스트가 GitHub Actions나 Jenkins 같은 CI 파이프라인만 타면 허망하게 에러를 뱉는 상황을 자주 겪는다.
다만 많은 개발자가 이 문제를 해결하겠다고 cy.wait(5000) 같은 임의의 대기 시간을 코드 여기저기에 덕지덕지 붙이곤 한다.
이번에는 Cypress 테스트에서 Flaky Test가 발생하는 정확한 원인과 네트워크 비동기 통신을 완벽히 제어하여 100% 신뢰할 수 있는 E2E 테스트를 구축하는 방법을 완벽하게 정리해서 소개하겠다.

 

1. Flaky Test가 발생하는 근본적인 원인

E2E(End-to-End) 테스트에서 Flaky Test가 터지는 가장 큰 이유는 브라우저 내부의 비동기 처리 타이밍과 테스트 코드의 실행 타이밍이 어긋나기 때문이다.
서버로부터 데이터를 받아와 화면에 렌더링하기 전에 테스트 코드가 DOM 요소를 검증해 버리면 에러가 발생한다.

개발자들이 흔히 범하는 실수는 고정된 시간 동안 실행을 멈추는 대기 함수를 사용하는 것이다.
네트워크 상태는 가변적이기 때문에 3초를 기다리게 작성해 둔 코드는 서버 응답이 3.1초만 걸려도 즉시 실패로 이어진다.

구분cy.wait(하드코딩 숫자)cy.intercept() + Alias 대기
대기 방식지정한 시간(ms) 동안 무조건 정지실제 HTTP 응답이 올 때까지 동적 대기
테스트 속도응답이 빨라도 설정된 시간만큼 무조건 지연됨응답이 오자마자 즉시 다음 검증 단계 진행
테스트 안정성네트워크 지연 발생 시 바로 실패(Flaky 발생)네트워크 지연에 구애받지 않고 안정적 통과

 

2. cy.intercept()를 활용한 네트워크 이벤트 동기화

Cypress에서 비동기 데이터를 다룰 때 가장 강력한 해결책은 cy.intercept() 메서드다.
이 기능을 활용하면 특정 API 요청을 감시(Listen)하고, 해당 요청에 고유한 별칭(Alias)을 부여한 뒤, 그 응답이 완료되는 시점까지 테스트를 정확하게 대기시킬 수 있다.

단순히 시간을 끄는 것이 아니라 백엔드 HTTP 응답 헤더와 바디가 브라우저에 수신된 정확한 이벤트 시점에 맞춰 다음 Assertion(검증)을 실행하는 원리다.

 

3. 실전 예제: 동적 데이터 로딩 테스트 리팩토링

사용자 목록을 API로 불러와 화면에 출력하는 기능에 대한 E2E 테스트 작성 사례를 살펴보자.

✗ 잘못된 코드(네트워크 지연 시 실패하는 대기 방식)

// ✗ 하드코딩된 대기 시간 사용
describe('사용자 목록 조회 테스트', () => {
  it('버튼 클릭 시 사용자 리스트가 노출되어야 한다', () => {
    cy.visit('/users');
    cy.get('#fetch-btn').click();

    // 네트워크 응답 시간을 예측하여 3초간 멈춤 (위험한 방식)
    cy.wait(3000);

    // 3초 내에 응답이 도착하지 않으면 이 검증은 즉시 실패함
    cy.get('.user-card').should('have.length', 5);
  });
});

✓ 올바른 코드(cy.intercept 기반의 정확한 비동기 검증)

// ✓ 네트워크 요청 인터셉트 및 별칭 사용
describe('사용자 목록 조회 테스트', () => {
  it('버튼 클릭 시 사용자 리스트가 노출되어야 한다', () => {
    // 1. GET /api/v1/users 요청을 감시하고 'getUsers'라는 별칭 부여
    cy.intercept('GET', '/api/v1/users').as('getUsers');

    cy.visit('/users');
    cy.get('#fetch-btn').click();

    // 2. HTTP 응답이 돌아올 때까지 동적으로 대기
    cy.wait('@getUsers').its('response.statusCode').should('eq', 200);

    // 3. API 응답 완료 후 안정적으로 DOM 상태 검증
    cy.get('.user-card').should('have.length', 5);
  });
});

실행 결과: 네트워크 응답이 100ms 만에 오면 대기 없이 즉시 통과하며, CI 환경처럼 4초 이상 지연되더라도 설정된 타임아웃 범위 안에서 응답을 수신한 순간 테스트가 안정적으로 통과한다.

 

4. Cypress 테스트 작성 시 흔히 하는 실수와 주의사항

✗ 잘못된 방식: 화면 전환이나 팝업 애니메이션을 기다리기 위해 cy.wait(1000)을 코드 전반에 남발하는 패턴.
✓ 올바른 방식: DOM 요소의 시각적 상태 조건(should('be.visible'))이나 특정 CSS 클래스 변경을 감지하도록 작성해야 한다.

✗ 잘못된 방식: 이전 테스트 케이스(it 블록)에서 생성된 DOM 상태나 글로벌 변수에 다음 테스트가 의존하는 구조.
✓ 올바른 방식: 각 it 블록은 완전히 독립적이어야 한다. beforeEach() 단계를 활용해 쿠키, 세션, 로컬스토리지를 항상 클리어하고 필요한 데이터는 사전에 Mocking하거나 API로 초기화한다.

 

5. 요약 및 마무리

Cypress E2E 테스트의 간헐적 실패는 개발팀 전체의 테스트 신뢰도를 저하시키는 주요 원인이다.
하드코딩된 대기 시간을 지우고 cy.intercept()를 활용한 정확한 네트워크 동기화 습관이 모여서 튼튼한 CI/CD 파이프라인을 만든다는 점을 잊지 말자.
이 글의 cy.intercept() 리팩토링 패턴을 참고해 기존 Flaky한 테스트 코드를 수정하면, 실패 없이 안정적인 자동화 테스트 결과를 얻을 수 있을 것이다.