AIコーディング 2026.07.23

PlaywrightとChatGPT APIでAI自動テストケース生成——エンドツーエンドテスト自動化【2026年版】

タグ:Playwright / ChatGPT API / Function Calling / テスト自動化 / エンドツーエンドテスト

PlaywrightとChatGPT APIで実現するAI自動テスト生成

従来のエンドツーエンド(E2E)テストは手作業でテストケースを作成し、メンテナンスに大きな負担がかかっていました。ChatGPT APIのFunction Calling機能を組み合わせることで、AIがアプリケーションの画面情報を理解し、自動的にPlaywrightのテストシナリオを生成できるようになりました。

この方法を使うと、テストケース作成の時間を最大70%削減でき、新機能追加時のテスト追加も自動化できます。

ChatGPT APIのFunction Callingとは

Function Calling(ファンクションコーリング)は、ChatGPT APIがユーザーの指示に基づいて特定の関数を呼び出すよう指示される仕組みです。APIが単に文字列を返すのではなく、「このタスクを実行するにはこの関数を呼べ」という指示を構造化データで返します。

Agentアーキテクチャの基本的な要素の一つとして、Function Callingはツール連携を実現する仕組みです。テストケース生成の文脈では、AIエージェントが次のようなプロセスを自動実行します:

  1. ウェブアプリケーションのHTML構造をプレーンテキストで取得
  2. ChatGPT APIにHTML情報を送信
  3. APIが「クリック」「入力」「待機」などのPlaywright操作関数を生成
  4. 生成されたテストコードを自動実行

この方式により、手作業でテストコードを書く代わりに、AIが画面の状態を理解して適切なテスト操作を提案できるようになります。

必要な環境とインストール

前提条件

  • Node.js 18以上
  • OpenAI APIキー(ChatGPT API使用権)
  • テスト対象のウェブアプリケーション

パッケージのインストール

Playwrightとopenai(公式SDK)をインストールします:

npm install playwright openai dotenv
npm install --save-dev typescript @types/node ts-node

動作確認済みバージョン:

  • Playwright: 1.45以上
  • openai: 4.30以上
  • Node.js: 18.17.0以上

環境変数の設定

プロジェクトルートに.envファイルを作成します:

OPENAI_API_KEY=sk-xxxxxxxxxxxxxx
TEST_TARGET_URL=http://localhost:3000

OpenAI APIキーはOpenAI公式ダッシュボードから取得できます。

AIテストケース自動生成の実装

ステップ1:ページのHTML構造を取得するモジュール

Playwrightでテスト対象ページを開き、HTML構造とテキストコンテンツを抽出します:

import { chromium } from 'playwright';

async function getPageStructure(url: string): Promise<string> {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  
  await page.goto(url);
  
  // ページ全体のHTML取得
  const htmlContent = await page.content();
  
  // 見やすいテキスト表現に変換(AIが理解しやすくするため)
  const pageText = await page.innerText('body');
  
  await browser.close();
  
  return `HTML:\n${htmlContent}\n\nPage Text:\n${pageText}`;
}

このモジュールはPlaywrightでページを読み込んだ後、HTMLとテキストの両方をChatGPT APIに送信するための形式に整形します。

ステップ2:ChatGPT APIのFunction定義

AIに「テストケース生成関数」を認識させるために、Function定義をOpenAI APIに送信します:

import { OpenAI } from 'openai';

const client = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

const testGenerationTools = [
  {
    type: 'function',
    function: {
      name: 'generate_playwright_test',
      description: 'Playwrightのテストケースコードを生成します。ページ構造を分析し、ユーザーシナリオに基づいたテストステップを作成します。',
      parameters: {
        type: 'object',
        properties: {
          test_name: {
            type: 'string',
            description: 'テストの名前(例:「ログインフローのテスト」)',
          },
          scenario: {
            type: 'string',
            description: 'テストシナリオの説明(ユーザーが何をするのか)',
          },
          playwright_steps: {
            type: 'array',
            items: {
              type: 'object',
              properties: {
                action: {
                  type: 'string',
                  enum: ['click', 'fill', 'wait', 'verify', 'navigate'],
                  description: 'Playwrightのアクションタイプ',
                },
                selector: {
                  type: 'string',
                  description: 'CSSセレクタまたはXPath',
                },
                value: {
                  type: 'string',
                  description: 'fill時の入力値またはwait時の待機時間(ミリ秒)',
                },
                description: {
                  type: 'string',
                  description: 'このステップが何をするかの説明',
                },
              },
              required: ['action', 'selector', 'description'],
            },
          },
          expected_result: {
            type: 'string',
            description: 'テスト成功時の期待結果',
          },
        },
        required: ['test_name', 'scenario', 'playwright_steps', 'expected_result'],
      },
    },
  },
];

