こんにちは!フロントエンド開発グループの前田です。
E2Eテストには、以前から「実行時間が長い」「並列で動かすとテストデータが競合する」という課題がありました。
フロントエンドの中だけで閉じず、SRE・認証・QAの各チームに相談しながら進めた結果、E2Eの実行時間を10分以上から約3分半にできました。
振り返ると、kubellのValue「Beyond Boundaries」を体験した話でもあります。
E2Eテストを見直すことになった理由
現在、フロントエンド開発グループでは、大きなリファクタリングに向けた準備を進めています。このリファクタリングではさまざまな部分に変更が入る予定のため、Chatworkの各機能を網羅的に動作保証したいというニーズがありました。
Chatworkでは、jQueryで書かれた古い層とReactの層が混在しています。
リファクタリングでは両方に手が入るため、画面の動きが壊れていないかを、単体テストだけでは確かめきれない箇所があります。
そこで、壊れたらChatworkが使えなくなる主要な操作に絞って、E2Eテストを増やすことにしました。
E2Eテスト、思ったより時間がかかる
E2Eテストを増やしていくと、まず気になったのが1回あたりの実行時間です。完了まで10分以上かかっていました。
最近はAIを使った開発で、複数の開発を並行して進めることが増え、E2Eを実行する回数も増えています。1回ごとに10分以上待つのはつらいので、短縮したいと考えました。
SREに相談してみた
使っていない準備を削る
まず、他にも削れるところがないか、Claude Codeに分析してもらいました。
すると、テスト前の準備として、shard(テストを分割して並列に流す単位)ごとにnode_modules一式とCypress本体(562MB)をキャッシュから取得・展開していることが分かりました。これに1shardあたり約90秒かかっていました。あわせて、node_modulesのビルドも毎回実行していました。既存のワークフローを流用していたため、Playwright以外の分までまとめて持ってきていたようです。
実際に動いている内容を確認すると、Playwrightのテストがnode_modulesから使っているのは@playwright/testだけで、準備のほとんどは不要でした。テストが実際に使うものだけを入れる形にすると、各shardの準備がほぼなくなりました。
これだけでも、実行時間は
10分以上 → 8〜10分程度
まで短縮できました。
ランナーにPlaywrightを事前に入れておく
さらにSREに相談すると、E2Eのジョブは動き始めてからPlaywrightのイメージを取得していることが分かりました。ジョブが終わると環境ごと捨てられるので、shardごとに毎回同じ準備をやり直していました。イメージの取得が詰まって、テストジョブに進めないままrun(ワークフローの1回の実行)ごと失敗することもありました。
ChatworkのE2Eは、接続元IPを制限した環境に対して実行します。GitHubが用意するランナーはIPが固定されないため、IP制限を通過できるセルフホステッドランナー(自社管理のマシンで動くランナー)で動かしていました。そこで、ランナーのイメージにPlaywrightをあらかじめ入れておく構成に変えたことで、ジョブごとのイメージ取得がなくなりました。あわせて、shardごとに行っていたログインを1回にまとめました。ログイン状態はPlaywrightのstorageState(ログイン状態をファイルに保存する機能)に保存し、全shardで共有しています。
すると、
8〜10分程度 → 約3分半
まで短縮できました。
初めて3分台の結果を見たときは、思わず画面を二度見しました。
「E2Eだから時間がかかる」と考えていましたが、実際にはE2Eそのもの以外にも、セットアップやビルドなど時間のかかっている処理がありました。
テストデータが競合する
実行時間とは別に、もう一つ問題がありました。
複数のPRでE2Eテストを並列実行すると、同じテストアカウントやテストデータを使っているため、テスト同士が干渉してしまいます。
例えば、あるテストがデータを更新した直後に、別のテストが同じデータを参照すると、想定していた結果にならないことがあります。
その結果、テストが不安定になることがありました。
また、テストアカウントを共有していたため、E2Eラベル付きのPRが2件以上同時に走るだけで待ちが発生し、PRが集中した朝には待ちが最大16分程度に達したこともあります。
ここについては、フロントエンド開発グループで解決方法を考えました。
テストデータをスロット化する
そこで考えたのが、テストデータをスロットに分ける方法です。
PRごとに利用するスロットを分け、それぞれのPRが異なるテストアカウントやテストデータを使うようにしました。
イメージとしては、こんな感じです。
| PR番号 | スロット | 使うアカウント |
|---|---|---|
| #101 | Slot 1 | テストアカウント(slot 1) |
| #102 | Slot 2 | テストアカウント(slot 2) |
| #103 | Slot 3 | テストアカウント(slot 3) |
| #104 | Slot 4 | テストアカウント(slot 4) |
| #105 | Slot 0 | 既存の共用アカウント |
| #106 | Slot 1 | #101と同じスロット |
PR番号を5で割った余りでスロットが決まり、以降も同じ順に巡回します。
GitHub Actions側では、PR番号をスロット数で割った余りをそのままスロット番号にしています。
env: ACCOUNT_SLOT_COUNT: 5 steps: - name: Pick test account slot env: SLOT_KEY: ${{ github.event.pull_request.number || github.run_id }} run: | account_slot=$(( SLOT_KEY % ACCOUNT_SLOT_COUNT )) echo "account_slot=${account_slot}" >> "$GITHUB_OUTPUT"
手動実行はPR番号がないので、run_idで散らしています。
各スロットに対応するテストアカウントのログイン情報(メールアドレス・パスワード等)は、以下のように環境変数を動的に切り替えてテスト実行プロセスに引き渡しています。
# スロットに応じた環境変数のマッピング例 echo "TEST_USER_EMAIL=${{ secrets[format('E2E_USER_SLOT_{0}', env.account_slot)] }}" >> "$GITHUB_ENV"
これによって、別のPRのテストデータを触ってしまう問題を避けられるようにしました。E2Eラベル付きのPRが2件以上同時に走るだけで待たされる状態も、解消できました。各スロットのテストデータは、テスト実行後にクリーンアップしています。
「PR #101とPR #106が同時に走ったら、同じスロットになるのでは?」と思うかもしれません。
その場合は、同じスロットを使うrun同士をconcurrency(同じグループの実行を1つずつ流す設定)で直列化しています。後から来たrunは、先に動いているrunが終わるまで待ちます。そのため、テストデータは競合しません。
concurrency: group: e2e-playwright-serial-execution-group-slot${{ inputs.account_slot }}
同じPRに続けてpushした場合は、同じスロットに載るので、古いコミットのE2Eをキャンセルして新しい方だけを動かしています。
現状の構成では、テストアカウントを5組(スロット)に分けたことで、最大5PR分のE2Eを同時に実行できます。1PRの実行がランナー4本を使うため、5PR同時でも20本で、セルフホステッドランナーの上限30本の範囲内で運用しています。
ここで認証チームにも相談した
スロット化の仕組み自体は、フロントエンド開発グループで考えました。
ただ、実際に作ろうとすると、テストアカウントをどう作るのか、テストアカウントがbot対策に引っかからないようにするにはどうするのかなど、フロントエンドだけでは分からない部分も出てきました。
そこで認証チームに相談しました。認証の仕組みやテストアカウントについて教えてもらいながら、スロット化を進められました。
フロントエンド側で問題を整理して解決方法を考え、そのうえで専門的な部分を認証チームに相談しました。
E2EのことはQAチームにも聞いてみた
もう一つ、E2Eについて気になっていたことがありました。
「そもそもE2Eテストって、どういうところでハマりやすいんだろう?」
そこで、QAチームにも相談してみました。QAチームのこれまでの経験から、E2Eテストの設計や運用について、次のような話を聞きました。
- E2Eで起きやすい問題
- テストデータの扱い
- 不安定なテストへの対応
- E2Eとしてどこまで確認するか
自分たちだけで考えていると、どうしても「フロントエンドから見たE2E」になってしまいます。QAチームの知見を聞くことで、E2Eテストそのものについても別の視点を得られました。
振り返ってみると、いろんなチームに助けてもらっていた
それぞれのチームに相談した内容は、こんな感じでした。
| 問題 | 対応 | 効果 | 関わったチーム |
|---|---|---|---|
| テスト前準備が重い(不要なビルド) | 使っていないものを洗い出して削減 | 1shardあたり約90秒の準備がほぼゼロに。10分以上 → 8〜10分程度 | SRE・フロントエンド開発グループ |
| E2Eの実行時間が長い(イメージ取得) | ランナーにPlaywrightを事前インストール | 8〜10分程度 → 約3分半。イメージ取得詰まりによるrun失敗も解消 | SRE |
| テストデータの競合 | スロット化 | 待ち最大16分 → 解消。最大5PRを同時実行 | フロントエンド開発グループ |
| テストアカウント・認証 | 認証の仕組みを確認 | スロット別のテストアカウント5組を用意 | 認証チーム |
| E2Eの設計・運用 | E2Eに関する知見を相談 | E2Eを別の視点で見直せた | QA |
「E2Eは遅い」と決めつけなくてよかった
実際には、セットアップ、ビルド、イメージの取得、テストデータの競合といった問題が重なっていました。
一つずつ見ていくと、それぞれに改善できるポイントがあり、その問題を一番よく知っている人もそれぞれ違いました。ランナーや実行環境ならSRE、認証やアカウントなら認証チーム、E2Eの設計や運用ならQAです。その人たちに相談しながら進めたことで、自分たちだけでは気づけなかった問題にも手を入れられました。
「Beyond Boundaries」を実感した
kubellには、
Beyond Boundaries 「越境し、共に高め合う」
というValueがあります。今回のE2E改善は、このValueを実際の開発で経験した出来事だったと思います。
目の前のE2Eの問題を調べていったら、SREに相談することになり、テストデータの問題からは認証チームにも、E2EそのものについてはQAチームにも話を聞くことになりました。結果として、越境していたというのが実感です。
それぞれのチームに解決してもらったというより、自分たちで問題を考えて、分からないところはその領域に詳しい人に聞きながら、一緒に解決していった感覚に近いです。
問題を分けて、一番詳しい人に聞く
今回の改善を通して、「E2Eだから仕方ない」で止めずに、
「何に時間がかかっているんだろう?」
「この問題を一番よく知っている人は誰だろう?」
と問いかけることが大事だと感じました。
E2Eをこれから導入する方や、実行時間に悩んでいる方の参考になればうれしいです。
最後に、相談に乗ってくれたSRE・認証・QAのみなさん、ありがとうございました。


