Skip to content

Comments

chore: fix more flakey tests#7961

Merged
eduardoboucas merged 7 commits intomainfrom
chore/ci-fixes
Feb 23, 2026
Merged

chore: fix more flakey tests#7961
eduardoboucas merged 7 commits intomainfrom
chore/ci-fixes

Conversation

@eduardoboucas
Copy link
Member

Follow-up to #7958.

@eduardoboucas eduardoboucas requested a review from a team as a code owner February 23, 2026 16:04
@github-actions
Copy link

github-actions bot commented Feb 23, 2026

📊 Benchmark results

Comparing with 2453221

  • Dependency count: 1,080 (no change)
  • Package size: 347 MB (no change)
  • Number of ts-expect-error directives: 362 (no change)

@eduardoboucas eduardoboucas changed the title Chore/ci fixes chore: fix more flakey tests Feb 23, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Workflows now fail when site-creation returns an error, preventing silent success.
  • Tests

    • Stabilized end-to-end runs by increasing job timeouts.
    • Reworked test infrastructure to use a shared package registry, centralized publish workflow, and improved global setup/cleanup.
    • Replaced hard-coded ports with dynamic allocation for more reliable network tests.

Walkthrough

The pull request updates CI workflows and test suites: GitHub Actions e2e job timeouts are increased and an integration test script now fails the job on non-201 HTTP responses. The e2e install tests are refactored to use a single shared Verdaccio registry with centralized publish, setup, and teardown logic, plus improved error handling. Two integration tests replace hardcoded ports with dynamic allocation via get-port.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main objective of fixing flaky tests across the codebase, matching changes to timeouts, dynamic port allocation, and test stability improvements.
Description check ✅ Passed The description references a related follow-up PR, which provides context for the changes, though it is minimal in detail.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/ci-fixes

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e/install.e2e.ts`:
- Around line 41-154: Prettier formatting failed for e2e/install.e2e.ts; run
Prettier and commit the formatted file. Open the file that contains the
beforeAll block (which defines shared, calls runServer, creates
publishWorkspace, uses shouldCopyCLIFile, and runs execa/npm publish), run your
project's Prettier configuration (e.g. npx prettier --write e2e/install.e2e.ts)
or your repo's formatting script, verify the file is reformatted, then stage and
commit the updated e2e/install.e2e.ts.
- Around line 52-154: The beforeAll hook in e2e/install.e2e.ts needs an explicit
timeout because Vitest's default 10s is too short for the Verdaccio setup and
npm publish steps; update the existing beforeAll(async () => { ... }) invocation
to pass a longer timeout as the second argument (for example 120000 ms or
another CI-appropriate value) so the setup (functions like runServer, execa
publishes, fs operations) can complete without timing out.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2453221 and d489a03.

📒 Files selected for processing (5)
  • .github/workflows/e2e-tests.yml
  • .github/workflows/integration-tests.yml
  • e2e/install.e2e.ts
  • tests/integration/commands/dev/dev.test.ts
  • tests/integration/commands/dev/redirects.test.ts

Copy link
Member

@serhalp serhalp left a comment

Choose a reason for hiding this comment

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

🙌🏼

'"dist" directory does not exist or is not a directory. The project must be built before running E2E tests.',
{ cause: err },
)
// Shared registry state: one verdaccio server + one publish for all tests.
Copy link
Member Author

Choose a reason for hiding this comment

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

Main change here is that we publish once and reuse that registry for all the tests, as opposed to doing it once per test.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
e2e/install.e2e.ts (3)

164-176: Consider adding a timeout to afterAll hook.

If the server fails to close cleanly, events.once(shared.server, 'close') could hang indefinitely. While this is non-CI only, it could affect local development.

♻️ Suggested fix with timeout
-  afterAll(async () => {
+  afterAll(async () => {
     if (shared.server) {
       await Promise.all([
         events.once(shared.server, 'close'),
         shared.server.close(),
         // eslint-disable-next-line `@typescript-eslint/no-confusing-void-expression`
         shared.server.closeAllConnections(),
       ])
     }
     if (shared.verdaccioStorageDir) {
       await fs.rm(shared.verdaccioStorageDir, { force: true, recursive: true, maxRetries: 3, retryDelay: 1000 })
     }
-  })
+  }, 30_000)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/install.e2e.ts` around lines 164 - 176, The afterAll teardown can hang
waiting on events.once(shared.server, 'close'); modify the afterAll to race the
close sequence (events.once(shared.server, 'close'), shared.server.close(),
shared.server.closeAllConnections()) against a timeout promise (e.g.,
Promise.race with a short timeout like 5–10s) and ensure the timeout path logs a
warning and proceeds to cleanup (still remove shared.verdaccioStorageDir).
Update the block that references afterAll, shared.server, events.once(...,
'close'), shared.server.close(), and shared.server.closeAllConnections() to use
the timeout race and handle the timeout case gracefully so the test suite
doesn't hang.

