Playwrightのcodegenで操作を記録してテストを作る

​​Playwright​E2Eテスト,TypeScript

Playwright の codegen(テストジェネレーター)を使うと、ブラウザで実際にクリックや入力をするだけで、その操作をテストのコードにしてくれます。E2E テストを書き始めるときに、フォームへの入力やボタンのクリックを一から手で書かずに済み、要素の指定(locator)も Playwright が選んでくれます。

私は検証環境で、お問い合わせフォームのページを codegen で操作して記録し、できたテストを npx playwright test で実行するところまで試しました。

補足

検証環境は Ubuntu 24.04、Node.js 22.23.4、@playwright/test 1.63.0 です。ブラウザは Chromium だけを使いました。画面のない環境なので、仮想ディスプレイ(Xvfb)の上で codegen を動かし、マウスとキーボードの操作は xdotool というツールで送りました。テストするページは検証用に書いた小さな Node.js のサーバーが返すもので、外部のサイトには接続していません。VS Code の拡張機能から記録する方法、TypeScript 以外の言語の出力、ログインした状態の保存(--save-storage)、ツールバーの Assert snapshot は試していません。

記録に使ったページ

検証に使ったのは、次の部品が並んだお問い合わせフォームです。

  • 「お名前」の入力欄
  • 「種類」のセレクトボックス(「質問」と「不具合の報告」)
  • 「返信を希望する」のチェックボックス
  • 「送信」ボタン

「送信」を押すと、フォームの下の <p id="result" role="status"> に「Neko Taro さんの「不具合の報告」を受け付けました」のような文が出ます。ラベルはどれも <label> で入力欄と結びつけてあります。

codegen を起動する

codegen は、記録したいページの URL を付けて起動します。--output(-o)を付けると、生成したコードをファイルに書き出します。

bash
npx playwright codegen --output tests/default.spec.ts http://localhost:3000

起動すると、ウィンドウが2つ開きます。ページを操作するためのブラウザのウィンドウと、生成されたコードが出る Playwright Inspector のウィンドウです(公式ドキュメントの、Playwright Inspector でテストを生成する節)。

何も操作せずに止めたとき、ファイルにはページを開く1行だけが入っていました。

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

test('test', async ({ page }) => {
  await page.goto('http://localhost:3000/');
});

--target を付けると、出力する言語を選べます。npx playwright codegen --help で見ると、playwright-test(既定)のほか javascript、python、python-pytest、java、csharp などがありました。

画面の大きさを指定すると、コードにも書き込まれる

検証では、2つのウィンドウを画面に並べるために --viewport-size でページの表示サイズを小さくしました。

bash
npx playwright codegen --viewport-size "760,560" --output tests/recorded.spec.ts http://localhost:3000

このときは、起動した直後のファイルに test.use() でビューポートの設定が入りました。codegen で指定した表示サイズが、そのままテストのコードに書き込まれます。公式ドキュメントの、表示サイズを指定する節によると、テストは同じ条件で実行する必要があるので、codegen が開くブラウザは決まった表示サイズになっています。

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

test.use({
  viewport: {
    height: 560,
    width: 760
  }
});

test('test', async ({ page }) => {
  await page.goto('http://localhost:3000/');
});

公式ドキュメントの Emulation の節によると、ほかに --device(端末)、--color-scheme(ライトかダークかの配色)、--lang(言語)、--timezone なども同じように指定できます。

ブラウザで操作して記録する

あとは、開いたブラウザでふつうにページを操作します。名前を入れ、種類を選び、チェックを入れて、送信を押しました。操作するたびに、Inspector のコードが増えていきます。

codegen で記録しているところのアニメーション。上はブラウザのお問い合わせフォームで、お名前の入力、種類の選択、チェックボックス、送信ボタンを順に操作し、最後にツールバーの Assert text で結果の文を選んでいる。下は Playwright Inspector のコードで、操作のたびに新しい行が増え、その行がピンクで目立つ
操作するたびに、下の Inspector のコードが増えていく様子

要素にマウスを乗せると、そこに使われる locator(getByRole('button', { name: '送信' }) など)が小さく表示されます。--output を付けておくと、ファイルも操作のたびに書き換わりました。名前を入れた直後に tests/recorded.spec.ts を見ると、すでに次の2行が増えていました。

ts

await page.getByRole('textbox', { name: 'お名前' }).click();
  await page.getByRole('textbox', { name: 'お名前' }).fill('Neko Taro');

公式ドキュメントの冒頭にあるとおり、codegen は role・テキスト・test id による locator を優先して選びます。検証でも、入力欄やボタンは、getByRole() と、ラベルやボタンの文字で指定されました。セレクトボックスは getByLabel('種類') になり、選んだ項目は表示の文字(「不具合の報告」)ではなく、<option> の value の 'bug' で書かれました。

結果を確かめる行を足す

操作を記録しただけでは、「送信したら何が出るか」は確かめていません。ページの上部に出ているツールバーから、アサーション(期待する結果の確認)を足せます。ボタンにマウスを乗せると、次の名前が出ました。

