Playwrightを使用した最新のWeb自動化テスト:包括的ガイド

Playwright

はじめに

Webアプリケーションの複雑さが増す中、効率的で信頼性の高い自動化テストの重要性はますます高まっています。Microsoft社が開発したPlaywrightは、そんな現代のWeb開発者とQAエンジニアのニーズに応える、強力なオープンソースの自動化テストツールです。

本記事では、Playwrightの基本から高度な使用方法まで、技術的な観点から詳しく解説します。設定方法、基本的な操作、高度な機能、そしてベストプラクティスまで、Playwrightを最大限に活用するための包括的なガイドをお届けします。

Playwrightとは

Playwrightは、Webブラウザを自動操作するためのNode.jsライブラリです。Chromium、Firefox、WebKitの主要3ブラウザに対応し、JavaScriptまたはTypeScriptを使用してクロスブラウザテストを簡単に実行できます。

主な特徴

  1. クロスブラウザサポート: 単一のAPIでChromium、Firefox、WebKitをサポート
  2. 自動待機: ページの読み込みや要素の表示を自動的に待機
  3. ネットワークインターセプト: リクエストのモックやネットワークトラフィックの分析が可能
  4. モバイルエミュレーション: レスポンシブデザインのテストに対応
  5. 高度な自動化: ファイルのダウンロード、アップロード、ジオロケーション、権限の操作などをサポート

Playwrightの設定

Playwrightを使い始めるには、まずNode.jsプロジェクトにインストールする必要があります。

npm init -y
npm install @playwright/test

次に、Playwrightの設定ファイル playwright.config.js を作成します:

// @ts-check
const { defineConfig, devices } = require('@playwright/test');

module.exports = defineConfig({
  testDir: './tests',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
  use: {
    trace: 'on-first-retry',
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
  ],
});

この設定ファイルでは、テストの並列実行、リトライ回数、レポーターの設定、そして各ブラウザでのテスト実行を定義しています。

基本的な使用方法

最初のテストを書く

Playwrightでの最初のテストを書いてみましょう。tests ディレクトリを作成し、その中に example.spec.js ファイルを作成します:

const { test, expect } = require('@playwright/test');

test('basic test', async ({ page }) => {
  await page.goto('https://playwright.dev/');
  const title = page.locator('.navbar__inner .navbar__title');
  await expect(title).toHaveText('Playwright');
});

このテストでは、Playwrightの公式サイトにアクセスし、ナビゲーションバーのタイトルが "Playwright" であることを確認しています。

テストの実行

テストを実行するには、以下のコマンドを使用します:

npx playwright test

このコマンドは、playwright.config.js で定義されたすべてのブラウザでテストを実行します。

高度な機能

ページオブジェクトモデル(POM)

大規模なテストスイートを管理する際、ページオブジェクトモデル(POM)パターンを使用すると効果的です。以下は、シンプルなPOMの例です:

// models/HomePage.js
class HomePage {
  constructor(page) {
    this.page = page;
    this.searchInput = page.locator('#search-input');
    this.searchButton = page.locator('#search-button');
  }

  async navigate() {
    await this.page.goto('https://example.com');
  }

  async search(query) {
    await this.searchInput.fill(query);
    await this.searchButton.click();
  }
}

module.exports = HomePage;

// tests/search.spec.js
const { test, expect } = require('@playwright/test');
const HomePage = require('../models/HomePage');

test('search functionality', async ({ page }) => {
  const homePage = new HomePage(page);
  await homePage.navigate();
  await homePage.search('Playwright');
  
  // 検索結果の検証
  const results = page.locator('.search-results');
  await expect(results).toBeVisible();
});

このアプローチにより、テストコードの再利用性と保守性が向上します。

ネットワークインターセプト

Playwrightでは、ネットワークリクエストをインターセプトし、モックレスポンスを返すことができます:

const { test, expect } = require('@playwright/test');

test('mock API response', async ({ page }) => {
  await page.route('**/api/users', route => {
    route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify([{ id: 1, name: 'John Doe' }])
    });
  });

  await page.goto('https://example.com');
  
  // モックレスポンスを使用したUIの検証
  const userName = page.locator('.user-name');
  await expect(userName).toHaveText('John Doe');
});

この機能は、バックエンドに依存せずにフロントエンドのテストを行う際に非常に有用です。

ビジュアルテスト

Playwrightは、ビジュアルテスト(スクリーンショットの比較)もサポートしています:

const { test, expect } = require('@playwright/test');

test('visual regression test', async ({ page }) => {
  await page.goto('https://example.com');
  
  // ページ全体のスクリーンショットを撮影
  const screenshot = await page.screenshot();
  
  // 前回のスクリーンショットと比較
  await expect(screenshot).toMatchSnapshot('homepage.png');
});

