Masayan tech blog.

  1. ブログ記事一覧>
  2. Playwrightを実業務で使用する際に必要な最低限の知識・Tips

Playwrightを実業務で使用する際に必要な最低限の知識・Tips

公開日

環境

playwright 1.40.1

Playwrightとは

  • WEB アプリケーションのテストを自動化するオープンソースのテストツール
    • ページ遷移、ボタン押下などの操作を自動化し、その操作の結果として期待する内容になっているかをアサートできる
  • E2E、APIテストなどが可能
  • Microsoft製(GoogleのPuppeteerを開発していたメンバーがMicrosoftで開発したもの)
  • クロスブラウザ対応(Chromium(Google Chrome/Microsoft Edge)/WebKit(Safari)/ Firefox)
  • モバイル用ブラウザもサポート(Android 向けの Google Chrome や iOS 用の Safari のテストも可能)
  • Test generator(画面操作からテストコードを生成できる)
  • 2020年に登場した比較的新しいツールであるにも関わらず、すでにCypressよりもGitHubのスター数が多い
  • VSCodeの拡張機能を使用して、効率的にテストケースの作成、実行、デバッグ可能
  • Locator
    • Playwright上で、getByRoleなどページ上のある要素を特定する方法を抽象化したもの(API)
    • locatorは単一要素である場合と複数要素である場合がある。(classセレクタなどで取得し、一致するclassの要素が複数あるなど。複数要素の場合は配列のように扱えるので、ループすることが可能)
    • cypressでは要素を取得すると、Cypress.Chainableが取得できる
import { test, expect } from "@playwright/test";

test('locator example', async ({ page }) => {
  // ページを開く
  await page.goto('https://example.com');

  // locatorを作成
  const locator = page.locator('#my-element');

  // 要素が存在することを確認
  await expect(locator).toBeVisible();

  // 要素のテキストを取得
  const text = await locator.textContent();
  console.log(text);

  // 要素に値を入力
  await locator.fill('Hello, world!');

  // 要素をクリック
  await locator.click();
});

Playwrightのインストール

npm

$ npm init playwright@latest

yarn

$ yarn create playwright

pnpm

$ pnpm create playwright
  • インストール後、node_modules フォルダの他に tests、tests-examples フォルダが作成され、サンプルのテストコードが保存される。
  • プロジェクト直下のplaywright.config.ts ファイルは Playwright の設定ファイル

テストの作成

概要

  • describe, test、expectでテストケースを作成していく
  • Cypressを使用したことがある方は、ほぼ同じ感覚で記述していくことができると思う
  • 基本的な流れは、page.gotoで特定のページに遷移し、page.getByLabelなどで指定の要素を取得し、一定の操作後、期待する状態になっているかをexpect(foo).toBe(bar)のようにアサートする
import { test, expect } from "@playwright/test";

test('test example', async ({ page }) => {
  await page.goto('https://example.com');
  
  const locator = page.locator('#my-element');

  await expect(locator).toBeVisible();

});

test

  • この関数内にテストを記述する
    • 第一引数にはテストの名前を設定し、第二引数にコールバック関数でテスト内容を記述
    • 第二引数のコールバック関数の引数では page オブジェクトを受け取ることができる
import { test, expect } from "@playwright/test";

