開発ブログ

株式会社Nextatのスタッフがお送りする技術コラムメインのブログ。

電話でのお問合わせ 075-744-6842 ([月]-[金] 10:00〜17:00)

  1. top >
  2. 開発ブログ >
  3. Next.js >
  4. Playwrightを試してみた
Playwrightを試してみた

Playwrightを試してみた

皆様、E2Eテストを導入していますか?

今Microsoft製E2Eツール「Playwright」がアツいので、検証してみました。

公式サイト:https://playwright.dev/

環境とソフトウェアのバージョン

  • OS: macOS Monterey 12.4
  • Next.js: 12.1.6
  • Node: 16.13.1
  • React: 18.1.0
  • Playwright: 1.24.1
  • TypeScript: 4.6.2

Playwrightのインストール

自動インストール(initコマンドを使用)、手動インストールの2通りの方法があります。

自動インストール(initコマンドの使用)

# npm
npm init playwright@latest
# yarn
yarn create playwright

構成ファイルが作成されます。テストディレクトリを指定し、GitHub Actions workflowを追加するかを選択します。テストサンプルとして、指定したテストディレクトリ配下に example.spec.ts が追加されます。

ブラウザはChromium、Firefox、Webkitが自動でインストールされます。

手動インストール

選択したブラウザのみをインストールしたい場合は手動インストールがおすすめです。

# npm
npm install -D @playwright/test
# yarn
yarn add -D @playwright/test

次に、Playwrightでサポートされているブラウザをインストールします。

デフォルトでは、Chromium、Firefox、Webkitがインストールされます。

# npx
npx playwright install
# yarn
yarn playwright install

例えば、chromiumのみをインストールしたい場合は以下のコマンドを実行することでchromiumのみをインストールすることができます。

npx playwright install chromium

playwright.config.ts ファイルの作成

configファイルを作成します。

playwright.config.ts

import type { PlaywrightTestConfig } from "@playwright/test";
import { devices } from "@playwright/test";

const config: PlaywrightTestConfig = {
  testDir: "./tests",
  use: {
    baseURL: process.env.BASE_URL || "http://localhost:3000",
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
  ],
};

export default config;

※様々な設定項目があるため、詳しくは公式ドキュメントを参照

package.jsonの設定

テスト実行用のscriptを追加します。

package.json

{
  "name": "playwright",
  "version": "0.1.0",
        .
        .
        .
  "scripts": {
    "dev": "next dev",
        .
        .
        .
    "test:e2e": "playwright test",  // 追加

    // playwright.config.tsを別のディレクトリに作成した場合は以下でファイルの指定が可能
    "test:e2e": "playwright test -c ../playwright/playwright.config.ts"
  }
}

テスト検証用のページを作成

今回はTopページからボタンをクリックするとAboutページへ遷移することをテストします。

ソースコード

/src/pages/index.tsx

import { useRouter } from "next/router";

export default function Login() {
  const router = useRouter();

  return (
    <div>
      <p>Topページ</p>
      <button
        onClick={async () => {
          await router.push("/about");
        }}
     data-testid="about-button"
      >
        Aboutページへ移動
      </button>
    </div>
  );
}

画面イメージ
スクリーンショット 2022-08-19 16.45.41.png

/src/pages/about.tsx

export default function About() {
  return (
    <div>
      <p>Aboutページ</p>
    </div>
  );
}

画面イメージ

スクリーンショット 2022-08-19 16.45.54.png

data-testidについての補足

上記で作成したindex.tsxファイルでdata-testidという属性を定義しました。

テストの実行は、idclassを指定して要素を取得することも可能ですが、特殊なclassを設定する等、テストのためだけにソースコードを改修すると、例えばページ遷移のテストがログインボタンの色を変える度に落ちるという問題が発生することが考えられます。

このようにテスト対象のロジックとは無関係なコードにテストは依存すべきではありません。

