🎭

Playwright運用で役に立つかもしれないTipsたち

2024/12/22に公開

Social Databank Advent Calendar 2024 の22日目です。

こんにちは!saimyonです👶
前回の投稿からだいぶ時間が経ってしまいましたね…
みなさま、PlaywrightでE2Eテスト自動化していますでしょうか?
私も最近再び力をいれるようになり、その中でやってきたことを共有します。

Viteのホットリロードはやめる

最近弊社ではE2Eテスト専用の開発環境を立ち上げ、その中でコンテナ間通信を用いてE2Eテストを実行するようにしています。

(以前 CircleCI上でPlaywrightを実行する記事 を書いたのですが、現状はそこから変わっています。その件については今後別に記事にします。)
そうした中、なんてことないページの読み込みに時間がかかりすぎてTimeoutが多発し、テストどころではありませんでした。
そこでまずはどこで時間がかかっているかを調査してみました。
PerformanceNavigationTiming を使って各タイミングの時間を測定します。

const performanceTiming = await page.evaluate(() => {
  const performanceEntries = performance.getEntriesByType("navigation");
  if (performanceEntries.length > 0) {
    return performanceEntries[0].toJSON();
  }
  return null;
});

if (performanceTiming) {
  const formatTiming = (timing: number | undefined) => {
    if (typeof timing === "number") {
      return `${timing.toFixed(2)}ms`;
    }
    return "N/A";
  };

  console.log("Performance Navigation Timing:");
  console.log(
    `Navigation Start: ${formatTiming(performanceTiming.startTime)}`
  );
  console.log(
    `Unload Event Start: ${formatTiming(performanceTiming.unloadEventStart)}`
  );
  console.log(
    `Unload Event End: ${formatTiming(performanceTiming.unloadEventEnd)}`
  );

  // 省略 //
  
  console.log(
    `Load Event Start: ${formatTiming(performanceTiming.loadEventStart)}`
  );
  console.log(
    `Load Event End: ${formatTiming(performanceTiming.loadEventEnd)}`
  );
} else {
  console.log("No performance timing data available.");
}

こちらをPlaywrightでのページ読み込み時に実行して測定してみます。以下結果です。

どうやらDOM周りで時間がかかっていそうです。
さらにどのscriptで時間がかかっているか見てみます。

const performanceTiming = await page.evaluate(() => {
  return {
    scriptExecutionTimes: Array.from(document.scripts).map(script => {
      const start = performance.getEntriesByName(script.src || script.baseURI)[0]?.startTime;
      const end = performance.getEntriesByName(script.src || script.baseURI)[0]?.responseEnd;
      return { src: script.src, executionTime: end - start };
    })
  };
});

console.log('Script execution times:', performanceTiming.scriptExecutionTimes);

こちらで各スクリプトのsrc, executionTime(実行時間)が一括で取得できます。

Viteまわりで時間かかってそうなのがわかりました。
結論からすると、Viteのホットリロード が影響していました。
Viteのホットリロードについての詳細は割愛しますが、javascriptのソースコードの変更をリアルタイムにブラウザで確認できる機能です。素晴らしい開発者体験です。
Vite + Docker環境だと、

  • ソースコードを変更
  • Dockerボリュームを通じて変更がコンテナに即時反映
  • Viteが変更を検知、WebSocketを通じてブラウザに通知
  • ブラウザが変更箇所だけを更新

といった流れでホットリロードが実現されています。
このWebSocket通信により、ページの読み込みが終わったと判定されずにTimeoutしていたようです。
対応としては、E2Eテスト専用開発環境では本番環境用ビルドすることでことなきを得ました。
テスト対象がViteでホットリロードを有効にしていて、同じような悩みを持っている方には参考になるかと。
私はこの件で長いこと沼って時間を溶かしました…🫠

slowMoを設定する

みなさま、「通るはずのテストがなぜかたまに落ちる」みたいなことはありませんか?私はありました。
何が起きているのか(おそらくレンダリングタイミング関係だとは思う)は明確ではないですが、Playwrightの操作が速すぎることが一因にありそうでした。
そこで、slowMoオプション によってわざと操作を遅延させてあげることで対応できるのでは?と思い設定してみたところ、テストが安定して実行されるようになりました!

import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
  use: {
    launchOptions: {
      slowMo: 50,
    },
  },
});

今回は50msに設定してみました。
もし安定しない場合には設定してみるといいかもしれません。
とはいえテストケースが膨大な場合は実行時間に大きく効いてくると思うので、根本原因は突き止める必要はありそうですが…
安定のためにみなさまがしている工夫や対処をご教授いただきたいです!

テストに失敗したらSlack通知するように

上記でも触れましたが、最近弊社ではE2Eテスト専用の開発環境を立ち上げ、その中でE2Eテストを実行するようにしています。
そのなかでテストの失敗に気づく術が「Playwright実行コンテナに入ってログを見る」くらいしかありませんでした。
しかし、失敗には即座に気づきたいので、失敗したらSlack通知するようにしました。
下記のようにtestを拡張し、テスト実行後にその結果に応じてSlack通知するようにしてみました。

Utils/base-test.ts
import { test as base, TestInfo } from "@playwright/test";
import { IncomingWebhook } from "@slack/webhook";

const SLACK_WEBHOOK_URL = process.env.SLACK_WEBHOOK_URL || "";
const SLACK_WEBHOOK_CHANNEL = process.env.SLACK_WEBHOOK_CHANNEL || "";

const webhook = new IncomingWebhook(SLACK_WEBHOOK_URL, {
  channel: SLACK_WEBHOOK_CHANNEL,
});

async function notifySlack(message: string): Promise<void> {
  try {
    await webhook.send({
      text: message,
    });
  } catch (error) {
    console.error("Slack通知エラー:", error);
  }
}

export const test = base.extend<{ testNotification: void }>({
  testNotification: [
    async ({ page }, use, testInfo: TestInfo) => {
      // テスト実行前のコード
      await use();

      // テスト実行後のコード
      if (process.env.CI) {
        // CI環境ではSlack通知を行わない
        return;
      }
      if (testInfo.status !== "passed" && testInfo.status !== "skipped") {
        const message =
          `🚨 テスト失敗 🚨\n` +
          `テスト: ${testInfo.title}\n` +
          `ステータス: ${testInfo.status}\n` +
          `エラー: ${testInfo.error?.message || "Unknown error"}\n`;
        await notifySlack(message);
      }
    },
    { auto: true },
  ],
});

export { expect } from "@playwright/test";

各テストファイルで上記を呼び出して使ってあげることで、どのテストでもSlack通知がされるようになりました。

import { test, expect } from "./Utils/base-test";
test("トップページへの遷移", async ({ page }) => {
  await page.goto("/"); // ここでタイムアウトとかするとSlack通知される
});

わざとタイムアウトさせてみると…

届きました!(ANSIカラーコードが入ってしまっていますが!)
これでいつでもテスト失敗に気づけます!

まとめ

Playwright運用にあたって最近やってきたことの共有でした。
みなさまの一助になれば幸いです🙏

それでは!よいPlaywright Lifeを!🎭

ソーシャルデータバンク テックブログ

Discussion