108-116: Simplify the server startup pattern.

The Promise.all wrapping Promise.race and server.listen() is slightly convoluted. Since server.listen() triggers the 'listening' event asynchronously, you can simplify by calling listen() first, then awaiting the race.

♻️ Suggested simplification
-  await Promise.all([
-    Promise.race([
-      events.once(server, 'listening'),
-      events.once(server, 'error').then(() => {
-        throw new Error('Verdaccio server failed to start')
-      }),
-    ]),
-    server.listen(),
-  ])
+  server.listen()
+  await Promise.race([
+    events.once(server, 'listening'),
+    events.once(server, 'error').then(([err]) => {
+      throw new Error(`Verdaccio server failed to start: ${err instanceof Error ? err.message : String(err)}`)
+    }),
+  ])

Additionally, the error handler discards the actual error from the 'error' event. Including it would help diagnose startup failures.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/install.e2e.ts` around lines 108 - 116, The startup logic for the test
server is overcomplicated and drops the real error; change the pattern to call
server.listen() first and then await a Promise.race between events.once(server,
'listening') and events.once(server, 'error') so we wait for whichever happens
next, and when the 'error' event wins capture and include the actual error
object in the thrown/propagated error message. Update the code around
server.listen(), events.once(server, 'listening'), and events.once(server,
'error') to reflect this simpler ordering and include the error value instead of
discarding it.

329-329: Unused temporary directory.

The cwd directory is created at line 329 but never used as a working directory for any execa calls in this test (lines 336, 347, 370 all omit cwd). The directory is only referenced in the cleanup at line 384, making the creation and cleanup overhead unnecessary.

♻️ Suggested fix: remove unused temp directory
   it.skipIf(skipOnWindows)('runs commands without errors', async () => {
-    const cwd = await fs.mkdtemp(path.join(os.tmpdir(), tempdirPrefix))
     const [cmd, args] = config.run
     const env = {
       npm_config_registry: shared.registryURL,
     }
 
     try {
       // ... test body unchanged ...
-    } finally {
-      if (!process.env.CI) {
-        await fs.rm(cwd, { force: true, recursive: true, maxRetries: 3, retryDelay: 1000 })
-      }
-    }
+    } finally {}
   })

Or remove the try/finally entirely if there's no cleanup needed.

Also applies to: 382-385

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/install.e2e.ts` at line 329, The temp directory created by const cwd =
await fs.mkdtemp(path.join(os.tmpdir(), tempdirPrefix)) is never used for any
execa calls (the execa invocations in this test omit a cwd option) and is only
cleaned up, so either remove the fs.mkdtemp call and the associated
cleanup/try-finally, or actually pass cwd into the execa calls (e.g., include {
cwd } in their options) so the temp dir is used as the working directory; update
references in e2e/install.e2e.ts accordingly and remove any now-unnecessary
cleanup if you choose to delete the temp-dir creation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e/install.e2e.ts`:
- Around line 379-381: The assertion message for the unlink test is misleading;
change the failure message passed to expect on the unlinkOutput variable so it
accurately describes the failure (e.g., "Unlink output should include the
suggested link command" or "Unlink command missing command context") instead of
"includes command context". Update the expect call that uses unlinkOutput and
cmd in e2e/install.e2e.ts to use the clearer message so test failures indicate
that the unlink output didn’t contain the expected "Run ${cmd} netlify link"
suggestion.
- Around line 296-298: The assertion message is misleading because it reads as
if the output includes the command context even though the message is shown only
on failure; update the failure message used with expect(unlinkOutput,
`...`).toContain(...) to indicate the negative case (e.g., "Unlink command
missing command context" or "expected unlink output to include 'Run netlify
link' but it did not") so it accurately describes the failure when the variable
unlinkOutput does not contain the expected string.

---

Nitpick comments:
In `@e2e/install.e2e.ts`:
- Around line 164-176: The afterAll teardown can hang waiting on
events.once(shared.server, 'close'); modify the afterAll to race the close
sequence (events.once(shared.server, 'close'), shared.server.close(),
shared.server.closeAllConnections()) against a timeout promise (e.g.,
Promise.race with a short timeout like 5–10s) and ensure the timeout path logs a
warning and proceeds to cleanup (still remove shared.verdaccioStorageDir).
Update the block that references afterAll, shared.server, events.once(...,
'close'), shared.server.close(), and shared.server.closeAllConnections() to use
the timeout race and handle the timeout case gracefully so the test suite
doesn't hang.
- Around line 108-116: The startup logic for the test server is overcomplicated
and drops the real error; change the pattern to call server.listen() first and
then await a Promise.race between events.once(server, 'listening') and
events.once(server, 'error') so we wait for whichever happens next, and when the
'error' event wins capture and include the actual error object in the
thrown/propagated error message. Update the code around server.listen(),
events.once(server, 'listening'), and events.once(server, 'error') to reflect
this simpler ordering and include the error value instead of discarding it.
- Line 329: The temp directory created by const cwd = await
fs.mkdtemp(path.join(os.tmpdir(), tempdirPrefix)) is never used for any execa
calls (the execa invocations in this test omit a cwd option) and is only cleaned
up, so either remove the fs.mkdtemp call and the associated cleanup/try-finally,
or actually pass cwd into the execa calls (e.g., include { cwd } in their
options) so the temp dir is used as the working directory; update references in
e2e/install.e2e.ts accordingly and remove any now-unnecessary cleanup if you
choose to delete the temp-dir creation.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d489a03 and 06f3bf0.

📒 Files selected for processing (1)
  • e2e/install.e2e.ts

Comment on lines +296 to 298
expect(unlinkOutput, `Unlink command includes command context':\n\n${unlinkOutput}`).toContain(
`Run netlify link to link it`,
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Misleading assertion message.

