Zenn
🤹

約3日でE2E環境をPlaywrightで構築して、定常テスト工数を半分以上減らしたこと

2025/03/21に公開
1

要約

  • 定常テストに時間がかかりすぎて不具合も多くなり、リリース頻度も下げざるを得ない!助けて!
  • そうだ、E2Eテスト環境を作って定常テストを自動化させよう!ツールはPlaywrightを使おう!
  • めっちゃ簡単に作れるやーん!
  • 半年続けてみた!課題もあるが、全体的に最高やな!

はじめに

こんにちは!イノベーション開発チームのmiyaken85です!
今回は、以前、弊社が運営するITトレンドの定常テストを自動化させる際、Playwrightを用いてE2Eテスト環境を作った時の話と、約半年間運用してみた結果をお話しできればと思います。

背景

ITトレンド開発チームでは週に3回程、新規で開発したタスクのリリース作業を行います。主な内容は、

  • 新規の開発内容に伴い、影響の出る箇所のテスト
  • サービスの根幹機能に関する箇所を網羅的にテスト

です。
今までそのやり方をまとめたドキュメントがあったので、それを見ながら全て手動でテストを行なっていました。
当然1回のリリースで複数のタスクをリリースします。そのため持ち回りの担当者がかなり時間をかけてテストをしていました。具体的には半日、多くて1日かかっていた時もありました。

その分開発にかけられる工数が減り、人力のため不具合も発生することがあり、その結果を元に更に確認しっかりしないとだね!ってなっていったせいで、
元々週3リリースだったものを週1回のリリースにせざるを得ない状況になってしまっていました泣

これが人間の限界なのか・・

と感じたため、E2Eテストを導入し自動化することにより、人的不具合を減らしつつチームメンバーがより開発に時間を割けるようにしたかったわけです。

E2Eテストとは?

ユーザーの視点からアプリケーションのフロー全体をテストするプロセスです。
E2Eテストでは、ユーザーが実際にアプリケーションを使用するシナリオを模倣し、フロントエンドからバックエンドまでの全ての層を通じて機能が正しく動作することを確認します。

フロントエンドテストにおいて有名な概念図であるテスティングトロフィーの一番上の層であり、信頼性が一番高いテストですが、Flakyテスト(失敗しがちなテスト)になる危険性が最も高く、メンテナンスが大変なテストでもあります。

testing-trophy
以下の記事の図を参照
https://zenn.dev/longbridge/articles/38572a8a9970f4#testing-trophy-とは?

そのため今回は、サービスの根幹に関連するドメインにフォーカスしてユーザーシナリオを作成し、その箇所のE2Eテストを書くことにしました。

Playwrightを採用

E2Eテストを行うために選定したツールが、Microsoftで開発およびメンテナンスが行われている、Node.js ベースの E2E テスト自動化フレームワーク、Playwrightです。
https://playwright.dev/

E2Eテストを行うにあたってハードルになり得る箇所がほぼ全てケアされている、とても優れたテストツールという印象でした!Microsoft管理なので、TypeScriptとの互換性も抜群ですね!
特に採用しようと思えた点以下6つです。

  1. configの設定が豊富で、設定一つで簡単にテストレポートやビデオ、スナップショットが撮れる
  2. getByRoleなどを用いて、アクセシビリティを意識したテストが書ける
  3. 2に関連するのだが、要素を指定ではなくアクセシビリティ属性で書けるため、変更に強いテストが書ける。(これはテスト該当箇所の作りによる)
  4. 並列テストがデフォルトで備わっていて、テスト速度が速い。
  5. 立ち上がった GUI 画面から直接テストコードを確認することが可能

https://playwright.dev/docs/trace-viewer

  1. Test Generatorで、GUIでブラウザ操作するだけで勝手にテストコードを生成してくれる

https://playwright.dev/docs/codegen

導入過程(実装ステップ)

とりま公式コマンドを叩く

公式に記載ある通り、Playwrightをインストールします。

npm init playwright@latest

この際、以下のことを聞かれるので選択

  • TypeScriptとJavaScriptどっち使う? → 今回はTypeScriptを選択
  • テストファイルの名前どうする? → defaultのtestを選択
  • GitHub Actions workflow作っといてあげようか? → yesを選択
  • Playwrightを実行するブラウザもインストールする? → yesを選択

