https://d226lax1qjow5r.cloudfront.net/blog/blogposts/how-to-build-a-visual-regression-test-system-using-playwright/visual-regression-test.png

Playwrightを使用したビジュアル回帰テスト・システムの構築方法

最終更新日 August 23, 2022

所要時間:1 分

例えば、Joeが "button "再利用可能コンポーネントで作業していて、変更を加え、ボタンが期待通りに動作することを確認したとしましょう。ジョーは、この変更によって他のコンポーネントがうまく見えることを知っているのでしょうか?ジョーは、どのコンポーネントがボタンに依存しているかを知り、それらすべてを確認し、さまざまなシナリオでボタンが問題なく見えることを視覚的に確認する必要があります。そして、異なるブラウザについてはどうだろう?テストマトリックスは増え続けるばかりだ...。

ビジュアル・テストのプロセスを自動化することで、ジョーが時間を有効に使えるようにしよう!

ジョーと同じように、あなたはアプリを動かしているか、コンポーネントを作成してそれを世界と共有したいと考えている。変更のたびに、あなた(あるいはチームの誰か)は何時間もかけてUIを見直し、ピクセルが完璧であることを確認する。それでも、時には人間の注意の隙間から抜け落ちてしまうこともある。現実を直視しよう-ピクセルのマッチングにおいては、機械は我々よりも優れている。また、コストも安く、何度でもやり直すことができる。

ここから私たちの物語が始まる。私たちは UIコンポーネントの束.また コンポーネントのドキュメントで「遊ぶ」(手作業でテストする)ことができます。

ドキュメントは、各コンポーネントのフォルダ内にあるreadmeファイルから作成されます。 以下に例を示します。.

例は単純なHTMLスニペットであることに注意してください:

<vwc-icon type="heart-solid" connotation="accent"></vwc-icon>
<vwc-icon type="heart-solid" connotation="announcement"></vwc-icon>
<vwc-icon type="heart-solid" connotation="cta"></vwc-icon>
<vwc-icon type="heart-solid" connotation="success"></vwc-icon>
<vwc-icon type="heart-solid" connotation="alert"></vwc-icon>
<vwc-icon type="heart-solid" connotation="info"></vwc-icon>

また後で話そう。

つまり...約50のコンポーネントがあり、それらを視覚的に確認しながら開発し、手動でテストする方法がある。それは素晴らしい!出荷する前にコンポーネントを確認できるようにしたいですよね :).

これらのコンポーネントの中には、複数のアトミック・コンポーネントで構成されているものもある。つまり、「ボタン」コンポーネントを変更すると、ボタンを使用している複数の複合コンポーネントに影響を与える可能性があるということだ。

プロジェクトに劇作家を設定するには?

まずは走ることだ:

npm i -D playwright

これでいろいろなことができそうだ:

  1. パッケージ.jsonファイルに playwrightをpackage.jsonファイルに追加する。

  2. インストール playwright現地で

  3. インストール playwrightのブラウザをローカルにインストールする。

また playwright.configファイルをセットアップする必要がある。今回の紹介では(そして私が個人的にやっていることでもあるので)、ファイル・セットアップに typescriptバージョンを使いますが、普通のjavascriptを使うこともできます。次のような内容の playwright.config.tsファイルを作成します:

import type {
  PlaywrightTestConfig
} from '@playwright/test';

const config: PlaywrightTestConfig = {
  testMatch: 'src/**/*.test.ts',
  projects: [{
      name: 'Chrome Stable',
      use: {
        browserName: 'chromium',
        channel: 'chrome',
      },
    },
    {
      name: 'Desktop Safari',
      use: {
        browserName: 'webkit',
      }
    },
    {
      name: 'Desktop Firefox',
      use: {
        browserName: 'firefox',
      }
    },
  ]
};

export default config;

上記のコンフィギュレーションは以下のようなものだ:

  1. testMatch:テストファイルの取得場所をplaywrightに指定します。パスはファイルの場所からの相対パスであることに注意してください。vivid リポジトリでは、設定ファイルは components フォルダにあり、すべてのコンポーネントは srcフォルダの中にあります。テストファイルを *.test.ts- このパターンに従ったファイルはすべてテスト実行に含まれます。

  2. projects:どのような設定でテストを実行するかを playwright に指示します。この例では、テストしたい主要なブラウザごとに 3 つの設定を用意しています。

コンフィギュレーションには playwright設定にあります。 詳しくはこちら.

これで playwrightどこでファイルを入手し、どのブラウザーでテストを実行するかを「知っている」ので、テストそのもののセットアップを始めることができる。