このテストは、ページの外観が前回のテスト実行時から変更されていないことを確認します。

API テスト

Playwrightはブラウザテストだけでなく、APIテストにも使用できます:

const { test, expect } = require('@playwright/test');

test('API test', async ({ request }) => {
  const response = await request.get('https://api.example.com/users');
  expect(response.ok()).toBeTruthy();
  
  const users = await response.json();
  expect(users.length).toBeGreaterThan(0);
});

この機能により、E2EテストとAPIテストを同じフレームワーク内で統合できます。

パフォーマンス最適化

Playwrightを使用する際、テストの実行速度は重要な要素です。以下に、パフォーマンスを最適化するためのいくつかのテクニックを紹介します。

並列実行

Playwrightは、デフォルトで並列実行をサポートしています。playwright.config.js で以下のように設定することで、並列実行を有効にできます:

// playwright.config.js
module.exports = defineConfig({
  // ...他の設定...
  fullyParallel: true,
  workers: process.env.CI ? 2 : undefined,
});

この設定により、CIでは2つの並列ワーカーを使用し、ローカル環境では利用可能なCPUコア数に基づいて自動的にワーカー数を決定します。

テストの分離

テスト間の依存関係を最小限に抑えることで、並列実行の効率が向上します。各テストが独立して実行できるようにすることが重要です。

const { test, expect } = require('@playwright/test');

test.describe('User management', () => {
  test.beforeEach(async ({ page }) => {
    // 各テストの前に新しいユーザーを作成
    await page.goto('/create-user');
    await page.fill('#username', `user_${Date.now()}`);
    await page.fill('#password', 'password123');
    await page.click('#submit');
  });

  test('user can login', async ({ page }) => {
    // ログインのテスト<
  });

  test('user can update profile', async ({ page }) => {
    // プロフィール更新のテスト
  });
});

この例では、各テストが独自のユーザーを使用するため、テスト間の干渉が防げます。

リソースの効率的な使用

ブラウザの起動と終了には時間がかかるため、可能な限りブラウザコンテキストを再利用することで、テスト実行時間を短縮できます。

const { test, expect } = require('@playwright/test');

test.describe('E-commerce tests', () => {
  let context;

  test.beforeAll(async ({ browser }) => {
    context = await browser.newContext();
  });

  test.afterAll(async () => {
    await context.close();
  });

  test('add to cart', async () => {
    const page = await context.newPage();
    // テストロジック
  });

  test('checkout process', async () => {
    const page = await context.newPage();
    // テストロジック
  });
});

この方法により、複数のテストで同じブラウザコンテキストを共有し、リソースの効率的な使用が可能になります。

継続的インテグレーション(CI)との統合

Playwrightは、様々なCI/CDプラットフォームとシームレスに統合できます。ここでは、GitHub Actionsを使用した例を示します。

name: Playwright Tests
on:
  push:
    branches: [ main, master ]
  pull_request:
    branches: [ main, master ]
jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - uses: actions/setup-node@v3
      with:
        node-version: 18
    - name: Install dependencies
      run: npm ci
    - name: Install Playwright Browsers
      run: npx playwright install --with-deps
    - name: Run Playwright tests
      run: npx playwright test
    - uses: actions/upload-artifact@v3
      if: always()
      with:
        name: playwright-report
        path: playwright-report/
        retention-days: 30

この設定により、メインブランチへのプッシュやプルリクエスト時に自動的にPlaywrightテストが実行されます。テスト結果は、GitHub Actionsのアーティファクトとして保存されます。

デバッグ技術

Playwrightのテストをデバッグする際に役立つテクニックをいくつか紹介します。

トレースビューアの使用

Playwrightのトレースビューアは、テストの実行過程を詳細に分析するための強力なツールです。

// playwright.config.js
module.exports = defineConfig({
  use: {
    trace: 'on-first-retry', // または 'on' ですべてのテストでトレースを有効化
  },
});

テスト失敗時に生成されたトレースファイルは、以下のコマンドで表示できます:

npx playwright show-trace trace.zip

ヘッドフルモードでのデバッグ

テストをヘッドフルモード(ブラウザを表示)で実行することで、テストの動作を視覚的に確認できます。

npx playwright test --headed

さらに、page.pause() メソッドを使用することで、テストの特定のポイントで実行を一時停止し、ブラウザの状態を調査することができます。

test('debug example', async ({ page }) => {
  await page.goto('https://example.com');
  await page.pause(); // この行でテストが一時停止
  // 後続の操作
});

コンソールログの活用

ブラウザのコンソールログをテスト出力に含めることで、JavaScriptエラーや警告を簡単に確認できます。

// playwright.config.js
module.exports = defineConfig({
  use: {
    // ブラウザコンソールの内容をテスト出力に含める
    logger: {
      isEnabled: (name, severity) => name === 'browser',
      log: (name, severity, message, args) => console.log(`${name} ${message}`)
    }
  },
});

