镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

test(browser): prepare fixture dependencies once per run - #1126

Merged
kalvinnchau merged 1 commit into
mainfrom
larry/fixture-vite-deps
Oct 15, 2026
Merged

kalvinnchau merged 1 commit into
mainfrom
larry/fixture-vite-deps

Conversation

@loganj

@loganj loganj commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

🤖

Summary

Browser tests that start their own Vite dev server sometimes fail with page.goto: Timeout 15000ms exceeded. The cause is not a slow page. Every fixture server started with an empty dependency cache. So on the first page load, Vite scanned the fixture pages and bundled all of the app's npm dependencies inside the test's 15 s navigation budget. On a busy CI runner that work can take longer than 15 s. In one CI job (116031456316), Vite logged scanning dependencies… bundling dependencies… before each test, and all 7 messages.spec tests failed in a row.

After this change, the browser test run scans and bundles the dependencies once, in Playwright's globalSetup, before any test starts. Each fixture server starts from its own copy of that cache. No fixture server scans or bundles dependencies during a test.

Details

  • Why a copy is valid for every server. Vite reuses a dependency cache only if the server's config hash matches. That hash includes the root, the plugin names and optimizeDeps, and those values varied from spec to spec. tests/browser/vite-server.mjs now sets all three:
    • The root is always the repository root.
    • optimizeDeps is owned by the helper, so a fixture can no longer set it. Its scan covers index.html and every HTML page under tests/. @jsquash/webp/encode.js, which one test imports only from page.evaluate, is in include.
    • Fixture plugins can only add server middleware (configureServer) or rewrite index HTML (transformIndexHtml). Neither changes the bundled dependencies, so the helper runs them all as one plugin with a fixed name. A fixture plugin with any other hook is rejected with an error.
  • Isolation is unchanged. Each server still owns a private cache directory, and close() still deletes it. One server cannot invalidate another server's imports. The copy uses copy-on-write where the filesystem supports it.
  • Callers. Spec code is unchanged except for removing optimizeDeps from mentions, channel-completion and composer-attachments. conversation.spec moves from a direct vite server onto the helper.
  • Relation to test(browser): serve stateless dev-server specs from the shared source server #1109. test(browser): serve stateless dev-server specs from the shared source server #1109 moves stateless specs onto one shared server per worker. This PR does not conflict with that work and does not repeat it. It covers the servers that test(browser): serve stateless dev-server specs from the shared source server #1109 cannot share (messages, channel-completion, mentions, composer-attachments and the other specs with custom middleware), and it makes the first, cold server on each worker fast too. emoji.spec still has its own direct cache here, because test(browser): serve stateless dev-server specs from the shared source server #1109 moves it to the shared server. Expected overlap: one line each in docs/browser-testing.md and tests/integration/vite-fixture.test.mjs.
  • Production builds are unaffected. Resolving a dev-server config makes Vite set NODE_ENV=development when it is unset. Setup runs in the Playwright runner, and workers inherit its environment, so a leaked value would make the built-app specs' production build report import.meta.env.DEV as true (developer settings then appear and fetch /api/relay/stats). Setup restores NODE_ENV to its original value.
  • Cost. Setup adds one dependency bundle per Playwright invocation, about 1 s on a fast laptop. The same work was done before in every fixture test.
  • Vite API. Setup calls Vite's exported optimizeDeps(), which is deprecated. If a later Vite release removes it, setup will fail with an error before any test runs. It will not fall back to a cold cache.

