🕌

Playwright を使用した E2E テスト (feat. Cypress)

2023/12/25に公開

はじめに

こんにちは、クラウドエース フロントエンドディビジョン所属の金です。
本記事では End to End テスト (E2E テスト) のツールの一つである Playwright について基本的な機能を中心にご紹介します。

本記事の対象者

  • E2E テストの初心者
  • Playwright を軽く試してみたい人

End to End (E2E) テストとは

Playwright のご紹介を始める前に、E2E テストについて簡単にご説明します。
E2E テストは、アプリケーションが意図通りに動作するかどうかをテストするためのソフトウェアテスト技術です。

ご参考: E2E (エンドツーエンド) テストとは? | CircleCI

フロントエンドの E2E テストとは

フロントエンドの E2E テストは、主にブラウザ上での動作を検証します。
サービスによって異なりますが、一般的には以下の項目がテストされます。

  • ユーザーがアプリケーションを使用するときに、アプリケーション上でテキストが正しく表示されるかどうか
  • ユーザーがボタンをクリックしたときに、アプリケーションが期待通りの動作をするかどうか
  • ユーザーが入力フィールドに値を入力したときに、期待通りに機能するかどうか
  • クロスブラウザーテスト

Playwright とは

今回ご紹介する Playwright は、Microsoft で開発およびメンテナンスが行われている、Node.js ベースの E2E テスト自動化フレームワークです。
同様のツールには、Cypress や Selenium などがあります。

Playwright 公式ドキュメントの原文より:

Playwright framework is an open-source, Nodejs-based automation framework for end-to-end testing. It was developed and maintained by Microsoft. Its main goal is to run across the major browser engines – Chromium, Webkit, and Firefox.

ご参考:

Playwright の特徴

Playwright の特長は以下の通りです。

  • Chromium や WebKit、Firefox など、最新のレンダリングエンジンをサポート
    ※ レガシー版の Microsoft Edge と IE11 はサポート対象外
  • モバイル用ブラウザのテストをサポート
    • Android 向けの Google Chrome や iOS 用の Safari のテストも可能
  • npx playwright test --ui から立ち上がった GUI 画面から直接テストコードを確認することが可能
  • テストのスクリーンショットが残るため、対象となるテスト画面の前後の状態を簡単に確認することが可能
  • テストの結果を自動的に整理することが可能
  • GitHub Actions や Google Cloud Build など、数多くの CI ツールをサポート
  • Visual Studio Code(以下、VS Code)用の公式の拡張機能が提供されており、エディタ上でテストを実行することが可能

他にも、テストタスクを実行する前に、要素が実行可能になるまでは、自動的にテストを待機させることができます。
例えば、Playwright を使用して任意のアプリケーションでテストを行う場合、テスト対象のボタンをクリックすると Playwright はセレクタが DOM に表示されるまで待機したり、要素が可視になるまで待機します。
アニメーションが停止するまで待つことも可能で、他のテストツールと異なり、要素が実行可能になるまで待機するためのコードを書く必要はありません。

UI テスト実行例

UI テスト画面

VS Code でテストコード確認ができます。

Playwright の使い方

続いて、簡単な React アプリケーションを作成して、Playwright で E2E テストを実施する方法をご紹介します。

インストール方法の詳細については、以下の公式ドキュメントをご参考ください。

ご参考: Installation | Playwright

インストールは、以下のように作業ディレクトリ配下で実施します。

$ # npm を使う場合
$ npm init playwright@latest

$ # yarn を使う場合
$ yarn create playwright

$ # pnpm を使う場合
$ pnpm create playwright

インストール後、以下のようなファイルが作成されます。

playwright.config.ts
tests/
 example.spec.ts

テスト実行コマンド

$ # ディレクトリ配下の全てにテストを実行するコマンド
$ npx playwright test

$ # UI modeでテストを実行するコマンド
$ npx playwright test --ui

Playwright のテストコードの書き方 (基本)

このセクションでは、よく使われている基本的なメソッドについてご紹介します。
詳細なテストコードの書き方については、以下の公式ドキュメントをご参考にしてください。
ご参考: Locators | Playwright

ほとんどのテストは、ページへの URL 移動から始まります。
その後、以下のようなメソッドを使用してページやフレームでアクションを実行する要素ロケータが返されます。

1. page.locator

