🎭

PlaywrightとLambdaTestを用いたE2Eテストをやってみた

2023/07/04に公開

こんにちは!
リンクウェルオンライン診療システムチーム、フロントエンドエンジニアの松原です。
今回はPlaywrightLambdaTestを組み合わせて、複数の OS、ブラウザでの E2E テスト自動化の方法を紹介したいと思います。

やりたいこと

特定の機種やブラウザで発生するバグはユニットテストや統合テストでは検出するのが困難でしばしば問題となっていました。
特に古い OS をサポートするのは、LambdaTest で毎回手動で立ち上げて確認するにはコストが高く、トラブルを未然に防ぐのは難しい状況でした。

そこで、今回提案する Playwright と LambdaTest を組み合わせることで、上記の問題を解決できるのではないかと検討しました。

本記事ではサンプルコードを参考に、以下の順番に説明することで最終的に、共通のコードで Playwright 単体と LambdaTest 環境で自動テストの両方に対応できる環境構築の方法を紹介したいと思います。

  1. 1種類の OS,ブラウザに対して LambdaTest にジョブを1ケース投げる方法
  2. 複数の OS,ブラウザに対して LambdaTest にジョブを1ケース投げる方法
  3. 複数の OS,ブラウザに対して LambdaTest にジョブを複数ケース投げる方法
  4. 実運用のための設定

LambdaTest とは

Playwrightは日本ユーザーも多く有名だと思いますが、LambdaTestはご存じでない方もいると思いますので、最初に紹介したいと思います。

LambdaTest は、ユーザーがさまざまなブラウザーや OS で Web サイトや Web アプリケーションをテストできる、クラウドベースのクロスブラウザーテストプラットフォームです。

手元に実機がなくても、iOS/Android の様々な機種と OS 環境でテストできるのが非常に便利で、トラブル発生時には重宝してます。
テストできるデバイスの種類はこちらから確認できます。

日本語の資料が少ないのが欠点ですが、YouTube チャンネルが豊富ですので、参考にすると良いでしょう。

https://www.youtube.com/watch?v=na07BInGXpM&list=PLZMWkkQEwOPlMNEvHeCyztAl_ve2TegvG&index=15

自動テストするためにはWeb Automation Testing プランを契約する必要があるのですが、Manual Testing プラン登録に100分のテスト時間が付与されますので、Web Automation Testing プラン契約前に試してみると良いでしょう。

https://www.lambdatest.com/pricing

Playwright の注意点

現時点(2023/06)で Playwright は iOS/Safari のドライバーにまだ対応していないため、以下のような Android 実機でのテストは iPhone 実機でのブラウザテストには対応できないので注意が必要です。

iOS/Safari の対応は以下の issue で議論されているようです。
iOS の対応が必須の場合は appium の利用を検討すると良いかもしれません。

Github:issue: [Feature] Run Playwright tests on real mobile device browsers?
https://github.com/microsoft/playwright/issues/1122

対応デバイスの参考
https://playwright.dev/
https://developers.play.jp/entry/2022/12/09/160940

1. 単一の OS,ブラウザに対して LambdaTest にジョブを投げる方法

サンプルコードを見ながら LambdaTest にジョブを投げる方法を確認してみましょう。
まずは環境構築からです。

$ git clone https://github.com/LambdaTest/playwright-sample.git
$ cd cd playwright-sample
$ npm install

次に LambdaTest の環境変数を追加します
アカウント登録していれば、こちらのページから LT_USERNAMELT_ACCESS_KEY を取得できます

$ export LT_USERNAME="your_name"
$ export LT_ACCESS_KEY="your_key"

以上が環境構築です。
早速サンプルコードを動かしてみましょう。
最も簡単なサンプルがplaywright-single.jsです。

https://github.com/LambdaTest/playwright-sample

以下を実行すると、LambdaTest にジョブを投げることができます

$ node playwright-single.js

実行結果は以下のように LambdaTest の管理画面で確認できます

今回実行したコードを確認すると、capabilitiesオブジェクトに実行したい OS とブラウザの情報を設定して、
chromium.connectに渡し、browserオブジェクトより先は通常の Playwright の書き方となるようです。

https://github.com/LambdaTest/playwright-sample/blob/main/playwright-single.js

(async () => {
  // 一つのブラウザに対する設定
  const capabilities = {
     // ブラウザの種類をここで設定します
    'browserName': 'Chrome', // Browsers allowed: `Chrome`, `MicrosoftEdge`, `pw-chromium`, `pw-firefox` and `pw-webkit`
    // ブラウザのバージョンをここで設定します
    'browserVersion': 'latest',
    'LT:Options': {
       // 実行するPCの種類をここで設定します
      'platform': 'Windows 10', // `MacOS Ventura`,`Windows 11`,等
      'build': 'Playwright Single Build',
      'name': 'Playwright Sample Test',
      'user': process.env.LT_USERNAME,
      'accessKey': process.env.LT_ACCESS_KEY,
      'network': true,
      'video': true,
      'console': true,
      'tunnel': false, // Add tunnel configuration if testing locally hosted webpage
      'tunnelName': '', // Optional
      'geoLocation': '', // country code can be fetched from https://www.lambdatest.com/capabilities-generator/
      'playwrightClientVersion': playwrightClientVersion
    }
  }


  const browser = await chromium.connect({
    wsEndpoint: `wss://cdp.lambdatest.com/playwright?capabilities=${encodeURIComponent(JSON.stringify(capabilities))}`
  })
  const page = await browser.newPage()

  await page.goto('https://www.bing.com')

  const element = await page.$('[id="sb_form_q"]')
  await element.click()

  // 以下テストコード...

capabilities の設定は LambdaTest の capabilities-generator ページから作成できます。

2. 複数の OS,ブラウザに対して LambdaTest にジョブを投げる方法

次は、1 つのテストケースを複数のブラウザで実行できるように拡張した場合のサンプルになります。
以下のコマンドでサンプルを実行できます。

$ node playwright-parallel.js

複数のブラウザで実行できていることが確認できました。

コードを見ると、capabilities を配列に変えて、forEachで複数のブラウザ条件で実行していることがわかります。

https://github.com/LambdaTest/playwright-sample/blob/main/playwright-parallel.js


// 実行したいブラウザを設定
const capabilities = [
  {
    'browserName': 'Chrome', // Browsers allowed: `Chrome`, `MicrosoftEdge`, `pw-chromium`, `pw-firefox` and `pw-webkit`
    'browserVersion': 'latest',
    'LT:Options': {
      'platform': 'Windows 10',
      'build': 'Playwright With Parallel Build',
      'name': 'Playwright Sample Test on Windows 10 - Chrome',
      'user': process.env.LT_USERNAME,
      'accessKey': process.env.LT_ACCESS_KEY,
      'network': true,
      'video': true,
      'console': true,
      'playwrightClientVersion': playwrightClientVersion
    }
  },
  {
  ...
]

capabilities.forEach(async (capability) => {
  await parallelTests(capability)
})

3. 複数の OS,ブラウザに対して LambdaTest にジョブを複数ケース投げる方法

これまではテストケースが1つの場合のみでしたが、次は複数のテストケースに対応する例を紹介します

この例は以下のサンプルを参考にすると良いです。TypeScript の場合も用意されています。

このサンプルは以下のコマンドで実行可能です。

$ cd playwright-test-js
$ npm install
$ npm run test

このサンプルのファイル構成は以下のようになっており、3つのテストケースが用意されていました。

playwright-sample/playwright-test-jsのディレクトリ構成
├── README.md
├── lambdatest-setup.js # playwrightのテストメソッドのラッパーを定義
├── package.json
├── playwright.config.js # playwrightのテスト条件に加えて、実行するブラウザを管理
└── tests # テストケース
    ├── multipleBrowserContexts.spec.js
    ├── test_1.spec.js
    └── test_2.spec.js

test_1.spec.jsのテストコードを見ると、LambdaTest のことを意識しなくても Playwright のテストを書けていることがわかります。
ただしtest関数lambdatest-setupからインポートしており、ここで LambdaTest の設定を加えたラッパー関数としています。

test_1.spec.js
const { test } = require('../lambdatest-setup') // playwrightのtest関数をインポートしていない
const { expect } = require('@playwright/test')

test.describe('Browse LambdaTest in different search engines', () => {
  test('Search LambdaTest on Bing', async ({ page }) => {
    await page.goto('https://www.bing.com')
    await page.waitForLoadState('domcontentloaded')
    await page.waitForTimeout(3000)
    const element = await page.$('[id="sb_form_q"]')
    await element.click()
    await element.type('LambdaTest')
    await page.waitForTimeout(1000)
    await page.waitForTimeout(1000)
    await page.keyboard.press("Enter")
    await page.waitForSelector('[class=" b_active"]')
    const title = await page.title()

    console.log('Page title:: ', title)
    // Use the expect API for assertions provided by playwright
    expect(title).toEqual(expect.stringContaining('LambdaTest'))
  })
})

@playwright/test のラッパー関数を見てみるとtestInfolambdatestの文字が含まれている場合は LambdaTest にジョブを投げる設定をし、そうでない場合は通常の Playwright のpageを使う関数となっていました。

lambdatest-setup.js
// 40行目 付近
exports.test = base.test.extend({
  page: async ({ page, playwright }, use, testInfo) => {
    // Configure LambdaTest platform for cross-browser testing
    let fileName = testInfo.file.split(path.sep).pop()
    if (testInfo.project.name.match(/lambdatest/)) {
      modifyCapabilities(testInfo.project.name, `${testInfo.title} - ${fileName}`)

      const browser = await chromium.connect({
        wsEndpoint: `wss://cdp.lambdatest.com/playwright?capabilities=${encodeURIComponent(JSON.stringify(capabilities))}`
      })

      const ltPage = await browser.newPage(testInfo.project.use)
      await use(ltPage)

      const testStatus = {
        action: 'setTestStatus',
        arguments: {
          status: testInfo.status,
          remark: testInfo.error?.stack || testInfo.error?.message,
        }
      }
      await ltPage.evaluate(() => {},
        `lambdatest_action: ${JSON.stringify(testStatus)}`)
      await ltPage.close()
      await browser.close()
    } else {
      // Run tests in local in case of local config provided
      await use(page)
    }
  }
})

testInfoとはplaywright.config.jsにて設定されている、configオブジェクト の中の projects に記載されているnameが対応しているようです。下の例だとchrome:latest:MacOS Ventura@lambdatestchrome:latest:Windows 11@lambdatestです。

playwright.config.js
const config = {
  testDir: "tests",
  testMatch: "**/*.spec.js",
  timeout: 60000,
  use: {},
  projects: [
    {
      name: "chrome:latest:MacOS Ventura@lambdatest",
      use: {
        viewport: { width: 1920, height: 1080 },
      },
    },
    {
      name: "chrome:latest:Windows 11@lambdatest",
      use: {
        viewport: { width: 1280, height: 720 },
      },
    },
    ....

また、lambdatest-setup.js では、modifyCapabilitiesとうメソッドを使ってtestInfoから各ブラウザの設定である capabilities を作成していました

lambdatest-setup.js
// capabilitiesのモック
const capabilities = {
  'browserName': 'Chrome', // Browsers allowed: `Chrome`, `MicrosoftEdge`, `pw-chromium`, `pw-firefox` and `pw-webkit`
  'browserVersion': 'latest',
  'LT:Options': {
    'platform': 'Windows 10',
    'build': 'Playwright JS Build',
    'name': 'Playwright Test',
    'user': process.env.LT_USERNAME,
    'accessKey': process.env.LT_ACCESS_KEY,
    'network': true,
    'video': true,
    'console': true,
    'tunnel': false, // Add tunnel configuration if testing locally hosted webpage
    'tunnelName': '', // Optional
    'geoLocation': '', // country code can be fetched from https://www.lambdatest.com/capabilities-generator/
    'playwrightClientVersion': playwrightClientVersion
  }
}

// ここでtestInfoからcapabilitiesを作成している
const modifyCapabilities = (configName, testName) => {
  let config = configName.split('@lambdatest')[0]
  let [browserName, browserVersion, platform] = config.split(':')
  capabilities.browserName = browserName ? browserName : capabilities.browserName
  capabilities.browserVersion = browserVersion ? browserVersion : capabilities.browserVersion
  capabilities['LT:Options']['platform'] = platform ? platform : capabilities['LT:Options']['platform']
  capabilities['LT:Options']['name'] = testName
}

例えば、testInfo"chrome:latest:MacOS Ventura@lambdatest"の場合はcapabilities次のようになります。

{
  'browserName': 'Chrome',
  'browserVersion': 'latest',
  'LT:Options': {
    'platform': 'MacOS Ventura',
    'build': 'Playwright JS Build',
    'name': 'Playwright Test',
    'user': process.env.LT_USERNAME,
    'accessKey': process.env.LT_ACCESS_KEY,
    'network': true,
    'video': true,
    'console': true,
    'tunnel': false,
    'tunnelName': '',
    'geoLocation': '',
    'playwrightClientVersion': playwrightClientVersion
  }
}

以上で、複数の OS,ブラウザに対して LambdaTest にジョブを複数ケースを実行する方法を確認しました。

4. 実運用のための設定

最後にplaywright-test-jsサンプルを参考に実運用に合わせて行うとよさそうな設定を紹介したいと思います。

Playwright 単体でも実行できるようにしておく

LambdaTest 上で実行するテスト環境と Playwright のエミュレーター上で実行する環境をそれぞれ用意します。

playwright.config.js

/**
 * LambdaTest上でテストするブラウザの一覧
 * nameに「@lambdatest」が含まれていると、LambdaTestのブラウザーでテストする。
 */
const lambdatestDeviceConfig = [
  {
    name: 'chrome:latest:MacOS Ventura@lambdatest',
    use: {
      viewport: { width: 1920, height: 1080 }
    }
  },
  ...
  ]

/**
 * エミュレーター上でテストするブラウザの一覧
 */
const browserDeviceConfig = [
  {
    name: 'chromium',
    use: {
      ...devices['Desktop Chrome']
    }
  },
  ...
]

次に環境変数で LambdaTest と Playwright の切り替えを制御できるようにしました。
localhost とステージング環境を切り替えたい場合はbaseURLも環境変数で制御すると良いでしょう。

playwright.config.js

const config = {
  testDir: "tests",
  testMatch: "**/*.spec.js",
  timeout: 60000,
  use: {
     // localhostやstgのURLを渡す
     baseURL: process.env.PLAYWRIGHT_TEST_URL,
    ...
  },
   // LambdaTestの場合とPlaywrightの場合で設定を切り替える
   projects: process.env.RUN_LAMBDATEST ? lambdatestDeviceConfig : browserDeviceConfig
  };

module.exports = config;

package.json の scripts にて切り替えを制御した例が以下です

package.json
  ...
  "scripts": {
    "test:lambdatest": "PLAYWRIGHT_TEST_URL=https://stg.xxx.com && RUN_LAMBDATEST=true && npx playwright test --config=./playwright.config.js",
    "test:local": "PLAYWRIGHT_TEST_URL=localhost:xxxx && npx playwright test --config=./playwright.config.js"
  },
  ...
LambdaTest環境の実行例
$ npm run test:lambdatest
ローカルでPlaywrightを実行する例
$ npm run test:local

以下のように Playwright のレポートを見ることができます。

Android 実機へ対応

今回は詳細な説明をしませんでしたが、Android 実機でブラウザテストするサンプルも公式から用意されています。
https://github.com/LambdaTest/playwright-sample/blob/main/playwright-android-real-device.js

もし Android 実機でテストしたい場合は、先ほど紹介したlambdatest-setup.jsの中で Playwright と LambdaTest で場合わけしている部分に Android 実機の場合分けを追加することで対応できそうです。

// PCのテストの場合
const browser = await chromium.connect({
  wsEndpoint: `wss://cdp.lambdatest.com/playwright?capabilities=${encodeURIComponent(
    JSON.stringify(capabilities)
  )}`,
});
const ltPage = await browser.newPage(testInfo.project.use);

// Androidのテストの場合

let device = await _android.connect(
  `wss://cdp.lambdatest.com/playwright?capabilities=${encodeURIComponent(
    JSON.stringify(capabilities)
  )}`
);
await device.shell("am force-stop com.android.chrome");
let context = await device.launchBrowser();
let page = await context.newPage();

ただし、capability の構成が Android の場合は異なるので注意が必要です

// PCの場合
const capabilities = {
  browserName: "Chrome",
  browserVersion: "latest",
  "LT:Options": {
    platform: "Windows 10",
    build: "Playwright Single Build",
    name: "Playwright Sample Test",
    user: process.env.LT_USERNAME,
    accessKey: process.env.LT_ACCESS_KEY,
    network: true,
    video: true,
    console: true,
    tunnel: false,
    tunnelName: "",
    geoLocation: "",
    playwrightClientVersion: playwrightClientVersion,
  },
};

// Androidの場合
const capabilities = {
  "LT:Options": {
    platformName: "android",
    deviceName: "Galaxy S21 5G",
    platformVersion: "12",
    isRealMobile: true,
    build: "Playwright Android Build",
    name: "Playwright android test",
    user: process.env.LT_USERNAME,
    accessKey: process.env.LT_ACCESS_KEY,
    network: true,
    video: true,
    console: true,
    projectName: "New UI",
  },
};

Android の場合も PC の時と同じく公式サイトから capabilities の作成が可能です
https://www.lambdatest.com/playwright-android-capability-generator/

さいごに

Playwright と LambdaTest を組み合わせて、複数の OS、ブラウザでの E2E テスト自動化の方法を紹介しました。
iOS のリアルデバイスに対応できていないことは残念でしたが、公式の回答によると、Playwright 側の対応待ちとのことでその内対応しているかもしれません。
今後の動向もウォッチしていきたいと思います。
ありがとうございました。

GitHubで編集を提案
Linc'well, inc.

Discussion