Cypress E2E 테스트 설정: 엔드투엔드 테스트 환경 구성
Cypress E2E 테스트 설정과 환경 구성 방법을 설명합니다.
Cypress E2E 테스트 설정
Introduction
Cypress는 현대적인 웹 애플리케이션을 위한 엔드투엔드(E2E) 테스트 프레임워크입니다. 브라우저에서 직접 실행되어 실제 사용자와 유사한 테스트 시나리오를 구현할 수 있습니다. 이 글에서는 Cypress 설정부터 최적화까지 전체 프로세스를 다루겠습니다.
Environment
// package.json
{
"name": "my-project",
"scripts": {
"cypress:open": "cypress open",
"cypress:run": "cypress run",
"cypress:run:chrome": "cypress run --browser chrome",
"test:e2e": "start-server-and-test dev http://localhost:3000 cypress:open",
"test:e2e:ci": "start-server-and-test dev http://localhost:3000 cypress:run"
},
"devDependencies": {
"cypress": "^13.6.0",
"start-server-and-test": "^2.0.0"
}
}npx cypress --version
# Cypress 13.6.1Problem
Cypress 설정 시 발생하는 문제들:
// cypress.config.js
const { defineConfig } = require('cypress');
module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}'
}
});
// 문제점:
// 1. 브라우저 설정 미흡
// 2. 시간 초과 문제
// 3. 네트워크 요청 처리 미숙# 테스트 실행 시 에러
npx cypress run
# 에러:
# CypressError: Timed out retrying after 5000ms:
# cy.visit() failed trying to load:
# http://localhost:3000
# We attempted to make an http request to this URL but the request failed without a response.Analysis
Cypress 설정 구조를 분석했습니다:
// cypress.config.js 구조 분석
const { defineConfig } = require('cypress');
module.exports = defineConfig({
// 프로젝트 설정
projectId: 'abc123',
// 브라우저 설정
chromeWebSecurity: true,
// E2E 테스트 설정
e2e: {
baseUrl: 'http://localhost:3000',
specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
supportFile: 'cypress/support/e2e.js',
viewportWidth: 1280,
viewportHeight: 720,
defaultCommandTimeout: 10000,
requestTimeout: 10000,
responseTimeout: 30000,
pageLoadTimeout: 60000
},
// 컴포넌트 테스트 설정
component: {
devServer: {
framework: 'react',
bundler: 'vite'
}
}
});# Cypress 폴더 구조
cypress/
├── e2e/
│ ├── auth/
│ │ ├── login.cy.ts
│ │ └── register.cy.ts
│ └── dashboard.cy.ts
├── fixtures/
│ └── users.json
├── support/
│ ├── commands.ts
│ └── e2e.ts
└── tsconfig.jsonSolution
1단계: 기본 설정 구성
// cypress.config.ts
import { defineConfig } from 'cypress';
export default defineConfig({
projectId: 'abc123',
chromeWebSecurity: false,
e2e: {
baseUrl: 'http://localhost:3000',
specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
supportFile: 'cypress/support/e2e.ts',
// 뷰포트 설정
viewportWidth: 1280,
viewportHeight: 720,
// 타임아웃 설정
defaultCommandTimeout: 10000,
requestTimeout: 10000,
responseTimeout: 30000,
pageLoadTimeout: 60000,
// 비디오 녹화
video: true,
videoCompression: 32,
// 스크린샷
screenshotOnRunFailure: true,
trashAssetsBeforeRuns: true,
// 로깅
experimentalRunAllSpecs: true
}
});2단계: 커스텀 커맨드 작성
// cypress/support/commands.ts
declare global {
namespace Cypress {
interface Chainable {
login(email: string, password: string): Chainable;
getByTestId(testId: string): Chainable>;
interceptGQL(operationName: string): Chainable;
}
}
}
Cypress.Commands.add('login', (email: string, password: string) => {
cy.session([email, password], () => {
cy.visit('/login');
cy.get('[data-testid="email-input"]').type(email);
cy.get('[data-testid="password-input"]').type(password);
cy.get('[data-testid="login-button"]').click();
cy.url().should('include', '/dashboard');
});
});
Cypress.Commands.add('getByTestId', (testId: string) => {
return cy.get(`[data-testid="${testId}"]`);
});
Cypress.Commands.add('interceptGQL', (operationName: string) => {
cy.intercept('POST', '/graphql', (req) => {
if (req.body.operationName === operationName) {
req.alias = operationName;
}
});
});
// cypress/support/e2e.ts
import './commands';
// 전역 에러 핸들링
Cypress.on('uncaught:exception', (err, runnable) => {
return false;
}); 3단계: 테스트 시나리오 작성
// cypress/e2e/auth/login.cy.ts
describe('Login', () => {
beforeEach(() => {
cy.visit('/login');
});
it('should login successfully', () => {
cy.get('[data-testid="email-input"]').type('user@example.com');
cy.get('[data-testid="password-input"]').type('password123');
cy.get('[data-testid="login-button"]').click();
cy.url().should('include', '/dashboard');
cy.get('[data-testid="welcome-message"]').should('contain', 'Welcome');
});
it('should show error for invalid credentials', () => {
cy.get('[data-testid="email-input"]').type('wrong@example.com');
cy.get('[data-testid="password-input"]').type('wrongpassword');
cy.get('[data-testid="login-button"]').click();
cy.get('[data-testid="error-message"]').should('be.visible');
});
});
// cypress/e2e/dashboard.cy.ts
describe('Dashboard', () => {
beforeEach(() => {
cy.login('user@example.com', 'password123');
cy.visit('/dashboard');
});
it('should display user data', () => {
cy.interceptGQL('GetUserData');
cy.wait('@GetUserData');
cy.get('[data-testid="user-stats"]').should('be.visible');
});
});Lessons Learned
- data-testid 활용: 테스트 안정성을 위해 CSS 클래스 대신 data-testid 속성을 사용해야 합니다
- cy.session 활용: 로그인 세션을 캐싱하여 테스트 실행 시간을 단축할 수 있습니다
- 네트워크 인터셉트: API 요청을 인터셉트하여 테스트 환경을 격리해야 합니다
- 타임아웃 조정: 테스트 안정성을 위해 적절한 타임아웃 값을 설정해야 합니다
- CI/CD 통합: Cypress Dashboard와 연동하여 테스트 결과를 모니터링하는 것이 효과적입니다
이 블로그는 외부 스폰서십, 제휴 마케팅 또는 광고 수익을 받지 않습니다.