📌 목차
- 서론
- Visual Regression Test를 적용한 이유
- Visual Regression Test란?
- Visual Regression Test 핵심 용어 정리
- 왜 Cypress를 선택하였는가?
- 설치 및 환경 설정
- script 명령어 소개
- 테스트 소개
- 서버 데이터 mocking
- 결론
📌 서론
안녕하세요! 에디 입니다. 오늘은 회사에서 Visual Regression Test를 적용한 이유 그리고 과정을 한번 소개를 해보겠습니다.
그럼 출발! 🚀
📌 Visual Regression Test를 적용한 이유
기존 프로젝트에는 styled-component로 세팅이 되어있었는데 이걸 emotion으로 포팅을 하게 되었습니다. emotion의 더 다양한 기능을 사용하기 위해 변경 작업을 진행했지만 비슷한 듯 조금 씩 다른 문법 그리고 styled-component에서는 의도한 대로 화면이 그려졌지만 emotion으로 넘어가면서 레이아웃이 깨지는 문제가 발생을 했어요. 그래서 emotion으로 변경할 때 마다 일일이 확인해야하는 번거로움이 있었습니다.
📌 Visual Regression Test란?
Visual Regression Test란 가시적으로 보여지는 화면에서 기존과 다른 부분(레이아웃)을 체크하는 Testing 입니다.여기서 중요한 건 레이아웃을 확인 한다는 점 이예요. 단순하게 글자가 추가되었거나 하는 부분은 레이아웃으로 인식을 안해서 단순 통과가 됩니다. 전체적인 틀을 확인 하는 테스트가 포인트 입니다.
Visual Regression Test의 순서는 대략 이렇습니다.
1.시나리오 실행
2.브라우저에서 동작
3-1. Reference Screens가 없다면 Reference Screens를 저장
3-2. Reference Screens가 있다면 Test Screens와 비교
4-1. Reference Screens와 달라진 점이 없다면 테스트 통과
4-2. Reference Screens와 달라진 점이 있다면 Report 발행 및 테스트 실패
그림으로 나타내면 다음과 같아요.
📌 Visual Regression Test 핵심 용어 정리
Visual Regression Test에서 가장 중요한 용어는 다음 3가지 이예요.
- Scenarios
- Reference Screens
- Test Screens
1. Scenarios
Scenarios는 테스트 코드를 의미합니다. 어떤 페이지의 레이아웃을 테스트 할 것 인가를 정의합니다.
2. Reference Screens
Reference Screens는 기준이 되는 스크린 샷 입니다. 레이아웃을 고치기 전에 스크린 샷이라고 이해하시면 빠를 것 같아요.
3. Test Screens
Test Screens는 테스트를 할 스크린 샷 입니다. 변경이 이루어진 스크린 샷으로 Reference Screens와 비교를 하게 됩니다.
📌 왜 Cypress를 선택하였는가?
저는 회사에서 Visual Regression Test를 위해 Cypress를 선택하였습니다. 다른 툴들 말고 Cypress를 선택한 이유는 다음과 같아요.
- 추후 해당 프로젝트에서 e2e 테스트를 해야함으로 툴을 통합하기 위함
- 많은 사용자 수를 보유하고 있어 문서, 참고 문헌이 많이 쓰여있음
- 무료 ⭐️
📌 설치 및 환경 설정
설치
먼저 3가지 패키지를 다운 받습니다. (cypress, cypress-image-snapshot, @types/cypress-image-snapshot)
🚧 @types/cypress-image-snapshot은 typescript용 이기 때문에 javascript 사용자는 설치 할 필요가 없습니다.
yarn install cypress cypress-image-snapshot @types/cypress-image-snapshot -D
이렇게 실행을 하시면 cypress라는 폴더가 생기셨을 거예요.
그럼 이제 config를 한번 건드려 볼까요?
환경 설정
cypress.config.ts파일을 하나 만들어 줍니다.
// cypress.config.ts
import { defineConfig } from 'cypress'
import { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin'
export default defineConfig({
e2e: {
"baseUrl": "여기에 베이스 주소를 넣어주세요",
env: {
// 🌈 이건 아래서 설명 !
"ALLOW_SNAPSHOT": process.env.ALLOW_SNAPSHOT === undefined ? false : true
},
// ⭐️ 이걸 해주셔야 matchSnapshot이라는 cypress command를 사용하실 수 있습니다.
setupNodeEvents(on, config) {
addMatchImageSnapshotPlugin(on, config)
}
},
})
📌 script 명령어 소개
script 명령어는 총 4가지 명령어로 구성이 되어있습니다.
// 📌 콘솔에서만 보고 싶을 때
// --browser chrome 명령은 실행하는 환경을 정의합니다.
yarn test:cypress // "cypress run --browser chrome"
yarn test:snapshot // "ALLOW_SNAPSHOT=true cypress run -- browser chrome"
// 📌 실제 제대로 동작을 하는지 보고 싶을 때
yarn test:cypress-visual // "cypress open"
yarn test:snapshot-visual // "ALLOW_SNAPSHOT=true cypress open"
엥 왜 명령어가 4개야? 🤔
두가지 이유로 4개의 명령어가 탄생했습니다.
- 추후에 있을 e2e 테스트와 Visual Regression Test를 분리 하기 위해
- Visual based와 console based를 분리하기 위해
추후에 있을 e2e 테스트와 Visual Regression Test를 분리 하기 위해
ALLOW_SNAPSHOT을 통해 Visual Regression Test인지 e2e 테스트인지 분리를 합니다. 그리고 cypress.config.ts에서 env 설정 값으로 넘겨주게 되어있어요.
// cypress.config.ts
import { defineConfig } from 'cypress'
import { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin'
export default defineConfig({
e2e: {
"baseUrl": "여기에 베이스 주소를 넣어주세요",
env: {
// cypress에 env 값을 넣어줍니다. ALLOW_SNAPSHOT이라면 true를 넣어주고 아니라면 false로 값을 저장합니다.
"ALLOW_SNAPSHOT": process.env.ALLOW_SNAPSHOT === undefined ? false : true
},
setupNodeEvents(on, config) {
addMatchImageSnapshotPlugin(on, config)
}
},
})
그리고 위의 ALLOW_SNAPSHOT env를 통해 matchImageSnapShot 명령어를 재정의 해줍니다.
// cypress/support/commands
Cypress.Commands.overwrite(
'matchImageSnapshot',
(originalFn, snapshotName, options) => {
if (Cypress.env('ALLOW_SNAPSHOT')) { // flag 값을 통해 변경이 가능함
originalFn(snapshotName, options)
} else {
cy.log(`Screenshot comparison is disabled`)
}
}
)
ALLOW_SNAPSHOT이 true 라면 기존 cypress-image-snapshot의 명령어인 matchImageSnapshot을 실행하고 그렇지 않다면 그냥 로그를 찍는 형태로 재정의를 해줍니다.
Visual based와 console based를 분리하기 위해
Visual와 console을 분리하는 명령어는 run과 open으로 분리가 되었습니다.
Visual | Console |
---|---|
📌 테스트 소개
그럼 이제 모든 준비가 끝났습니다. 이제 한번 테스트 코드를 작성해볼까요?
다음 테스트는 /notices/3rd-parties 라는 페이지로 들어가서 데스크톱 사이즈에서 한번 촬영을 하고 모바일 사이즈에서 다시 한번 촬영을 할 거예요.
describe('notices snapshot test', () => {
beforeEach(() => {
/** Init size of view port */
cy.setDesktopViewport() // 화면을 desktop 사이즈로 변경
})
it('3rd parties snapshot test', () => {
cy.visit('/notices/3rd-parties')
/** Desktop size */
cy.prepareSnapshot() // 🚨 이 명령어를 입력해야 스크린샷을 찍을 준비가 됌(스크롤이 가능해짐)
cy.matchImageSnapshot('notices/3rd-parties-desktop', {
capture: 'fullPage',
})
/** Mobile size */
cy.setMobileViewport() // mobile 사이즈로 변경
cy.matchImageSnapshot('notices/3rd-parties-mobile', { capture: 'fullPage' })
})
})
위에서 사용 된 커스텀 명령어는 다음과 같습니다.
// cypress/support/commands
/**
* @description Snapshot 을 촬영할 때 scroll 이 전체 영역을 다 찍을 수 있도록 변경
*/
Cypress.Commands.add('prepareSnapshot', () => {
cy.get('div[id=root]')
.invoke('css', 'position', 'absolute')
.invoke('css', 'width', '100%')
})
/**
* @description 페이지가 전부 랜딩을 되기 위해 wait(1000) 을 사용했지만 url을 통한 방법이 있어 visit 를 재활용
* @see https://newdevzone.com/posts/how-to-wait-until-page-is-fully-loaded
*/
Cypress.Commands.overwrite('visit', (originalFn, args) => {
originalFn(args)
cy.url().should('include', 'https://local-ditto.devel.kakao.com/')
})
/**
* @description Cypress 의 viewport 를 desktop size 로 조정
*/
Cypress.Commands.add('setDesktopViewport', () => {
cy.viewport(1280, 800)
})
/**
* @description Cypress 의 viewport 를 mobile size(IPHONE_XR) 로 조정
*/
Cypress.Commands.add('setMobileViewport', () => {
cy.viewport(375, 667)
})
그리고 커스텀 명령어의 타입을 정의해줍니다.
// cypress/support/index.ts
declare namespace Cypress {
interface Chainable {
prepareSnapshot: () => void
setDesktopViewport: () => void
setMobileViewport: () => void
}
}
그럼 이제 한번 실제로 돌려볼까요?
가장 왼쪽에 있는 건 Reference Screens 입니다. 그리고 가장 오른쪽에 있는 건 Test Screens 입니다. Reference Screens가 Test Screens와 다르게 되면 중간에 보이는 것 처럼 Report가 발행이 됩니다. 어느 구간에서 어떻게 변경이 이루어 졌는지 나타내 줍니다.
📌 서버 데이터 mocking
만약 매번 새로운 데이터로 인해 테스트가 깨진다면 어떻게 대처를 할 수 있을까요? Cypress에서는 손 쉽게 intercept를 사용하여 server에서 내려주는 데이터를 mock 할 수 있습니다.
먼저 cypress/fixtures 폴더에 내려줄 데이터를 json으로 저장을 합니다.
// apollo를 사용함으로 POST로 요청을 받아옵니다.
cy.intercept('POST', '/api/v1', (req) => {
if (req.body.operationName === 'QueryItem')
req.reply({ fixture: 'shuttle-item.json' })
}).as('data-fetch')
// intercept 데이터가 다 내려올 때 까지 기다려줍니다.
cy.wait('@data-fetch')
📌 결론
이렇게 styled-components에서 emotion으로 편하게 변경 작업을 진행 했습니다. 여러분도 혹시 화면에 대한 변경 사항을 일일이 쫓아가는 작업을 하시고 계시다면 Cypress snapshot test를 추천드릴게요. 그럼 긴 글 읽어주셔서 감사합니다! 🙇♂️ 그럼 또 만나요 안녕 ~
'회사이야기' 카테고리의 다른 글
getInitialProps, getServerSideProps, getStaticProps에서 브라우저 로그를 찍을수 있을까? (0) | 2023.04.30 |
---|