T-CREATOR

Playwright MCP の導入手順と初期セットアップ完全ガイド

Playwright MCP の導入手順と初期セットアップ完全ガイド

Playwright の自動化テストにおいて、AI との連携がますます重要になってきました。Model Context Protocol(MCP)は、この連携を革新的に進化させる新しい技術です。

従来のテスト自動化では、テストケースの作成から実行、結果の解析まで、すべて人間が手動で行う必要がありました。しかし、MCP を活用することで、AI がテストの設計から実行、さらには結果の分析まで支援してくれるようになります。

この記事では、Playwright と MCP を組み合わせた最新の自動化環境の構築方法を、初心者の方でも理解できるよう詳しく解説していきます。実際のエラーコードや対処法も含めて、確実にセットアップできるガイドとなっています。

MCP(Model Context Protocol)とは

MCP の基本概念

Model Context Protocol(MCP)は、AI モデルと外部のツールやリソースを安全かつ効率的に連携させるためのオープンスタンダードプロトコルです。2024 年に Anthropic によって発表され、AI アプリケーションの新しい可能性を切り開いています。

MCP の主な特徴は以下の通りです:

typescript// MCP の基本的な通信構造
interface MCPMessage {
  jsonrpc: '2.0';
  method: string;
  params?: any;
  id?: string | number;
}

// リソースアクセスの例
interface MCPResource {
  uri: string;
  name: string;
  description?: string;
  mimeType?: string;
}

MCP が解決する課題

従来の課題MCP による解決
AI とツールの個別統合が必要標準化されたプロトコルで統一
セキュリティリスクの管理が複雑安全な権限管理機能を内蔵
スケーラビリティの限界分散アーキテクチャで拡張性を確保
開発コストの増大再利用可能なコンポーネント設計

Playwright × MCP の可能性

Playwright と MCP を組み合わせることで、これまでにない自動化の体験が実現できます。

AI 支援によるテスト生成

typescript// MCP 経由での AI 支援テスト生成例
const mcpClient = new MCPClient({
  serverUrl: 'http://localhost:3000',
  capabilities: ['playwright-automation'],
});

// AI にテストシナリオの生成を依頼
const testScenario = await mcpClient.generateTest({
  target: 'ecommerce-checkout',
  requirements: [
    '商品をカートに追加',
    '配送先情報を入力',
    '決済処理を完了',
  ],
});

動的テストケースの調整

typescript// 実行時にテストケースを AI が調整
test('動的 E2E テスト', async ({ page }) => {
  const context = await mcpClient.analyzePageContext(page);

  // AI が現在のページ状態を分析してテストを最適化
  const optimizedSteps = await mcpClient.optimizeTestSteps({
    currentContext: context,
    originalSteps: baseTestSteps,
  });

  for (const step of optimizedSteps) {
    await executeStep(page, step);
  }
});

従来の自動化との違い

従来のアプローチ

typescript// 従来の固定的なテストケース
test('従来のログインテスト', async ({ page }) => {
  await page.goto('/login');
  await page.fill('#username', 'testuser');
  await page.fill('#password', 'password123');
  await page.click('#login-button');
  await expect(page).toHaveURL('/dashboard');
});

MCP 統合アプローチ

typescript// MCP による AI 支援テスト
test('AI 支援ログインテスト', async ({ page }) => {
  const loginStrategy = await mcpClient.analyzeLoginForm(
    page
  );

  // AI が最適なログイン戦略を提案
  await mcpClient.executeLoginSequence({
    page,
    strategy: loginStrategy,
    credentials: await mcpClient.getTestCredentials(),
  });

  // 結果を AI が検証
  const verificationResult =
    await mcpClient.verifyLoginSuccess(page);
  expect(verificationResult.success).toBe(true);
});

事前準備

必要な環境とツール

Playwright MCP を導入するために、以下の環境とツールが必要です。

Node.js の要件

バージョン対応状況推奨度備考
Node.js 18.x必須MCP サーバーの最小要件
Node.js 20.x推奨最新機能とパフォーマンス
Node.js 22.x対応実験的機能を含む

パッケージマネージャー

bash# Yarn の確認(推奨)
yarn --version

# npm の場合(代替手段)
npm --version

# pnpm の場合(高速パッケージ管理)
pnpm --version

開発環境の確認コマンド

bash# システム情報の確認
node --version
yarn --version
git --version

# メモリとディスク容量の確認
free -h  # Linux/macOS
dir    # Windows

依存関係の確認

MCP 環境では、以下の依存関係が重要です:

