概要

本記事ではPlaywrightを使ったE2Eテスト自動化の実践経験を踏まえ、Playwrightにおけるテスト実装のノウハウを紹介します。Playwrightは、WebアプリのE2E(end-to-end)テストを自動化するためのライブラリです。テスト実装手順などは公式ドキュメントを参照すれば理解できましたが、良いテストを書くために参考にできる記事はあまり見つけられませんでした

本記事では実際にWebサイトへE2Eテストを実施して得られた、より良いテストを書くためのノウハウを紹介します。

前提

今回は本サイトFintanがテスト対象となります。FintanはWordPressで作成したサイトですが、テーマを修正したり、WordPressプラグインを導入したりすることで日々改善を行っています。Fintanのサイトの改善業務においては、以下2つのテストを自動化しています。

「記事が問題なく表示できる」観点で基本的な機能を検証するリグレッションテスト:

Fintanの最も重要なことは読者に記事を届けることですので、記事が想定通りに表示できることと、正しく記事の検索ができることをテスト観点としたリグレッションテストをPlaywrightを用いて自動化しました。

機能追加・変更に対する妥当性検証テスト:

いわゆる一般的な機能追加・変更した際のテストです。

今回ここにE2Eテストを導入するにあたり、何をE2Eテストの対象にするべきか基準に悩みました。例えば「HTMLのulタグをolタグに変更する」という修正を行う場合、この修正はE2Eテストで確認すべきなのか。当初、我々は明確な答えを持っていなかったため、実践を通してノウハウを得ることを目指して「迷ったらテスト対象にする」という仮の方針を立ててテスト化を進めました。

ノウハウ1:E2Eテストで自動化すべきテストの選択基準

システムのふるまいを検証するテストは自動化したほうが良い:

「記事の検索と表示」などの基本的な機能が自動テストで常に確認できるようになったことで、サイト改善の変更に伴う動作不良があるかどうかを確認する手間が省けるようになりました。

たとえばFintanのトップ画面には最新記事を表示するエリアがあり、初期表示には8件だけ表示され、「もっと読む」をクリックすると最大32件の記事が表示されるという仕様があります。32件表示しきった後は「短く表示する」とリンクが切り替わり、表示件数が少なくなります(下記イメージ図)。この「もっと読む」リンクの挙動テストを自動化しました。

 

最新記事表示のイメージ

自動テストを適用したのちに他のサイト改善策でコード修正を行った際に、初期表示時で32件のデータ取得されるところが、最大件数(約230件)が取得されてしまうという不具合が入り込んでしまいました。このときGitHub Actionsの自動テストで、関連するテストの失敗が検出され、以下の図に示すように「短く表示する」リンクが表示されていないというエラーメッセージが表示されました。この不具合事象、初期表示時の8件以外の記事はDOM要素に存在しているものの非表示になっているため、人の目では検知ができない事象です。このようなシーンで、他の修正の意図しない影響が自動的に検知され、迅速に対応できたなと感じています。

Error:    TopPageTest.displayBlogsShortly:177 Locator expected to be visible
Call log:
Locator.expect with timeout 5000ms
waiting for selector "text=短く表示する >> nth=0"
  selector resolved to 
  unexpected value "false"

その他でも、Fintanでは基本機能のテストとして以下のようなケースを自動化しています。

ヘッダのカテゴリリンクをマウスホバーし、カテゴリメニューが表示されること。

ヘッダのカテゴリリンク押下でカテゴリ一覧ページに遷移できること。

キーワード検索欄にキーワードを入力し、検索画面が表示されること。

カテゴリ一覧ページに、正しいカテゴリ名、数、順番で表示されること。

修正内容を検証するテストは自動化しないほうが良い:

機能修正を行うテストを自動テスト化する場合、実行結果のふるまいが変わらなければテストが失敗することはありません。リグレッションテストとして、ふるまいが変わらないことの確認にはなるのですが、修正後の内容そのものを検証したい場合は必ずテストも修正する必要が出てきてしまいます。

以下は、「記事内のリダイレクトリンクを修正する」という具体例での説明です。

過去Fintanは大幅なサイト改善が行われ、URLの形式が変わりました。記事内にも古い形式のURLが埋め込まれており、リダイレクトで問題なく挙動するように対応していましたが、SEO対策としてクローラーに正しく情報を伝える必要があるため、修正することにしました(下記表)。

種類  URL 説明  
URL https://fintan.jp/?p=7525 URLにリダイレクトされる。
URL https://fintan.jp/page/248/ 正規のURL。リダイレクトは発生しない。

URLが正規のURLですので、ユーザーにもクローラーにもこちらのほうが親切です。

