ローカル開発環境のPlaywrightテストをTestingBotで実行

TestingBotは、多様なブラウザ環境をクラウド上で提供するテストプラットフォームです。以前の記事でTestMu AI(LambdaTest)を使ってローカル開発環境へのクラウドテストを実行しましたが、今回は同じことをTestingBotで試してみます。

TestingBotは、多様なブラウザ環境をクラウド上で提供するテストプラットフォームです。以前の記事でTestMu AI(LambdaTest)を使ってローカル開発環境へのクラウドテストを実行しましたが、今回は同じことをTestingBotで試してみます。

LambdaTest版の記事はこちらです。 ローカル開発環境のPlaywrightテストをTestMu AI(LambdaTest)で実行

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

今回も2段階で進めます。まず公開されているデモサイトを対象にTestingBotとPlaywrightの接続を確認し、そのあとトンネルを使ってテスト対象をローカルの開発環境に切り替える、という形です。

TestingBotの接続情報を取得

まずはTestingBotのサイトにて、接続に必要な情報を取得します。トップ画面のStart free(28日間無料、60分間まで)をクリックし、会員登録画面から登録を完了させます。

登録後はこちらのページにて、KeyとSecretが取得できます。

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

.env.playwright
TB_KEY=取得したKey
TB_SECRET=取得したSecret

公開サイトへの接続を確認する

まずは公開されているサイトを使って接続確認を行います。テストはシンプルに、デモサイトにアクセスするだけの内容にします。

e2e/testingbot-demo.spec.ts
import { test, expect } from '@playwright/test';

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

configは、TestingBot用のplaywright.testingbot.config.tsを新規作成します。以下configの全文になります。

playwright.testingbot.config.ts
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 TB_KEY = process.env.TB_KEY ?? '';
const TB_SECRET = process.env.TB_SECRET ?? '';

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

