icon안동민 개발노트

E2E 테스트 (Cypress)


 Cypress는 현대적인 웹 애플리케이션을 위한 강력한 End-to-End (E2E) 테스팅 도구입니다.

 TypeScript와 함께 사용하면 타입 안정성과 개발자 경험을 크게 향상시킬 수 있습니다.

 이 절에서는 Cypress를 TypeScript 프로젝트에 통합하고 효과적으로 사용하는 방법을 살펴보겠습니다.

Cypress와 TypeScript 설정

  1. 필요한 패키지 설치
npm install --save-dev cypress @cypress/typescript-preprocessor typescript
  1. cypress/tsconfig.json 파일 생성
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["es5", "dom"],
    "types": ["cypress"]
  },
  "include": ["**/*.ts"]
}
  1. cypress/plugins/index.js 파일 수정
const wp = require('@cypress/webpack-preprocessor')
module.exports = (on) => {
  const options = {
    webpackOptions: {
      resolve: {
        extensions: [".ts", ".js"]
      },
      module: {
        rules: [
          {
            test: /\.ts$/,
            exclude: [/node_modules/],
            use: [
              {
                loader: "ts-loader"
              }
            ]
          }
        ]
      }
    },
  }
  on('file:preprocessor', wp(options))
}

TypeScript로 Cypress 테스트 작성

 Cypress 명령어와 선택자를 TypeScript로 작성하면 자동완성과 타입 검사의 이점을 누릴 수 있습니다.

describe('Home Page', () => {
  it('should display the correct title', () => {
    cy.visit('/');
    cy.get('h1').should('contain.text', 'Welcome to My App');
  });
 
  it('should navigate to the about page', () => {
    cy.get('[data-cy="nav-about"]').click();
    cy.url().should('include', '/about');
  });
});

페이지 객체 모델 (POM) 구현

 TypeScript를 사용하여 페이지 객체 모델을 구현하면 테스트 코드의 재사용성과 유지보수성이 향상됩니다.

// pages/HomePage.ts
export class HomePage {
  visit() {
    cy.visit('/');
  }
 
  getTitle() {
    return cy.get('h1');
  }
 
  navigateToAbout() {
    cy.get('[data-cy="nav-about"]').click();
  }
}
 
// tests/home.spec.ts
import { HomePage } from '../pages/HomePage';
 
describe('Home Page', () => {
  const homePage = new HomePage();
 
  it('should display the correct title', () => {
    homePage.visit();
    homePage.getTitle().should('contain.text', 'Welcome to My App');
  });
 
  it('should navigate to the about page', () => {
    homePage.navigateToAbout();
    cy.url().should('include', '/about');
  });
});

커스텀 명령어 정의 및 사용

 TypeScript로 Cypress 커스텀 명령어를 정의하고 사용하면 코드 재사용성이 향상됩니다.

// cypress/support/commands.ts
declare global {
  namespace Cypress {
    interface Chainable {
      login(email: string, password: string): Chainable<void>
    }
  }
}
 
Cypress.Commands.add('login', (email: string, password: string) => {
  cy.get('#email').type(email);
  cy.get('#password').type(password);
  cy.get('#login-button').click();
});
 
// 사용 예
cy.login('user@example.com', 'password123');

API 요청 모킹 및 스터빙

 TypeScript 환경에서 Cypress를 사용하여 API 요청을 모킹하고 스터빙하는 방법

interface User {
  id: number;
  name: string;
}
 
cy.intercept('GET', '/api/users', (req) => {
  req.reply({
    statusCode: 200,
    body: {
      users: [
        { id: 1, name: 'John Doe' },
        { id: 2, name: 'Jane Doe' }
      ] as User[]
    }
  });
}).as('getUsers');
 
cy.visit('/users');
cy.wait('@getUsers');
cy.get('.user-list').should('have.length', 2);

비동기 패턴 다루기

 Cypress의 비동기 특성과 TypeScript의 비동기 패턴을 조화롭게 사용하는 전략

cy.get('.loader').should('not.exist').then(() => {
  return new Cypress.Promise(resolve => {
    // 비동기 작업 수행
    setTimeout(() => {
      resolve('Data loaded');
    }, 1000);
  });
}).then((result: string) => {
  expect(result).to.eq('Data loaded');
  cy.get('.data-container').should('be.visible');
});

타입 관련 문제 해결

 Cypress 테스트에서 발생할 수 있는 타입 관련 문제들과 해결 방법

  1. cy.stub() 사용 시 타입 정의
interface MyObject {
  myMethod: (arg: string) => number;
}
 
const stub = cy.stub().as('myStub');
(stub as unknown as MyObject['myMethod'])('test');
  1. cy.wrap() 사용 시 타입 단언
const wrappedValue = cy.wrap('Hello') as unknown as Cypress.Chainable<string>;
wrappedValue.should('eq', 'Hello');

Cypress 대시보드 및 CI/CD 통합

  1. Cypress 대시보드 설정
npx cypress run --record --key your-project-key
  1. CI/CD 파이프라인 (예: GitHub Actions) 설정
name: Cypress Tests
on: [push]
jobs:
  cypress-run:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Cypress run
        uses: cypress-io/github-action@v2
        with:
          command: npm run test:e2e
          record: true
        env:
          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}

Best Practices

  1. 데이터 독립성 : 각 테스트가 독립적으로 실행될 수 있도록 테스트 데이터 관리
  2. 선택자 관리 : 데이터 속성 사용 (data-cy)을 통한 안정적인 선택자 관리
  3. 네트워크 요청 처리 : cy.intercept()를 사용하여 네트워크 요청 제어 및 응답 시뮬레이션
  4. 타입 안정성 활용 : TypeScript의 타입 시스템을 최대한 활용하여 오류 사전 방지
  5. 페이지 객체 모델 사용 : 테스트 코드의 구조화 및 재사용성 향상
  6. 병렬 실행 : Cypress의 병렬 실행 기능을 활용하여 테스트 실행 시간 단축
  7. 시각적 회귀 테스트 : Cypress의 이미지 스냅샷 기능을 활용한 UI 변경 감지
  8. 성능 모니터링 : Cypress의 성능 측정 기능을 활용하여 애플리케이션 성능 모니터링
  9. 지속적인 통합 : CI/CD 파이프라인에 Cypress 테스트 통합
  10. 문서화 : 테스트 케이스에 대한 명확한 설명과 주석 추가