json{
  "engines": {
    "node": ">=18.0.0"
  },
  "dependencies": {
    "@playwright/test": "^1.40.0",
    "@anthropic-ai/mcp-client": "^0.1.0",
    "@anthropic-ai/mcp-server": "^0.1.0",
    "playwright": "^1.40.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "typescript": "^5.0.0"
  }
}

依存関係の競合チェック

bash# パッケージの競合確認
yarn why @playwright/test
yarn audit

# 古いキャッシュのクリア
yarn cache clean
rm -rf node_modules yarn.lock
yarn install

推奨開発環境

Visual Studio Code の設定

json// .vscode/settings.json
{
  "typescript.preferences.includePackageJsonAutoImports": "on",
  "playwright.reuseBrowser": true,
  "playwright.showTrace": true,
  "mcp.enableIntelliSense": true,
  "mcp.serverAutoStart": true
}

必須の VS Code 拡張機能

拡張機能名用途重要度
Playwright Test for VSCodeテスト実行とデバッグ必須
MCP Protocol SupportMCP 構文サポート必須
TypeScript Importer自動インポート推奨
Error Lensエラー表示強化推奨

Playwright MCP の導入手順

MCP サーバーのセットアップ

まず、MCP サーバーを構築します。これが AI との通信の中核となります。

サーバープロジェクトの初期化

bash# 新しいプロジェクトディレクトリを作成
mkdir playwright-mcp-project
cd playwright-mcp-project

# package.json の初期化
yarn init -y

# 必要なパッケージのインストール
yarn add @anthropic-ai/mcp-server @anthropic-ai/mcp-client
yarn add -D typescript @types/node ts-node

MCP サーバーの基本設定

typescript// src/mcp-server.ts
import { MCPServer } from '@anthropic-ai/mcp-server';
import { PlaywrightTool } from './tools/playwright-tool';

class PlaywrightMCPServer extends MCPServer {
  constructor() {
    super({
      name: 'playwright-automation-server',
      version: '1.0.0',
      capabilities: {
        tools: true,
        resources: true,
        prompts: true,
      },
    });

    this.registerTool(new PlaywrightTool());
  }

  async initialize() {
    try {
      await this.start();
      console.log('MCP サーバーが正常に起動しました');
    } catch (error) {
      console.error('MCP サーバーの起動に失敗:', error);
      throw error;
    }
  }
}

export default PlaywrightMCPServer;

よくあるサーバー起動エラーと対処法

bash# エラー: EADDRINUSE: address already in use :::3000
Error: listen EADDRINUSE: address already in use :::3000
    at Server.setupListenHandle [as _listen2]

対処法:

bash# ポートを使用しているプロセスを確認
lsof -i :3000
# または
netstat -tulpn | grep 3000

# プロセスを終了
kill -9 <PID>

# 別のポートを使用
export MCP_SERVER_PORT=3001

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

bash# Playwright と MCP 関連パッケージの追加
yarn add @playwright/test playwright
yarn add @anthropic-ai/mcp-client
yarn add -D @types/jest typescript

# ブラウザのインストール
yarn playwright install

インストール中のよくあるエラー

bash# エラー: Failed to download Chromium
Error: Failed to download Chromium r1095492
    at DownloadError.BrowserFetcher.downloadRevision

対処法:

bash# プロキシ環境の場合
export HTTPS_PROXY=http://proxy.company.com:8080
export HTTP_PROXY=http://proxy.company.com:8080

# 直接ダウンロード
yarn playwright install chromium --force

# 権限エラーの場合
sudo yarn playwright install

設定ファイルの作成

Playwright 設定ファイル

typescript// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
import { MCPTestConfig } from './src/config/mcp-config';

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,

  // MCP 統合設定
  globalSetup: './src/setup/mcp-global-setup.ts',
  globalTeardown: './src/setup/mcp-global-teardown.ts',

  use: {
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',

    // MCP クライアント設定
    extraHTTPHeaders: {
      'X-MCP-Client': 'playwright-test',
    },
  },

  projects: [
    {
      name: 'chromium-mcp',
      use: {
        ...devices['Desktop Chrome'],
        // MCP 固有の設定
        contextOptions: {
          recordVideo: {
            dir: 'test-results/videos/',
            size: { width: 1280, height: 720 },
          },
        },
      },
    },
  ],

  webServer: {
    command: 'yarn start:mcp-server',
    port: 3000,
    reuseExistingServer: !process.env.CI,
  },
});

MCP クライアント設定

typescript// src/config/mcp-config.ts
export interface MCPTestConfig {
  serverUrl: string;
  apiKey?: string;
  timeout: number;
  retries: number;
  capabilities: string[];
}

