Zenn
💡

Playwright MCPとCursorで、E2Eテストを自動生成してみた 〜AI×ブラウザ操作の新アプローチ〜

2025/03/25に公開
63

概要

https://github.com/microsoft/playwright-mcp

Playwright MCPとCursorを活用し、E2Eテストをゼロから自動生成してみました。
本記事ではその検証プロセスと得られた知見を紹介します。

この記事で分かること

  • Playwright MCPでE2Eテストを自動生成する方法
  • Playwright MCPの活用のヒント

はじめに

昨今のMCPブームは収まることを知らず、日々新しいMCP Serverが公開されています。
そんな中、自動化テストツールとして有名なPlaywrightのMCP Serverが公開されました。

Playwrightには、既にユーザーがブラウザを操作しテストを生成できる非常に便利な機能がありますが、今回はそれを超えるために、ユーザーの代わりにCursor(MCP Client)を使用し、ブラウザの自動操作とE2Eテストの自動生成を行えるのかを検証しました。

検証環境

  • Cursor: claude-sonnet-3.7
  • MCP Server: microsoft/playwright-mcp
  • Playwright version: playwright/test: ^1.51.1

検証ステップ

Step 1:ログイン画面のE2Eテスト作成

🔸 インプット(Cursorへの指示)

# ゴール

- Playwright用のE2Eテストの作成

## 作成するテスト

- root画面を表示し、ID/PASSを使ったログインとトップページが表示できることの確認

## テスト作成方法

PlaywrightのMCPを使ってローカルの開発環境にアクセスして要素を解析してください。そして、解析した要素をベースにE2Eテストを作成してください。

🔸 生成されたコード(ログインフロー)

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

test.describe('ログインフロー', () => {
  test('メールアドレス/ユーザーIDとパスワードでログインできる', async ({ page }) => {
    await page.goto('http://localhost:3000')

    await expect(page.getByRole('heading', { name: 'dotHatch' })).toBeVisible()

    await page.getByRole('textbox', { name: 'メールアドレス / ユーザーID' }).fill('********************')
    await page.getByRole('textbox', { name: 'パスワード' }).fill('********************')

    await page.getByRole('button', { name: 'ログイン', exact: true }).click()

    await expect(page).toHaveURL('http://localhost:3000/v2/projectlist')
  })
})

テストを作成後に自動でテストを実行し、エラーが発生した箇所の修正も含めて全てCursorが行いました。ID/パスワードの入力箇所を除き初回の指示のみで、自動生成に成功しました。

Step 2:ログイン後の画面のE2Eテスト作成

🔸 インプット(Cursorへの指示)

OK,次は続きの画面である/v2/projectlist画面のE2Eテストを作成してほしいです。
表示はもちろん、プロジェクトの作成の確認とかもやりたいな
http://localhost:3000/v2/projectlist

🔸 生成されたコード(プロジェクト管理)


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

test.describe('プロジェクト管理機能', () => {
  test.beforeEach(async ({ page }) => {
    // ログインページにアクセス
    await page.goto('http://localhost:3000')

    // ログイン情報を入力
    await page
      .getByRole('textbox', { name: 'メールアドレス / ユーザーID' })
      .fill('*********************')
    await page
      .getByRole('textbox', { name: 'パスワード' })
      .fill('*********************')
    await page.getByRole('button', { name: 'ログイン', exact: true }).click()

    // プロジェクトリスト画面に遷移したことを確認
    await expect(page).toHaveURL('http://localhost:3000/v2/projectlist')

    // 初回表示時のモーダルが表示された場合は閉じる
    try {
      const initialModal = page.locator('div[class*="StyledModal"]')
      if (await initialModal.isVisible({ timeout: 3000 })) {
        // モーダル内の閉じるボタンやOKボタンをクリック
        const closeButton = initialModal.getByRole('button')
        if (await closeButton.isVisible()) {
          await closeButton.click()
        }
        // モーダルが閉じるまで待機
        await page.waitForTimeout(1000)
      }
    } catch (e) {
      // モーダルがない場合はエラーを無視
      console.log('初回表示モーダルはありませんでした')
    }
  })

  test('プロジェクトリスト画面の表示確認', async ({ page }) => {
    // ヘッダーメニューの表示確認
    await expect(
      page.getByRole('link', { name: 'マイページ', exact: true })
    ).toBeVisible()
    await expect(
      page.getByRole('link', { name: 'プロジェクト', exact: true })
    ).toBeVisible()
    await expect(
      page.getByRole('link', { name: '組織・メンバー', exact: true })
    ).toBeVisible()
    await expect(
      page.getByRole('link', { name: 'フェーズ規則', exact: true })
    ).toBeVisible()
    await expect(
      page.getByRole('link', { name: '申請', exact: true })
    ).toBeVisible()
    await expect(
      page.getByRole('link', { name: '承認', exact: true })
    ).toBeVisible()

    // プロジェクト一覧の要素確認
    await expect(
      page.getByRole('paragraph').filter({ hasText: 'プロジェクト' })
    ).toBeVisible()
    await expect(
      page.getByRole('button', { name: 'プロジェクトを作成', exact: true })
    ).toBeVisible()

    // プロジェクト数の表示確認
    await expect(page.getByText('/50個のプロジェクト')).toBeVisible()
  })

  test('プロジェクトの新規作成', async ({ page }) => {
    // プロジェクト作成ボタンをクリック
    await page
      .getByRole('button', { name: 'プロジェクトを作成', exact: true })
      .click()

    // モーダルが表示されるまで待機
    await page.waitForTimeout(1000)

    // プロジェクト作成モーダルの表示確認
    const modal = page
      .locator('div[class*="StyledModal"]')
      .filter({ hasText: 'プロジェクトを作成' })
    await expect(modal).toBeVisible()

    // プロジェクト情報を入力
    const projectName = `テストプロジェクト_${Date.now()}`
    await page.locator('input[name="name"]').fill(projectName)
    await page
      .locator('textarea[name="description"]')
      .fill('これはテストプロジェクトです。')

    // フェーズ規則を選択
    await page.locator('div[class*="StyledSelect"]').click()
    await page.waitForTimeout(500) // オプションの表示を待機
    await page.getByRole('option').first().click()

    // 作成ボタンをクリック
    await modal.getByRole('button', { name: '作成', exact: true }).click()

    // モーダルが閉じるまで待機
    await page.waitForTimeout(1000)

    // プロジェクトが作成され、一覧に表示されることを確認(タイムアウトを長めに設定)
    await expect(page.getByText(projectName, { exact: true })).toBeVisible({
      timeout: 10000
    })
  })
})

生成されたコードは、ログイン後の状態遷移には手動での調整が必要でしたが、これも、Cursorを使いながらエラーを修正していくことでテストを実行できるようになりました。

まとめ

今回は、E2Eテストの自動生成をゼロベースでCursorに行ってもらいましたが、テストケース自体もMCP経由で取得できるようにすることで、より質の高いE2Eテストが作られることが期待できます。

今回の検証の結果から、テスト作成の負担を軽減しながら、コストをかけずにE2Eテストを導入したい開発チームにとって、Playwright MCPは強力な選択肢となりうると感じました。

ぜひ実際に試してみて、AIを活用したテスト自動化の新しい可能性を探ってみてください。

63
dotD Tech Blog

Discussion

ログインするとコメントできます