Evidence

  • CI run 38641809304 on 2dcc6e20d: all 18 Browser journeys shards (Chromium and WebKit, 9 each) passed, 1442 tests, none flaky or failed. No shard log has a Vite scanning dependencies, bundling dependencies or new dependencies optimized line, and none has Timeout 15000ms exceeded.
  • Same messages + channel-completion Chromium run (9 tests), with DEBUG=vite:deps:
    • main: 9 dependency scans and 9 bundles, one inside each test.
    • This branch: one bundle in setup. In each of the 9 tests, Vite logged "Hash is consistent. Skipping", so the server reused the cache and did not scan or bundle.
  • Chromium + WebKit, 2 workers: messages, channel-completion, mentions, composer-attachments, image-copy-focus, window-chrome, custom-emoji-authoring, mocked-native-ipc and avatar-shapes (shared sourceOrigin server). 58 passed. That run did one bundle (in setup) and had 58 cache reuses. It had no scans and no runtime re-optimization.
  • tests/integration/vite-fixture.test.mjs adds three tests:
    • Three differently configured servers (no plugin, a middleware plugin, an HTML plugin with define) all start from the prepared cache. Both possible regressions make this test fail: removing the plugin merge, and removing the copy.
    • Configs that could change the dependency bundle are rejected.
    • Setup leaves NODE_ENV unchanged. This test fails without the restore.
  • Built-app specs (appearance, account-profile, gifs, external-links) with messages and channel-completion, Chromium: 28 passed, no /api/relay/stats page errors.

@loganj
loganj force-pushed the larry/fixture-vite-deps branch from 59d6a9b to 2dcc6e2 Compare October 14, 2026 21:38
Every Vite fixture server started with an empty optimizer cache, so it
scanned and bundled the app dependencies inside the test's 15 s navigation
budget. On a busy runner that ran past the budget (messages.spec failed all
seven tests in one CI job).

Playwright's globalSetup now scans every fixture page and bundles the
dependencies once. Each fixture server starts from its own copy of that
cache, so servers stay isolated. The helper fixes the settings in Vite's
config hash (root, plugin names, optimizeDeps) so the copy is valid for
every fixture: fixture plugins that only add middleware or rewrite index
HTML run as one stable plugin, and fixtures can no longer set optimizeDeps.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
@loganj
loganj force-pushed the larry/fixture-vite-deps branch from 2dcc6e2 to 7896b81 Compare October 15, 2026 02:24
@loganj
loganj marked this pull request as ready for review October 15, 2026 15:22
@loganj
loganj requested a review from a team as a code owner October 15, 2026 15:22

@wpfleger96 wpfleger96 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 Thanks for this. The approach is sound: pinning the root, plugin names and optimizeDeps so one prepared cache matches every fixture server, keeping a private copy per server, and failing loudly on plugins or configs that would change the bundle. One gap keeps it from fully doing what it sets out to do.

Blocking: a vite build earlier in the same worker still forces a full re-optimization inside the test.

In the CI run at the head commit 7896b81 (run 38670801149), two Browser journeys shards still re-bundle inside tests: chromium 116216855958 and webkit 116216856307. Each logs Re-optimizing dependencies because vite config has changed five times, followed by scanning dependencies… / bundling dependencies…, for the terminal-motion and terminal-renderer servers. That is the same cold scan and bundle inside the 15 s navigation budget that this PR is meant to remove. It also means the description's claim that no shard log has a scanning or bundling line doesn't hold at this head.

Cause: Vite's dependency config hash includes process.env.NODE_ENV || config.mode (getConfigHash in Vite 8.3.0). Setup bundles the seed with NODE_ENV=development. In both shards, the worker that ran the terminal specs had just run reminders-layout.spec or sidebar-reveal.spec, which call vite build. When NODE_ENV is unset, build sets it to production for the rest of the worker process. Every fixture server created on that worker afterward hashes differently, throws away the copied cache, and optimizes from scratch. tests/browser/build.mjs (the built-app fixture) and channel-members-focus.spec also call build, so any worker that runs a built-app spec before a fixture spec can hit this. Which shards are affected depends on test order.

This is the same kind of NODE_ENV leak the setup already guards against, just in the other direction and inside the worker. Two possible fixes:

  • Have the four in-worker build call sites restore NODE_ENV the same way prepareFixtureDependencies does, perhaps through one shared helper.
  • Or make the hash independent of the worker's leftover state in createServer, for example by pinning NODE_ENV while the server initializes its optimizer and then restoring it.

