Skip to content

test(bake): assert the served document and page state after each edit in ecosystem.test.ts - #39432

Closed
robobun wants to merge 1 commit into
mainfrom
farm/734b9b94/bake-ecosystem-test-speed
Closed

robobun wants to merge 1 commit into
mainfrom
farm/734b9b94/bake-ecosystem-test-speed

Conversation

@robobun

@robobun robobun commented Aug 17, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • test/bake/dev/ecosystem.test.ts (one test, the svelte islands fixture) was flagged by the slow-test sweep at 11-13s on the release Linux lanes. Measured locally it is 3.9s, of which 3.0s is the harness's fixed poll for an error overlay (Client.expectErrorOverlay, bake-harness.ts:1046: 5 x 200ms, no early exit when nothing is shown), paid once in dev.client() and once after each of the two writes; the dev server, both bundles, the client and the test's own work add up to about 0.3s. On the 13s CI run (build 99854, where this was the third file the job ran) the log timestamps put 4.8s in starting the dev server (it imports the svelte compiler), 3.8s in starting node + happy-dom, 3.0s in the polls and about 0.25s in the rest, so the CI number is mostly the cold box.
  • The poll lives in the harness and bake test harness: check the error overlay once instead of polling for a second per connect and write #37866 (open) removes it for every connect and write site in test/bake, which takes this file to about 0.9s without touching it. A per-file errors: null opt-out would cover two of this file's three polls and become dead as soon as that lands (bake test harness: check the error overlay once instead of polling for a second per connect and write #37866 deletes the one existing opt-out of that kind; the other errors: null uses in test/bake are for writes that expect errors or have no client), so this PR does not add one; the speed of this file is bake test harness: check the error overlay once instead of polling for a second per connect and write #37866's.
  • What this file can improve on its own is its assertions: an SSR failure was throw new Error("failed"), the served HTML was checked with three toContains, the click waited a fixed 10ms, the hot update was checked with toInclude("magical"), and the page was not looked at after the reload at all.

Fix

  • servedDocument() asserts the response is 200 (a throw during SSR is served as the 500 fallback page; the error itself is in the dev server output the harness echoes) and returns the whole document with bake's generated names normalized and one tag per line; it is compared with toBe against expectedDocument({ server, island }) before and after the edits, so the two fetches are shown to differ exactly by the two edited strings, and the stylesheet link, island wrapper and $islands script are covered as well. A template rather than a snapshot because snapshot matchers throw outside bun test, which would break the harness's interactive mode (bun test/bake/dev/ecosystem.test.ts svelte); checked that mode still passes.
  • The template pins the document as served today, which has only the island's stylesheet: the fixture's plugin gives each component's CSS the component's own path in the svelte-css namespace, and the bundler currently merges the server component's CSS into the component (the module identity bug bundler: key PathToSourceIndexMap on (namespace, path) for plugin module identity #36549 fixes; with distinct paths both stylesheets are served). The line carries a comment saying so, so that bundler: key PathToSourceIndexMap on (namespace, path) for plugin module identity #36549 knows to add the second link here when it lands.
  • pageState() reads the server component text, the island text and the button text in one round trip and is compared with toEqual after connecting, after the reload and after the hot update. The post-reload page is now asserted at all (new server text, and its counter started over), and the hot update is checked for its exact text and for leaving the server text alone.
  • clickButton() waits for the button's text to change with a MutationObserver registered before the click instead of sleeping 10ms, and is repeated after the reload and after the hot update, so the reloaded page and the re-mounted island are shown to be interactive, not just rendered.
  • Fixture: render() resets the island counter per render (it already reset the island map), so the document after the edits has I:0 like the first one instead of a counter carried across renders; the server component's paragraph gets an id for pageState().
  • timeoutMultiplier: 2 is dropped: it predates WAIT_MULTIPLIER, which now scales the base 30s by 3 for debug, 3 for ASAN and 2 for CI; the release CI budget becomes 60s and the ASAN one 180s for a test that took at most 13s on a cold lane.
  • Verified: bun bd test test/bake/dev/ecosystem.test.ts passes (debug + ASAN); 10/10 runs with the release binary at 3.85-3.94s, i.e. unchanged from before (3.87-3.99s), as expected since the time is the poll; the interactive-mode run above.
Earlier version of this PR

The first push also passed errors: null to the two writes, which took the file from 3.9s to 1.86s locally (median of 30 runs) and passed 131 of 132 runs under 5x CPU oversubscription; the one failure was a late watcher event rebuilding index.svelte in the next batch, which the previous version of the test is equally exposed to. Review pointed out that this duplicates #37866 for two of the file's ~three poll sites while that PR removes the same opt-out elsewhere, so it was dropped; the assertions are unaffected, since dev.patch() resolves on the client's ack either way.

