Visual Regression Test with jest-image-snapshot, puppeteer and Circle CI

Taehwan Noh
11 min readAug 19, 2017

--

Visual Regression Test with jest-image-snapshot, puppeteer and Circle CI

jest-image-snapshotpuppeteer 그리고 Circle CI 2.0을 통해 visual regression test를 진행해보도록 하겠습니다. 이번 글과 관련된 코드는 taehwanno/jest-image-snapshot-example에서 보실 수 있습니다.

Visual Regression Test

Regression testing is a type of software testing which verifies that software which was previously developed and tested still performs the same way after it was changed or interfaced with other software. — Regression Testing Wikipedia

Regression Test는 테스트 완료된 소프트웨어의 코드가 변했을 때 이전과 동일한 방식으로 동작하는지 검증하는 테스트 방법 중 하나입니다.

… The purpose of regression testing is to ensure that changes such as those mentioned above have not introduced new faults. One of the main reasons for regression testing is to determine whether a change in one part of the software affects other parts of the software. — Regression Testing Wikipedia

코드가 변할 때 새로운 결함이 생겼는지 검증하는 것이 Regression Test의 목적이며 브라우저에서 Visual Regression Test의 경우 HTML, CSS, JavaScript코드 수정으로 인한 UI 관련 부작용을 테스트할 수 있습니다.

Prerequisite

  • node ≥ v7.6.0
  • jest ≥ v20.0.0
  • yarn

puppeteer은 기본적으로 node ≥ v6.4.0을 필요로 하며 async, await 구문을 사용할 경우 node ≥ v7.6.0을 필요로 합니다. 그리고 jest-image-snapshot에서 jest ≥ v20.0.0을 필요로 합니다.

Installation and Environment Setup

jest, jest-image-snapshot, puppeteer 설치를 진행합니다.

$ yarn add --dev jest jest-image-snapshot puppeteer

commonjs가 아닌 ES module syntax 사용을 위해 babel-plugin-transform-es2015-modules-commonjs를 설치합니다.

$ yarn add --dev babel-plugin-transform-es2015-modules-commonjs

.babelrc에 해당 플러그인을 test 환경에서 사용하도록 명시합니다.

// .babelrc{
"env": {
"test": {
"plugins": ["transform-es2015-modules-commonjs"]
}
}
}

Scripts

테스트 script를 package.json에 추가하며 server 실행 script도 추가합니다.

// package.json"start": "node server.js",
"test": "NODE_ENV=test jest --config jest.config.json",
"test:coverage": "yarn test -- --coverage",
"test:watch": "yarn test -- --watch"

snapshot은 yarn test -- -u를 통해 최신화할 수 있습니다.

Setup Test

jest expect.extend를 활용해서 expect().toMatchImageSnapshot을 통해 jest snapshot 테스트와 같은 형식으로 코드를 작성할 수 있게 됩니다. 프로젝트 root에jest.config.json을 생성한 후 setupTestFrameworkScriptFile<rootDir>/test/setupTests.js를 추가합니다.

// jest.config.json{
"setupTestFrameworkScriptFile": "<rootDir>/test/setupTests.js"
}

test/setupTests.js에 아래 코드를 작성합니다.

// test/setupTests.jsimport { toMatchImageSnapshot } from 'jest-image-snapshot';expect.extend({ toMatchImageSnapshot });

jest-image-snapshot의 경우 blink-diff를 통해 이미지를 비교하게 되며 기본적으로 1% 이상 변경될 경우 테스트가 실패하도록 내부적으로 설정되어 있습니다. (customDiffConfig을 통해 변경 가능) 또한 이미지 스냅샷이 저장되는 경로는 기본적으로 테스트 파일과 동일한 위치의 __image_snapshots__ 폴더에 저장되며 각 이미지의 diff 결과는 __image_snapshots__/__diff_output__폴더에 생성됩니다. .gitignore에 diff output 파일 경로를 추가해서 git에 의해 추적되지 않도록 합니다.

# .gitignorenode_modules/
test/**/__diff_output__

Test code

테스트 코드를 작성합니다. 브라우저 실행 후 localhost:3000에서 페이지를 불러온 뒤 스크린샷으로 저장하고 jest-image-snapshot을 이용해서 테스트를 진행하는 코드입니다. 3000번 포트로 서버를 실행한 상태로 다른 터미널에서 yarn test를 통해 테스트를 진행하면 됩니다.

// test/index.spec.jsimport puppeteer from 'puppeteer';