このFunction定義により、ChatGPT APIは単なる文字列ではなく、構造化されたテストステップを返すようになります。

ステップ3:AIにテスト生成を依頼するメイン処理

async function generateTestCase(
  pageStructure: string,
  testScenario: string
): Promise<any> {
  const messages = [
    {
      role: 'user',
      content: `以下のウェブページのHTML構造を分析して、Playwrightのテストケースを生成してください。\n\nテストシナリオ:${testScenario}\n\nページ情報:\n${pageStructure}`,
    },
  ];

  const response = await client.chat.completions.create({
    model: 'gpt-4',
    messages: messages as any,
    tools: testGenerationTools as any,
    tool_choice: 'auto',
  });

  // Function Call結果を抽出
  if (response.choices[0].message.tool_calls) {
    const toolCall = response.choices[0].message.tool_calls[0];
    if (toolCall.function.name === 'generate_playwright_test') {
      return JSON.parse(toolCall.function.arguments);
    }
  }

  return null;
}

このコードでは、gpt-4モデルを使用して、ページの構造とテストシナリオを分析させます。APIはtool_calls配列で、実行すべき関数と その引数を返します。

ステップ4:生成されたテストコードをPlaywrightで実行

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

async function buildAndRunTest(
  generatedTestSpec: any,
  targetUrl: string
): Promise<void> {
  test(generatedTestSpec.test_name, async ({ page }) => {
    await page.goto(targetUrl);

    for (const step of generatedTestSpec.playwright_steps) {
      switch (step.action) {
        case 'click':
          await page.click(step.selector);
          console.log(`✓ Clicked: ${step.description}`);
          break;

        case 'fill':
          await page.fill(step.selector, step.value);
          console.log(`✓ Filled: ${step.description}`);
          break;

        case 'wait':
          await page.waitForTimeout(parseInt(step.value));
          console.log(`✓ Waited: ${step.description}`);
          break;

        case 'navigate':
          await page.goto(step.value);
          console.log(`✓ Navigated: ${step.description}`);
          break;

        case 'verify':
          const text = await page.innerText(step.selector);
          expect(text).toContain(step.value);
          console.log(`✓ Verified: ${step.description}`);
          break;
      }
    }

    console.log(`✅ Test passed: ${generatedTestSpec.expected_result}`);
  });
}

各ステップをswitchで分岐させることで、ChatGPT APIが生成した操作を忠実に再現します。

実装の全体フロー

import * as dotenv from 'dotenv';

dotenv.config();

async function main() {
  const targetUrl = process.env.TEST_TARGET_URL || 'http://localhost:3000';
  const testScenario = 'ユーザーがログインフォームに日本語メールアドレスを入力し、パスワードを入力してログインボタンをクリックする';

  try {
    // ステップ1:ページ構造を取得
    console.log('📄 ページ構造を取得中...');
    const pageStructure = await getPageStructure(targetUrl);

    // ステップ2:AIにテスト生成を依頼
    console.log('🤖 AIがテストケースを生成中...');
    const generatedTest = await generateTestCase(pageStructure, testScenario);

    if (!generatedTest) {
      console.error('❌ テスト生成に失敗しました');
      return;
    }

    console.log('✅ 生成されたテストケース:');
    console.log(JSON.stringify(generatedTest, null, 2));

    // ステップ3:生成されたテストを実行
    console.log('🚀 テストを実行中...');
    await buildAndRunTest(generatedTest, targetUrl);
  } catch (error) {
    console.error('エラーが発生しました:', error);
  }
}

