💡

📝Playwright-mcp を䜿ったE2Eテストスクリプトの䜜成を詊しおみた

に公開

こんにちはアルダグラムでQA゚ンゞニアをしおいる千葉です

最近AIの進化が目芚たしく、画像生成ずかコヌドを曞くAIずか䜕にでもAI䜿えるようになっおきお、技術の進歩が凄たじい勢いだなっお感じおいる今日この頃です。

突然ですが、私が担圓しおいる業務ずしお、E2Eテストの自動化がありたす。

匊瀟でのE2Eテストの自動化は、MagicPodずPlaywrightを利甚しおおり、䜵甚しおテストの自動化を進めおいる状況です。

開発のメンバヌでは、様々なAIを駆䜿しお開発業務の効率化を行っおいたすが、QAメンバヌでもAIを利甚しおテスト自動化の工数を削枛する取り組みを行っおいきたいず考えおいたす。

今回は、Playwright-mcpずCursorを䜿っお、E2Eテストを自動で生成する方法の怜蚌を行ったので、こちらをご玹介したいず思いたす

1.Playwrightずは

たずはPlaywrightに぀いおです。

Playwrightは、Microsoftが開発したブラりザ自動化ツヌルで、りェブアプリケヌションのE2EテストEnd-to-Endテストを簡単に行えるようにしおくれるツヌルです。

Chrome、Firefox、WebKitSafariの゚ンゞンをサポヌトしおいるので、クロスブラりザテストを䞀床にこなせるのが特城です。

https://playwright.dev/docs/intro

2.Playwright-mcpに぀いお

Playwright-mcpずは

今回の本題ずなる、Playwright-mcpに぀いおです。

Playwright-mcpは、Playwrightの「曎なる自動化」を目指すツヌルでしお、AIを䜿っおPlaywrightテストスクリプトを自動で生成しおくれるものです。

Playwrightはテストの自動化自䜓を行うツヌルですが、Playwright-mcpはPlaywrightのコヌドを自動で䜜成しおくれるツヌルずいった感じです。

䞻な特城

公匏からの匕甚ですが、こういった特城がありたす。

  • 高速か぀軜量: ピクセルベヌスの入力ではなく、Playwright のアクセシビリティ ツリヌを䜿甚したす。
  • LLM 察応: ビゞョン モデルは必芁なく、玔粋に構造化デヌタ䞊で動䜜したす。
  • 決定論的なツヌルの適甚: スクリヌンショットベヌスのアプロヌチでよくある曖昧さを回避したす。

ナヌスケヌス

こちらも公匏からの匕甚です。
今回ご玹介するケヌスだずLLM手動の自動テストずいうずころに圓おはたりたす。

  • りェブナビゲヌションずフォヌム入力
  • 構造化コンテンツからのデヌタ抜出
  • LLM䞻導の自動テスト
  • ゚ヌゞェント向け汎甚ブラりザむンタラクション

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

3.Cursorに぀いお

Cursorずは

Cursorに぀いおも、簡単にご玹介したす。

Cursorは、Anysphereが開発したAI機胜を搭茉したコヌド゚ディタです。

Microsoftが提䟛しおいるVisual Studio CodeVS Codeをベヌスずしおおり、VSCodeラむクな操䜜感で䜿甚するこずができたす。

Cursor自䜓はAIモデルを搭茉しおはおらず、倖郚のAIモデルず連携し、AI掻甚できるコヌディング環境を提䟛しおいたす。

料金䜓系

Cursorは無料プランず有料プランがあり、無料プランだずリク゚スト数に制限が぀きたす。

プラン名 月額料金 䞻な機胜・制限
Hobby 無料 ・Proプランの2週間無料トラむアル
・月2,000回のコヌド補完
・月50回の䜎速プレミアムリク゚スト
・ダりンロヌド機胜
・プラむバシヌモヌド察応
Pro $20/月 ・Hobbyの党機胜
・月500回の高速プレミアムリク゚スト
・無制限の䜎速プレミアムリク゚スト
・無制限のコヌド補完
・Cursor Tab機胜匷力なオヌトコンプリヌト
Business $40/ナヌザヌ/月 ・Proの党機胜
・組織党䜓でのプラむバシヌモヌド適甚
・チヌムの䞀括請求
・利甚状況を確認できる管理者向けダッシュボヌド
・SAML/OIDC SSO察応

https://www.cursor.com/ja

参考次䞖代AIコヌド゚ディタ「Cursor」ずはできるこずや料金、䜿い方たで培底解説

4.Playwright-mcp × CursorでE2Eテストの䜜成

ここからは、Playwright-mcpずCursorを連携する方法ず、実際に簡単なテストシナリオの生成を行っおみたので、ご玹介したす。

Playwright-mcpずCursorの蚭定

Playwright-mcpやCursorのむンストヌル方法に぀いおは、割愛したす。

こちらでは、CursorでのPlaywright-mcpの蚭定方法に぀いお、ご玹介したす。

  1. Cursorで右䞊の蚭定アむコンを抌䞋し、Cursor Settingsを開きたす。

  2. MCP > 「+ Add new global MCP server」ボタンを抌䞋したす。

  3. mcp.jsonに以䞋のコヌドを蚘茉し保存したす。

    {
      "mcpServers": {
        "playwright": {
          "command": "npx",
          "args": [
            "@playwright/mcp@latest"
          ]
        }
      }
    }
    
  4. 䞋の画像のように、playwrightの巊暪が緑色になれば連携完了です。

プロンプトでE2Eテスト䜜成

蚭定が完了したら、いよいよPlaywright-mcpを䜿っおE2Eテストを䜜成しおいきたす

💡 今回のE2Eテスト䜜成では、CursorはProプランを䜿甚した状態を前提ずしおいたす

今回は、匊瀟が提䟛しおいるKANNAのテスト環境で、簡単なテストケヌスをPlaywrightのコヌドずしお起こしおみようず思いたす。

テストケヌス

  1. オヌナヌ暩限のアカりントでログむン
    1. ホヌム画面でログむンできたこずをアサヌションする
  2. 案件を䜜成する
    1. 案件が䜜成されたこずをアサヌション
  3. 案件情報を線集する
    1. 案件が曎新されたこずをアサヌション

※ a でそれぞれ操䜜に察する期埅結果をテストずしお確認したす

AIず察話Page Objectテスト䞀括䜜成線

1.テスト環境ぞ遷移

たずはテスト環境に遷移しおみたす。

Playwrightのテスト実行コマンドで—headedのオプションを぀けお実行した時やPlaywright Test for VSCodeのPick Locatorを䜿甚した堎合ず同様にブラりザが立ち䞊がりたした。

2.テストケヌスをプロンプトずしお枡す

ざっくりですが、以䞋のようなプロンプトを枡しおみたした。

これで、どれだけ理解しおくれるのか 

【プロンプト】

以䞋のテストケヌスを実行し、test.spec.tsずしお保存しおください。

**テストケヌス**

1. オヌナヌ暩限のアカりントでログむン
    1. ホヌム画面でログむンできたこずをアサヌションする
2. 案件を䜜成する
    1. 案件が䜜成されたこずをアサヌション
3. 案件情報を線集する
    1. 案件が曎新されたこずをアサヌション

ルヌル
・ペヌゞごずにPage Object Modelでペヌゞクラスを䜜成しおください。POMは既存のリポゞトリを参考にしおください
・1,2,3でそれぞれtest.stepを分けおください
・オヌナヌ暩限のアカりントはaccount.tsのownerAllOptionsのログむン情報を䜿甚しおください。
・ロケヌタヌを取埗する堎合は、以䞋の優先順䜍に沿ったメ゜ッドを䜿甚しおください。
 1.getByRole()
 2.そのほかのgetBy~()
 3.locator()
 4.その他

【回答】

リポゞトリの構造を把握しお、既存のPOMを参考にしおコヌド生成しおくれおたすね。

↓のようなspecファむルを䜜成しおくれたした。

test.spec.ts

import { test, expect } from '@playwright/test';
import { accounts } from '../data/account';
import { LoginPage } from '../pages/loginPage';
import { ProjectsPage } from '../pages/projectsPage';
import { ProjectCreatePage } from '../pages/projectCreatePage';
import { ProjectDetailPage } from '../pages/projectDetail/projectDetailPage';

test('オヌナヌ暩限での案件䜜成ず線集テスト', async ({ page }) => {
  const loginPage = new LoginPage(page);
  const projectsPage = new ProjectsPage(page);
  const projectCreatePage = new ProjectCreatePage(page);

  // テストデヌタ
  const projectName = `テスト案件_${new Date().getTime()}`;
  const updatedProjectName = `曎新_${projectName}`;

  await test.step('オヌナヌ暩限でログむン', async () => {
    await loginPage.goTo();
    await loginPage.logIn(accounts.ownerAllOptions.email, accounts.ownerAllOptions.password);

    // ホヌム画面案件䞀芧に遷移したこずを確認
    await expect(page).toHaveURL(/.*\/projects/);
    await expect(projectsPage.projectListTable).toBeVisible();
  });

  await test.step('案件を䜜成', async () => {
    // 案件䜜成ペヌゞに遷移
    await page.getByRole('button', { name: '案件を䜜成' }).click();

    // 案件情報を入力しお䜜成
    await projectCreatePage.projectNameInput.fill(projectName);
    await projectCreatePage.sideCreateBtn.click();

    // 案件が䜜成されたこずを確認
    await projectsPage.quickSearchProject(projectName);
    await expect(page.getByRole('rowheader', { name: projectName })).toBeVisible();
  });

  await test.step('案件情報を線集', async () => {
    // 案件詳现画面に遷移
    const projectDetailPage = await projectsPage.gotoProjectDetail(page, projectName);

    // 案件名を曎新
    await projectDetailPage.projectNameEditLink.click();
    await projectDetailPage.projectNameInput.fill(updatedProjectName);
    await page.getByRole('button', { name: '保存' }).click();

    // 曎新が反映されたこずを確認
    await expect(projectDetailPage.headerProjectName).toHaveText(updatedProjectName);
  });
});

3.テストを実斜しおみる

【プロンプト】

${テスト環境}だけで䜜成したテストを実行しおください

【回答】

テストが倱敗したした。

どうやら、ログむン埌の画面遷移先の期埅倀を案件䞀芧ペヌゞずしおいるようです。

実際はこのペヌゞは存圚せず、ログむン埌はトップペヌゞに遷移するこずを期埅倀ずしおいるので、期埅結果を修正したプロンプトを投げおみたす。

【プロンプト】

ログむン埌は、案件䞀芧画面ではなくトップペヌゞに遷移するこずを期埅倀ずしおください。

【回答】

実際のトップペヌゞ䞊には存圚しない「ダッシュボヌド」の文字列を持぀ロケヌタヌを期埅倀ずしおしたいたした。

ざっくりずしたプロンプトを枡すだけだず、完璧に動くテストスクリプトを䞀発で䜜成するのは難しいようです。

テスト倱敗時のスクリヌンショット

AIず察話Page Object/テスト分割䜜成線

先ほどたでは、テストケヌスをプロンプトずしお枡しお、䞀気にテストスクリプトを䜜成しようずしたしたが、うたくいかなかったのでちょっずやり方を倉えお小さめの粒床で怜蚌しおみたす。

各ペヌゞに遷移しお、そのPage Objectを䜜成しおもらうようにしおみたす。

1.テスト環境ぞ遷移

同じようにテスト環境に遷移したす。

【プロンプト】

@https://${テスト環境のドメむン}/signin にアクセスしおください

【回答】

ログむンペヌゞに遷移したした。
たた、ペヌゞ内の芁玠に぀いおも蚀及しおくれおいたす。

2.Page Objectの䜜成

ペヌゞ内に衚瀺されおいる芁玠を矅列しおくれたので、ロケヌタヌをたずめお取埗させおみたした。

【プロンプト】

このペヌゞのペヌゞオブゞェクトを䜜成しおください

【回答】

以䞋のようなPageクラスを䜜成しおくれたした。

ロケヌタヌも実際の画面で䜿えるものが取埗できおいお、メ゜ッドも比范的簡単なものですが汎甚的に䜿えるものを甚意しおくれおいたすね。

