test(visual): stop content and API drift from repainting baselines - #5706
Conversation
Three sources moved the screenshot baselines, only one of them being the CSS the suite exists to watch. Dated content: a SNAPSHOT_CUTOFF env var, set by the snapshot workflow alone, filters the blogs, external blogs and tutorial videos the /blogs and /docs pages sort by date. Unset everywhere else, so the site renders its full content as usual. Live API: the snapshot workflow now builds and screenshots against scripts/api-fixture-server.mjs, which Lighthouse already used, with its own FIXTURE_PATHS covering the endpoints the sampled SSR routes read. The matcher grew an inner wildcard so /v1/plugins/<fqcn>/versions can be recorded without /v1/plugins* swallowing the schema fetches beside it. Incidental content: /docs renders the four newest posts through WhatsNew, where the blog list is not what that baseline is for, so a stylesheet pins the cards and blanks their copy for that screenshot. Expect one refresh PR as the cutoff lands, then quiet until the CSS actually changes or someone bumps the date. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GHRsDvXNS9LLTVhdmjA8or
☁️ Cloudflare Worker Preview Deployed!🔗 https://ks-elevate-bot-inspiring-rubin-iz-docs.kestra-io.workers.dev 🔦 Lighthouse Benchmark
Scores (0–100, higher is better)
Core Web Vitals (lower is better)
Legend🟢 improved · 🔻 regressed · (blank) no significant change Median of repeated runs: Home x5, About Us x3, Docs Landing x5, Flow (full featured docs) x3, Plugins Landing x3, Blueprints Landing x3. A single run of these swings 20+ points between runners. View full Lighthouse HTML report for a pageFull per-page Lighthouse Results (LHR) are attached as the |
Keeps the sampled blog content current up to 2026-09-16T13:00. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GHRsDvXNS9LLTVhdmjA8or
Brings in the bootstrap removal and the 43 baselines it refreshed. Docs-Landing-desktop was the only conflict, both sides having redrawn it: took main's, since its version carries the bootstrap removal and this branch's only carries the WhatsNew mask. The next snapshot run reapplies the mask on top. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GHRsDvXNS9LLTVhdmjA8or
iitzIrFan
left a comment
There was a problem hiding this comment.
Nice work. This goes after the actual causes of baseline churn, and it fits the existing setup: it reuses the fixture server and startup steps from lighthouse.yml, declares SNAPSHOT_CUTOFF like the other env settings, and puts snapshotCutoff.ts next to its test in the same style as the other utils. I checked out the branch: the new path matching handles exact, trailing-* and inner-* paths correctly (/v1/plugins/core is still not recorded, /v1/plugins/<fqcn>/versions is), and the new tests pass.
Approving. The inline notes are non-blocking suggestions, fine here or in a follow-up.
Review follow-up. Neither list moves a baseline today, but both would later, which is the drift this branch exists to stop: - fetchSecondaryData feeds the /plugins/core* pages from the posts carrying a `plugins:` frontmatter. None do yet; the first one would. - The orchestration fallback shows the 4 newest posts matching the tool name. /orchestration/ansible only escapes it by having curated resources, so sampling a page without them brings the drift back. Also spells out in refresh_api_fixtures that ticking it drops the blueprint detail responses only Lighthouse reads. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GHRsDvXNS9LLTVhdmjA8or
Review follow-up. Nothing moves SNAPSHOT_CUTOFF on its own, and a date drifting months into the past only showed up in the body of a refresh PR nobody had to read. The run now says it: a warning past 90 days, an error and a red job past 180. It runs last, so a neglected cutoff still gets its baselines refreshed and committed before the job fails, and it reports the age to the job summary either way. Both windows are env vars on the job. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GHRsDvXNS9LLTVhdmjA8or
Snapshots taken from main wholesale: this branch never touched them, so the baselines #5706 repainted come across untouched. Two real conflicts, both resolved by keeping each side: - tests/visual-regression.spec.ts: main's per-page stylePath and this branch's maxDiffPixelRatio sit in the same options object but do different jobs. The odiff matcher implements stylePath (loadScreenshotStyles in its dist), so the combined call works. - update-linux-snapshots.yml: both sides append to the generated PR's body, the cutoff/fixture note and the diff-report link. No reason to pick a favourite. The report step still runs before the commit step, which is the one ordering constraint that matters: after the commit there is nothing left to diff against. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PStmR9iSDAvvDSV7rD6DyA
Three things moved the screenshot baselines, and only one of them was the CSS the suite exists to watch. Blog-Index has the most refreshes in the snapshot history, and none of them were about layout.
Dated content
SNAPSHOT_CUTOFF, a new optional server env field, filters every date-sorted blog list the sampled pages can render:WhatsNew.astroandblogs/[category].astro(blogs, external blogs, tutorial videos),fetchSecondaryDatabehind the/plugins/core*pages, and the orchestration fallback list.beforeCutoffis a pass-through when the var is unset, which is everywhere except the snapshot workflow, so the site keeps rendering its full content.The workflow sets it to
2026-09-17. A date-only value resolves to midnight UTC, so the one post dated2026-09-17T10:00:00falls outside it and the sample runs current through2026-09-16T13:00. Pass a full timestamp if you want a day inclusive.Live API
/plugins,/blueprintsand the community metrics are read at request time, so a plugin release repainted them. The snapshot workflow now builds and screenshots againstscripts/api-fixture-server.mjs, which Lighthouse already used, with its ownFIXTURE_PATHSadding the endpoints the sampled SSR routes need:/plugins/definitions/*,/plugins/metadata*,/plugins/pluginsInformation*,/plugins/artifacts/*,/plugins/*/versions,/versions,/communities/github/*.isRecordedgrew an inner wildcard matching a single path segment, so/v1/plugins/<fqcn>/versionscan be recorded without/v1/plugins*swallowing the schema fetches sitting next to it. Trailing-*prefixes behave exactly as before.Anything recorded during a run is committed with the refreshed baselines.
workflow_dispatchgained arefresh_api_fixturesbox; ticking it clears the whole directory, including the ~300 blueprint detail responses only Lighthouse reads, which its next run re-records into an artifact to commit. Called out in the input description and the fixtures README.Incidental content
/docsrenders the four newest posts throughWhatsNew, where the blog list is not what that baseline is for. A stylesheet injected viastylePathpins the cards to their thumbnail height and blanks the copy, so bumping the cutoff moves Blog-Index and leaves Docs-Landing alone.Keeping the cutoff honest
The date only moves when someone moves it, so the run reports its age to the job summary, warns past 90 days and fails past 180. Both windows are job env vars. The check runs last, so a neglected cutoff still gets its baselines refreshed and committed before the job goes red.
Verified
Dispatched on this branch (run 35364764388): the fixture server served 360 replays with zero errors, proving the SSR routes under
wrangler devreach it, and recorded the 11 endpoints the widened paths added. 13 baselines moved, and the four Plugin-* pages did not, the recorded data matching what was live. Local builds with the cutoff set come back clean, server bundle included. 422 unit tests pass, oxlint clean.What to expect
Quiet nightlies until the CSS actually changes or someone bumps the date, and a nag on the run once the date ages. The trade is that the sampled content ages with it, so a card redesign that only triggers on newer frontmatter would not show up until the next bump.
🤖 Generated with Claude Code
https://claude.ai/code/session_01GHRsDvXNS9LLTVhdmjA8or