// css= or xpath= prefix のように prefix で要素指定
await page.locator("css=button").click(); // css の場合
await page.locator("xpath=//button"); // xpath の場合
await page.locator(".className").click(); // class の場合
await page.locator("#id").click(); // id の場合
await page
  .locator(
    "#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input"
  )
  .click();

await page
  .locator('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input')
  .click();

2. page.getByTestId

// dataset で要素を選択
<button data-testid="login-button">login</button>;

await page.getByTestId("login-button").click();

3. page.getByRole

<h3>Sign up</h3>
<label>
  <input type="checkbox" /> Subscribe
</label>
<br/>
<button>Submit</button>

// 以下のようにimplicit roleを指定して使用
await expect(page.getByRole('heading', { name: 'Sign up' })).toBeVisible();
await page.getByRole('checkbox', { name: 'Subscribe' }).check();
await page.getByRole('button', { name: /submit/i }).click();

4. page.getByText

// 色んな要素がテキストを持ってる場合 locator.filter で必要な要素のみフィルタリング可能
<div>Hello<span>world</span></div>
<div>Hello</div>

// Matches <span>
page.getByText('world')
// Matches first <div>
page.getByText('Hello world')
// Matches second <div>
page.getByText('Hello', { exact: true })
// Matches both <div>s
page.getByText(/Hello/)
// Matches second <div>
page.getByText(/^hello$/i)

5. locator.filter

// 以下のように row の中で必要な要素だけフィルタリングも可能
const rowLocator = page.locator("tr");
// ...
await rowLocator
  .filter({ hasText: "text in column 1" })
  .filter({ has: page.getByRole("button", { name: "column 2 button" }) })
  .screenshot();

Playwright でテストコードを作成 (実践)

React で簡易的なブログアプリを用意し、以下のようなシナリオで実際のテストコードを作成します。

  • シナリオ:
    • ブログアプリの Home ページでは投稿されてる記事や作成者を確認できる
    • 記事をクリックすると詳細画面に遷移できる
    • ブラウザの戻るボタンをクリックすると Home ページに戻ることができる
    • 画面右上部のリンクをクリックすると該当のページに遷移できる

上記のシナリオに応じたテストコードは以下のようになります。

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

test("Home ページレンダリンテスト", async ({ page }) => {
  // Home ページにアクセス
  await page.goto("http://localhost:3000");

  // 'All'タブがアクティブになっていることを確認
  await expect(page.locator(".post__navigation--active")).toHaveText("All");

  // 'All'タブをクリック該当ページに遷移
  await page.locator(".post__navigation--active").click();

  // 各ページに遷移したことを確認
  const postLinks = await page.locator(".post__box a").all();

  // 各 post のリンクをクリックして、post ページに遷移
  for (const link of postLinks) {
    await link.click();

    // post ページに遷移したことを確認
    await expect(page.locator(".post__title")).toHaveText(
      "Lorem ipsum dolor sit amet"
    );

    // ブラウザの戻るボタンをクリックして、Home ページに戻る
    await page.goBack();
  }

  // Footer のリンクをクリックして、該当ページに遷移
  await page.locator('footer a:has-text ("write")').click();
  await expect(page).toHaveURL("http://localhost:3000/posts/new");

  await page.goBack();
  await page.locator('footer a:has-text("posts")').click();
  await expect(page).toHaveURL("http://localhost:3000/posts");

  await page.goBack();
  await page.locator('footer a:has-text("profile")').click();
  await expect(page).toHaveURL("http://localhost:3000/profile");
});

test("PostDetail ページレンダリン", async ({ page }) => {
  // PostDetail ページにアクセス
  await page.goto("http://localhost:3000/posts/0");

  // Expect post__detail クラス名を持つ要素が存在することを確認
  const postDetailElement = await page.locator(".post__detail").first();
  expect(await postDetailElement.isVisible()).toBe(true);

  // post__title クラス名を持つ要素を取得
  const postTitleElement = await page.locator(".post__title").first();

  // post__author-name クラス名を持つ要素が存在することを確認
  expect(await postTitleElement.isVisible()).toBe(true);

  // post__author-name クラス名を持つ要素を取得
  const authorNameElement = await page.locator(".post__author-name").first();

  // post__author-name クラス名を持つ要素が存在することを確認
  expect(await authorNameElement.isVisible()).toBe(true);
});