import { Locator, Page } from '@playwright/test';

/** ログむン画面のペヌゞオブゞェクト */
export class LoginPage {
  readonly page: Page;
  /** メヌルアドレス入力欄 */
  readonly emailField: Locator;
  /** パスワヌド入力欄 */
  readonly passwordField: Locator;
  /** ログむンボタン */
  readonly loginBtn: Locator;
  /** パスワヌドを忘れた方リンク */
  readonly forgotPasswordLink: Locator;
  /** 倖郚アカりントでログむンリンク */
  readonly externalLoginLink: Locator;
  /** 新芏アカりント登録リンク */
  readonly signupLink: Locator;
  /** ログむンでお困りの方リンク */
  readonly helpLink: Locator;
  /** 䌚瀟抂芁リンク */
  readonly companyInfoLink: Locator;
  /** お問い合わせリンク */
  readonly contactLink: Locator;

  constructor(page: Page) {
    this.page = page;
    this.emailField = page.getByPlaceholder('mail@example.com');
    this.passwordField = page.getByPlaceholder('8文字以䞊半角英数字');
    this.loginBtn = page.getByRole('button', { name: 'ログむンする', exact: true });
    this.forgotPasswordLink = page.getByRole('link', { name: 'パスワヌドを忘れた方' });
    this.externalLoginLink = page.getByRole('link', { name: '倖郚アカりントでログむン' });
    this.signupLink = page.getByRole('link', { name: '新芏アカりント登録無料' });
    this.helpLink = page.getByRole('link', { name: 'ログむンでお困りの方' });
    this.companyInfoLink = page.getByRole('link', { name: '䌚瀟抂芁' });
    this.contactLink = page.getByRole('link', { name: 'お問い合わせ' });
  }

  /**
   * ログむン画面に遷移する
   */
  async goTo() {
    await this.page.goto('/signin');
  }

  /**
   * メヌルアドレスずパスワヌドを入力し、ログむンする
   * @param email メヌルアドレス
   * @param password パスワヌド
   */
  async logIn(email: string, password: string) {
    await this.emailField.fill(email);
    await this.passwordField.fill(password);
    await this.loginBtn.click();
  }

  /**
   * パスワヌドリセットペヌゞに遷移する
   */
  async goToPasswordReset() {
    await this.forgotPasswordLink.click();
  }

  /**
   * 倖郚アカりントログむンペヌゞに遷移する
   */
  async goToExternalLogin() {
    await this.externalLoginLink.click();
  }

  /**
   * 新芏アカりント登録ペヌゞに遷移する
   */
  async goToSignup() {
    await this.signupLink.click();
  }

  /**
   * ヘルプペヌゞに遷移する
   */
  async goToHelp() {
    await this.helpLink.click();
  }
}

3.テストをケヌスをプロンプトずしお枡す

先ほどより簡単なケヌスで、以䞋のようなシナリオを想定しおプロンプトを枡しおみたした。

  1. ログむンペヌゞに遷移
  2. 䌚瀟抂芁に遷移

【プロンプト】

䌚瀟抂芁に遷移するテストシナリオを䜜成しお

【回答】

import { test, expect } from '@playwright/test';
import { LoginPage } from '../../pages/loginPage';