このコマンドによってほぼ雛形が完成し、仮置きでテストファイルも作ってくれます。(めっちゃ簡単やんけ!)
テストファイルを実行するコマンドを叩くと、CLIにテストのpass状況、そしてブラウザにテストレポートが自動で出力されます。

npx playwright test

playwright-result
CLIに結果表示
playeright-testing-report
テストレポートに出力

すごすぎ。。

Volta(Node.JSのバージョン管理ツール)インストール

人によってローカルのNodeバージョン違うだろうし、かといってこれのためにDockerを立ち上げるまでもないなと思ったので、Node.JSのバージョン管理ツールとして非常に便利な、Voltaをインストールしました。
プロジェクトごとに異なるバージョンのNode.jsを自動で切り替えてくれるので、今のところNodeバージョン管理ツールとして一番使いやすいのではないかと思ってます。
Rust製なので速度も鬼速いです!

この記事では詳しい説明は控えますが、興味あれば公式サイトをご覧ください!
https://volta.sh/

軽くESLintの設定

あくまでテストファイルを作成することがメインであるため、ESLintの設定は最小限にしてます。
ESLintのバージョンは2024年時点での最新版であるv9で、flat config形式にしてます。

import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import { includeIgnoreFile } from "@eslint/compat";
import path from "node:path";
import { fileURLToPath } from "node:url";
import playwright from 'eslint-plugin-playwright';
import eslintConfigPrettier from "eslint-config-prettier";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const gitignorePath = path.resolve(__dirname, ".gitignore");

export default [
  includeIgnoreFile(gitignorePath),
  {files: ["**/*.{js,mjs,cjs,ts}"]},
  {languageOptions: { 
    globals: globals.browser,
    parserOptions: {
      project: ["./tsconfig.json"],
    }
  }},
  pluginJs.configs.recommended,
  eslintConfigPrettier,
  ...tseslint.configs.recommended,
  {...playwright.configs['flat/recommended'],}
];

Playwright.config.tsの設定

テストの細かい設定をしておきます。
重要部分のみ抜粋して載せます。

playwright.config.ts
export default defineConfig({
 /* テスト対象ファイルどうするか */
  testDir: './tests',
  /* 全てのテストを並列に実行する */
  fullyParallel: true,
  /* テストが失敗した際、3回までリトライ */
  retries: process.env.CI ? 3 : 3,
  /* 並列テスト時のワーカープロセス数は4 */
  workers: 4,
  /* 成功しても失敗しても、testing-reportを出力する */
  reporter: [['html', { open: 'always' }]],
  use: {
    /* テスト対象のURLドメイン。本番環境と同等のステージング環境でのテストとする */
    baseURL: 'https://stg.it-trend.jp',
    /* 失敗したテストの再試行時にtraceを収集する */
    trace: 'on-first-retry',
    /* 失敗した時のみ、失敗時のスクリーンショットを撮る */
    screenshot: 'only-on-failure',
    /* 常にテストをビデオ録画する */
    video: 'on',
  },

  /* PCページのテストブラウザをchromiumに設定 */
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
  ],
});

テストファイルを作成

Playwrightは、Page Object Modelという、実際のページやコンポーネントごとのclassを作り、そこに要素の定義を集約する方法を推してたりします。
https://playwright.dev/docs/pom

ただITトレンドでは、上述した通り、サービスの根幹に関連するドメインにフォーカスしてテストを書いていくことにしているため、pageごとのテストではなく、ドメインごとに集約してテストを書いてます。
また、一つのドメイン、例えば資料請求処理においても様々なパターンが混在するため、クラスオブジェクトに共通定義を集約すると恐ろしく長く、可読性の悪いオブジェクトが爆誕します。
そのため、各ドメインで共通で使えそうな処理や要素があれば、それを個別で関数化してあげる方法をとっています。

form.test.ts
import { registryOrder } from '@/modules/order/registryOrder';
import { test, expect, Locator } from '@playwright/test';

