PER-8985 feat: Playwright drop-in baseline seeding — build attributes + exec orchestration#2335
PER-8985 feat: Playwright drop-in baseline seeding — build attributes + exec orchestration#2335Shivanshu-07 wants to merge 17 commits into
Conversation
…orchestration - @percy/client createBuild: allow-listed drop-in build sources via PERCY_BUILD_SOURCE, plus dropin-baseline-candidate / dropin-baseline-setup attributes (env or explicit options) the API keys baseline auto-approval on. - @percy/cli-exec: generic baseline-provider discovery (any installed SDK package can declare "@percy/cli".baselineProvider). On an empty project, percy exec uploads the committed baseline screenshots as an auto-approved build #1 before the user's command runs; established projects get a notice pointing at the SDK's explicit setup command. Seeding helpers exported for SDK-contributed commands. - @percy/core: expose the server-decided build source on percy.build so SDKs can key on it through the healthcheck. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Verified E2E against a local percy-api: a web project's snapshot create requires a root resource, so raw-PNG comparison tiles are rejected. Seed uploads now use client.sendSnapshot with the same generated root-DOM + image-resource shape 'percy upload' uses for web projects — Percy renders the baseline in the project's own browsers, so it pairs with the head run's DOM snapshots by (name, browser, width). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Seeding dispatches by project type: web projects seed rendered snapshots (root DOM + image resource); app projects seed raw comparison uploads (tag + tile), exactly how App Percy ingests screenshots. Exec seeding now engages for app tokens too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The same env var the SDK override honors — one switch turns off both the toHaveScreenshot override and exec's baseline provider discovery. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- core: only set percy.build.source when the API returned one — an own 'source: undefined' key survives in-process but drops out of JSON responses, breaking the API-server shape specs. - cli-exec: cover the exec seeding block (real provider-package fixture) and the remaining baseline.js branches for the 100% coverage gate. - cli-exec: sanitize dirent names / paths at the fs boundary in the provider walk (also clears semgrep's path-join-resolve-traversal findings on the diff). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Direct sanitizer unit tests, walk fixtures for every discovery skip branch (empty dir, ESM named-export provider without discoverBaselines), and the non-web/app token exec branch; istanbul-ignore the two defense-in-depth dirent guards unreachable via readdir. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The seed build keeps rendering and only auto-approves once it reaches finished; head snapshots select their baseline as they process, so starting the head immediately raced the approval and a lost race made the whole first run report as new instead of diffing. Poll the seed build up to the pipeline latency budget (10m) with a 30s heartbeat, degrade to a warning instead of blocking on timeout or status errors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pin-baseline # Conflicts: # packages/client/src/client.js # packages/client/test/client.test.js
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Master-inherited coverage gap from the metadata-SSRF change — the catch path for a colon-containing host that fails URL parsing was never exercised, holding @percy/core under the 100% gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every caller (matchMetadataHost) null-guards before calling, so the branch cannot be exercised by tests; it held @percy/core under the 100% gate on master too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The seed upload omitted browserName, so seed and head comparisons landed on different canonical tag rows and screenshot pairing missed — the first app run diffed nothing. One tag shape, defined by the SDK. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- discovery walk now mirrors @percy/cli's findModulePackages exactly: stop at the nearest node_modules, never cross the home directory, and degrade to [] on any filesystem error — a throw here previously escaped to exec's outer catch and disabled Percy for the whole run - provider modules are confined to their declaring package (a baselineProvider path escaping the package root is skipped) - only ever seed build #1: an API that predates the candidate attribute hands back a normal build, which previously seeded stray baseline builds into established projects on every run - falsy provider baseline entries are filtered instead of silently terminating an upload worker Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… idiom) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude Code PR ReviewPR: #2335 • Head: 0a38a90 • Reviewers: fallback inline checklist (deep-review agent) SummaryCLI-side drop-in baseline seeding: generic Review Table
FindingsAll review findings were fixed on this PR before this report:
Verdict: PASS |
…pin-baseline # Conflicts: # packages/core/src/utils.js
| let polls = 0; | ||
|
|
||
| for (;;) { | ||
| ({ state } = (await client.getBuild(buildId)).data.attributes); |
There was a problem hiding this comment.
client.getBuild's own doc comment in @percy/client says it requires a read access token, but most percy exec users run with a plain write-only project token. If GET /builds/:id 403s here, the first poll throws, maybeSeedBaseline swallows it, and every first run warns did not finish processing in time (state: undefined) — then starts the head build immediately, racing the unapproved seed. That silently defeats the seed-before-head ordering this feature depends on.
Can we verify this loop works on staging with a standard write-only token (not a full-access one)? If it 403s, the API needs to allow build-state reads for the token that created the build.
| log, projectType: percy.projectType | ||
| }); | ||
|
|
||
| await percy.client.finalizeBuild(buildId); |
There was a problem hiding this comment.
If every upload fails transiently (e.g. a network blip), seeded is 0 but we still finalize — creating an empty, auto-approved build #1. The project is now "established" with an empty baseline: the next run diffs against nothing and seeding never retries (build-number is no longer 1), so a one-time hiccup permanently burns the auto-seed path.
Suggest: when seeded === 0, skip the finalize (or at least don't log "Baseline established") and point at percy playwright:setup-baseline instead. Partial seeding is fine; zero is not.
| // Ask the server. An explicit baseline source on an ESTABLISHED project returns the | ||
| // baseline-skipped sentinel (no build persisted); on an empty project it creates build #1 as | ||
| // the baseline. First-ness is decided by the API from the project token — never locally. | ||
| let res = await percy.client.createBuild({ |
There was a problem hiding this comment.
Once a project has builds, every subsequent percy exec with committed baselines still pays this full cost on every run, forever: the provider's discoverBaselines() filesystem walk, plus a doomed createBuild POST the API answers with the sentinel, plus two info log lines ("already has builds — skipping" / "run npx percy playwright:setup-baseline"). That's permanent log noise and an extra API round-trip per run for the lifetime of the project.
Consider gating the notice so it only shows once, or having the head build response carry an "established" hint so later runs can skip the sentinel round-trip entirely.
What
CLI-side support for the
@percy/playwrighttoHaveScreenshot drop-in's baseline flow:createBuildaccepts allow-listed drop-in build sources viaPERCY_BUILD_SOURCE(playwright-dropin/playwright-dropin-baseline) plusdropin-baseline-candidate/dropin-baseline-setupattributes (env or explicit options). The API keys baseline auto-approval on these."@percy/cli": { "baselineProvider": "..." }(no framework code enters the CLI, same scaling model as command discovery). On an EMPTY project,percy execuploads the committed baseline screenshots as an auto-approved build ⬆️ Bump @babel/register from 7.10.4 to 7.10.5 #1 before the user's command runs (the suite never executes for the baseline), then starts the head build. Established projects get a notice pointing at the SDK's explicitplaywright:setup-baselinecommand — the API's no-build-persisted sentinel guarantees no stray builds. Seeding dispatches by project type: web → rendered web snapshots (root DOM + image resource, thepercy uploadshape); app → comparison ingest (tag + tile, no render).sourceis kept onpercy.buildso SDKs can key on it through the healthcheck.Depends on
Testing
Client + exec suites green incl. 11 new seeding specs; E2E verified against a local percy-api and on staging (empty web/app seed → head diff flow, established-project sentinel, explicit setup command).
🤖 Generated with Claude Code