Skip to content

Commit 4a2571d

Browse files
mishushakovclaude
andauthored
test(js-sdk): wait for workers.dev propagation in the CF deploy suite (#1592)
## Problem The `cloudflare-deploy` CI job intermittently fails with `non-JSON response (404): <!DOCTYPE html>...` ([example run](https://github.com/e2b-dev/E2B/actions/runs/30009788154/job/89214641280)). The truncated HTML boilerplate is easy to mistake for a Cloudflare captcha/challenge page, but it's the standard Cloudflare **404** page: each `wrangler deploy --temporary` lands on a brand-new account subdomain (`e2b-js-sdk-smoke.<random>.workers.dev`), and Cloudflare serves "nothing is here yet" until the route propagates to the edge. The in-test retry window (initial attempt + 10 retries × 3s ≈ 35s) wasn't always enough. ## Fix - `setup.mts`: after the deploy, poll the worker URL until the worker itself answers (405 to GET — the worker is POST-only), with a 240s deadline. Tests only start once the route is live. Only the propagation 404 and thrown fetch errors (transient DNS/connect) keep the poll waiting — any other status (403 challenge, 500 from a broken worker, ...) is a real failure and fails the setup immediately, with the error page's `<title>` in the message. - `run.test.ts`: retry only on the propagation 404 / `fetch failed`, so other Cloudflare error pages propagate on the first attempt; include the page `<title>` in the `non-JSON response` error, since the truncated body is boilerplate shared by every Cloudflare error page. Test-only change, no changeset. ## Verification Ran `pnpm test:cf:deploy` against real Cloudflare (both revisions): ``` Deployed: https://e2b-js-sdk-smoke.quick-bike.workers.dev Worker route not live yet (404), waiting... Worker route not live yet (404), waiting... Worker is live. Test Files 1 passed (1) Tests 1 passed (1) ``` The fresh subdomain served 404 for ~6s post-deploy — exactly the failure mode from CI — then the suite passed on the first test attempt. `pnpm run format`, `lint`, and `typecheck` pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 86934c7 commit 4a2571d

2 files changed

Lines changed: 55 additions & 9 deletions

File tree

packages/js-sdk/tests/runtimes/cloudflare-deploy/run.test.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ import { template } from '../../template'
44

55
test(
66
'sandbox lifecycle inside a deployed Cloudflare Worker',
7-
// Retry the whole test while the fresh workers.dev subdomain propagates:
8-
// Cloudflare serves an HTML error page until the route is live ("non-JSON
9-
// response"), and fetch itself can fail at the DNS/connect level first
10-
// (undici throws "fetch failed").
7+
// setup.mts waits for the fresh workers.dev subdomain to propagate before
8+
// tests run; these retries only absorb transient blips — a stray
9+
// propagation 404 or an undici DNS/connect error ("fetch failed"). Any
10+
// other Cloudflare error page is a real failure and propagates.
1111
{
1212
retry: {
1313
count: 10,
1414
delay: 3_000,
15-
condition: /non-JSON response|fetch failed/,
15+
condition: /non-JSON response \(404|fetch failed/,
1616
},
1717
},
1818
async () => {
@@ -35,10 +35,14 @@ test(
3535
try {
3636
body = JSON.parse(text)
3737
} catch {
38-
// Cloudflare edge error page (e.g. DNS still propagating) — fail and
39-
// let the retry kick in.
38+
// Cloudflare edge error page — fail and let the retry kick in. The
39+
// page's <title> says which error it is (the truncated body is just
40+
// boilerplate shared by every Cloudflare error page).
41+
const title = text.match(/<title>(.*?)<\/title>/is)?.[1]?.trim()
4042
throw new Error(
41-
`non-JSON response (${response.status}): ${text.slice(0, 200)}`
43+
`non-JSON response (${response.status}${
44+
title ? `, "${title}"` : ''
45+
}): ${text.slice(0, 200)}`
4246
)
4347
}
4448

packages/js-sdk/tests/runtimes/cloudflare-deploy/setup.mts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,47 @@ function wrangler(args: string[]) {
3030
return { ...result, output: `${result.stdout ?? ''}\n${result.stderr ?? ''}` }
3131
}
3232

33-
export default function setup(project: TestProject) {
33+
// A temporary deploy lands on a brand-new account subdomain
34+
// (<worker>.<random-name>.workers.dev), and Cloudflare serves its HTML 404
35+
// page ("nothing is here yet") until the route propagates to the edge —
36+
// usually seconds, occasionally minutes. Wait for the worker itself to
37+
// answer (405 to GET, worker.mjs is POST-only) so the test's own retries
38+
// only have to absorb transient network failures.
39+
async function waitUntilLive(workerUrl: string, timeoutMs = 240_000) {
40+
const deadline = Date.now() + timeoutMs
41+
for (;;) {
42+
// Thrown fetch errors (DNS/connect for the fresh subdomain) are also
43+
// transient — keep waiting on those, but fail fast on any status other
44+
// than the propagation 404 so real errors surface immediately.
45+
const response = await fetch(workerUrl).catch((err) => {
46+
console.log(`Worker not reachable yet (${err}), waiting...`)
47+
return undefined
48+
})
49+
if (response) {
50+
if (response.status === 405) {
51+
return
52+
}
53+
if (response.status !== 404) {
54+
const text = await response.text()
55+
const title = text.match(/<title>(.*?)<\/title>/is)?.[1]?.trim()
56+
throw new Error(
57+
`Worker at ${workerUrl} answered ${response.status}${
58+
title ? ` ("${title}")` : ''
59+
} — not the propagation 404: ${text.slice(0, 200)}`
60+
)
61+
}
62+
console.log('Worker route not live yet (404), waiting...')
63+
}
64+
if (Date.now() >= deadline) {
65+
throw new Error(
66+
`Worker at ${workerUrl} did not come up within ${timeoutMs / 1000}s of deploy`
67+
)
68+
}
69+
await new Promise((resolve) => setTimeout(resolve, 3_000))
70+
}
71+
}
72+
73+
export default async function setup(project: TestProject) {
3474
rmSync(outputFile, { force: true })
3575

3676
console.log('Deploying worker to a temporary Cloudflare preview account...')
@@ -58,6 +98,8 @@ export default function setup(project: TestProject) {
5898
}
5999

60100
console.log(`Deployed: ${workerUrl}`)
101+
await waitUntilLive(workerUrl)
102+
console.log('Worker is live.')
61103
project.provide('cfWorkerUrl', workerUrl)
62104

63105
return function teardown() {

0 commit comments

Comments
 (0)