mcpField
M
meticulous-increase-coverage

meticulous-increase-coverage

github.com/alwaysmeticulousテスト・品質102.5k 回インストール今日更新
リポジトリ

meticulous-increase-coverage について

meticulous-increase-coverageは、記録済みブラウザーセッションのカバレッジ拡大を目指すチーム向けのMeticulousスキルです。基準値との比較、到達可能なUIフローの選定、`.meticulousignore`の除外案作成を支援します。

主な機能

  1. 01JavaScriptカバレッジの基準値測定
  2. 02到達不能なコードと対象箇所の特定
  3. 03対象ファイルにつながるUI操作の追跡
  4. 04ブラウザードライバーでのセッション記録
  5. 05カバレッジ増加の比較と除外ルールの提案

活用シーン

  1. 01記録セッションにないユーザーフローの特定
  2. 02カバレッジ向上を狙う機能領域の優先付け
  3. 03ブラウザーから到達不能と確認したコードの除外

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/alwaysmeticulous/skills --skill meticulous-increase-coverage

インストール後、Claude Code で/meticulous-increase-coverageを入力して読み込まれたか確認します。

同梱ファイルreferences/worked-example.md

詳細

Meticulous プロジェクトのカバレッジを増やす

この会話ですでに実行していない場合は、まず meticulous-cli-update を実行してください(認証とプロジェクトの選択も行います)。

提出するもの

個別の成果物を2つ提出してください。どちらも必須で、一方が他方の代わりになることはありません。

  1. 新たに記録したセッションによるテスト実行を1件以上。カバレッジが確実に拡張されていることを示してください。 ここで「確実に」とは、ベースラインと比較し、カバレッジがどのファイルでどれだけ増えたかを明示することです(ステップ7)。
  2. .meticulousignore の変更を提案するPR。 含めるのは、まったくカバーできないと確信できるパス(多くの場合、ディレクトリ全体)のみです。1回試して到達できなかっただけのものは対象にしないでください。PRの説明で触れ、除外リストには含めないでください。対象がカバーできないと判明した場合は、リストを水増しせず、その旨を伝えてください。

開始前:main で、作業ツリーがクリーンな状態で実行してください

以下のコマンドはすべて、ローカルのチェックアウトから情報を取得するCLIに依存しています。js-coverage はデフォルトで現在のgit HEADを参照し、trigger-test-run はデプロイ先にHEADを、ベースにoriginのデフォルトブランチとのマージベースを使用します。作業ツリーがクリーンな main では、それらが同一のコミットになります。これが望ましい状態です。差分はなく、HEADのみを対象とした実行になり、ステップ7のユニオンをAPIが受け付けます。

main 以外では、問題の切り分けに手間がかかります。すべての実行が_まったく同じ_コミットで実行されていないと、カバレッジのユニオンは拒否されます。また、PRのマージコミットはベースブランチが更新されるたびに再計算されるため、以前に更新前のベースに対してトリガーした実行は、新しい実行とユニオンできなくなります。

そのため、ステップ1の前に main をチェックアウトしてプルし、git status がクリーンであることを確認してください。

次に、対象にできるテスト実行があることを確認します。

meticulous agent test-run-for-commit

表示されたIDを控えておいてください。ステップ7では、必要に応じてこれにフォールバックします。「No test run found for commit …」 と表示された場合は、作業を中止してユーザーに報告してください。この実行がなければベースラインを作成できません。よくある原因は、CLIが誤ったプロジェクトを参照していることです。その場合は meticulous auth get-project で確認するよう提案してください。meticulous auth set-project が適用されるのはOAuthトークンのみです。APIトークンは1つのプロジェクトに紐付いているため、set-project は失敗します。また、認証情報が注入されている場合は、選択可能なローカルトークンがありません。こうしたケースでは、set-project ではなく別の認証情報を使う必要があります。

ステップ1 — カバレッジのベースライン

全体の主要な数値を取得します。コマンドは1回だけで、ステップ7と8で比較する数値です。

meticulous agent js-coverage --summary

リポジトリ全体のファイル別カバレッジを出力しないでください。このスキルでの確認事項はどれも、ディレクトリ1つ、またはカバレッジが低いファイルから順に調べる、といった範囲を絞った問い合わせです。1ページで答えが得られます。ページ分けされた各コマンドは、取得した行数と該当ファイル数(files 1-50 of 8214; …)をstderrに出力します。この通知を確認してください。ファイル数が答えそのものであることもよくあります。