そこで、テストのためだけのカスタムデータ属性のdata-testidを使用することによってソースコードとテストを分離します。

その結果、テストを意識することはなくソースコードの改修が可能になりますし、テストも要素の取得元がdata-testid属性と明確なため、書きやすくなります。

テストファイルの作成と実装

Topページのテストファイルを作成します。

/tests/e2e/index.spec.ts

import { test, expect } from "@playwright/test";

test.describe("Top画面", () => {
  test.beforeEach(async ({ page }) => {
    await page.goto("./");
  });

  test("Aboutボタンが存在すること", async ({ page }) => {
    const aboutButton = page.locator("data-testid=about-button"); // ※
    expect(aboutButton).toHaveCount(1);
    await expect(aboutButton).toHaveText("Aboutページへ移動");
  });

  test("Aboutボタンクリックで、Aboutページに遷移すること", async ({ page }) => {
    const aboutButton = page.locator("data-testid=about-button");
    await aboutButton.click();

    await expect(page).toHaveURL("./about");
  });
});

data-testidを指定してAboutボタンの要素を取得

テストの実行

テストを実行します。

# npm
npm run test:e2e
# yarn
yarn run test:e2e

スクリーンショット 2022-08-19 15.37.01.png
全てのブラウザで成功したことが確認できました。

ページ遷移以外のテストの紹介

example1

ログインページでバリデーションに失敗した時にエラーメッセージが表示されることをテストする

ソースコード(※Props等省略)

<div>
  <input
    id="email"
    type="email"
    value={value}
    onChange={onChange}
    data-testid="email"
  />
  {hasError && (
    <ul data-testid="email-errors">
      {errors?.map((message, i) => (
        <li key={i}>
          {message}
        </li>
      ))}
    </ul>
  )}
</div>
<div>
  <input
    id="password"
    type="password"
    value={value}
    onChange={onChange}
    data-testid="password"
  />
  {hasError && (
    <ul data-testid="password-errors">
      {errors?.map((message, i) => (
        <li key={i}>
          {message}
        </li>
      ))}
    </ul>
  )}
</div>
<div>
  <button
    onClick={onClickLogin}
   data-testid="login-button"
  >
    ログイン
  </button>
</div>

テストコード

test("バリデーション不合格でログインボタンをクリックした場合、エラーメッセージが表示されること", async ({
  page,
}) => {
  const email = page.locator("data-testid=email");  // フォームの要素を取得
  const password = page.locator("data-testid=password");

  await email.fill("");  // フォームを空にする
  await password.fill("");

  const loginButton = page.locator("data-testid=login-button");
  await loginButton.click();

  await expect(loginButton).toBeDisabled();
  await expect(page.locator("[data-testid=email-errors] li")).toHaveText(
    "必須入力項目です",
  );
  await expect(page.locator("[data-testid=password-errors] li")).toHaveText(
    "必須入力項目です",
  );
});

example2

CSSクラスを持っていることをテストする

ソースコード

<div class="primary wFull" data-testid="hello-world">Hello World</div>

テストコード

test("Aboutページとテキストが表示されていること", async ({ page }) => {
  const helloWorld = page.locator("data-testid=hello-world"); 
  await expect(helloWorld).toHaveClass(/primary/);  // 緩和された正規表現で部分一致
  await expect(helloWorld).toHaveClass("primary wFull");  // 完全一致
});

※アサーションは様々なものがあります。詳しくは公式ドキュメントを参照

感想

フロント側のテストといえば今まで手動で行うことも多かったのですが、自動化できると便利ですね。

前回、弊社のtakagi先生がPlaywrightでのコンポーネントテストの記事を書いていて、とても勉強になったので、そちらもぜひ読んでみてください。

PlaywrightでのNext.jsのコンポーネントテストでパスエイリアスを解決する|Next.js|開発ブログ|株式会社Nextat(ネクスタット)

ありがとうございました。

TOPに戻る