Either fix should come with a regression test: a build followed by createServer still reuses the prepared cache. That test would catch this case, which the current marker test doesn't.

Minor

  • src/bundled/workflows/workflows.playwright.config.mjs, the focused offline config for the workflow editor, has no globalSetup. Running it directly now hits the new "Fixture dependencies are not prepared" error in createServer, because workflows.journey.mjs uses the helper. Either add globalSetup to that config, or point the error message at both configs.
  • Relatedly, the scan entries cover index.html and tests/**/*.html, but src/bundled/workflows/fixture.html and session-fixture.html are also served through the helper. Their dependencies are currently reachable from index.html (CI shows no runtime discovery for the workflow journeys), but listing them explicitly would keep that from being an accident. The docs line "every HTML page under tests/" would need the same update.

@wesbillman

This comment has been minimized.

@kalvinnchau
kalvinnchau merged commit 999dbcf into main Oct 15, 2026
66 of 67 checks passed
@kalvinnchau
kalvinnchau deleted the larry/fixture-vite-deps branch October 15, 2026 15:50
@loganj

This comment has been minimized.

johnmatthewtennant added a commit that referenced this pull request Oct 15, 2026
* origin/main:
  perf(css): replace wide :has() anchors with owner-set data attributes (#1180)
  fix(goose): share an MCP server pool and harden session lifecycle (#1142)
  test(browser): hold reading focus until the unread baseline is observed (#1182)
  Refine the profile menu and open status editing immediately (#1172)
  unify session naming, workspace and lifetime settings (#1079)
  perf(messages): stop forcing a layout on every action bar position frame (#1184)
  test(browser): prepare fixture dependencies once per run (#1126)
  test: replace fragile string assertions with precise checks (#1162)
  test: keep thread fixture errors out of http responses (#1163)
  feat: upgrade tauri and native plugins (#1169)
  test: pass native menu scripts to eval as arguments (#1160)
  test: cover shared link url policy (#1165)
  test: preserve literal text across markdown comment projection (#1164)
  test: pin html escaping of clipboard link labels (#1166)
  Preserve linked text pasted from other apps (#1069)
  fix(messages): keep the reader's message in place when a thread reply and layout change land together (#1130)

Signed-off-by: John Tennant <jtennant@squareup.com>

# Conflicts:
#	src/bundled/channels/ChannelBody.tsx
#	src/bundled/channels/ConversationTab.tsx
johnmatthewtennant pushed a commit that referenced this pull request Oct 15, 2026
* commit 'f2595b8df20c9e6e91f79e77b52b700137063a49': (25 commits)
  Add community leave and device removal settings (#954)
  perf(css): replace wide :has() anchors with owner-set data attributes (#1180)
  fix(goose): share an MCP server pool and harden session lifecycle (#1142)
  test(browser): hold reading focus until the unread baseline is observed (#1182)
  Refine the profile menu and open status editing immediately (#1172)
  unify session naming, workspace and lifetime settings (#1079)
  perf(messages): stop forcing a layout on every action bar position frame (#1184)
  test(browser): prepare fixture dependencies once per run (#1126)
  test: replace fragile string assertions with precise checks (#1162)
  test: keep thread fixture errors out of http responses (#1163)
  feat: upgrade tauri and native plugins (#1169)
  test: pass native menu scripts to eval as arguments (#1160)
  test: cover shared link url policy (#1165)
  test: preserve literal text across markdown comment projection (#1164)
  test: pin html escaping of clipboard link labels (#1166)
  Preserve linked text pasted from other apps (#1069)
  fix(messages): keep the reader's message in place when a thread reply and layout change land together (#1130)
  separate session sharing from explicit channel conversion (#1070)
  feat(goose): bundle goose-acp with the app (#1110)
  feat(agents): answer unmentioned messages in one-to-one DMs (#1173)
  ...

Signed-off-by: Sol <49aa1f65411fd096d2e2ec144f1e7aa36fdc76d1b907cfdf7be000c66f9d3b8e@buzz.block.builderlab.xyz>
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.

4 participants