「No test run found for commit …」 と表示された場合(上記の確認が成功していれば表示されないはずです)、作業を中止してユーザーに報告してください。別のコミットの実行をベースラインにするなどして回避しないでください。ステップ7でユニオンするには、新しい実行とベースラインが同じコミットで実行されている必要があります。

ベース実行のセッションは、まだすべてリプレイされていないことが多く、カバレッジが実際より低く見えることがあります。js-coverage にその旨が表示された場合は、meticulous agent complete-base-run を実行してください。デフォルトでは、これ以上スケジュールできるものがなくなるまで待機します。時間がかかることがあるため、処理が停止したと決めつけず、後で状況を確認するか再実行してください。その後、js-coverage を再実行します。unexecutedSessionCount が常に 0 になるとは限りません。恒久的に取得できないセッションもあり、js-coverage はそのようなセッションが少数であれば、いつまでも処理を拒否せずに許容します。

ステップ 2 — デッドコードと実際の対象を分ける

調べる対象は 2 種類あります。分けて考えると便利です。

  • .meticulousignore の候補:ディレクトリ全体で一律に 0% になっているファイルです。ブラウザーではまったく実行されないことが示唆されます。
  • カバレッジの対象:実際のユーザーフローで到達できるものの、記録されたセッションではたまたま到達していないファイルです。

それぞれに専用のクエリがあり、どちらも --orderBy を使います。このオプションは、サーバー上に実行可能行がある実行対象のすべてのファイルを順位付けします。0% のファイルも含まれます(型のみのファイルは除外されます)(--includeAllFiles は不要です)。同率の場合はパス順になります。--globFilter でクエリ対象をディレクトリに絞り込めます。このオプションは繰り返し指定できます。

除外候補

まずここから始めると、対象を絞り込めます。カバレッジデータではなく、リポジトリ自体の構成をもとにディレクトリを選びます(モノレポなら ls packages/、単一アプリならトップレベルのソースディレクトリ)。ディレクトリごとに、最も実行されたファイルを調べます。

meticulous agent js-coverage --globFilter "packages/<name>/**" \
  --orderBy=executedLines --includeLineCounts --limit=1
  • **そのファイルでも executedLines が 0 の場合:**そのディレクトリのすべてのファイルが 0% で、stderr の通知にその数が表示されます。これは、そのディレクトリがブラウザー向けに配布されない(バックエンド、CLI ツール、ドキュメント、E2E テストハーネスなど)ことを強く示唆しているため、.meticulousignore の候補になります。
  • **一致するファイルがない場合:**そのディレクトリはすでにカバレッジレポートの対象外であり、ルールを追加しても効果はありません。
  • **それ以外の場合:**そのディレクトリは使用されています。内部にデッドコードがあると思われる場合は、同じ方法でサブディレクトリを確認してください。

カバレッジデータに依存しない、より強力なシグナルもあります。これは、使用中のディレクトリ内に散在する個々のデッドファイルを検出します。ディレクトリ単位の確認では見落とされるファイルです。使用中のディレクトリにある 0% のファイルを一覧表示します(パス順で先頭に並びます。0% を超える行が出てきたら、ページ送りを終了してください)。

meticulous agent js-coverage --globFilter "<live-dir>/**" \
  --orderBy=coveragePercentage --order=asc --includeLineCounts --limit=100

各ファイルについて、ファイル名だけではなく、実際にエクスポートされているシンボルをコードベース全体で grep してください。

# example only — adjust the source root and extensions to this repo
grep -rn "theActualExportedName" <src-root> --include="*.ts" --include="*.tsx"

一致するのがファイル内の定義だけであれば、そのファイルは何からもインポートされていません。したがって、どれほど包括的なセッションでも、そのファイルを実行することはできません。これは沈黙からの推測ではなく、到達不能であることの証拠です。このファイル内にすでにディレクトリ単位のルールがある場合でも、確認する価値があります。ほかの点では十分に使用されているパッケージにも、デッドコードのエクスポートは蓄積します。

ほぼどこでも安全に除外できるカテゴリがいくつかあります。ディレクトリ単位の確認で一律に 0% であることを確認できれば、追加の追跡をせずに候補として提案できます。ディレクトリだけでなく、**/__tests__/** や **/*.stories.* のような glob も指定できます。

  • テストファイルとそのディレクトリ — __tests__/、__mocks__/、*.test.*、*.spec.*
  • Storybook — *.stories.*、__stories__/、.storybook/
  • テスト/モックのハーネスディレクトリ — testing/、mocks/、フィクスチャ
  • Node でのみ実行されるビルド、Lint、コード生成の設定 — *.config.*、setupTests.*、スクリプトのディレクトリ

これらの一部は、すでにカバレッジレポートの対象外になっている場合があります。ファイルに一致しない glob にルールは不要です。

フロントエンドにバンドルされると分かっているディレクトリ(メインアプリがインポートする共有コンポーネント/ユーティリティライブラリなど)が、全体で 0% になっている場合は注意してください。このパターンは、実際にデッドコードであるというより、ソースマップ/パスの帰属に関する問題である可能性が高いです。除外リストには含めず、未解決の問題として記録してください。

カバレッジの候補

対象はカバレッジが0%のファイルだけではありません。カバレッジが12%や40%のファイルは、通常、ある経路は実行されているものの、それ以外は実行されていないことを意味します。こうした部分的にカバーされたファイルは、手を付けると効果が出やすいことがよくあります。モジュールはすでに読み込まれているため、操作を1つ追加するだけで広い範囲をカバーできることがあります。テスト、ストーリー、除外候補のファイルが混ざらないよう、アプリの実際のソースに範囲を絞り、カバーできる範囲が最も広がるファイルから始めてください。

meticulous agent js-coverage --globFilter "<app-src>/**" \
  --orderBy=uncoveredLines --includeLineCounts --includeCoveragePercentage \
  --limit=50

この一覧にはカバレッジが0%のファイルと部分的にカバーされたファイルの両方が含まれており、割合の列で見分けられます。効果が同程度なら、カバレッジが0%のファイルより部分的にカバーされたファイルを優先してください。カバレッジが最も低い部分的にカバーされたファイルを確認するには、代わりに--orderBy=coveragePercentage --order=ascを使います。カバレッジが0%のファイルを除外するフィルターはないため、一覧の先頭には0%のファイルがまとめて表示されます。--offsetでその範囲を読み飛ばしてください。

候補は、カバレッジが最も低いファイルだけを選ぶのではなく、機能領域ごとにまとめてください。記録したフロー1つで、関連するファイル群全体のカバレッジが上がることがよくあります。そのため、複数のファイルが一様に5~20%のディレクトリは、分かりにくい分岐の奥にある単独の0%ファイルよりも、優先度の高い対象です。機能ディレクトリに範囲を絞ってクエリを再実行し、そのファイル群を確認してください。

カバレッジが0%または低いファイルの中には、誰も通らないUI経路にあるのではなく、デフォルトでオフになっている機能トグルによって実行が制限されているものもあります。新しいフローが必要だと決めつける前に、トグルの一覧とファイルの実行条件を確認してください。ローカルでトグルをオンにすれば、動いていないように見えるファイルを簡単に対象にできることがあります。

Step 3 — 推測せず、呼び出し元をたどる

候補の各ファイルについて、実際の呼び出し元を特定してください。たとえば、次のようにします。

# example only — adjust the source root and extensions to this repo
grep -rln "<ExportedThing" <src-root> --include="*.tsx" | grep -v test

呼び出し元を読み、以下を確認してください。

  • 簡単に説明できるUI操作(特定のボタンやメニュー項目など)で到達できます。機能フラグ、無効になっている設定(この環境ではオフの課金/SSOのトグルなど)、または特定のオブジェクトタイプでのみ実行される条件分岐の奥にあるものではありません。
  • 対象がフックの場合は、それを呼び出すすべての分岐を確認してください。フックは条件付きで呼び出されることがよくあります。ある分岐では、まったく別の仕組み(アプリ独自のナビゲーションフックではなく、通常のルーター<Link>を使う、または画面全体を遷移させるのではなくサイドパネルを開くなど)を経由する場合があります。候補の操作が実際にどの分岐を通るのか確認してください。

ステップ 4 — フローを操作する

次の3つのドライバーはいずれも正しく記録されます — 異なる2つのアプリで検証済みです。

  • Claude in Chrome — ユーザー自身のログイン済み Chrome プロファイルを操作できる唯一のドライバーなので、認証が必要なアプリでもログインフローは不要です。ただし、入力経路が動作しなくなることもあるため(下記参照)、早めに確認し、切り替えられるようにしておきましょう。
  • Playwright と agent-browser — CDP で起動するブラウザーで、より高速かつスクリプトで操作しやすく、ここでのすべてのテストで安定していました。デメリットは毎回新しいプロファイルになるため、サインインが必要なことです。さらに agent-browser は、対象が別の要素に覆われている場合、その要素名を示してクリックを拒否します。これにより、ほかの2つなら気づかずに実行してしまう、誤クリックの一種を検出できます。

どのツールを選ぶ場合も、現実的なビューポートを設定してください。CDP ブラウザーのデフォルトは小さめ(幅約1280px)ですが、実際の Chrome ウィンドウはその倍ほどの幅であることもよくあります。レスポンシブレイアウトでは幅によって異なるコンポーネントが描画されるため、デフォルトのビューポートでは、気づかないうちに異なるコードをカバーしたり、操作しようとしていたコントロールが隠れたりする可能性があります。

ツールの選択よりも、以下のルールのほうがはるかに重要です。

記録されるのは信頼されたイベントのみです。 レコーダーはページの JS で合成されたイベントを無視するため、element.click()、input.value の代入、自作イベントのディスパッチでは、アプリをそれらしく操作できても、記録は一切残りません。ページ上では正しく動いているように見えても、セッションは空のままです。すべての操作を、ツールの実際の入力アクションを通じて行ってください。

操作が反映されないときは、方法を変える前に、どちら側に問題があるのかを確認してください。キャプチャ用プローブをインストールし、操作を繰り返します。

window.__ev = [];
["pointerdown", "click", "keydown", "change"].forEach((t) =>
  window.addEventListener(
    t,
    (e) => window.__ev.push({ t, trusted: e.isTrusted }),
    true,
  ),
);
  • 何もキャプチャされない → 入力がページに届いていません。別のセレクターや座標を試しても解決しません(下記の拡張機能が動作しなくなる場合の注意を参照してください)。
  • キャプチャされたが trusted: false → ページには届いていますが、記録されません。どこかでイベントを合成しています。
アクションが何も記録されない3つのケース

ブラウザー上ではどれも成功したように見えます。そのため、問題が厄介なのです。事実が判明するのは、ずっと後になってカバレッジの数値を確認したときです。

ネイティブの<select>。 フォーム入力アクションで値を設定したり、JSで値を割り当てたりすると、isTrusted: false のchangeが発火します。アプリは反応して値も画面上で更新されるため、動作したように見えますが、レコーダーはこれを無視するので、リプレイ時にソートやフィルターは実行されません。<select>をクリックする方法も使えません。ドライバーから見えないOSレベルのポップアップが開くためです。うまくいくのは、要素にフォーカスを当てて、選択肢の表示テキストの最初の文字("p" → "Priority")を押す方法です。これにより、信頼できるkeydownと信頼できるchangeの両方が発生します。先頭の文字が同じ選択肢を切り替えるには、その文字を繰り返し押します。changeリスナーでe.isTrustedを読み取って確認してください。どちらの場合も値は更新されるため、値だけでは何も分かりません。

修飾キーを使うショートカット。 リプレイで修飾キーが再現されるのは、独立した修飾キーのkeydownが記録され、なおかつキーが押された状態の場合だけです。一部のドライバーは基本キーのkeydownしか送信しません。Claude in Chromeのcmd+kとagent-browserのpress Alt+ArrowRightはどちらも、修飾キーのフラグが設定された単一のkeydownだけを記録し、修飾キー単独の押下は記録しません。そのため、ライブでは問題なく動作する一方、リプレイ時にはフラグがクリアされ、ハンドラー本体が実行されません。Playwrightのpress('Alt+ArrowRight')は独立した押下を記録し、カバレッジで確認したところ、正しくリプレイされます。

そのため、同等のクリック対象がある場合は、そちらを優先してください。キーの組み合わせを使う必要がある場合は、Playwrightで操作し、後でカバレッジを確認してください。if (… && event.metaKey)の行はカバーされているのに本体がカバーされていない場合、keydownは届いていますが、条件がfalseと評価されています。これが該当するケースです。

ダブルクリック。 ダブルクリックが単一クリックとして記録されることがあります。その場合、リプレイでonDoubleClickが発火せず、そのセッション内の後続イベントはすべて、開かなかったUIを対象にするため、カバレッジが_低下_します。記録されたイベント数が、押下と解放のペア2組になっていることを確認してください。ダブルクリックでしか使えない機能は、カバレッジで確認できるまでは問題があるものとして扱います。

エージェント主導の記録では再現できないインタラクションもあります

まれに、インタラクションが問題なく記録され、ライブでは目に見えて動作するのに、実行されるはずのハンドラーがリプレイ時には発火しないことがあります。エラーはなく、対象のファイルのStep 7のユニオンにおける割合はベースラインと_完全に_同じです。確認された事例の1つは、ドロップダウン内で独自のポータルとして描画されるコンテンツにある入力欄への値の入力です(フィルターチップや、「...」メニューの背後にあるビュー名の変更など)。同じポータル内での通常のクリックと、メインページのツリーにある入力欄で同じように入力してEnterを押す操作は、どちらも問題なくリプレイされます。そのため、これはドライバーの問題ではなく、ポータル内でのキーボード入力やテキスト入力に固有の問題です。

