TestMu AI(旧LambdaTest)は、多様なブラウザや実機OS環境をクラウド上で提供するテストプラットフォームです。Playwrightで作成したテストをTestMu AIのクラウドブラウザで実行することで、手元のマシンを使わずにChrome・Firefox・Safari・Edgeなど複数のブラウザでテストを走らせることができます。今回は、すでに構築済みのローカルプロジェクトに対して、クラウドブラウザからテストを実行することを目指します。

通常のPlaywrightテストもすでに構築済みの状態からTestMu AIの導入を進めますので、まだPlaywrightの環境を構築していない方はこちらもご参照ください。

PlaywrightでE2Eテストを自動化(1)セットアップ
PlaywrightでE2Eテストを自動化 (2)ログイン・ログアウト

本記事のゴール:公開サイトからローカル環境のテストまで

「ローカルのプロジェクトに対してクラウドブラウザからテスト実行」を実現するにあたり、この記事では2段階で環境構築を進めてゆきます。まずは公開されているURL(デモサイト)をテスト対象に設定を進め、TestMu AIとPlaywrightとの接続を確認します。

そこまでできたら次に、ローカルの開発サーバーをテスト対象に変更します。その際、クラウドブラウザからローカルサーバーへ接続するために、TestMu AIが提供しているTunnelという機能を使います。

なお、サービス名は現在TestMu AIに変更されていますが、今回使用するnpmパッケージ(@lambdatest/node-tunnel)やAPIのエンドポイント(cdp.lambdatest.com)など関連するコードは引き続きLambdaTestの名称が使われています。そのため本記事内でも以降はLambdaTestの表記で統一します。

LambdaTestの接続情報を取得

クラウドでの自動テストには、LambdaTestのWeb Automationという機能を使う必要があります。無料会員でも100分まではこの機能を使うことができますので、お試しにはぴったりです。

こちらから会員登録し、こちらにアクセスするとAutomationに必要な接続情報が取得できます。

取得した接続情報を.env.playwrightに追記します。

LT_USERNAME=ユーザー名
LT_ACCESS_KEY=アクセスキー

デモサイトへアクセスするテスト

必要な情報が揃ったので、次はPlaywrightとLambdaTestの接続設定を進めます。

冒頭でお伝えしたように、まずは公開されているデモサイトをテスト対象とします。テストもシンプルにデモサイトにアクセスするだけの以下のような内容で、PlaywrightからLambdaTestのクラウドブラウザに接続し、動作を確認します。

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

test('トップページが表示される', async ({ page }) => {
  await page.goto('/');
  await expect(page).toHaveTitle(/Your Store/i);
});

公開ページにアクセスするための設定ファイル

次にconfigファイルを作成します。既存のplaywright.config.tsはローカルでテストを実行する際の設定ファイルとして変更せず、新たにplaywright.lambdatest.config.tsを作成して、こちらをLambdaTest用の設定ファイルとします。

まずはコードの全体をお見せします。

import { defineConfig, devices } from '@playwright/test';
import dotenv from 'dotenv';
import { fileURLToPath } from 'url';
import path from 'path';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

dotenv.config({ path: path.resolve(__dirname, '.env') });
dotenv.config({ path: path.resolve(__dirname, '.env.playwright'), override: true });

const LT_USERNAME = process.env.LT_USERNAME ?? '';
const LT_ACCESS_KEY = process.env.LT_ACCESS_KEY ?? '';

// ---- デバイスマッピング ----
// DEVICE 変数1つで「LTブラウザ名・デフォルトOS・Playwrightデバイス」が連動して決まる。
type DeviceConfig = {
  ltBrowser: string;
  browserVersion: string;
  platform: string;
  device: string;
};

const DEVICE_MAP: Record<string, DeviceConfig> = {
  chrome: { ltBrowser: 'Chrome', browserVersion: 'latest', platform: 'Windows 11', device: 'Desktop Chrome' },
  firefox: { ltBrowser: 'pw-firefox', browserVersion: 'latest', platform: 'Windows 11', device: 'Desktop Firefox' },
  edge: { ltBrowser: 'Microsoft Edge', browserVersion: 'latest', platform: 'Windows 11', device: 'Desktop Edge' },
  safari: { ltBrowser: 'pw-webkit', browserVersion: 'latest', platform: 'MacOS Tahoe', device: 'Desktop Safari' },
  ios: { ltBrowser: 'pw-webkit', browserVersion: 'latest', platform: 'MacOS Tahoe', device: 'iPhone 15' },
};

