サイプレスを使用した視覚的回帰テスト:実用的なアプローチ
公開: 2022-03-11コンポーネントライブラリの新しいバージョンであるPicassoがリリースされるたびに、すべてのフロントエンドアプリケーションを更新して、新しい機能を最大限に活用し、サイトのすべての部分でデザインを調整します。
先月、私たちはPicassoのアップデートをToptal Talent Portalに公開しました。これは、私たちの才能が仕事を見つけてクライアントとやり取りするために使用するプラットフォームです。 リリースには大きな設計変更が伴うことを知っており、予期しない問題を最小限に抑えるために、リリース前に問題を見つけるのに役立つ視覚的な回帰テスト手法を使用することは理にかなっています。
視覚的回帰テストは新しい概念ではありません。 ピカソ自体を含め、Toptalの他の多くのプロジェクトがすでにそれを使用しています。
Percy、Happo、Chromaticなどのツールを使用して、チームが健全な視覚的回帰パイプラインを構築できるようにすることができます。最初は、それらを追加することを検討しました。 最終的に、セットアッププロセスには時間がかかりすぎて、スケジュールが狂う可能性があると判断しました。 移行を開始するためのコードフリーズの日付はすでに設定されており、締め切りまであと数日しか残っていないため、創造性を発揮するしかありませんでした。
UIテストによる視覚的回帰テスト
プロジェクトには視覚的な回帰テストはありませんでしたが、サイプレスを使用したUI統合テストについては十分にカバーしていました。 これはツールの主な用途ではありませんが、Cypressのドキュメントには、視覚テスト専用のページと、視覚テスト用にCypressを構成するために使用できるすべてのプラグインをリストした別のページがあります。
サイプレスからスクリーンショットまで
入手可能なドキュメントを確認した後、cypress-snapshot-pluginを試してみることにしました。 セットアップには数分しかかかりませんでしたが、セットアップすると、従来の視覚的な回帰出力を追求していないことにすぐに気付きました。
ほとんどの視覚的回帰ツールは、スナップショットを比較し、既知の受け入れられたベースラインとページまたはコンポーネントの変更されたバージョンとの間のピクセルの違いを検出することにより、不要な変更を識別するのに役立ちます。 ピクセル差が設定された許容しきい値よりも大きい場合、ページまたはコンポーネントに手動で検査するようにフラグが立てられます。 ただし、このリリースでは、ほとんどのUIコンポーネントにいくつかの小さな変更が加えられることがわかっていたため、しきい値の設定は適用できませんでした。 特定のコンポーネントがたまたま100%異なっていたとしても、新しいバージョンのコンテキストでは正しい可能性があります。 同様に、わずか数ピクセルの偏差は、コンポーネントが現在生産に適していないことを意味する可能性があります。
その時点で、2つの対照的なことが明らかになりました。ピクセルの違いが問題の特定に役立たないことと、コンポーネントを並べて比較することがまさに必要なことでした。 スナップショットプラグインを脇に置き、ピカソの更新が適用される前後に、コンポーネントを使用して画像のコレクションを作成することに着手しました。 そうすれば、すべての変更をすばやくスキャンして、新しいバージョンがサイトのニーズとライブラリの標準にまだ一致しているかどうかを判断できます。
新しい計画では、コンポーネントのスクリーンショットを撮り、それをローカルに保存し、更新されたPicassoバージョンを使用してブランチ内の同じコンポーネントの新しいスクリーンショットを撮り、それらを1つのイメージにマージすることでした。 最終的に、この新しいアプローチは当初のアプローチとそれほど変わりませんでしたが、プラグインをインポートして新しいコマンドを使用する必要がなくなったため、実装フェーズでの柔軟性が向上しました。
比較画像用のAPIの活用
明確な目標を念頭に置いて、サイプレスが必要なスクリーンショットを取得するのにどのように役立つかを検討するときが来ました。 前述のように、タレントポータルの大部分をカバーするUIテストが大量にあったため、できるだけ多くの重要なコンポーネントを収集するために、各インタラクションの後に個々の要素のスクリーンショットを撮ることにしました。
別のアプローチは、テスト中の重要な瞬間にページ全体のスクリーンショットを撮ることでしたが、それらの画像を比較するのは難しすぎると判断しました。 また、このような比較では、フッターが変更されたことを見逃すなど、人為的エラーが発生しやすくなる可能性があります。
3番目のオプションは、すべてのテストケースを調べて何をキャプチャするかを決定することでしたが、これにはかなりの時間がかかるため、ページで使用されるすべての要素に固執することは実際的な妥協のように思われました。
サイプレスのAPIを使用して画像を生成しました。 cy.screenshot()コマンドを使用すると、すぐにコンポーネントの個々のイメージを作成できます。AfterScreenshot APIを使用すると、ファイルの名前を変更したり、ディレクトリを変更したり、視覚的な回帰実行を標準のものと区別したりできます。 この2つを組み合わせることで、機能テストに影響を与えない実行を作成し、適切なフォルダーに画像を保存できるようにしました。
まず、プラグインディレクトリのindex.jsファイルを拡張して、2つの新しい実行タイプ(ベースラインと比較)をサポートしました。 次に、実行タイプに従ってイメージのパスを設定します。
// plugins/index.js const fs = require('fs') const path = require('path') module.exports = (on, config) => { // Adding these values to your config object allows you to access them in your tests. config.env.baseline = process.env.BASELINE || false config.env.comparison = process.env.COMPARISON || false on('after:screenshot', details => { // We only want to modify the behavior of baseline and comparison runs. if (config.env.baseline || config.env.comparison) { // We keep track of the file name and number to make sure they are saved in the proper order and in their relevant folders. // An alternative would have been to look up the folder for the latest image, but this was the simpler approach. let lastScreenshotFile = '' let lastScreenshotNumber = 0 // We append the proper suffix number to the image, create the folder, and move the file. const createDirAndRename = filePath => { if (lastScreenshotFile === filePath) { lastScreenshotNumber++ } else { lastScreenshotNumber = 0 } lastScreenshotFile = filePath const newPath = filePath.replace( '.png', ` #${lastScreenshotNumber}.png` ) return new Promise((resolve, reject) => { fs.mkdir(path.dirname(newPath), { recursive: true }, mkdirErr => { if (mkdirErr) { return reject(mkdirErr) } fs.rename(details.path, newPath, renameErr => { if (renameErr) { return reject(renameErr) } resolve({ path: newPath }) }) }) }) } const screenshotPath = `visualComparison/${config.env.baseline ? 'baseline' : 'comparison'}` return createDirAndRename(details.path .replace('cypress/integration', screenshotPath) .replace('All Specs', screenshotPath) ) } }) return config } 次に、プロジェクトのpackage.jsonのCypress呼び出しに対応する環境変数を追加して、各実行を呼び出しました。

