diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 3c20c4fd..66dbdc94 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -17,7 +17,7 @@ jobs: run: npm install -g pnpm && pnpm install - name: Install Playwright browsers run: pnpm run install-playwright - - name: Run playwright tests (api) - run: pnpm -F api run e2e - - name: Run playwright tests (create-next-app) - run: pnpm -F create-next-app run e2e + - name: Run playwright tests + run: pnpm e2e + - name: Run playwright dev tests + run: pnpm e2e:dev diff --git a/examples/api/package.json b/examples/api/package.json index 54bb2149..15ce8261 100644 --- a/examples/api/package.json +++ b/examples/api/package.json @@ -11,6 +11,7 @@ "dev:worker": "wrangler dev --port 8770", "preview:worker": "pnpm build:worker && pnpm dev:worker", "e2e": "playwright test", + "e2e:dev": "playwright test -c playwright.dev.config.ts", "cf-typegen": "wrangler types --env-interface CloudflareEnv" }, "dependencies": { diff --git a/examples/api/playwright.dev.config.ts b/examples/api/playwright.dev.config.ts new file mode 100644 index 00000000..e3cc777b --- /dev/null +++ b/examples/api/playwright.dev.config.ts @@ -0,0 +1,80 @@ +import { defineConfig, devices } from "@playwright/test"; + +declare var process: { env: Record }; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "./e2e-tests", + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: "http://localhost:3333", + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, + + { + name: "webkit", + use: { ...devices["Desktop Safari"] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: "pnpm dev --port 3333", + url: "http://localhost:3333", + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/package.json b/package.json index 7bae7dd2..fdffc8c9 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "prettier:fix": "prettier --write .", "postinstall": "pnpm --filter cloudflare build", "install-playwright": "playwright install --with-deps", - "e2e": "pnpm -r e2e" + "e2e": "pnpm -r e2e", + "e2e:dev": "pnpm -r e2e:dev" } } diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json index 439af866..a78d963d 100644 --- a/packages/cloudflare/package.json +++ b/packages/cloudflare/package.json @@ -48,5 +48,8 @@ }, "dependencies": { "ts-morph": "catalog:" + }, + "peerDependencies": { + "wrangler": "catalog:" } } diff --git a/packages/cloudflare/src/api/get-cloudflare-context.ts b/packages/cloudflare/src/api/get-cloudflare-context.ts index b9afec87..a2baac0d 100644 --- a/packages/cloudflare/src/api/get-cloudflare-context.ts +++ b/packages/cloudflare/src/api/get-cloudflare-context.ts @@ -29,25 +29,55 @@ const cloudflareContextSymbol = Symbol.for("__cloudflare-context__"); /** * Utility to get the current Cloudflare context * - * Throws an error if the context could not be retrieved - * * @returns the cloudflare context */ export async function getCloudflareContext< CfProperties extends Record = IncomingRequestCfProperties, Context = ExecutionContext, >(): Promise> { - const cloudflareContext = ( - globalThis as unknown as { - [cloudflareContextSymbol]: CloudflareContext | undefined; - } - )[cloudflareContextSymbol]; + const global = globalThis as unknown as { + [cloudflareContextSymbol]: CloudflareContext | undefined; + }; + + const cloudflareContext = global[cloudflareContextSymbol]; if (!cloudflareContext) { - // TODO: cloudflareContext should always be present in production/preview, if not it means that this - // is running under `next dev`, in this case use `getPlatformProxy` to return local proxies - throw new Error("Cloudflare context is not defined!"); + // the cloudflare context is initialized by the worker and is always present in production/preview, + // so, it not being present means that the application is running under `next dev` + return getCloudflareContextInNextDev(); } return cloudflareContext; } + +const cloudflareContextInNextDevSymbol = Symbol.for("__next-dev/cloudflare-context__"); + +/** + * Gets a local proxy version of the cloudflare context (created using `getPlatformProxy`) when + * running in the standard next dev server (via `next dev`) + * + * @returns the local proxy version of the cloudflare context + */ +async function getCloudflareContextInNextDev< + CfProperties extends Record = IncomingRequestCfProperties, + Context = ExecutionContext, +>(): Promise> { + const global = globalThis as unknown as { + [cloudflareContextInNextDevSymbol]: CloudflareContext | undefined; + }; + + if (!global[cloudflareContextInNextDevSymbol]) { + // Note: we never want wrangler to be bundled in the Next.js app, that's why the import below looks like it does + const { getPlatformProxy } = await import( + /* webpackIgnore: true */ `${"__wrangler".replaceAll("_", "")}` + ); + const { env, cf, ctx } = await getPlatformProxy(); + global[cloudflareContextInNextDevSymbol] = { + env, + cf: cf as unknown as CfProperties, + ctx: ctx as Context, + }; + } + + return global[cloudflareContextInNextDevSymbol]!; +} diff --git a/packages/cloudflare/tsup.config.ts b/packages/cloudflare/tsup.config.ts index 367faabe..e69b049b 100644 --- a/packages/cloudflare/tsup.config.ts +++ b/packages/cloudflare/tsup.config.ts @@ -8,6 +8,7 @@ const cliConfig = defineConfig({ format: ["esm"], platform: "node", external: ["esbuild"], + clean: true, onSuccess: async () => { await cp(`${__dirname}/src/cli/templates`, `${__dirname}/dist/cli/templates`, { recursive: true, @@ -22,6 +23,7 @@ const apiConfig = defineConfig({ format: ["esm"], platform: "node", external: ["server-only"], + clean: true, }); export default [cliConfig, apiConfig]; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 335379d9..32cd52f4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -145,6 +145,9 @@ importers: ts-morph: specifier: 'catalog:' version: 23.0.0 + wrangler: + specifier: 'catalog:' + version: 3.78.6(@cloudflare/workers-types@4.20240919.0) devDependencies: '@cloudflare/workers-types': specifier: 'catalog:'