const deviceKey = process.env.DEVICE ?? 'chrome';

if (!(deviceKey in DEVICE_MAP)) {
  console.error(`エラー: DEVICE="${deviceKey}" は未定義です。有効な値: ${Object.keys(DEVICE_MAP).join(', ')}`);
  process.exit(1);
}

const { ltBrowser, browserVersion, platform: defaultPlatform, device: deviceName } = DEVICE_MAP[deviceKey];
const ltPlatform = process.env.LT_PLATFORM ?? defaultPlatform;
const selectedDevice = devices[deviceName];

// ---- LambdaTest CDP エンドポイント ----
function ltEndpoint(testName: string) {
  return `wss://cdp.lambdatest.com/playwright?capabilities=${encodeURIComponent(
    JSON.stringify({
      browserName: ltBrowser,
      browserVersion,
      'LT:Options': {
        user: LT_USERNAME,
        accessKey: LT_ACCESS_KEY,
        build: 'Playwright Demo',
        name: `${testName} [${deviceKey} / ${ltPlatform}]`,
        platform: ltPlatform,
        tunnel: false,
        network: true,
        console: true,
        video: true,
      },
    })
  )}`;
}

// ---- Playwright 設定 ----
export default defineConfig({
  testDir: './e2e',
  fullyParallel: false,
  workers: 1,
  timeout: 60_000,

  reporter: [['html'], ['list']],

  use: {
    baseURL: 'https://ecommerce-playground.lambdatest.io',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },

  projects: [
    {
      name: `${deviceKey}`,
      testMatch: '**/lambdatest-demo.spec.ts',
      use: {
        ...selectedDevice,
        connectOptions: { wsEndpoint: ltEndpoint(deviceKey) },
      },
    },
  ],
});

大きく分けて ①環境変数の読み込み、②デバイスマッピング、③LambdaTestへの接続設定、④Playwrightの実行設定、の4つのブロックで構成されており、ポイントは以下になります。

②デバイスマッピング

DEVICE_MAPでは、ブラウザとOSの組み合わせを定義しています。

テスト実行時にDEVICE=safariのように環境変数を渡すと、マップのsafari: { ltBrowser: 'pw-webkit', browserVersion: '26.0', platform: 'MacOS Tahoe', device: 'Desktop Safari' }に合致してltBrowser(LambdaTestに渡すブラウザ名)・platform(OS)・deviceの3つが同時に確定します。

この設定のおかげで、「Windows上のSafari」という存在しない組み合わせを指定することを防げます。

OSとブラウザの組み合わせについては少しややこしいので、公式ドキュメントやジェネレーターを使って確認することをおすすめします。

公式ドキュメント
ジェネレーター

③LambdaTestへの接続設定

ltEndpoint()では、使用するブラウザ・バージョン・OS・認証情報など渡し、LambdaTestのクラウドブラウザに接続するためのURLを生成します。このURLを後述のconnectOptionsに渡すことで、PlaywrightがLambdaTestのクラウドブラウザに接続できるようになります。

④Playwrightの実行設定

Playwrightの設定はローカルで動かす時と似ていますが、以下の違いがあります。