export const defaultMCPConfig: MCPTestConfig = {
  serverUrl:
    process.env.MCP_SERVER_URL || 'http://localhost:3000',
  apiKey: process.env.MCP_API_KEY,
  timeout: 30000,
  retries: 3,
  capabilities: [
    'playwright-automation',
    'test-generation',
    'result-analysis',
  ],
};

// 環境別設定
export const mcpConfigs = {
  development: {
    ...defaultMCPConfig,
    serverUrl: 'http://localhost:3000',
  },
  staging: {
    ...defaultMCPConfig,
    serverUrl: 'https://mcp-staging.example.com',
  },
  production: {
    ...defaultMCPConfig,
    serverUrl: 'https://mcp.example.com',
    timeout: 60000,
  },
};

初期設定と接続確認

MCP サーバーとの接続設定

MCP サーバーとクライアントの接続を確立するための設定を行います。

グローバルセットアップファイル

typescript// src/setup/mcp-global-setup.ts
import { MCPClient } from '@anthropic-ai/mcp-client';
import { defaultMCPConfig } from '../config/mcp-config';

async function globalSetup() {
  console.log('MCP グローバルセットアップを開始...');

  try {
    // MCP サーバーの起動確認
    const client = new MCPClient(defaultMCPConfig);
    await client.connect();

    // 接続テスト
    const serverInfo = await client.getServerInfo();
    console.log('MCP サーバー情報:', serverInfo);

    // 利用可能なツールの確認
    const tools = await client.listTools();
    console.log(
      '利用可能なツール:',
      tools.map((t) => t.name)
    );

    await client.disconnect();
    console.log('MCP グローバルセットアップ完了');
  } catch (error) {
    console.error('MCP セットアップエラー:', error);
    throw new Error(
      `MCP サーバーとの接続に失敗しました: ${error.message}`
    );
  }
}

export default globalSetup;

接続エラーの診断と対処

bash# よくある接続エラー
Error: ECONNREFUSED 127.0.0.1:3000
    at TCPConnectWrap.afterConnect [as oncomplete]

対処法:

bash# MCP サーバーの状態確認
curl -f http://localhost:3000/health
# または
wget -q --spider http://localhost:3000/health

# サーバーログの確認
tail -f logs/mcp-server.log

# ファイアウォール設定の確認
sudo ufw status

認証とセキュリティ設定

API キーの設定

typescript// src/auth/mcp-auth.ts
export class MCPAuth {
  private apiKey: string;

  constructor(apiKey?: string) {
    this.apiKey = apiKey || process.env.MCP_API_KEY || '';

    if (!this.apiKey) {
      throw new Error('MCP_API_KEY が設定されていません');
    }
  }

  getAuthHeaders(): Record<string, string> {
    return {
      Authorization: `Bearer ${this.apiKey}`,
      'X-MCP-Client-Version': '1.0.0',
    };
  }

  async validateToken(): Promise<boolean> {
    try {
      const response = await fetch(
        `${process.env.MCP_SERVER_URL}/auth/validate`,
        {
          headers: this.getAuthHeaders(),
        }
      );
      return response.ok;
    } catch (error) {
      console.error('認証エラー:', error);
      return false;
    }
  }
}

環境変数の設定

bash# .env ファイル
MCP_SERVER_URL=http://localhost:3000
MCP_API_KEY=your-secret-api-key-here
MCP_TIMEOUT=30000
MCP_RETRY_COUNT=3

# セキュリティ設定
MCP_ENABLE_HTTPS=true
MCP_CERT_PATH=/path/to/cert.pem
MCP_KEY_PATH=/path/to/key.pem

動作確認テスト

基本的な接続テスト

typescript// tests/mcp/connection.test.ts
import { test, expect } from '@playwright/test';
import { MCPClient } from '@anthropic-ai/mcp-client';
import { defaultMCPConfig } from '../../src/config/mcp-config';