test("Post タイトルと作成者確認", async ({ page }) => {
  // PostDetail ページにアクセス add
  await page.goto("http://localhost:3000/posts/0");
  // posto__title クラス名を持つ要素のテキストを取得
  const postTitle = await page.textContent(".post__title");
  // post__author-name クラス名を持つ要素のテキストを取得
  const authorName = await page.textContent(".post__author-name");

  // post__title クラス名を持つ要素のテキストが正しいことを確認
  expect(postTitle).toContain("Lorem ipsum dolor sit amet");
  expect(authorName).toContain("kim");
});

Playwright と GitHub Actions でテストを自動化

公式ドキュメントに CI テスト自動化について詳しく記載してあるので以下をご参考ください。
ご参考: CI GitHub Actions | Playwright

設定ファイルは、以下のように YAML 形式で記述します。

name: Playwright Tests  #タイトル名を作成
on:
  # 指定したブランチで PR & PUSH イベントが発生したら 自動で実行
  push:
    branches: [ main, master ]
  pull_request:
    branches: [ main, master ]
jobs:
  test:
    timeout-minutes: 60
    # 実行環境設定
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - uses: actions/setup-node@v3
      with:
        node-version: 18

    # npm package インストール
    - name: Install dependencies
      run: npm ci

      # 以下のように env ファイルを指定できる(下の process.env.CI の例を参考)
      env:
        CI: true

    # Playwright E2E test で使う Browser を設定
    - name: Install Playwright Browsers
      run: npx playwright install --with-deps

    # Playwright E2E test 実行
    - name: Run Playwright tests
      run: npx playwright test
    - uses: actions/upload-artifact@v3
      if: always()
      with:
        name: playwright-report
        path: playwright-report/
        retention-days: 30

playwright.config.ts ファイルに以下のように設定することでテスト実行の分岐処理も可能です。
process.env.CI の例

 /* Run your local dev server before starting the tests */
  webServer: process.env.CI
    ? [
        { // web server
          command: 'npm run dev',
          port: 3000,
        },
        { // proxy server
          command: 'cd proxy-server && npm run dev',
          port: 8080,
        },
      ]
    : undefined,

Playwright 用の VS Code 拡張機能

Microsoft が提供している VS Code 用の拡張機能を追加すると、VS Code 上から直接 Playwright のテストを実行できるようになります。

以下のように Record at cursor をクリックすると、実際のブラウザでの操作がリアルタイムでテストコードに反映されます。

Playwright と Cypress の比較

最後に、Playwright と Cypress と比較してみました。

基準 (Criteria) Platwright Cypress
対応言語 JavaScript, Java, Python, and .NET C# JavaScript
対応 Test Runner Frameworks Mocha, Jest, Jasmine Mocha
対応 OS Windows, Linux, and macOS Windows, Linux, and macOS 10.9 ~
Open Source Open Source and Free Open Source and Free
Architecture Headless Browser with event-driven architecture Executes test cases directly inside the browser
対応 Browsers Chromium, Firefox, and WebKit Chrome, Firefox, and Edge
iFrame 対応 あり 制限 (chromeWebSecurity =false を指定)、出来ない case もあり
並列処理 (Parallel run) あり 課金 (Paid dashboard)か free plugin を使用
Mobile testing 対応 Only view for Android Only view for website
CI/CD あり あり
Automatic waiting あり あり
Screenshots / Video 対応 Screenshots / Video あり Screenshots のみ

まとめ

本記事では、E2E テストツールの一つである Playwright の使い方や Cypress との違いについて簡単にご紹介しました。Playwright か Cypress の導入をご検討中の方はの導入をご検討中の方は、以下のまとめをご参考にしていただければと思います。

  • Playwright:
    • DevTools Protocols を使用して WebKit と Chromium をテストする必要がある場合は、Playwright が最適。
    • スクリーンレコーディング、ビデオキャプチャ、tracer viewer などの機能が必要な場合にも Playwright が最適。(Cypress は提供していない)
    • テストスピードに関しては Playwright の方が速い。
  • Cypress:
    • インストールや使いやすさを求める初心者には最適。
    • ネットワークキャプチャやビデオ録画などの完全なテスト証拠は提供されていないが、スクリーンショットだけが必要であれば良いツール。

Discussion