このURLを修正するにあたって「リンクを押すと期待したページに遷移すること」というテストを、修正するリンクの数だけ作成しました。記事内のURL修正前に一度実行し、URL修正後もう一度実行することで、修正前後の遷移先URLが一致していることをテストしようとしたのですが、実際に自動化したところ、以下のような問題を感じました。

現在のURLを誤ったURLに変更しない限り、リグレッションテストとしてほとんど失敗することはありません。一方、著者が記事内のURLを更新または削除するたびにテストは失敗します。都度修正が必要となり、テストが開発負担になってしまいます。

修正内容の検証テストを自動化したことで、テストの数が増えてしまいまいましたが、リグレッションテストの質は上がりませんでした。テスト全体の実行時間も伸びてしまったため、このように「機能修正を行う際ふるまいが変わらない修正に対するテスト」は実行対象外とするようにしました。

他にも、同様に実施を見送ったテストの例を示します。

不適切な見出しの解除(適切ではないh2,h3タグをdivタグに修正する)で該当タグ要素がdiv要素であることの確認。

トップ画面の画像フォーマットがWebPであることの確認。

timeタグにある記事の更新日にdatetime属性が設定されていることの確認。

ノウハウ2:E2Eテストにおける環境差異の吸収方法

環境によって実行するテストを分けるのが一つの策:

複数環境でそれぞれテストを行うシーンにおいて、どうするべきか悩みました。多くの場合にテスト環境にはデータ差異が生まれます。そこに対して、同じテストを複数環境に対して実行するべきか、環境ごとにテストをわけるべきかも検討しました。

結果、環境ごとにテストを分けて実行しています。

データ差異を埋めることができないという前提において、それぞれの環境ごとにテストを用意することで必要な確認を行うことができますが、環境が増えるたびに同じ観点のテストが増えていくというネックもあります。

そのため、我々はPage Objectを使用してテストロジックを一か所に集約し、アサーションに使う期待値データは環境ごとに分けるという対応で効率化を図りました。例えば、「カテゴリ一覧が正確な内容で表示されること」を検証するサンプルコードは以下のように実装可能です(参考としてご覧ください。プログラミング言語はJavaを使用。以下同様)。本番環境でテストする際は、アサーションに用いる期待値データのみを変更することで対応可能です。

private static final String[] categoryNamesTestEnv = new String[]{
    "その他", "アジャイル・スクラム", "エンジニア育成・学習",
    "セキュリティ・暗号化", "XR", "ソフトウェアテスティング", "ブロックチェーン",
    "モバイルアプリケーション開発", "環境構築・ログ・CI/ CD", "量子コンピュータ",
    "開発プロセス", "要件定義", "Lerna",
    "Nablarch", "UX/UIデザイン", "Webアプリケーション開発",
    "先進技術研究", "新規事業開発", "活動発信・イベントレポート"
};

private static final String[] categoryNamesProductionEnv = new String[]{
    // 本番環境のカテゴリデータは表示順、カテゴリ数だけが違うため、詳細は省略
};

@Test
@DisplayName("カテゴリ一覧に正しい内容のカテゴリ一覧が表示されること(テスト環境)。")
@EnabledOnEnvironment(production = false) // 本番環境かテスト環境のどちらで実行するかを設定するアノテーションを自作 
void categoryListIsCorrectTestEnv(Page page) {
    CategoryListPage categoryListPage = new CategoryListPage(page);
    // カテゴリ一覧ページを表示する処理とカテゴリリンク要素を取得する処理のロジックが同じため
    // それぞれをnavigate()とgetCategoryNameLinks()に集約している
    categoryListPage.navigate();
    Locator categoryNameLinks = categoryListPage.getCategoryNameLinks();
    assertThat(categoryNameLinks).hasCount(categoryNamesTestEnv.length);
    assertThat(categoryNameLinks).containsText(categoryNamesTestEnv);
}

@Test
@DisplayName("カテゴリ一覧に正しい内容のカテゴリ一覧が表示されること(本番環境)。")
@EnabledOnEnvironment(production = true) // 本番環境のみで実行する
void categoryListIsCorrectProductionEnv(Page page) {
    // 本番環境の場合、カテゴリのデータ以外は全部同じため、詳細は省略
    // アサーションにcategoryNamesTestEnvではなくcategoryNamesProductionEnvの本番環境のデータを使えば良い
}

ノウハウ3:アサーションを行う際の考慮点

「人間の手で操作したこと、目で確認したこと」をできるだけアサーションで再現したほうが良い:

手動テストと同じ操作の流れをテストスクリプトに反映させ、アサーションを正確に再現することで、テストの品質を向上できると感じました。

