E2Eテストの効率的な書き方の検討。

今まで複数のプロジェクトでE2Eテストを作る機会がありました。 最初は、必要な箇所で要素を取得するコードをその都度書いていました。 しかし、これだと複数のファイルに同じ要素を取得するコードが存在するため、その要素に変更があった場合、同じ修正が複数ファイルに必要となります。 そこで、ある要素の取得は1つの関数だけにして、あとはそれらを利用するようにして、要素に修正があっても修正箇所が少なくなる書き方を考えました。 ここではその方法についての説明となります

上記の方法を使うようになった後、Playwrightの公式ページで Page object models というクラスにまとめる方法を見つけました。 こちらの方法でもテストのコード量を減らして効率を上げることができます。

Page object models

公式ドキュメントは以下になります。

関数を使う方法

Next.jsを使ったサンプル

基本は各ページごとに要素取得などの基本関数をオブジェクトにまとめた1つのファイルを作成します。それを使って各ページや複数ページのテストを作成します。 ヘッダー、フッター、テーブルコンポーネントなど共通で使用しているものは別ファイルで定義します。それを利用しているページはそれをインポートして使用します。

1つのファイルは、以下の単位でグループごとにオブジェクトにまとめます。 このグループ分けは1つのサンプルで、各自に合ったグループに分ければ良いと思います。

名前 備考
Locatorr 要素を取得する関数グループ
Input 要素への入力用。ラジオボタンやチェックボックスも入力として扱う
Click リンクやボタンのクリック用。
Action ページへの直接遷移(goto) や複数処理をまとめたもの
Expect 要素単位のテスト
Assert 複数要素のテスト

トップページの基本関数をまとめた例

import { Page, expect } from "@playwright/test";
import AppHeader from "../../components/AppHeader";
import { getClickOption, PageOption } from "../PageOption";

const Locator = {
  title: (page: Page) => page.locator("body > main > h1"),
  itemSchedule: (page: Page) => page.locator("body > main > div > ul > li > a"),
};

const Action = {
  goto: async (page: Page) => await page.goto("/"),
};

const Click = {
  Header: AppHeader.Click,

  itemSchedule: async (page: Page, option?: PageOption) =>
    Locator.itemSchedule(page).click(getClickOption(option)),
};

const Expect = {
  Header: AppHeader.Expect,

  title: async (page: Page) =>
    await expect(Locator.title(page)).toHaveText("Next-Todo"),
  itemSchedule: async (page: Page) =>
    await expect(Locator.itemSchedule(page)).toHaveText("予定"),
};

const Assert = {
  init: async (page: Page) => {
    await AppHeader.Assert.init(page);

    await Expect.title(page);
    await Expect.itemSchedule(page);
  },
};

export default {
  Action,
  Locator,
  Click,
  Expect,
  Assert,
};
AppHeaderは共通ヘッダー用で、これも同じような構成になっています。

これを使ってトップページのE2Eテストを作成します。

import { test } from "@playwright/test";
import TopPage from "../../lib/pages/app/TopPage";
import SchedulePage from "../../lib/pages/app/schedule/SchedulePage";

test.describe("トップ画面テスト", () => {
  test.beforeEach(async ({ page }) => {
    await TopPage.Action.goto(page);
  });

  test("初期表示", async ({ page }) => {
    await TopPage.Assert.init(page);
  });

  test("予定画面への遷移", async ({ page }) => {
    await TopPage.Assert.init(page);
    await TopPage.Click.itemSchedule(page);
    await SchedulePage.Assert.init(page);
  });
});
注 ページによっては入力のバリデーション等など各要素ごとに複数のテストが必要ですが、ここでは省略しています。

ディレクトリ構成

サンプル

上記サンプルのE2Eテストは、以下のようなディレクトリ構成になっています。

lib以下に各ページやコンポーネントの基本関数を定義したファイルを置きます。 これらを使った各ページのテストは、spec以下に置いています。