Skip to content

Conversation

@onlywei
Copy link
Collaborator

@onlywei onlywei commented Jan 3, 2026

The integration tests were flaky due to improper async cleanup:

  1. The onClose hook in development.js called devServer.close() without awaiting it, causing the Vite dev server to continue cleanup in the background while the next test started. This created race conditions on CI runners with limited resources.

  2. test-factories.mjs had a 3-second setTimeout as a workaround, but this was unreliable on slower CI runners and added unnecessary delay.

Changes:

  • Make onClose hook async and await devServer.close() for proper cleanup
  • Remove arbitrary setTimeout workaround from makeIndexTest

This ensures Vite dev servers fully shut down before tests complete, eliminating resource conflicts between consecutive test runs.

Checklist

The integration tests were flaky due to improper async cleanup:

1. The onClose hook in development.js called devServer.close() without
   awaiting it, causing the Vite dev server to continue cleanup in the
   background while the next test started. This created race conditions
   on CI runners with limited resources.

2. test-factories.mjs had a 3-second setTimeout as a workaround, but
   this was unreliable on slower CI runners and added unnecessary delay.

Changes:
- Make onClose hook async and await devServer.close() for proper cleanup
- Remove arbitrary setTimeout workaround from makeIndexTest

This ensures Vite dev servers fully shut down before tests complete,
eliminating resource conflicts between consecutive test runs.
@changeset-bot
Copy link

changeset-bot bot commented Jan 3, 2026

⚠️ No Changeset found

Latest commit: 6ed2c76

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

claude added 6 commits January 3, 2026 20:13
The module runners created by createServerModuleRunner() also need
explicit cleanup. Each runner maintains HMR listeners and module caches
that must be closed to prevent resource leaks between test runs.

According to Vite's ModuleRunner API documentation, the close() method:
- Clears all caches including HMR listeners
- Removes all HMR listeners
- Resets sourcemap support

This ensures complete cleanup of all Vite dev resources before
proceeding to the next test.
The loadEntries() function is called on every request in development mode
to enable HMR. However, it was resetting this.runners = {} without closing
the old runners first, causing them to accumulate in memory with:
- Active file watchers
- HMR connection listeners
- Module caches
- Sourcemap support state

This resource leak caused flaky tests as runners accumulated across
requests, especially when running many tests sequentially on CI.

Now properly closes old runners before creating new ones, ensuring
clean resource management on every request.
Previous approach closed ALL runners on every loadEntries() call,
which happens on every request for HMR. This caused race conditions:

1. Request 1 arrives -> closes all runners, creates new ones
2. Request 2 arrives (before cleanup completes) -> closes all runners again
3. server.close() -> onClose tries to close already-closing runners

This overlap caused unpredictable behavior on CI runners.

New approach: Only close a specific runner when we're replacing it
for the same environment. This prevents:
- Race conditions between concurrent requests
- Conflicts between loadEntries and onClose cleanup
- Attempting to close already-closed runners

The onClose hook still serves as final cleanup for any remaining runners.
loadEntries() is called:
1. At startup (initial setup)
2. On every HTTP request (for HMR in development)

This created a race condition where multiple concurrent requests could
trigger overlapping loadEntries() calls, causing:
- Runners being closed while still being created
- onClose hook conflicting with in-progress loadEntries()
- Unpredictable state when server.close() is called during a request

The mutex ensures only one loadEntries() execution at a time:
- Subsequent requests skip if already loading
- Prevents concurrent runner close/create operations
- Eliminates conflicts between onRequest and onClose hooks

This is the final piece for reliable test execution on CI.
The loadBundle() function had a bug where if neither .js nor .mjs
bundle files existed, it would still attempt to import the last
checked path, causing a confusing ERR_MODULE_NOT_FOUND error.

This could happen due to:
- Filesystem race conditions on CI (build completes but files not
  fully flushed to disk before next test runs)
- Build failures that weren't caught
- Incorrect build output paths

Now explicitly checks if a bundle file was found and throws a clear
error message listing all attempted paths if not, making it easier
to diagnose the root cause.

This addresses intermittent test failures where the production test
runs before build outputs are fully available on disk.
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.

3 participants