Fresh Install テスト(smoke ハーネス)
「アプリを完全に初期状態でインストールしたときに正常起動するか」を、実 exe を空 userData で起動して自動検証する smoke ハーネス。ユニットテストがすり抜けた 6.1.0 の起動不能バグ(migration 前に新列参照インデックスを作り旧 DB を持つ全ユーザーが起動不能)のような、起動パス破壊を検出する。
実装: scripts/test/fresh-install/cli.ts(scripts/test/fresh-install/ 配下)
TL;DR(コマンド早見表)
bash
yarn test:fresh:dev # dev ビルドで検証(最速。CI 向き)
yarn test:fresh:packaged # 配布物(release/win-unpacked)で検証・インストールしない(初回はビルド)
yarn test:fresh:installed # NSIS silent install 込みで検証(★実機のアプリを上書き)
yarn test:fresh:upgrade # 旧スキーマ userData からのアップグレード起動検証共通オプション:
| オプション | 用途 |
|---|---|
--skip-build | 既存ビルド成果物を使う(packaged/installed のビルド数分を省略) |
--keep-sandbox | 成功時もサンドボックスを残す(中身を調べたいとき) |
--manual | アサーションせず fresh 状態の Manager を起動したまま待つ(packaged 版の手動確認)。Ctrl+C で終了 |
--scenario <name> | 単一シナリオだけ実行(freshManager / freshRecorder / freshViewer / freshTopology / upgrade / packagedLayout) |
--timeout <sec> | boot マーカー待機の上限(既定 dev=90 / packaged=120 / installed=120) |
何を検証するか
各シナリオが実 exe を空 userData で起動し、以下のプローブで成否を判定する。
| プローブ | 内容 |
|---|---|
bootMarker(<role>) | <userData>/logs/ のログ(Manager = update.log / Recorder = update-recorder.log / Viewer = update-viewer.log)に [boot] init complete role=… が出る(=起動完了) |
processAlive | boot 後もプロセスが生存 |
dbCreated | matches.db が生成され、主要テーブル(matches/players/bans/picks/profiles/season_* 等 13 個)が揃う |
dbAbsent | Viewer は read-only なので DB を新規作成しない |
recorderLock / recorderSpawned | Recorder が process.lock を取得(=単一インスタンス制御が機能) |
delegationEstablished | Manager が Recorder へ記録を委譲するモードに入る |
overlayHttp(served) / overlayHttp(absent) | OBS ブラウザソース配信 GET /overlay.html が 200 + HTML を返す(Recorder 単独 / own-writer Manager / 委譲 topology)。Viewer は配信サーバを持たないので接続拒否が期待値 |
obsDelegatedToRecorder | (topology)Manager ログに「OBS 配信は Recorder が担当」が出る=配信の所有者が Recorder(spec: obs-overlay-recorder-hosting) |
siblingExes | 実行ディレクトリに 3 つの exe(Manager/Recorder/Viewer)が揃う |
migratedColumn/Index / rowsPreserved | (upgrade)旧 DB に新列・新インデックスが追加され、既存行が保持される |
noLogErrors | 各プロセスのログに [ERROR] 行が無い(許容リストは logScan.ts の DEFAULT_ERROR_ALLOWLIST) |
シナリオ × target:
| target | シナリオ |
|---|---|
dev | freshManager / freshRecorder / freshViewer / freshTopology / upgrade |
packaged | packagedLayout / freshManager / freshRecorder / freshViewer / freshTopology(release/win-unpacked の配布物をインストールせずに起動) |
installed | packagedLayout / freshManager / freshRecorder / freshViewer / freshTopology |
仕組み
- サンドボックス隔離: 環境変数
ELECTRON_USER_DATAで userData を%TEMP%/wsm-fresh-<ts>-<scenario>/に差し替える(electron/main.ts冒頭で最初の Logger 呼び出しより前に適用)。実 userData(%APPDATA%/streaming-overlay-manager)は一切変更されない。 - 起動判定: Logger は console と独立して常に
<userData>/logs/のプロセス別ログ(Manager =update.log/ Recorder =update-recorder.log/ Viewer =update-viewer.log)へ書く。ハーネスはこれらを(各.prevも含めて)連結して読み、boot マーカーの出現を待つ。以前は全プロセスが同じupdate.logを書き、後から起動したプロセスのローテートで先のプロセスのログが追い出されていた(spec: settings-ownership)。 - ネットワーク抑止 env: smoke 実行では以下をセットして決定性を確保する。
WINGSTATS_NO_UPDATE_CHECK=1— 自動更新チェックを止める(installed で本物の更新が発火して Programs 配下を書き換える事故を防ぐ)WINGSTATS_NO_LOGIN_PROBE=1— 起動時ログイン確認(重い Chromium 起動 + ネットワーク依存)を止めるWINGSTATS_NO_FORCE_RECORDER=1— 単一ロール検証時に Recorder の強制起動を止める(topology シナリオでは付けない)
- ポート分離:
WINGSTATS_LIVE_WS_PORT/WINGSTATS_OBS_HTTP_PORTをシナリオごとに分けてセットする(launchEnv.tsのscenarioPortEnv)。freshManager = 47841 / 47842、freshRecorder = 47843 / 47844、freshViewer = 47845 / 47846、 freshTopology = 47847 / 47848、upgrade = 47849 / 47850(全体で 47841〜47850)。 本番インストール版(47831 / 47832)が同じ PC で動いていても衝突せず、サンドボックスの Manager が本番の Recorder に 繋がる事故も防ぐ。呼び出し側のシェルで同名の変数を設定していればそれを起点にずらす。overlayHttpprobe もこのポートを見る - 起動前のポート空き待ち: 各シナリオは起動前にそのポートが空くのを最大 120 秒待つ(
portsFreeprobe)。空かなければ 起動せず fail にし、掴んでいる pid を出す(EADDRINUSE をoverlayHttpのタイムアウトとして見せない)。 - 後片付け: 終了時は
taskkill /T(プロセスツリー)→ 猶予 →taskkill /F /Tで spawn した子孫ごと終了する。止めたプロセスは/Fの後も数十秒〜数分、ポートやサンドボックスを掴んだまま残ることがある(process.kill(pid, 0)は直後から「居ない」を返す)。 そのため「居るか」は OS のプロセス一覧(CIM)で見て、起動時に覚えた exe 名・作成時刻と同じプロセスかを毎回確かめ (PID の再利用で無関係なプロセスを止めない)、実行の最後に消えるまで最大 180 秒待つ。7 日より古い置き去りサンドボックスは起動時に自動掃除。
注意事項
test:fresh:installedは実機環境を変更する: NSIS silent install でインストール済み WingStats Manager を現ブランチのビルドで上書きし、実行中のWingStats *プロセス(常駐 Recorder 含む)を kill する。build-install-win スキルと同じ挙動。exit 2 になったらファイルロック(残プロセス)を疑う。- packaged / installed はビルドに数分かかる。2 回目以降は
--skip-buildで省略できる(release/win-unpacked/ インストール済み exe を使う)。 - ポータブル版は v7.2.1 の次の版から配布をやめたため、
portabletarget(portableSuite)はpackagedに置き換えた。 - 失敗したシナリオのサンドボックスは自動で保全され、パスがレポートに出る。
logs/update*.log(と.prev)を読めば起動がどこで止まったか分かる。
既知の失敗(KNOWN_FAILURES)
report.ts の KNOWN_FAILURES に載るプローブ失敗は ⚠️ 表示になり exit code に影響しない(修正を別 PR で進めているもの)。現在の登録はなし。
リリース前チェックリストへの組み込み(推奨運用)
リリースビルドを配布する前に、少なくとも以下を green(⚠️ known は許容)で確認する:
bash
yarn test:fresh:packaged # 配布 exe が空 userData で 3 ロール起動できる(インストールしない)
yarn test:fresh:installed # インストーラ経由でも起動できる(実機を上書きしてよいときのみ)dev / upgrade は軽いので日常的に回してよい。
トラブルシュート
- サンドボックスが残る:
%TEMP%/wsm-fresh-*は失敗時 /--keep-sandbox時に残る。7 日で自動掃除。手動ならGet-ChildItem $env:TEMP -Filter 'wsm-fresh-*' | Remove-Item -Recurse -Force。 portsFreeが fail する / 最後に「止めたプロセスが消えていません」と出る: 前の実行で止めたプロセスがまだ終了中。detail の pid が消えてから回し直す。WingStats *プロセスが残る: 通常はツリー kill で回収されるが、残ったらtaskkill /F /IM "WingStats Recorder.exe"等。ただし常駐 Recorder(ユーザーのインストール済み)も巻き込む点に注意。
関連
docs/operations/dev-fresh-userdata.md— 手動確認用の fresh userData サンドボックス(本ツールの手動版)- 設計書:
docs/superpowers/specs/2026-07-13-fresh-install-test-environment-design.md(VitePress のビルド対象外なのでパスのみ記載)