カミナシ エンジニアブログ

株式会社カミナシのエンジニアが色々書くブログです

AIはE2Eテスト撲滅の夢を見るか?

AIはE2Eテスト撲滅の夢を見るか?

おはようございます。カミナシでシニアマネージャーを担当している daipresents です。息子がけん玉にハマり、定期的にボールをぶつけられて痛い思いをしております。

僕は普段、「カミナシ 教育」のエンジニアリングマネージャも担当しているのですが、プロダクト開発の中で、プロダクトマネージャと一緒に、開発された機能の受け入れテストも手伝っています。

また、新しい変化を受け入れるとともに、プロダクト全般の挙動も確認する必要があるので、少し前にE2Eを整備しはじめました。

E2Eテストの整備は、仕事柄いろいろやってきたのですが、AIの活用が劇的に広がっているので、今回は、自分はコードを書かず、E2EテストのすべてをAIに作ってもらいました。

AIを使ったE2Eテスト作成

今回作成したのは、Webアプリ向けのE2Eテストです。技術的にはSeleniumからPlaywrightへの技術変換が進み、成熟が進んできた領域です。

作り方はいろいろ方法がありそうですが、自分でコードを書いていた時代の名残をもとに、AIで楽をする方法を試しています。

僕もどこかでいろいろ試そうと思っていますが、もっといい方法があればぜひ教えて下さいー。

今回のE2Eテスト作成の流れは以下です。

  1. シナリオを考える
  2. レコーディングする
  3. リファクタリングする

シナリオを考える

シナリオは一連のユーザ操作の流れです。プロダクトであれば、想定しているいくつかの操作の流れがあるはずなので、それにあわせたシナリオを考えます。

ここで「あれもカバーしたい」、「これもカバーしたい」となると、シナリオ数がどんどん増えてしまうので、最初は「これが動かないとまずいやろ」レベルのシナリオを一本だけ作るのがおすすめです。

例: Google検索であれば、検索ページでキーワードを入力して、検索結果にキーワードが含まれていることを確認してそのページを開く

今回も、管理者としてデータを作り、そのデータをユーザが利用し、利用した状況を管理者が確認する・・・といった流れをE2Eのシナリオとしました。

レコーディングする

実物をお見せできないので、サンプルプロジェクトをプロジェクトを作成します。プロンプトはこんな感じです。

Playwrithg + TypeScriptを使ったE2Eのサンプルを作成し、ブログにコードを掲載したいと考えています。

ベストプラクティスとしてはここを参考にしてください。
https://playwright.dev/docs/best-practices

POMの考え方も設計の参考になるはずです
https://playwright.dev/docs/pom

要素を安定化させるためにAuto-waitingも参考にしてください。とくに、要素の表示を待つ場合は、WaitForメソッドを使うと要素の再取得を行わないので、最初の要素取得に失敗した場合にWaitするだけ時間の無駄になってしまいます。ExpcetとToBeVisibleメソッドを使うなど落ちにくい工夫をお願いします。
https://playwright.dev/docs/actionability

まずはプロジェクト環境を作ってください。

上記の命令の結果、以下のようなファイル構成ができました。

playwright-e2e-sample/
├── .github/
│   └── copilot-instructions.md    # プロジェクト説明とガイド
├── .vscode/
│   └── tasks.json                 # VS Code タスク設定
├── pages/                         # Page Object Model
│   ├── BasePage.ts               # 共通機能のベースクラス
│   ├── HomePage.ts               # ホームページのPOM
│   └── DocsPage.ts               # ドキュメントページのPOM
├── tests/                         # テストファイル
│   ├── home.spec.ts              # ホームページテスト
│   ├── docs.spec.ts              # ドキュメントページテスト
│   └── advanced.spec.ts          # 高度なテスト手法
├── utils/
│   └── TestUtils.ts              # テストユーティリティ
├── playwright.config.ts          # Playwright設定(複数ブラウザ対応)
├── package.json                  # 依存関係とスクリプト
├── tsconfig.json                 # TypeScript設定
├── README.md                     # 詳細なドキュメント
└── .gitignore

サンプルコードなのでUtilとかサンプルテストとか必要ないのですが、ちゃんとPage Object Model(POM)を使った設計になっています。賢い。

// ページを抽象化したクラスを作成してくれた
export abstract class BasePage {
  protected page: Page;

  constructor(page: Page) {
    this.page = page;
  }