この設定により、ブラウザで発生したコンソールメッセージがテスト出力に表示されます。

高度なシナリオ

Playwrightを使用して、より複雑で現実的なテストシナリオを作成する方法を見ていきましょう。

認証の処理

多くのWebアプリケーションでは、認証が必要です。Playwrightを使用して、効率的に認証を処理する方法を示します。

const { test, expect } = require('@playwright/test');

test.describe('Authenticated tests', () => {
  test.beforeEach(async ({ page }) => {
    // ログインページに移動
    await page.goto('https://example.com/login');
    
    // ログインフォームに入力
    await page.fill('#username', 'testuser');
    await page.fill('#password', 'password123');
    
    // ログインボタンをクリック
    await page.click('#login-button');
    
    // ログイン後のリダイレクトを待つ
    await page.waitForURL('https://example.com/dashboard');
  });

  test('user can access protected content', async ({ page }) => {
    await page.goto('https://example.com/protected-page');
    const protectedContent = page.locator('#protected-content');
    await expect(protectedContent).toBeVisible();
  });

  test('user can log out', async ({ page }) => {
    await page.click('#logout-button');
    await page.waitForURL('https://example.com/login');
    const loginForm = page.locator('#login-form');
    await expect(loginForm).toBeVisible();
  });
});

この例では、beforeEach フックを使用して各テストの前にログインを行い、その後の各テストで認証が必要なアクションを実行しています。

ステート保持

テスト間でステートを保持することで、テストの実行時間を短縮できます。以下は、認証状態を保持する例です。

const { test, expect } = require('@playwright/test');

let authContext;

test.beforeAll(async ({ browser }) => {
  // 新しいコンテキストを作成
  authContext = await browser.newContext();
  
  // ログイン処理
  const page = await authContext.newPage();
  await page.goto('https://example.com/login');
  await page.fill('#username', 'testuser');
  await page.fill('#password', 'password123');
  await page.click('#login-button');
  await page.waitForURL('https://example.com/dashboard');
  
  // 認証済みのストレージ状態を保存
  await authContext.storageState({ path: 'auth.json' });
});

test.use({ storageState: 'auth.json' });

test('authenticated user can access protected content', async ({ page }) => {
  await page.goto('https://example.com/protected-page');
  const protectedContent = page.locator('#protected-content');
  await expect(protectedContent).toBeVisible();
});

test('authenticated user can perform actions', async ({ page }) => {
  await page.goto('https://example.com/dashboard');
  await page.click('#create-new-item');
  // 以降のテストロジック
});

この方法では、最初にログインを行い、その認証状態を auth.json ファイルに保存します。その後の各テストでは、この保存された状態を使用することで、毎回ログインする必要がなくなります。

複雑なユーザーインタラクション

Playwrightは、ドラッグアンドドロップやホバーなどの複雑なユーザーインタラクションもサポートしています。

const { test, expect } = require('@playwright/test');

test('drag and drop functionality', async ({ page }) => {
  await page.goto('https://example.com/drag-and-drop');

  const sourceElement = page.locator('#draggable-item');
  const targetElement = page.locator('#drop-zone');

  // ドラッグアンドドロップを実行
  await sourceElement.dragTo(targetElement);

  // ドロップ後の状態を確認
  await expect(targetElement).toContainText('Item dropped');
});

test('hover menu interaction', async ({ page }) => {
  await page.goto('https://example.com/hover-menu');

  const menuTrigger = page.locator('#menu-trigger');
  const submenuItem = page.locator('#submenu-item');

  // ホバーアクションを実行
  await menuTrigger.hover();
  await expect(submenuItem).toBeVisible();

  // サブメニュー項目をクリック
  await submenuItem.click();

  // クリック後の状態を確認
  await expect(page).toHaveURL('https://example.com/submenu-page');
});

これらの例は、Playwrightが提供する高度なインタラクション機能を示しています。

パフォーマンステスト

Playwrightを使用して、Webアプリケーションのパフォーマンスをテストすることも可能です。以下は、ページの読み込み時間と特定の操作の実行時間を測定する例です。

const { test, expect } = require('@playwright/test');

test('page load performance', async ({ page }) => {
  const startTime = Date.now();

  await page.goto('https://example.com');

  const loadTime = Date.now() - startTime;
  console.log(`Page load time: ${loadTime}ms`);

  // 任意の閾値を設定してテスト
  expect(loadTime).toBeLessThan(3000); // 3秒未満であることを期待
});

