一発でパスしたテスト、中身まで読んでいますか
前々回はPlaywright MCPのプロンプトインジェクションのリスク、前回は壊れたテストをHealerに直させる話を書きました。どちらも「AIにテストを触らせる」ことが前提の記事でしたが、今回はその手前の話です。AIに書かせたテストが、そのまま信じていいものかどうかという話です。
PlaywrightのGeneratorやMCP経由でAIにテストを書かせると、驚くくらいすんなり動くコードが出てきます。実行すればグリーン。テスト名も「ログインできる」のようにそれっぽい。ここで「できた、次」と次の作業に移ってしまっていないでしょうか。私も最初の頃はそうでした。
ですが、グリーンで終わっているテストの中身を1行ずつ読むと、実は操作しかしていなくてアサーションがほぼ無い、ということがよくあります。テストが通っているのは「壊れていないから」ではなく「そもそも何も検証していないから」というオチです。今回は1年目のメンバーがAIの書いたテストをレビューするときに、最低限見てほしい4つの落とし穴を、悪い例と直した例のコードでまとめておきます。
落とし穴その1:操作だけして中身を見ていないテスト
AIが書いたテストで一番よく見るのがこれです。ログインもフォーム送信も丁寧にコードにしてくれるのですが、最後の確認が申し訳程度のtoBeTruthy()だったりします。
// 悪い例:操作はしているが何も検証していない
test('ログインできる', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('メールアドレス').fill('user@example.com');
await page.getByLabel('パスワード').fill('password123');
await page.getByRole('button', { name: 'ログイン' }).click();
expect(await page.title()).toBeTruthy();
});
タイトルなんて空文字でなければ大抵toBeTruthy()を通ってしまいます。ログインに失敗して元のページに戻されても、このテストは平気でグリーンになるということに注意してください。
// 直した例:ログイン後の状態を具体的に確認する
test('ログインできる', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('メールアドレス').fill('user@example.com');
await page.getByLabel('パスワード').fill('password123');
await page.getByRole('button', { name: 'ログイン' }).click();
await expect(page).toHaveURL(/\/mypage/);
await expect(page.getByRole('heading', { name: 'マイページ' })).toBeVisible();
});
「ログインできる」というテスト名なら、ログイン後のURLか画面要素まで見て初めて検証できたと言えます。どのマッチャーを使うべきか迷ったら、以前まとめたexpectマッチャーの完全リファレンスを見ながら選んでみましょう。
落とし穴その2:存在しないセレクタを平気で書いてくる
次に多いのが、実際のマークアップには無いdata-testidやロール名をAIが自信満々に書いてくるパターンです。
// 悪い例:実際のDOMに存在しないtestidを想像で書いている
await page.getByTestId('submit-btn').click();
実行すればタイムアウトで落ちるので気づけそうなものですが、AIがついでにpage.waitForSelectorやリトライ処理まで足してしまい、落ちるはずのテストがなぜか通ってしまうことがあります。落ちて当然のテストが通っている時ほど怪しい、と思って読むくらいがちょうどいいです。
// 直した例:実際のマークアップにある属性・ロールを使う
await page.getByRole('button', { name: '送信する' }).click();
Playwright MCPはブラウザのアクセシビリティツリーを見ながらセレクタを組み立てるので、この手の幻覚セレクタは比較的少ない印象です(この体感はMCP経由で書かせた場合の話で、DOMを見せずにプロンプトだけでコードを書かせる使い方だとまだ起きやすいと思っています。ここは今の私の理解なので、実際に使う際は手元で確認してください)。いずれにしても、AIが出したセレクタは一度自分の目で該当要素があるか見てから採用しましょう。
落とし穴その3:sleepならぬwaitForTimeoutで待たせる
Seleniumの頃からある古典的な落とし穴が、Playwrightでも形を変えて出てきます。
// 悪い例:とりあえず5秒待って通そうとする
await page.getByRole('button', { name: '保存' }).click();
await page.waitForTimeout(5000);
await expect(page.getByText('保存しました')).toBeVisible();
これでテストは通りますが、CIのマシンが混雑している日はどうなるでしょうか。5秒で終わらなければ普通に落ちます。逆に処理が1秒で終わる日は、4秒分ただ待たされるだけの無駄なテストです。Seleniumのsleep問題と同じことが、道具を変えても繰り返されているだけです。
// 直した例:状態が変わるまで自動で待ってくれるアサーションに任せる
await page.getByRole('button', { name: '保存' }).click();
await expect(page.getByText('保存しました')).toBeVisible();
Playwrightのlocatorベースのアサーションは、条件を満たすまで自動でリトライしてくれます。固定の待ち時間を書かせるくらいなら、何が起きたら成功なのかをAIに聞き返して、その条件をアサーションに書かせてみましょう。ずっと安定します。
落とし穴その4:テスト名と中身がズレている
地味ですが実害が大きいのがこれです。テスト名は「カートに商品を追加できる」なのに、中身を読むとカートページを開いて見出しがあることだけ確認していて、肝心の追加操作も個数の検証もしていない、というようなケースです。
// 悪い例:テスト名と検証内容が一致していない
test('カートに商品を追加できる', async ({ page }) => {
await page.goto('/cart');
await expect(page.getByRole('heading', { name: 'カート' })).toBeVisible();
});
// 直した例:テスト名で宣言した操作と検証を実際に行う
test('カートに商品を追加できる', async ({ page }) => {
await page.goto('/products/1');
await page.getByRole('button', { name: 'カートに追加' }).click();
await page.goto('/cart');
await expect(page.getByRole('listitem').filter({ hasText: '商品1' })).toBeVisible();
await expect(page.getByTestId('cart-count')).toHaveText('1');
});
テスト名とコードの中身がズレる理由は単純で、AIに「カートに商品を追加できるテストを書いて」と頼んだつもりが、実際に渡した情報がカートページのURLだけだったりするからです。AIは渡された材料の範囲でしか書けません。テスト名だけ見て中身を読まずにマージすると、このズレにレビューの段階で気づけません。
1年目のレビューで見るところ
というわけで4つの落とし穴を見てきましたが、レビューのたびに全部を丁寧に読むのは大変だと思います。最低限、次の順番でチェックしてみましょう。それだけで大きな見落としはかなり減ります。
- テスト名を読んで、期待する検証内容を自分の頭でまず想像する
- 最後のアサーションが、その想像と一致しているか確認する(
toBeTruthy()だけで終わっていたら要注意) - 使われているセレクタが実際の画面に存在するか、ブラウザで一度確認する
waitForTimeoutが出てきたら、待っている条件をアサーションに置き換えられないか考える- わざとテストを壊してみて(ボタン名を変える等)、ちゃんと落ちるか確認する
最後の「わざと壊して落ちるか確認する」が地味に一番効きます。これ、意外と使えます。グリーンのテストを見て安心するのではなく、レッドになるべき時にちゃんとレッドになるかまで確認して初めて、そのテストを信じられます。
まとめ:AIが書いたテストは下書きとして扱う
AIに書かせたPlaywrightテストは、下書きとしては優秀です。ボイラープレートを書く手間はかなり減りますし、私自身も日常的に頼っています。ただ、パスしたという事実だけで信じるのは危険です。何を検証しているか、セレクタは実在するか、待ち方は妥当か、テスト名と中身は一致しているか。この4点だけは人間が読んで確認する必要があります。
1年目のメンバーにレビューをお願いするときは、ぜひこの記事のチェックリストを渡してみてください。AIが書いたコードだからといって難しく考えず、普通のプルリクエストと同じ目でテストコードも読む、それだけのことです。
関連記事も合わせてどうぞ。

コメント