ちょっと話題の記事

WEBアプリのE2Eテストを自動化 ~ Playwrightの機能紹介とコード例

2024.01.25
E2Eテストを自動化できるPlaywrightを紹介します。
自分はwebアプリの開発でE2Eテスト(シナリオテスト)を書く際にPlaywrightをよく使います。 Playwrightは、ブラウザテストとWebスクレイピングのためのオープンソースの自動化ライブラリで、Microsoftによって開発されました。
テストコードを書いて実行すると、ブラウザ操作が自動で行われE2Eテストをしてくれます。

導入方法

npm init playwright@latest

実行すると

  • @playwright/testがインストール
  • playwrightの設定ファイル(playwright.config.ts)が追加
  • サンプル用のテストコードが追加

が行われます。

テストコード自動生成

playwrightにはブラウザ操作でテストコードを生成する機能があります。 package.jsonのscriptsに以下を追加してnpm run test:genするか、

"test:gen" : "playwright codegen"

npxでも実行できます。引数のURLは任意です。

npx playwright codegen http://localhost:3000/
ブラウザが立ち上がり操作を行うと、別ウィンドウで生成されたコードが表示されます。
テストしたい要素にtest-idを追加するのは手間ですが、test-idを使わずに特定の要素をコードで指定するのは難しかったりします。ブラウザ操作からコード生成すると、その手間がなく便利だと思いました。

拡張機能

VSCodeの拡張機能を導入したスクショです。
コードの行数の隣に三角形を押すると個別にテストを実行できます。コマンドでテスト実行すると、行数を指定しない限り全てのテストが走ってしまうので、この機能は便利です。

VSCodeの左のバーから、三角フラスコのアイコンをクリックするとサイドメニューが表示されます。

  • Show Browser : テスト実行時にブラウザを起動
  • Show trace viewer : あとで紹介するUIモードのようなものが起動
  • Pick locator : 特定の要素を取得するコードをコピー
  • Record new : ブラウザ操作を記録して、新しいテストファイルにテスト追加
  • Record at cursor : ブラウザ操作を記録して、現在カーソルがあるファイルにテスト追加
  • Reveal test output : テストのログ出力を確認

UIモード

UIモードは2023年3月に追加された機能です。

npx playwright test --ui
タイムラインで、アクション前後のスナップショットを見ることができたり、newtworkタブで、テスト中の通信ログを確認できたりします。 どのような状態でテストがこけたのか見れるのでテストの修正がしやすいと思います。

コード例

  • テストファイル内の各テストの前に毎回実行
  • test.beforeEach(async ({ page }) => {
      await page.goto('http://localhost:3000/')
    })
  • 遷移確認
  • test('~へ遷移できること', async ({ page }) => {
      await page.getByTestId('~').click()
      await expect(page).toHaveTitle('title')
    })
    
  • 要素や文言の表示確認
  • test('要素や文言の表示確認', async ({ page }) => {
      await expect(page.getByTestId("menu")).toBeVisible();
      await expect(page.getByTestId("text")).toHaveText(/テスト/);
    })
    
  • クリックによる状態変化の確認
  • test('クリックによりボタンの背景色が変化することを確認', async ({ page }) => {
      const button = await page.getByTestId('button')
      button.click()
      await expect(button).toHaveCSS('background', '#FFFFFF')
    })
    
  • ログイン確認
  • page.waitForResponse()は指定されたレスポンスが来るまで待ちます。

    test("ログインできることを確認", async ({
      page,
    }) => {
      await page.getByTestId("nav-login").click();
      await page.getByTestId("mail").fill("example@example.com");
      await page.getByTestId("password").fill("xxxxxxx");
      await page.getByTestId("login-button").click();
      await page.waitForResponse(
        (resp) => resp.url().includes("api/login") && resp.status() === 200
      );
      await expect(await page.getByText("ユーザー名")).toBeVisible();
    });
    
  • 別タブ表示確認
  • 公式を参考に書いたコードですが、別タブをうまく取得できずタイムアウトとなることが多かったです。
    test('別タブ表示確認', async ({ page }) => {
      const pagePromise = context.waitForEvent('page')
      const newPage = await pagePromise
      await newPage.waitForLoadState()
      await expect(newPage).toHaveTitle('new page')
    })
    
  • 別タブ表示確認の代わり
  • hrefを確認する程度で良いかもしれません。
    test('別タブ表示確認', async ({ page }) => {
      const href = await page
        .getByRole('link', { name: '~の利用規約' })
        .getAttribute('href')
      expect(href).toEqual('https://example.com/')
    })
    
  • 外部リンクの疎通確認
  • test('~の利用規約が閲覧できること', async ({ page }) => {
      const href = await page
        .getByRole('link', { name: '~の利用規約' })
        .getAttribute('href')
      const res = await page.request.get(`${href}`)
      await expect(res.status()).toBe(200)
    })
    
  • 画像のリンク切れ確認
  • test('画像のリンク切れ確認', async ({ page }) => {
      const imgSrc = await page.getByTestId('~').getAttribute('src')
      const imgRes = await page.request.get(`${imgSrc}`)
      await expect(imgRes.status()).toBe(200)
    })
    

    関連記事