AIのE2Eテストの落とし穴? Wrangler × Windowsに潜むトークン浪費ポイントのサムネイル

はじめに

Windows × Wrangler環境でE2Eテストを実行すると、コードは正しいのにナビゲーションリンクをクリックしてもURLが変化しない間欠的な失敗が起きることがあります。
原因はコードではなく .wrangler/state/v3 のSQLiteファイルがプロセスにロックされている環境の澱みで、PC再起動または npm run clean:wrangler で解消します。

E2Eテストでこんなことありませんか?

  • コードは正しいのにテストが落ち、AIと一緒にデバッグしても仮説を次々と棄却し続けるだけで解決しない。
  • Expected: "/account/settings" Received: "/account" というエラーが出るが、Linkコンポーネントの実装に問題が見当たらない。
  • 初回のテスト実行は成功するが、Wranglerサーバー再起動後の2回目以降から失敗する。

この記事をお勧めしない人

  • E2Eテストが失敗しても、とりあえずローカルで動けば本番も大丈夫だと考える人。
  • AIのデバッグ提案を、すべて正しいと無条件で信じている人。
  • 「PC再起動で直った」という解決策を、技術者として恥ずかしいと感じる人。

もし一つでも当てはまらないなら、読み進める価値があるかもしれません。

コードの世界だけで解決しようとすると

  • AIはフォーカストラップ・ハイドレーション・ルーティングなどコード側の仮説を提案するが、環境の澱みには届かない。
  • 論理的に正しい修正を繰り返すループに入り、数時間とトークンが消費される。
  • 撤退基準を持っていないと「もう少しで解決できる」という錯覚が続く。

撤退基準と信頼の階層化という明るい未来

  • この記事を読めば、「複数の仮説が棄却されたら環境リセットを試みる」という撤退基準と、ローカルE2E(速度)・デプロイ後E2E(信頼性)の役割分離が手に入る。
  • 具体的には、scripts/clean-wrangler.js でキャッシュを一括削除するスクリプトと、CLAUDE.mdへの追記内容が手に入る。
  • この方法は、このブログの開発で実証済みで、数時間溶かした後に確立した判断パターンだ。

私も同じでした

このブログの開発中、common.spec.ts のナビゲーションテストが間欠的に失敗し、AIと一緒にフォーカストラップ・ハイドレーション・useRouteLoaderData の3仮説を検証しましたが全て棄却されました。PC再起動で問題が消え、原因が .wrangler/state/v3 のファイルロックだと特定しました。以後は npm run dev:wrangler:clean を用意し、CLAUDE.mdに撤退基準を明記しました。

概要

特定のOS環境 × エッジランタイムエミュレータ × E2Eテストツールの組み合わせで、コードは正しいのにテストが間欠的に失敗する現象に遭遇しました。この記事では、問題の発見から、AIとのデバッグの限界、そして「撤退」という判断に至るまでのプロセスを記録します。

さらに、この具体的な事例から、環境起因バグへの普遍的な対処パターンを抽出します。

発生環境の特徴

  • 環境構成 : 特定OS × エッジランタイムエミュレータ × E2Eテストフレームワーク
  • 問題の性質 : 環境起因の間欠的失敗
  • 影響範囲 : ローカル開発環境のE2Eテスト

問題の発見と症状

アカウント管理画面のE2Eテストで、ナビゲーションリンクをクリックしてもページ遷移が発生しないという問題が発生しました。

特徴的だったのは、その間欠性です :

  • 初回のテスト実行: 成功
  • サーバー再起動後の2回目以降: 失敗

症状:

  • リンクをクリックしてもURLが変化しない
  • クライアントサイドナビゲーションが動作していない
  • コンポーネントのレンダリング自体は正常(リンクは表示されている)
  • エラーメッセージは期待URLと実際のURLの不一致を示している

調査と試行錯誤のプロセス

仮説1: グローバルイベントリスナーがナビゲーションを阻害している?

