Playwrightのcodegenで操作を記録してテストを作る
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)を付けると、生成したコードをファイルに書き出します。
npx playwright codegen --output tests/default.spec.ts http://localhost:3000
起動すると、ウィンドウが2つ開きます。ページを操作するためのブラウザのウィンドウと、生成されたコードが出る Playwright Inspector のウィンドウです(公式ドキュメントの、Playwright Inspector でテストを生成する節)。
何も操作せずに止めたとき、ファイルにはページを開く1行だけが入っていました。
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 でページの表示サイズを小さくしました。
npx playwright codegen --viewport-size "760,560" --output tests/recorded.spec.ts http://localhost:3000
このときは、起動した直後のファイルに test.use() でビューポートの設定が入りました。codegen で指定した表示サイズが、そのままテストのコードに書き込まれます。公式ドキュメントの、表示サイズを指定する節によると、テストは同じ条件で実行する必要があるので、codegen が開くブラウザは決まった表示サイズになっています。
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 のコードが増えていきます。

要素にマウスを乗せると、そこに使われる locator(getByRole('button', { name: '送信' }) など)が小さく表示されます。--output を付けておくと、ファイルも操作のたびに書き換わりました。名前を入れた直後に tests/recorded.spec.ts を見ると、すでに次の2行が増えていました。
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 を押して、結果の文をクリックしました。すると、確かめる文字を入れるダイアログが開き、要素の今の文字が最初から入っていました。

文字はこのダイアログで書き換えられます。右上のチェックマークで確定すると、次の1行が増えました。
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 で止めました。止めたあとも、ファイルには最後まで記録したコードが残っていました。
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 の画面にあるコピーのボタンでコードをコピーし、エディターに貼り付けます。
記録したテストを実行する
生成されたファイルは、そのままテストとして実行できます。
npx playwright test tests/recorded.spec.ts
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)は次のとおりです。
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 さんの「不具合の報告」を受け付けました');
});
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 などは省いています)。
✘ 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 などで「何が出れば正しいか」を足しておくと、テストとして役に立ちます。





