Cypressとreg-cliで画像回帰テスト 2022.05.06
Cypressとreg-cliを使って、手元で画像回帰テストをしてみた。姉妹品でreg-suitというS3などの外部と連携できるツールもあるが、今回は簡単にしたかったので薄いreg-cliを選択。
Next.jsのサンプルを構成
検証のため複数ページを簡単に構成する必要があったのでNext.jsのダイナミックルーティングのサンプルを使うことにした。
% npx create-next-app --example dynamic-routing dynamic-routing-app
# ...
% cd dynamic-routing-app
% yarn dev
これでダイナミックルーティングのサンプルアプリが開始されることを確認。
Cypressの導入
Cypressを追加する。まずは、公式のチュートリアルを動かせるようにする。
% yarn add -D cypress
% yarn run cypress open
これでCypressのテストランナーが動く。package.jsonにコマンドとして追加した。
"test": "cypress open"
% yarn test # cypress open
cypress/integration
以下にある例を削除して、新しくsample_spec.js
を追加。
% rm -rf cypress/integration/{1-getting-started,2-advanced-examples}
% touch cypress/integration/sample_spec.js
次のテストを追加。
// sample_spec.js
describe('My First Test', () => {
it('Visits the Kitchen Sink', () => {
cy.visit('https://example.cypress.io')
})
})
% yarn test
テストランナーが開くので、sample_spec.jsのリンクをクリックするとテストが動く。
このテストランナーは、画面上の要素をセレクター形式で取得できたりして、テストの構築自体にも便利。
Cypressでスクリーンショットを撮る
別プロセスでNext.jsのアプリを立ち上げておく。
% yarn build
% yarn start
スクリーンショットを撮るように、テストを変更。
// sample_spec.js
Cypress.Screenshot.defaults({ overwrite: true })
describe('My First Test', () => {
it('Visits the Kitchen Sink', () => {
cy.visit('http://localhost:3000').screenshot('Home')
})
})
テストランナーを使わずに即実行するようにpackage.jsonを変更。
"test": "cypress run",
% yarn test # cypress run
# ...
(Screenshots)
- /Users/same/dynamic-routing-app/cypress/screenshots/sample_spec.js/Home.png (1000x660)
# ...
これでスクリーンショットが撮れるようになった。cypress/screenshots
に保存されている。
テストと設定をまとめる
cypress.jsonに設定を少し追加しておく。
baseUrl
の指定- テストが実行される前に以前のスクリーンショットを削除する
- 動画は撮らない
// cypress.json
{
"baseUrl": "http://localhost:3000",
"trashAssetsBeforeRuns": true,
"video": false
}
サンプルアプリのその他のルーティングを追加して、最終的にテストは以下のようになっている。
// sample_spec.js
Cypress.Screenshot.defaults({ overwrite: true })
describe('My First Test', () => {
it('Run through and take screenshots', () => {
cy.visit('/').screenshot('Home')
cy.visit('/about').screenshot('About')
cy.visit('/post/first').screenshot('First')
cy.visit('/post/second').screenshot('Second')
})
})
Cypressにクリックをさせて画面遷移してもよいが、動的なナビゲーション(例えばレスポンシブで隠れてしまうなど)の場合に少し面倒になる。この場合は単にスクリーンショットを撮りたいだけなので、すべてvisit
で直接開くようにした。
reg-cliの導入
% yarn add -D reg-cli
reg-cliのコマンドを見てみると、expect(旧画像)とactual(新画像)を比較してdiffのディレクトリに差分を出力するという形になっている。これに合わせてディレクトリとコマンドを用意する。
$ reg-cli /path/to/actual-dir /path/to/expected-dir /path/to/diff-dir -R ./report.html
ディレクトリ構成
今回は以下の通りディレクトリを生成するようにした。
.
└── .reg
├── actual
├── diff
└── expect
実は、これはreg-suitの構成を拝借している。
スクリーンショットの処理とレポートの実行
今回は、手元でコマンドを実行して差分が期待どおりか、想定外の変更がでていないか人間が見る用途を想定した。回帰テストと言っているものの、ここでは差分が発生する前提で使っている。そういう意味では差分の承認としてexpectをactualで上書きする処理も必要だったかも。
- 旧画像を生成
- 新画像を生成
- 差分を生成、レポートを表示
例えば、旧画像としてmasterをチェックアウトしてスクリーンショットを撮影し、新画像としてfeatureブランチをチェックアウトしてスクリーンショットを撮影、差分を見るといった手順になる。package.jsonにコマンドを追加。
"test:expect": "rm -rf .reg && mkdir -p .reg/{actual,diff,expect} && cp -r cypress/screenshots/* .reg/expect",
"test:actual": "mkdir -p .reg/{actual,diff,expect} && cp -r cypress/screenshots/* .reg/actual",
"test:diff": "reg-cli .reg/actual .reg/expect .reg/diff -J .reg/reg.json -R .reg/index.html -M 0.1; npx http-server -c-1 .reg"
- reg-cliの実行結果に差分があるとテストとしては失敗扱いになるので、今回の用途では
&&
でコマンドをつなぐことができない。レポートの表示を前のコマンドの成否に関わらず実行させるためには;
にする。(Linuxコマンドを連続して使うには) -R
オプションで.reg
ディレクトリにレポートHTMLを吐き出すように指定。-J
オプションはデフォルトだとプロジェクトルートに生成されてしまうので.reg
ディレクトリを指定。- レポートを表示するには
http-server
を.reg
ディレクトリに対してキャッシュなしで実行するだけとした。
画像回帰テストの実行
旧画像の生成
さっそく、test:expect
を実行。
% yarn test:expect
先ほどスクリーンショットは撮影済みなので、これで旧画像としてmasterブランチのスクリーンショットがコピーされた。ここまでで、作業をコミットしておく。
% git init
Cypressなどのアセットを無視するように.gitingore
に追加。
# cypress
/cypress/screenshots
/cypress/videos
.reg
% git add .
% git commit -m "first commit"
新画像の生成
差分を出すため、少し変更を加える。
diff --git a/pages/about.js b/pages/about.js
index 255d076..4d1ace3 100644
--- a/pages/about.js
+++ b/pages/about.js
@@ -3,7 +3,7 @@ import Header from '../components/header'
const About = () => (
<>
<Header />
- <h1>About page</h1>
+ <h1>About page!!!</h1>
</>
)
diff --git a/pages/index.js b/pages/index.js
index 0f56525..4786d42 100644
--- a/pages/index.js
+++ b/pages/index.js
@@ -3,7 +3,7 @@ import Header from '../components/header'
const Home = () => (
<>
<Header />
- <h1>Hello World!</h1>
+ <h1>Hello World!!!</h1>
</>
)
diff --git a/pages/post/[id]/index.js b/pages/post/[id]/index.js
index c8f4a77..092545c 100644
--- a/pages/post/[id]/index.js
+++ b/pages/post/[id]/index.js
@@ -9,16 +9,16 @@ const Post = () => {
return (
<>
<Header />
- <h1>Post: {id}</h1>
+ <h1>Super Post: {id}</h1>
<ul>
<li>
<Link href="/post/[id]/[comment]" as={`/post/${id}/first-comment`}>
- <a>First comment</a>
+ <a>First comment!</a>
</Link>
</li>
<li>
<Link href="/post/[id]/[comment]" as={`/post/${id}/second-comment`}>
- <a>Second comment</a>
+ <a>Second comment!!</a>
</Link>
</li>
</ul>
別プロセスでサーバーを開始しておく。
% yarn build
% yarn start
featureブランチのつもりでブランチを切って変更をコミット。
% git checkout -b changed
% git add .
% git commit -m "changed"
変更後のスクリーンショットの撮影、新画像へコピーを行う。
% yarn test # cypress run
% yarn test:actual
検証
% yarn test:diff
検証ツールが立ち上がるので、差分を確認。
今回はCypressを使用したが、ほかのE2Eテストを行わないのであればpuppeteerでも十分かもしれない。