💻 프로그래밍

Cypress

프론트엔드 E2E 테스트 프레임워크

📖 상세 설명

Cypress는 2015년 처음 개발되어 2017년 공개된 차세대 프론트엔드 테스트 프레임워크입니다. 기존 Selenium 기반 도구들과 달리 브라우저 내부에서 직접 실행되어 네이티브 접근이 가능하며, 이로 인해 네트워크 요청 모킹, DOM 조작, 시간 제어 등을 훨씬 쉽고 안정적으로 수행할 수 있습니다.

Cypress의 핵심 장점은 '시간 여행 디버깅(Time Travel Debugging)'입니다. 테스트 실행 중 각 단계마다 DOM 스냅샷을 자동으로 저장하여, 실패한 테스트를 디버깅할 때 어느 시점에서 무엇이 잘못되었는지 시각적으로 확인할 수 있습니다. 또한 자동 대기(Auto-waiting) 기능으로 요소가 나타날 때까지 자동으로 기다려 테스트 안정성이 크게 향상됩니다.

아키텍처 면에서 Cypress는 Node.js 프로세스와 브라우저 내부의 두 부분으로 구성됩니다. 테스트 코드는 브라우저 내에서 애플리케이션과 동일한 이벤트 루프에서 실행되므로, 비동기 작업을 동기식처럼 작성할 수 있고 타이밍 이슈가 현저히 줄어듭니다.

실무에서 Cypress는 E2E 테스트 외에도 컴포넌트 테스트, API 테스트, 시각적 회귀 테스트까지 지원합니다. GitHub Actions, Jenkins, CircleCI 등 주요 CI/CD 도구와 쉽게 통합되며, Cypress Dashboard를 통해 병렬 실행, 테스트 분석, 실패 녹화 등 엔터프라이즈급 기능도 제공합니다.

💻 코드 예제

// cypress/e2e/login.cy.js - 로그인 E2E 테스트
describe('로그인 기능', () => {
    beforeEach(() => {
        // 각 테스트 전에 로그인 페이지 방문
        cy.visit('/login');
    });

    it('올바른 자격증명으로 로그인 성공', () => {
        // 입력 필드 조작
        cy.get('[data-cy="email"]').type('user@example.com');
        cy.get('[data-cy="password"]').type('secure123');
        cy.get('[data-cy="submit"]').click();

        // 로그인 성공 확인
        cy.url().should('include', '/dashboard');
        cy.get('[data-cy="welcome-message"]')
            .should('contain', '환영합니다');
    });

    it('잘못된 비밀번호로 에러 메시지 표시', () => {
        cy.get('[data-cy="email"]').type('user@example.com');
        cy.get('[data-cy="password"]').type('wrong');
        cy.get('[data-cy="submit"]').click();

        // 에러 메시지 확인
        cy.get('[data-cy="error"]')
            .should('be.visible')
            .and('contain', '비밀번호가 일치하지 않습니다');
    });

    it('API 응답 모킹으로 네트워크 테스트', () => {
        // 네트워크 요청 가로채기
        cy.intercept('POST', '/api/login', {
            statusCode: 200,
            body: { token: 'fake-jwt-token', user: { name: '테스트' } }
        }).as('loginRequest');

        cy.get('[data-cy="email"]').type('test@test.com');
        cy.get('[data-cy="password"]').type('test123');
        cy.get('[data-cy="submit"]').click();

        // API 호출 대기 및 검증
        cy.wait('@loginRequest').its('request.body')
            .should('deep.equal', {
                email: 'test@test.com',
                password: 'test123'
            });
    });
});

🗣️ 실무에서 이렇게 말하세요

💬 회의에서
"이번 릴리즈 전에 Cypress로 핵심 사용자 플로우 E2E 테스트를 작성해두면 좋겠어요. 특히 결제 흐름은 intercept로 PG사 API를 모킹해서 안정적으로 테스트할 수 있습니다."
💬 면접에서
"Cypress는 Selenium과 달리 브라우저 내부에서 실행되어 네이티브 접근이 가능합니다. 자동 대기 기능이 있어서 flaky 테스트가 적고, 시간 여행 디버깅으로 실패 원인을 쉽게 찾을 수 있어요. 단, 크로스 브라우저 지원은 Selenium이 더 넓습니다."
💬 코드 리뷰에서
"cy.wait(3000) 대신 cy.get().should()를 사용해주세요. 하드코딩된 대기 시간은 CI 환경에서 불안정해질 수 있고, Cypress의 자동 대기 기능을 활용하는 게 베스트 프랙티스입니다."

⚠️ 흔한 실수 & 주의사항

cy.wait()에 고정 시간 사용

cy.wait(5000)처럼 하드코딩된 대기는 피하세요. 대신 cy.get().should('be.visible')이나 cy.intercept().as()와 cy.wait('@alias')를 사용하여 명시적으로 대기하세요.

테스트 간 상태 의존성

각 테스트는 독립적이어야 합니다. beforeEach에서 상태를 초기화하고, cy.session()이나 cy.request()로 로그인 상태를 빠르게 설정하세요.

data-cy 속성으로 선택자 관리

클래스나 ID 대신 data-cy="login-button" 같은 테스트 전용 속성을 사용하세요. CSS 변경에 영향받지 않아 테스트 유지보수가 쉬워집니다.

🔗 관련 용어

📚 더 배우기