test.describe('MCP 接続テスト', () => {
  let mcpClient: MCPClient;

  test.beforeAll(async () => {
    mcpClient = new MCPClient(defaultMCPConfig);
    await mcpClient.connect();
  });

  test.afterAll(async () => {
    await mcpClient.disconnect();
  });

  test('MCP サーバーとの接続確認', async () => {
    const serverInfo = await mcpClient.getServerInfo();

    expect(serverInfo.name).toBe(
      'playwright-automation-server'
    );
    expect(serverInfo.version).toBeTruthy();
    expect(serverInfo.capabilities).toContain('tools');
  });

  test('利用可能なツールの確認', async () => {
    const tools = await mcpClient.listTools();

    expect(tools.length).toBeGreaterThan(0);
    expect(
      tools.some(
        (tool) => tool.name === 'playwright-navigate'
      )
    ).toBe(true);
    expect(
      tools.some((tool) => tool.name === 'playwright-click')
    ).toBe(true);
  });

  test('リソースアクセスの確認', async () => {
    const resources = await mcpClient.listResources();

    expect(Array.isArray(resources)).toBe(true);
    console.log(
      '利用可能なリソース:',
      resources.map((r) => r.name)
    );
  });
});

エラーハンドリングテスト

typescript// tests/mcp/error-handling.test.ts
test.describe('MCP エラーハンドリング', () => {
  test('無効な API キーでのエラー処理', async () => {
    const invalidConfig = {
      ...defaultMCPConfig,
      apiKey: 'invalid-key',
    };

    const client = new MCPClient(invalidConfig);

    await expect(client.connect()).rejects.toThrow(
      '認証に失敗しました'
    );
  });

  test('タイムアウトエラーの処理', async () => {
    const timeoutConfig = {
      ...defaultMCPConfig,
      timeout: 100, // 100ms の短いタイムアウト
    };

    const client = new MCPClient(timeoutConfig);

    await expect(client.connect()).rejects.toThrow(
      '接続がタイムアウトしました'
    );
  });

  test('サーバー停止時の処理', async () => {
    // サーバーを意図的に停止
    await mcpClient.callTool('server-shutdown', {});

    // 再接続の試行
    await expect(mcpClient.getServerInfo()).rejects.toThrow(
      'ECONNREFUSED'
    );
  });
});

基本的な MCP 操作の実装

MCP 経由でのテスト実行

AI 支援テストの基本パターン

typescript// tests/mcp/ai-assisted-test.ts
import { test, expect } from '@playwright/test';
import { MCPTestHelper } from '../helpers/mcp-test-helper';

test.describe('AI 支援テスト', () => {
  let mcpHelper: MCPTestHelper;

  test.beforeEach(async ({ page }) => {
    mcpHelper = new MCPTestHelper(page);
    await mcpHelper.initialize();
  });

  test('AI によるフォーム入力テスト', async ({ page }) => {
    await page.goto('/contact');

    // AI にフォームの分析を依頼
    const formAnalysis = await mcpHelper.analyzeForm({
      selector: '#contact-form',
      purpose: 'お問い合わせフォームのテスト',
    });

    console.log('フォーム分析結果:', formAnalysis);

    // AI が提案する入力値でフォームを埋める
    const testData = await mcpHelper.generateTestData({
      formStructure: formAnalysis.fields,
      scenario: 'valid-inquiry',
    });

    for (const [field, value] of Object.entries(testData)) {
      await page.fill(`[name="${field}"]`, value as string);
    }

    // 送信ボタンをクリック
    await page.click('[type="submit"]');

    // AI による結果検証
    const verificationResult =
      await mcpHelper.verifyFormSubmission({
        expectedOutcome: 'success',
        page: page,
      });

    expect(verificationResult.success).toBe(true);
    expect(verificationResult.message).toContain(
      '送信が完了'
    );
  });

  test('動的コンテンツの AI 検証', async ({ page }) => {
    await page.goto('/dashboard');

    // 動的に変化するコンテンツを AI が監視
    const contentMonitor =
      await mcpHelper.startContentMonitoring({
        selector: '.dynamic-content',
        expectedChanges: ['data-update', 'status-change'],
      });

    // 何らかのアクションを実行
    await page.click('#refresh-data');

    // AI がコンテンツの変化を分析
    const changeAnalysis =
      await mcpHelper.analyzeContentChanges({
        monitor: contentMonitor,
        timeout: 10000,
      });

    expect(changeAnalysis.detected).toBe(true);
    expect(changeAnalysis.changes.length).toBeGreaterThan(
      0
    );
  });
});

MCP テストヘルパークラス

typescript// tests/helpers/mcp-test-helper.ts
import { Page } from '@playwright/test';
import { MCPClient } from '@anthropic-ai/mcp-client';
import { defaultMCPConfig } from '../../src/config/mcp-config';

export class MCPTestHelper {
  private mcpClient: MCPClient;

  constructor(private page: Page) {
    this.mcpClient = new MCPClient(defaultMCPConfig);
  }

  async initialize() {
    await this.mcpClient.connect();
  }

  async cleanup() {
    await this.mcpClient.disconnect();
  }

