Skip to content

test(e2e): fail tests when ZodError appears in browser console#1894

Draft
jeffredodd wants to merge 1 commit into
mainfrom
cursor/e2e-detect-zod-errors-306c
Draft

test(e2e): fail tests when ZodError appears in browser console#1894
jeffredodd wants to merge 1 commit into
mainfrom
cursor/e2e-detect-zod-errors-306c

Conversation

@jeffredodd
Copy link
Copy Markdown
Contributor

Summary

Today an e2e shard can pass green while the SDK is silently crashing into an error boundary mid-flow. The most common cause is a ZodError thrown out of @gusto/embedded-api's response parsing — the API client validates every response against the published Zod schema, and when the backend ships a shape the schema disagrees with, the parse throws and React catches it at the nearest error boundary. Example of what this looks like in the browser console:

Caused by: ZodError: [
  {
    "received": "Payroll",
    "code": "invalid_enum_value",
    "options": ["payroll", "ContractorPaymentGroup"],
    "path": ["Wire-In-Request-List", 0, "payment_type"],
    "message": "Invalid enum value. Expected 'payroll' | 'ContractorPaymentGroup', received 'Payroll'"
  }
]
    at safeParseResponse (chunk-WPXFCYAE.js?...)
    at async $do (@gusto_embedded-api-...wireInRequestsList.js?...)
The above error occurred in the <Root> component.

When this happens the SDK renders an error boundary instead of the expected screen, but unless the test happens to assert on something that's now missing it passes anyway. We want the shard to fail loudly so we catch schema drift between zenpayroll / gws-flows and the published @gusto/embedded-api Zod schemas.

Changes

  • In the shared Playwright page fixture (e2e/utils/localTestFixture.ts), subscribe to the console and pageerror events for every test.
  • Capture any message whose text contains ZodError (covers both uncaught throws and React's error-boundary console.error logs).
  • At the end of the test:
    • Attach the captured text as zod-errors.txt on the Playwright report so the HTML report / artifacts show exactly what was logged.
    • If the test would otherwise pass, throw to fail it. If the test already failed for another reason, the existing failure is preserved (the attachment still surfaces the Zod text).

This fixture wraps every test in every config (playwright.config.ts, playwright.demo.config.ts, playwright.local.config.ts), so the check is on by default for MSW-mode runs, demo runs, and local-backend runs.

Testing

  • npm run test:scenarios — passes (scenario module tests).
  • npx tsc --noEmit ... e2e/utils/localTestFixture.ts — passes.
  • npx prettier --check e2e/utils/localTestFixture.ts — passes.
  • Behavioral check (manual): the listener fires before await use(page) returns, so it sees console output for the entire test lifetime, including renders triggered by page.goto and any subsequent interactions. If a ZodError shows up at any point the test fails with a message that includes the full captured text plus a zod-errors.txt attachment.

Related

  • Affects every e2e shard (MSW mode default config + demo + local).
Open in Web Open in Cursor 

Watch the browser console and pageerror events in the shared page fixture
for 'ZodError' strings. @gusto/embedded-api validates every response with
Zod, so when the backend ships a shape that disagrees with the published
schema the request rejects and surfaces in the console — either as an
uncaught page error or via React's error-boundary console.error.

Previously tests could pass while the SDK was silently crashing into an
error boundary mid-flow because the harness never asserted on console
output. Now the fixture collects any ZodError text observed during the
test, attaches it as zod-errors.txt for the HTML report, and fails the
test (only when it would otherwise pass — existing failures are
preserved).
@jeffredodd jeffredodd force-pushed the cursor/e2e-detect-zod-errors-306c branch from 29406e9 to 2f4a319 Compare May 21, 2026 22:03
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