劇作家テストの書き方

テストを作成するには、次のようなファイルを作成する必要がある。 testMatches.そのため、リポジトリでは、コンポーネントごとにファイル ui.test.tsファイルを作成します。テストファイルの例を示します:

test('should show the component', async ({
  page
}: {
  page: Page
}) => {
  const template = `...`;

  await loadComponents({
    page,
    components,
  });
  await loadTemplate({
    page,
    template,
  });

  const testWrapper = await page.locator('#wrapper');
  await page.locator('#modal');

  await page.waitForLoadState('networkidle');

  await page.evaluate(() => {
    const modal = (document.getElementById('modal') as Dialog);
    modal.showModal();
    return modal;
  });

  expect(await testWrapper?.screenshot()).toMatchSnapshot(
    './snapshots/dialog.png'
  );
});

全ファイルはこちら.

それを分解してみよう。

劇作家テストブロック

すべてのインポートが終わった後、最初に気づくのは testブロックがあることだ。ブロックに送られた関数は testブロックに送られた関数はテストオブジェクトを受け取ります。オブジェクトのプロパティの1つは pageプロパティである。この pageが実際の pageplaywrightでの表現です。テストされたページを操作し、観察するために使用する。

テストページの準備

このファイルでは、テンプレート(変数 template変数)を生成するテストがあります。そして、私たちが構築したユーティリティ関数を使用して、必要なコンポーネントとテンプレートをテストページにロードします(詳細は後述します)。

HTMLの読み込みを待つ

次の行では page.locatorメソッドを使用して、探している要素を取得します。ロケータを使うもうひとつの「副次的効果」は、要素がページ上に表示されているときに解決されることだ。これにより、テストを実行するときに、要素がすでにDOM内にあることを確認できます。

リソースのフェッチ待ち

もうひとつの待ち時間は、すべてのネットワーク・トラフィックがアイドル状態になるまで待つことだ。アイコンや画像がCDNからロードされるまで待ちたいこともあるので、これは私たちにとって重要だ。

トリガーDOM要素 プログラムAPI

最終的には evaluateのメソッドがある。 pageオブジェクトのメソッドがあります。このメソッドは、ページ内のJavaScriptコードを評価します。この場合、これが評価されるコードです:

await page.evaluate(() => {
  const modal = (document.getElementById('modal') as Dialog);
  modal.showModal();
  return modal;
});

この場合 modal要素を取得し、その showModalメソッドを呼び出す。こうすることで、DOM要素のプログラムAPIをテストすることができる。

スナップショットの生成と比較

最後のセリフは、劇作家における視覚的回帰の「マジック」である:

expect(await testWrapper?.screenshot()).toMatchSnapshot(
  './snapshots/dialog.png'
);

解決された locator解決されたオブジェクト testWrapperには screenshotメソッドを持っています。このメソッドを呼び出して toMatchSnapshotを期待すると、次のいずれかを実行する:

  1. に指定されたパスにスナップショットがある場合、取得されたスナップショットとファイルシステムに存在するスナップショットを比較します。 toMatchSnapshotに与えられたパスにスナップショットがある場合、取得したスナップショットとファイルシステムに存在するスナップショットを比較します。一致しない場合、テストは失敗します。

  2. パスにスナップショットがなければ、新しいスナップショットを生成する。

テスト中にテストページをロードするには?

前回のセクションでは、テストの書き方の例を見た。テストページを操作するために pageオブジェクトを使ってテスト・ページを操作しましたが、いくつか疑問があります。HTML/CSS/JSのコンテンツはどこから来るのか?

要するに、劇作家の pageにはメソッドがある。 goto.このメソッドはURLを受け取り、それを page.動作中のアプリケーションがある場合は、これで十分です。この場合、必要なのはアプリケーションを提供することだけです。これは、bundler の開発用サーバーを使ったり、静的な HTML サーバーを使ったり、QA や開発環境、本番環境でテストしたりすることができます。

テストは通常 gotoで始まる:

test('should show text hello world', async ({page}) => {
	await page.goto(myAppUrl);
	const headerText = await manipulateApp(page);
	expect(headerText).toEqual('Hello World');
});

私たちは gotoアプリやテストページを何らかの方法で操作し、何らかの機能が存在する、あるいは同等であることを期待する。

スナップショット比較のために単一コンポーネントをどのように提供するか?