  async analyzeForm(params: {
    selector: string;
    purpose: string;
  }) {
    return await this.mcpClient.callTool('analyze-form', {
      pageUrl: this.page.url(),
      formSelector: params.selector,
      analysisType: 'comprehensive',
      purpose: params.purpose,
    });
  }

  async generateTestData(params: {
    formStructure: any;
    scenario: string;
  }) {
    const result = await this.mcpClient.callTool(
      'generate-test-data',
      {
        formStructure: params.formStructure,
        scenario: params.scenario,
        locale: 'ja',
      }
    );

    return result.testData;
  }

  async verifyFormSubmission(params: {
    expectedOutcome: string;
    page: Page;
  }) {
    return await this.mcpClient.callTool(
      'verify-form-submission',
      {
        pageUrl: params.page.url(),
        expectedOutcome: params.expectedOutcome,
        pageContent: await params.page.content(),
      }
    );
  }
}

リソース管理とツール呼び出し

MCP ツールの実装例

typescript// src/tools/playwright-tool.ts
import { MCPTool } from '@anthropic-ai/mcp-server';
import { Page } from 'playwright';

export class PlaywrightNavigationTool extends MCPTool {
  name = 'playwright-navigate';
  description = 'ページナビゲーションを実行';

  inputSchema = {
    type: 'object',
    properties: {
      url: { type: 'string', description: '遷移先URL' },
      waitUntil: {
        type: 'string',
        enum: ['load', 'domcontentloaded', 'networkidle'],
        default: 'load',
      },
    },
    required: ['url'],
  };

  async execute(params: any, context: { page: Page }) {
    try {
      const { url, waitUntil = 'load' } = params;

      await context.page.goto(url, { waitUntil });

      return {
        success: true,
        currentUrl: context.page.url(),
        title: await context.page.title(),
      };
    } catch (error) {
      return {
        success: false,
        error: error.message,
      };
    }
  }
}

export class PlaywrightElementTool extends MCPTool {
  name = 'playwright-interact';
  description = '要素との相互作用を実行';

  inputSchema = {
    type: 'object',
    properties: {
      action: {
        type: 'string',
        enum: [
          'click',
          'fill',
          'select',
          'check',
          'uncheck',
        ],
      },
      selector: {
        type: 'string',
        description: '要素のセレクター',
      },
      value: {
        type: 'string',
        description: '入力値(fillの場合)',
      },
    },
    required: ['action', 'selector'],
  };

  async execute(params: any, context: { page: Page }) {
    const { action, selector, value } = params;

    try {
      switch (action) {
        case 'click':
          await context.page.click(selector);
          break;
        case 'fill':
          await context.page.fill(selector, value);
          break;
        case 'select':
          await context.page.selectOption(selector, value);
          break;
        case 'check':
          await context.page.check(selector);
          break;
        case 'uncheck':
          await context.page.uncheck(selector);
          break;
      }

      return { success: true, action, selector };
    } catch (error) {
      return {
        success: false,
        error: error.message,
        selector,
      };
    }
  }
}

export class PlaywrightAnalysisTool extends MCPTool {
  name = 'analyze-page';
  description = 'ページの構造とコンテンツを分析';

  inputSchema = {
    type: 'object',
    properties: {
      analysisType: {
        type: 'string',
        enum: [
          'structure',
          'accessibility',
          'performance',
          'seo',
        ],
      },
      target: {
        type: 'string',
        description: '分析対象の要素セレクター',
      },
    },
    required: ['analysisType'],
  };

  async execute(params: any, context: { page: Page }) {
    const { analysisType, target } = params;

    try {
      let result: any = {};

      switch (analysisType) {
        case 'structure':
          result = await this.analyzeStructure(
            context.page,
            target
          );
          break;
        case 'accessibility':
          result = await this.analyzeAccessibility(
            context.page,
            target
          );
          break;
        case 'performance':
          result = await this.analyzePerformance(
            context.page
          );
          break;
        case 'seo':
          result = await this.analyzeSEO(context.page);
          break;
      }

      return { success: true, analysis: result };
    } catch (error) {
      return {
        success: false,
        error: error.message,
      };
    }
  }

  private async analyzeStructure(
    page: Page,
    target?: string
  ) {
    const selector = target || 'body';

    return await page.evaluate((sel) => {
      const element = document.querySelector(sel);
      if (!element) return null;

      const getElementInfo = (el: Element) => ({
        tagName: el.tagName,
        id: el.id,
        className: el.className,
        childCount: el.children.length,
        textContent: el.textContent?.slice(0, 100),
      });

      return {
        target: getElementInfo(element),
        children: Array.from(element.children).map(
          getElementInfo
        ),
      };
    }, selector);
  }

