mcpField
P
playwright-test-results

playwright-test-results

github.com/microsoft测试与质量98.8k417 次安装今天更新
代码仓库

关于 playwright-test-results

playwright-test-results 是面向 Playwright CI 结果分析的技能,将近期测试结果存入单个 DuckDB 文件,供 SQL 查询失败、波动和耗时情况。数据库会定期更新,并保留近期运行摘要。

主要功能

  1. 01DuckDB 保存近期测试结果
  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 请求,所有工作流共享仓库的请求额度,而且重建几天的数据就会耗尽额度。若只想刷新快照而不导入数据,请运行 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 安全),因此请在 SQL 中进行排序和筛选,而不要在 JS 中处理。

架构

单个 test_results 表,每条测试结果占一行(每次重试占一行)。列根据 reporter 输出的 parquet 推断得出(tests/config/parquetReporter.ts),此外 CLI 还会添加两个末尾列:

列含义
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 机器人。操作系统和架构编码在此列中;没有单独的操作系统列。
project_nameCI 项目 = 浏览器 + 测试套件,例如 chromium-page、webkit-library、playwright-test
test_title文件内的标题路径,以 › 连接(describe › test)
file, line, column_number源代码位置(文件路径相对于仓库根目录)
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 哈希不会存储;这三列是其哈希前的原始数据。)
  • 不稳定性是推导得出的,而非直接存储。最重要的信号是跨运行情况:测试在重试后的最终结果会在不同运行之间变化——有时通过,有时失败。另一种单独的单次运行内不稳定,指测试在一次运行中经重试后恢复通过(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 评论中,请将每次最终运行判定渲染为带链接的方块。编辑四个测试标识字段,然后运行:

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)

每个方块代表一次工作流运行尝试,按时间从早到晚排列。绿色表示通过,橙色表示重试挽救了之前的失败,红色表示失败或超时。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 天,因此此方法仅适用于近期运行;数据库本身会更长时间地保留摘要(直到因运行数量达到上限而被淘汰)。

评论

支持 Markdown