他のUIコンポーネント・ライブラリと同様に vivid他のUIコンポーネント・ライブラリーと同様、テストするための実際のアプリは存在しない。私たちにはドキュメントがありますが、それをテストすることは、私たちのコンポーネントとは関係のないものもテストすることになります。実際、ドキュメントのビジュアル・データのほとんどは、コンポーネントとは関係ありません。

一番いいのは、コンポーネントごとに専用のテストページを作ることだろう。この目的のために、プロジェクト全体を http-server.視覚的なリグレッション・コマンドはこんな感じだ: npm run build & npx http-server -s & npx playwright test.

つまり、コンポーネントをビルドし、リポジトリとそれによるビルドを提供し、テストを開始する。また、空のHTMLファイルを用意し、それを次のように page.gotoコマンドで使用します:

await page.goto('[http://127.0.0.1:8080/scripts/visual-tests/index.html](http://127.0.0.1:8080/scripts/visual-tests/index.html)');

このファイルは空のHTMLファイルです。テストのたびに手動でHTMLファイルを作成することもできますが、それでは何が楽しいのでしょうか?それでは、テスト・ページの生成をコードから自動化する方法を見てみよう。

PlaywrightでJSファイルとスタイルをページに注入するには?

これでHTMLページは goto.コンポーネントのコードを注入できるようにする必要があるので、これはまだ役に立たない。

これについては、テスト・ファイルの例で見たユーティリティ関数を参照してください。 loadComponents.

loadComponentsは、コンポーネントの名前を配列で受け取り、playwrightの addScriptTagメソッドを使ってコンポーネントを追加します:

export async function loadComponents({
    page,
    components,
    styleUrls = defaultStyles,
}: {
    page: Page,
    components: string[],
    styleUrls ? : string[]
}) {
    await page.goto('http://127.0.0.1:8080/scripts/visual-tests/index.html');

    (async function() {
        for (const component of components) {
            await page.addScriptTag({
                url: `http://127.0.0.1:8080/dist/libs/components/${component}/index.js`,
                type: 'module',
            });
        }
    })();

    const styleTags$ = styleUrls.map(url => page.addStyleTag({
        url
    }));
    await Promise.all(styleTags$);
}

マジックは、関数の途中にある非同期のforループの中で起こります。 addScriptTagを使ってデモページにロードします。

また、1つ下の行のスタイル・タグに対しても同様です。この関数は、すべてのスタイル タグが DOM 内にあるときに解決されます。

こうすることで、どのテストでも、同じ空白ページを使い、テストしたいコンポーネントをロードする。たとえば、フォーム要素をテストしたい場合は、配列に text-field, text-area, select, buttonなどのフォーム要素を配列に設定します。といったフォーム要素を配列にセットします。 loadComponents関数は私たちのためにそれらをテストページに注入します。

ネットワーク・トラフィックは、配列をこのように設定した場合、次のようになる: [icon, button, focus, dialog, text-field,layout]:

The JavaScript files loaded in the test page as shown in the network tab of chrome dev toolsA list of JS files from the network tab

テンプレートもロードされていることを念頭に置いてほしい:

The CSS files loaded in the test page as shown in the network tab of chrome dev toolsA list of css files from the network tab

フーピー - JSとCSSがロードされました!

HTMLページにコンポーネントを表示するには?

これで、必要なスクリプトとスタイルが注入されたHTMLページができました。残りの作業は、実際にコンポーネントを使用するHTMLを注入することです。

ボタンをテストしたいとしよう。次のような文字列を作成する:

<vwc-button label=”Click Me”></vwc-button>

意味合いや外観など、ボタンにもっと味付けをすることもできるし、別のコンポーネントを選択することもできる(つまり、ダイアログをテストしたいのであれば、ボタンのスニペットを作成するのはむしろ無駄だろう)。

さて、このHTMLをテスト・ページに表示したい。そのために、playwrightの page.addScriptTag.ユーティリティ関数 loadTemplateで、ページとテンプレート文字列を受け取ります。そして、HTMLテンプレートをページに挿入するスクリプトをページに追加します:

export async function loadTemplate({
    page,
    template
}: {
    page: Page,
    template: string
}) {
    const wrappedTemplate = `<div id="wrapper">${template}</div>`;
    await page.addScriptTag({
        content: `
            document.body.innerHTML = \`${wrappedTemplate}\`;
        `,
    });
}

この関数は、まずテンプレートを div文字列でラップします。そして、bodyのinnerHTMLをラップしたHTML文字列で置き換えるスクリプトをページに追加します。

つまり、次のようなHTML文字列があるとする:

<vwc-dialog id="modal"
		icon="info" 
		headline="Headline"
		text="This is the content that I want to show, and I will show it!!!"
