Skip to content

Commit dbe1fa7

Browse files
shadcnclaude
andauthored
fix(tests): fix e2e sleep (#10061)
* fix(tests): wait for registry readiness in global setup instead of per-test sleep The first e2e test was flaky on CI because `start-server-and-test` only checks that the root URL (http://localhost:4000) responds before running tests, not the /r registry endpoint. The existing 2-second hardcoded sleep in the first test was unreliable on slower CI runners. Move the readiness check into the vitest globalSetup so all tests wait for the registry /r endpoint to actually be reachable before any test starts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(tests): fix race condition in global setup - poll correct URL and CLI binary Two issues caused the previous fix to fail: 1. Was polling `http://localhost:4000/r` which is a directory → always 404. Now polls `{REGISTRY_URL}/index.json`, a real static file that returns 200. 2. The v4 dev script (`pnpm --filter=shadcn build && pnpm icons:dev & next dev`) runs the shadcn CLI build in the background while next dev starts immediately. On fast CI runs start-server-and-test can detect the server as ready before the CLI binary (packages/shadcn/dist/index.js) has been built, causing the first test to fail when it tries to invoke the CLI. Now explicitly waits for the binary to exist before any test runs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(tests): warm up /init route in global setup to prevent first-test timeout The CLI's first request during `shadcn init` hits the dynamic Next.js /init route. On a cold dev server this route takes ~1.8s to compile. Combined with the rest of what init does (pnpm install, file writes), this pushes the first test over the 30s CLI timeout on CI. Subsequent tests pass because the route is already warm. Polling /init in global setup ensures the route is compiled before any test runs, making the first test's CLI invocation as fast as all subsequent ones. Also replaced the /r/index.json poll (a static file that responds immediately and doesn't reflect real route readiness) with the actual /init route poll, which also naturally verifies the registry server is up. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(tests): warm up 404 route and increase default CLI timeout Two more issues found in CI logs: 1. The CLI requests font files that don't exist (e.g. /r/styles/new-york-v4/ font-geist.json), causing Next.js to compile /_not-found/page on the first 404 response. That compilation takes ~4-5s on a cold dev server and is another hidden cost on the first test. Now triggering a 404 in global setup so the not-found page is compiled before any test runs. 2. The default CLI timeout of 30s is too tight for CI. Even with the /init and 404 routes pre-warmed, pnpm install inside the fixture takes ~25s, leaving only ~5s of headroom. Increasing the default from 30s to 60s gives a comfortable buffer without masking real hangs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 74c4c75 commit dbe1fa7

File tree

3 files changed

+51
-4
lines changed

3 files changed

+51
-4
lines changed

packages/tests/src/tests/init.test.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ import { createRegistryServer } from "../utils/registry"
1212

1313
describe("shadcn init - next-app", () => {
1414
it("should init with default configuration", async () => {
15-
// Sleep for 1 second to avoid race condition with the registry server.
16-
await new Promise((resolve) => setTimeout(resolve, 2000))
17-
1815
const fixturePath = await createFixtureTestDirectory("next-app")
1916
await npxShadcn(fixturePath, ["init", "--defaults"])
2017

packages/tests/src/utils/helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export async function runCommand(
4747
},
4848
input: options?.input,
4949
reject: false,
50-
timeout: options?.timeout ?? 30000,
50+
timeout: options?.timeout ?? 60000,
5151
})
5252

5353
const result = await childProcess

packages/tests/src/utils/setup.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,59 @@ import { rimraf } from "rimraf"
44

55
export const TEMP_DIR = path.join(__dirname, "../../temp")
66

7+
const SHADCN_CLI_PATH = path.join(__dirname, "../../../shadcn/dist/index.js")
8+
9+
async function waitForCondition(
10+
label: string,
11+
check: () => Promise<boolean>,
12+
timeoutMs = 60000
13+
) {
14+
const deadline = Date.now() + timeoutMs
15+
while (Date.now() < deadline) {
16+
if (await check()) return
17+
await new Promise((resolve) => setTimeout(resolve, 500))
18+
}
19+
throw new Error(`Timed out waiting for: ${label} (${timeoutMs}ms)`)
20+
}
21+
722
export default async function setup() {
823
await fs.ensureDir(TEMP_DIR)
924

25+
// The v4 dev script runs `pnpm --filter=shadcn build` in the background
26+
// while `next dev` starts immediately. On fast CI runs the server can be
27+
// ready before the CLI binary is built, so we wait for it explicitly.
28+
await waitForCondition("shadcn CLI binary", () =>
29+
fs.pathExists(SHADCN_CLI_PATH)
30+
)
31+
32+
// The CLI's first request goes to the dynamic /init route. On a cold Next.js
33+
// dev server, this route needs to be compiled on first access (~1.8s). That
34+
// compilation time, on top of everything else init does, pushes the first
35+
// test over the 30s CLI timeout. Warming up the route here ensures it is
36+
// already compiled before any test starts.
37+
const registryUrl = process.env.REGISTRY_URL || "http://localhost:4000/r"
38+
const shadcnUrl = registryUrl.replace(/\/r\/?$/, "")
39+
const initWarmupUrl = `${shadcnUrl}/init?base=base&style=nova&baseColor=neutral&theme=neutral&iconLibrary=lucide&font=geist&rtl=false&menuAccent=subtle&menuColor=default&radius=default&template=next`
40+
41+
await waitForCondition("init route warm-up", async () => {
42+
try {
43+
const res = await fetch(initWarmupUrl)
44+
return res.ok
45+
} catch {
46+
return false
47+
}
48+
})
49+
50+
// The CLI fetches registry paths that may not exist (e.g. font files),
51+
// causing Next.js to compile the /_not-found/page route on first 404.
52+
// That compilation takes ~4-5s and contributes to the first test timing
53+
// out. Trigger one 404 here so the not-found page is pre-compiled.
54+
try {
55+
await fetch(`${shadcnUrl}/r/styles/new-york-v4/font-geist.json`)
56+
} catch {
57+
// Best effort — don't block setup if this fails.
58+
}
59+
1060
return async () => {
1161
try {
1262
await rimraf(TEMP_DIR)

0 commit comments

Comments
 (0)