const DEVICE_MAP: Record<string, DeviceConfig> = {
  chrome: { tbBrowser: 'chrome', browserVersion: 'latest', platform: 'WIN11', device: 'Desktop Chrome' },
  firefox: { tbBrowser: 'firefox', browserVersion: 'latest', platform: 'WIN11', device: 'Desktop Firefox' },
  edge: { tbBrowser: 'edge', browserVersion: 'latest', platform: 'WIN11', device: 'Desktop Edge' },
  safari: { tbBrowser: 'webkit', browserVersion: 'latest', platform: 'TAHOE', device: 'Desktop Safari' },
  ios: { tbBrowser: 'webkit', browserVersion: 'latest', platform: '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 { tbBrowser, browserVersion, platform: defaultPlatform, device: deviceName } = DEVICE_MAP[deviceKey];
const tbPlatform = process.env.TB_PLATFORM ?? defaultPlatform;
const selectedDevice = devices[deviceName];

// ---- TestingBot エンドポイント ----
function tbEndpoint(testName: string) {
  return `wss://cloud.testingbot.com/playwright?capabilities=${encodeURIComponent(
    JSON.stringify({
      browserName: tbBrowser,
      browserVersion,
      platform: tbPlatform,
      'tb:options': {
        key: TB_KEY,
        secret: TB_SECRET,
        build: 'TestingBot × Playwright',
        name: `${testName} [${tbPlatform}]`,
      },
    })
  )}`;
}

// ---- Playwright 設定 ----
export default defineConfig({
  testDir: './e2e',
  workers: 1,
  timeout: 60_000,
  reporter: [['html'], ['list']],

  use: {
      baseURL: 'https://demoblaze.com/',
      trace: 'on-first-retry',
      screenshot: 'only-on-failure',
  },

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

構成はLambdaTestと同じく ①環境変数の読み込み、②デバイスマッピング、③TestingBotへの接続設定、④Playwrightの実行設定、の4つのブロックとなっています。

以下、2箇所だけ注意点です。

デバイスマッピング

DEVICE_MAPでは、ブラウザとOSの組み合わせを定義しています。ぱっと見はLambdaTestと同じですが、ブラウザ名の指定は少し異なります。例えば、TestingBotではSafariをwebkitと指定します(LambdaTestはpw-webkit)。macOSもTAHOEのようなバージョン名の大文字コードになります。

OSとブラウザの組み合わせは公式ドキュメントで確認できます。

Playwrightの実行設定

こちらも、デモサイト用に少し変更を加えています。

export default defineConfig({
・・・・・
  use: {
      baseURL: 'https://demoblaze.com/',
・・・・・
  projects: [
    {
      name: `${deviceKey}`,
      testMatch: '**/testingbot-demo.spec.ts',
      use: {
        ...selectedDevice,
        connectOptions: { wsEndpoint: tbEndpoint(deviceKey) },
      },
    },
  ],
});

baseURLにデモサイトを指定し、testMatchにも、デモサイト表示確認用のテストを指定しています。

そしてconnectOptionsは、「ローカルのブラウザを起動する代わりに、リモートのブラウザに接続する」ための設定です。ここに、上で用意したtbEndpoint()が返すURLを渡すことで、テストがTestingBotのクラウドブラウザ上で実行されるようになります。

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

第一段階の確認のため、テストを実行します。

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

TestingBotのダッシュボードにセッションが表示され、クリックするとテストの詳細が開くので、そこから実際のテストの様子を確認できます。

ちゃんとデモサイトが開いている様子が確認でき、テストも問題なく通りました。

Tunnelを導入

次は、テスト対象をローカル環境に切り替えます。クラウドブラウザからローカルサーバーへは直接接続できないため、TestingBotが提供しているTunnelを使います。

トンネルの導入方法は何種類かありますが、今回はnpmパッケージを使用します。こちらはマシンにJavaが必要になるので、無い場合は事前にインストールしてください。

https://testingbot.com/support/tunnel/installation#node

トンネルのライブラリは、以下のコマンドでインストールします。

$ npm install --save-dev testingbot-tunnel-launcher

トンネル実行ファイル

インストールしたライブラリを使ったトンネルの起動・停止をPlaywrightに組み込むため、実行ファイルを2つ作成します。LambdaTest版のときと同じ役割の2ファイルです。

トンネル起動用はこちら。

e2e/support/testingbot-tunnel.ts
import testingbotTunnel from 'testingbot-tunnel-launcher';
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 TB_KEY = process.env.TB_KEY ?? '';
const TB_SECRET = process.env.TB_SECRET ?? '';

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

  console.log('[TestingBot] トンネルを起動中...');

  const t = await new Promise((resolve, reject) => {
    testingbotTunnel(
      {
        apiKey: TB_KEY,
        apiSecret: TB_SECRET,
        tunnelIdentifier: 'my-tunnel', // configのtb:optionsと同じ名前にする
      },
      (err: Error | null, tunnel: any) => {
        if (err) reject(err);
        else {
          console.log('[TestingBot] トンネル起動完了');
          resolve(tunnel);
        }
      }
    );
  });

  (globalThis as any).__tb_tunnel__ = t;
}

トンネル終了用は以下になります。

e2e/support/testingbot-tunnel-teardown.ts
export default async function teardown() {
  const t = (globalThis as any).__tb_tunnel__;
  if (t) {
    console.log('[TestingBot] トンネルを停止中...');
    await new Promise<void>((resolve) => t.close(resolve));
    console.log('[TestingBot] トンネル停止完了');
  }
}

playwright.testingbot.config.tsを更新

次はconfigファイルを、トンネルを使ったバージョンに更新します。以下は、更新箇所の抜粋となります。

・・・・・
// ---- TestingBot エンドポイント ----
function tbEndpoint(testName: string) {
  return `wss://cloud.testingbot.com/playwright?capabilities=${encodeURIComponent(
    JSON.stringify({
・・・・・
      'tb:options': {
        tunnelIdentifier: 'my-tunnel', // ① トンネル実行ファイルと同じ識別子を指定
・・・・・
      },
    })
  )}`;
}
・・・・・

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

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

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

    // 実行したいテスト
    {
      name: `${deviceKey} 未ログイン`,
      testMatch: '**/auth.spec.ts',
      use: {
        ...selectedDevice,
        connectOptions: { wsEndpoint: tbEndpoint(`${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. tunnelIdentifier:トンネル実行ファイルで指定した識別子(my-tunnel)と同じ名前をtb:optionsに渡します。この設定によって、テストでトンネルを使うという指定になります。
  2. globalSetup / globalTeardown:先ほど作成したトンネル実行ファイルを指定します。テスト実行前にトンネルが自動で起動し、終了後に停止します。
  3. baseURL:デモサイトからローカルサーバーのURLに変更します。
  4. projects:DB初期化(db-setup)はローカルで行い、テスト本体だけクラウドブラウザで実行する構成にしています。この棲み分けはconnectOptionsの指定あり・なしで決まり、指定があるprojectはTestingBotのクラウドブラウザで、無いものは手元のブラウザで実行されます。

テスト実行

$ npx playwright test --config=playwright.testingbot.config.ts
◇ injected env (45) from .env // tip: ⌘ enable debugging { debug: true }
◇ injected env (8) from .env.playwright // tip: ⌘ custom filepath { path: '/custom/path/.env' }
◇ injected env (0) from .env // tip: ◈ secrets for agents [www.dotenvx.com]
◇ injected env (8) from .env.playwright // tip: ⌘ multiple files { path: ['.env.local', '.env'] }
[TestingBot] トンネルを起動中...
Tunnel is ready
[TestingBot] トンネル起動完了

Running 4 tests using 1 worker

・・・・・中略・・・・・
1 [db-setup] › e2e/support/db.setup.ts:5:1 › reset ★ database (567ms)
◇ injected env (0) from .env // tip: ◈ secrets for agents [www.dotenvx.com]
◇ injected env (8) from .env.playwright // tip: ⌘ suppress logs { quiet: true }
2 [chrome 未ログイン] › e2e/auth.spec.ts:4:1 › ログインページが表示される (9.4s)
・・・・・中略・・・・・
[TestingBot] トンネルを停止中...
[TestingBot] トンネル停止完了

  4 passed (1.0m)

出力から、トンネルが問題なく起動・終了していることがわかります。テストも問題なく通り、トンネル経由でローカルサーバーへのテストが無事成功しました。

現在ではchrome・edge・firefox・safari・iosの5つすべてで動作確認ができましたが、実は最初にトンネルで検証したときは、firefoxとsafariだけローカルサーバーに到達できずエラーとなっていました。

接続先をホスト名に変えてみたり既定のポートに変更してみたりしたのですがエラー解消とはいかず、TestingBotのサポートに問い合わせたところ、一週間足らずで修正対応いただいたという経緯があります。サポートの対応が早かったのは好印象でした。

なお、LambdaTestのときはSafari実行時にローカルサーバーへ到達できず少し工夫が必要でしたが、TestingBotでは設定面での工夫は不要でした。

実行時間について

Playwrightのテスト実行時間は、ローカルでは速いですがクラウド経由はどうしても時間がかかります。同じクラウドサービスであるLambdaTestと比べてもTestingBotのほうが時間がかかる印象があり、この点も気になったので、こちらもサポートに聞いてみたところ以下のような回答でした。

I think the latency plays a big part in this. Our only datacenter is Europe, we don’t have any others. I don’t think there is currently a way to optimize this.

TestingBotのデータセンターはヨーロッパのみであり、現時点で最適化する方法は無さそうとのことです。日本からだとPlaywrightのコマンドごとに日本⇔欧州の往復が発生します。この物理的な距離による通信の往復時間(レイテンシ)が積み重なって、テスト時間の遅延につながってしまうようです。

ちなみにLambdaTestのセッション情報にはアジアのリージョン名が含まれていたので、ヨーロッパより近い分だけ速いのかもしれません。

Hugo で構築されています。
テーマ StackJimmy によって設計されています。