main();

このメイン処理を実行することで、ページからテスト生成、テスト実行までが自動で行われます。

つまずきやすいポイントと解決策

セレクタが正確に取得できない場合

ChatGPT APIが生成するセレクタが実際のページと一致しないことがあります。対策として、ページのHTML構造をより詳しく提供します:

async function getDetailedPageStructure(url: string): Promise<string> {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  
  await page.goto(url);
  
  // フォーム要素などの詳細情報を抽出
  const elements = await page.evaluate(() => {
    const items: any[] = [];
    document.querySelectorAll('input, button, a, select, textarea').forEach((el) => {
      items.push({
        type: el.tagName,
        id: el.id,
        name: (el as any).name,
        placeholder: (el as any).placeholder,
        text: el.textContent?.trim(),
        ariaLabel: el.getAttribute('aria-label'),
        cssSelector: el.className,
      });
    });
    return items;
  });

  await browser.close();

  return JSON.stringify(elements, null, 2);
}

このようにHTML要素の属性情報を明示的に提供することで、AIがより正確なセレクタを生成できます。

API呼び出しコストの管理

ChatGPT API(特にgpt-4)は使用量に応じて課金されます。トークン数を削減するには:

// ページ構造を圧縮して送信
const compressedStructure = pageStructure
  .replace(/\n\n+/g, '\n')  // 複数の改行を1つに統一
  .substring(0, 3000);       // 最初の3000文字に制限

const response = await client.chat.completions.create({
  model: 'gpt-3.5-turbo',  // gpt-4より安価
  messages: messages as any,
  tools: testGenerationTools as any,
  max_tokens: 1000,  // 出力トークンを制限
});

本番運用では、テストシナリオごとに異なるモデルを使い分けるのが効果的です。

非同期処理のタイムアウト

Playwrightのページ読み込みやAPI呼び出しがタイムアウトする場合:

async function getPageStructureWithTimeout(
  url: string,
  timeoutMs: number = 15000
): Promise<string> {
  const timeoutPromise = new Promise<string>((_, reject) =>
    setTimeout(() => reject(new Error('ページ読み込みタイムアウト')), timeoutMs)
  );

  const pagePromise = getPageStructure(url);
  return Promise.race([pagePromise, timeoutPromise]);
}

応用:複数シナリオの一括テスト生成

実務では、複数のテストシナリオを一度に処理する必要があります:

const testScenarios = [
  'ユーザーが新規登録フォームを入力してアカウントを作成する',
  'ログイン済みユーザーが商品を検索して購入する',
  'ユーザーがプロフィール画面でメールアドレスを変更する',
];

async function generateAllTests(
  pageUrl: string,
  scenarios: string[]
): Promise<any[]> {
  const pageStructure = await getPageStructure(pageUrl);
  const results = [];

  for (const scenario of scenarios) {
    try {
      const testSpec = await generateTestCase(pageStructure, scenario);
      if (testSpec) {
        results.push(testSpec);
      }
    } catch (error) {
      console.error(`シナリオ生成失敗: ${scenario}`);
    }
  }

  return results;
}

複数シナリオを処理する場合、API呼び出しを順序実行(1つずつ実行)することで、レート制限に引っかかるのを防げます。

CI/CDパイプラインへの統合

GitHub Actionsなどのパイプラインに組み込む場合の例:

name: AI-Generated E2E Tests

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      
      - run: npm install
      
      - name: Start application
        run: npm run dev &
        
      - name: Wait for app startup
        run: sleep 10
        
      - name: Generate and run AI tests
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          TEST_TARGET_URL: http://localhost:3000
        run: npx ts-node src/testGenerator.ts

このパイプラインにより、毎回のコミット時にAIがテストケースを自動生成・実行できます。

次のステップ:テスト結果の自動分析

生成されたテスト結果をさらにAIで分析することで、バグパターンの自動検出も可能です。Function Callingを用いて、失敗したテストの原因を自動診断するなど、さらなる自動化が期待できます。

このアプローチにより、テスト作成から実行、分析までをAIが支援し、開発チームはビジネスロジックの改善に集中できるようになります。


あわせて読みたい

参考ソース