"scripts": { "cypress:baseline": "BASELINE=true yarn cypress:open", "cypress:comparison": "COMPARISON=true yarn cypress:open" }新しいコマンドを実行すると、実行中に撮影されたすべてのスクリーンショットが適切なフォルダーに移動されたことがわかります。
次に、サイプレスのメインコマンドであるcy.get()を上書きしてDOM要素を返し、デフォルトの実装とともに呼び出された要素のスクリーンショットを撮りました。 残念ながら、 cy.get()は、独自の定義で元のコマンドを呼び出すと無限ループが発生するため、変更するのが難しいコマンドです。 この制限を回避するための推奨されるアプローチは、別のカスタムコマンドを作成し、要素を見つけた後、その新しいコマンドにスクリーンショットを撮らせることです。
Cypress.Commands.add("getAndScreenshot", (selector, options) => { // Note: You might need to tweak the command when getting multiple elements. return cy.get(selector).screenshot() }); it("get overwrite", () => { cy.visit("https://example.cypress.io/commands/actions"); cy.getAndScreenshot(".action-email") }) ただし、ページ上の要素を操作するための呼び出しは、すでに内部のgetElement()関数でラップされています。 したがって、ラッパーが呼び出されたときにスクリーンショットが撮られていることを確認するだけで済みました。
視覚的回帰テストで得られた結果
スクリーンショットを取得したら、あとはそれらをマージするだけです。 そのために、Canvasを使用して単純なノードスクリプトを作成しました。 結局、このスクリプトにより、618個の比較画像を生成することができました。 タレントポータルを開くことで、いくつかの違いを簡単に見つけることができましたが、いくつかの問題はそれほど明白ではありませんでした。
UIテストに付加価値を与える
まず、追加された視覚的回帰テストが有用であることが証明され、それらがなければ見逃していた可能性のあるいくつかの問題が明らかになりました。 コンポーネントの違いを期待していましたが、実際に何が変更されたかを知ることで、問題のあるケースを絞り込むことができました。 したがって、プロジェクトにインターフェースがあり、まだこれらのテストを実行していない場合は、それを入手してください。
ここでの2番目の教訓、そしておそらくもっと重要な教訓は、完璧は善の敵であることをもう一度思い出させたことです。 事前の設定がなかったためにこのリリースの視覚的回帰テストを実行する可能性を除外した場合、移行中にいくつかのバグを見逃した可能性があります。 代わりに、私たちは理想的ではありませんが、実行が速いという計画に同意し、それに向けて取り組み、それが報われました。
プロジェクトに堅牢な視覚的回帰パイプラインを実装する方法の詳細については、サイプレスの視覚的テストページを参照し、ニーズに最適なツールを選択して、チュートリアルビデオをご覧ください。
