첫 번째 엔드투엔드(E2E) 테스트 작성하기

첫 번째 엔드투엔드(E2E) 테스트 작성하기

Cypress를 설치하고 앱을 열었다면, 이제 실제로 테스트 한 줄을 작성해 볼 차례예요. 예제 앱(Kitchen Sink)을 이용하면 직접 만든 페이지가 없어도 바로 Cypress의 흐름을 체험할 수 있답니다. 이번 글에서는 비어 있는 스펙 파일 하나를 만들어 통과하는 테스트부터, 실제 페이지를 방문하고 요소를 조작하고 검증하는 테스트까지 차근차근 만들어 볼게요.

출처: Cypress 공식 문서 — End-to-end testing: your first test with Cypress 원본 URL: https://docs.cypress.io/app/end-to-end-testing/writing-your-first-end-to-end-test

빈 스펙 파일 만들기

Cypress 앱에서 Create new empty spec 버튼을 누르면 새 스펙 이름을 입력하는 대화상자가 나와요. 이름은 일단 기본값 그대로 두고 생성하면 됩니다. 파일이 생기자마자 화면 오른쪽의 엔드투엔드 스펙 목록에 바로 나타나는데, Cypress는 스펙 파일의 변경을 계속 감시해서 목록을 자동으로 갱신해 줘요.

아직 코드를 한 줄도 안 썼지만 괜찮아요. 새 스펙을 클릭해서 Cypress가 실행해 보면 기본 예제(https://example.cypress.io)를 방문해서 테스트가 통과하는 모습을 확인할 수 있습니다.

첫 번째 테스트 — 통과시키고 또 실패시키기

이제 진짜 테스트를 적어 볼게요. IDE에서 스펙 내용을 아래처럼 바꿔보세요.

describe('My First Test', () => {
  it('Does not do much!', () => {
    expect(true).to.equal(true)
  })
})

저장하면 브라우저가 자동으로 다시 로드되면서, 아직 쓸모는 없지만 첫 번째로 통과하는 테스트가 됩니다. 이때 Command Log를 보면 스위트와 테스트, 첫 번째 단언(assertion)이 초록색으로 떠 있는 게 보여요.

이제 이 값을 false로 바꿔보면 테스트가 실패해서 빨간색으로 표시됩니다. truefalse가 같지 않으니까요. Cypress는 실패 시 스택 트레이스와 정확한 코드 프레임을 보여줘서 문제 지점을 바로 찾을 수 있게 도와줘요.

여기서 잠깐 짚고 갈게요. describe, it, expect는 Cypress가 자체 번들 라이브러리로 제공하는 전역 함수입니다. describeit은 Mocha에서, expect는 Chai에서 왔어요. 이미 익숙한 도구라면 반갑겠지만, 처음이라 해도 걱정할 필요가 없습니다.

제대로 된 테스트를 만들어 볼게요

좋은 테스트는 대체로 세 단계로 나뉩니다. 먼저 애플리케이션의 상태를 준비하고, 그다음 어떤 동작을 취하고, 마지막으로 그 결과 상태를 검증해요. Given-When-Then이나 Arrange-Act-Assert라고도 표현하죠.

이 세 단계를 Cypress 커맨드에 맞춰서 좁게 보면 이렇게 됩니다.

  1. 웹 페이지를 방문한다.
  2. 요소를 조회한다.
  3. 그 요소와 상호작용한다.
  4. 페이지의 내용을 검증한다.

1단계: 페이지 방문 (cy.visit)

우리가 방문하고 싶은 URL을 cy.visit()에 넘겨줍니다.

it('Visits the Kitchen Sink', () => {
  cy.visit('https://example.cypress.io')
})

저장하고 Test Runner로 돌아가면 몇 가지가 눈에 띄어요. Command Log에 VISIT 액션이 새로 생기고, Kitchen Sink 앱이 화면에 로드되며, 단언 없이도 테스트가 초록색이 되는 것을 확인할 수 있어요. VISIT은 페이지 로딩이 끝날 때까지 파란 pending 상태로 표시됩니다. 만약 응답이 404나 500처럼 2xx가 아니거나 앱 코드에 자바스크립트 오류가 있었다면 테스트는 실패했을 거예요.

2단계: 요소 조회 (cy.contains)

이번에는 페이지에 있는 링크 하나를 클릭해 볼게요. 내용으로 요소를 찾을 때는 cy.contains()를 씁니다.

it('finds the content "type"', () => {
  cy.visit('https://example.cypress.io')
  cy.contains('type')
})

Command Log에 CONTAINS가 나타나고 여전히 초록색이에요. 단언을 하나도 안 넣었는데 왜 통과할까요? Cypress의 많은 커맨드는 기대하는 걸 찾지 못하면 실패하도록 만들어져 있거든요. 이것을 **암묵적 단언(Implicit Assertion)**이라고 해요.

여기서 type 대신 페이지에 없는 hype을 넣어보면 약 4초 후에 테스트가 빨간색으로 바뀝니다. Cypress가 내부적으로 자동으로 기다리고 재시도하면서, 요소가 결과적으로 DOM에 나타나기를 기대했기 때문이에요. 바로 이 지점이 Cypress 커맨드의 재시도(Retry-ability)가 동작하는 순간이죠.

3단계: 클릭 (.click)

찾은 링크를 클릭해 보겠습니다. 앞선 커맨드 끝에 .click()을 붙이면 돼요.

it('clicks the link "type"', () => {
  cy.visit('https://example.cypress.io')
  cy.contains('type').click()
})

마치 작은 이야기를 읽는 것처럼 느껴지죠? Cypress는 이런 걸 **체이닝(chaining)**이라고 불러요. 커맨드를 이어 붙여서 앱의 동작을 선언적으로 표현하는 거예요. 클릭 후에는 앱이 링크를 따라가 목적지 페이지를 보여줍니다.

4단계: 검증 (.should)

이제 새로운 페이지에 대해 검증을 해 볼게요. 새로운 URL이 기대한 URL인지 cy.url().should()로 확인합니다.

it('clicking "type" navigates to a new url', () => {
  cy.visit('https://example.cypress.io')
  cy.contains('type').click()
  cy.url().should('include', '/commands/actions')
})

한 번의 상호작용과 검증으로 끝날 필요는 없어요. 이어서 cy.get()으로 클래스 기반 요소를 조회하고, .type()으로 입력을 채우고, 다시 .should()로 값이 반영됐는지 확인할 수 있습니다.

it('Gets, types and asserts', () => {
  cy.visit('https://example.cypress.io')
  cy.contains('type').click()
  cy.url().should('include', '/commands/actions')
  cy.get('.action-email').type('[email protected]')
  cy.get('.action-email').should('have.value', '[email protected]')
})

일반적으로 테스트의 흐름은 query → query → command 또는 assertion 형태가 좋아요. 액션 커맨드 뒤에는 체이닝을 이어 붙이지 않는 게 관례인데, 그 이유가 궁금하다면 retry-ability 문서를 참고하면 됩니다.

페이지 전환은 자동으로 처리돼요

이 테스트는 두 개의 다른 페이지를 오갔어요. 첫 cy.visit()부터 .click()으로 인한 새 페이지까지. Cypress는 page transition event를 자동으로 감지해서, 다음 페이지가 로딩을 완료할 때까지 실행 중인 커맨드를 멈춰 둡니다. 다음 페이지 로딩이 끝나지 않았다면 테스트는 오류를 내며 끝났을 거예요.

즉, 낡은 페이지나 반쯤 로드된 페이지에 커맨드가 실행되는 걱정을 하지 않아도 됩니다. 앞서 DOM 요소 검색이 4초를 기다렸던 것과 달리, Cypress는 페이지 전환을 감지하면 단일 PAGE LOAD 이벤트에 대해 타임아웃을 자동으로 60초까지 늘려줘요. 이 타임아웃 값들은 Configuration 문서에 정의되어 있어요.

더 알아보기