ライブで操作できたというだけで、入力値を使った対象が機能したと決めつけず、Step 7の差分を確認してください。この種のインタラクションでしか再現できず、カバレッジも変化しない場合は、このフローにおけるエージェント主導の記録の限界です。リトライを増やし続けるのではなく、未解決として報告し、そのフローは人間が手動で操作することを提案してください。

claude-in-chrome の注意点
  • スクリーンショットは縮小されています(約 0.6 倍)そのため、CSS ピクセルとは一致しません。スクリーンショット上で読み取ったクリック座標を使うか、getBoundingClientRect() の中心座標に screenshotWidth / window.innerWidth を掛けてください。サイズ変更後は必ず再計算し、前のページで使った座標を再利用せず、スクリーンショットを撮り直してください。レイアウトはずれるため、古い座標では別の要素をクリックしてしまい、意図しない操作が記録されることがあります。
  • クリックする直前に必ず ref を取得し直し、ページやタブをまたいで再利用しないでください。 数値を予測しないでください。read_page は順番どおりに番号を出力するとは限りません。ref と座標の信頼性は同等なので、使いやすい方を選んでください。これは claude-in-chrome に限った問題ではありません。agent-browser の snapshot の ref も同じように古くなり、再利用すると、意図したものではなく、その ref が現在指している要素を気づかないままクリックします。実際に一度、古い ref がテーブルの「Add New」操作要素をクリックし、空のレコードが作成されてしまいました。DOM が変化した可能性がある場合は、クリックの直前に必ず新しいスナップショットを取得してください。また、クリック直後にページやレコード数が予想外に変化したら、無関係なバグではなく、ref が誤っていた兆候と考えてください。続行する前に、作成されたものを削除してください。
  • 入力の送信が断続的に機能しなくなります。 クリックやキー入力は成功したと報告され、読み取りも引き続き機能しますが、ページには何も反映されません。ref と座標は同時に機能しなくなるため、これはセレクターの問題ではありません。また、新しいタブを開く、ページを再読み込みする、待機する、再試行するといった方法でも解消されません。Chrome を完全に再起動すると改善しますが、根本的な解決にはなりません。一連の操作を進める前に、最初の操作後にプローブを確認してください。 入力が反映されない場合は、セレクターの方式を切り替えるのではなく、Playwright または agent-browser に切り替えてください。Claude in Chrome が同じページで同じ瞬間に機能しなくなったときも、どちらも終始安定して動作しました。
操作してから検証する
  1. 対象の URL に移動し、実際の操作を行います。
  2. スクリーンショットだけに頼らず、DOM を確認して操作が成功したことを確かめます(例:document.body.innerText.includes(...))。ツールチップが表示されただけでも成功したように見えることがあります。想定したページにまだいることも確認してください。気づかないうちにログイン画面へリダイレクトされたアプリでは、何も見ずに座標をクリックしても空白部分がクリックされ、イベントが一件もないセッションが記録されます。
  3. タブを閉じます。

次に、何かを実行する前に、記録を簡単に確認します。タブを閉じてから約10秒待ち、セッションが完了していることを確認してから、記録されているか確認します。

meticulous agent sessions --limit 10 --excludeSyntheticSessions \
  --includeDurationSeconds --includeNumberUserEvents \
  --includeNumberUrlsVisited --includeStartUrl --includeAbandonedReason

作成された行を確認します。

  • 行がまったくない — まだ何もアップロードされていません。記録に失敗したと判断する前に、もう少し待ってください。
  • numberUserEvents が 0 — レコーダーはユーザー入力を検出していません。クリックがページに届いていないか、信頼された入力ではなく合成された入力です。上記の入力配信の確認に戻ってください。このセッションを再生しても意味がありません。
  • 何度も移動したのに numberUrlsVisited が 1 — 後続のページはこのセッションに記録されていません。それらのページは別のセッションに記録されたか(問題ありません。その ID も取得してください)、再生できない末尾部分として取り込まれなかったかのどちらかです(手順5のハードナビゲーションに関する注意を参照)。
  • durationSeconds が 300 を超えている — 5分を過ぎた部分はすべて、再生時に通知なく切り詰められます(手順5)。残ることを期待せず、時間を超えた部分を独立したセッションとして再記録してください。
  • abandonedReason に値がある — レコーダーはセッションを途中で断念しました(手順5の10分制限を参照)。再生しても意味がありません。
  • startUrl が操作したページと異なる — 目的のナビゲーションは、想定していたものとは別のセッションに記録されています。

タブを閉じて初めて、セッションは完了します。 タブを開いている間、行に表示されるのはそれまでにアップロードされたチャンクだけです。そのため、numberUserEvents が少ない、または 0 でも、「まだフラッシュされていない」という意味であり、「記録に失敗した」という意味ではありません。このスキルで計測したセッションは、タブを開いている間はイベント数が 0 でしたが、閉じると 12 になりました。早まって判断したことで、問題なく記録できていた内容を最初からやり直すところでした。

イベントは短い間隔(数秒ごと)でアップロードされますが、ページのアンロード時にフラッシュされていないものは sessionStorage に一時保存され、同じオリジンへの後続のページ読み込み時 に再送されます。そのため、セッションの末尾部分は次回の訪問まで遅れることがあります。ブラウザーを強制終了するより、別のページへ移動するようにしてください。また、記録をすぐに評価しないでください。

一度に複数の対象を記録する場合は、最後に一度だけではなく、対象ごとにこの確認を実行してください。 最後にまとめて確認すると、どの操作でセッションが失われたのか特定できません。どれをやり直す必要があるか調べるためだけに、すべての操作を再実行することになります。

ステップ 5 — セッション時間の予算と終了時の注意事項

  • クラウドでのリプレイはセッション時間が 5 分に制限されています。 その後に記録された内容は、リプレイから気づかれないまま切り捨てられます。実行は成功し、正確であると報告されるため、確認しない限り記録の欠落には気づきません(スナップショットのルートは途中で終わり、許可されたイベント数はセッション中のクリック数よりずっと少なくなります)。感覚に頼らず、agent sessions --includeDurationSeconds で時間を確認してください。300 秒を超えるセッションは、末尾が失われます。
  • 操作のペースがこの予算を消費します。 検索・クリック・確認の各ラウンドトリップで、記録されるセッション時間が 10〜60 秒かかります。タブを開く前にフローを完全に計画し、操作をできるだけ少ないラウンドトリップにまとめ、操作間の待ち時間は短くしてください。フローの途中ではなく、後から記録を確認し、4 分以内を目指してください。長時間の一連の操作を 1 回行うより、短いセッションを複数回行うほうが効果的です。
  • 直接 URL に移動する方法は、各対象ページへの正当な高速ルートであり、問題なくリプレイできます。 時間のかかるクリック経路よりこちらを優先してください。ただし、ハードナビゲーションを行うと記録は複数のセッションに分割されるため、後ですべてのセッション ID を収集し、--sessionIds に渡してください(ステップ 6)。通常は問題ありません。それぞれのセッションがリプレイの制限時間内に収まるためです。注意が必要なのは、移動を短時間に続けて行う場合です。前のページから 1〜2 秒後に表示されたページは、独自のセッションとして開始されず、前のセッションの 末尾 に追加されることがあり、その末尾部分はリプレイされないことがよくあります。そのため、操作中はページが問題なく表示されていても、カバレッジにはまったく反映されないことがあります。確認したいページは、それぞれ移動後に約 10 秒間表示したままにしてから次へ進み、ステップ 6 でそのページが独立した startUrl として記録されていることを確認してください。同じ注意は、onSubmit ハンドラーが設定されていない通常の <form> の送信にも当てはまります。SPA の画面遷移ではなくブラウザーの再読み込みが実行されるため、その直後に移動すると、記録したものの未フラッシュのセッションが失われることがあります。
  • レコーダーの制限: タブを開いてから 10 分が経過すると、セッション全体が「放棄」としてマークされます。アップロードは 5 秒間隔でフラッシュされるため、最後の操作を終えてから約 6〜8 秒待ってタブを閉じてください。

ステップ 6 — セッション ID を収集してテスト実行を開始する

まず、実際に記録した内容を新しい順に一覧表示します。

meticulous agent sessions --limit 20 --excludeSyntheticSessions \
  --includeDurationSeconds --includeNumberUserEvents \
  --includeNumberUrlsVisited --includeStartUrl

numberUserEvents が 0 の行は、リプレイしても何も起こらず、実行結果を薄めるだけなので除外してください。durationSeconds が 300 を超える行があれば、リプレイされるのは最初の 5 分だけです。セッションの末尾部分のカバレッジは、フロー全体が実行されたと思い込まず、存在しないものとして扱ってください。

記録時刻と startUrl で自分のセッションを特定します。ほかの人や、同じプロジェクトを参照するほかのアプリも記録を行うため、最新の N 行が自分のものだと決めつけないでください。一覧が多い場合は、--recordedSince と --visitedUrlFilter を使うと、すばやく絞り込めます。

操作したページ数よりセッション数が多くなることを想定してください。通常、ハードナビゲーションを行うと 1 つのセッションが終了して次のセッションが始まるため、5 ページを一巡すると 5 つの ID が生成されることがあります。すべて収集してください。URL が startUrl として一度も表示されないページは、前のセッションの末尾に取り込まれてリプレイされない可能性があります。カバレッジに含める必要がある場合は、そのページを個別に再記録してください。

次に、以下を実行します。

meticulous agent trigger-test-run --sessionIds "<id1>,<id2>,..."

ステップ 7 — 生の差分ではなく、和集合で比較する

まず集計値から確認します。1 回の呼び出しで「何か変化があったか」に答えられ、ステップ 1 の --summary と直接比較できます。

meticulous agent js-coverage --summary --headPlusTestRunIds "<newRunId>"

これにより、新しい実行結果が HEAD から解決されたベースラインの実行結果に統合されます(ステップ 1 で使用したものと同じ実行結果です)。つまり、ベースラインのカバレッジに新しいセッションのカバレッジが加わります。コミットの解決では、明示的なセッションセットを指定した実行結果は除外されるため、今実行したばかりの結果がベースラインとして選ばれることはありません。--sessionIds はその実行結果の選択セットにセッションを追加するのではなく、置き換えた ため、和集合が必要です。新しい実行結果単体ではベースラインよりカバレッジが大幅に少なく、直接比較すると大量のリグレッションがあるように見えてしまいます。和集合ではカバレッジを増やすことしかできないため、その値がステップ 1 の値を下回ることはありません。

新しい実行結果だけを渡します。実行結果を自分自身と統合することはできません。

次に、カバーする対象として指定したファイルについて確認します。各対象を渡し(--globFilter を繰り返します)、和集合を指定する場合と、ベースラインとして和集合を指定しない場合の両方を実行します。

meticulous agent js-coverage --headPlusTestRunIds "<newRunId>" \
  --globFilter "<target1>" --globFilter "<target2>" \
  --includeLineCounts --includeCoveragePercentage
meticulous agent js-coverage \
  --globFilter "<target1>" --globFilter "<target2>" \
  --includeLineCounts --includeCoveragePercentage

HEAD がゴールデンセットの実行結果以外に解決されたことを示す兆候は 2 つあります。和集合が「is the run being queried」というメッセージで拒否されるか、集計値がステップ 1 の値を_下回る_場合です(別の限定されたセッションセットに統合しただけであり、リグレッションではありません)。最も可能性が高い原因は、除外が適用される前に作成された、セッションを固定した実行結果です。いずれの場合も、ステップ 0 のベースライン ID を使って、両方の側を明示的に指定してください。

meticulous agent js-coverage --summary --testRunIds "<baselineRunId>,<newRunId>"

対象ごとのクエリでは、--headPlusTestRunIds の代わりに同じ --testRunIds を使います。

実行結果で異なるコミットが実行されていたために和集合が拒否された場合は、main/クリーンなツリーという前提条件に引っかかっています。このスキルの冒頭を参照してください。

ステップ 8 — 検証して報告する

トレースした各対象ファイルについて、カバレッジは増えましたか? ステップ 2 で候補に挙がった各 .meticulousignore について、ユニオンで 0% のままでしたか? ステップ 2 のディレクトリごとのチェックを再実行し、--headPlusTestRunIds "<newRunId>" を追加します。executedLines が引き続き 0 なら、セッションで行ったことは何もそのファイルに到達していません。

十分にトレースした対象のカバレッジが増えていなくても、記録に失敗したと決めつけないでください。まずセッションを再確認します(ステップ 4:セッションは存在するか、ユーザーイベントと URL へのアクセスを記録しているか、放棄されていないか、startUrl は操作したページか)。次に、クリックが期待したファイルではなく、兄弟コンポーネントや親コンポーネントを通っていないか再確認します(ステップ 3)。対象が未解決のままなら、正直に報告してください。裏付けとなるカバレッジの数値なしに成功を主張しないでください。

テスト実行によりカバレッジに到達可能であることは証明されますが、セッションが選択セットに含まれるまでは、定期的な実行に変化はありません。次のセッション選択で採用されるのを待つのではなく、対象のカバレッジを増やしたセッションを昇格させてください。

meticulous agent promote-sessions --testRunId="<newRunId>" \
  --sessionIds="<id1>,<id2>"

ステップ 7 でカバレッジの増加が確認できたセッションだけを渡してください。昇格したセッションは、以降すべてのテスト実行で実行されるためです。すべてのセッションが対象の場合に限り、--sessionIds を省略します。次のセッション選択では、残りのセットと合わせて再評価され、冗長だと判明したセッションは除外されます。

レスポンスの updatedBaseTestRunId は、コミットの更新後のベース実行を示します。これはベースラインに、昇格したセッションのリプレイを加えたものです。後処理には数分かかり、完了するまではコミットが古いベース実行を参照します。そのため、昇格直後に通常の js-coverage を実行すると、古い数値が返されますが、それが古い情報であることは示されません。代わりに、更新後の実行を ID で読み込みます。処理の完了まで待機します。

meticulous agent js-coverage --summary --testRunId="<updatedBaseTestRunId>"

この数値を報告に記載してください。昇格を反映したコミット固有の数値であり、ステップ 7 のユニオンと一致するはずです。updatedBaseTestRunId が空で返された場合は、標準エラー出力の通知に理由が示されています。その場合は、ステップ 7 のユニオンを報告してください。

コミットのベース実行で選択セット全体がリプレイされていない場合、昇格は拒否されます。そのため、ステップ 1 で complete-base-run をスキップした場合は、今実行してから再度昇格してください。また、上限があります。1 回の呼び出しで最大 20 セッション、2 回のセッション選択の間に昇格できるのは、プロジェクトで設定された選択セットの最大 10% までです。上限に達した場合は、増加量の大きいセッションを昇格させ、残りのセッションを報告に記載してください。

ステップ 9 — .meticulousignore の PR を作成する

2 つ目の成果物です。ブランチを作成し、.meticulousignore の変更をコミットして、PR を作成します。

まったくカバーできないと確信できるパスだけを提案してください。 基準となるのは「今日、確認する時間がなかった」ではなく、「どのセッションでも実行される可能性がない」です。個々のファイルを長々と列挙するより、ディレクトリ単位のルールを優先してください。ディレクトリ単位のルールはファイルが追加されても有効ですが、ファイルの一覧はいつの間にか古くなります。実際には、ほとんどの項目はステップ 2 の安全に除外できるカテゴリに加え、ステップ 2 のディレクトリごとの確認で一律 0% だった、ブラウザー向けではないパッケージ全体です。

コミットする前に確認してください。無視しようとしているすべてのパスが、全体で 0% のままであること(ステップ 8 の確認)。新しいセッションでカバーできたパスは、当然ながら無視リストに含めるべきではありません。この確認でそれを検出できます。

特にモノレポでは、「すべて無視してから、ブラウザーで実行されるものを無視対象から外す」という構成がよく使われます。

# Ignore everything except packages that are executed in the
# browser and have meaningful frontend coverage.

packages/*
!packages/<frontend-app>/
!packages/<frontend-app>/**
!packages/<shared-ui-lib>/
!packages/<shared-ui-lib>/**

.meticulousignore は gitignore のセマンティクスに従うため、親ディレクトリが除外されていると、ファイルを再度含めることはできません。ファイルだけでなく、ディレクトリ(!some/dir/**/)も無視対象から外してください。

PR の説明では、各ルールの理由、つまり、このコードをブラウザーで実行できない理由(Node 専用のビルドスクリプト、テストハーネス、バックエンドパッケージなど)を記載してください。ベースラインでわかるのは、ある項目が今日 0% であることだけです。それは到達不可能である証拠には決してならないため、実際のコードの内容に基づいて理由を説明する必要があります。また、次の点も明示してください。

  • ソースマップまたはアトリビューションの不足が原因で、デッドコードではないと考え、カバレッジが低いにもかかわらず無視リストに含めなかったもの(ステップ 2)
  • 環境やフィーチャーフラグの設定が原因でのみ到達不可能なもの。これはレビュー担当者が判断することであり、あなたが判断することではありません。黙って無視するのではなく、その旨を明記してください

参考資料

references/worked-example.md では、小規模なアプリ(kanban-demo)を使って、このスキルを実際のカバレッジ差分とともに一通り実行しています。判断基準をつかむのに役立ちます。0% のファイルは一つもなく、カバレッジが上がったターゲットが一つ、正常に記録された後に再生に失敗したターゲットが一つあります。また、実行済み範囲から両者を見分ける方法も示しています。

コメント