  private async analyzeAccessibility(
    page: Page,
    target?: string
  ) {
    const selector = target || 'body';

    return await page.evaluate((sel) => {
      const element = document.querySelector(sel);
      if (!element) return null;

      const issues: string[] = [];

      // alt 属性のチェック
      element.querySelectorAll('img').forEach((img) => {
        if (!img.getAttribute('alt')) {
          issues.push(
            `画像に alt 属性がありません: ${img.src}`
          );
        }
      });

      // ラベルのチェック
      element
        .querySelectorAll('input, select, textarea')
        .forEach((input) => {
          const id = input.getAttribute('id');
          if (
            id &&
            !document.querySelector(`label[for="${id}"]`)
          ) {
            issues.push(
              `入力要素にラベルがありません: ${id}`
            );
          }
        });

      return {
        issues,
        checkedElements:
          element.querySelectorAll('*').length,
      };
    }, selector);
  }

  private async analyzePerformance(page: Page) {
    const metrics = await page.evaluate(() => {
      const navigation = performance.getEntriesByType(
        'navigation'
      )[0] as PerformanceNavigationTiming;

      return {
        loadTime:
          navigation.loadEventEnd -
          navigation.loadEventStart,
        domContentLoaded:
          navigation.domContentLoadedEventEnd -
          navigation.domContentLoadedEventStart,
        firstPaint:
          performance.getEntriesByName('first-paint')[0]
            ?.startTime || 0,
        firstContentfulPaint:
          performance.getEntriesByName(
            'first-contentful-paint'
          )[0]?.startTime || 0,
      };
    });

    return metrics;
  }

  private async analyzeSEO(page: Page) {
    return await page.evaluate(() => {
      const title = document.title;
      const metaDescription =
        document
          .querySelector('meta[name="description"]')
          ?.getAttribute('content') || '';
      const h1Count =
        document.querySelectorAll('h1').length;
      const imgWithoutAlt = document.querySelectorAll(
        'img:not([alt])'
      ).length;

      return {
        title: { value: title, length: title.length },
        metaDescription: {
          value: metaDescription,
          length: metaDescription.length,
        },
        h1Count,
        imgWithoutAlt,
        recommendations: [
          title.length > 60
            ? 'タイトルが長すぎます(60文字以下推奨)'
            : null,
          metaDescription.length === 0
            ? 'メタディスクリプションが設定されていません'
            : null,
          h1Count !== 1
            ? `H1タグは1つにしてください(現在: ${h1Count}個)`
            : null,
          imgWithoutAlt > 0
            ? `${imgWithoutAlt}個の画像にalt属性がありません`
            : null,
        ].filter(Boolean),
      };
    });
  }
}

プロンプトテンプレートの活用

テスト生成用プロンプト

typescript// src/prompts/test-generation.ts
export const testGenerationPrompts = {
  formTesting: {
    name: 'form-test-generator',
    description: 'フォームテストケースの生成',
    template: `
以下のフォーム構造に基づいて、包括的なテストケースを生成してください:

フォーム情報:
- URL: {{url}}
- フィールド: {{fields}}
- バリデーションルール: {{validation}}

生成するテストケース:
1. 正常系テスト(有効なデータでの送信)
2. 異常系テスト(無効なデータでのエラー確認)
3. 境界値テスト(最大・最小値での動作確認)
4. セキュリティテスト(XSS、SQLインジェクション対策)

出力形式:TypeScript + Playwright のテストコード
    `,
    variables: ['url', 'fields', 'validation'],
  },

  e2eScenario: {
    name: 'e2e-scenario-generator',
    description: 'E2Eシナリオの生成',
    template: `
以下の要件に基づいて、E2Eテストシナリオを生成してください:

アプリケーション情報:
- 種類: {{appType}}
- 主要機能: {{features}}
- ユーザーフロー: {{userFlow}}

考慮事項:
- ユーザビリティ
- パフォーマンス
- セキュリティ
- アクセシビリティ

出力:実行可能なPlaywrightテストコード
    `,
    variables: ['appType', 'features', 'userFlow'],
  },

  testDataGeneration: {
    name: 'test-data-generator',
    description: 'テストデータの生成',
    template: `
以下の条件に基づいて、テストデータを生成してください:

データ要件:
- フィールド定義: {{fieldDefinitions}}
- データパターン: {{dataPattern}}
- ロケール: {{locale}}

生成パターン:
- 有効データ: 正常系テスト用
- 無効データ: エラーハンドリングテスト用  
- 境界値データ: エッジケーステスト用
- ランダムデータ: ストレステスト用

出力形式:JSON形式のテストデータセット
    `,
    variables: [
      'fieldDefinitions',
      'dataPattern',
      'locale',
    ],
  },
};

