playwright-test-results について
playwright-test-results は、Playwright の CI 結果を単一の DuckDB ファイルに保存して分析するスキルです。SQL で最近の失敗、フレーク、実行時間を調べられ、データベースは定期更新されます。
主な機能
- 01最近の結果を DuckDB に保存
- 02リトライごとに結果を記録
- 03SQL による失敗やフレークの分析
- 04タグによるテストの絞り込み
- 05リンク付き実行履歴の生成
活用シーン
- 01実行間で結果が変わるテストの特定
- 02時間や失敗数によるテストの順位付け
- 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_attempt | GitHub Actions の実行識別情報 |
run_started_at | 実行の開始時刻 |
workflow_name | 例:tests 1 / tests 2 / tests others / MCP |
event | push / pull_request |
head_sha, head_branch, pr_number | テスト対象 |
bot_name | 例:chromium-ubuntu-22.04-node20、webkit-macos-15-large — CI ボットです。OS とアーキテクチャはここに含まれます。独立した os 列はありません。 |
project_name | CI プロジェクト=ブラウザー+スイート。例:chromium-page、webkit-library、playwright-test |
test_title | ファイル内のタイトルパス。›(describe › test)で連結されます |
file, line, column_number | ソースの場所(file はリポジトリルートからの相対パス) |
expected_status | passed / skipped / ... |
status | 実際の結果:passed / failed / timedOut / skipped / interrupted |
retry | 0 = 初回の試行 |
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 日間です。そのため、これは最近の実行でのみ機能します。データベース自体には、実行数による削除が行われるまで、概要がより長く保持されます。