오랜만에 작성하는 e2e test... 오랜만에 해서 까먹기도하고, 복잡한 iframe 구조와 인증구조 때문에 어려움을 겪어 정리해본다
e2e test : e2e test는
End-to-End test
의 약자로, 사용자 관점에서 전체적인 시스템의 흐름을 테스트 하는 소프트웨어 테스트이다. 이 테스트의 목적은 실제 생산 환경과 유사한 환경에서 시스템이 전체적인 비즈니스 목표에 충족하는지 확인하기 위해 실제 사용자 시나리오를 시뮬레이션한다.
Cypress
란 e2e Testing library 중 하나로 e2e test를 위한 도구이다. headless 모드도 제공하지만, GUI 환경도 지원하여 손쉽게 테스트 및 관리할 수 있다는 장점이있다. (단위, 통합테스트도 제공)
$ yarn add cypress
// package.json
"scripts": {
...
"cy:open": "cypress open", (GUI)
"cy:run": "cypress run", (headless)
...
},
install 한 후 위의 script로 Cypress를 실행 할 수 있다.
기본적으로 아래의 폴더구조를 가진다.
cypress/e2e
: e2e 테스트 파일들이 위치 *.cy.js
의 네이밍을 가진다.
cypress/fixture
: 테스트에 사용할 정적 데이터 파일들을 보관한다.
cypress/support
: 공용으로 사용할 함수를 정의하거나, 테스트 전 사전에 실행할 코드를 작성한 파일들을 보관한다. (보통 commands.js 에 함수를 정의)
이외의 옵션에 따라 videos
, screenshots
등이 추가된다.
// test.cy.js
describe('test 대분류명', () => {
before(() => {
// 블록의 모든 테스트 전에 한 번 실행
})
beforeEach(() => {
// 블록의 각 테스트 전에 실행
})
afterEach(() => {
// 블록의 각 테스트 후에 실행
})
after(() => {
// 블록의 모든 테스트 후 한 번 실행
})
it('테스트 이름', () => {
// test code
});
context('테스트 그룹 이름', () => { // context -> 가독성을 위해 그룹화 용도로 사용 (optional)
it('테스트2 이름', () => {
// test code
});
it('테스트3 이름', () => {
// test code
});
});
})
*.cy.js
는 기본적으로 위의 구조를 가진다.
아래는 기본적인 cy 함수들이다. 아래의 함수들을 활용하여 테스트 코드를 작성한다.
cy.viewport()
: 테스트 화면 크기 지정
cy.viewport(1920, 1080);
cy.visit()
: 웹 사이트 방문
cy.visit('https://google.com');
cy.wait()
: 대기 (단위는 ms)
cy.wait(3000);
cy.get()
: 선택자에 해당하는 엘리먼트를 반환 (여러개 가져올수있음)
cy.get('#title');
cy.get('.title');
// Cypress에서는 html element에 data-cy attribute를 추가해 사용할 것을 권장한다.
cy.get('[data-cy="title"]');
cy.contains()
: text 내용을 포함한 엘리먼트 반환
cy.contains('제목');
cy.find()
: 이미 선택된 요소의 자식 요소를 반환
cy.get('#my-form').find('#my-btn');
cy.type()
: 텍스트 입력
cy.get('#my-input').type('입력값')
cy.click()
: 요소 클릭
cy.get('#my-btn').click();
cy.should()
: 특정 조건이 충족되는지 확인
cy.contains('제목').should('exist');
cy.get('#my-btn').should('be.disabled');
cy.get('#my-btn').should('not.be.disabled');
cy.get('#my-input').should('have.value', value);
cy.get('#my-tab').should('have.attr', 'aria-selected', 'true');
// 등등...
cy.intercept
: 네트워크 요청을 가로채고 제어함
cy.intercept('GET', '/user').as('getUser');
// api 요청까지 대기
cy.wait('@getUser');
// api 요청 code 확인
cy.wait('@getUser').then((api) => {
expect(api.response.statusCode).to.eq(200);
});
// post user 요청에 대해 실제 요청을 보내지않고, response를 반환함
cy.intercept('POST', '/user', { statusCode: 200 });
commands.js
에 자주 사용하는 테스트 코드를 함수화 하여 작성할 수 있다.
// commands.js
Cypress.Commands.add('initService', (url) => {
cy.viewport(1920, 1080);
cy.visit(url, { headers });
});
// test.cy.js
cy.initService('https://google.com');
지금 내가 테스트하는 환경은 iframe 내에서 동작하고있어 조금 다르게 진행해야했다.
wrap
과 within
을 사용하여 테스트코드를 작성하고있다.
여기서 within
은 특정 요소를 선택한 뒤 이후 cy 메소드의 범위를 선택한 요소 내부로 제한한다.
// commands.js
Cypress.Commands.add('getIframe', () => {
return cy.get('#iframe').then(($iframe) => {
const $body = $iframe.contents().find('body');
return cy.wrap($body);
});
});
// test.cy.js
cy.getIframe().within(() => {
// 테스트 코드 ...
});