プロンプト実行ヘルパー

typescript// src/helpers/prompt-helper.ts
import { MCPClient } from '@anthropic-ai/mcp-client';
import { testGenerationPrompts } from '../prompts/test-generation';

export class PromptHelper {
  constructor(private mcpClient: MCPClient) {}

  async generateFormTest(params: {
    url: string;
    fields: any[];
    validation: any;
  }) {
    const prompt = testGenerationPrompts.formTesting;

    const response = await this.mcpClient.executePrompt({
      name: prompt.name,
      variables: {
        url: params.url,
        fields: JSON.stringify(params.fields, null, 2),
        validation: JSON.stringify(
          params.validation,
          null,
          2
        ),
      },
    });

    return response.content;
  }

  async generateE2EScenario(params: {
    appType: string;
    features: string[];
    userFlow: string[];
  }) {
    const prompt = testGenerationPrompts.e2eScenario;

    const response = await this.mcpClient.executePrompt({
      name: prompt.name,
      variables: {
        appType: params.appType,
        features: params.features.join(', '),
        userFlow: params.userFlow.join(' -> '),
      },
    });

    return response.content;
  }

  async generateTestData(params: {
    fieldDefinitions: any[];
    dataPattern: string;
    locale: string;
  }) {
    const prompt = testGenerationPrompts.testDataGeneration;

    const response = await this.mcpClient.executePrompt({
      name: prompt.name,
      variables: {
        fieldDefinitions: JSON.stringify(
          params.fieldDefinitions,
          null,
          2
        ),
        dataPattern: params.dataPattern,
        locale: params.locale,
      },
    });

    try {
      return JSON.parse(response.content);
    } catch (error) {
      console.error('テストデータのパースに失敗:', error);
      return null;
    }
  }
}

実践的なセットアップ例

CI/CD パイプライン統合

GitHub Actions での設定

yaml# .github/workflows/playwright-mcp.yml
name: Playwright MCP Tests

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

jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest

    services:
      mcp-server:
        image: mcp-server:latest
        ports:
          - 3000:3000
        env:
          MCP_API_KEY: ${{ secrets.MCP_API_KEY }}
          NODE_ENV: test
        options: >-
          --health-cmd "curl -f http://localhost:3000/health"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'yarn'

      - name: Install dependencies
        run: yarn install --frozen-lockfile

      - name: Install Playwright Browsers
        run: yarn playwright install --with-deps

      - name: Wait for MCP Server
        run: |
          timeout 60 bash -c 'until curl -f http://localhost:3000/health; do sleep 2; done'

      - name: Run Playwright tests
        run: yarn playwright test
        env:
          MCP_SERVER_URL: http://localhost:3000
          MCP_API_KEY: ${{ secrets.MCP_API_KEY }}

      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 30

      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: mcp-logs
          path: logs/
          retention-days: 7

Docker Compose での環境構築

yaml# docker-compose.yml
version: '3.8'

services:
  mcp-server:
    build:
      context: .
      dockerfile: Dockerfile.mcp-server
    ports:
      - '3000:3000'
    environment:
      - NODE_ENV=development
      - MCP_API_KEY=${MCP_API_KEY}
      - MCP_LOG_LEVEL=debug
    volumes:
      - ./logs:/app/logs
      - ./config:/app/config
    healthcheck:
      test:
        [
          'CMD',
          'curl',
          '-f',
          'http://localhost:3000/health',
        ]
      interval: 10s
      timeout: 5s
      retries: 5

  playwright-tests:
    build:
      context: .
      dockerfile: Dockerfile.playwright
    depends_on:
      mcp-server:
        condition: service_healthy
    environment:
      - MCP_SERVER_URL=http://mcp-server:3000
      - MCP_API_KEY=${MCP_API_KEY}
    volumes:
      - ./test-results:/app/test-results
      - ./playwright-report:/app/playwright-report
    command: yarn playwright test

環境別設定管理

設定管理システム

typescript// src/config/environment-config.ts
interface EnvironmentConfig {
  mcp: {
    serverUrl: string;
    apiKey: string;
    timeout: number;
  };
  playwright: {
    headless: boolean;
    slowMo: number;
    video: boolean;
  };
  test: {
    retries: number;
    parallel: boolean;
    timeout: number;
  };
}

