Skip to content

Commit b190856

Browse files
clean up the vite e2e remote mode tests to avoid flakes (#9902)
1 parent 25dbe54 commit b190856

File tree

8 files changed

+150
-100
lines changed

8 files changed

+150
-100
lines changed

packages/vite-plugin-cloudflare/e2e/fixtures/remote-bindings/auxiliary-worker/wrangler.jsonc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"services": [
77
{
88
"binding": "REMOTE_WORKER",
9-
"service": "tmp-e2e-vite-plugin-mixed-mode-remote-worker-alt",
9+
"service": "<<REMOTE_WORKER_PLACEHOLDER_ALT>>",
1010
"experimental_remote": true,
1111
},
1212
],

packages/vite-plugin-cloudflare/e2e/fixtures/remote-bindings/entry-worker/wrangler.jsonc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
},
1212
{
1313
"binding": "REMOTE_WORKER",
14-
"service": "tmp-e2e-vite-plugin-mixed-mode-remote-worker",
14+
"service": "<<REMOTE_WORKER_PLACEHOLDER>>",
1515
"experimental_remote": true,
1616
},
1717
],
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default {
2+
fetch() {
3+
return new Response("Hello from an alternative remote worker");
4+
},
5+
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"compatibility_date": "2025-04-03",
3+
"name": "<<REMOTE_WORKER_PLACEHOLDER_ALT>>",
4+
"main": "./index.js",
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default {
2+
fetch() {
3+
return new Response("Hello from a remote worker");
4+
},
5+
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"compatibility_date": "2025-04-03",
3+
"name": "<<REMOTE_WORKER_PLACEHOLDER>>",
4+
"main": "./index.js",
5+
}

packages/vite-plugin-cloudflare/e2e/helpers.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ const strictPeerDeps = {
3737
};
3838

3939
/** Seed a test project from a fixture. */
40-
export function seed(fixture: string, pm: "pnpm" | "yarn" | "npm") {
40+
export function seed(
41+
fixture: string,
42+
pm: "pnpm" | "yarn" | "npm",
43+
replacements: Record<string, string> = {}
44+
) {
4145
const root = inject("root");
4246
const projectPath = path.resolve(root, fixture, pm);
4347

@@ -48,6 +52,8 @@ export function seed(fixture: string, pm: "pnpm" | "yarn" | "npm") {
4852
});
4953
debuglog("Fixture copied to " + projectPath);
5054
await updateVitePluginVersion(projectPath);
55+
debuglog("Fixing up replacements in seeded files");
56+
await fixupReplacements(projectPath, replacements);
5157
debuglog("Updated vite-plugin version in package.json");
5258
runCommand(`${pm} install ${strictPeerDeps[pm]}`, projectPath, {
5359
attempts: 2,
@@ -181,7 +187,7 @@ async function updateVitePluginVersion(projectPath: string) {
181187
export function runCommand(
182188
command: string,
183189
cwd: string,
184-
{ attempts = 1 } = {}
190+
{ attempts = 1, canFail = false } = {}
185191
) {
186192
while (attempts > 0) {
187193
debuglog("Running command:", command);
@@ -196,6 +202,8 @@ export function runCommand(
196202
attempts--;
197203
if (attempts > 0) {
198204
debuglog(`Retrying failed command (${e})`);
205+
} else if (canFail) {
206+
debuglog(`Command failed but canFail is true, not throwing: ${e}`);
199207
} else {
200208
throw e;
201209
}
@@ -231,3 +239,24 @@ export async function waitForReady(proc: Process) {
231239
);
232240
return match[1];
233241
}
242+
243+
async function fixupReplacements(
244+
projectPath: string,
245+
replacements: Record<string, string>
246+
) {
247+
const files = await fs.readdir(projectPath, { withFileTypes: true });
248+
for (const file of files) {
249+
if (file.isDirectory()) {
250+
await fixupReplacements(path.join(projectPath, file.name), replacements);
251+
} else if (file.isFile()) {
252+
let content = await fs.readFile(
253+
path.join(projectPath, file.name),
254+
"utf8"
255+
);
256+
for (const [key, value] of Object.entries(replacements)) {
257+
content = content.replaceAll(key, value);
258+
}
259+
await fs.writeFile(path.join(projectPath, file.name), content, "utf8");
260+
}
261+
}
262+
}
Lines changed: 97 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,127 +1,128 @@
11
import { execSync } from "node:child_process";
2+
import { randomUUID } from "node:crypto";
23
import fs from "node:fs";
34
import { readFile, writeFile } from "node:fs/promises";
45
import os from "node:os";
56
import { setTimeout } from "node:timers/promises";
67
import { afterAll, beforeAll, describe, test, vi } from "vitest";
7-
import { fetchJson, runLongLived, seed, waitForReady } from "./helpers.js";
8+
import {
9+
fetchJson,
10+
runCommand,
11+
runLongLived,
12+
seed,
13+
waitForReady,
14+
} from "./helpers.js";
815

916
const isWindows = os.platform() === "win32";
1017
const commands = ["dev", "buildAndPreview"] as const;
1118

12-
// These tests focus on remote bindings which require an authed connection to the CF API
13-
// They are skipped if you have not provided the necessary account id and api token.
14-
describe
15-
.skipIf(
16-
isWindows ||
17-
!process.env.CLOUDFLARE_ACCOUNT_ID ||
18-
!process.env.CLOUDFLARE_API_TOKEN
19-
)
20-
// Note: the reload test applies changes to the fixture files, so we do want the
21-
// tests to run sequentially in order to avoid race conditions
22-
.sequential("remote bindings tests", () => {
23-
const remoteWorkerName = "tmp-e2e-vite-plugin-mixed-mode-remote-worker";
24-
const alternativeRemoteWorkerName =
25-
"tmp-e2e-vite-plugin-mixed-mode-remote-worker-alt";
19+
if (
20+
isWindows ||
21+
!process.env.CLOUDFLARE_ACCOUNT_ID ||
22+
!process.env.CLOUDFLARE_API_TOKEN
23+
) {
24+
describe.skip(
25+
"Skipping remote bindings tests on Windows or without account credentials."
26+
);
27+
} else {
28+
describe
29+
// Note: the reload test applies changes to the fixture files, so we do want the
30+
// tests to run sequentially in order to avoid race conditions
31+
.sequential("remote bindings tests", () => {
32+
const replacements = {
33+
"<<REMOTE_WORKER_PLACEHOLDER>>": `tmp-e2e-vite-remote-${randomUUID().split("-")[0]}`,
34+
"<<REMOTE_WORKER_PLACEHOLDER_ALT>>": `tmp-e2e-vite-remote-alt-${randomUUID().split("-")[0]}`,
35+
};
2636

27-
const projectPath = seed("remote-bindings", "pnpm");
37+
const projectPath = seed("remote-bindings", "pnpm", replacements);
2838

29-
beforeAll(() => {
30-
const tmp = fs.mkdtempSync(`${os.tmpdir()}/vite-plugin-e2e-tmp`);
31-
[
32-
{
33-
name: remoteWorkerName,
34-
content:
35-
"export default { fetch() { return new Response('Hello from a remote worker'); } };",
36-
},
37-
{
38-
name: alternativeRemoteWorkerName,
39-
content:
40-
"export default { fetch() { return new Response('Hello from an alternative remote worker'); } };",
41-
},
42-
].forEach((worker) => {
43-
fs.writeFileSync(`${tmp}/index.js`, worker.content);
44-
execSync(
45-
`npx wrangler deploy index.js --name ${worker.name} --compatibility-date 2025-01-01`,
46-
{ cwd: tmp }
39+
beforeAll(() => {
40+
runCommand(`npx wrangler deploy`, `${projectPath}/remote-worker`);
41+
runCommand(`npx wrangler deploy`, `${projectPath}/remote-worker-alt`);
42+
}, 35_000);
43+
44+
afterAll(() => {
45+
runCommand(
46+
`npx wrangler delete --force`,
47+
`${projectPath}/remote-worker`,
48+
{ canFail: true }
49+
);
50+
runCommand(
51+
`npx wrangler delete --force`,
52+
`${projectPath}/remote-worker-alt`,
53+
{ canFail: true }
4754
);
4855
});
49-
}, 35_000);
5056

51-
afterAll(() => {
52-
[remoteWorkerName, alternativeRemoteWorkerName].forEach((worker) => {
53-
execSync(`npx wrangler delete --name ${worker}`);
57+
describe.each(commands)('with "%s" command', (command) => {
58+
test("can fetch from both local (/auxiliary) and remote workers", async ({
59+
expect,
60+
}) => {
61+
const proc = await runLongLived("pnpm", command, projectPath);
62+
const url = await waitForReady(proc);
63+
expect(await fetchJson(url)).toEqual({
64+
localWorkerResponse: {
65+
remoteWorkerResponse: "Hello from an alternative remote worker",
66+
},
67+
remoteWorkerResponse: "Hello from a remote worker",
68+
});
69+
});
5470
});
55-
});
5671

57-
describe.each(commands)('with "%s" command', (command) => {
58-
test("can fetch from both local (/auxiliary) and remote workers", async ({
59-
expect,
60-
}) => {
61-
const proc = await runLongLived("pnpm", command, projectPath);
72+
test("reflects changes applied during dev", async ({ expect }) => {
73+
const proc = await runLongLived("pnpm", "dev", projectPath);
6274
const url = await waitForReady(proc);
6375
expect(await fetchJson(url)).toEqual({
6476
localWorkerResponse: {
6577
remoteWorkerResponse: "Hello from an alternative remote worker",
6678
},
6779
remoteWorkerResponse: "Hello from a remote worker",
6880
});
69-
});
70-
});
7181

72-
test("reflects changes applied during dev", async ({ expect }) => {
73-
const proc = await runLongLived("pnpm", "dev", projectPath);
74-
const url = await waitForReady(proc);
75-
expect(await fetchJson(url)).toEqual({
76-
localWorkerResponse: {
77-
remoteWorkerResponse: "Hello from an alternative remote worker",
78-
},
79-
remoteWorkerResponse: "Hello from a remote worker",
80-
});
81-
82-
const entryWorkerPath = `${projectPath}/entry-worker/src/index.ts`;
83-
const entryWorkerContent = await readFile(entryWorkerPath, "utf8");
82+
const entryWorkerPath = `${projectPath}/entry-worker/src/index.ts`;
83+
const entryWorkerContent = await readFile(entryWorkerPath, "utf8");
8484

85-
await writeFile(
86-
entryWorkerPath,
87-
entryWorkerContent
88-
.replace(
89-
"localWorkerResponse: await",
90-
"localWorkerResponseJson: await"
91-
)
92-
.replace(
93-
"remoteWorkerResponse: await",
94-
"remoteWorkerResponseText: await"
95-
),
96-
"utf8"
97-
);
85+
await writeFile(
86+
entryWorkerPath,
87+
entryWorkerContent
88+
.replace(
89+
"localWorkerResponse: await",
90+
"localWorkerResponseJson: await"
91+
)
92+
.replace(
93+
"remoteWorkerResponse: await",
94+
"remoteWorkerResponseText: await"
95+
),
96+
"utf8"
97+
);
9898

99-
await setTimeout(500);
99+
await setTimeout(500);
100100

101-
await vi.waitFor(
102-
async () => {
103-
expect(await fetchJson(url)).toEqual({
104-
localWorkerResponseJson: {
105-
remoteWorkerResponse: "Hello from an alternative remote worker",
106-
},
107-
remoteWorkerResponseText: "Hello from a remote worker",
108-
});
109-
},
110-
{ timeout: 5_000, interval: 250 }
111-
);
101+
await vi.waitFor(
102+
async () => {
103+
expect(await fetchJson(url)).toEqual({
104+
localWorkerResponseJson: {
105+
remoteWorkerResponse: "Hello from an alternative remote worker",
106+
},
107+
remoteWorkerResponseText: "Hello from a remote worker",
108+
});
109+
},
110+
{ timeout: 5_000, interval: 250 }
111+
);
112112

113-
await writeFile(entryWorkerPath, entryWorkerContent, "utf8");
113+
await writeFile(entryWorkerPath, entryWorkerContent, "utf8");
114114

115-
await vi.waitFor(
116-
async () => {
117-
expect(await fetchJson(url)).toEqual({
118-
localWorkerResponse: {
119-
remoteWorkerResponse: "Hello from an alternative remote worker",
120-
},
121-
remoteWorkerResponse: "Hello from a remote worker",
122-
});
123-
},
124-
{ timeout: 5_000, interval: 250 }
125-
);
115+
await vi.waitFor(
116+
async () => {
117+
expect(await fetchJson(url)).toEqual({
118+
localWorkerResponse: {
119+
remoteWorkerResponse: "Hello from an alternative remote worker",
120+
},
121+
remoteWorkerResponse: "Hello from a remote worker",
122+
});
123+
},
124+
{ timeout: 5_000, interval: 250 }
125+
);
126+
});
126127
});
127-
});
128+
}

0 commit comments

Comments
 (0)