test("サンプル", async ({ page }) => {
  page.goto(‘https://playwright.dev/‘)
  //
  await expect(page).toHaveTitle(/Playwright/);
});

fixtures

test 関数の第二引数のコールバック関数の引数の page オブジェクトは fixturesと呼ばれており、page 以外にも context, browser, browserName, request の fixtures が利用できる

https://playwright.dev/docs/test-fixtures

expect

色々なマッチャーと組み合わせて要素の状態をアサートする

セットアップ処理

他のE2Eテストツールでも使用できるような一通りのセットアップ処理が揃っている

test.describe("グループ化", () => {
  test.beforeAll(async () => {
    // すべてのテストケースの前に一度だけ実行
  });

  test.beforeEach(() => {
    // 各テストケースの前に実行
  });

Auto-wait

例えば、page.click(selector[, options])を実行する場合、playwrightは下記を確認してから次の操作へ移るため、ユーザーは要素が表示されているかどうかについて気にする必要がなくなっている

  1. 要素がDOMにアタッチ
  2. 要素がVisibleになる
  3. 要素がStableになる(アニメーション途中 / アニメーション完了時点を終えている)
  4. 要素がイベント受け取りが可能な状態である
  5. 要素がEnabledになる

指定時間内に上記をクリアできなければ、TimeoutErrorになる

Cypressとの比較

  • CypressにもPlaywrightのAuto-waitに相当する仕組みが存在する
  • Cypressは自動的に待機する機能を持っており、DOMの変更を自動的に検知してその完了を待つことができる
  • 例えば、cy.get()メソッドはデフォルトで要素がDOMに存在し、アクションを受け付ける状態になるまで待機する

テストの実行

以下でテストを実行可能

npx playwright test
  • デフォルトではheadlessで実行される
  • テスト実行後、プロジェクトフォルダ直下に playwright-report, test-results フォルダが新たに作成される
  • test-results フォルダは空だが、playwright-report フォルダには index.html ファイルが作成される

ブラウザで実行する場合は以下のように実行する

npx playwright test --headed

ブラウザでインタラクティブにテストを行いたい場合には—ui オプションをつける

npx playwright test --ui 

この起動モードのWatchモード(目のアイコンをクリック)でテストコードの変更を検知して、自動的にテストを実行してくれるようになる

テストコードを修正して、実行結果を確認するという作業を繰り返す際に便利。

デバッグモードで実行したい場合は以下のようにする。ブレークポイントを使用してデバッグできるようになる

npx playwright test --headed --debug

設定ファイルを指定するためのコマンド(ユースケースごとに設定ファイルを複数分けている場合に有用)

npx playwright test -c playwright.config.js

タグ

特定のタグがついているテストのみを実行することが可能

$ npx playwright test --grep @tutorial

テスト名にタグ名を含める

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

test("playwrightドキュメントのFixturesの説明ページに遷移できる | @tutorial", async ({
  page,
}) => {
  await page.goto("https://playwright.dev/");

  // トップページのGet startedボタンをクリック
  await page.getByRole("link", { name: "Get started" }).click();
  await expect(page).toHaveTitle(/.*Install/);

  // インストールページのスクリーンショットを撮る
  await page.screenshot({ path: "Installation page screenshot.png" });
});

いずれかのタグを含むテストを実行することも可能(=or条件)

npx playwright test --grep "@tutorial|@api"

コード生成

以下で指定したurlでブラウザが開き、そのブラウザ上で操作した内容をもとにテストコードが自動生成される機能

npx playwright codegen <url>

コード生成のアルゴリズム

公式Docによると、おそらくgetByRoleで特定可能であればこれを優先的に、なければ・・・みたいな感じでいい感じに、要素の特定方法が実装の詳細になり過ぎないように、優先順位をつけて要素を取得してくれるようになっているみたい

Playwright はあなたのページを見て、ロール、テキスト、テスト ID ロケーターに優先順位を付けて最適なロケーターを見つけ出します。ジェネレーターがロケーターに一致する複数の要素を見つけた場合、ロケーターを改善して、ターゲット要素を一意に識別する復元力を高めます。

アサート文の生成

codegenで起動したブラウザの画面上部に表示されているツールバーの、abとなっている箇所を選択して何かしらの画面上の要素をクリックすると、その要素に関してアサート文を候補で表示してくれる

レスポンシブエミュレート

デバイスやビューポートを指定してコード生成できる。SP時はヘッダーメニューがハンバーガーになったりということを想定してテストできる

デバイス

npx playwright codegen --device="iPhone 13" playwright.dev

ビューポート

npx playwright codegen --viewport-size=800,600 playwright.dev

認証

あらかじめ認証情報をjsonファイルに保存しておき、テスト実行時に認証状態を適用させることで、テストの安定性を向上させることが可能な機能が備わっている

認証情報をjsonへ保存

context.storageState()で保存

import { test, expect } from "@playwright/test";
import { describe } from "node:test";
・・・

test("メールアドレスとパスワードでログインし、トップページに遷移できる", async ({
    page,
  }) => {

  //ログイン
  await new LoginUseCase().login(page);

  // MEMO: トップページに遷移するまで待機(待機しないと、auth_tokenがクッキーから取得できない)
  await page.waitForURL("/top");

  await page.context().storageState({ path: "auth.json" });
});

テスト実行時に認証状態を適用

test.useで適用

describe("認証が必要な画面でのテスト", () => {
  test.beforeEach(() => {
    // 
  });
  test.use({ storageState: "auth.json" });
  test("テスト", async ({
    page,
  }) => {
    // すでに認証済みなので、これはトップ画面に遷移する
    await page.goto("/login");

上記は各テストケースごとに適用する例だが、認証を適用する粒度も指定できる

全てのテスト実行前に認証ファイルを保存する

すべてのテストが、同じアカウントで同時に実行しても問題ないような場合に適している
詳細はこちらを参照

この場合は、各テストケースでtest.useするのではなく、playwright.config.tsのstorageStateにjsonファイルを指定してあげた方が冗長でないので、こちらの方がいい

playwright.config.ts

import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    storageState: 'auth.json',
  },
});

その他、パラレルワーカー単位でテストアカウントが異なる場合(ワーカー プロセスごとに 1 回認証し、それぞれに固有のアカウントを使用)や、APIテスト時の認証情報の設定も可能

認証状態のリセット

テスト全体に適用する認証を、一部のテストだけ適用しないようにすることも可能

import { test } from '@playwright/test';

// Reset storage state for this file to avoid being authenticated
test.use({ storageState: { cookies: [], origins: [] } });

test('not signed in test', async ({ page }) => {
  // ...
});

テストの設定

playwright.config.tsはデフォルトでは以下の通り。主要なものをピックアップして列挙する

import { defineConfig, devices } from "@playwright/test";

/**
 * Read environment variables from file.
 * https://github.com/motdotla/dotenv
 */
// require('dotenv').config();

/**
 * See https://playwright.dev/docs/test-configuration.
 */
export default defineConfig({
  testDir: "./tests",
  /* Run tests in files in parallel */
  fullyParallel: true,
  /* Fail the build on CI if you accidentally left test.only in the source code. */
  forbidOnly: !!process.env.CI,
  /* Retry on CI only */
  retries: process.env.CI ? 2 : 0,
  /* Opt out of parallel tests on CI. */
  workers: process.env.CI ? 1 : undefined,
  /* Reporter to use. See https://playwright.dev/docs/test-reporters */
  reporter: "html",
  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
  use: {
    /* Base URL to use in actions like `await page.goto('/')`. */
    baseURL: "http://127.0.0.1:8000",

    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
    trace: "on-first-retry",
  },

  /* Configure projects for major browsers */
  projects: [
    {
      name: "chromium",
      use: { ...devices["Desktop Chrome"] },
    },

    // {
    //   name: "firefox",
    //   use: { ...devices["Desktop Firefox"] },
    // },

    // {
    //   name: "webkit",
    //   use: { ...devices["Desktop Safari"] },
    // },

    /* Test against mobile viewports. */
    // {
    //   name: 'Mobile Chrome',
    //   use: { ...devices['Pixel 5'] },
    // },
    // {
    //   name: 'Mobile Safari',
    //   use: { ...devices['iPhone 12'] },
    // },

    /* Test against branded browsers. */
    // {
    //   name: 'Microsoft Edge',
    //   use: { ...devices['Desktop Edge'], channel: 'msedge' },
    // },
    // {
    //   name: 'Google Chrome',
    //   use: { ...devices['Desktop Chrome'], channel: 'chrome' },
    // },
  ],

  /* Run your local dev server before starting the tests */
  // webServer: {
  //   command: 'npm run start',
  //   url: 'http://127.0.0.1:3000',
  //   reuseExistingServer: !process.env.CI,
  // },
});

testDir

テスト対象のディレクトリ。npx playwright testコマンドを実行した時に実行されるテストファイルが格納されているディレクトリ

fullyParallel

テストを並列で実行させる場合はtrueを指定する(デフォルトtrue)

retries

テストが失敗した際に、何度リトライするかを指定する

workers

  • テスト実行時に同時に起動する Worker Process の最大値を設定する
  • 実際に使用されたWorker Processの数は、テスト実行後にコンソールに出力されている
npx playwright test tests/sample.spec.ts

Running 1 test using 1 worker
  1 passed (2.0s)

use

デフォルトをheadedにする

useの中でheadlessプロパティをfalseに設定することで可能

use: {
    headless: false,
  },

認証ファイルのパス

全てのテストケースで使用する認証情報のファイルを指定できる

storageState: "auth.json",

projects

テストを実行する際に起動するブラウザーを指定する。デフォルトで chromium, firefox, webkit の3つを利用してテストを実行するようになっている

アサーション(expect)のタイムアウト値

デフォルトは5000ms。変更する場合は以下を追加して調整する

expect: {
    timeout: 2000,
  },

Playwright Test for VSCode

VSCode拡張により、エディタ上でテストケースの作成、実行、デバッグを実行することが可能。

Show browser

チェックをつけると、ブラウザモードでのテスト実行が可能

Show Trace viewer

チェックをつけると、画面上部にテストのトレース情報がスライド式で表示される。トレースビューワーはテストのデバッグにとても便利

Pick locator

Pick locatorをクリックして起動したブラウザ上でカーソルを当てると、その要素のセレクタが表示され、それをコピーして使用することができる。特定の要素の取得方法(ロケーター)を知りたいときに有用

Record new

Record newをクリックすると、レコード状態でブラウザが起動しつつ、新規テストファイルがtestsディレクトリ以下に作成される

Record at cursor

Record at cursorをクリックすると、ブラウザ操作に対応するテストコードが、リアルタイムでエディターに書き込まれていく。エディター上のテストケースの任意の位置からレコードを開始できる非常に強力な機能

APIテスト

テストサンプル

test関数の第二引数のコールバック関数の引数としてrequestオブジェクトを受け取ることができ、それを利用してhttpリクエストを送信できる

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

test.describe("APIリクエストサンプル", () => {
  test("条件指定なし", async ({ request }) => {
    const res = await request.get(
      "https://jsonplaceholder.typicode.com/posts/1"
    );

    const actual = await res.json();

    console.log(actual);

    const expected = {
      userId: 1,
      id: 1,
      title:
        "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
      body: "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto",
    };

    expect(actual).toStrictEqual(expected);
  });
});

全てのテストで共通して使用するリクエストヘッダーや認証情報があれば、configに指定可能

playwright.config.ts

import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
      'X-My-Header': 'value',
    },

    httpCredentials: {
      username: 'user',
      password: 'pass',
    },
  },
});