Background

  • Bake is bun's dev server. test/bake drives it through bake-harness.ts: dev.patch() edits a file and resolves once the rebuild is done and every connected client has acked it; dev.client() loads the page in a node process running happy-dom (client-fixture.mjs); c.js evaluates code inside that page. Running a test file directly instead of through bun test runs one case interactively, stopping before each step.
  • The fixture is a minimal svelte framework: pages/index.svelte is a server component rendered with svelte/server; _Counter.svelte is marked "use client", so the server renders it inside a <bake-island> and the client bundle hydrates it from the self.$islands script. Editing the island is a hot update applied in place (svelte's HMR re-mounts the component, which is why its count goes back to 5); editing the server component makes the runtime reload the page, which the test allows with c.expectReload().
  • The route script URL is /_bun/client/route-<index><generation>.js, where the generation is randomized every time the route's client bundle is invalidated, and the stylesheet URL is a hash, which is why both are normalized before the comparison.

@robobun

robobun commented Aug 17, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: reworked after review. Measured where the file's time goes (3.0 of 3.9s is the harness's fixed error overlay poll, which #37866 removes for all of test/bake; on CI the rest is cold start of the dev server and of node + happy-dom), so this PR no longer tries to speed the file up and is the assertion half only: served document compared against one template before and after the edits, page state and a click asserted after connect, reload and hot update, islands numbered per render in the fixture. Passes with bun bd test test/bake/dev/ecosystem.test.ts and in the harness's interactive mode. Waiting for CI.

@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 6 minutes

Limit details: You’ve used all 5 included reviews currently available under your plan.

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e599e4a9-5026-40bf-b42f-8d056ee600a1

📥 Commits

Reviewing files that changed from the base of the PR and between 1dd66af and b5eeeec.

📒 Files selected for processing (3)
  • test/bake/dev/ecosystem.test.ts
  • test/bake/fixtures/svelte-component-islands/framework/server.ts
  • test/bake/fixtures/svelte-component-islands/pages/index.svelte

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

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — test-only change that tightens assertions and cuts ~2s of fixed polling.

What was reviewed:

  • errors: null matches the established opt-out in hot.test.ts/bundle.test.ts/css.test.ts; dev.patch still awaits the client ack, and the new pageState() assertions cover what the overlay poll guarded.
  • clickButton() replaces the fixed setTimeout(10) with a MutationObserver — the observer is registered before .click(), so no race.
  • Fixture change resets nextIslandId alongside the existing islands = {} reset so repeated renders produce stable I:0 ids for the snapshot; normalizeBunSnapshot handles the Bun v<bun-version> substitution and Dev/Client are exported from the harness.
  • Snapshot embeds svelte scoped-class hashes (svelte-1f5jrvn) — stable while the fixture CSS is unchanged, normal snapshot maintenance otherwise.
Extended reasoning...

Overview

Test-only PR touching one bake dev-server test and its svelte fixture. ecosystem.test.ts is rewritten to (a) pass errors: null on the two dev.patch() calls to skip the harness's fixed 1s overlay poll, (b) replace three toContain checks and a throw new Error("failed") with a full-document inline snapshot via servedPage(), (c) replace a setTimeout(10) click wait with a MutationObserver, (d) assert exact page state (toEqual on server/island/button text) after connect, after the reload, and after the hot update, and (e) drop timeoutMultiplier: 2. The fixture gains nextIslandId = 0 in render() (alongside the existing islands = {} reset) and an id="server_text" on the server paragraph. No production code is touched.

Security risks

None. Changes are confined to test/bake/ — a test file and two test-fixture files.

Level of scrutiny

Low-to-medium. Test-only, but the repo guidelines are explicit that weakening or de-flaking tests must preserve the property being asserted. Here the assertions move in the stronger direction: full-document snapshots replace substring checks, exact toEqual replaces toInclude, page state is now asserted after the reload (previously unchecked), and interactivity is verified after each edit rather than once. The dropped overlay poll is compensated by directly asserting rendered content after each write, which would fail if a build error left stale content.

Other factors

  • errors: null is the same opt-out already used in three sibling bake test files; Dev/Client are exported from bake-harness.ts and c.js<T> accepts a type parameter.
  • normalizeBunSnapshot in test/harness.ts replaces Bun v${version} with Bun v<bun-version>, matching the snapshot; the asset hash and route generation are normalized by the test's own regexes.
  • The MutationObserver is attached before button.click() and disconnects on first change, so the promise cannot miss the mutation.
  • The fixture's nextIslandId reset is a straightforward per-request state reset in a test-only server; it makes the second SSR snapshot deterministic (I:0 instead of a carried-over counter).
  • The PR description documents 30/30 sequential release runs, 131/132 under heavy CPU oversubscription, and debug+ASAN passes; the one stress failure was traced to a pre-existing late watcher event that the old test is equally exposed to.
  • Snapshot brittleness from svelte's scoped-class hashes is acceptable — they derive from the fixture's own <style> blocks, which this PR does not change.

… in ecosystem.test.ts

The svelte islands test checked the served HTML with three substring
matches, treated an SSR failure as `throw new Error("failed")`, waited a
fixed 10ms after clicking, checked the hot update with toInclude("magical")
and did not look at the page after the reload at all.

It now compares the whole served document (with bake's generated names
normalized) against one expected-document template before and after the
edits, asserts the response status instead of searching for the fallback
page, reads the server text, island text and button text of the live page
into one object after connecting, after the reload and after the hot
update, and clicks the button after each of those, waiting for its text to
change with a MutationObserver, so the page is shown to be hydrated and the
re-mounted island to be interactive rather than just rendered.

The fixture numbers islands per render instead of across the server's
lifetime, so the document served after the edits differs from the first one
only by the edits, and the server component's paragraph gets an id the test
can select.

timeoutMultiplier: 2 predates the harness scaling its timeouts for debug,
ASAN and CI builds and is dropped.

The file's time (about 3.9s locally, of which 3.0s is the harness's fixed
error overlay poll on connect and after each write) is left to #37866,
which removes that poll in the harness.
@robobun
robobun force-pushed the farm/734b9b94/bake-ecosystem-test-speed branch from b5eeeec to bb6c8ad Compare August 17, 2026 17:55
@robobun robobun changed the title test(bake): drop the per-write overlay poll in ecosystem.test.ts and assert the page state test(bake): assert the served document and page state after each edit in ecosystem.test.ts Aug 17, 2026
Comment thread test/bake/dev/ecosystem.test.ts
@robobun

robobun commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by #39488, which consolidates the open Bake PRs from this period into one branch. It carries this PR's change and its test, reworked where needed (see the commit in #39488 that names this PR). Closing this one in its favor.

@robobun robobun closed this Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant