mcpField
P
playwright-test-results

playwright-test-results

github.com/microsoftテスト・品質98.8k417 回インストール今日更新
リポジトリ

playwright-test-results について

playwright-test-results は、Playwright の CI 結果を単一の DuckDB ファイルに保存して分析するスキルです。SQL で最近の失敗、フレーク、実行時間を調べられ、データベースは定期更新されます。

主な機能

  1. 01最近の結果を DuckDB に保存
  2. 02リトライごとに結果を記録
  3. 03SQL による失敗やフレークの分析
  4. 04タグによるテストの絞り込み
  5. 05リンク付き実行履歴の生成

活用シーン

  1. 01実行間で結果が変わるテストの特定
  2. 02時間や失敗数によるテストの順位付け
  3. 03@slow などのタグでテストを抽出

インストール

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

$npx skills add https://github.com/microsoft/playwright --skill playwright-test-results

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

詳細

Playwright テスト結果(DuckDB)

単一の DuckDB ファイルに最近の Playwright CI テスト結果が保存されているため、失敗、不安定さ、遅いテストに関する質問にプレーンな SQL で回答できます。数時間ごとに更新されます。

データベースを取得する

最新のスナップショットをダウンロードし、新しい実行結果を追加します。

npm ci                       # first time only, from the repo root
GITHUB_TOKEN=$(gh auth token) node utils/test-results-db/cli.ts update --lookback-days 3

ローカルにデータベースがない場合、update はまずスナップショットをダウンロードしてから、スナップショットに含まれていないアーティファクトだけを取り込みます。データベースを削除して再構築しないでください。各アーティファクトにつき GitHub API リクエストが 1 回必要で、リクエスト上限はリポジトリ内のすべてのワークフローで共有されているため、数日分を再構築するだけで上限に達します。取り込みを行わず、スナップショットのみを更新するには、download を実行してください。

同梱の @duckdb/node-api バインディングを介してクエリを実行できます。別途 DuckDB をインストールする必要はなく、npm ci の後に node_modules に含まれます。

node --input-type=module -e '
import { DuckDBInstance } from "@duckdb/node-api";
const conn = await (await DuckDBInstance.create("utils/test-results-db/test-results.duckdb")).connect();
console.table((await conn.runAndReadAll(process.argv[1])).getRowObjectsJson());
' "SELECT count(*) FROM test_results"

整数列は文字列として返されるため(JSON セーフ)、ランキングやフィルタリングは JS ではなく SQL で行ってください。

スキーマ

単一のテーブル test_results があり、テスト結果ごとに 1 行(再試行ごとに 1 行)が記録されます。列はレポーターが出力する parquet(tests/config/parquetReporter.ts)から推定され、この CLI が末尾に 2 列を追加します。

列意味
run_id, run_attemptGitHub Actions の実行識別情報
run_started_at実行の開始時刻
workflow_name例:tests 1 / tests 2 / tests others / MCP
eventpush / pull_request
head_sha, head_branch, pr_numberテスト対象
bot_name例:chromium-ubuntu-22.04-node20、webkit-macos-15-large — CI ボットです。OS とアーキテクチャはここに含まれます。独立した os 列はありません。
project_nameCI プロジェクト=ブラウザー+スイート。例:chromium-page、webkit-library、playwright-test
test_titleファイル内のタイトルパス。›(describe › test)で連結されます
file, line, column_numberソースの場所(file はリポジトリルートからの相対パス)
expected_statuspassed / skipped / ...
status実際の結果:passed / failed / timedOut / skipped / interrupted
retry0 = 初回の試行
result_started_atこの試行の開始時刻
duration_ms結果の所要時間
error_messageすべてのエラーを結合し、ANSI を除去したもの(エラーがない場合は NULL)
tags文字列のリスト。例:['@slow', '@flaky'](リスト関数/list_contains を使用)
annotations{type, description} 構造体のリスト。例:[{'type': 'skip', 'description': 'flaky on CI'}](何もない場合は空のリスト)
artifact_idこの行の元になった GitHub アーティファクト(重複排除キー)
ingested_atデバッグ専用 — この行を取り込んだ時刻

注:

  • テストは (project_name, file, test_title) で識別されます — このタプルでグループ化してください。(Playwright の test_id ハッシュは意図的に保存されていません。この 3 列がそのハッシュの生成元です。)
  • 不安定さは保存されるのではなく、導出されます。最も重要なシグナルは実行間の変化です。再試行後の最終判定が実行ごとに変わるテスト、つまりある実行では成功し、別の実行では失敗するテストです。別の実行内の不安定さとは、1 回の実行内で再試行によって救済されたテスト(failed→passed)です。
  • 実際の失敗と意図的な失敗の区別: expected_status = 'passed' でフィルタリングしてください。test.fail() のマークが付いたテストは、expected_status='failed' のまま status='failed' を記録するため、そうしないと「失敗が最も多い」リストを占めてしまいます。
  • データベースのサイズは実行数で上限が設定されています。最も古い実行全体が時間の経過とともに削除されるため、全履歴ではなく最近の期間のデータが保持されます。

クエリの例