The assertion message says "Unlink command includes command context" but the expect().toContain() call is checking that the output does contain the expected text. The message appears if the assertion fails, so it should indicate what's wrong (i.e., the output does not include the expected text).

✏️ Suggested fix
-      expect(unlinkOutput, `Unlink command includes command context':\n\n${unlinkOutput}`).toContain(
+      expect(unlinkOutput, `Unlink command does not include expected context:\n\n${unlinkOutput}`).toContain(
         `Run netlify link to link it`,
       )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
expect(unlinkOutput, `Unlink command includes command context':\n\n${unlinkOutput}`).toContain(
`Run netlify link to link it`,
)
expect(unlinkOutput, `Unlink command does not include expected context:\n\n${unlinkOutput}`).toContain(
`Run netlify link to link it`,
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/install.e2e.ts` around lines 296 - 298, The assertion message is
misleading because it reads as if the output includes the command context even
though the message is shown only on failure; update the failure message used
with expect(unlinkOutput, `...`).toContain(...) to indicate the negative case
(e.g., "Unlink command missing command context" or "expected unlink output to
include 'Run netlify link' but it did not") so it accurately describes the
failure when the variable unlinkOutput does not contain the expected string.

Comment on lines +379 to 381
expect(unlinkOutput, `Unlink command includes command context':\n\n${unlinkOutput}`).toContain(
`Run ${cmd} netlify link to link it`,
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Misleading assertion message (same issue as install tests).

The assertion message says "includes command context" but should indicate what's wrong when the assertion fails.

✏️ Suggested fix
-      expect(unlinkOutput, `Unlink command includes command context':\n\n${unlinkOutput}`).toContain(
+      expect(unlinkOutput, `Unlink command does not include expected context:\n\n${unlinkOutput}`).toContain(
         `Run ${cmd} netlify link to link it`,
       )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
expect(unlinkOutput, `Unlink command includes command context':\n\n${unlinkOutput}`).toContain(
`Run ${cmd} netlify link to link it`,
)
expect(unlinkOutput, `Unlink command does not include expected context:\n\n${unlinkOutput}`).toContain(
`Run ${cmd} netlify link to link it`,
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/install.e2e.ts` around lines 379 - 381, The assertion message for the
unlink test is misleading; change the failure message passed to expect on the
unlinkOutput variable so it accurately describes the failure (e.g., "Unlink
output should include the suggested link command" or "Unlink command missing
command context") instead of "includes command context". Update the expect call
that uses unlinkOutput and cmd in e2e/install.e2e.ts to use the clearer message
so test failures indicate that the unlink output didn’t contain the expected
"Run ${cmd} netlify link" suggestion.

@eduardoboucas eduardoboucas merged commit 8712bd3 into main Feb 23, 2026
73 checks passed
@eduardoboucas eduardoboucas deleted the chore/ci-fixes branch February 23, 2026 22:50
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