・・・・・
// ---- Playwright 設定 ----
export default defineConfig({
・・・・・
  use: {
    baseURL: 'https://ecommerce-playground.lambdatest.io',
・・・・・
  projects: [
    {
      name: `${deviceKey}`,
      testMatch: '**/lambdatest-demo.spec.ts',
      use: {
        ...selectedDevice,
        connectOptions: { wsEndpoint: ltEndpoint(deviceKey) },
      },
    },

baseURLには、第1段階なのでデモサイトを対象としています。

そしてprojectsのconnectOptionsの箇所は、「ローカルのブラウザを起動する代わりにリモートのブラウザに接続する」ための設定です。ここで渡しているのが③で生成したクラウド接続用のURLになっています。

デモサイトへのテスト実行

ここでいったん動作確認のため、以下のコマンドでテストを実行してみます。環境変数にブラウザ名を渡していないので、デフォルトのchromeで実行されるはずです。

$ npx playwright test --config=playwright.lambdatest.config.ts

テスト実行中、ブラウザでLambdaTestのAutomationダッシュボードへアクセスするとリアルタイムでテストが実行されてデモサイトにアクセスしている様子が確認できます。ちゃんとchromeで実行されていますね。

無事に動作確認ができたので、次はテスト対象をデモサイトからローカルサーバーに切り替えます。

Tunnelバイナリ管理のライブラリ

ローカルサーバー(http://127.0.0.1:8001)をテストするにあたり、クラウドブラウザからlocalhostへは接続できないためTunnel(トンネル)という機能を使う必要があります。

今回は公式が提供しているNode.jsライブラリを使ってトンネル管理を行います。トンネルを動かすためのバイナリファイルを自動でダウンロードし、そのバイナリをNode.jsのコードから起動・停止できるようにしてくれるパッケージです。

以下のコマンドでインストールします。

$ npm install -D @lambdatest/node-tunnel

また、このライブラリを使用すると.lambdatestというディレクトリに自動的にバイナリファイルがダウンロードされるため、.gitignoreへ追記が必要です。実行時にはログファイルも出るため、こちらも併せて記載します。

・・・・・
*.log
.lambdatest/

Tunnel実行ファイル

先ほどインストールしたライブラリを使ったトンネルの起動・停止をPlaywrightに組み込むため、以下の実行ファイルを作成します。トンネル起動用・終了用の2ファイルあります。

トンネル起動用

import tunnel from '@lambdatest/node-tunnel';
import dotenv from 'dotenv';
import { fileURLToPath } from 'url';
import path from 'path';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
dotenv.config({ path: path.resolve(__dirname, '..', '..', '.env') });
dotenv.config({ path: path.resolve(__dirname, '..', '..', '.env.playwright'), override: true });

const LT_USERNAME   = process.env.LT_USERNAME   ?? '';
const LT_ACCESS_KEY = process.env.LT_ACCESS_KEY ?? '';

export default async function setup() {
  if (!LT_USERNAME || !LT_ACCESS_KEY) {
    throw new Error('LT_USERNAME と LT_ACCESS_KEY を .env.playwright に設定してください。');
  }

  console.log('[LambdaTest] トンネルを起動中...');
  const t = new tunnel();

  await new Promise<void>((resolve, reject) => {
    t.start(
      { user: LT_USERNAME, key: LT_ACCESS_KEY },
      (err: Error | null) => {
        if (err) reject(err);
        else {
          console.log('[LambdaTest] トンネル起動完了');
          resolve();
        }
      }
    );
  });

  (globalThis as any).__lt_tunnel__ = t;
}

トンネル停止用

export default async function teardown() {
  const t = (globalThis as any).__lt_tunnel__;
  if (t) {
    console.log('[LambdaTest] トンネルを停止中...');
    await new Promise<void>((resolve) => t.stop(resolve));
    console.log('[LambdaTest] トンネル停止完了');
  }
}

このファイルを後述のplaywright.lambdatest.config.tsglobalSetupglobalTeardownに指定することで、テスト実行のたびに手動でトンネルを起動する手間がなくなります。

playwright.lambdatest.config.tsを更新

playwright.lambdatest.config.tsを、トンネルを使ったバージョンに更新します。以下、更新箇所を抜粋しています。

・・・・・
// ---- LambdaTest CDP エンドポイント ----
function ltEndpoint(testName: string) {
  return `wss://cdp.lambdatest.com/playwright?capabilities=${encodeURIComponent(
    JSON.stringify({
・・・・・
      'LT:Options': {
・・・・・
        tunnel: true, // トンネルをtrueに
・・・・・
      },
    })
  )}`;

// ---- Playwright 設定 ----
export default defineConfig({
・・・・・
  globalSetup:    './e2e/support/lambdatest-tunnel.ts',          // トンネル起動
  globalTeardown: './e2e/support/lambdatest-tunnel-teardown.ts', // トンネル停止

  use: {
    baseURL: 'http://127.0.0.1:8001', // デモサイトのURLからローカルへ変更
・・・・・
  },

  projects: [
    // DB リセット&シード(ローカル実行)
    { name: 'db-setup', testMatch: '**/db.setup.ts' },

    // 実行したいテスト
    {
      name: `${deviceKey} 未ログイン`,
      testMatch: '**/auth.spec.ts',
      use: {
        ...selectedDevice,
        connectOptions: { wsEndpoint: ltEndpoint(`${deviceKey} 未ログイン`) },
      },
      dependencies: ['db-setup'],
    },
  ],

  webServer: {
    command: 'php artisan serve --port=8001',
    url: 'http://127.0.0.1:8001',
    reuseExistingServer: true,
    stdout: 'ignore',
    stderr: 'pipe',
  },
});

重要な変更箇所は以下の4点です。

  1. トンネル設定ltEndpoint()tunnel: trueとし、トンネルを使う旨を設定します。
  2. globalSetup / globalTeardown:先ほど作成したトンネル実行ファイルを指定します。これによりテスト実行前にトンネルが自動で起動し、終了後に停止します。
  3. baseURL:デモサイトのURLからローカルサーバーのURLに変更します。
  4. projects:テストで実行したいprojectを並べる点は通常のPlaywrightの記述と同じですが、DB初期化はローカルで行い、テスト本体のみクラウドブラウザで実行する構成となっています。
    この棲み分けはconnectOptionsの指定あり・なしによって決まり、connectOptionsが指定されているprojectはLambdaTestのクラウドブラウザで実行され、指定されていないprojectはローカルのブラウザで実行されます。

テスト実行

トンネルの設定も完了したので、改めてテストを実行してみます。

$ npx playwright test --config=playwright.lambdatest.config.ts
◇ injected env (44) from .env // tip: ◈ encrypted .env [www.dotenvx.com]
◇ injected env (5) from .env.playwright // tip: ◈ encrypted .env [www.dotenvx.com]
◇ injected env (0) from .env // tip: ⌘ enable debugging { debug: true }
◇ injected env (5) from .env.playwright // tip: ⌘ suppress logs { quiet: true }
[LambdaTest] トンネルを起動中...
Current version of Node Tunnel:  4.0.11
Verifying credentials
Auth succeeded
Checking for updates
Binary already at latest version
Starting tunnel
Tunnel successfully initiated. You can start testing now
[LambdaTest] トンネル起動完了

Running 4 tests using 1 worker

◇ injected env (0) from .env // tip: ⌘ enable debugging { debug: true }
◇ injected env (5) from .env.playwright // tip: ◈ encrypted .env [www.dotenvx.com]
     1 [db-setup] › e2e/support/db.setup.ts:5:1 › reset database

・・・・・中略・・・・・

  ✓  2 [chrome 未ログイン] › e2e/auth.spec.ts:4:3 › 認証機能 › ログインページが表示される (5.6s)
[LambdaTest] トンネルを停止中...
[LambdaTest] トンネル停止完了

  4 passed (1.3m)

テストの前後でトンネルが問題なく起動・終了しているようです。クラウドのダッシュボードでも実行中の動作が確認でき、トンネル経由でローカルサーバーへのテストが成功しました!

safari/ios対応

ここまでの設定でchrome・edgeの実行は問題なかったのですが、safari・iosで以下のようなエラーが発生しました。

Error: page.goto: Could not connect to the server.

ログを確認すると、トンネル接続自体は成功しているものの、LambdaTest側のWebKit(Safari)ブラウザがローカルのbaseURLに接続できずに失敗していることがわかりました。

これはWebKit特有の仕様が原因だそうです。LambdaTestの公式ドキュメントでもlocalhostの代わりにlocalhost.lambdatest.comを使うよう案内されており、baseURLを以下のように変更することで解決しました。

  // Before
  use: {
    baseURL: 'http://127.0.0.1:8001',
・・・・・
  },

  // After
  use: {
    baseURL: 'http://localhost.lambdatest.com:8001',
・・・・・
  },

公式ドキュメント

この修正で、safariやiosでもトンネルを使ったクラウドテストが実行できるようになりました。ですがsafariやiosは接続が少し重く、同じクラウド実行でもchromeなどに比べて1.5倍〜時間がかかる感じがします。

メルマガ購読の申し込みはこちらから。

By hmatsu