テストを (project_name, file, test_title) でグループ化し、(失敗/不安定さについては)expected_status = 'passed' に範囲を絞って、意図的な test.fail() テストが結果に影響しないようにします。

実行間で不安定なテスト — テストの最終判定が実行ごとに変わります(これが CI の失敗結果を曖昧にする原因です)。least(failed_runs, passed_runs) によって、常に失敗するテストや一度だけ失敗したテストよりも、実際に両方の結果が出ているテストを上位にランク付けします。

WITH per_run AS (
  SELECT project_name, file, test_title, run_id, run_attempt,
         arg_max(status, retry) AS final_status,
         any_value(expected_status) AS expected
  FROM test_results
  GROUP BY project_name, file, test_title, run_id, run_attempt)
SELECT project_name, test_title,
       count(*) AS runs,
       count(*) FILTER (WHERE final_status IN ('failed','timedOut')) AS failed_runs,
       count(*) FILTER (WHERE final_status = 'passed') AS passed_runs,
       round(100.0 * count(*) FILTER (WHERE final_status IN ('failed','timedOut'))
             / count(*), 1) AS fail_pct
FROM per_run
WHERE expected = 'passed'
GROUP BY project_name, test_title
HAVING failed_runs > 0 AND passed_runs > 0 AND runs >= 10
ORDER BY least(failed_runs, passed_runs) DESC, failed_runs DESC
LIMIT 20;

タグでフィルタリング(tags は文字列ではなくリストです)。

SELECT project_name, test_title, count(*) AS runs
FROM test_results
WHERE list_contains(tags, '@slow')
GROUP BY project_name, test_title
ORDER BY runs DESC
LIMIT 20;

リンク付きの絵文字で実行履歴を生成する

GitHub のコメントにそのまま貼り付けられるコンパクトな結果にするには、最終的な実行結果をそれぞれリンク付きの四角形で表示します。4 つのテスト識別フィールドを編集してから、次を実行します。

node --input-type=module <<'EOF'
import { DuckDBInstance } from "@duckdb/node-api";

const repository = "microsoft/playwright";
const test = {
  projectName: "firefox-library",
  file: "library/proxy.spec.ts",
  testTitle: "should exclude patterns",
  botName: "firefox-macos-15-large",
};

const conn = await (await DuckDBInstance.create(
  "utils/test-results-db/test-results.duckdb"
)).connect();
const result = await conn.runAndReadAll(`
  WITH per_run AS (
    SELECT run_id, run_attempt,
           any_value(run_started_at) AS run_started_at,
           arg_max(status, retry) AS final_status,
           arg_max(expected_status, retry) AS expected_status,
           list(status ORDER BY retry) AS attempt_statuses
    FROM test_results
    WHERE project_name = $projectName
      AND file = $file
      AND test_title = $testTitle
      AND bot_name = $botName
    GROUP BY run_id, run_attempt
  )
  SELECT run_id, run_attempt, final_status, attempt_statuses
  FROM per_run
  WHERE expected_status = 'passed'
    AND final_status IN ('passed', 'failed', 'timedOut')
  ORDER BY run_started_at, run_id, run_attempt
`, test);

const markdown = result.getRowObjectsJson().map(row => {
  const rescued = row.final_status === "passed" &&
    row.attempt_statuses.some(status => status === "failed" || status === "timedOut");
  const emoji = rescued ? "🟧" : row.final_status === "passed" ? "🟩" : "🟥";
  const url = `https://github.com/${repository}/actions/runs/${row.run_id}/attempts/${row.run_attempt}`;
  return `[${emoji}](${url})`;
}).join("");

console.log(markdown);
EOF

出力は Markdown です。

[🟩](https://github.com/microsoft/playwright/actions/runs/123/attempts/1)[🟧](https://github.com/microsoft/playwright/actions/runs/456/attempts/1)[🟥](https://github.com/microsoft/playwright/actions/runs/789/attempts/1)

各四角形はワークフローの 1 回の実行試行を表し、古い順に並びます。緑は成功、オレンジはリトライによって以前の失敗が回復したことを示し、赤は失敗またはタイムアウトを示します。arg_max(status, retry) はリトライ後の最終結果を選択し、(run_id, run_attempt) でグループ化することで、リトライが余分な四角形として表示されるのを防ぎます。/attempts/<n> URL は、結果を生成した正確な再実行へのリンクです。

詳細情報の取得

データベースには各結果の概要が保存されています。ステップツリー/添付ファイル/標準入出力の全容を確認するには、その実行でアップロードされている場合、元の blob レポートを取得します。行は run_id と bot_name で特定されます。実行の blob アーティファクト名は blob-report-<bot_name> です。

# List the run's blob artifacts and find the one for this bot_name:
gh api /repos/microsoft/playwright/actions/runs/<run_id>/artifacts \
  --jq '.artifacts[] | select(.name | startswith("blob-report")) | {id, name}'

# Download it (name == "blob-report-<bot_name>"):
gh api /repos/microsoft/playwright/actions/artifacts/<artifact_id>/zip > blob.zip

blob アーティファクトと parquet アーティファクトの保持期間は 7 日間です。そのため、これは最近の実行でのみ機能します。データベース自体には、実行数による削除が行われるまで、概要がより長く保持されます。

コメント