chore: import upstream snapshot with attribution
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
Handle Changesets / Handle Changesets (push) Has been cancelled
Semgrep OSS scan / semgrep-oss (push) Has been cancelled
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
Handle Changesets / Handle Changesets (push) Has been cancelled
Semgrep OSS scan / semgrep-oss (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import { test as base, expect } from "@playwright/test";
|
||||
import { http, HttpResponse } from "msw";
|
||||
import { setupServer, type SetupServerApi } from "msw/node";
|
||||
import { createTestHarness, type TestHarness } from "wrangler";
|
||||
|
||||
type TestFixtures = {
|
||||
reset: void;
|
||||
};
|
||||
|
||||
type WorkerFixtures = {
|
||||
network: SetupServerApi;
|
||||
server: TestHarness;
|
||||
};
|
||||
|
||||
const test = base.extend<TestFixtures, WorkerFixtures>({
|
||||
network: [
|
||||
async ({}, use) => {
|
||||
const network = setupServer();
|
||||
network.listen({ onUnhandledRequest: "error" });
|
||||
|
||||
await use(network);
|
||||
|
||||
network.close();
|
||||
},
|
||||
{ scope: "worker" },
|
||||
],
|
||||
|
||||
server: [
|
||||
async ({}, use) => {
|
||||
const server = createTestHarness({
|
||||
workers: [
|
||||
// Playwright tests run against the Vite build output.
|
||||
{ configPath: "./dist/web_worker/wrangler.json" },
|
||||
{ configPath: "./dist/api_worker/wrangler.json" },
|
||||
],
|
||||
});
|
||||
|
||||
await server.listen();
|
||||
|
||||
await use(server);
|
||||
|
||||
await server.close();
|
||||
},
|
||||
{ scope: "worker" },
|
||||
],
|
||||
|
||||
baseURL: async ({ server }, use) => {
|
||||
const { url } = await server.listen();
|
||||
await use(url.href);
|
||||
},
|
||||
|
||||
reset: [
|
||||
async ({ network, server }, use, testInfo) => {
|
||||
await server.getWorker("api-worker").applyD1Migrations("DATABASE");
|
||||
|
||||
await use();
|
||||
|
||||
if (testInfo.status !== testInfo.expectedStatus) {
|
||||
server.debug();
|
||||
}
|
||||
|
||||
network.resetHandlers();
|
||||
await server.reset();
|
||||
},
|
||||
{ auto: true },
|
||||
],
|
||||
});
|
||||
|
||||
test.describe("createTestHarness: Playwright setup", () => {
|
||||
test("renders a user profile", async ({ page, network }) => {
|
||||
network.use(
|
||||
http.get("http://identity.example.com/profile/:id", ({ params }) => {
|
||||
return HttpResponse.json({ id: params.id, name: "Ada" });
|
||||
})
|
||||
);
|
||||
|
||||
await page.goto("/users/123");
|
||||
|
||||
await expect(page.getByText("Profile: Ada")).toBeVisible();
|
||||
});
|
||||
|
||||
test("renders a report generated by a scheduled handler", async ({
|
||||
page,
|
||||
network,
|
||||
server,
|
||||
}) => {
|
||||
network.use(
|
||||
http.get("http://identity.example.com/profile/:id", ({ params }) => {
|
||||
return HttpResponse.json({ id: params.id, name: "Ada" });
|
||||
})
|
||||
);
|
||||
|
||||
const apiWorker = server.getWorker("api-worker");
|
||||
await apiWorker.fetch("/v1/users/123");
|
||||
await apiWorker.fetch("/v1/users/456");
|
||||
await apiWorker.scheduled({
|
||||
cron: "0 0 * * *",
|
||||
scheduledTime: new Date("2026-05-29T00:00:00.000Z"),
|
||||
});
|
||||
|
||||
await page.goto("/reports/2026-05-29");
|
||||
|
||||
await expect(
|
||||
page.getByText("Daily report (2026-05-29): active users 123, 456")
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "@cloudflare/workers-tsconfig/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"noEmit": true,
|
||||
"types": ["@cloudflare/workers-types", "node"]
|
||||
},
|
||||
"include": [
|
||||
"../playwright.config.ts",
|
||||
"../workers/*/worker-configuration.d.ts",
|
||||
"**/*.ts"
|
||||
],
|
||||
"exclude": []
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
import { http, HttpResponse } from "msw";
|
||||
import { setupServer } from "msw/node";
|
||||
import {
|
||||
afterAll,
|
||||
afterEach,
|
||||
beforeAll,
|
||||
beforeEach,
|
||||
describe,
|
||||
test,
|
||||
} from "vitest";
|
||||
import { createTestHarness } from "wrangler";
|
||||
|
||||
// Point each worker to the Wrangler config you want to test.
|
||||
const server = createTestHarness({
|
||||
workers: [
|
||||
{
|
||||
configPath: "./workers/web/wrangler.jsonc",
|
||||
bindingOverrides: { BROWSER: "mock-browser" },
|
||||
},
|
||||
{ configPath: "./workers/api/wrangler.jsonc" },
|
||||
{ configPath: "./workers/mock-browser/wrangler.jsonc" },
|
||||
],
|
||||
});
|
||||
|
||||
// server.getWorker() would return the first Worker, but naming it makes it explicit.
|
||||
const webWorker = server.getWorker<
|
||||
WebEnv,
|
||||
typeof import("../workers/web/index")
|
||||
>("web-worker");
|
||||
const apiWorker = server.getWorker<
|
||||
ApiEnv,
|
||||
typeof import("../workers/api/index")
|
||||
>("api-worker");
|
||||
const mockBrowserWorker = server.getWorker<
|
||||
unknown,
|
||||
typeof import("../workers/mock-browser/index")
|
||||
>("mock-browser");
|
||||
|
||||
// Workers started by createTestHarness route outbound fetches to globalThis.fetch().
|
||||
// You can use libraries like MSW to intercept those requests.
|
||||
const network = setupServer();
|
||||
|
||||
describe("createTestHarness: Vitest setup", () => {
|
||||
beforeAll(async () => {
|
||||
network.listen({ onUnhandledRequest: "error" });
|
||||
await server.listen();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await apiWorker.applyD1Migrations("DATABASE");
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
network.close();
|
||||
await server.close();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Keep tests isolated while reusing the same running server.
|
||||
network.resetHandlers();
|
||||
await server.reset();
|
||||
});
|
||||
|
||||
test("fetches the primary Worker with a relative URL", async ({ expect }) => {
|
||||
// Relative URLs are dispatched to the primary Worker (the first one listed).
|
||||
const response = await server.fetch("/");
|
||||
expect(await response.text()).toBe("Hello World");
|
||||
});
|
||||
|
||||
test("mocks outbound requests", async ({ expect }) => {
|
||||
network.use(
|
||||
http.get("http://identity.example.com/profile/:id", ({ params }) => {
|
||||
return HttpResponse.json({ id: params.id, name: "Ada" });
|
||||
})
|
||||
);
|
||||
|
||||
const userResponse = await apiWorker.fetch("/v1/users/123");
|
||||
expect(await userResponse.json()).toEqual({
|
||||
id: "123",
|
||||
name: "Ada",
|
||||
});
|
||||
});
|
||||
|
||||
test("overrides a platform binding with a mock Worker", async ({
|
||||
expect,
|
||||
}) => {
|
||||
const apiEnv = await apiWorker.getEnv();
|
||||
await apiEnv.DATABASE.prepare(
|
||||
"INSERT INTO daily_reports (date, user_ids) VALUES (?, ?)"
|
||||
)
|
||||
.bind("2026-05-29", JSON.stringify(["123", "456"]))
|
||||
.run();
|
||||
|
||||
const stubPng = Uint8Array.from([
|
||||
137, 80, 78, 71, 13, 10, 26, 10, 109, 111, 99, 107,
|
||||
]);
|
||||
const mockBrowser = await mockBrowserWorker.getExport();
|
||||
await mockBrowser.setScreenshot(Array.from(stubPng));
|
||||
|
||||
const response = await webWorker.fetch("/reports/2026-05-29.png");
|
||||
expect(response.headers.get("content-type")).toBe("image/png");
|
||||
expect(await response.bytes()).toEqual(stubPng);
|
||||
});
|
||||
|
||||
test("dispatches requests using configured routes", async ({ expect }) => {
|
||||
network.use(
|
||||
http.get("http://identity.example.com/profile/:id", ({ params }) => {
|
||||
return HttpResponse.json({ id: params.id, name: "Ada" });
|
||||
})
|
||||
);
|
||||
|
||||
// server.fetch() matches requests to workers based on routes.
|
||||
const apiResponse = await server.fetch(
|
||||
"http://api.example.com/v1/users/123"
|
||||
);
|
||||
expect(await apiResponse.json()).toEqual({
|
||||
id: "123",
|
||||
name: "Ada",
|
||||
});
|
||||
|
||||
const webResponse = await server.fetch("http://example.com/users/123");
|
||||
expect(await webResponse.text()).toBe("Profile: Ada");
|
||||
});
|
||||
|
||||
test("runs scheduled jobs and stores the result", async ({ expect }) => {
|
||||
network.use(
|
||||
http.get("http://identity.example.com/profile/:id", ({ params }) => {
|
||||
return HttpResponse.json({ id: params.id, name: "Ada" });
|
||||
})
|
||||
);
|
||||
|
||||
// Seed user data that the scheduled job will read to generate a report.
|
||||
await apiWorker.fetch("/v1/users/123");
|
||||
await apiWorker.fetch("/v1/users/456");
|
||||
|
||||
const initialResponse = await webWorker.fetch("/reports/2026-05-29");
|
||||
expect(initialResponse.status).toBe(404);
|
||||
expect(await initialResponse.text()).toBe("No report");
|
||||
|
||||
server.clearLogs();
|
||||
|
||||
expect(
|
||||
await apiWorker.scheduled({
|
||||
cron: "0 0 * * *",
|
||||
scheduledTime: new Date("2026-05-29T00:00:00.000Z"),
|
||||
})
|
||||
).toEqual({ outcome: "ok", noRetry: false });
|
||||
expect(server.getLogs()).toContainEqual(
|
||||
expect.objectContaining({
|
||||
level: "info",
|
||||
message: "Generated daily report for 2026-05-29",
|
||||
})
|
||||
);
|
||||
|
||||
const webResponse = await webWorker.fetch("/reports/2026-05-29");
|
||||
expect(await webResponse.text()).toBe(
|
||||
"Daily report (2026-05-29): active users 123, 456"
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user