Skip to content

E2E テストガイド (Playwright + Electron)

spec: e2e-automation-tests(Phase 0–4 完了)/ e2e-test-sdk(SDK 化)

このドキュメントは、tests/e2e/ 配下に実装した E2E テストの 設計判断 / 主要ヘルパー / よくあるハマり をまとめたもの。 日々の実行手順は tests/e2e/README.md を参照。

新規シナリオを書くとき は、まず tests/e2e/sdk/README.md を読む。 SDK の scenario() / controlPanel.tab().open() 等を使えば、本ドキュメントの「ハマりどころ」に意識せず 5〜10 行でシナリオが書ける。 本ドキュメントは SDK が wrap している lower layer の理屈を説明する位置付け。 SDK 化の経緯・設計判断・before/after 比較は E2E Test SDK 実装概要 を参照。


1. 何を保証しているか

シナリオカバー対象smoke 含
S1 起動スモーク (startup.e2e.ts)Electron 起動 / Control 画面描画 / エラーダイアログ不在
S2 ログ取込 (log-import.e2e.ts)seed した試合が DB と DB 閲覧 UI の両方で見える
S3 DB ビューア (db-viewer.e2e.ts)試合詳細を開いて Pick / Ban が描画される
S4 プロファイル切替 (profile-switch.e2e.ts)複数プロファイル作成 → 切替で UI 反映
S5 オーバーレイ同期 (overlay-sync.e2e.ts)Control の syncConfig → Overlay の onConfigUpdated が 1.5 秒以内に届く

smoke (PR ごと) は約 45 秒full (nightly)5 シナリオで約 1 分 20 秒

これらは「リリース前の手動回帰チェック」の自動化版という位置付け。 従来 docs/release-checklist などで人手で確認していた起動 / DB / プロファイル / 同期の経路を CI が見るようになった。


2. アーキテクチャ

tests/e2e/
├── sdk/                        # ★ ユーザー向け fluent API(新規シナリオはここ経由で書く)
│   ├── index.ts                # 単一 entry — `import { scenario } from '../sdk'`
│   ├── scenario.ts             # scenario(name).withFixture().run/.smoke
│   ├── pageObjects/
│   │   ├── ControlPanel.ts     # tab / detailButton / myUid / profile / expectVisible
│   │   ├── Overlay.ts          # page() / waitForConfigUpdate()
│   │   └── constants.ts        # TAB_NAMES / STORAGE_KEYS / SELECTORS
│   ├── fixtures/
│   │   ├── MatchFixtureBuilder.ts  # f.match().player().pick().ban().done()
│   │   └── schema.ts               # FIXTURE_DEFAULTS / Team / MatchType
│   ├── actions/
│   │   └── syncToOverlay.ts    # control 側 syncConfig → overlay listener 待ち合わせ
│   ├── templates/
│   │   └── example.e2e.ts.template
│   └── README.md               # SDK API 早見表 + 典型 4 サンプル
├── helpers/                    # lower layer(SDK の内部実装。直接使うのはエスケープ時のみ)
│   ├── launchApp.ts            # Electron を _electron.launch + control window pick
│   ├── withTempUserData.ts     # Playwright fixture: テストごとに OS tmp dir を割り当て
│   ├── seedDatabase.ts         # node:sqlite で matches.db を直接作る
│   ├── dbAccess.ts             # node:sqlite で matches.db を read-only で開く
│   ├── dismissOnboardingDialogs.ts  # 初回起動の onboarding / wishlist を強制 close
│   └── step.ts                 # demo モード用のナレーション / slowMo ラッパー
├── scenarios/
│   ├── startup.e2e.ts          # S1
│   ├── log-import.e2e.ts       # S2
│   ├── db-viewer.e2e.ts        # S3 (SDK ベース、dogfood 済)
│   ├── profile-switch.e2e.ts   # S4
│   └── overlay-sync.e2e.ts     # S5
└── README.md                   # 実行手順