test.describe('カテゴリーTOP資料請求シナリオ', () => {
  test('カテTOPから3個の製品をカート追加して資料請求できることを確認', async ({
    page,
  }) => {
    await page.goto('/attendance_management_system');

    const addToListButtons: Locator = page.locator('button:has-text("リストに追加する")');

    for (let i = 0; i < 3; i++) {
      await addToListButtons.nth(i).click();
    }

    const requestInfoButtons: Locator = page.locator('text=リストの製品に資料請求する');
    await expect(requestInfoButtons).toBeVisible();
    await requestInfoButtons.first().click();
    const inquiryText: Locator = page.getByRole('heading', { name: '資料請求・お問い合わせ' });
    await expect(inquiryText).toBeVisible();
    <!-- 資料請求の共通処理を関数化 -->
    await registryOrder(page);
  });

  test('カテTOPから一括資料請求できることを確認', async ({ page }) => {
    await page.goto('/e_learning');

    const allAddCartButton: Locator = page
      .locator('span:has-text("一括資料請求する(無料)")')
      .first();

    await allAddCartButton.click();
    const inquiryText: Locator = page.getByRole('heading', { name: '資料請求・お問い合わせ' });
    await expect(inquiryText).toBeVisible();
    await registryOrder(page);
  });
});

テスト実行

上記のテスト以外も何個かシナリオに沿ってテストを書き、実行してみました。
一つだけわざと落ちるようなテストを書いてあります。

レポートが出力され、一つだけ該当テストが落ちてることも確認!
testing-report

ちなみに、VS CodeとCursorとか限定にはなりますが、Playwright Test for VSCodeという、Playwrightテスト専用拡張機能があるのですが、
エディター上でテストが実行できるので、コード書く度にnpmコマンドでテストする必要がなくなり、めちゃくちゃ便利です!
https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright
https://playwright.dev/docs/getting-started-vscode

技術選定〜全てのシナリオのテストを書くまで、約3日間で導入することができました!

約半年導入してみて変わったこと

このE2Eテストを導入して約半年が経ったわけですが、上述した問題はどうなったのかというと、
まだ課題点はあるものの、大分好転したなと感じます。

テスト工数の大幅削減

今まで半日、多くて1日かかっていたテストが、E2E導入後、諸々含めても最低で30分程度で終わる程に大幅削減できました!

リリース頻度をあげることができ、細かい不具合を減らすことにつながった

上述した週一リリースになってしまっていたリリース頻度が、翌営業日が休日以外の毎日になりました。
また、リリース頻度が増えたことで一日当たりのリリースインパクトが小さくなり、細かい不具合は減りました。

結合テスト時に確認する項目を減らせた

そしてこれはプラスアルファでよかったことなのですが、
テスト担当者は。E2E含めて行なったテストをまとめて行うようにしていたものを、プルリクエスト上でテストレポート結果を残すようにしたので、そもそも結合テスト時に確認する項目を減らせました。
これによって更にテストにかける時間を削減でき、より開発に集中できるようになったのかなと思います。

今後の課題

一方でまだまだ課題も残ってます。

ABテストが絡む開発部分はflakyになってしまう。

フォームでABテストを行なっている場合、それによって出てくるDOMが違うこともあり、「Aパターン想定でテストコードを書いてたら、実際にテストでブラウザ開いた時はBパターンがきたんで落ちる」みたいなことがあったりします。
対策として、テストを3回までならリトライするようにしてますが、潜り抜けて落ちることもあるかもなので、そこら辺は割り切るか、どうにかするかしたいところです。

変更が多い箇所はメンテナンス頻度が多くなる

社内管理画面側で編集して公開したものがDOMに反映されるような箇所のテストは、編集が行われる度にテストの方も修正を加える必要があります。変更頻度が多い箇所は中々面倒です。
ここはテスト用のダミーデータを作成して対応するとかなんとかしたいところですね。

まとめ

今回の導入にあたっては、公式ドキュメントの他、こちらの書籍がとても手助けになりました。
https://gihyo.jp/book/2024/978-4-297-14220-9

Playwrightがすごすぎて実装者自身が手を加えたのはほんの一部分でいいし、テスト自体もとても書きやすかったです。
それでいて費用対効果が凄まじかったので、めちゃくちゃ得した気分でした笑
今後も運用していく予定です。

最後まで読んでいただきありがとうございました!

1
株式会社イノベーション Tech Blog

Discussion

ログインするとコメントできます