例えば、前述したFintanトップページの最新記事の表示仕様(初期表示8件・最大表示32件)をテストする場合、以下のような手順が必要です。

1.画面を表示する
↓
2.最新記事が8件表示されていることを確認する
↓
3.もっと読むリンクをクリックし最新記事が8件追加表示されることを確認する
↓
4.もっと読むリンクをクリックするごとに、最新記事が8件ずつ追加表示され、最大で合計32件が表示されることを確認する
↓
5.もっと読むリンクがなくなり、短く表示するリンクが表示されていることを確認する

「最大で32件まで表示されることの確認だから、手順1から4までで十分では?」と思いがちですが手順5も必要です。手順5が欠けていると、最大記事数が32件以上例えば33件となった不具合が発生した場合に、テストが合格してしまいます。これは、33件の場合でも、手順1から4が実施された後にDOM要素に1件非表示で、画面上には32件しか表示されないためです。そのため、手順5のアサーションも実装したほうがテストの正確さが上がります。このように、「人間が操作し、確認したこと」を可能な限り正確に再現したアサーションによって、テストの品質向上につながります。

こういったテストスクリプトを作成するとき、Playwrightでは、手動で操作した内容をコード生成する機能があります。この機能は非常に便利なのですが、アサーションは後で適切に追加する必要があります。手順5の内容を反映したアサーションコードはツールでは生成されないので、後で追加しました。参考まで、この例のテストコードを以下に記載します。

@Test
@DisplayName("トップ画面の最新記事が初期に8件表示で、もっと読むリンククリックで毎回8件、最大32件表示すること")
public void checkLatestArticlesDisplay() {
    Locator readMoreButton = page.locator("text=もっと読む").first();
    // 該当のDOM要素を取得する
    Locator cards = getLatestArticleCards();

    // 初期表示に記事が8件表示される
    assertThat(cards).hasCount(8);

    // 「もっと読む」リンクをクリックするごとに、最新記事が8件ずつ追加表示される
    readMoreButton.click();
    assertThat(cards).hasCount(16);
    readMoreButton.click();
    assertThat(cards).hasCount(24);

    // 「もっと読む」クリックで最大32件までに増える
    readMoreButton.click();
    assertThat(cards).hasCount(32);

    // 最大記事数が32件以上となった不具合の場合に以下のアサーションがないと、このケースが合格してしまう
    // Locator displayShortlyButton = page.locator("text=短く表示する").first();
    // assertThat(readMoreButton).not().isVisible();
    // assertThat(displayShortlyButton).isVisible();
}

ノウハウ4:テストコードの可読性向上のポイント

セレクタを用いて、分かりやすい表現にしたほうが良い:

Playwrightでは、要素を特定するためにセレクタを使うことができます。セレクタには、CSSセレクタ、XPath、テキスト、属性値などを使用できます。これらのセレクタを使用して要素を選択し、Playwrightのメソッドを使って、クリックやテキスト入力などのアクションを実行することができます。適切なCSSセレクタを使用することで、コードをより理解しやすくなり、可読性が向上することが期待できます。

例えば、「記事のパンくずリスト内のTopというリンクをクリックしてトップ画面に戻れる」というテストを実行する場合、以下の2つの書き方が考えられます。Playwrightで自動生成されたコードでもテストは通りますが、可読性も考慮すると後者のほうが「何をクリックしようとしているか」が分かりやすくなります。このように、セレクタをわかりやすく書くことで、コードの可読性が向上します。

// Playwrightで自動生成されたコード
// 「top」という文字列しか情報がないため、画面内のどの要素のことか想像しにくい
// ハイパーリンクなのかボタンなのかもわからない
page.locator("text=top").click(); 

// わかりやすいセレクタに書き直したコード
// 画面のどこをクリックしようとしているかを分かりやすく表現した
// (セレクタの記述から、パンくずリスト内にあるトップページへのリンクだと想像できる)
page.locator(".post-breadcrumbs .top a").click();

最後に

このようにPlaywrightを使用してE2E自動テストを実施してみました。

あくまでに個人的な感想ですが、以下のメリットを感じました。

  • Chrome, Firefox, Safariなど主要なブラウザでテストできるため、手動テストと比較して非常に効率的でした。
  • 充実した公式ドキュメントにより、実装が予想以上に簡単に進められました。また、複雑なテスト手順も短いテストコードで完結できます。
  • テストコードの自動生成機能が優れており、参考にすることでテスト実装を迅速に行うことができました。
  • 対応しているプログラミング言語が多い印象を受けました。(ただし、他のツールについては調査していないため、比較はできません)。

これらの情報が、今後E2Eテストを始める方々の参考になれば幸いです。