production コードへの侵入:

  • electron/main.ts … 起動時に process.env.ELECTRON_USER_DATA があれば app.setPath('userData', ...) で隔離 dir に切替(PathResolver より前)
  • electron/windows/{Control,Overlay}WindowManager.tsELECTRON_USE_DIST=1 のとき dev でも dist/ の HTML を読む(後方互換で ELECTRON_PERF_MEASURE=1 も同等扱い)

CI ワークフロー:

  • .github/workflows/e2e.yml … PR で smoke を windows-latest 実行
  • .github/workflows/e2e-nightly.yml … 夜間 cron で full を実行

3. 主要ヘルパー

3.1 launchApp(options)

「Electron を起動して control window の Page を返す」だけが責務。 内部で:

  1. Windows の taskkill /F /IM electron.exe(前回テストの leftover を掃除)
  2. _electron.launch({ executablePath, args: ['.'], env })
  3. URL に control.html を含む Page を pick(devtools://chrome-error:// を弾く)
  4. dismissOnboardingDialogs(controlPage) で初回起動 dialog を閉じる
  5. cleanup() を返す(app.close() + leftover kill)

env として既定で ELECTRON_USE_DIST=1 を投入。userDataDir を渡すと ELECTRON_USER_DATA も設定される。

3.2 withTempUserData fixture

Playwright の test.extenduserDataDir を提供。 os.tmpdir() 配下に wsm-e2e-XXXXmkdtemp で作り、テスト終了時に rm -rftmpdir() 外を削除しないガードと、Windows の EBUSY バックオフ retry 入り。

3.3 seedFixtureDatabase(userDataDir, matches?)

この PR の中核。「ログを開いて parse して save」の 3 段階を全部 skip し、 matches.db に固定行を直接 insert する。

実装の要点:

  • node:sqlite を使用(Node 22+ 標準)。better-sqlite3 の native module は Electron バージョンに紐付くため Node test runner では rebuild が必要になり詰むので避けた。
  • production schema (matches / players / bans / picks / log_files) を再現
  • default fixture は 2v2 Rank 1 試合、自分 UID 1046101022 が Win で MVP(MatchListTab.isReflectable2v2Match の "2v2" 含む条件を満たす)
ts
import { seedFixtureDatabase } from '../helpers/seedDatabase';
seedFixtureDatabase(userDataDir);

開発時にも使える(scripts/dev/seed-fixture-db.ts):

bash
npx tsx scripts/dev/seed-fixture-db.ts ./tmp/dev-userdata
$env:ELECTRON_USER_DATA = "$PWD/tmp/dev-userdata"; yarn dev:electron

→ 毎回ログを取り込まずに固定 fixture が載った状態で起動できる。

3.4 step(page, label, fn)

CI 既定では何も挟まないが、demo モード(E2E_NARRATE=1 + E2E_SLOW_MS=1000)のときに:

  • 画面上部に黄色いバナー ▶ <ラベル> を inject
  • stdout に [step] <ラベル> を出力
  • fn() 完了後に E2E_SLOW_MS だけ wait

ユーザーが「いま何が動いている?」を目で追える。詳細は §6。

3.5 dismissOnboardingDialogs(page)

初回起動の 2 つの dialog(onboarding guide / support wishlist)を強制 close する。

  • localStorage に「見た / 表示しない」フラグを書き込む
  • <div id="root">aria-hidden になっている / MuiDialog-root が残っている間 Esc を最大 30 回連打

これがないと dialog 配下で root が aria-hidden 化し、getByRole('tab') が拾えなくなる。

3.6 dbAccess.ts

openTestDB(userDataDir) / countMatches / getMatchById 等。これも node:sqlite ベース。 test runner と Electron で別 native binding を持たずに済むのが大きい。


4. テストの書き方

4.1 最小テンプレ(DB seed なし)

ts
import { test, expect } from '../helpers/withTempUserData';
import { launchApp } from '../helpers/launchApp';
import { step } from '../helpers/step';

test('説明', async ({ userDataDir }) => {
  const { controlPage, cleanup } = await launchApp({ userDataDir });
  try {
    await step(controlPage, '<何をするか>', async () => {
      // ...
    });
  } finally {
    await cleanup();
  }
});

4.2 DB seed が必要なテスト

ts
import { seedFixtureDatabase } from '../helpers/seedDatabase';

test('...', async ({ userDataDir }) => {
  seedFixtureDatabase(userDataDir);   // 起動前に matches.db を作る
  const { controlPage, cleanup } = await launchApp({ userDataDir });
  // ...
});

カスタム fixture を渡したい場合は seedFixtureDatabase(dir, [{...}, ...]) で配列を直接渡す。

4.3 click が intercept される時

OperationalChecklistPanel など複数の overlay が pointer event を奪うため、 通常の .click() は flake する。代わりに dispatchEvent('click') を使う:

ts
await page.getByRole('tab').filter({ hasText: /^DB$/ }).first().dispatchEvent('click');

副作用(mousedown → mouseup → click)が必要なケースは evaluate で MouseEvent を順次 dispatch する。

4.4 narrow viewport の tab label

ControlPanel は useMediaQuery('(max-width: 1100px)')isNarrowScreen を判定し、 タブ label を 'DB閲覧' → 'DB''ログ解析' → 'ログ' 等に省略する。 Electron の既定 window 1200×800 は実描画 1100px 未満になるケースがあり narrow に倒れるので、 locator は filter({ hasText: /^DB$|DB.*閲覧/ }) のように両対応させる。

4.5 プロファイル選択

ProfileManager は localStorage[syncKey] を読んで初期選択する。 combobox を click → option を click のフローは intercept されやすいので、 localStorage に直接 id を書いて location.reload() する pattern を使う:

ts
await page.evaluate((id) => {
  localStorage.setItem('logAnalysis_selectedProfileId', String(id));
  localStorage.setItem('logAnalysis_selectedProfileIds', JSON.stringify([id]));
  location.reload();
}, profileId);

5. 実行モード一覧

script用途env
yarn test:e2e全シナリオ通常実行
yarn test:e2e:smokesmoke project(S1+S2)。CI/PR 既定
yarn test:e2e:headedGUI 表示で実行
yarn test:e2e:demo目視確認モード: 黄色バナー + 1 秒間隔E2E_NARRATE=1 E2E_SLOW_MS=1000 --workers=1
yarn test:e2e:debugPlaywright Inspector で手動ステップ実行PWDEBUG=1 --workers=1

CI 設定(playwright.config.ts):

  • retries: isCI ? 2 : 0(CI で稀な flake を吸収、local では即検出)
  • trace: on-first-retry(初回 retry 時のみ trace を取って artifact に出す)
  • E2E_DEMO=1 のときは trace/video/screenshot 全部を on にする(手元 demo 録画用)

6. demo モードの使いどころ

ユーザー(人間)が「ブラウザ自動化が単一ウィンドウで何をしているか目で追えない」となったときに:

bash
yarn test:e2e:demo db-viewer       # 1 シナリオだけ
yarn test:e2e:demo                 # 全シナリオを 1 秒間隔で

挙動:

  1. Electron 起動(既に表示される)
  2. step() の入口で画面上部に 黄色バナー を出す(▶ DB 閲覧タブへ移動 等)
  3. ステップ完了後に 1 秒 wait(人間が目で追える速度)
  4. stdout にも [step] ... を併記

リリース前の最後の確認や、新シナリオを書いたときの「期待通りに動いてる?」の目視確認に使う。 CI と完全に同じ test code が走るため、demo で目視 OK = CI でも OK。


7. よくあるハマり(実装時の lesson)

7.1 better-sqlite3 native module mismatch

開発時に Electron 経由で実行した直後に test runner で開くと、片方が NODE_MODULE_VERSION mismatch で落ちる。 tests/e2e/helpers/{seedDatabase,dbAccess}.tsnode:sqlite を使うことで完全回避。 production の electron/database/*.tsbetter-sqlite3 のままで OK(Electron でしか使われないため)。

7.2 deferred boot の dialog ブロック

initDatabase() が throw すると electron/main.ts の catch で dialog.showMessageBox が開き、 ユーザー操作なしには deferred boot が進まない(feature IPC が登録されないままロックする)。 yarn rebuild:db を忘れて Electron バージョンを上げると即この症状になる。 今は test 用に hang 検出経路はないので、Electron 再起動 / 再 build / rebuild:db を試す。

7.3 ELECTRON_USE_DIST を忘れると chrome-error://

dev で起動した Electron は既定で http://localhost:3000/control.html を取りに行き、Vite が無いと chrome-error 画面になる。 test では launchAppELECTRON_USE_DIST=1 を自動投入するため dist/control.html が読まれる。 新しく test runner 以外の経路で起動する場合は明示的に env を渡すこと。

7.4 「ログファイル読込」を本物で踏むと脆い

過去の実装では「dialog を mock して open-log-file IPC で読み込ませる」方式を採用していたが、

  • production の useLogAnalysisStateanalysisMode 既定値が 'realtime'StaticAnalysisPanel が render されない
  • open-log-file IPC が file content を返さず { success, path } だった production bug
  • ログ format の細部 (UID 形式 / Rank_2v2 文字列 / 2v2 filter) に縛られる

これらに振り回されたため、fixture DB seed 方式に切替えた(spec Phase 4.1)。 production bug は同 PR で electron/ipc/FileSystemHandlers.ts の修正として残してある。

7.5 __dirname が ESM で未定義

package.json"type": "module" のため、test ファイルで __dirname は使えない。 fileURLToPath(import.meta.url) 経由で resolve すること。

7.6 OverlayWindow の chrome-error 残骸

dev 起動時の Overlay は http://localhost:3000/overlay.html を取りに行って失敗する。 ControlWindowManager と同じ ELECTRON_USE_DIST 分岐が OverlayWindowManager にも必要(spec Phase 0.3 / 4.5 で対応済)。

7.7 タブクリックが intercept される

OperationalChecklistPanel の「すべて完了にする」ボタンや、.control-panel 自体が ヒットテストを奪うことがある。force: true よりも dispatchEvent('click') の方が安定。

7.8 narrow viewport で tab label 短縮

§4.4 参照。/^DB$|DB.*閲覧/ のような両対応 regex を最初から使う。


8. 関連ファイル

FileRole
playwright.config.tssmoke / full project 定義、retries / trace / video
package.jsontest:e2e* scripts
vitest.config.tstests/e2e/** を unit から exclude
tsconfig.jsontests/ を typecheck 対象に追加
electron/main.tsELECTRON_USER_DATA override
electron/windows/{Control,Overlay}WindowManager.tsELECTRON_USE_DIST 分岐
electron/ipc/FileSystemHandlers.tsopen-log-file の content 同梱(bug fix)
.github/workflows/e2e.ymlPR smoke
.github/workflows/e2e-nightly.ymlnightly full
scripts/dev/seed-fixture-db.tsdev 用 seed CLI
.spec-workflow/specs/e2e-automation-tests/spec / tasks / Implementation Logs

9. 拡張時のチェックリスト

新しいシナリオを追加する場合:

  • [ ] tests/e2e/scenarios/<name>.e2e.ts を作成(命名は *.e2e.ts
  • [ ] import { test, expect } from '../helpers/withTempUserData'userDataDir を受け取る
  • [ ] launchApp({ userDataDir }) で Electron を起動し、必ず try / finallycleanup() を呼ぶ
  • [ ] DB を使うなら seedFixtureDatabase(userDataDir) を起動前に呼ぶ
  • [ ] role / accessible name / data-testid ベースの locator のみ使う(class 名や生 DOM 文字列に依存しない)
  • [ ] click が flake するなら dispatchEvent('click') に切替
  • [ ] 主要ステップを step(page, label, fn) で wrap(demo モードで目視できるように)
  • [ ] smoke に含めるかは費用対効果で判断(PR 実行時間 5 分以内が目安)— 含めるなら playwright.config.tssmoke project の testMatch を更新
  • [ ] yarn test:e2e:demo <scenario名> で目視 OK を確認してから push