ci(playwright): auto-refresh timing-baseline.json after every successful full merge_group - #30872
Conversation
…ful full merge_group Follow-up to PR #30871's one-shot manual refresh. Without this, `.github/playwright/timing-baseline.json` drifts fast — 12 days between captures produced 23 % dead titles and 30 uncovered spec files on main, enough to make the planner materially wrong on both LPT balance and atomic-unit weight decisions. Adds a `refresh-timing-baseline` job that runs after `playwright-summary` on every successful full-mode merge_group run. The job: * downloads the same `playwright-timing-history-full-*` artifact the summary already uploaded * normalizes it to the checked-in baseline schema via a new `.github/scripts/refresh_timing_baseline.py` * preserves `retainedUnstableTestIds`, `retainedSourceRunId`, `retainedSourceSha` from the current baseline (curated fields) * writes compact JSON so the diff stays a one-line reserialization * skips if the file is byte-identical * force-pushes a single tracked branch `ci/auto-refresh-timing-baseline` and opens or updates one PR against main * refuses (exit 2) if drift > 40 % of test-id entries — a safety valve against an accidentally-broken source run Guards: * merge_group event only (safest ci-status signal) * full execution mode only (targeted runs miss coverage) * playwright-summary success only (no partial data) * single tracked PR — successive runs update it in place rather than piling up The auto-PR still requires human merge. It just eliminates the "regenerate the file" busywork that PR #30871 did by hand. 6 new pytest cases in `test_refresh_timing_baseline.py` cover the normalization, retention-field preservation, diff computation, drift cap, non-full rejection, and end-to-end `main()` behavior. 94 total tests in `.github/scripts/tests/` pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
❌ PR checklist incompleteThis PR cannot be merged until the following are addressed on its linked issue:
The fields live on the linked issue in the Shipping project (open the issue → right sidebar → Projects). After you set them, re-run this check (or push a commit) — issue/project changes do not re-trigger it automatically. Maintainers can bypass this check by adding the |
Code Review ✅ ApprovedAdds an automated GitHub Actions job to refresh the Playwright timing baseline after successful full merge group runs, preventing configuration drift. No issues found. OptionsDisplay: compact → Showing less information. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Gitar | Powered by Gitar — free for open source |
There was a problem hiding this comment.
Pull request overview
This PR adds automation to keep Playwright’s checked-in timing baseline (.github/playwright/timing-baseline.json) up to date by running a post-summary refresh on successful full-mode merge_group runs and opening/updating a single tracked PR with the refreshed file.
Changes:
- Adds a new
refresh-timing-baselinejob to the Playwright PostgreSQL E2E workflow that downloads the full timing-history artifact, regenerates the baseline, and opens/updates a single PR. - Introduces
.github/scripts/refresh_timing_baseline.pyto normalize merged timing history into the baseline schema, preserve curated retention fields, and enforce a drift cap. - Adds pytest coverage for the new refresh script.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
.github/workflows/playwright-postgresql-e2e.yml |
Adds a guarded post-summary job that regenerates and PRs baseline updates. |
.github/scripts/refresh_timing_baseline.py |
New baseline refresh/normalization script with drift summary + drift cap. |
.github/scripts/tests/test_refresh_timing_baseline.py |
New pytest suite validating normalization, retention, drift cap, and main() behavior. |
Suppressed comments (1)
.github/scripts/refresh_timing_baseline.py:93
- compute_diff() keys tests by (file, title), but timing-baseline.json contains many distinct tests that share the same file+title (e.g., repeated leaf titles in Pages/EntityHeaderBreadcrumb.spec.ts). This collapses entries in the diff calculation and makes the drift safety valve inaccurate. Key by stable test identity (project,id) instead so added/removed/drift are computed per test-id entry as intended.
def key(t: dict[str, Any]) -> tuple[str, str]:
return (t.get("file", ""), t.get("title", ""))
cur_by_key = {key(t): t for t in current.get("tests", [])}
new_by_key = {key(t): t for t in new.get("tests", [])}
| new = {"tests": [ | ||
| {"file": "Pages/A.spec.ts", "title": "case A", "durationMs": 450}, | ||
| {"file": "Pages/A.spec.ts", "title": "case B", "durationMs": 600}, # recovered | ||
| {"file": "Pages/A.spec.ts", "title": "case NEW", "durationMs": 200}, | ||
| {"file": "Pages/B.spec.ts", "title": "new file", "durationMs": 100}, | ||
| ]} |
| import argparse | ||
| import json | ||
| from collections import defaultdict | ||
| from pathlib import Path | ||
| from typing import Any | ||
|
|
||
|
|
||
| BASELINE_TEST_FIELDS = ("id", "project", "file", "title", "durationMs", | ||
| "attempts", "retries", "outcome") |
| needs.detect-changes.outputs.mode == 'full' && | ||
| needs.playwright-summary.result == 'success' | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 10 |
| git checkout -B "$branch" | ||
| git add .github/playwright/timing-baseline.json | ||
| git commit -m "chore(playwright): auto-refresh timing baseline from run ${RUN_ID}" | ||
| git push --force-with-lease origin "$branch" |
✅ Playwright Results — workflow succeededValidated commit ✅ 550 passed · ❌ 0 failed · 🟡 0 flaky · ⏭️ 3 skipped · 🧰 0 lifecycle flaky PerformanceBlocking targets: ✅ met · Optimization targets: 🟡 in progress Shard-job maxima below are not the full workflow wall time; the linked run includes build, fixture, planning, and reporting. 🕒 Full workflow signal wall (to summary) 53m 11s ⏱️ Max setup 2m 58s · max shard execution 17m 8s · max shard-job elapsed before upload 20m 4s · reporting 5s 🌐 201.26 requests/attempt · 2.83 app boots/UI scenario · 16.15% common-shard skew Optimization targets still in progress:
How to debug locally# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip # view trace |
Summary
Follow-up to #30871's one-shot manual refresh. Without an automated refresh,
.github/playwright/timing-baseline.jsondrifts fast — the 12-day gap that #30871 closed had produced 23% dead titles and 30 uncovered spec files onmain, enough to make the planner materially wrong on both LPT balance and atomic-unit weight decisions.Adds a
refresh-timing-baselinejob that runs afterplaywright-summaryon every successful full-mode merge_group run and opens (or updates) a single tracked PR with the refreshed baseline. A human still clicks merge — the auto-PR just eliminates the manual regeneration step.What the job does
playwright-timing-history-full-*artifact the summary job already uploaded..github/scripts/refresh_timing_baseline.py, which:title= leaf, dropretryDurationMs) to match the checked-in baseline;retainedUnstableTestIds+retainedSourceRunId+retainedSourceShafrom the current baseline (curated fields);ci/auto-refresh-timing-baselineand opens or updates one PR againstmain.Guards
event == merge_grouponly (safest ci-status signal — PRs are targeted, schedule/dispatch have less trust).execution_mode == fullonly (targeted plans skip specs).playwright-summary.result == successonly (no partial data on failures).Files
.github/scripts/refresh_timing_baseline.py(new, 160 lines) — normalization + drift-cap script..github/scripts/tests/test_refresh_timing_baseline.py(new, 6 pytest cases) — covers normalization, retention preservation, diff, drift cap, non-full rejection, end-to-endmain()..github/workflows/playwright-postgresql-e2e.yml— newrefresh-timing-baselinejob appended afterplaywright-summary.Verified
.github/scripts/tests/pass (88 existing + 6 new).Test plan
maintitledchore(playwright): auto-refresh timing-baseline.json.Related: #30812, #30827, #30835, #30871.
🤖 Generated with Claude Code