ボタン 何をするか
Stop Recording 記録を止める
Pick locator 要素を選んで、その locator を調べる
Assert visibility 選んだ要素が表示されていることを確かめる
Assert text 選んだ要素に、その文字が含まれることを確かめる
Assert value 入力欄などの値を確かめる
Assert snapshot 選んだ要素の aria snapshot を確かめるアサーションを作る

表の「何をするか」は、Stop Recording と Assert visibility・text・value が公式ドキュメントの Recording a test の節、Pick locator がPlaywright Inspector の Generating locators の節、Assert snapshot がaria snapshot のドキュメントにある、codegen で作る方法の節によるものです。

送信のあとに Assert text を押して、結果の文をクリックしました。すると、確かめる文字を入れるダイアログが開き、要素の今の文字が最初から入っていました。

ブラウザのお問い合わせフォームで、ツールバーの Assert text を押してから結果の文をクリックしたところ。ページ全体が灰色になり、結果の文が青く囲まれ、その下に「Assert that element contains text」というダイアログが開いて「Neko Taro さんの「不具合の報告」を受け付けました」が入っている
Assert text で結果の文を選ぶと、確かめる文字を入れるダイアログが開く

文字はこのダイアログで書き換えられます。右上のチェックマークで確定すると、次の1行が増えました。

ts

await expect(page.locator('#result')).toContainText('Neko Taro さんの「不具合の報告」を受け付けました');

結果の要素には role="status" も付けていましたが、locator は getByRole() ではなく、id の #result で指定されました。

記録を止めて、できたファイルを見る

記録が済んだら、ツールバーの記録ボタン(Stop Recording)で記録を止めます。codegen を終えるには、Playwright Inspector のウィンドウを閉じるか、コマンドを止めます(公式ドキュメントの Recording a test の節)。検証では、ターミナルで Ctrl+C を押したときと同じ SIGINT で止めました。止めたあとも、ファイルには最後まで記録したコードが残っていました。

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

test.use({
  viewport: {
    height: 560,
    width: 760
  }
});

test('test', async ({ page }) => {
  await page.goto('http://localhost:3000/');
  await page.getByRole('textbox', { name: 'お名前' }).click();
  await page.getByRole('textbox', { name: 'お名前' }).fill('Neko Taro');
  await page.getByLabel('種類').selectOption('bug');
  await page.getByRole('checkbox', { name: '返信を希望する' }).check();
  await page.getByRole('button', { name: '送信' }).click();
  await expect(page.locator('#result')).toContainText('Neko Taro さんの「不具合の報告」を受け付けました');
});

--output を付けずに起動したときは、Inspector の画面にあるコピーのボタンでコードをコピーし、エディターに貼り付けます。

記録したテストを実行する

生成されたファイルは、そのままテストとして実行できます。

bash
npx playwright test tests/recorded.spec.ts
text
Running 1 test using 1 worker

  ✓  1 tests/recorded.spec.ts:10:1 › test (332ms)

  1 passed (860ms)

記録したコードを整える

そのまま動くとはいえ、記録したままのコードには、手で直したほうがよいところがありました。

  • テストの名前が 'test' のままなので、何を確かめるテストかが分からない
  • URL が http://localhost:3000/ と直接書かれている。設定ファイルに baseURL があるなら、'/' で足りる
  • 名前の欄で、fill() の前に click() が入っている。fill() だけで入力できる
  • test.use() のビューポートは、記録のために小さくしただけなら要らない

検証では、playwright.config.ts の use に baseURL: 'http://localhost:3000' を書いてあります。直したテスト(tests/contact.spec.ts)は次のとおりです。

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

test('お問い合わせを送信すると受付のメッセージが出る', async ({ page }) => {
  await page.goto('/');
  await page.getByRole('textbox', { name: 'お名前' }).fill('Neko Taro');
  await page.getByLabel('種類').selectOption('bug');
  await page.getByRole('checkbox', { name: '返信を希望する' }).check();
  await page.getByRole('button', { name: '送信' }).click();
  await expect(page.locator('#result')).toContainText('Neko Taro さんの「不具合の報告」を受け付けました');
});
text
Running 1 test using 1 worker

  ✓  1 tests/contact.spec.ts:3:1 › お問い合わせを送信すると受付のメッセージが出る (235ms)

  1 passed (782ms)

画面の文言が変わると、記録したアサーションで落ちる

Assert text で足した行が働いているかも確かめました。ページの文言を「受け付けました」から「送信しました」に書き換えて同じテストを実行すると、5秒待ったあとに失敗しました(作業ディレクトリのパスは /path/to/app に置き換え、Call log などは省いています)。

text
  ✘  1 tests/contact.spec.ts:3:1 › お問い合わせを送信すると受付のメッセージが出る (5.2s)

    Error: expect(locator).toContainText(expected) failed

    Locator: locator('#result')
    Expected substring: "Neko Taro さんの「不具合の報告」を受け付けました"
    Received string:    "Neko Taro さんの「不具合の報告」を送信しました"
    Timeout: 5000ms
(中略)
        at /path/to/app/tests/contact.spec.ts:9:41

この失敗は、Assert text で足した1行で起きたものです。codegen で記録するときは、操作のあとに Assert text などで「何が出れば正しいか」を足しておくと、テストとして役に立ちます。

​Posted by 86