その他

データ投入

セットアップでDBのデータ投入を行いたい場合は、テストケースに必要なdumpファイルをいくつか用意しておいて、
execSync を使ってスキーマの作成やダンプの投入を行うことが可能

以下はpostgresコンテナに対してダンプファイルを投入する例。psqlコマンドを使用している

import { exec } from 'child_process';
import { promisify } from 'util';

const execAsync = promisify(exec);

async function loadDumpFile(dumpFilePath: string) {
  try {

    // Dockerコンテナで立てたpostgresqlにデータを投入する
    await execAsync(
      `docker exec postgres psql -U postgres -d sample_db -f ${dumpFilePath}`
    );

  } catch (error) {
    console.error('Error resetting schema:', error);
  }
}

// 

const dumpFilePath ="/home/dump.sql";
loadDumpFile(dumpFilePath);

レポートの表示

以下を実行すると、playwright-report フォルダのindex.htmlが出力される。これをブラウザ上で表示し、テストの詳細について確認することが可能

npx playwright show-report

スクリーンショット

page.screenshotを使用すると、テスト実行時のスクリーンショットを指定したパス、ファイル名の画像として保存することが可能

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

test('take a screenshot', async ({ page }) => {
  // ページを開く
  await page.goto('https://example.com');

  // スクリーンショットを取得
  await page.screenshot({ path: 'example.png' });
});