>				
</vwc-dialog>

ブラウザには次のような結果が表示される:

A web browser with a modal dialog open. To the side the chrome dev tools are open showing the HTML we expectedThe test page and the devtools

モーダルを表示します。モーダルの右側に、ページに注入した HTML コードが表示されています。

Playwrightでのスクリーンショット生成と比較

テストページの準備ができたので、スナップショットを撮ることができます。概要のところですでに説明しましたが、ここで思い出してください:

const testWrapper = await page.locator('#wrapper');
await page.locator('#modal');

await page.waitForLoadState('networkidle');

await page.evaluate(() => {
  const modal = (document.getElementById('modal') as Dialog);
  modal.showModal();
  return modal;
});

expect(await testWrapper?.screenshot()).toMatchSnapshot(
  './snapshots/dialog.png'
);

ステップ1:ページが準備されるのを待つ

メソッドを使って、ラッパーとモーダルがページに表示されるのを待ちます。 page.locatorメソッドと networkidleイベントを使用して、すべてのアセットがロードされたことを確認します。

ステップ2:ページの操作

ページの準備ができたら、モーダル要素で showModalAPI を使用するスクリプトを評価します。

ステップ3:スクリーンショットを撮って検証する

さて、すべてが正常に機能していれば、スナップショットを取得し、以前のスナップショットと比較するために expect行でスナップショットを取得し、以前のスナップショットと比較します。

この testWrapper.screenshotメソッドは playwright.locatorAPIの一部です。このメソッドは要素のスクリーンショットを取り、それを使って toMatchSnapshot期待値APIを使うことができる。この場合、ファイルパス(このメソッドでは match path) - ここにスクリーンショットが保存されます。

テストを初めて実行する場合、スクリーンショットが match path.私たちが行ったすべての変更が元の結果と比較され、コンポーネントの見た目を変更してもテストに失敗し、不要なビジュアルの変更が出荷されないことを確認します。

ステップ4:スクリーンショットの更新

この場合、見た目を変えたい、 playwrightには update-snapshotsフラグがあります。このために updateタスクがありますが、playwrightではこのように動作します:

npx playwright test --update-snapshots

概要

これで、UIコンポーネントのビジュアル・リグレッション・システムが完全に動作するようになった。UIコンポーネントのためのこの種のテストの構築は、アプリケーションのテストよりも少し複雑です。

アプリケーションをテストする際に必要なのは、外部サーバー経由でアプリケーションをロードし、それを gotoを使うだけです。コンポーネントをテストする場合、同じサーバーが必要ですが、今回はテスト中に構築する必要のある空のページを指します。

つの効用関数、 loadComponentsloadTemplate劇作家の addScriptTagaddStyleTagは、HTMLスニペットを作成するだけで、各コンポーネントを分離してテストできるようにする秘密のソースだ。

よく似たコードを使って、ライブラリ内のすべてのコンポーネント、あるいはアプリケーション内のすべてのページ/ユーザーストーリーに対してテストを作成します。こうすることで、すべてのコンポーネント/ページ/ユーザーストーリーに対してスナップショットを作成し、ユーザーが同じ手順で同じ結果を得られることを確認します。すべて、コンフィギュレーションで設定したブラウザ(クローム、ファイヤーフォックス、サファリ)で自動的に行われます。品質向上 - 時間節約。

私たちは現在、ローカルの自動化されたビジュアル・リグレッション・テストのメカニズムを持っている。これを作った理由は、これらのテストをローカルで実行できるようにするためです。そうすれば、開発者は自分の変更をプッシュする前に、素早いフィードバックを得てエラーを修正することができます。

でも、もっと改善できることはたくさんある:

  • テスト生成を自動化するには?

  • e2eテストでは、不安定さは既知の問題である。

  • GitHubのアクションにテストを統合し、最適化するには?

  • また、ビジュアル・テストの開発モードとデバッグ・モードについてはどうだろうか?

次回はこれらのトピックについて解説する。それまでは Vivid リポジトリ脚本家の設定そして UI テストドキュメントを参照してください。

シェア:

https://a.storyblok.com/f/270183/400x400/7bf76cb05c/yonatankra.png
Yonatan Kraボネージ・ソフトウェア・アーキテクト

ヨナタンは、C/C++からMatlab、PHP、javascriptまで、アカデミーや業界で素晴らしいプロジェクトに携わってきた。元Webiks社CTO、WalkMe社ソフトウェアアーキテクト。現在はVonageのソフトウェア・アーキテクトであり、eggheadのインストラクターでもある。