it('Visual regression test', async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('http://localhost:3000');
const screenshot = await page.screenshot();
browser.close();

expect(screenshot).toMatchImageSnapshot();
});

expectbrowser.close() 보다 앞에 위치할 경우 테스트 실패 시 yarn test script를 통해 실행된 프로세스가 정상적으로 종료되지 않고 계속 실행되므로 스크린샷을 가져온 후 browser.close 코드가 먼저 호출되어야 한다는 것을 주의하시길 바랍니다.

Circle CI 2.0

전체 과정은 아래와 같이 5단계로 구성됩니다.

부분적으로 나와 있는 Circle CI config.yml은 steps을 의미합니다. 전체 Circle CI 2.0에 대한 config.yml은 해당 링크에서 보실 수 있습니다.

npm 모듈 설치의 경우 cache에 대한 처리와 같이 step에 명시해줍니다.

- restore_cache:
key:
dependency-cache-{{ .Branch }}-{{ checksum "yarn.lock" }}
- run:
name:
Install Dependency
command: yarn install
- save_cache:
key:
dependency-cache-{{ .Branch }}-{{ checksum "yarn.lock" }}
paths:
- ./node_modules
- ~/.yarn-cache

Circle CI에서는 빌드 환경으로 Linux를 사용할 경우 기본적으로 Ubuntu 14.04를 제공하고 있습니다. GoogleChrome/puppeteer #290 이슈에서 알 수 있듯이 현재 Debian 계열 리눅스에서 의존성 및 샌드박스에 대한 문제가 발생하고 있으며 Circle CI에서 제공하는 Ubuntu Linux 또한 puppeteer을 이용한 headless chrome 실행과 관련된 모든 의존성이 설치된 것은 아니므로 추가적으로 설치하는 과정이 필요하게 됩니다.

이슈 코멘트에 명시된 의존성을 설치하는 스크립트를 작성하고 해당 스크립트를 실행하는 step을 config.yml에 명시해줍니다.

- run:
name:
Workaround for GoogleChrome/puppeteer/issues/290
command: sh ./scripts/workaround-puppeteer-issue-290.sh

서버를 background 프로세스로 실행하는 step을 추가합니다.

- run:
name:
Start Server
command: yarn start
background: true

테스트를 실행합니다. background 프로세스로 실행할 경우 곧바로 다음 step이 실행되므로 서버가 확실하게 준비될 시간을 주기 위해 sleep을 통해 2초가 지난 후 테스트를 실행하도록 작성되어 있습니다. 조금 더 확실하게 하고 싶다면 curl을 이용한 heath check script를 작성하시면 됩니다.

- run:
name:
Test
command: |
sleep 2
yarn test -- --maxWorkers=2

마지막으로 이미지 스냅샷 diff output을 Circle CI Artifacts로 업로드하는 step을 추가합니다. 아쉽게도 현재 시점의 Circle CI 2.0에서는 glob pattern을 artifacts path에서 지원하지 않으며 폴더구조가 복잡할 경우 하나하나 명시해줘야합니다.

- store_artifacts:
path:
test/__image_snapshots__/__diff_output__
destination: image_snapshot_diff

Circle CI Artifacts 활용

디자이너 혹은 개발자가 코드를 변경한 후 Pull Request를 생성했을 때 이미지 스냅샷 diff output을 artifacts로 업로드하도록 step을 추가했으므로 이를 기반으로 리뷰가 가능해집니다.

taehwanno/jest-image-snapshot-example 저장소의 #1처럼 위치 및 배경색을 변경하고 Pull Request를 제출합니다. Push 이벤트에 따라 자동으로 테스트가 되므로 성공, 실패 여부를 바로 알 수 있으며 아래 코멘트처럼 artifacts를 활용할 수 있습니다.

Comment

Image Snapshot Diff Output

원래 세로로 나오지만, 글을 위해 가로로 편집을 했습니다. 왼쪽에서 오른쪽 방향이 기존 파일의 위에서 아래 방향입니다. 왼쪽 그림이 현재 페이지의 스크린샷 이미지이며 오른쪽 그림이 코드 수정하기 전에 커밋되어있던 페이지의 스크린샷 이미지입니다. 중간에 있는 그림을 통해 변화 정도를 시각적으로 알 수 있습니다.

Image Snapshot Diff Output

Conclusion

잦은 변화가 일어나는 랜딩 페이지에서 jest를 활용해서 최소한의 테스트로 효율적으로 협업하고 리뷰할 방법을 고민하다가 글을 쓰게 되었습니다. 많은 도움이 되셨으면 좋겠습니다.

--

--