デフォルトでは、ビューポート(ブラウザウィンドウ内で現在見えている部分)のみのスクリーンショットを取得する。fullPage: trueを設定すると、ページ全体をスクロールしながらスクリーンショットを取得し、一枚の画像として出力してくれる

await page.screenshot({
  path: 'screenshot.png',
  fullPage: true,
});

ループ処理を行う場合、forEachのコールバックに非同期関数をサポートしていないので、for文で対応する必要がある

NG

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

test('async loop test', async ({ page }) => {
  // ページを開く
  await page.goto('https://example.com');

  // テスト対象の要素のセレクタの配列
  const selectors = ['#selector1', '#selector2', '#selector3'];

  // forEach ループを使用
  selectors.forEach(async (selector) => {
    // locatorを取得
    const locator = page.locator(selector);

    // 要素が存在することを確認
    await expect(locator).toBeVisible();

    // その他の非同期処理...
  });
});

OK

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

test('async loop test', async ({ page }) => {
  // ページを開く
  await page.goto('https://example.com');

  // テスト対象の要素のセレクタの配列
  const selectors = ['#selector1', '#selector2', '#selector3'];

  // for...of ループを使用
  for (const selector of selectors) {
    // locatorを取得
    const locator = page.locator(selector);

    // 要素が存在することを確認
    await expect(locator).toBeVisible();

    // その他の非同期処理...
  }
});

