Skip to content

qa: fixtures, screenshot diffing, and a CI job that can actually fail - #2819

Open
kushagrasarathe wants to merge 13 commits into
feat/design-systemfrom
qa/visual-shots
Open

qa: fixtures, screenshot diffing, and a CI job that can actually fail#2819
kushagrasarathe wants to merge 13 commits into
feat/design-systemfrom
qa/visual-shots

Conversation

@kushagrasarathe

Copy link
Copy Markdown
Contributor

Summary

Verifying a design-system change meant building the base branch, screenshotting by hand, building the head branch, screenshotting again, and pairing the results into a table. PR #2758 shipped 25 of those, at one width, and the table could not say which screens had actually moved.

This makes that automatic, and removes a CI job that had reported success without running a test since April.

Base: feat/design-system (PR #2813). Ships with the design-system release.

What lands

1. Named app states — ?__fixture=<name>

30 fixtures. <route>?__fixture=<name> answers every API call from a named state, so any screen renders with no database, no API, no provider keys and no harness secret. The same URL works in a browser, in /dev/devices at six widths, on a Vercel preview, and under Playwright.

Hugo's question — "I want to see the profile screen with username bh12ui2buibui52bi and 1.2 billion points" — is now a URL. /dev/fixtures lists them all.

Built on demo-api, which already answers every route the app calls, so a fixture states only what differs. Most are one to three lines.

2. Screenshots — npm run test:visual

30 fixtures × 4 widths (320/375/393/430) = 120 PNGs in about 60 seconds. 320 is an iPhone on Larger Text and finds the most bugs; 430 is a Pro Max.

npm run test:visual:diff a b prints which screens moved, worst first.

A diff tool that cries wolf gets ignored in a week, so the run pins everything that moves: frozen clock, no cross-origin requests, no service worker, animations and transitions off, fonts and images awaited, and a wait for two equal text samples because the points total counts up from JavaScript. Two full captures of one build differ in zero pixels. A deliberate margin change on one heading surfaced exactly the three affected fixtures at all four widths, nothing else.

The run fails rather than shoots when a loader is on camera. A green run of 120 spinners looks identical to a green run of 120 screens.

3. CI — e2e out, ds-shots in

The e2e job has reported success without running a test since 271750934 (2026-04-27), which demoted it with continue-on-error and the note "TEST_HARNESS_SECRET not in CI env yet". It dies in globalSetup before a spec loads.

Adding the secret cannot fix it: requireTestMode only accepts a database on localhost, so no hosted API can ever serve /dev/test-session.

ds-shots replaces it — captures the head, diffs against a baseline captured on pushes to dev, and keeps one PR comment up to date with the screens that moved and the images inline. Advisory: not in ci-success.needs, so it never blocks a merge. The gate stays at about 110 seconds.

4. The old spec suite

18 specs, 2,154 lines, running nowhere. Eight contained no assertion at all. One asserted that the URL contained the step it had just typed into the URL.

Four earned their place and are kept. They did not actually run either — with no API the layout holds every screen on the loading mascot, so two failed and three passed against a page that had rendered nothing. ?__fixture=home gives them a session; they now assert against real content in 11 seconds.

Coverage this gives up

Stated plainly. No fixture covers the add-money or withdraw country forms, /claim link states, /qr-pay, /send/success, or the public routes outside (mobile-ui). The claim page runs a real crypto check against the link's pubKey, which static fixture responses cannot satisfy — that belongs in the nightly sandbox tier. None of it was running before this PR either.

Verified

  • typecheck clean · npx jest 302 suites, 3632 tests · prettier clean · ds-lint --check no metric increased
  • Kept specs: 7 tests, 11.3s, green against a production build
  • Capture stability re-run after every change: 0 changed, 120 unchanged
  • Screenshots read back by eye, not trusted from a green run — eight of the original 38 fixtures rendered something other than what they claimed and were fixed or deleted

Follow-ups, not in this PR

  • error-points and error-user fixtures land after the production-fix PR, or they would capture the bugs those fixes remove
  • Nothing runs the four kept specs in CI. test:e2e:regression takes 8 seconds and needs no secret — cheap to add
  • The /dev chunks still ship in the production bundle; the routes are blocked, the JavaScript is not

…no backend

A screen in a specific state was, until now, something you reproduced: seed a
user, move the balance, wait for a provider, and hope the state survived until
the screenshot. The states that break layouts — a 17-character username, a
nine-digit points total, an empty list, a rejected document — are exactly the
ones nobody reproduces twice.

`<route>?__fixture=<name>` answers every API call from a named app state. The
same URL works in a browser, in the /dev/devices harness at six widths, on a
preview build, and under Playwright later. 38 states: one per screen, plus the
text, number, empty, KYC and error cases that carry the layout risk.

The baseline is the demo API, not a new mock layer. demo-api already answers
every route the app calls and already sits on this exact line in callApi, so a
fixture states only what differs and most are one to three lines.

Four screens needed more than API answers. The kernel address, the balance and
the charges socket are not responses, so a fixture screen waited forever on
state no API can give it. Each extends the isDemoMode() branch that already
exists rather than adding a second concept.

Everything hangs off DEV_TOOLS_ENABLED. Verified on a production build served
with next start: /profile?__fixture=… redirects to /setup, no cookie is
written, no session key is set, nothing renders. /dev/fixtures answers 404.

Known and deliberate: the registry still reaches production chunks (~35 KB of
fixture names and English descriptions — no credentials, no user data).
Webpack emits a chunk for every import() in the source, so neither the gate
nor a lazy import removes it. /dev/devices, /dev/ds and /dev/debug already
ship the same way. Dropping them needs a build rule over every /dev route,
which is a repo-wide decision and not this change.
Verifying a design-system change meant building the base branch, taking
screenshots by hand, building the head branch, taking them again, and pairing
them into a table. PR #2758 shipped 25 of those, at one width, and nobody
could tell from the table which screens had actually moved.

`npm run test:visual` builds and captures every fixture at 320, 375, 393 and
430. 320 is an iPhone on Larger Text and finds the most bugs; 430 is a Pro
Max. `test:visual:diff` pairs two capture directories and prints what changed,
worst first. 152 screens, about 70 seconds after the build.

The registry is the only route list. Adding a fixture adds four screenshots;
there is no second list to forget.

A diff tool that cries wolf gets ignored in a week, so the run pins everything
that moves: a frozen clock, no cross-origin requests, no service worker, CSS
animation and transitions off, fonts and images awaited, and a wait for two
equal text samples because the points total counts up from JavaScript and
ignores the stylesheet. Two full runs of the same build: 0 changed, 152
unchanged. A deliberate margin change on one heading: exactly the three
/limits fixtures at all four widths, nothing else.

The run fails rather than shoots when a loader is on camera. A green run of
152 spinners looks exactly like a green run of 152 screens, and that mistake
has been made in this repo before.

No API, no database, no provider keys, no harness secret — the fixtures answer
every call, so this can run anywhere a build runs.
A fixture that says one thing and renders another is worse than no fixture.
It becomes a screenshot baseline, and every later diff compares against a
screen nobody looked at.

Reading all 152 screenshots found eight lying and five unreachable.
`kyc-action-required` promised a rejected document and rendered four
green-ticked regions. `error-user` promised the backend error screen and
rendered an unsupported-browser modal. `huge-limits` promised nine-figure caps
that sit one screen deeper than the route it pointed at. `withdraw` promised
two saved accounts and rendered "No accounts yet".

Most of these overrode an endpoint the screen never calls. The withdraw list
comes from `user.accounts`, not `GET /users/accounts`. The region lists come
from capabilities rails, not from `identityVerification.status`. The activity
list injects a row per badge and one for the ID check, so clearing `entries`
alone still leaves four rows.

Five are deleted rather than faked: their states need a tap the camera cannot
make, or a query string the route field cannot carry. 33 fixtures, all
verified by eye at 375 and at 320 for the overflow cases. Two captures of the
same build still differ by nothing.

Found and left alone, both worth their own change: `BackendErrorScreen` is
unreachable, because the redirect effect in `(mobile-ui)/layout.tsx` fires on
the same state without a `userFetchError` guard — a backend outage sends
signed-in users to the signup flow. And the "Pending" region badge on /limits
can never render, because `hasFunctionalRail` counts a pending rail as
unlocked before the badge is ever considered.
The `e2e` job has reported success without running a test since 2026-04-27.
It dies in globalSetup on a missing TEST_HARNESS_SECRET, and the
continue-on-error added that day paints the failure green. The artifact it
uploads is empty for the same reason.

Adding the secret would not fix it. requireTestMode only accepts a database
on localhost, so no hosted API can ever serve /dev/test-session. Keeping the
specs would mean running Postgres and the API inside the job. The specs stay
on disk; a later change folds them into the fixture approach, which needs
neither.

`ds-shots` replaces it with something that can actually fail. It captures 33
fixtures at four phone widths, diffs them against the base branch, and keeps
one comment up to date with the screens that moved, worst first, with the
images inline. No API, no database, no provider key, no secret.

It never blocks a merge — it is not in ci-success.needs, and the gate stays at
about 110 seconds. Removing e2e takes a little off the median and much more
off the tail: e2e carried a 20 minute timeout, hung three times on 2026-08-18,
and report and ci-success both waited on it, so ci-success never reported at
all and the content automerge stalled with it.

The baseline is captured on pushes to dev and main, not inside the PR. A cache
written by a PR run is scoped to that PR, so a sibling PR cannot read it; ten
PRs on one base restore the baseline instead of rebuilding it.

Fork PRs skip both jobs. They get a read-only token and no submodule token, so
the checkout and the comment cannot work, and a red job would tell the author
nothing they can fix.

Assets go to pr-assets-<n> as a force-pushed orphan under a per-commit path,
so the branch never grows and raw.githubusercontent cannot serve yesterday's
image. A new workflow deletes the branch when the PR closes. The 20 branches
left by the hand-made version of this convention still need one sweep.
… not

The Playwright suite has run nowhere since 2026-04-27: not in CI, and not
locally without a secret nobody has. Eight of its eighteen specs contained no
assertion at all — they opened a page and took a screenshot. The shots
pipeline does that at four widths, pixel-stable and diffed, so those were
already replaced.

Of the rest, most asserted something the code cannot break. One checked that
the URL contained the step it had just typed into the URL. One checked a back
button that an eslint rule and a hook test already guard.

Four earned their place: icons keep their inline fill and the stroke their
viewBox asks for, the claim and send-link pages render without a wagmi
provider crash, a stale cookie does not bounce /setup to /home, and the
captured share-asset card is not blank.

Those four did not actually run, which is how they survived. With no API the
app layout holds every screen on the loading mascot, so two failed and three
passed against a page that had rendered nothing. `?__fixture=home` gives them
the fake session, and they now assert against real content in 11 seconds.

The icon check was stale too. It demanded stroke-width 2 from every icon, but
Icon.tsx crops seven of them to a 20-unit viewBox and the stroke scales with
the crop. It now reads the rule from each icon's own viewBox, so it stays
honest without an allowlist of seven names to maintain.

Coverage does go with this. No fixture covers the add-money and withdraw
country forms, the claim link states, /qr-pay, /send/success, or the public
routes outside (mobile-ui). Those need real links, real rails or a route group
the fixture registry does not reach, and they belong in the nightly sandbox
tier. Screenshot-only coverage of everything else was already dead.

Everything the deletions orphaned goes with them: the old config, the global
setup, all six utils, both scripts, and five package scripts that pointed at
nothing.
Rebasing onto the design-system tip turned the registry test red, which is
what it is for. /settings and /notifications are gone — bc084ae removed the
orphaned settings root page, 537c2a4 removed the notifications page — so
three fixtures pointed at routes that no longer exist.

A fixture that opens a 404 is not a broken screenshot. It is a screenshot
baseline for a screen that is not in the product, and every later diff
compares against it.

30 fixtures. settings-language stays; only the settings root went.
@vercel

vercel Bot commented Aug 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview Aug 27, 2026 7:04am

Request Review

@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 73e59d65-2a9f-41d0-b257-99d9a6ab4713

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

…in CI, fix the frozen clock

Three review findings. The fixture cookie writes were origin-wide and
unguarded: activate() overwrote a real jwt-token session, and while
?__fixture=off sat in the URL every API call re-deleted a freshly
created real login. Both writes now touch the cookie only when its
value is the fixture's own. The four kept behaviour specs ran in no
CI job — they now run inside ds-shots, which already has the preview
build and Chromium (verified: 7 pass in 11.5s). FROZEN_NOW predated
the registry's newest timestamp, so relative times rendered as future
dates; it now sits after 2026-08-01.
@kushagrasarathe

Copy link
Copy Markdown
Contributor Author

/code-review pass — 2026-08-26

Fixed in b70943f (typecheck + 3,632 unit tests + all 7 regression tests + a live capture smoke all verified green):

  • The fixture cookie writes were origin-wide and unguarded: activate() overwrote a real jwt-token session, and while ?__fixture=off sat in the URL, every API call re-deleted a freshly created real login. Both writes now touch the cookie only when its value is the fixture's own.
  • The four kept behaviour specs (setup reload-loop guard, icon-regression, send-link hydration, share-asset capture) ran in NO CI job. They now run inside ds-shots, which already has the preview build and Chromium — 11.5s.
  • FROZEN_NOW (2026-06-15) predated the registry's newest timestamp (2026-08-01), so relative times rendered as future dates in every baseline. Now 2026-08-15; baselines recapture on the next dev push.

Found but NOT fixed (advisory, for the author to weigh):

  • Server actions bypass the fixture layer (interception is client-only), so a fixture screen whose data flows through serverFetch can hit the real API from CI. Worth checking which of the 30 fixtures depend on server actions.
  • Baselines save only on dev/main pushes; a PR based elsewhere falls back to the newest dev/main baseline and diffs against the wrong code line. Fine for the intended dev-based flow, misleading for feature-train PRs (like this one).
  • ds-shots failure is escalated by nothing (not in ci-success.needs) — the 'advisory job silently dead for weeks' failure mode this PR describes for the old harness.
  • Cleanups suggested, not applied: shared isBackendlessSession() helper for the isDemoMode()||fixture disjunction (3 sites today, ~30 potential), deepMerge reuse from i18n, dedup of the two Playwright configs and the three PR-comment builders.

@jjramirezn

jjramirezn commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Review notes. Full digest for the whole batch: https://claude.ai/code/artifact/9d565cfc-146b-44d9-98a6-2d76cd081bad

The fixture idea is good and the dev story holds up. ?__fixture=<name> with no database, no API and no keys is the right primitive. Findings below went through a refute pass; two candidates died and are listed at the end so nobody chases them.

This also conflicts with its base right now, so it needs a rebase before it can merge.

1. The visual diff has never run on this PR

tests.yml limits the pull_request trigger to main, dev and develop. This PR is based on feat/design-system, so no pull_request run fires. On the push run, ds-shots-filter and ds-shots both show as skipped.

The baseline problem stacks on top. Save this commit as the baseline is gated on github.event_name == 'push' with the branch allowlist ["dev","main"], so nothing ever writes a feat/design-system baseline. Actions cache scoping lets a PR read caches from its own ref, its base branch and the default branch only — main is the default here, so a dev-written baseline is not even reachable from a DS-based PR. The restore-keys: ds-shots- fallback then serves whatever main entry exists, and every DS change reads as a visual regression.

So the headline feature cannot work on the branch it ships on, and it has not been seen to work in CI. Worth solving before merge — either compute the baseline in-job from the merge-base, or widen the trigger and the allowlist.

2. The capture spec never checks that fixture mode is on

e2e/shots/fixtures.spec.ts:112 is goto then settle(page) then screenshot. Nothing asserts the URL after navigation, and nothing reads the fixture back. The only failure it can produce is a settle() timeout.

In CI this is safe — the Build step sets NEXT_PUBLIC_VERCEL_ENV: preview, and test:visual sets it too. Locally it is not. pnpm test:visual:capture against a normal build gives DEV_TOOLS_ENABLED === false, every protected route redirects to /setup, and /setup settles (it drops its mascot loader once sessionChecked flips). You get 120 screenshots of /setup under the fixture filenames and a no screen moved comment.

reuseExistingServer: true at playwright.shots.config.ts:79 is unconditional, so a stale next start on 3080 gets adopted the same silent way. Gate it on !process.env.CI, and assert the URL or read back peanut_fixture after goto.

Also FIXTURE_PARAM is exported from active.ts but the spec hardcodes ?__fixture=. Rename the param and the spec still passes while capturing nothing real.

3. Path filter blind spot

The ds-shots-filter KEEP regex covers src/, e2e/shots/, the visual scripts, the shots config and a few build files. It does not list e2e/flows/ or playwright.regression.config.ts. A PR touching only those gets render=false, so the four behaviour specs never execute.

To be clear this is not a regression — the old e2e job had continue-on-error: true and playwright.config.ts has no webServer block, so it pointed at port 3000 with nothing running and could never pass or fail a test. Advisory before, advisory now. But the new filter adds a way to skip the specs silently, so please add the two paths.

4. Render-phase side effect in useWallet

src/hooks/wallet/useWallet.ts:214 calls ensureActiveFixture() in the render body, not in an effect — the sibling useDemoBalanceUnits on the next line is a useSyncExternalStore, so the contrast looks unintended. On the first render of a ?__fixture= URL that call writes sessionStorage (active.ts:25) and can write document.cookie (active.ts:31). React does not promise to run render once.

5. A real session gets faked for the rest of the tab

Not the direction I first suspected. A user who is already logged in and then opens a ?__fixture= URL keeps their real session — activate() skips the cookie write because a cookie is present — while sessionStorage pins the fixture and api-fetch fakes every response until ?__fixture=off. Worth a guard or at least a visible banner, given /dev tooling is reachable on previews.

Checked and cleared — please ignore these

  • No hydration mismatch. (mobile-ui)/layout.tsx holds children behind isReady and returns the mascot loader on both branches until the effect fires, so useWallet never runs during SSR for any of the 30 fixture routes. There is no server render of the balance to diverge from.
  • A fixture cannot survive into a real login. Both cookie writes are guarded, and while a fixture is stored api-fetch intercepts every call, so the login response that would mint a real JWT is itself faked. The guards in b70943f4 are correct.

Minor: the workflow comment says 33/132 screenshots. 30 fixtures at 4 widths is 120.

…al-shots

# Conflicts:
#	.github/workflows/tests.yml
…rain, prove fixture mode on camera

- tests.yml: feat/design-system joins the push/PR triggers and the
  baseline-save allowlist, because a DS-based PR can only read caches
  written on its base branch; remove after the DS branch merges to dev
- ds-shots-filter KEEP now lists e2e/flows/ and the regression config,
  so a PR touching only the behaviour specs still runs them
- fixtures.spec.ts asserts the app promoted the URL param into
  sessionStorage before shooting — a non-preview build otherwise yields
  120 screenshots of /setup under fixture filenames; the param comes
  from FIXTURE_PARAM instead of a hardcoded string
- reuseExistingServer only outside CI, so a stale next start is never
  silently adopted on a runner
- peekActiveFixture(): read-only fixture lookup for render bodies;
  useWallet no longer writes sessionStorage/cookie during render
- FixtureBanner: fixed strip naming the active fixture, since a
  logged-in user opening a ?__fixture= link would otherwise see faked
  API data with no visible tell; tap exits via ?__fixture=off
- fix the fixture arithmetic: 30 fixtures x 4 widths = 120 PNGs
@kushagrasarathe

Copy link
Copy Markdown
Contributor Author

Re: your review — rebased on feat/design-system (merge 404bc6a) and all findings addressed in 64bd8db. Every gate re-verified after the merge: typecheck clean, 3,664 unit tests, preview build + 7 regression tests, capture smoke.

  1. Diff never ran on this PR — took your 'widen the trigger and the allowlist' option: feat/design-system added to the pull_request branches, the push branches, and the baseline-save allowlist (commented as the DS release train, remove after it merges to dev). Cache scoping then works: DS-based PRs read the baseline their base branch pushes.
  2. No fixture-mode assertion — after settle the spec now expect.polls sessionStorage[FIXTURE_STORAGE_KEY] to equal the fixture name (key exported from active.ts), and the URL is built from the imported FIXTURE_PARAM — rename the param and the spec breaks loudly instead of shooting /setup 120 times. Proven: mutating the expected name fails both widths with the 'fixture mode never engaged' message. reuseExistingServer is now !process.env.CI in both the shots and regression configs.
  3. Path filtere2e/flows/ and playwright.regression.config.ts added to the KEEP regex.
  4. Render-phase write — added a read-only peekActiveFixture() twin (URL→session, zero writes, 'off' reads as null); the useWallet render body uses it. ensureActiveFixture (the promoting write) stays for the non-render sites: api-fetch, respond.ts, the kernelClient effect, the websocket callback path.
  5. Invisible fake session — new FixtureBanner: fixed bottom strip 'fixture: — API faked · tap to exit' (plain <a href="?__fixture=off">), DEV_TOOLS_ENABLED-gated, mounted once in the (mobile-ui) layout. Side effect worth knowing: the banner is on camera, so all 120 baselines shift once — and every capture now carries visible proof of fixture mode.
  6. Minor: 33/132 → 30/120 fixed (plus the same stale 152 in the spec comment).

Thanks for the refute-pass section — the two dead candidates stayed dead.

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 3686 ran, 0 failed, 0 skipped, 1.0m

📊 Coverage (unit)

metric %
statements 69.8%
branches 55.4%
functions 61.3%
lines 70.8%
⏱ 10 slowest test cases
time test
3.7s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
1.0s src/hooks/query/__tests__/user.test.tsx › does NOT clear a token that rotated mid-request (stale 401 racing a fresh login)
0.5s src/utils/__tests__/sentry.utils.test.ts › defaults to the client budget under a browser global
0.4s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
0.4s src/utils/__tests__/crisp.test.ts › settles, and hands back a usable plugin, against a real-shaped plugin proxy
0.4s src/utils/__tests__/auth-token.test.ts › authReady does not park — hydrates the plain token without an unlock
0.4s src/utils/__tests__/crisp.test.ts › retries configuration on the next open after a failure
0.4s src/app/[locale]/(marketing)/status/StatusBoard.test.tsx › renders one 72-hour bar row per service, in user-facing language
0.4s src/utils/__tests__/crisp.test.ts › configures once across repeated support opens
0.3s src/app/(mobile-ui)/withdraw/__tests__/withdraw-states.test.tsx › Bank withdrawal keeps the $1 minimum for sub-$1 amounts
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

🖼 Visual diff — no baseline yet

Nothing is cached for feat/design-system. The next push to that
branch captures one, and this comment fills in on your next push here.

…e, failure artifacts

Three CI-only failures from the first run ds-shots ever made on this
PR. The ds-lint ratchet counted the fixture banner's inline styles —
src/dev/fixtures is dev tooling, same class as the dev/ds and
dev/devices dirs already allowlisted. The icon spec waited for
'visible' where its assertions only read attributes — 'attached' with
a longer budget survives a slow runner. The regression step moves to
the end of the job so a red spec cannot abort the baseline/diff/
comment steps, and on failure the e2e/__results__ error contexts
upload as an artifact — the share-builder failure reproduces nowhere
locally (passes with CI=1), so the next red run must show what the
runner actually rendered.
The artifact from the first upload showed the runner rendering the
not-found page: BASE_URL falls back to https://peanut.me when unset,
which flips the dev layout's prod gate, and CI has no .env to set it.
The icon and share-builder specs drive /dev pages, so both died on a
404 that never reproduces locally. Also removed the swallowed
waitForSelector in the button test — it passed green against that
same 404 (zero icons found, empty loop).
…al-shots

# Conflicts:
#	e2e/flows/dev-showcase.spec.ts
The second base merge collided on dev-showcase.spec.ts: this branch
deleted it with the old harness, the DS branch just rebuilt it as the
M2 regression target. Keep the DS spec — it has a live consumer again
— and restore the two utils it imports. Its console-error gate now
allowlists what the static regression harness cannot provide: SW
registration, backend 401/403 resources, and public-RPC CORS refusals
from the local origin (external target + 127.0.0.1 origin only, so an
app-own CORS bug still fails). All 10 regression tests pass under
CI=1.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants