feat: Percy feature testbed — one-command build seeder (Units 1–10) - #1
Open
AkashBrowserStack wants to merge 17 commits into
Open
feat: Percy feature testbed — one-command build seeder (Units 1–10)#1AkashBrowserStack wants to merge 17 commits into
AkashBrowserStack wants to merge 17 commits into
Conversation
resolveProfile (pure) validates the allow-list (prod never targetable, preprod gated), derives PERCY_CLIENT_API_URL, and resolves declared secret refs from env (fail-fast, naming any missing var). Fixes test discovery to recurse. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pletion (Unit 3) Injectable HttpClient + auth helpers (user principal vs project write/read token vs Basic-auth). createProject/editProject/setAutoApprove/fetchProjectToken; createBuild/finalize/reviewBuild/getBuildState/waitForBuildFinished (bails on terminal state). Auth split verified against percy-api policies. Shared test fakes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Injectable Runner + parseFinalizedBuild; captureWeb shell-out helper (always injects PERCY_CLIENT_API_URL, nonces the branch, supports capture.js DIFF_MODE and the YAML path). generateCoreStates: approved baseline -> changed vs baseline, awaiting completion between. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…al (Units 5+6) Feature generators (R9-R14): visual-git + A/B variant (target-branch), recurring diff (2 consecutive), AI fixture pair, regions pair, plus approval.ts with auto-finalization, supersede (same branch + skipCache), and the NET-NEW auto-approve generator (editProject branch rule via user principal + matching/ control builds). Shared generator-context test fake. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…y (Units 7-10) - app-percy: shells app_capture.py (baseline+changed), prod-hub creds + BS_APP_ID - preconditions: reserved-substring name guard + declared-flag/app-cred gate - provisioning: local seed-rake shell + createProject/fetch write+read tokens - orchestrator: one dependency-ordered pass over tiers x features, per-feature isolation, --only filter, AI skipped on ai_off - report: feature-labeled, token-free run summary - wired realDispatch into the CLI (live provisioning throws a Phase-0 TODO) All 51 unit tests green (mocked; live validation is Unit 0, pending tokens). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…visionProject Per public Percy docs (browserstack.com/docs/percy): create project = POST /api/v1/projects with HTTP Basic auth (BrowserStack user:key), org inferred from creds — not an org-scoped path or a bearer user token. editProject/setAutoApprove likewise Basic auth. fetchProjectToken via the tokens relationship (flagged verify-on-first-run — not fully public-documented). provisionProject now REAL (create project -> fetch write+read tokens) and wired into run.ts, replacing the Phase-0 throw. Builds still come from the CLI capture (no public create-build REST). 51 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…percy.io) Live validation against the public API confirmed create-project (POST /api/v1/projects, Basic auth) and token fetch both work; the tokens endpoint returns roles write_only / master / read_only. Corrected fetchProjectToken + provisionProject to use 'read_only' (was 'read', which would 404 the read token). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Live run confirmed the tokens/self links use the full-slug (orgSlug/projectSlug); createProject now returns full-slug so fetchProjectToken/editProject build the correct path. End-to-end create->fetch->build validated against percy.io (build #52251139). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the browser-capture path with the image-upload path proven live: - src/images.ts: static PNG generator + variant sets (baseline/unchanged/changed/ new/removed) driving review states - captureWeb now writes a variant image set to a temp dir and runs 'npx @percy/cli upload <dir>' (no browser), injecting PERCY_CLIENT_API_URL + branch/target-branch/skip-cache - generators (core/visual-git/recurring/ai/regions/supersede) switched from the YAML/browser path to diffMode image sets; ai/regions labeled as degraded on the upload path - @percy/cli added as a dependency - provisionProject (create project via Basic auth -> fetch write+read tokens) was already validated live; orchestrator now drives real builds end to end So 'seed-testbed --profile <env>' reproduces the hand-proven flow. 54 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
save-exact + audit; commented placeholder for an approved internal registry (auth via env var, no secrets committed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the exact directives required by the BrowserStack supply-chain audit (SC-12282/Enigma): ignore-scripts, strict-ssl, save-exact, audit-level=high, engine-strict, legacy-peer-deps=false, access=restricted. Relaxed engines to >=18.19 (code runs there) so engine-strict doesn't block installs. Install + 54 tests verified green with the hardened config. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reflects the validated flow: prerequisites (Node 18.19+, BS user:key), CLI usage (--profile / --only), what each build type produces, the create-project -> fetch-token -> percy upload mechanics, a manual prod recipe (CLI refuses prod), profiles/secrets, known limitations (AI/regions smoke builds, App Percy, archive-not-delete), and dev commands. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a --tier flag (scope a run to one account/project) that combines with --only, and one-liner npm scripts per feature suite: npm run core / ab / recurring-diff / ai / regions / approval / app / seed:all. Each defaults to --profile canary --tier paid (override via PROFILE=/TIER= env). README documents them. 58 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Testbed is very basic just color change, it should have complex dom to verify, to get proper AI diffs intelli ignore diffs etc.... |
Replaces the solid-colour PNG upload path with `percy snapshot` over generated fixture pages. Colour blocks proved the create-project -> token -> build plumbing but gave the AI classifier nothing to classify and region rules nothing to reason about, so those features were smoke builds by construction. - src/fixtures/pages.ts: one zoned storefront page rendered from a single source, with the zones Percy's algorithmConfiguration actually knows about (carousel, ad, banner, dynamic timestamp) plus a price table as the SIGNAL and a sidebar for layout. Deterministic — no Date.now/random — so re-runs reproduce pixels. - src/fixtures/sets.ts: file sets per review state (new adds a page, removed drops one); filenames stable so snapshots line up build to build. - src/fixtures/rules.ts: generates the Percy config carrying regions[] with algorithm standard|layout|ignore|intelliignore + configuration. Pins width to 1280 — at the default 375px the fixed-width fixture's price table falls outside the render and a real change silently produces no diff. - generators/intelli-ignore.ts (new): every rule build is paired with a `standard` CONTROL over the same fixture pair, because "0 diffs" is otherwise indistinguishable from a no-op. Also enables the noise classes on the project — they default to off, which a live run showed makes the region config inert. - generators/regions.ts: covers ignore + layout, each with its control. - generators/ai.ts: renders the upstream percy_playwright test_bed/ai pages in place (no vendoring), 8 baseline/changed pairs + reduce-diff. - GeneratedBuild.expectation + run summary: states what QA should SEE, since rich DOM makes "looks fine" a judgement nobody can make unaided. - build-api: tolerate 409 on approve. Orgs whose default branch auto-approves answer 409 for every baseline, which would fail every generator. - drops src/images.ts (dead with the upload path). Validated live against prod: rendered builds finalize, and `algorithm` demonstrably changes outcomes (same layout-shift pair = 0 diffs under standard, 4 under layout). NOT yet verified: that `ignore` and `intelliignore` suppress what they should — total-comparisons-diff is per comparison, not per region, so it cannot show whether a rule fired. Recorded in README limitations; needs per-region comparison data. 80 tests green, tsc clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rishi's review caught that no AI or IntelliIgnore build had actually run. Three causes, all fixed here. 1. editProject sent snake_case attribute keys. Percy answers 200 for unknown keys and ignores them, so every settings write reported success and changed nothing. Verified directly against prod: ignore_carousels_enabled -> stays false, ignore-carousels-enabled -> true, both 200. Keys are now dash-cased, and editProject reads the response back and throws when a value did not stick, so a silent 200 cannot hide a no-op write again. The same bug was already in setAutoApprove (auto_approve_branch_filter), so the auto-approve generator has been quietly doing nothing. Two existing tests asserted the same wrong key the code sent, which is exactly why it survived — both corrected. 2. ai-enabled defaulted to false on fresh projects. IntelliIgnore is AI-backed, so the rule never ran at all and every build matched its standard control, with ai-details.total-diffs-reduced null. Both the intelli-ignore and ai generators now enable it. After the fix the field carries real values. 3. AI bug classification (the third R12 capability) was never wired. Upstream's fixtures point at external sites — a random-content joke page and a GitHub Pages demo — which cannot give a testbed a reproducible diff, so this adds an in-repo `visual-bugs` variant instead: five distinct defects, one per zone (unreadable carousel text, clipped banner copy, misaligned price row with an overlapping value, failed ad image, sidebar overlapping the main column). Percy marks such regions visual_quality: 'irregularity', so the diff has to look like something broke rather than like a content edit. 82 tests green, tsc clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The existing ignore scenario could not fail. It changed four zones and ignored one, so three changed zones remained, the snapshot differed either way, and total-comparisons-diff stayed at 4 whether or not the rule fired. That is an unfalsifiable test, and it is why the earlier region builds looked like "ignore does nothing" — the result was inconclusive by construction, not negative. Adds a `carousel-only` variant where exactly one zone differs, and a pair over it: ignore on the carousel (expect ZERO diffs) against a standard control (expect the carousel flagged). Now the rule build can actually fail, and a zero means the rule worked rather than meaning nothing changed. The four-zone version is kept, relabelled to say why it is the weaker test. Correction to an earlier claim: the previous region builds were NOT invalidated by the snake_case settings bug — generateRegions never calls editProject, and regions.ts did not change in that commit. They were inconclusive for this reason instead. 82 tests green, tsc clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Live runs give layout=4 diffs and standard=0 on an identical layout-shift pair — the opposite of the "layout suppresses pure movement" claim the expectation made. The rule may be designed to DETECT layout change rather than ignore it, but that is not confirmed, so the build now tells QA which pair to compare instead of asserting a direction that the evidence contradicts. Verified in the same run: the isolated ignore pair works exactly as labelled — carousel-only change gives 0 diffs under `ignore` and 4 under `standard`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
One command —
seed-testbed --profile <env>— populates a Percy environment with real renderer-backed builds exercising every Percy feature, for manual QA. Default is a full pass (every account tier × every feature);--only <feature|tier>narrows it for targeted re-runs.Builds render actual DOM via
percy snapshot. The earlier approach uploaded solid-colour PNGs, which provedcreate project → token → build → diffand nothing else: the AI classifier had nothing to classify and region rules had nothing to reason about, so both were smoke builds by construction.Origin: brainstorm → plan (
docs/plans/2026-07-27-001-feat-percy-feature-testbed-plan.mdin the percy hub).Verified live
Every build below is on prod org
9560f98d, produced by this branch's generators.Region rules ✅ —
percy-testbed-rich-rgd7733ignoreon the carousel, carousel-only changestandard, same pairignoreon carousel, all 4 zones changedlayouton a displaced sidebarstandard, same pairThe first pair is the proof. Identical fixtures, the carousel is the only thing that changed:
standardflags it,ignoretakes the build to zero. Region config demonstrably reaches Percy and does what it says.That pair exists because the earlier version of this test could not fail — it changed four zones and ignored one, leaving three still changed, so the count stayed at 4 whether or not the rule fired. An unfalsifiable test reads as "the feature is broken" when it is really saying nothing. Isolating the change fixed that.
layoutrule flags 4 andstandardflags 0. The rule may be designed to detect layout change rather than suppress it — that is not confirmed, so the build asks QA to compare the pair rather than asserting a direction.AI ✅ — all three R12 capabilities —
percy-testbed-rich-bug322aai-details.total-diffs-reducedwasnullon every build before this branch's fixes; it now carries real values. The bug-classification build ships five deliberate defects — unreadable carousel text, banner copy clipped mid-word, a misaligned price row with an overlapping value, a failed ad image, and the sidebar overlapping the main column — because Percy tags such regionsvisual_quality: 'irregularity', which needs something that looks broken rather than merely changed.Core review states ✅ —
percy-testbed-rich-rich0180masterfeature/seed-changeIntelliIgnore⚠️ — runs, but not yet distinguishable —
percy-testbed-rich-fix4b7bintelliignore, 4 noise zonesstandardintelliignore, noise + price changeAI reduction happens, but the pattern does not track the rules — the
standardcontrol also shows reduction, suggesting project-level IntelliIgnore reduces across all builds independently of the per-regionalgorithm. These scenarios need the same isolated-change treatment the ignore test just got before they prove anything.Bugs this work surfaced
editProjectsent snake_case keys; Percy answers 200 for unknown keys and ignores them. Verified against prod:ignore_carousels_enabled→ stays false,ignore-carousels-enabled→ true, both 200. Keys are now dash-cased, andeditProjectreads the response back and throws when a value did not stick. The same bug was already insetAutoApprove, so auto-approve had been quietly doing nothing — and two existing tests asserted the same wrong key the code sent, which is why it survived.ai-enableddefaults to false, so AI and IntelliIgnore never ran. Both generators now enable it.reviewBuildtreated that as failure, which would break every generator that approves a baseline.What's in this PR
src/fixtures/) — a zoned storefront generated from one source so variants cannot drift, with the zones Percy'salgorithmConfigurationknows about (carousel, ad, banner, dynamic timestamp) plus a price table as the signal and a sidebar for layout. Deterministic: noDate.now, no randomness, so re-runs reproduce pixels.intelli-ignore,regionscoveringignore/layoutwith controls,aicovering all three R12 capabilities (build summary and reduce-diff render the upstreampercy_playwright/test_bed/aipages in place, no vendoring; bug classification uses in-repo fixtures because upstream's point at external sites).GeneratedBuild.expectation— every build states what QA should see, printed in the run summary.src/images.ts— dead with the upload path.Tests
82 unit tests (mocked HTTP + shell — nothing hits the network),
tsc --noEmitclean.npm test.Known limitations
BS_APP_ID).seed:allacross tiers still assumes the seeded account matrix exists on the target env.npm run buildoutput is not runnable (pre-existing):tscemits extensionless ESM imports, sodist/main.js— the declaredbin— fails to resolve. Everything runs throughtsx.Notes
profiles/*.example.jstemplates.🤖 Generated with Claude Code