waitForTimeout

処理と処理の間の操作が早過ぎてテストが失敗する場合は、待機時間を設けることを検討する

ドラッグ&ドロップ関連

dragTo

ドラッグする要素の上にマウスを置き、次にマウスの左ボタンを押し、マウスをドロップ ターゲットに移動し、最後にマウスの左ボタンを放す

const source = page.locator('#source');
const target = page.locator('#target');

await source.dragTo(target);

dragAndDrop

page.dragAndDrop(source, target[, options])

参考
https://ceroshjacob.medium.com/drag-and-drop-using-playwright-c09aa99cfca

mouse.move

mouse.moveやmouse.hoverなどを使用したより低レイヤーのAPIの操作も可能

タイムアウト値の設定

テスト全体

playwright.config.ts

import { defineConfig } from '@playwright/test';  
 
export default defineConfig({
 // テストケースのタイムアウト時間  
 timeout: 100000,

 // expectのタイムアウト時間
 expect: {  
   timeout: 100000,
 },
});

特定のテスト

test.setTimeoutで指定する

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

// テストケースのタイムアウト時間    
test('sample', async ({ page }) => {  
  test.setTimeout(120000);
  // ...  
});

// expectのタイムアウト時間
test('sample', async ({ page }) => {  
// timeoutの時間を変更
await expect(page.getByRole('button')).toHaveText('追加する', { timeout: 100000 });   
});

まとめ

いかがでしたでしょうか。本記事では、Playwrightを実業務で使用する際に必要な最低限の使用方法やTipsなどについて紹介しています。具体的には要素の取得方法や検証方法、デバッグの手順などについて例を挙げながら説明していますのでぜひ参考にしてみてください。