test('action performance', async ({ page }) => {
  await page.goto('https://example.com/heavy-action');

  const actionButton = page.locator('#perform-heavy-action');

  const startTime = Date.now();

  await actionButton.click();
  await page.waitForSelector('#action-complete');

  const actionTime = Date.now() - startTime;
  console.log(`Action execution time: ${actionTime}ms`);

  expect(actionTime).toBeLessThan(5000); // 5秒未満であることを期待
});

これらのテストは、アプリケーションのパフォーマンスが期待される閾値内に収まっていることを確認します。

アクセシビリティテスト

Playwrightを使用してアクセシビリティテストを実行することで、Webアプリケーションがアクセシビリティ基準を満たしていることを確認できます。

const { test, expect } = require('@playwright/test');
const AxeBuilder = require('@axe-core/playwright').default;

test('homepage should not have any automatically detectable accessibility issues', async ({ page }) => {
  await page.goto('https://example.com');

  const accessibilityScanResults = await new AxeBuilder({ page }).analyze();

  expect(accessibilityScanResults.violations).toEqual([]);
});

test('form inputs should have associated labels', async ({ page }) => {
  await page.goto('https://example.com/form');

  const inputs = await page.$$('input');

  for (const input of inputs) {
    const hasLabel = await page.evaluate((el) => {
      const id = el.id;
      return !!document.querySelector(`label[for="${id}"]`);
    }, input);

    expect(hasLabel).toBe(true);
  }
});

この例では、axe-coreライブラリを使用して自動的なアクセシビリティスキャンを実行し、また手動でフォーム要素にラベルが適切に関連付けられていることを確認しています。

セキュリティテスト

Playwrightを使用して、基本的なセキュリティテストを実行することもできます。以下は、クロスサイトスクリプティング(XSS)脆弱性をテストする簡単な例です。

const { test, expect } = require('@playwright/test');

test('input fields should be protected against XSS', async ({ page }) => {
  await page.goto('https://example.com/input-form');

  const xssPayload = '<script>alert("XSS")</script>';

  await page.fill('#user-input', xssPayload);
  await page.click('#submit-button');

  // 結果ページに移動したことを確認
  await page.waitForURL('https://example.com/result');

  // スクリプトタグがエスケープされているか確認
  const content = await page.content();
  expect(content).toContain('<script>');
  expect(content).not.toContain('<script>');

  // アラートが発生していないことを確認
  const alertTriggered = await page.evaluate(() => {
    return new Promise(resolve => {
      window.alert = () => resolve(true);
      setTimeout(() => resolve(false), 1000);
    });
  });

  expect(alertTriggered).toBe(false);
});

このテストでは、XSS攻撃のペイロードをフォームに入力し、そのペイロードが適切にエスケープされ、実行されないことを確認しています。

継続的デリバリー(CD)との統合

Playwrightテストを継続的デリバリー(CD)パイプラインに統合することで、デプロイ前に自動的にテストを実行し、品質を確保することができます。以下は、AWS CodePipelineとの統合例です。

version: 0.2

phases:
  install:
    runtime-versions:
      nodejs: 14
    commands:
      - npm ci
      - npx playwright install --with-deps
  pre_build:
    commands:
      - echo "Starting Playwright tests"
  build:
    commands:
      - npx playwright test
  post_build:
    commands:
      - echo "Playwright tests completed"

artifacts:
  files:
    - playwright-report/**/*
  name: playwright-report

この設定をAWS CodeBuildプロジェクトに使用することで、CodePipelineのビルドステージでPlaywrightテストを自動的に実行できます。テスト結果は、アーティファクトとして保存され、後で確認することができます。

まとめ

Playwrightは、現代のWeb開発におけるテスト自動化の強力なツールです。本記事で紹介した技術と方法論を活用することで、より効率的で信頼性の高いテストスイートを構築することができます。

  • 基本的な使用方法から高度な機能まで、Playwrightは幅広いテストシナリオをカバーします。
  • パフォーマンス最適化技術を適用することで、テスト実行時間を短縮し、CI/CDパイプラインの効率を向上させることができます。
  • 認証、ステート管理、複雑なユーザーインタラクションなど、現実世界のアプリケーションに必要な機能をテストするための方法を提供します。
  • パフォーマンステスト、アクセシビリティテスト、セキュリティテストなど、品質保証の様々な側面をカバーすることができます。
  • CI/CDパイプラインとの統合により、継続的なテストと品質管理が可能になります。

Playwrightを活用することで、開発チームは高品質なWebアプリケーションを迅速かつ確実にデリバリーすることができます。テスト自動化の導入と改善は継続的なプロセスであり、Playwrightはそのジャーニーを支援する強力なツールとなるでしょう。

参考リンク

Playwrightの世界は常に進化しています。最新の機能や改善点については、公式ドキュメントを定期的に確認し、コミュニティに参加することをおすすめします。テスト自動化の旅を楽しんでください!

コメント

タイトルとURLをコピーしました