直前にモーダルコンポーネントにフォーカストラップを実装していたため、グローバルなキーボードイベントリスナーがナビゲーションに干渉している可能性を疑いました。

検証結果 : 該当ページではモーダルは閉じた状態なので、イベントリスナーは登録されていない。この仮説は棄却。

仮説2: サーバーサイドレンダリングのハイドレーション問題?

サーバーサイドとクライアントサイドでレンダリング結果が異なり、ハイドレーションエラーが発生している可能性を検討しました。

検証結果 : ブラウザコンソールにハイドレーションエラーは表示されていない。この仮説も棄却。

仮説3: 親ルートのデータ取得方法が影響?

親ルートの認証データを取得するフックを使用するよう変更した直後だったため、この変更が影響している可能性を疑いました。

検証結果 : データローダーの有無に関わらず、問題は解決せず。この仮説も棄却。

パターン認識: AIが出す仮説の共通点

ここで気づいたことがあります。 AIが出す仮説はすべて「コードの世界」の話でした 。

  • グローバルイベントリスナーの干渉
  • サーバーとクライアントの整合性
  • フレームワークのルーティング設定
  • データフローの型安全性

すべて論理的に正しい仮説です。しかし、どれも問題を解決しませんでした。

根本原因の特定

最終的に、 PC再起動 で問題が解消しました。

根本原因は「コードの世界」ではなく「環境の世界」にありました:

  1. エミュレータのキャッシュ問題 : ステートフォルダ内のデータベースやキャッシュファイルがプロセスによってロックされていた
  2. OSのファイルロック機構 : プロセスがファイルを開いている間は削除不可(Unixとは異なる設計)
  3. プロセスの残留 : 開発サーバー停止後もバックグラウンドプロセスが残り、古い状態を保持していた

なぜAIはこの原因に到達できなかったのか?

AIは「与えられた情報の中で論理的に正しい仮説」を出します。しかし、OSのプロセス状態、ファイルシステムのロック状況、キャッシュの腐敗といった「環境の澱み」は、コードを読むだけでは見えません。

これは AIの限界 ではなく、 AIへの入力の限界 です。人間が「環境を疑う」という判断をしなければ、AIはその方向に思考を向けられません。

私が設計した撤退基準と信頼の階層化

この問題を解決するため、私は「環境起因バグへの撤退基準」と「信頼の階層化」という2つの戦略を設計しました。

具体的には、以下のアプローチで時間とトークンを守ります:

  1. 撤退基準の設定 : 論理的仮説が複数棄却されたら、環境リセットを試みるルールを明示
  2. 信頼の階層化 : ローカルE2Eは速度、デプロイ後E2Eは信頼性と役割を分離
  3. 核オプションの用意 : キャッシュクリーンスクリプトを作成し、心理的ハードルを除去

このアプローチにより、単に「AIとデバッグし続ける」のではなく、 論理では届かない領域を構造的に回避する という、より高度なプロジェクト設計を実現しました。

達成した成果

改善項目 Before After
デバッグ時間 数時間の論理的仮説検証ループ 撤退基準により早期リセット
テスト戦略 ローカルE2Eの不安定さに振り回される 信頼の階層化で役割分離
環境問題への対処 論理的修正を繰り返す 環境リセットを合理的選択肢とする

その結果、 「コードは正しいはず」という論理的確信から「環境を疑う」というメタ判断への転換 に成功しました。

AIに「この仮説を試しましょう」と言われると、高確率で以下のような状況になります:

  • 論理的に正しい修正を実装する
  • しかし問題は解決しない
  • 次の仮説が提示され、また実装する
  • 気づけば数時間が経過し、トークンだけが消費されている

しかし、これは 論理の限界 です。一時的に納得できる仮説ですが、「環境の澱み」という目に見えない要因には届きません。

根本原因は、 AIは論理の世界で最強だが、環境の澱みは見えない ことにありました。AIは「コードの問題」には強力な相棒ですが、人間は「環境を疑う」というメタ判断を下す必要があります。

ここから先は、AIが絶対に提案しない 「撤退基準」という判断パターン の全貌と、キャッシュクリーンスクリプトの具体的な実装コード、CLAUDE.mdへの知見追記の実例、そして信頼の階層化によるテスト戦略の設計図を、すべて公開します。

この戦略と実装をコピーすれば、環境起因バグでのトークン浪費ループを回避し、 初回から撤退基準を持ったプロジェクト設計 を実現できます。私が実践で確立した撤退判断の基準と、実装済みの環境リセット手順を、ここで全て公開します。

解決策

では、実際に私が作成したキャッシュクリーンスクリプトの具体的な実装コードと、CLAUDE.mdに追記した環境起因バグの判断基準、そして信頼の階層化によるテスト戦略の設計図を公開します。

このスクリプトと戦略をそのままコピーすれば、「環境を疑うべきか」「いつ撤退すべきか」を毎回悩むことなく、 再現可能な撤退判断 を実現できます。また、なぜローカルE2Eとデプロイ後E2Eで役割を分けるべきか、環境リセットを心理的ハードルなく実行する仕組み、そして論理と環境の境界線を見極める判断基準も解説します。

使用した技術スタック

  • OS: Windows 11
  • フレームワーク : Remix v2
  • ホスティング : Cloudflare Pages/Workers
  • ローカル開発 : Wrangler (miniflare)
  • E2Eテスト : Playwright

発生した実際のエラーメッセージ

Expected: "http://127.0.0.1:8788/account/settings"
Received: "http://127.0.0.1:8788/account"

ナビゲーションリンク(tests/e2e/account/common.spec.ts)をクリックしても、URLが変化しない。

検証した3つの仮説と棄却理由

仮説1: フォーカストラップがナビゲーションを阻害している?

// EmailChangeModal.tsx
useEffect(() => {
  if (!isOpen) return;
  document.addEventListener('keydown', handleTabKey);
  return () => document.removeEventListener('keydown', handleTabKey);
}, [isOpen]);

検証結果 : /accountページではモーダルは閉じた状態(isOpen = false)なので、イベントリスナーは登録されていない。棄却。

仮説2: Remixのハイドレーション問題?

サーバーサイドとクライアントサイドでレンダリング結果が異なる可能性を検討。

検証結果 : ブラウザコンソールにハイドレーションエラーなし。棄却。

仮説3: useRouteLoaderDataの使用が影響?

// account.settings.tsx
const parentData = useRouteLoaderData<typeof accountLoader>('routes/account');

検証結果 : loaderの有無に関わらず問題再現。棄却。

環境起因の根本原因

Wranglerのキャッシュ問題 :

  • .wrangler/state/v3フォルダ内のSQLiteデータベースやキャッシュファイルがNode.jsプロセスによってロックされていた
  • Windowsのファイルロック機構により、プロセスがファイルを開いている間は削除不可
  • 開発サーバー停止後もバックグラウンドプロセスが残留し、古い状態を保持

具体的対処(この事例)

ステップ1: キャッシュクリーンアップスクリプトの作成

{
  "scripts": {
    "clean:wrangler": "node scripts/clean-wrangler.js && npm run db:migrate",
    "dev:wrangler:clean": "npm run clean:wrangler && npm run dev:wrangler"
  }
}
// scripts/clean-wrangler.js
const fs = require('fs');
const path = require('path');

const wranglerPath = path.join(process.cwd(), '.wrangler', 'state', 'v3');

if (fs.existsSync(wranglerPath)) {
  try {
    fs.rmSync(wranglerPath, { recursive: true, force: true });
    console.log('Wrangler cache cleaned successfully');
  } catch (error) {
    console.error('Failed to clean cache. Try closing all Node processes.');
    process.exit(1);
  }
}

ステップ2: CLAUDE.mdへの知見追記

#### Windows/Wrangler固有の注意事項