  /**
   * Navigate to a specific URL
   */
  async goto(url: string): Promise<void> {
    await this.page.goto(url);
  }

  /**
   * Wait for page to be loaded
   */
  async waitForLoad(): Promise<void> {
    await this.page.waitForLoadState('networkidle');
  }
  
  ・・・

ひとまず土台ができたので、早速コードを作成してみます。

プロンプトで命令しても作成可能ですが、開発中の画面だとログインを突破したり、開発環境にアクセスさせたりいろいろ考えることがあるので、今回はレコーディングという手段を使っています。

PlaywrightのVSCodeプラグインをいれると、上記のようなツールを利用できます。「Record new」をクリックします。

すると、「test-1.spec.ts」というファイルが作成され、ブラウザが起動します。

レコーディングが始まっているので、ブラウザのURLに「google.co.jp」を入力して開くと、コードとしてレコーディングしてくれます。

E2Eテストのコーディングは、画面を開き、ポチポチ操作するだけの簡単なお仕事です。

ただ、これだけだと以下のように、操作がつらつらと記録されるだけのフラットなコード構造になります。

await page.goto('https://www.google.com/?zx=1764579693207&no_sw_cr=1');
await page.getByRole('combobox', { name: '検索' }).click();
await page.getByRole('combobox', { name: '検索' }).fill('カミナシ');
await page.getByRole('button', { name: 'Google 検索' }).first().click();
await expect(page.getByRole('link', { name: '現場DXプラットフォーム「カミナシ」|全国17,000' })).toBeVisible();

これでも動作に問題はありませんが、画面が変わったり機能が増えたりすると、テストに失敗するリスクが上がったり、運用保守が煩雑になったりするため、Page Object Model (通称POM)のような構造化が一般的に必要になってきます。

リファクタリングする

前述の通り、このままだと運用保守が大変になるので、リファクタリングを行っていきます。

一昔前は、「画面(ページ)」を定義し、画面に表示される要素や一部の操作を閉じ込めていきました。しかし、最近ではAIの力をお借りできるので、以下のようなやり方も可能になりました。

# プロンプトの例
VS CodeのPlaywrightプラグインを使って、Playwrightでシナリオを一通り作成しました。言語はTypeScriptを使っています。

しかし、この方法だと操作が平坦に並ぶだけで、画面のデザイン修正や機能追加があったときに変更が難しくなっていきます。

よって、Page Object Model(POM)の考え方を参考に、画面を構造化、オブジェクト化していきたいと思います。

POMについては、以下の資料を参考にしてください。

POMについて: https://playwright.dev/docs/pom

今回は、検索画面と検索結果画面のシナリオなので、この2画面を対象に、POMを作成し、メンテナンスしやすいコードにリファクタリングしてください。

なお、POMは src/pages フォルダに作成してください。
POMは pages フォルダに作成してください。

最初は思い通りにPOMを作ってくれない部分もありますが、命令をカスタマイズしていくと、より精度の高いものが生まれます。今回の場合だと以下のようなテストになりました。

test.describe('Google検索テスト - POMパターン適用', () => {
  test('カミナシを検索して結果を確認', async ({ page }) => {
    // Page Objectインスタンスを作成
    const searchPage = new GoogleSearchPage(page);
    const resultsPage = new GoogleSearchResultsPage(page);

    // Google検索画面にアクセス
    await searchPage.navigate();
    await searchPage.verifyPageTitle();

    // 「カミナシ」で検索実行
    await searchPage.search('カミナシ');

    // 検索結果画面の読み込み完了を待機
    await resultsPage.waitForPageLoad();

    // 検索結果にカミナシのリンクが表示されることを確認
    await resultsPage.verifySearchResultVisible('現場DXプラットフォーム「カミナシ」|全国17,000');

    // 検索結果が存在することを確認
    await resultsPage.verifyResultsExist();

    // ページタイトルに検索キーワードが含まれることを確認
    await resultsPage.verifyPageTitleContainsKeyword('カミナシ');

    // URLに検索パラメータが含まれることを確認
    await resultsPage.verifyURLContainsSearchParams('カミナシ');
  });
  
  ・・・

POMを使ってスッキリした形になりましたね。「操作や確認ごとにstepメソッドをいれたいです」杜明礼すれば、Stepメソッドを使ってログが見やすくなります。

await test.step('Google検索画面にアクセス', async () => {
  await searchPage.navigate();
  await searchPage.verifyPageTitle();
});

await test.step('「カミナシ」で検索実行', async () => {
  await searchPage.search('カミナシ');
});

await test.step('検索結果画面の読み込み完了を待機', async () => {
  await resultsPage.waitForPageLoad();
});

今回の例だと、レコーディングした内容をリファクタしていますが、E2EでアクセスするページのHTMLを自動保存するスクリプトを書くことで、簡易的な自動修復もできそうです。たとえばこんなかんじでしょうか。

  1. E2Eを実行する
  2. 画面変更のためユーザ一覧ページでテストに失敗
  3. テストに失敗したときの画面のHTMLを保存
  4. HTMLを分析してPOMとE2Eテストに反映
  5. 再実行・・・

E2Eの安定化

E2Eが不安定な理由のほとんどが「HTMLの描画タイミング」の問題です。複雑なWeb画面があたりまえになり、描画のタイミングやレスポンスの時差によって、うまく操作できずにテストに失敗します。

Playwrightの場合は、Auto-Waiting という機能があり、「要素が表示されるまで待つ」といった動作を自動で行ってくれます。

レコーディングした状態だとそこまで配慮されていない可能性があるので、以下のような命令でリファクタリングできます。

# プロンプトの例
テストを安定化させたいので以下の情報をもとに、リファクタリングしてください。

1. Auto-Waitingを活用してください。
参考資料は以下です。

Auto-Waitingの参考資料: https://playwright.dev/docs/actionability

ここに登場するメソッドを活用することで、要素の操作や確認が安定化します。

2. Playwright のベストプラクティスを反映してください。
参考資料は以下です。

ベストプラクティスの参考資料: https://playwright.dev/docs/best-practices

これによって、よりテストが強固になるはずです。

Playwrightのベストプラクティスはとてもよくまとまった資料なので、昔翻訳をしたこともありましたが、AIが解釈してくれるのでURLをそのまま渡せてすっごく便利になりました。

ベストプラクティス系の文章は、インターネット上でたくさんあるので、気に入ったものをAIに教える作戦もできそうです。

  1. End-to-end testing Best Practices by GitLab
  2. 7 Essential End-to-End Testing Best Practices by Testim
  3. Best Practices by Cypress

社内のSET経験者の方に、Playwright用ESLintプラグインを教えていただきました。静的解析で品質を高めていく手もありますねー。

E2Eの修正

E2Eの作成まではAIで簡単にできました。ここからは運用を考慮して更新方法を考えていきます。

方法としては、主に以下の2パターンがあるかなと思っています。

  1. 部分的に作り直す
  2. めんどうなのでもう一度AIに最初から作ってもらう

理想は1なのですが、AI時代だと2もありなのかもしれません。

まだベストな手段は見つかっていませんが、今のところ、以下のような運用を行っています。

  1. E2Eテストを失敗させる
  2. エラーメッセージを下に、「一覧画面に登録ボタンができたことで間違ったボタンをクリックしてしまっているので、修正してほしい」のような命令を行う
  3. AIの自動修正後に内容を確認して動作確認を行う

AIはE2Eテスト撲滅の夢を見るか?

2018年に JaSST Tokyoというイベントで、「テストの未来、品質の未来 ~自動化はテスター撲滅の夢を見るか?~」というパネルディスカッションを開いたことがあります。

イベントの参加者が品質にかかわる人たちだったので、このセッションでは、自動化が広がることでどういった未来が待ち受けているのか? 人間の仕事はなくなるのか? といったテーマについて議論しました。

当時の有識者たちの意見は、価値を提供できる人材が生き残り、その人材は継続的な改善・チャレンジに取り組める人だというもの。時が経ち、今振り返ってみても、有識者たちの意見は正しかった気がします。

当時は「アジャイル・DevOps時代がやってきたな」と思っていましたが、あっという間に「AI時代」に塗り替わってしまいました。

今回、AIを活用して、自分でコードを書かずにE2E環境を整備してみて思ったのは・・・

  1. AIによって命令だけでコードが作れるようになった
  2. 情報を与えることで、より質のいいコードを作れるようになった
  3. 命令できるだけのソフトウェア品質知識(ここではE2E作成の基礎スキル)はまだ必要そう

というところでしょうか。

AIがE2Eテストを駆逐するところまではもう少し時間がかかりそうですが、あっというまにそういう時代がやってきそうな予感がします。