test.describe('ログむンペヌゞからの䌚瀟抂芁遷移テスト', () => {
  let loginPage: LoginPage;

  test.beforeEach(async ({ page }) => {
    loginPage = new LoginPage(page);
    await loginPage.goTo();
  });

  test('䌚瀟抂芁ペヌゞに正垞に遷移できる', async ({ page, context }) => {
    // 新しいタブでの遷移を埅ち受ける Promise を䜜成
    const popupPromise = page.waitForEvent('popup');

    // 䌚瀟抂芁リンクをクリック
    await loginPage.companyInfoLink.click();

    let targetPage: any;
    try {
      // 新しいタブが開かれるのを埅぀1秒でタむムアりト
      targetPage = await popupPromise.then(
        (popup) => popup,
        () => page,
      );
    } catch {
      // タむムアりトした堎合は珟圚のペヌゞを䜿甚
      targetPage = page;
    }

    // ペヌゞの読み蟌みを埅぀
    await targetPage.waitForLoadState();

    // URLが正しいこずを確認
    expect(targetPage.url()).toBe('https://aldagram.com/');

    // ペヌゞタむトルを確認
    expect(await targetPage.title()).toBe('株匏䌚瀟アルダグラム');

    // 䌚瀟の所圚地情報が衚瀺されおいるこずを確認
    await expect(targetPage.getByText('東京郜枯区芝浊1䞁目1-1')).toBeVisible();
    await expect(targetPage.getByText('浜束町ビルディング11階')).toBeVisible();
  });

  test('䌚瀟抂芁リンクが衚瀺されおいる', async () => {
    // 䌚瀟抂芁リンクが衚瀺されおいるこずを確認
    await expect(loginPage.companyInfoLink).toBeVisible();

    // リンクのテキストが正しいこずを確認
    await expect(loginPage.companyInfoLink).toHaveText('䌚瀟抂芁');
  });
});

4.テスト実斜

【プロンプト】

テストを${テスト環境}だけで実行しおください。
たた、ブラりザを立ち䞊げたモヌドで実行しおください

【結果】

以䞋のような゚ラヌが出たした。

どうやら新芏タブで開かれるこずを期埅倀ずしおいたようですが、実際は同タブで画面遷移するようですね。

Running 2 tests using 2 workers
[1/2] [xxxx] › tests/signIn/companyInfo.spec.ts:31:7 › ログむンペヌゞからの䌚瀟抂芁遷移テスト › 䌚瀟
[2/2] [xxxx] › tests/signIn/companyInfo.spec.ts:12:7 › ログむンペヌゞからの䌚瀟抂芁
  1) [xxxx] › tests/signIn/companyInfo.spec.ts:12:7 › ログむンペヌゞからの䌚瀟抂芁遷移テスト › 䌚瀟抂芁ペヌゞに正垞に遷移できる ───

    TimeoutError: page.waitForEvent: Timeout 10000ms exceeded while waiting for event "popup"
    =========================== logs ===========================
    waiting for event "popup"
    ============================================================

      15 |
      16 |     // 新しいタブで開かれるこずを確認
    > 17 |     const newPage = await page.waitForEvent('popup');
         |                                ^
      18 |     await newPage.waitForLoadState();
      19 |
      20 |     // URLが正しいこずを確認

        at /Users/chibakoji/Desktop/work/kanna-playwright/tests/signIn/companyInfo.spec.ts:17:32

5.スクリプトの修正ず再実行

先ほどのスクリプトでぱラヌになっおしたったのですが、ここで終わらないのがPlaywright-mcpです。

自動的に、゚ラヌ内容を把握しお自動でスクリプトを修正しおくれたした

修正したテストケヌスを再実行し、今床は成功です

だいぶ長い道のりでしたが、なんずか動くものをプロンプトだけで䜜成できたした。

5.たずめ

怜蚌結果

簡単に怜蚌結果をたずめたす。

  • Playwright-mcpを䜿うこずで、Playwrightの知芋がなくおも、ある皋床簡単なテストケヌスであればテストスクリプトを実装するこずができる
  • テストケヌスを䞀発で実装させるこずは難しい
  • ペヌゞクラスのロケヌタヌの取埗やメ゜ッドの䜜成などの土台䜜成を、ある䞀定は任せるこずができる

結論

ただE2E自動テストスクリプト䜜成の補助的な領域をでないですが、初めおのテスト䜜成やペヌゞクラスの生成ずいった郚分的な箇所では利甚䟡倀があるず思いたす

ただただ、さたざたなプロンプトで怜蚌する䜙地があるず思いたすし、Playwright-mcpも今埌アップデヌトされおいくかず思いたすので、今埌も匕き続きAI掻甚したE2Eテスト自動化に取り組んでいきたいず思いたす

もっずアルダグラム゚ンゞニア組織を知りたい人、ぜひ䞋蚘の情報をチェックしおみおください

アルダグラム Tech Blog

Discussion