** 症状 **: 以下のいずれかが発生した場合
- テストが `ERR_CONNECTION_REFUSED` で全て失敗
- ナビゲーションテストが予期せず失敗
- コード変更が反映されない

** 解決策 **:
1. `npm run dev:wrangler:clean` を実行
2. 解決しない場合はPC再起動

汎用パターン(他の環境でも適用可能)

この事例から抽出した、環境起因バグへの普遍的な対処パターンです:

パターン1: 撤退基準を設ける

IF コードに問題が見つからない
AND 複数の論理的仮説がすべて棄却された
THEN 環境を疑い、リセットを試みる

深追いしない勇気が、時間とトークンを守ります。

パターン2: 信頼の階層化

環境 役割 信頼度
ローカルE2E 高速フィードバック ベストエフォート
デプロイ後E2E マージ前ゲート 信頼できる

ローカルE2Eは「速度」のため、デプロイ後E2Eは「信頼性」のため。目的を分けることで、ローカルの不安定さに振り回されなくなります。

パターン3: 核オプションを用意する

環境起因バグに対しては、「論理的に正しい修正」より「環境リセット」の方が効果的な場合があります。

  • 即時対処 : PC再起動、Docker再起動、キャッシュ全削除
  • 自動化 : クリーンスクリプトの用意
  • 心理的ハードル除去 : 「再起動で直った」を恥じない

学んだこと・まとめ

技術的な学び

E2Eテストが間欠的に失敗する場合、以下のチェックリストを確認することが重要です:

  1. コードの問題か? : ソースコード、テストコード、設定ファイルを確認
  2. 環境の問題か? : キャッシュ、プロセス残留、ファイルロックを疑う
  3. 撤退すべきか? : 複数の仮説が棄却されたら、環境リセットを試みる

AIとの協調開発における学び

  • AIは論理の世界で最強 : コードの問題には強力な相棒
  • AIは環境の澱みが見えない : OS状態、キャッシュ、プロセス残留は盲点
  • 人間の役割 : 「環境を疑う」というメタ判断を下すこと

今後のベストプラクティス

  1. 信頼の階層化 : ローカルE2Eは速度、デプロイ後E2Eは信頼性と割り切る
  2. 撤退基準の設定 : 「コードに問題がない」と確信したら環境をリセット
  3. 知見の構造化 : 環境固有の対処法をCLAUDE.mdに明記し、AIが同じ轍を踏まないようにする

「PC再起動で直った」は、恥ずかしい解決策ではありません。環境起因バグに対しては、最も合理的な撤退戦術です。深追いしないことも、エンジニアリングです。

この問題の本質は、.wranglerという外部状態への依存です。D1ローカル環境を「決定論的空間」として設計する思想は、この種の環境起因バグを根本から防ぐアプローチです。そしてその背景にある「AIが正しくしか動けない環境を作る」という設計原理はEnvironment as Lawで論じています。

関連リソース

あなたのリポジトリへの適用

どちらを採用しますか?(1つ選択)

SAFETY|安全性を高める

Wranglerキャッシュロック解消スクリプトの存在と動作を確認する

調査:

  1. npm run clean:wrangler コマンドが現在も定義されているか確認せよ
  2. .wrangler/state/v3のSQLiteファイルロック問題がWindows環境で再発していないか確認せよ
  3. E2Eテスト失敗時に「環境起因」と「コード起因」を切り分ける手順が文書化されているか確認せよ

この内容をClaude Codeに貼り付けて実行します

SIMPLICITY|シンプルに管理

E2Eテスト失敗時の「信頼の階層化」判断基準を整理する

調査:

  1. 現在のE2Eテスト失敗時のデバッグ手順を確認せよ
  2. 「環境起因 → コード起因 → テスト設計起因」の順で切り分けるフローが定義されているか確認せよ
  3. Wrangler固有の問題に対するトラブルシューティング手順が記録されているか確認せよ

この内容をClaude Codeに貼り付けて実行します

外部コードのローカル実行にはリスクがあります。ブラウザ環境での実行を推奨します。