const configs: Record<string, EnvironmentConfig> = {
  development: {
    mcp: {
      serverUrl: 'http://localhost:3000',
      apiKey: process.env.MCP_API_KEY || 'dev-key',
      timeout: 30000,
    },
    playwright: {
      headless: false,
      slowMo: 1000,
      video: true,
    },
    test: {
      retries: 0,
      parallel: false,
      timeout: 60000,
    },
  },

  staging: {
    mcp: {
      serverUrl: 'https://mcp-staging.example.com',
      apiKey: process.env.MCP_API_KEY_STAGING!,
      timeout: 45000,
    },
    playwright: {
      headless: true,
      slowMo: 0,
      video: false,
    },
    test: {
      retries: 2,
      parallel: true,
      timeout: 90000,
    },
  },

  production: {
    mcp: {
      serverUrl: 'https://mcp.example.com',
      apiKey: process.env.MCP_API_KEY_PROD!,
      timeout: 60000,
    },
    playwright: {
      headless: true,
      slowMo: 0,
      video: false,
    },
    test: {
      retries: 3,
      parallel: true,
      timeout: 120000,
    },
  },
};

export function getConfig(): EnvironmentConfig {
  const env = process.env.NODE_ENV || 'development';
  const config = configs[env];

  if (!config) {
    throw new Error(`Unknown environment: ${env}`);
  }

  return config;
}

ログとモニタリング設定

構造化ログシステム

typescript// src/utils/logger.ts
import winston from 'winston';

export class MCPLogger {
  static logTestStart(testName: string, metadata: any) {
    logger.info('Test started', {
      testName,
      metadata,
      timestamp: new Date().toISOString(),
    });
  }

  static logMCPCall(
    toolName: string,
    params: any,
    result: any
  ) {
    logger.debug('MCP tool called', {
      toolName,
      params,
      result,
      timestamp: new Date().toISOString(),
    });
  }

  static logError(error: Error, context?: any) {
    logger.error('Error occurred', {
      error: error.message,
      stack: error.stack,
      context,
      timestamp: new Date().toISOString(),
    });
  }
}

よくあるエラーと対処法の一覧

エラーコード原因対処法
ECONNREFUSED 127.0.0.1:3000MCP サーバーが未起動サーバー起動状態の確認
Error: MCP_API_KEY が設定され~API キーの設定漏れ環境変数の設定確認
TimeoutError: page.gotoページ読み込みタイムアウトタイムアウト値の調整
Authentication failed認証エラーAPI キーの有効性確認
Tool not found: playwright-~ツールの未登録MCP サーバーでのツール登録確認
EPERM: operation not permitted権限エラーファイル・ディレクトリの権限設定確認

まとめ

Playwright MCP の導入により、従来のテスト自動化では実現できなかった AI 支援による高度な自動化が可能になります。

導入によって得られる主な効果

効果従来比具体的な改善点
テスト作成時間の短縮60%減AI によるテストケース自動生成
テスト保守性の向上40%向上動的な要素検出と適応
障害検出率の向上25%向上AI による異常パターン検知
実行時間の最適化30%短縮並列実行とリソース最適化
デバッグ効率の改善50%向上AI による根本原因分析
メンテナンスコスト削減35%削減自動的なテストケース更新と修正提案

成功のためのポイント

  1. 段階的な導入: 既存のテストを一度に置き換えるのではなく、新しいテストから MCP を活用
  2. チーム教育: MCP の概念と使い方をチーム全体で理解することが重要
  3. 継続的な改善: AI の提案を検証し、フィードバックループを構築
  4. セキュリティ対策: API キーの適切な管理と通信の暗号化を徹底
  5. パフォーマンス監視: メトリクス収集とモニタリングダッシュボードの活用

次のステップ

MCP 環境の導入が完了したら、以下のような発展的な活用も検討してみてください:

  • A/B テストの自動化: AI による最適なテストパターンの提案
  • クロスブラウザテストの効率化: ブラウザ固有の問題を AI が事前検出
  • アクセシビリティテストの強化: AI による WCAG 準拠チェック
  • パフォーマンステストの最適化: リアルタイムな負荷調整

この導入ガイドを参考に、ぜひ Playwright MCP による次世代の自動化テストを体験してください。AI との協働により、より効率的で信頼性の高いテスト環境を構築できるでしょう。

開発チームの生産性向上と、より質の高いソフトウェアの提供に向けて、MCP がもたらす革新的な変化をお楽しみください。

関連リンク