From c45d87f663a31efc02e9607d79f8708caef02888 Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Thu, 1 Oct 2026 17:20:44 +0200 Subject: [PATCH] test(nextjs): Run nextjs-16 on Bun, Deno and Cloudflare nextjs-16 gets variants for Bun, Deno and Cloudflare Workers (OpenNext) and replaces `nextjs-16-bun` and `nextjs-16-cf-workers`. Bun, Cloudflare and Cloudflare with the latest Next.js are required jobs, as the two old apps were. Deno is optional. - Bun and Deno run `next start` on the same build as Node.js. The server config adds the integrations of `@sentry/bun` and `@sentry/deno` that replace the Node.js instrumentation these runtimes do not trigger. The e2e README now describes this, and that `variants` run as required jobs. - Cloudflare builds with `opennextjs-cloudflare` and wraps the Worker in `worker.ts` with `withSentry` from `@sentry/nextjs/cloudflare`. `pg-cloudflare` is in `serverExternalPackages`, because OpenNext copies the `workerd` build of a package only when that list has the package. - The app does not depend on `@opentelemetry/api` directly, because the OpenNext middleware bundle can not load an installed copy. `/api/otel-context` uses the copy of Next.js. - The mock AI server runs in the Playwright global setup instead of the app, because a Worker can not start a `node:http` server. Thus the OpenAI test runs on every runtime. - Tests branch on `getRuntime()` where the runtimes differ (segment origin and attributes, middleware spans in the request segment of the Worker, fetch span attributes). They skip what a runtime can not do: Node.js runtime metrics on Workers, and `pg` spans on Bun and Workers, which have no runtime module hook. Co-Authored-By: Claude Opus 5.5 --- dev-packages/e2e-tests/README.md | 10 +- .../nextjs-16-bun/.gitignore | 44 ----- .../app/(nested-layout)/layout.tsx | 12 -- .../nested-layout/[dynamic]/layout.tsx | 12 -- .../nested-layout/[dynamic]/page.tsx | 15 -- .../(nested-layout)/nested-layout/layout.tsx | 12 -- .../(nested-layout)/nested-layout/page.tsx | 11 -- .../api/endpoint-behind-middleware/route.ts | 3 - .../nextjs-16-bun/app/global-error.tsx | 23 --- .../nextjs-16-bun/app/layout.tsx | 7 - .../app/nested-rsc-error/[param]/page.tsx | 17 -- .../nextjs-16-bun/app/page.tsx | 3 - .../app/pageload-tracing/layout.tsx | 8 - .../app/pageload-tracing/page.tsx | 14 -- .../parameterized/[one]/beep/[two]/page.tsx | 3 - .../app/parameterized/[one]/beep/page.tsx | 3 - .../app/parameterized/[one]/page.tsx | 3 - .../app/parameterized/static/page.tsx | 3 - .../app/route-handler/[xoxo]/edge/route.ts | 8 - .../app/route-handler/[xoxo]/node/route.ts | 7 - .../[param]/client-page.tsx | 8 - .../app/streaming-rsc-error/[param]/page.tsx | 18 --- .../app/third-party-filter/page.tsx | 24 --- .../nextjs-16-bun/instrumentation-client.ts | 16 -- .../nextjs-16-bun/instrumentation.ts | 13 -- .../nextjs-16-bun/next.config.ts | 9 -- .../nextjs-16-bun/package.json | 35 ---- .../nextjs-16-bun/playwright.config.mjs | 25 --- .../test-applications/nextjs-16-bun/proxy.ts | 33 ---- .../nextjs-16-bun/sentry.edge.config.ts | 8 - .../nextjs-16-bun/sentry.server.config.ts | 19 --- .../nextjs-16-bun/start-event-proxy.mjs | 14 -- .../nextjs-16-bun/tests/middleware.test.ts | 20 --- .../tests/nested-rsc-error.test.ts | 43 ----- .../tests/pageload-tracing.test.ts | 23 --- .../tests/parameterized-routes.test.ts | 83 ---------- .../nextjs-16-bun/tests/route-handler.test.ts | 16 -- .../tests/server-components.test.ts | 149 ----------------- .../tests/streaming-rsc-error.test.ts | 45 ------ .../tests/third-party-filter.test.ts | 28 ---- .../nextjs-16-bun/tsconfig.json | 27 ---- .../nextjs-16-cf-workers/.gitignore | 44 ----- .../app/(nested-layout)/layout.tsx | 12 -- .../nested-layout/[dynamic]/layout.tsx | 12 -- .../nested-layout/[dynamic]/page.tsx | 15 -- .../(nested-layout)/nested-layout/layout.tsx | 12 -- .../(nested-layout)/nested-layout/page.tsx | 11 -- .../api/endpoint-behind-middleware/route.ts | 3 - .../app/api/test-success/route.ts | 7 - .../nextjs-16-cf-workers/app/favicon.ico | Bin 25931 -> 0 bytes .../nextjs-16-cf-workers/app/global-error.tsx | 23 --- .../app/isr-test/[product]/page.tsx | 17 -- .../app/isr-test/static/page.tsx | 15 -- .../nextjs-16-cf-workers/app/layout.tsx | 7 - .../nextjs-16-cf-workers/app/metrics/page.tsx | 34 ---- .../app/metrics/route-handler/route.ts | 23 --- .../app/nested-rsc-error/[param]/page.tsx | 17 -- .../app/non-isr-test/[item]/page.tsx | 11 -- .../nextjs-16-cf-workers/app/page.tsx | 3 - .../app/pageload-tracing/layout.tsx | 8 - .../app/pageload-tracing/page.tsx | 14 -- .../parameterized/[one]/beep/[two]/page.tsx | 3 - .../app/parameterized/[one]/beep/page.tsx | 3 - .../app/parameterized/[one]/page.tsx | 3 - .../app/parameterized/static/page.tsx | 3 - .../app/prefetching/page.tsx | 9 -- .../app/prefetching/to-be-prefetched/page.tsx | 5 - .../app/redirect/destination/page.tsx | 7 - .../app/redirect/origin/page.tsx | 18 --- .../app/route-handler/[xoxo]/edge/route.ts | 8 - .../app/route-handler/[xoxo]/node/route.ts | 7 - .../[param]/client-page.tsx | 8 - .../app/streaming-rsc-error/[param]/page.tsx | 18 --- .../nextjs-16-cf-workers/eslint.config.mjs | 19 --- .../instrumentation-client.ts | 15 -- .../nextjs-16-cf-workers/instrumentation.ts | 13 -- .../nextjs-16-cf-workers/middleware.ts | 35 ---- .../nextjs-16-cf-workers/next.config.ts | 8 - .../nextjs-16-cf-workers/package.json | 51 ------ .../playwright.config.mjs | 21 --- .../nextjs-16-cf-workers/public/file.svg | 1 - .../nextjs-16-cf-workers/public/globe.svg | 1 - .../nextjs-16-cf-workers/public/next.svg | 1 - .../nextjs-16-cf-workers/public/vercel.svg | 1 - .../nextjs-16-cf-workers/public/window.svg | 1 - .../sentry.edge.config.ts | 7 - .../sentry.server.config.ts | 7 - .../start-event-proxy.mjs | 14 -- .../tests/async-params.test.ts | 14 -- .../nextjs-16-cf-workers/tests/isDevMode.ts | 1 - .../tests/isr-routes.test.ts | 83 ---------- .../tests/metrics.test.ts | 130 --------------- .../tests/middleware.test.ts | 70 -------- .../tests/nested-rsc-error.test.ts | 43 ----- .../tests/pageload-tracing.test.ts | 53 ------- .../tests/parameterized-routes.test.ts | 83 ---------- .../tests/prefetch-spans.test.ts | 26 --- .../tests/route-handler.test.ts | 33 ---- .../tests/server-action-redirect.test.ts | 47 ------ .../tests/server-components.test.ts | 150 ------------------ .../tests/streaming-rsc-error.test.ts | 45 ------ .../nextjs-16-cf-workers/tsconfig.json | 27 ---- .../nextjs-16-cf-workers/wrangler.jsonc | 74 --------- .../nextjs-16/ai-mock-server.mjs | 15 +- .../app/api/d1/route.ts | 0 .../nextjs-16/app/api/openai/route.ts | 5 +- .../app/api/otel-context/route.ts | 8 +- .../app/api/test-error/route.ts | 0 .../check/route.ts | 0 .../route.ts | 0 .../test-outgoing-fetch/check/route.ts | 0 .../propagation/test-outgoing-fetch/route.ts | 0 .../app/propagation/utils.ts | 0 .../nextjs-16/global-setup.mjs | 5 + .../nextjs-16/next.config.ts | 3 + .../open-next.config.ts | 0 .../test-applications/nextjs-16/package.json | 33 +++- .../nextjs-16/playwright.config.mjs | 11 +- .../nextjs-16/sentry.server.config.ts | 13 +- .../tests/cloudflare-runtime.test.ts | 6 +- .../tests/d1-binding.test.ts | 6 +- .../nextjs-16/tests/db-page.test.ts | 29 ++-- .../nextjs-16/tests/metrics.test.ts | 12 +- .../nextjs-16/tests/middleware.test.ts | 56 ++++++- .../tests/node-runtime-metrics.test.ts | 4 +- .../tests/otel-context.test.ts | 2 +- .../tests/propagation.test.ts | 14 +- .../nextjs-16/tests/server-components.test.ts | 29 +++- .../tests/trace-continuation.test.ts | 17 +- .../tests/worker-bundle.test.ts | 3 + .../worker.ts | 1 + .../nextjs-16/wrangler.jsonc | 36 +++++ 132 files changed, 251 insertions(+), 2385 deletions(-) delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/.gitignore delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/layout.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/[dynamic]/layout.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/[dynamic]/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/layout.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/api/endpoint-behind-middleware/route.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/global-error.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/layout.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/nested-rsc-error/[param]/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/pageload-tracing/layout.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/pageload-tracing/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/[one]/beep/[two]/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/[one]/beep/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/[one]/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/static/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/route-handler/[xoxo]/edge/route.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/route-handler/[xoxo]/node/route.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/streaming-rsc-error/[param]/client-page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/streaming-rsc-error/[param]/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/third-party-filter/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/instrumentation-client.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/instrumentation.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/next.config.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/package.json delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/playwright.config.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/proxy.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/sentry.edge.config.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/sentry.server.config.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/start-event-proxy.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/middleware.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/nested-rsc-error.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/pageload-tracing.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/parameterized-routes.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/route-handler.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/server-components.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/streaming-rsc-error.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/third-party-filter.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-bun/tsconfig.json delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/.gitignore delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/layout.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/[dynamic]/layout.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/[dynamic]/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/layout.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/endpoint-behind-middleware/route.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/test-success/route.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/favicon.ico delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/global-error.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/isr-test/[product]/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/isr-test/static/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/layout.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/metrics/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/metrics/route-handler/route.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/nested-rsc-error/[param]/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/non-isr-test/[item]/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/pageload-tracing/layout.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/pageload-tracing/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/[one]/beep/[two]/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/[one]/beep/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/[one]/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/static/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/prefetching/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/prefetching/to-be-prefetched/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/redirect/destination/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/redirect/origin/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/route-handler/[xoxo]/edge/route.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/route-handler/[xoxo]/node/route.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/streaming-rsc-error/[param]/client-page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/streaming-rsc-error/[param]/page.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/eslint.config.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/instrumentation-client.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/instrumentation.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/middleware.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/next.config.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/package.json delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/playwright.config.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/file.svg delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/globe.svg delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/next.svg delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/vercel.svg delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/window.svg delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/sentry.edge.config.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/sentry.server.config.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/start-event-proxy.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/async-params.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/isDevMode.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/isr-routes.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/metrics.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/middleware.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/nested-rsc-error.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/pageload-tracing.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/parameterized-routes.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/prefetch-spans.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/route-handler.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/server-action-redirect.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/server-components.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/streaming-rsc-error.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tsconfig.json delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/wrangler.jsonc rename dev-packages/e2e-tests/test-applications/{nextjs-16-cf-workers => nextjs-16}/app/api/d1/route.ts (100%) rename dev-packages/e2e-tests/test-applications/{nextjs-16-cf-workers => nextjs-16}/app/api/otel-context/route.ts (54%) rename dev-packages/e2e-tests/test-applications/{nextjs-16-cf-workers => nextjs-16}/app/api/test-error/route.ts (100%) rename dev-packages/e2e-tests/test-applications/{nextjs-16-bun => nextjs-16}/app/propagation/test-outgoing-fetch-external-disallowed/check/route.ts (100%) rename dev-packages/e2e-tests/test-applications/{nextjs-16-bun => nextjs-16}/app/propagation/test-outgoing-fetch-external-disallowed/route.ts (100%) rename dev-packages/e2e-tests/test-applications/{nextjs-16-bun => nextjs-16}/app/propagation/test-outgoing-fetch/check/route.ts (100%) rename dev-packages/e2e-tests/test-applications/{nextjs-16-bun => nextjs-16}/app/propagation/test-outgoing-fetch/route.ts (100%) rename dev-packages/e2e-tests/test-applications/{nextjs-16-bun => nextjs-16}/app/propagation/utils.ts (100%) rename dev-packages/e2e-tests/test-applications/{nextjs-16-cf-workers => nextjs-16}/open-next.config.ts (100%) rename dev-packages/e2e-tests/test-applications/{nextjs-16-cf-workers => nextjs-16}/tests/cloudflare-runtime.test.ts (79%) rename dev-packages/e2e-tests/test-applications/{nextjs-16-cf-workers => nextjs-16}/tests/d1-binding.test.ts (80%) rename dev-packages/e2e-tests/test-applications/{nextjs-16-cf-workers => nextjs-16}/tests/otel-context.test.ts (95%) rename dev-packages/e2e-tests/test-applications/{nextjs-16-bun => nextjs-16}/tests/propagation.test.ts (82%) rename dev-packages/e2e-tests/test-applications/{nextjs-16-cf-workers => nextjs-16}/tests/trace-continuation.test.ts (58%) rename dev-packages/e2e-tests/test-applications/{nextjs-16-cf-workers => nextjs-16}/tests/worker-bundle.test.ts (95%) rename dev-packages/e2e-tests/test-applications/{nextjs-16-cf-workers => nextjs-16}/worker.ts (87%) create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16/wrangler.jsonc diff --git a/dev-packages/e2e-tests/README.md b/dev-packages/e2e-tests/README.md index 4b2e8acf4298..4846ced41e19 100644 --- a/dev-packages/e2e-tests/README.md +++ b/dev-packages/e2e-tests/README.md @@ -136,8 +136,9 @@ Apps like `cloudflare-workers-send-to-sentry` deploy the built app as a real Wor ## Runtime variants (Bun, Deno, Cloudflare) -To test a framework on a runtime other than Node.js, add an `optionalVariants` entry to the existing test app instead of -creating a new app. `react-router-8-framework` is the reference setup. +To test a framework on a runtime other than Node.js, add a variant to the existing test app instead of creating a new +app: `variants` run as required CI jobs, `optionalVariants` as optional ones. `react-router-8-framework` is the +reference setup. - **`RUNTIME` env var**: `node` (default), `bun`, `deno` or `cloudflare`. Read it with `getRuntime()` from `@sentry-internal/test-utils`, which throws on any other value. Tests branch on it where the runtimes are expected to @@ -145,7 +146,10 @@ creating a new app. `react-router-8-framework` is the reference setup. - **SDK per runtime**: each runtime inits its own SDK, the way a user of that runtime would: Node the framework SDK (`instrument.mjs`), Bun `@sentry/bun` (`instrument.bun.mjs`), Deno `@sentry/deno` (`instrument.deno.mjs`) and Cloudflare `@sentry/cloudflare`. The framework SDK only provides the framework wrappers on the other runtimes, so - values that come from its `init()` (`sdk.name`, default integrations) are Node-only. + values that come from its `init()` (`sdk.name`, default integrations) are Node-only. Next.js is different: it loads + `instrumentation.ts` on every runtime, so `nextjs-16` calls `init()` of `@sentry/nextjs` on Bun and Deno too, and + adds the integrations of `@sentry/bun` or `@sentry/deno` that replace the Node instrumentation those runtimes do not + trigger. - **Start commands**: `playwright.config.mjs` selects the start command with `getRuntime()`. Bun and Deno use the same build as Node and only change the start command, for example `bun --bun --preload ./instrument.bun.mjs ./node_modules/@react-router/serve/bin.cjs ./build/server/index.js` and diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/.gitignore b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/.gitignore deleted file mode 100644 index dd146b53d966..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/.gitignore +++ /dev/null @@ -1,44 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/versions - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - -# env files (can opt-in for committing if needed) -.env* - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts - -# Sentry Config File -.env.sentry-build-plugin diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/layout.tsx deleted file mode 100644 index ace0c2f086b7..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/layout.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { PropsWithChildren } from 'react'; - -export const dynamic = 'force-dynamic'; - -export default function Layout({ children }: PropsWithChildren<{}>) { - return ( -
-

Layout

- {children} -
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/[dynamic]/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/[dynamic]/layout.tsx deleted file mode 100644 index dbdc60adadc2..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/[dynamic]/layout.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { PropsWithChildren } from 'react'; - -export const dynamic = 'force-dynamic'; - -export default function Layout({ children }: PropsWithChildren<{}>) { - return ( -
-

DynamicLayout

- {children} -
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/[dynamic]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/[dynamic]/page.tsx deleted file mode 100644 index 3eaddda2a1df..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/[dynamic]/page.tsx +++ /dev/null @@ -1,15 +0,0 @@ -export const dynamic = 'force-dynamic'; - -export default async function Page() { - return ( -
-

Dynamic Page

-
- ); -} - -export async function generateMetadata() { - return { - title: 'I am dynamic page generated metadata', - }; -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/layout.tsx deleted file mode 100644 index ace0c2f086b7..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/layout.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { PropsWithChildren } from 'react'; - -export const dynamic = 'force-dynamic'; - -export default function Layout({ children }: PropsWithChildren<{}>) { - return ( -
-

Layout

- {children} -
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/page.tsx deleted file mode 100644 index 8077c14d23ca..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/(nested-layout)/nested-layout/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -export const dynamic = 'force-dynamic'; - -export default function Page() { - return

Hello World!

; -} - -export async function generateMetadata() { - return { - title: 'I am generated metadata', - }; -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/api/endpoint-behind-middleware/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/api/endpoint-behind-middleware/route.ts deleted file mode 100644 index 2733cc918f44..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/api/endpoint-behind-middleware/route.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function GET() { - return Response.json({ name: 'John Doe' }); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/global-error.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/global-error.tsx deleted file mode 100644 index 20c175015b03..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/global-error.tsx +++ /dev/null @@ -1,23 +0,0 @@ -'use client'; - -import * as Sentry from '@sentry/nextjs'; -import NextError from 'next/error'; -import { useEffect } from 'react'; - -export default function GlobalError({ error }: { error: Error & { digest?: string } }) { - useEffect(() => { - Sentry.captureException(error); - }, [error]); - - return ( - - - {/* `NextError` is the default Next.js error page component. Its type - definition requires a `statusCode` prop. However, since the App Router - does not expose status codes for errors, we simply pass 0 to render a - generic error message. */} - - - - ); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/layout.tsx deleted file mode 100644 index c8f9cee0b787..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/layout.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export default function Layout({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/nested-rsc-error/[param]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/nested-rsc-error/[param]/page.tsx deleted file mode 100644 index 675b248026be..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/nested-rsc-error/[param]/page.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Suspense } from 'react'; - -export const dynamic = 'force-dynamic'; - -export default async function Page() { - return ( - Loading...

}> - {/* @ts-ignore */} - ; -
- ); -} - -async function Crash() { - throw new Error('I am technically uncatchable'); - return

unreachable

; -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/page.tsx deleted file mode 100644 index 3204f4df69e1..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Page() { - return

Next 16 Bun test app

; -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/pageload-tracing/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/pageload-tracing/layout.tsx deleted file mode 100644 index 1f0cbe478f88..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/pageload-tracing/layout.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { PropsWithChildren } from 'react'; - -export const dynamic = 'force-dynamic'; - -export default async function Layout({ children }: PropsWithChildren) { - await new Promise(resolve => setTimeout(resolve, 500)); - return <>{children}; -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/pageload-tracing/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/pageload-tracing/page.tsx deleted file mode 100644 index 689735d61ddf..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/pageload-tracing/page.tsx +++ /dev/null @@ -1,14 +0,0 @@ -export const dynamic = 'force-dynamic'; - -export default async function Page() { - await new Promise(resolve => setTimeout(resolve, 1000)); - return

I am page 2

; -} - -export async function generateMetadata() { - (await fetch('https://example.com/', { cache: 'no-store' })).text(); - - return { - title: 'my title', - }; -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/[one]/beep/[two]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/[one]/beep/[two]/page.tsx deleted file mode 100644 index f34461c2bb07..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/[one]/beep/[two]/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function ParameterizedPage() { - return
Dynamic page two
; -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/[one]/beep/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/[one]/beep/page.tsx deleted file mode 100644 index a7d9164c8c03..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/[one]/beep/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function BeepPage() { - return
Beep
; -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/[one]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/[one]/page.tsx deleted file mode 100644 index 9fa617a22381..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/[one]/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function ParameterizedPage() { - return
Dynamic page one
; -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/static/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/static/page.tsx deleted file mode 100644 index 16ef0482d53b..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/parameterized/static/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function StaticPage() { - return
Static page
; -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/route-handler/[xoxo]/edge/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/route-handler/[xoxo]/edge/route.ts deleted file mode 100644 index 7cd1fc7e332c..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/route-handler/[xoxo]/edge/route.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { NextResponse } from 'next/server'; - -export const runtime = 'edge'; -export const dynamic = 'force-dynamic'; - -export async function GET() { - return NextResponse.json({ message: 'Hello Edge Route Handler' }); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/route-handler/[xoxo]/node/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/route-handler/[xoxo]/node/route.ts deleted file mode 100644 index 5bc418f077aa..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/route-handler/[xoxo]/node/route.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { NextResponse } from 'next/server'; - -export const dynamic = 'force-dynamic'; - -export async function GET() { - return NextResponse.json({ message: 'Hello Node Route Handler' }); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/streaming-rsc-error/[param]/client-page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/streaming-rsc-error/[param]/client-page.tsx deleted file mode 100644 index 7b66c3fbdeef..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/streaming-rsc-error/[param]/client-page.tsx +++ /dev/null @@ -1,8 +0,0 @@ -'use client'; - -import { use } from 'react'; - -export function RenderPromise({ stringPromise }: { stringPromise: Promise }) { - const s = use(stringPromise); - return <>{s}; -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/streaming-rsc-error/[param]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/streaming-rsc-error/[param]/page.tsx deleted file mode 100644 index 9531f9a42139..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/streaming-rsc-error/[param]/page.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Suspense } from 'react'; -import { RenderPromise } from './client-page'; - -export const dynamic = 'force-dynamic'; - -export default async function Page() { - const crashingPromise = new Promise((_, reject) => { - setTimeout(() => { - reject(new Error('I am a data streaming error')); - }, 100); - }); - - return ( - Loading...

}> - ; -
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/third-party-filter/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/third-party-filter/page.tsx deleted file mode 100644 index b6b4bea80def..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/third-party-filter/page.tsx +++ /dev/null @@ -1,24 +0,0 @@ -'use client'; - -import * as Sentry from '@sentry/nextjs'; - -function throwFirstPartyError(): void { - throw new Error('first-party-error'); -} - -export default function Page() { - return ( - - ); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/instrumentation-client.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/instrumentation-client.ts deleted file mode 100644 index 9c0366b0cea2..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/instrumentation-client.ts +++ /dev/null @@ -1,16 +0,0 @@ -import * as Sentry from '@sentry/nextjs'; - -Sentry.init({ - environment: 'qa', // dynamic sampling bias to keep transactions - dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, - tunnel: `http://localhost:3031/`, // proxy server - tracesSampleRate: 1.0, - integrations: [ - Sentry.thirdPartyErrorFilterIntegration({ - filterKeys: ['nextjs-16-bun-e2e'], - behaviour: 'apply-tag-if-exclusively-contains-third-party-frames', - }), - ], -}); - -export const onRouterTransitionStart = Sentry.captureRouterTransitionStart; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/instrumentation.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/instrumentation.ts deleted file mode 100644 index 964f937c439a..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/instrumentation.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as Sentry from '@sentry/nextjs'; - -export async function register() { - if (process.env.NEXT_RUNTIME === 'nodejs') { - await import('./sentry.server.config'); - } - - if (process.env.NEXT_RUNTIME === 'edge') { - await import('./sentry.edge.config'); - } -} - -export const onRequestError = Sentry.captureRequestError; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/next.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/next.config.ts deleted file mode 100644 index 34d17391df28..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/next.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { withSentryConfig } from '@sentry/nextjs/config'; -import type { NextConfig } from 'next'; - -const nextConfig: NextConfig = {}; - -export default withSentryConfig(nextConfig, { - silent: true, - applicationKey: 'nextjs-16-bun-e2e', -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/package.json b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/package.json deleted file mode 100644 index 6b510830e98e..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "nextjs-16-bun", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "bun --bun next dev", - "build": "bun --bun next build > .tmp_build_stdout 2> .tmp_build_stderr || (cat .tmp_build_stdout && cat .tmp_build_stderr && exit 1)", - "clean": "npx rimraf node_modules pnpm-lock.yaml .tmp_dev_server_logs", - "start": "bun --bun next start", - "test:prod": "TEST_ENV=production playwright test", - "test:build": "pnpm install && pnpm build", - "test:assert": "pnpm test:prod" - }, - "dependencies": { - "@sentry/nextjs": "file:../../packed/sentry-nextjs-packed.tgz", - "@sentry/bun": "file:../../packed/sentry-bun-packed.tgz", - "@sentry/core": "file:../../packed/sentry-core-packed.tgz", - "import-in-the-middle": "^2", - "next": "16.2.11", - "react": "19.1.0", - "react-dom": "19.1.0", - "require-in-the-middle": "^8" - }, - "devDependencies": { - "@playwright/test": "~1.63.0", - "@sentry-internal/test-utils": "link:../../../test-utils", - "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", - "typescript": "^5" - }, - "volta": { - "extends": "../../package.json" - } -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/playwright.config.mjs deleted file mode 100644 index 28662eab2d7b..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/playwright.config.mjs +++ /dev/null @@ -1,25 +0,0 @@ -import { getPlaywrightConfig } from '@sentry-internal/test-utils'; -const testEnv = process.env.TEST_ENV; - -if (!testEnv) { - throw new Error('No test env defined'); -} - -const getStartCommand = () => { - if (testEnv === 'development') { - return 'bun --bun next dev -p 3030 2>&1 | tee .tmp_dev_server_logs'; - } - - if (testEnv === 'production') { - return 'bun --bun next start -p 3030'; - } - - throw new Error(`Unknown test env: ${testEnv}`); -}; - -const config = getPlaywrightConfig({ - startCommand: getStartCommand(), - port: 3030, -}); - -export default config; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/proxy.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/proxy.ts deleted file mode 100644 index e2d263c4d80c..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/proxy.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { getDefaultIsolationScope } from '@sentry/core'; -import * as Sentry from '@sentry/nextjs'; -import { NextResponse } from 'next/server'; -import type { NextRequest } from 'next/server'; - -export async function proxy(request: NextRequest) { - Sentry.setTag('my-isolated-tag', true); - Sentry.setTag('my-global-scope-isolated-tag', getDefaultIsolationScope().getScopeData().tags['my-isolated-tag']); // We set this tag to be able to assert that the previously set tag has not leaked into the global isolation scope - - // Streamed spans carry no scope tags, so the tests read the isolation state from this attribute instead - const activeSpan = Sentry.getActiveSpan(); - if (activeSpan) { - Sentry.getRootSpan(activeSpan).setAttribute( - 'isolation_scope.is_default', - Sentry.getIsolationScope() === getDefaultIsolationScope(), - ); - } - - if (request.headers.has('x-should-throw')) { - throw new Error('Middleware Error'); - } - - if (request.headers.has('x-should-make-request')) { - await fetch('http://localhost:3030/'); - } - - return NextResponse.next(); -} - -// See "Matching Paths" below to learn more -export const config = { - matcher: ['/api/endpoint-behind-middleware', '/api/endpoint-behind-faulty-middleware'], -}; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/sentry.edge.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/sentry.edge.config.ts deleted file mode 100644 index ad780407a5b7..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/sentry.edge.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import * as Sentry from '@sentry/nextjs'; - -Sentry.init({ - environment: 'qa', // dynamic sampling bias to keep transactions - dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, - tunnel: `http://localhost:3031/`, // proxy server - tracesSampleRate: 1.0, -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/sentry.server.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/sentry.server.config.ts deleted file mode 100644 index e683c5362818..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/sentry.server.config.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { bunHttpServerIntegration } from '@sentry/bun'; -import * as Sentry from '@sentry/nextjs'; -import { bunServerIntegration, fetchIntegration } from '@sentry/bun'; - -Sentry.init({ - environment: 'qa', // dynamic sampling bias to keep transactions - dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, - tunnel: `http://localhost:3031/`, // proxy server - tracesSampleRate: 1.0, - tracePropagationTargets: ['http://localhost:3030/propagation/test-outgoing-fetch/check'], - integrations: [ - // Bun does not emit the `node:http` diagnostics channel the Node SDK uses to isolate incoming - // requests, so each request would otherwise share one trace. Next.js emits its own server spans, - // hence `spans: false` — this only isolates the request and resets its trace. - bunHttpServerIntegration({ spans: false }), - // Adding bun-specific integration here - fetchIntegration(), - ], -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/start-event-proxy.mjs deleted file mode 100644 index d56d5fb74e59..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/start-event-proxy.mjs +++ /dev/null @@ -1,14 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import { startEventProxyServer } from '@sentry-internal/test-utils'; - -const packageJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'))); - -startEventProxyServer({ - port: 3031, - proxyServerName: 'nextjs-16-bun', - envelopeDumpPath: path.join( - process.cwd(), - `event-dumps/nextjs-16-bun-v${packageJson.dependencies.next}-${process.env.TEST_ENV}.dump`, - ), -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/middleware.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/middleware.test.ts deleted file mode 100644 index 2b56d36de0ce..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/middleware.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; - -test('Should create a span for middleware', async ({ request }) => { - const middlewareSpanPromise = waitForStreamedSpan('nextjs-16-bun', span => { - return span.name === 'middleware GET' && span.is_segment; - }); - - const response = await request.get('/api/endpoint-behind-middleware'); - expect(await response.json()).toStrictEqual({ name: 'John Doe' }); - - const middlewareSpan = await middlewareSpanPromise; - - expect(middlewareSpan.status).toBe('ok'); - expect(getSpanOp(middlewareSpan)).toBe('middleware'); - expect(middlewareSpan.attributes['sentry.segment.name.source']?.value).toBe('route'); - - // Assert that isolation scope works properly - expect(middlewareSpan.attributes['isolation_scope.is_default']).toEqual({ value: false, type: 'boolean' }); -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/nested-rsc-error.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/nested-rsc-error.test.ts deleted file mode 100644 index 7ca749c0b181..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/nested-rsc-error.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils'; - -test('Should capture errors from nested server components when `Sentry.captureRequestError` is added to the `onRequestError` hook', async ({ - page, -}) => { - const errorEventPromise = waitForError('nextjs-16-bun', errorEvent => { - return !!errorEvent?.exception?.values?.some(value => value.value === 'I am technically uncatchable'); - }); - - // Matched on the error's own trace so a span from an earlier spec cannot satisfy the correlation. - const serverSpanPromise = waitForStreamedSpan('nextjs-16-bun', async span => { - return ( - span.name === 'GET /nested-rsc-error/[param]' && - span.is_segment && - (await errorEventPromise).contexts?.trace?.trace_id === span.trace_id - ); - }); - - await page.goto(`/nested-rsc-error/123`); - const errorEvent = await errorEventPromise; - const serverSpan = await serverSpanPromise; - - // error event is part of the same trace as the server span - expect(errorEvent.contexts?.trace?.trace_id).toBe(serverSpan.trace_id); - - expect(errorEvent.request).toMatchObject({ - headers: expect.any(Object), - method: 'GET', - }); - - expect(errorEvent.contexts?.nextjs).toEqual({ - route_type: 'render', - router_kind: 'App Router', - router_path: '/nested-rsc-error/[param]', - request_path: '/nested-rsc-error/123', - }); - - expect(errorEvent.exception?.values?.[0]?.mechanism).toEqual({ - handled: false, - type: 'auto.function.nextjs.on_request_error', - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/pageload-tracing.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/pageload-tracing.test.ts deleted file mode 100644 index 89ac76eadc1c..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/pageload-tracing.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; - -test('App router spans should be attached to the pageload request span', async ({ page }) => { - const serverSpanPromise = waitForStreamedSpan('nextjs-16-bun', span => { - return span.name === 'GET /pageload-tracing' && span.is_segment; - }); - - const pageloadSpanPromise = waitForStreamedSpan('nextjs-16-bun', span => { - return span.name === '/pageload-tracing' && getSpanOp(span) === 'pageload' && span.is_segment; - }); - - await page.goto(`/pageload-tracing`); - - const [serverSpan, pageloadSpan] = await Promise.all([serverSpanPromise, pageloadSpanPromise]); - - expect(pageloadSpan.trace_id).toBeTruthy(); - expect(serverSpan.trace_id).toBe(pageloadSpan.trace_id); -}); - -// Bun runtime does not populate HTTP request headers as span attributes -// because the OTel HTTP instrumentation does not extract headers when running on Bun. -// This is a known behavioral difference from Node.js. diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/parameterized-routes.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/parameterized-routes.test.ts deleted file mode 100644 index 27afcc6c3cf3..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/parameterized-routes.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; - -test('should create a parameterized pageload span when the `app` directory is used', async ({ page }) => { - const spanPromise = waitForStreamedSpan('nextjs-16-bun', span => { - return span.name === '/parameterized/:one' && getSpanOp(span) === 'pageload' && span.is_segment; - }); - - await page.goto(`/parameterized/cappuccino`); - - const span = await spanPromise; - - expect(span.span_id).toEqual(expect.stringMatching(/[a-f0-9]{16}/)); - expect(span.trace_id).toEqual(expect.stringMatching(/[a-f0-9]{32}/)); - expect(span.attributes).toMatchObject({ - 'sentry.op': { value: 'pageload', type: 'string' }, - 'sentry.origin': { value: 'auto.pageload.nextjs.app_router_instrumentation', type: 'string' }, - 'sentry.segment.name.source': { value: 'route', type: 'string' }, - 'sentry.environment': { value: 'qa', type: 'string' }, - 'react.version': { value: expect.any(String), type: 'string' }, - }); -}); - -test('should create a span named after the static route when the `app` directory is used', async ({ page }) => { - const spanPromise = waitForStreamedSpan('nextjs-16-bun', span => { - return span.name === '/parameterized/static' && getSpanOp(span) === 'pageload' && span.is_segment; - }); - - await page.goto(`/parameterized/static`); - - const span = await spanPromise; - - expect(span.span_id).toEqual(expect.stringMatching(/[a-f0-9]{16}/)); - expect(span.trace_id).toEqual(expect.stringMatching(/[a-f0-9]{32}/)); - expect(span.attributes).toMatchObject({ - 'sentry.op': { value: 'pageload', type: 'string' }, - 'sentry.origin': { value: 'auto.pageload.nextjs.app_router_instrumentation', type: 'string' }, - 'sentry.segment.name.source': { value: 'route', type: 'string' }, - 'sentry.environment': { value: 'qa', type: 'string' }, - 'react.version': { value: expect.any(String), type: 'string' }, - 'url.template': { value: '/parameterized/static', type: 'string' }, - }); -}); - -test('should create a partially parameterized pageload span when the `app` directory is used', async ({ page }) => { - const spanPromise = waitForStreamedSpan('nextjs-16-bun', span => { - return span.name === '/parameterized/:one/beep' && getSpanOp(span) === 'pageload' && span.is_segment; - }); - - await page.goto(`/parameterized/cappuccino/beep`); - - const span = await spanPromise; - - expect(span.span_id).toEqual(expect.stringMatching(/[a-f0-9]{16}/)); - expect(span.trace_id).toEqual(expect.stringMatching(/[a-f0-9]{32}/)); - expect(span.attributes).toMatchObject({ - 'sentry.op': { value: 'pageload', type: 'string' }, - 'sentry.origin': { value: 'auto.pageload.nextjs.app_router_instrumentation', type: 'string' }, - 'sentry.segment.name.source': { value: 'route', type: 'string' }, - 'sentry.environment': { value: 'qa', type: 'string' }, - 'react.version': { value: expect.any(String), type: 'string' }, - }); -}); - -test('should create a nested parameterized pageload span when the `app` directory is used', async ({ page }) => { - const spanPromise = waitForStreamedSpan('nextjs-16-bun', span => { - return span.name === '/parameterized/:one/beep/:two' && getSpanOp(span) === 'pageload' && span.is_segment; - }); - - await page.goto(`/parameterized/cappuccino/beep/espresso`); - - const span = await spanPromise; - - expect(span.span_id).toEqual(expect.stringMatching(/[a-f0-9]{16}/)); - expect(span.trace_id).toEqual(expect.stringMatching(/[a-f0-9]{32}/)); - expect(span.attributes).toMatchObject({ - 'sentry.op': { value: 'pageload', type: 'string' }, - 'sentry.origin': { value: 'auto.pageload.nextjs.app_router_instrumentation', type: 'string' }, - 'sentry.segment.name.source': { value: 'route', type: 'string' }, - 'sentry.environment': { value: 'qa', type: 'string' }, - 'react.version': { value: expect.any(String), type: 'string' }, - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/route-handler.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/route-handler.test.ts deleted file mode 100644 index da51193cb345..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/route-handler.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import test, { expect } from '@playwright/test'; -import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; - -test('Should create a span for node route handlers', async ({ request }) => { - const routehandlerSpanPromise = waitForStreamedSpan('nextjs-16-bun', span => { - return span.name === 'GET /route-handler/[xoxo]/node' && span.is_segment; - }); - - const response = await request.get('/route-handler/123/node', { headers: { 'x-charly': 'gomez' } }); - expect(await response.json()).toStrictEqual({ message: 'Hello Node Route Handler' }); - - const routehandlerSpan = await routehandlerSpanPromise; - - expect(routehandlerSpan.status).toBe('ok'); - expect(getSpanOp(routehandlerSpan)).toBe('http.server'); -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/server-components.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/server-components.test.ts deleted file mode 100644 index c674845c4234..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/server-components.test.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { collectStreamedSpansUntilSegment, getSpanOp } from '@sentry-internal/test-utils'; - -// Next.js emits these spans itself. The SDK attaches no op, description or function name to -// them, so asserting `undefined` pins that they stay untouched. -const nextjsSpan = { op: undefined, description: undefined, codeFunctionName: undefined }; - -test('Sends a span for a request to app router with URL', async ({ page }) => { - const spansPromise = collectStreamedSpansUntilSegment( - 'nextjs-16-bun', - span => - span.name === 'GET /parameterized/[one]/beep/[two]' && - String(span.attributes['http.target']?.value).startsWith('/parameterized/1337/beep/42'), - ); - - await page.goto('/parameterized/1337/beep/42'); - - const spans = await spansPromise; - const segmentSpan = spans.find( - span => - span.name === 'GET /parameterized/[one]/beep/[two]' && - span.is_segment && - String(span.attributes['http.target']?.value).startsWith('/parameterized/1337/beep/42'), - )!; - - expect(segmentSpan.span_id).toEqual(expect.stringMatching(/[a-f0-9]{16}/)); - expect(segmentSpan.trace_id).toEqual(expect.stringMatching(/[a-f0-9]{32}/)); - expect(segmentSpan.status).toBe('ok'); - expect(segmentSpan.attributes).toMatchObject({ - 'sentry.op': { value: 'http.server', type: 'string' }, - 'sentry.origin': { value: 'auto', type: 'string' }, - 'sentry.sample_rate': { value: 1, type: 'integer' }, - 'sentry.segment.name.source': { value: 'route', type: 'string' }, - 'http.method': { value: 'GET', type: 'string' }, - 'http.response.status_code': { value: 200, type: 'integer' }, - 'http.route': { value: '/parameterized/[one]/beep/[two]', type: 'string' }, - 'http.status_code': { value: 200, type: 'integer' }, - 'http.target': { value: '/parameterized/1337/beep/42', type: 'string' }, - 'sentry.kind': { value: 'server', type: 'string' }, - 'next.route': { value: '/parameterized/[one]/beep/[two]', type: 'string' }, - }); - - // No child span should share the segment span's name - expect(spans.filter(span => !span.is_segment && span.name === segmentSpan.name)).toHaveLength(0); -}); - -test('Will create spans for every server component and metadata generation functions when visiting a page', async ({ - page, -}) => { - const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-bun', 'GET /nested-layout'); - - await page.goto('/nested-layout'); - - const fullSpans = await spansPromise; - const spans = fullSpans.map(span => ({ - name: span.name, - op: getSpanOp(span), - description: span.attributes['sentry.description']?.value, - codeFunctionName: span.attributes['code.function.name']?.value, - })); - - expect(spans).toContainEqual({ ...nextjsSpan, name: 'render route (app) /nested-layout' }); - expect(spans).toContainEqual({ ...nextjsSpan, name: 'build component tree' }); - // Server component spans: the name is the low-cardinality `code.function.name`, and the - // segment each one resolved for is on the description. - expect(spans).toContainEqual({ - name: 'Layout', - op: 'function', - description: 'resolve root layout server component', - codeFunctionName: 'Layout', - }); - expect(spans).toContainEqual({ - name: 'Layout', - op: 'function', - description: 'resolve layout server component "(nested-layout)"', - codeFunctionName: 'Layout', - }); - expect(spans).toContainEqual({ - name: 'Layout', - op: 'function', - description: 'resolve layout server component "nested-layout"', - codeFunctionName: 'Layout', - }); - expect(spans).toContainEqual({ - name: 'Page', - op: 'function', - description: 'resolve page server component "/nested-layout"', - codeFunctionName: 'Page', - }); - expect(spans).toContainEqual({ ...nextjsSpan, name: 'generateMetadata /(nested-layout)/nested-layout/page' }); - expect(spans).toContainEqual({ ...nextjsSpan, name: 'start response' }); -}); - -test('Will create spans for every server component and metadata generation functions when visiting a dynamic page', async ({ - page, -}) => { - const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-bun', 'GET /nested-layout/[dynamic]'); - - await page.goto('/nested-layout/123'); - - const fullSpans = await spansPromise; - const spans = fullSpans.map(span => ({ - name: span.name, - op: getSpanOp(span), - description: span.attributes['sentry.description']?.value, - codeFunctionName: span.attributes['code.function.name']?.value, - })); - - expect(spans).toContainEqual({ ...nextjsSpan, name: 'resolve page components' }); - expect(spans).toContainEqual({ ...nextjsSpan, name: 'render route (app) /nested-layout/[dynamic]' }); - expect(spans).toContainEqual({ ...nextjsSpan, name: 'build component tree' }); - // Server component spans: the name is the low-cardinality `code.function.name`, and the - // segment each one resolved for is on the description. - expect(spans).toContainEqual({ - name: 'Layout', - op: 'function', - description: 'resolve root layout server component', - codeFunctionName: 'Layout', - }); - expect(spans).toContainEqual({ - name: 'Layout', - op: 'function', - description: 'resolve layout server component "(nested-layout)"', - codeFunctionName: 'Layout', - }); - expect(spans).toContainEqual({ - name: 'Layout', - op: 'function', - description: 'resolve layout server component "nested-layout"', - codeFunctionName: 'Layout', - }); - expect(spans).toContainEqual({ - name: 'Layout', - op: 'function', - description: 'resolve layout server component "[dynamic]"', - codeFunctionName: 'Layout', - }); - expect(spans).toContainEqual({ - name: 'Page', - op: 'function', - description: 'resolve page server component "/nested-layout/[dynamic]"', - codeFunctionName: 'Page', - }); - expect(spans).toContainEqual({ - ...nextjsSpan, - name: 'generateMetadata /(nested-layout)/nested-layout/[dynamic]/page', - }); - expect(spans).toContainEqual({ ...nextjsSpan, name: 'start response' }); -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/streaming-rsc-error.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/streaming-rsc-error.test.ts deleted file mode 100644 index 7b5482d4d8e2..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/streaming-rsc-error.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils'; - -test('Should capture errors for crashing streaming promises in server components when `Sentry.captureRequestError` is added to the `onRequestError` hook', async ({ - page, -}) => { - const errorEventPromise = waitForError('nextjs-16-bun', errorEvent => { - return !!errorEvent?.exception?.values?.some(value => value.value === 'I am a data streaming error'); - }); - - // Matched on the error's own trace so a span from an earlier spec cannot satisfy the correlation. - const serverSpanPromise = waitForStreamedSpan('nextjs-16-bun', async span => { - return ( - span.name === 'GET /streaming-rsc-error/[param]' && - span.is_segment && - (await errorEventPromise).contexts?.trace?.trace_id === span.trace_id - ); - }); - - // The streaming RSC error can interrupt the HTTP response, causing the navigation to reject - // (e.g. net::ERR_ABORTED) even though the error and transaction are still captured. - await page.goto(`/streaming-rsc-error/123`).catch(() => {}); - const errorEvent = await errorEventPromise; - const serverSpan = await serverSpanPromise; - - // error event is part of the same trace as the server span - expect(errorEvent.contexts?.trace?.trace_id).toBe(serverSpan.trace_id); - - expect(errorEvent.request).toMatchObject({ - headers: expect.any(Object), - method: 'GET', - }); - - expect(errorEvent.contexts?.nextjs).toEqual({ - route_type: 'render', - router_kind: 'App Router', - router_path: '/streaming-rsc-error/[param]', - request_path: '/streaming-rsc-error/123', - }); - - expect(errorEvent.exception?.values?.[0]?.mechanism).toEqual({ - handled: false, - type: 'auto.function.nextjs.on_request_error', - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/third-party-filter.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/third-party-filter.test.ts deleted file mode 100644 index 4de1433597b5..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/third-party-filter.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import test, { expect } from '@playwright/test'; -import { waitForError } from '@sentry-internal/test-utils'; - -const isWebpackDev = process.env.TEST_ENV === 'development-webpack'; - -test('First-party error should not be tagged as third-party with exclusively-contains mode', async ({ page }) => { - test.skip(isWebpackDev, 'Only relevant for Turbopack builds'); - - const errorPromise = waitForError('nextjs-16-bun', errorEvent => { - return errorEvent?.exception?.values?.some(value => value.value === 'first-party-error') ?? false; - }); - - await page.goto('/third-party-filter'); - await page.locator('#first-party-error-btn').click(); - - const errorEvent = await errorPromise; - - expect(errorEvent.exception?.values?.[0]?.value).toBe('first-party-error'); - - // In production, TEST_ENV=production is shared by both turbopack and webpack variants. - // Only assert when the build is actually turbopack. - if (errorEvent.tags?.turbopack) { - // The integration uses `apply-tag-if-exclusively-contains-third-party-frames` which - // only tags errors if ALL frames are third-party. A first-party error with React frames - // should not be tagged because the first-party frames have metadata. - expect(errorEvent.tags?.third_party_code).toBeUndefined(); - } -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tsconfig.json b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tsconfig.json deleted file mode 100644 index cc9ed39b5aa2..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tsconfig.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2017", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "react-jsx", - "incremental": true, - "plugins": [ - { - "name": "next" - } - ], - "paths": { - "@/*": ["./*"] - } - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts", "**/*.mts"], - "exclude": ["node_modules"] -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/.gitignore b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/.gitignore deleted file mode 100644 index dd146b53d966..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/.gitignore +++ /dev/null @@ -1,44 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/versions - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - -# env files (can opt-in for committing if needed) -.env* - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts - -# Sentry Config File -.env.sentry-build-plugin diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/layout.tsx deleted file mode 100644 index ace0c2f086b7..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/layout.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { PropsWithChildren } from 'react'; - -export const dynamic = 'force-dynamic'; - -export default function Layout({ children }: PropsWithChildren<{}>) { - return ( -
-

Layout

- {children} -
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/[dynamic]/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/[dynamic]/layout.tsx deleted file mode 100644 index dbdc60adadc2..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/[dynamic]/layout.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { PropsWithChildren } from 'react'; - -export const dynamic = 'force-dynamic'; - -export default function Layout({ children }: PropsWithChildren<{}>) { - return ( -
-

DynamicLayout

- {children} -
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/[dynamic]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/[dynamic]/page.tsx deleted file mode 100644 index 3eaddda2a1df..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/[dynamic]/page.tsx +++ /dev/null @@ -1,15 +0,0 @@ -export const dynamic = 'force-dynamic'; - -export default async function Page() { - return ( -
-

Dynamic Page

-
- ); -} - -export async function generateMetadata() { - return { - title: 'I am dynamic page generated metadata', - }; -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/layout.tsx deleted file mode 100644 index ace0c2f086b7..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/layout.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { PropsWithChildren } from 'react'; - -export const dynamic = 'force-dynamic'; - -export default function Layout({ children }: PropsWithChildren<{}>) { - return ( -
-

Layout

- {children} -
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/page.tsx deleted file mode 100644 index 8077c14d23ca..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/(nested-layout)/nested-layout/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -export const dynamic = 'force-dynamic'; - -export default function Page() { - return

Hello World!

; -} - -export async function generateMetadata() { - return { - title: 'I am generated metadata', - }; -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/endpoint-behind-middleware/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/endpoint-behind-middleware/route.ts deleted file mode 100644 index 2733cc918f44..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/endpoint-behind-middleware/route.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function GET() { - return Response.json({ name: 'John Doe' }); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/test-success/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/test-success/route.ts deleted file mode 100644 index 26d85db4ac28..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/test-success/route.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { NextResponse } from 'next/server'; - -export const dynamic = 'force-dynamic'; - -export async function GET() { - return NextResponse.json({ success: true }); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/favicon.ico b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/favicon.ico deleted file mode 100644 index 718d6fea4835ec2d246af9800eddb7ffb276240c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25931 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 LrJugUA?W`A8`#=m diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/global-error.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/global-error.tsx deleted file mode 100644 index 20c175015b03..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/global-error.tsx +++ /dev/null @@ -1,23 +0,0 @@ -'use client'; - -import * as Sentry from '@sentry/nextjs'; -import NextError from 'next/error'; -import { useEffect } from 'react'; - -export default function GlobalError({ error }: { error: Error & { digest?: string } }) { - useEffect(() => { - Sentry.captureException(error); - }, [error]); - - return ( - - - {/* `NextError` is the default Next.js error page component. Its type - definition requires a `statusCode` prop. However, since the App Router - does not expose status codes for errors, we simply pass 0 to render a - generic error message. */} - - - - ); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/isr-test/[product]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/isr-test/[product]/page.tsx deleted file mode 100644 index cd1e085e2763..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/isr-test/[product]/page.tsx +++ /dev/null @@ -1,17 +0,0 @@ -export const revalidate = 60; // ISR: revalidate every 60 seconds -export const dynamicParams = true; // Allow dynamic params beyond generateStaticParams - -export async function generateStaticParams(): Promise> { - return [{ product: 'laptop' }, { product: 'phone' }, { product: 'tablet' }]; -} - -export default async function ISRProductPage({ params }: { params: Promise<{ product: string }> }) { - const { product } = await params; - - return ( -
-

ISR Product: {product}

-
{product}
-
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/isr-test/static/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/isr-test/static/page.tsx deleted file mode 100644 index f49605bd9da4..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/isr-test/static/page.tsx +++ /dev/null @@ -1,15 +0,0 @@ -export const revalidate = 60; // ISR: revalidate every 60 seconds -export const dynamicParams = true; - -export async function generateStaticParams(): Promise { - return []; -} - -export default function ISRStaticPage() { - return ( -
-

ISR Static Page

-
static-isr
-
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/layout.tsx deleted file mode 100644 index c8f9cee0b787..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/layout.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export default function Layout({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/metrics/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/metrics/page.tsx deleted file mode 100644 index fdb7bc0a40a7..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/metrics/page.tsx +++ /dev/null @@ -1,34 +0,0 @@ -'use client'; - -import * as Sentry from '@sentry/nextjs'; - -export default function Page() { - const handleClick = async () => { - Sentry.metrics.count('test.page.count', 1, { - attributes: { - page: '/metrics', - 'random.attribute': 'Apples', - }, - }); - Sentry.metrics.distribution('test.page.distribution', 100, { - attributes: { - page: '/metrics', - 'random.attribute': 'Manzanas', - }, - }); - Sentry.metrics.gauge('test.page.gauge', 200, { - attributes: { - page: '/metrics', - 'random.attribute': 'Mele', - }, - }); - await fetch('/metrics/route-handler'); - }; - - return ( -
-

Metrics page

- -
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/metrics/route-handler/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/metrics/route-handler/route.ts deleted file mode 100644 index 84e81960f9c9..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/metrics/route-handler/route.ts +++ /dev/null @@ -1,23 +0,0 @@ -import * as Sentry from '@sentry/nextjs'; - -export const GET = async () => { - Sentry.metrics.count('test.route.handler.count', 1, { - attributes: { - endpoint: '/metrics/route-handler', - 'random.attribute': 'Potatoes', - }, - }); - Sentry.metrics.distribution('test.route.handler.distribution', 100, { - attributes: { - endpoint: '/metrics/route-handler', - 'random.attribute': 'Patatas', - }, - }); - Sentry.metrics.gauge('test.route.handler.gauge', 200, { - attributes: { - endpoint: '/metrics/route-handler', - 'random.attribute': 'Patate', - }, - }); - return Response.json({ message: 'Bueno' }); -}; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/nested-rsc-error/[param]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/nested-rsc-error/[param]/page.tsx deleted file mode 100644 index 675b248026be..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/nested-rsc-error/[param]/page.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Suspense } from 'react'; - -export const dynamic = 'force-dynamic'; - -export default async function Page() { - return ( - Loading...

}> - {/* @ts-ignore */} - ; -
- ); -} - -async function Crash() { - throw new Error('I am technically uncatchable'); - return

unreachable

; -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/non-isr-test/[item]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/non-isr-test/[item]/page.tsx deleted file mode 100644 index e0bafdb24181..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/non-isr-test/[item]/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// No generateStaticParams - this is NOT an ISR page -export default async function NonISRPage({ params }: { params: Promise<{ item: string }> }) { - const { item } = await params; - - return ( -
-

Non-ISR Dynamic Page: {item}

-
{item}
-
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/page.tsx deleted file mode 100644 index 2bc0a407a355..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Page() { - return

Next 16 test app

; -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/pageload-tracing/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/pageload-tracing/layout.tsx deleted file mode 100644 index 1f0cbe478f88..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/pageload-tracing/layout.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { PropsWithChildren } from 'react'; - -export const dynamic = 'force-dynamic'; - -export default async function Layout({ children }: PropsWithChildren) { - await new Promise(resolve => setTimeout(resolve, 500)); - return <>{children}; -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/pageload-tracing/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/pageload-tracing/page.tsx deleted file mode 100644 index 689735d61ddf..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/pageload-tracing/page.tsx +++ /dev/null @@ -1,14 +0,0 @@ -export const dynamic = 'force-dynamic'; - -export default async function Page() { - await new Promise(resolve => setTimeout(resolve, 1000)); - return

I am page 2

; -} - -export async function generateMetadata() { - (await fetch('https://example.com/', { cache: 'no-store' })).text(); - - return { - title: 'my title', - }; -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/[one]/beep/[two]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/[one]/beep/[two]/page.tsx deleted file mode 100644 index f34461c2bb07..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/[one]/beep/[two]/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function ParameterizedPage() { - return
Dynamic page two
; -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/[one]/beep/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/[one]/beep/page.tsx deleted file mode 100644 index a7d9164c8c03..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/[one]/beep/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function BeepPage() { - return
Beep
; -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/[one]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/[one]/page.tsx deleted file mode 100644 index 9fa617a22381..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/[one]/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function ParameterizedPage() { - return
Dynamic page one
; -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/static/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/static/page.tsx deleted file mode 100644 index 16ef0482d53b..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/parameterized/static/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function StaticPage() { - return
Static page
; -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/prefetching/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/prefetching/page.tsx deleted file mode 100644 index 4cb811ecf1b4..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/prefetching/page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import Link from 'next/link'; - -export default function Page() { - return ( - - link - - ); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/prefetching/to-be-prefetched/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/prefetching/to-be-prefetched/page.tsx deleted file mode 100644 index 83aac90d65cf..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/prefetching/to-be-prefetched/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -export const dynamic = 'force-dynamic'; - -export default function Page() { - return

Hello

; -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/redirect/destination/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/redirect/destination/page.tsx deleted file mode 100644 index 5583d36b04b0..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/redirect/destination/page.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export default function RedirectDestinationPage() { - return ( -
-

Redirect Destination

-
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/redirect/origin/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/redirect/origin/page.tsx deleted file mode 100644 index 52615e0a054b..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/redirect/origin/page.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { redirect } from 'next/navigation'; - -async function redirectAction() { - 'use server'; - - redirect('/redirect/destination'); -} - -export default function RedirectOriginPage() { - return ( - <> - {/* @ts-ignore */} -
- -
- - ); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/route-handler/[xoxo]/edge/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/route-handler/[xoxo]/edge/route.ts deleted file mode 100644 index 7cd1fc7e332c..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/route-handler/[xoxo]/edge/route.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { NextResponse } from 'next/server'; - -export const runtime = 'edge'; -export const dynamic = 'force-dynamic'; - -export async function GET() { - return NextResponse.json({ message: 'Hello Edge Route Handler' }); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/route-handler/[xoxo]/node/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/route-handler/[xoxo]/node/route.ts deleted file mode 100644 index 5bc418f077aa..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/route-handler/[xoxo]/node/route.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { NextResponse } from 'next/server'; - -export const dynamic = 'force-dynamic'; - -export async function GET() { - return NextResponse.json({ message: 'Hello Node Route Handler' }); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/streaming-rsc-error/[param]/client-page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/streaming-rsc-error/[param]/client-page.tsx deleted file mode 100644 index 7b66c3fbdeef..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/streaming-rsc-error/[param]/client-page.tsx +++ /dev/null @@ -1,8 +0,0 @@ -'use client'; - -import { use } from 'react'; - -export function RenderPromise({ stringPromise }: { stringPromise: Promise }) { - const s = use(stringPromise); - return <>{s}; -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/streaming-rsc-error/[param]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/streaming-rsc-error/[param]/page.tsx deleted file mode 100644 index 9531f9a42139..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/streaming-rsc-error/[param]/page.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Suspense } from 'react'; -import { RenderPromise } from './client-page'; - -export const dynamic = 'force-dynamic'; - -export default async function Page() { - const crashingPromise = new Promise((_, reject) => { - setTimeout(() => { - reject(new Error('I am a data streaming error')); - }, 100); - }); - - return ( - Loading...

}> - ; -
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/eslint.config.mjs b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/eslint.config.mjs deleted file mode 100644 index 60f7af38f6c2..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/eslint.config.mjs +++ /dev/null @@ -1,19 +0,0 @@ -import { dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { FlatCompat } from '@eslint/eslintrc'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const compat = new FlatCompat({ - baseDirectory: __dirname, -}); - -const eslintConfig = [ - ...compat.extends('next/core-web-vitals', 'next/typescript'), - { - ignores: ['node_modules/**', '.next/**', 'out/**', 'build/**', 'next-env.d.ts'], - }, -]; - -export default eslintConfig; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/instrumentation-client.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/instrumentation-client.ts deleted file mode 100644 index a170a388c465..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/instrumentation-client.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as Sentry from '@sentry/nextjs'; -import type { Log } from '@sentry/nextjs'; - -Sentry.init({ - environment: 'qa', // dynamic sampling bias to keep transactions - dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, - tunnel: `http://localhost:3031/`, // proxy server - tracesSampleRate: 1.0, - // Verify Log type is available - beforeSendLog(log: Log) { - return log; - }, -}); - -export const onRouterTransitionStart = Sentry.captureRouterTransitionStart; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/instrumentation.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/instrumentation.ts deleted file mode 100644 index 964f937c439a..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/instrumentation.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as Sentry from '@sentry/nextjs'; - -export async function register() { - if (process.env.NEXT_RUNTIME === 'nodejs') { - await import('./sentry.server.config'); - } - - if (process.env.NEXT_RUNTIME === 'edge') { - await import('./sentry.edge.config'); - } -} - -export const onRequestError = Sentry.captureRequestError; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/middleware.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/middleware.ts deleted file mode 100644 index a6d31cc8c66f..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/middleware.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { getDefaultIsolationScope } from '@sentry/core'; -import * as Sentry from '@sentry/nextjs'; -import { NextResponse } from 'next/server'; -import type { NextRequest } from 'next/server'; - -export async function middleware(request: NextRequest) { - Sentry.setTag('my-isolated-tag', true); - Sentry.setTag('my-global-scope-isolated-tag', getDefaultIsolationScope().getScopeData().tags['my-isolated-tag']); // We set this tag to be able to assert that the previously set tag has not leaked into the global isolation scope - - // Streamed spans carry no scope tags, so the tests read the isolation state from this attribute instead - const activeSpan = Sentry.getActiveSpan(); - if (activeSpan) { - Sentry.getRootSpan(activeSpan).setAttribute( - 'isolation_scope.is_default', - Sentry.getIsolationScope() === getDefaultIsolationScope(), - ); - } - - if (request.headers.has('x-should-throw')) { - throw new Error('Middleware Error'); - } - - if (request.headers.has('x-should-make-request')) { - await fetch('http://localhost:3030/'); - } - - return NextResponse.next(); -} - -// See "Matching Paths" below to learn more -export const config = { - matcher: ['/api/endpoint-behind-middleware', '/api/endpoint-behind-faulty-middleware'], -}; - -export const runtime = 'experimental-edge'; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/next.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/next.config.ts deleted file mode 100644 index 28162396e80e..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/next.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { withSentryConfig } from '@sentry/nextjs/config'; -import type { NextConfig } from 'next'; - -const nextConfig: NextConfig = {}; - -export default withSentryConfig(nextConfig, { - silent: true, -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/package.json b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/package.json deleted file mode 100644 index 0a5cbe178693..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/package.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "name": "nextjs-16-cf-workers", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "next dev", - "cf:build": "opennextjs-cloudflare build", - "cf:preview": "opennextjs-cloudflare preview --var \"E2E_TEST_DSN:$E2E_TEST_DSN\"", - "build": "next build", - "clean": "npx rimraf node_modules pnpm-lock.yaml .tmp_dev_server_logs", - "start": "pnpm cf:preview", - "lint": "eslint", - "test:prod": "TEST_ENV=production playwright test", - "test:build": "pnpm install && pnpm cf:build", - "test:build-canary": "pnpm install && pnpm add next@canary && pnpm cf:build", - "test:build-latest": "pnpm install && pnpm add next@latest && pnpm cf:build", - "test:assert": "pnpm test:prod" - }, - "dependencies": { - "@opentelemetry/api": "^1.9.1", - "@opennextjs/cloudflare": "^1.14.9", - "@sentry/nextjs": "file:../../packed/sentry-nextjs-packed.tgz", - "@sentry/core": "file:../../packed/sentry-core-packed.tgz", - "next": "16.2.11", - "react": "19.1.0", - "react-dom": "19.1.0" - }, - "devDependencies": { - "@playwright/test": "~1.63.0", - "@sentry-internal/test-utils": "link:../../../test-utils", - "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", - "eslint": "^9", - "eslint-config-next": "^16", - "typescript": "^5", - "wrangler": "^4.61.0" - }, - "volta": { - "node": "24.15.0", - "extends": "../../package.json" - }, - "sentryTest": { - "variants": [ - { - "build-command": "pnpm test:build-latest", - "label": "nextjs-16-cf-workers (latest)" - } - ] - } -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/playwright.config.mjs deleted file mode 100644 index 0f15639161dd..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/playwright.config.mjs +++ /dev/null @@ -1,21 +0,0 @@ -import { getPlaywrightConfig } from '@sentry-internal/test-utils'; -const testEnv = process.env.TEST_ENV; - -if (!testEnv) { - throw new Error('No test env defined'); -} - -const getStartCommand = () => { - if (testEnv === 'production') { - return 'pnpm cf:preview --port 3030'; - } - - throw new Error(`Unknown test env: ${testEnv}`); -}; - -const config = getPlaywrightConfig({ - startCommand: getStartCommand(), - port: 3030, -}); - -export default config; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/file.svg b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/file.svg deleted file mode 100644 index 004145cddf3f..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/file.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/globe.svg b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/globe.svg deleted file mode 100644 index 567f17b0d7c7..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/globe.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/next.svg b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/next.svg deleted file mode 100644 index 5174b28c565c..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/vercel.svg b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/vercel.svg deleted file mode 100644 index 77053960334e..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/window.svg b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/window.svg deleted file mode 100644 index b2b2a44f6ebc..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/public/window.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/sentry.edge.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/sentry.edge.config.ts deleted file mode 100644 index eceebb29d45d..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/sentry.edge.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import * as Sentry from '@sentry/nextjs'; - -// Only hands over the build release: `withSentry` from `@sentry/nextjs/cloudflare` in worker.ts creates the client and -// holds the options. -Sentry.init({ - dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/sentry.server.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/sentry.server.config.ts deleted file mode 100644 index 1009ee9e1f39..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/sentry.server.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import * as Sentry from '@sentry/nextjs'; - -// Only hands over the build release and adds the global Next.js event processors and the turbopack tag: `withSentry` -// from `@sentry/nextjs/cloudflare` in worker.ts creates the client and holds the options. -Sentry.init({ - dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/start-event-proxy.mjs deleted file mode 100644 index efb664370443..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/start-event-proxy.mjs +++ /dev/null @@ -1,14 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import { startEventProxyServer } from '@sentry-internal/test-utils'; - -const packageJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'))); - -startEventProxyServer({ - port: 3031, - proxyServerName: 'nextjs-16-cf-workers', - envelopeDumpPath: path.join( - process.cwd(), - `event-dumps/next-16-v${packageJson.dependencies.next}-${process.env.TEST_ENV}.dump`, - ), -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/async-params.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/async-params.test.ts deleted file mode 100644 index e8160d12aded..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/async-params.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { expect, test } from '@playwright/test'; -import fs from 'fs'; -import { isDevMode } from './isDevMode'; - -test('should not print warning for async params', async ({ page }) => { - test.skip(!isDevMode, 'should be skipped for non-dev mode'); - await page.goto('/'); - - // If the server exits with code 1, the test will fail (see instrumentation.ts) - const devStdout = fs.readFileSync('.tmp_dev_server_logs', 'utf-8'); - expect(devStdout).not.toContain('`params` should be awaited before using its properties.'); - - await expect(page.getByText('Next 16 test app')).toBeVisible(); -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/isDevMode.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/isDevMode.ts deleted file mode 100644 index d2be94232110..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/isDevMode.ts +++ /dev/null @@ -1 +0,0 @@ -export const isDevMode = !!process.env.TEST_ENV && process.env.TEST_ENV.includes('development'); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/isr-routes.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/isr-routes.test.ts deleted file mode 100644 index fe61a6263913..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/isr-routes.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; - -test('should remove sentry-trace and baggage meta tags on ISR dynamic route page load', async ({ page }) => { - // Navigate to ISR page - await page.goto('/isr-test/laptop'); - - // Wait for page to be fully loaded - await expect(page.locator('#isr-product-id')).toHaveText('laptop'); - - // Check that sentry-trace and baggage meta tags are removed for ISR pages - await expect(page.locator('meta[name="sentry-trace"]')).toHaveCount(0); - await expect(page.locator('meta[name="baggage"]')).toHaveCount(0); -}); - -test('should remove sentry-trace and baggage meta tags on ISR static route', async ({ page }) => { - // Navigate to ISR static page - await page.goto('/isr-test/static'); - - // Wait for page to be fully loaded - await expect(page.locator('#isr-static-marker')).toHaveText('static-isr'); - - // Check that sentry-trace and baggage meta tags are removed for ISR pages - await expect(page.locator('meta[name="sentry-trace"]')).toHaveCount(0); - await expect(page.locator('meta[name="baggage"]')).toHaveCount(0); -}); - -test('should remove meta tags for different ISR dynamic route values', async ({ page }) => { - // Test with 'phone' (one of the pre-generated static params) - await page.goto('/isr-test/phone'); - await expect(page.locator('#isr-product-id')).toHaveText('phone'); - - await expect(page.locator('meta[name="sentry-trace"]')).toHaveCount(0); - await expect(page.locator('meta[name="baggage"]')).toHaveCount(0); - - // Test with 'tablet' - await page.goto('/isr-test/tablet'); - await expect(page.locator('#isr-product-id')).toHaveText('tablet'); - - await expect(page.locator('meta[name="sentry-trace"]')).toHaveCount(0); - await expect(page.locator('meta[name="baggage"]')).toHaveCount(0); -}); - -test('should create unique traces for ISR pages on each visit', async ({ page }) => { - const traceIds: string[] = []; - - // Load the same ISR page 5 times to ensure cached HTML meta tags are consistently removed - for (let i = 0; i < 5; i++) { - const spanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => { - return span.name === '/isr-test/:product' && getSpanOp(span) === 'pageload' && span.is_segment; - }); - - if (i === 0) { - await page.goto('/isr-test/laptop'); - } else { - await page.reload(); - } - - const span = await spanPromise; - const traceId = span.trace_id; - - expect(traceId).toBeDefined(); - expect(traceId).toMatch(/[a-f0-9]{32}/); - traceIds.push(traceId!); - } - - // Verify all 5 page loads have unique trace IDs (no reuse of cached/stale meta tags) - const uniqueTraceIds = new Set(traceIds); - expect(uniqueTraceIds.size).toBe(5); -}); - -test('ISR route should be identified correctly in the route manifest', async ({ page }) => { - const spanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => { - return span.name === '/isr-test/:product' && getSpanOp(span) === 'pageload' && span.is_segment; - }); - - await page.goto('/isr-test/laptop'); - const span = await spanPromise; - - // Verify the span is properly parameterized - expect(span.name).toBe('/isr-test/:product'); - expect(span.attributes['sentry.segment.name.source']?.value).toBe('route'); -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/metrics.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/metrics.test.ts deleted file mode 100644 index 1ccbe00953de..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/metrics.test.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForMetric } from '@sentry-internal/test-utils'; - -test('Should emit metrics from server and client', async ({ request, page }) => { - const clientCountPromise = waitForMetric('nextjs-16-cf-workers', async metric => { - return metric.name === 'test.page.count'; - }); - - const clientDistributionPromise = waitForMetric('nextjs-16-cf-workers', async metric => { - return metric.name === 'test.page.distribution'; - }); - - const clientGaugePromise = waitForMetric('nextjs-16-cf-workers', async metric => { - return metric.name === 'test.page.gauge'; - }); - - const serverCountPromise = waitForMetric('nextjs-16-cf-workers', async metric => { - return metric.name === 'test.route.handler.count'; - }); - - const serverDistributionPromise = waitForMetric('nextjs-16-cf-workers', async metric => { - return metric.name === 'test.route.handler.distribution'; - }); - - const serverGaugePromise = waitForMetric('nextjs-16-cf-workers', async metric => { - return metric.name === 'test.route.handler.gauge'; - }); - - await page.goto('/metrics'); - await page.getByText('Emit').click(); - const clientCount = await clientCountPromise; - const clientDistribution = await clientDistributionPromise; - const clientGauge = await clientGaugePromise; - const serverCount = await serverCountPromise; - const serverDistribution = await serverDistributionPromise; - const serverGauge = await serverGaugePromise; - - expect(clientCount).toMatchObject({ - timestamp: expect.any(Number), - trace_id: expect.any(String), - span_id: expect.any(String), - name: 'test.page.count', - type: 'counter', - value: 1, - attributes: { - page: { value: '/metrics', type: 'string' }, - 'random.attribute': { value: 'Apples', type: 'string' }, - 'sentry.environment': { value: 'qa', type: 'string' }, - 'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' }, - 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - }, - }); - - expect(clientDistribution).toMatchObject({ - timestamp: expect.any(Number), - trace_id: expect.any(String), - span_id: expect.any(String), - name: 'test.page.distribution', - type: 'distribution', - value: 100, - attributes: { - page: { value: '/metrics', type: 'string' }, - 'random.attribute': { value: 'Manzanas', type: 'string' }, - 'sentry.environment': { value: 'qa', type: 'string' }, - 'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' }, - 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - }, - }); - - expect(clientGauge).toMatchObject({ - timestamp: expect.any(Number), - trace_id: expect.any(String), - span_id: expect.any(String), - name: 'test.page.gauge', - type: 'gauge', - value: 200, - attributes: { - page: { value: '/metrics', type: 'string' }, - 'random.attribute': { value: 'Mele', type: 'string' }, - 'sentry.environment': { value: 'qa', type: 'string' }, - 'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' }, - 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - }, - }); - - expect(serverCount).toMatchObject({ - timestamp: expect.any(Number), - trace_id: expect.any(String), - name: 'test.route.handler.count', - type: 'counter', - value: 1, - attributes: { - 'random.attribute': { value: 'Potatoes', type: 'string' }, - endpoint: { value: '/metrics/route-handler', type: 'string' }, - 'sentry.environment': { value: 'qa', type: 'string' }, - 'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' }, - 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - }, - }); - - expect(serverDistribution).toMatchObject({ - timestamp: expect.any(Number), - trace_id: expect.any(String), - name: 'test.route.handler.distribution', - type: 'distribution', - value: 100, - attributes: { - 'random.attribute': { value: 'Patatas', type: 'string' }, - endpoint: { value: '/metrics/route-handler', type: 'string' }, - 'sentry.environment': { value: 'qa', type: 'string' }, - 'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' }, - 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - }, - }); - - expect(serverGauge).toMatchObject({ - timestamp: expect.any(Number), - trace_id: expect.any(String), - name: 'test.route.handler.gauge', - type: 'gauge', - value: 200, - attributes: { - 'random.attribute': { value: 'Patate', type: 'string' }, - endpoint: { value: '/metrics/route-handler', type: 'string' }, - 'sentry.environment': { value: 'qa', type: 'string' }, - 'sentry.sdk.name': { value: 'sentry.javascript.nextjs', type: 'string' }, - 'sentry.sdk.version': { value: expect.any(String), type: 'string' }, - }, - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/middleware.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/middleware.test.ts deleted file mode 100644 index 213c40a40a62..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/middleware.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { - collectStreamedSpans, - collectStreamedSpansUntilSegment, - getSpanOp, - waitForStreamedSpan, -} from '@sentry-internal/test-utils'; -import { isDevMode } from './isDevMode'; - -// `withSentry` from `@sentry/nextjs/cloudflare` wraps the Worker entry, so middleware runs inside the request's -// `http.server` segment and its span is a child of it rather than a segment of its own. -test('Should create a span for middleware inside the request segment', async ({ request }) => { - const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-cf-workers', 'GET /api/endpoint-behind-middleware'); - - const response = await request.get('/api/endpoint-behind-middleware'); - expect(await response.json()).toStrictEqual({ name: 'John Doe' }); - - const spans = await spansPromise; - const segmentSpan = spans.find(span => span.is_segment)!; - const middlewareSpan = spans.find(span => span.name === 'middleware GET')!; - - expect(segmentSpan.attributes['url.path']?.value).toBe('/api/endpoint-behind-middleware'); - expect(segmentSpan.attributes['sentry.segment.name.source']?.value).toBe('route'); - expect(segmentSpan.attributes['isolation_scope.is_default']).toEqual({ value: false, type: 'boolean' }); - expect(middlewareSpan.is_segment).toBe(false); - expect(spans.some(span => span.span_id === middlewareSpan.parent_span_id)).toBe(true); - expect(middlewareSpan.status).toBe('ok'); - expect(getSpanOp(middlewareSpan)).toBe('middleware'); -}); - -test('Faulty middlewares', async ({ request }) => { - test.skip(isDevMode, 'Throwing crashes the dev server atm'); // https://github.com/vercel/next.js/issues/85261 - const middlewareSpanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => { - return span.name === 'middleware GET' && span.status === 'error'; - }); - - request.get('/api/endpoint-behind-middleware', { headers: { 'x-should-throw': '1' } }).catch(() => { - // Noop - }); - - await test.step('should record spans', async () => { - const middlewareSpan = await middlewareSpanPromise; - expect(middlewareSpan.is_segment).toBe(false); - expect(getSpanOp(middlewareSpan)).toBe('middleware'); - }); -}); - -test('Should trace outgoing fetch requests inside middleware', async ({ request }) => { - test.skip(isDevMode, 'The fetch requests ends up in a separate tx in dev atm'); - - // `http.client` span names are low cardinality under span streaming, hence `GET localhost` rather - // than the full URL. - const spansPromise = collectStreamedSpans('nextjs-16-cf-workers', spans => - spans.some(span => getSpanOp(span) === 'http.client' && span.name === 'GET localhost'), - ); - - request.get('/api/endpoint-behind-middleware', { headers: { 'x-should-make-request': '1' } }).catch(() => { - // Noop - }); - - const spans = await spansPromise; - const fetchSpan = spans.find(span => getSpanOp(span) === 'http.client' && span.name === 'GET localhost')!; - - expect(fetchSpan.status).toBe('ok'); - expect(fetchSpan.attributes).toMatchObject({ - 'http.request.method': { value: 'GET', type: 'string' }, - 'http.response.status_code': { value: 200, type: 'integer' }, - 'url.full': { value: 'http://localhost:3030/', type: 'string' }, - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/nested-rsc-error.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/nested-rsc-error.test.ts deleted file mode 100644 index 03f7d224e466..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/nested-rsc-error.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils'; - -test('Should capture errors from nested server components when `Sentry.captureRequestError` is added to the `onRequestError` hook', async ({ - page, -}) => { - const errorEventPromise = waitForError('nextjs-16-cf-workers', errorEvent => { - return !!errorEvent?.exception?.values?.some(value => value.value === 'I am technically uncatchable'); - }); - - // Matched on the error's own trace so a span from an earlier spec cannot satisfy the correlation. - const serverSpanPromise = waitForStreamedSpan('nextjs-16-cf-workers', async span => { - return ( - span.name === 'GET /nested-rsc-error/[param]' && - span.is_segment && - (await errorEventPromise).contexts?.trace?.trace_id === span.trace_id - ); - }); - - await page.goto(`/nested-rsc-error/123`); - const errorEvent = await errorEventPromise; - const serverSpan = await serverSpanPromise; - - // error event is part of the same trace as the server span - expect(errorEvent.contexts?.trace?.trace_id).toBe(serverSpan.trace_id); - - expect(errorEvent.request).toMatchObject({ - headers: expect.any(Object), - method: 'GET', - }); - - expect(errorEvent.contexts?.nextjs).toEqual({ - route_type: 'render', - router_kind: 'App Router', - router_path: '/nested-rsc-error/[param]', - request_path: '/nested-rsc-error/123', - }); - - expect(errorEvent.exception?.values?.[0]?.mechanism).toEqual({ - handled: false, - type: 'auto.function.nextjs.on_request_error', - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/pageload-tracing.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/pageload-tracing.test.ts deleted file mode 100644 index d6165244752e..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/pageload-tracing.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; - -test('App router spans should be attached to the pageload request span', async ({ page }) => { - const serverSpanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => { - return span.name === 'GET /pageload-tracing' && span.is_segment; - }); - - const pageloadSpanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => { - return span.name === '/pageload-tracing' && getSpanOp(span) === 'pageload' && span.is_segment; - }); - - await page.goto(`/pageload-tracing`); - - const [serverSpan, pageloadSpan] = await Promise.all([serverSpanPromise, pageloadSpanPromise]); - - expect(pageloadSpan.trace_id).toBeTruthy(); - expect(serverSpan.trace_id).toBe(pageloadSpan.trace_id); -}); - -test('extracts HTTP request headers as span attributes', async ({ baseURL }) => { - const serverSpanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => { - const requestId = span.attributes['http.request.header.x-request-id']; - return ( - span.name === 'GET /pageload-tracing' && - span.is_segment && - requestId?.type === 'array' && - requestId.value[0] === 'nextjs-789' - ); - }); - - await fetch(`${baseURL}/pageload-tracing`, { - headers: { - 'User-Agent': 'Custom-NextJS-Agent/15.0', - 'Content-Type': 'text/html', - 'X-NextJS-Test': 'nextjs-header-value', - Accept: 'text/html, application/xhtml+xml', - 'X-Framework': 'Next.js', - 'X-Request-ID': 'nextjs-789', - }, - }); - - const serverSpan = await serverSpanPromise; - - expect(serverSpan.attributes).toMatchObject({ - 'http.request.header.user-agent': { value: ['Custom-NextJS-Agent/15.0'], type: 'array' }, - 'http.request.header.content-type': { value: ['text/html'], type: 'array' }, - 'http.request.header.x-nextjs-test': { value: ['nextjs-header-value'], type: 'array' }, - 'http.request.header.accept': { value: ['text/html, application/xhtml+xml'], type: 'array' }, - 'http.request.header.x-framework': { value: ['Next.js'], type: 'array' }, - 'http.request.header.x-request-id': { value: ['nextjs-789'], type: 'array' }, - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/parameterized-routes.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/parameterized-routes.test.ts deleted file mode 100644 index 777130edc5b9..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/parameterized-routes.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; - -test('should create a parameterized pageload span when the `app` directory is used', async ({ page }) => { - const spanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => { - return span.name === '/parameterized/:one' && getSpanOp(span) === 'pageload' && span.is_segment; - }); - - await page.goto(`/parameterized/cappuccino`); - - const span = await spanPromise; - - expect(span.span_id).toEqual(expect.stringMatching(/[a-f0-9]{16}/)); - expect(span.trace_id).toEqual(expect.stringMatching(/[a-f0-9]{32}/)); - expect(span.attributes).toMatchObject({ - 'sentry.op': { value: 'pageload', type: 'string' }, - 'sentry.origin': { value: 'auto.pageload.nextjs.app_router_instrumentation', type: 'string' }, - 'sentry.segment.name.source': { value: 'route', type: 'string' }, - 'sentry.environment': { value: 'qa', type: 'string' }, - 'react.version': { value: expect.any(String), type: 'string' }, - }); -}); - -test('should create a span named after the static route when the `app` directory is used', async ({ page }) => { - const spanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => { - return span.name === '/parameterized/static' && getSpanOp(span) === 'pageload' && span.is_segment; - }); - - await page.goto(`/parameterized/static`); - - const span = await spanPromise; - - expect(span.span_id).toEqual(expect.stringMatching(/[a-f0-9]{16}/)); - expect(span.trace_id).toEqual(expect.stringMatching(/[a-f0-9]{32}/)); - expect(span.attributes).toMatchObject({ - 'sentry.op': { value: 'pageload', type: 'string' }, - 'sentry.origin': { value: 'auto.pageload.nextjs.app_router_instrumentation', type: 'string' }, - 'sentry.segment.name.source': { value: 'route', type: 'string' }, - 'sentry.environment': { value: 'qa', type: 'string' }, - 'react.version': { value: expect.any(String), type: 'string' }, - 'url.template': { value: '/parameterized/static', type: 'string' }, - }); -}); - -test('should create a partially parameterized pageload span when the `app` directory is used', async ({ page }) => { - const spanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => { - return span.name === '/parameterized/:one/beep' && getSpanOp(span) === 'pageload' && span.is_segment; - }); - - await page.goto(`/parameterized/cappuccino/beep`); - - const span = await spanPromise; - - expect(span.span_id).toEqual(expect.stringMatching(/[a-f0-9]{16}/)); - expect(span.trace_id).toEqual(expect.stringMatching(/[a-f0-9]{32}/)); - expect(span.attributes).toMatchObject({ - 'sentry.op': { value: 'pageload', type: 'string' }, - 'sentry.origin': { value: 'auto.pageload.nextjs.app_router_instrumentation', type: 'string' }, - 'sentry.segment.name.source': { value: 'route', type: 'string' }, - 'sentry.environment': { value: 'qa', type: 'string' }, - 'react.version': { value: expect.any(String), type: 'string' }, - }); -}); - -test('should create a nested parameterized pageload span when the `app` directory is used', async ({ page }) => { - const spanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => { - return span.name === '/parameterized/:one/beep/:two' && getSpanOp(span) === 'pageload' && span.is_segment; - }); - - await page.goto(`/parameterized/cappuccino/beep/espresso`); - - const span = await spanPromise; - - expect(span.span_id).toEqual(expect.stringMatching(/[a-f0-9]{16}/)); - expect(span.trace_id).toEqual(expect.stringMatching(/[a-f0-9]{32}/)); - expect(span.attributes).toMatchObject({ - 'sentry.op': { value: 'pageload', type: 'string' }, - 'sentry.origin': { value: 'auto.pageload.nextjs.app_router_instrumentation', type: 'string' }, - 'sentry.segment.name.source': { value: 'route', type: 'string' }, - 'sentry.environment': { value: 'qa', type: 'string' }, - 'react.version': { value: expect.any(String), type: 'string' }, - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/prefetch-spans.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/prefetch-spans.test.ts deleted file mode 100644 index 53cebde1ff21..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/prefetch-spans.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; -import { isDevMode } from './isDevMode'; - -test('Prefetch client spans should have a http.request.prefetch attribute', async ({ page }) => { - test.skip(isDevMode, "Prefetch requests don't have the prefetch header in dev mode"); - - // The prefetch span is a child of the pageload segment span, which ends last. - const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-cf-workers', '/prefetching'); - - await page.goto(`/prefetching`); - - // Make it more likely that nextjs prefetches - await page.hover('#prefetch-link'); - - const spans = await spansPromise; - - expect(spans).toContainEqual( - expect.objectContaining({ - attributes: expect.objectContaining({ - 'sentry.op': { value: 'http.client', type: 'string' }, - 'http.request.prefetch': { value: true, type: 'boolean' }, - }), - }), - ); -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/route-handler.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/route-handler.test.ts deleted file mode 100644 index f8a8420f3694..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/route-handler.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import test, { expect } from '@playwright/test'; -import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; - -test('Should create a span for node route handlers', async ({ request }) => { - const routehandlerSpanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => { - return span.name === 'GET /route-handler/[xoxo]/node' && span.is_segment; - }); - - const response = await request.get('/route-handler/123/node', { headers: { 'x-charly': 'gomez' } }); - expect(await response.json()).toStrictEqual({ message: 'Hello Node Route Handler' }); - - const routehandlerSpan = await routehandlerSpanPromise; - - expect(routehandlerSpan.status).toBe('ok'); - expect(getSpanOp(routehandlerSpan)).toBe('http.server'); - expect(routehandlerSpan.attributes['http.request.header.x-charly']?.value).toEqual(['gomez']); -}); - -test('Should create a span for edge route handlers', async ({ request }) => { - test.skip(true, 'OpenNext does not support the Edge runtime'); - const routehandlerSpanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => { - return span.name === 'GET /route-handler/[xoxo]/edge' && span.is_segment; - }); - - const response = await request.get('/route-handler/123/edge', { headers: { 'x-charly': 'gomez' } }); - expect(await response.json()).toStrictEqual({ message: 'Hello Edge Route Handler' }); - - const routehandlerSpan = await routehandlerSpanPromise; - - expect(routehandlerSpan.status).toBe('ok'); - expect(getSpanOp(routehandlerSpan)).toBe('http.server'); - expect(routehandlerSpan.attributes['http.request.header.x-charly']?.value).toEqual(['gomez']); -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/server-action-redirect.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/server-action-redirect.test.ts deleted file mode 100644 index 9d06ee350aa4..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/server-action-redirect.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils'; - -test('Should handle server action redirect without capturing errors', async ({ page }) => { - // Wait for the initial pageload span - const pageLoadSpanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => { - return span.name === '/redirect/origin' && span.is_segment; - }); - - // Navigate to the origin page - await page.goto('/redirect/origin'); - - const pageLoadSpan = await pageLoadSpanPromise; - expect(pageLoadSpan).toBeDefined(); - - // Wait for the redirect span - const redirectSpanPromise = waitForStreamedSpan('nextjs-16-cf-workers', span => { - return span.name === 'GET /redirect/destination' && span.is_segment; - }); - - // No error should be captured - const redirectErrorPromise = waitForError('nextjs-16-cf-workers', async errorEvent => { - return !!errorEvent; - }); - - // Click the redirect button - await page.click('button[type="submit"]'); - - await redirectSpanPromise; - - // Verify we got redirected to the destination page - await expect(page).toHaveURL('/redirect/destination'); - - // Wait for potential errors with a 2 second timeout - const errorTimeout = new Promise((_, reject) => - setTimeout(() => reject(new Error('No error captured (timeout)')), 2000), - ); - - // We expect this to timeout since no error should be captured during the redirect - try { - await Promise.race([redirectErrorPromise, errorTimeout]); - throw new Error('Expected no error to be captured, but an error was found'); - } catch (e) { - // If we get a timeout error (as expected), no error was captured - expect((e as Error).message).toBe('No error captured (timeout)'); - } -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/server-components.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/server-components.test.ts deleted file mode 100644 index 344a62127543..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/server-components.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { collectStreamedSpansUntilSegment, getSpanOp } from '@sentry-internal/test-utils'; - -// Next.js emits these spans itself. The SDK attaches no op, description or function name to -// them, so asserting `undefined` pins that they stay untouched. -const nextjsSpan = { op: undefined, description: undefined, codeFunctionName: undefined }; - -// `withSentry` from `@sentry/nextjs/cloudflare` wraps the Worker entry, so the segment is its `http.server` -// span, named after the route Next.js resolved. -test('Sends a span for a request to app router with URL', async ({ page }) => { - const spansPromise = collectStreamedSpansUntilSegment( - 'nextjs-16-cf-workers', - span => - span.name === 'GET /parameterized/[one]/beep/[two]' && - span.attributes['url.path']?.value === '/parameterized/1337/beep/42', - ); - - await page.goto('/parameterized/1337/beep/42'); - - const spans = await spansPromise; - const segmentSpan = spans.find( - span => - span.name === 'GET /parameterized/[one]/beep/[two]' && - span.is_segment && - span.attributes['url.path']?.value === '/parameterized/1337/beep/42', - )!; - - expect(segmentSpan.span_id).toEqual(expect.stringMatching(/[a-f0-9]{16}/)); - expect(segmentSpan.trace_id).toEqual(expect.stringMatching(/[a-f0-9]{32}/)); - expect(segmentSpan.status).toBe('ok'); - expect(segmentSpan.attributes).toMatchObject({ - 'sentry.op': { value: 'http.server', type: 'string' }, - 'sentry.origin': { value: 'auto.http.cloudflare', type: 'string' }, - 'sentry.sample_rate': { value: 1, type: 'integer' }, - 'sentry.segment.name.source': { value: 'route', type: 'string' }, - 'http.request.method': { value: 'GET', type: 'string' }, - 'http.response.status_code': { value: 200, type: 'integer' }, - 'http.route': { value: '/parameterized/[one]/beep/[two]', type: 'string' }, - 'url.path': { value: '/parameterized/1337/beep/42', type: 'string' }, - 'next.route': { value: '/parameterized/[one]/beep/[two]', type: 'string' }, - }); - - // No child span should share the segment span's name - expect(spans.filter(span => !span.is_segment && span.name === segmentSpan.name)).toHaveLength(0); - expect(spans.filter(span => getSpanOp(span) === 'http.server')).toEqual([segmentSpan]); -}); - -test('Will create spans for every server component and metadata generation functions when visiting a page', async ({ - page, -}) => { - const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-cf-workers', 'GET /nested-layout'); - - await page.goto('/nested-layout'); - - const fullSpans = await spansPromise; - const spans = fullSpans.map(span => ({ - name: span.name, - op: getSpanOp(span), - description: span.attributes['sentry.description']?.value, - codeFunctionName: span.attributes['code.function.name']?.value, - })); - - expect(spans).toContainEqual({ ...nextjsSpan, name: 'render route (app) /nested-layout' }); - expect(spans).toContainEqual({ ...nextjsSpan, name: 'build component tree' }); - // Server component spans: the name is the low-cardinality `code.function.name`, and the - // segment each one resolved for is on the description. - expect(spans).toContainEqual({ - name: 'Layout', - op: 'function', - description: 'resolve root layout server component', - codeFunctionName: 'Layout', - }); - expect(spans).toContainEqual({ - name: 'Layout', - op: 'function', - description: 'resolve layout server component "(nested-layout)"', - codeFunctionName: 'Layout', - }); - expect(spans).toContainEqual({ - name: 'Layout', - op: 'function', - description: 'resolve layout server component "nested-layout"', - codeFunctionName: 'Layout', - }); - expect(spans).toContainEqual({ - name: 'Page', - op: 'function', - description: 'resolve page server component "/nested-layout"', - codeFunctionName: 'Page', - }); - expect(spans).toContainEqual({ ...nextjsSpan, name: 'generateMetadata /(nested-layout)/nested-layout/page' }); - expect(spans).toContainEqual({ ...nextjsSpan, name: 'start response' }); -}); - -test('Will create spans for every server component and metadata generation functions when visiting a dynamic page', async ({ - page, -}) => { - const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-cf-workers', 'GET /nested-layout/[dynamic]'); - - await page.goto('/nested-layout/123'); - - const fullSpans = await spansPromise; - const spans = fullSpans.map(span => ({ - name: span.name, - op: getSpanOp(span), - description: span.attributes['sentry.description']?.value, - codeFunctionName: span.attributes['code.function.name']?.value, - })); - - expect(spans).toContainEqual({ ...nextjsSpan, name: 'resolve page components' }); - expect(spans).toContainEqual({ ...nextjsSpan, name: 'render route (app) /nested-layout/[dynamic]' }); - expect(spans).toContainEqual({ ...nextjsSpan, name: 'build component tree' }); - // Server component spans: the name is the low-cardinality `code.function.name`, and the - // segment each one resolved for is on the description. - expect(spans).toContainEqual({ - name: 'Layout', - op: 'function', - description: 'resolve root layout server component', - codeFunctionName: 'Layout', - }); - expect(spans).toContainEqual({ - name: 'Layout', - op: 'function', - description: 'resolve layout server component "(nested-layout)"', - codeFunctionName: 'Layout', - }); - expect(spans).toContainEqual({ - name: 'Layout', - op: 'function', - description: 'resolve layout server component "nested-layout"', - codeFunctionName: 'Layout', - }); - expect(spans).toContainEqual({ - name: 'Layout', - op: 'function', - description: 'resolve layout server component "[dynamic]"', - codeFunctionName: 'Layout', - }); - expect(spans).toContainEqual({ - name: 'Page', - op: 'function', - description: 'resolve page server component "/nested-layout/[dynamic]"', - codeFunctionName: 'Page', - }); - expect(spans).toContainEqual({ - ...nextjsSpan, - name: 'generateMetadata /(nested-layout)/nested-layout/[dynamic]/page', - }); - expect(spans).toContainEqual({ ...nextjsSpan, name: 'start response' }); -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/streaming-rsc-error.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/streaming-rsc-error.test.ts deleted file mode 100644 index 1b991f1a9fe4..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/streaming-rsc-error.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils'; - -test('Should capture errors for crashing streaming promises in server components when `Sentry.captureRequestError` is added to the `onRequestError` hook', async ({ - page, -}) => { - const errorEventPromise = waitForError('nextjs-16-cf-workers', errorEvent => { - return !!errorEvent?.exception?.values?.some(value => value.value === 'I am a data streaming error'); - }); - - // Matched on the error's own trace so a span from an earlier spec cannot satisfy the correlation. - const serverSpanPromise = waitForStreamedSpan('nextjs-16-cf-workers', async span => { - return ( - span.name === 'GET /streaming-rsc-error/[param]' && - span.is_segment && - (await errorEventPromise).contexts?.trace?.trace_id === span.trace_id - ); - }); - - // The streaming RSC error can interrupt the HTTP response, causing the navigation to reject - // (e.g. net::ERR_ABORTED) even though the error and transaction are still captured. - await page.goto(`/streaming-rsc-error/123`).catch(() => {}); - const errorEvent = await errorEventPromise; - const serverSpan = await serverSpanPromise; - - // error event is part of the same trace as the server span - expect(errorEvent.contexts?.trace?.trace_id).toBe(serverSpan.trace_id); - - expect(errorEvent.request).toMatchObject({ - headers: expect.any(Object), - method: 'GET', - }); - - expect(errorEvent.contexts?.nextjs).toEqual({ - route_type: 'render', - router_kind: 'App Router', - router_path: '/streaming-rsc-error/[param]', - request_path: '/streaming-rsc-error/123', - }); - - expect(errorEvent.exception?.values?.[0]?.mechanism).toEqual({ - handled: false, - type: 'auto.function.nextjs.on_request_error', - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tsconfig.json b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tsconfig.json deleted file mode 100644 index cc9ed39b5aa2..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tsconfig.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2017", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "react-jsx", - "incremental": true, - "plugins": [ - { - "name": "next" - } - ], - "paths": { - "@/*": ["./*"] - } - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts", "**/*.mts"], - "exclude": ["node_modules"] -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/wrangler.jsonc b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/wrangler.jsonc deleted file mode 100644 index 16b0271383b1..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/wrangler.jsonc +++ /dev/null @@ -1,74 +0,0 @@ -/** - * For more details on how to configure Wrangler, refer to: - * https://developers.cloudflare.com/workers/wrangler/configuration/ - */ -/** - * For more details on how to configure Wrangler, refer to: - * https://developers.cloudflare.com/workers/wrangler/configuration/ - */ -{ - "$schema": "node_modules/wrangler/config-schema.json", - "name": "next-cf", - "main": "./worker.ts", - // From 2026-02-19 on, wrangler uses the native `process` of workerd, whose `cwd()` is `/bundle` during a request. - // Next.js then misses its router server context and extracts the incoming trace again, see trace-continuation.test.ts. - "compatibility_date": "2026-02-19", - "compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"], - "assets": { - "binding": "ASSETS", - "directory": ".open-next/assets", - }, - "images": { - // Enable image optimization - // see https://opennext.js.org/cloudflare/howtos/image - "binding": "IMAGES", - }, - "services": [ - { - // Self-reference service binding, the service name must match the worker name - // see https://opennext.js.org/cloudflare/caching - "binding": "WORKER_SELF_REFERENCE", - "service": "next-cf", - }, - ], - "d1_databases": [ - { - "binding": "DB", - "database_name": "e2e", - "database_id": "00000000-0000-0000-0000-000000000000", - }, - ], - "observability": { - "enabled": true, - }, - /** - * Smart Placement - * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement - */ - // "placement": { "mode": "smart" } - /** - * Bindings - * Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including - * databases, object storage, AI inference, real-time communication and more. - * https://developers.cloudflare.com/workers/runtime-apis/bindings/ - */ - /** - * Environment Variables - * https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables - */ - // "vars": { "MY_VARIABLE": "production_value" } - /** - * Note: Use secrets to store sensitive data. - * https://developers.cloudflare.com/workers/configuration/secrets/ - */ - /** - * Static Assets - * https://developers.cloudflare.com/workers/static-assets/binding/ - */ - // "assets": { "directory": "./public/", "binding": "ASSETS" } - /** - * Service Bindings (communicate between multiple Workers) - * https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings - */ - // "services": [{ "binding": "MY_SERVICE", "service": "my-service" }] -} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/ai-mock-server.mjs b/dev-packages/e2e-tests/test-applications/nextjs-16/ai-mock-server.mjs index b97e0cb162dc..50060d37c309 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/ai-mock-server.mjs +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/ai-mock-server.mjs @@ -24,11 +24,12 @@ function sendJson(res, status, obj) { res.end(JSON.stringify(obj)); } -let serverPromise; +/** Port of the mock server. The Playwright global setup starts it, so the app reaches it on every runtime. */ +export const MOCK_AI_PORT = 3032; -/** Lazily starts the shared mock server and resolves to its port. */ -export function getMockAiPort() { - serverPromise ??= new Promise(resolve => { +/** Starts the mock server on `MOCK_AI_PORT` and resolves to a function that stops it. */ +export function startMockAiServer() { + return new Promise(resolve => { const server = createServer(async (req, res) => { const url = req.url || ''; @@ -84,10 +85,8 @@ export function getMockAiPort() { res.writeHead(404).end(); }); - server.listen(0, () => { - resolve(server.address().port); + server.listen(MOCK_AI_PORT, () => { + resolve(() => new Promise(done => server.close(done))); }); }); - - return serverPromise; } diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/d1/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/app/api/d1/route.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/d1/route.ts rename to dev-packages/e2e-tests/test-applications/nextjs-16/app/api/d1/route.ts diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/app/api/openai/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/app/api/openai/route.ts index 9315f9d079ce..628b7a239697 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/app/api/openai/route.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/app/api/openai/route.ts @@ -1,13 +1,12 @@ import { NextResponse } from 'next/server'; import OpenAI from 'openai'; -import { getMockAiPort } from '../../../ai-mock-server.mjs'; +import { MOCK_AI_PORT } from '../../../ai-mock-server.mjs'; export const dynamic = 'force-dynamic'; export async function GET() { - const port = await getMockAiPort(); const client = new OpenAI({ - baseURL: `http://localhost:${port}/openai`, + baseURL: `http://localhost:${MOCK_AI_PORT}/openai`, apiKey: 'mock-api-key', }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/otel-context/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/app/api/otel-context/route.ts similarity index 54% rename from dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/otel-context/route.ts rename to dev-packages/e2e-tests/test-applications/nextjs-16/app/api/otel-context/route.ts index 1ca661b60b63..26199762ea55 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/otel-context/route.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/app/api/otel-context/route.ts @@ -1,4 +1,8 @@ -import { context, createContextKey, trace } from '@opentelemetry/api'; +// The app does not install `@opentelemetry/api`: OpenNext resolves the ESM build of an installed copy when it bundles +// the Node.js middleware for Workers, but its file tracing only copies the CommonJS build. The copy in Next.js uses the +// same global OpenTelemetry API, but has no types. +// @ts-expect-error `next/dist/compiled/@opentelemetry/api` ships no type declarations +import { context, createContextKey, trace } from 'next/dist/compiled/@opentelemetry/api'; import { NextResponse } from 'next/server'; export const dynamic = 'force-dynamic'; @@ -9,7 +13,7 @@ export async function GET() { const tracer = trace.getTracer('e2e'); context.with(context.active().setValue(E2E_CONTEXT_KEY, 'e2e-value'), () => { - tracer.startActiveSpan('otel-context-outer', outer => { + tracer.startActiveSpan('otel-context-outer', (outer: { end(): void }) => { // An explicit context, as OpenTelemetry instrumentations pass it. const inner = tracer.startSpan('otel-context-inner', {}, context.active()); inner.setAttribute('e2e.context.value', String(context.active().getValue(E2E_CONTEXT_KEY))); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/test-error/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/app/api/test-error/route.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/app/api/test-error/route.ts rename to dev-packages/e2e-tests/test-applications/nextjs-16/app/api/test-error/route.ts diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/propagation/test-outgoing-fetch-external-disallowed/check/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/app/propagation/test-outgoing-fetch-external-disallowed/check/route.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/propagation/test-outgoing-fetch-external-disallowed/check/route.ts rename to dev-packages/e2e-tests/test-applications/nextjs-16/app/propagation/test-outgoing-fetch-external-disallowed/check/route.ts diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/propagation/test-outgoing-fetch-external-disallowed/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/app/propagation/test-outgoing-fetch-external-disallowed/route.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/propagation/test-outgoing-fetch-external-disallowed/route.ts rename to dev-packages/e2e-tests/test-applications/nextjs-16/app/propagation/test-outgoing-fetch-external-disallowed/route.ts diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/propagation/test-outgoing-fetch/check/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/app/propagation/test-outgoing-fetch/check/route.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/propagation/test-outgoing-fetch/check/route.ts rename to dev-packages/e2e-tests/test-applications/nextjs-16/app/propagation/test-outgoing-fetch/check/route.ts diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/propagation/test-outgoing-fetch/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/app/propagation/test-outgoing-fetch/route.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/propagation/test-outgoing-fetch/route.ts rename to dev-packages/e2e-tests/test-applications/nextjs-16/app/propagation/test-outgoing-fetch/route.ts diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/propagation/utils.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/app/propagation/utils.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nextjs-16-bun/app/propagation/utils.ts rename to dev-packages/e2e-tests/test-applications/nextjs-16/app/propagation/utils.ts diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/global-setup.mjs b/dev-packages/e2e-tests/test-applications/nextjs-16/global-setup.mjs index dc7ec8736faf..41ec97defa8f 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/global-setup.mjs +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/global-setup.mjs @@ -1,6 +1,7 @@ import { execSync } from 'child_process'; import { dirname } from 'path'; import { fileURLToPath } from 'url'; +import { startMockAiServer } from './ai-mock-server.mjs'; const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -11,4 +12,8 @@ export default async function globalSetup() { cwd: __dirname, stdio: 'inherit', }); + + // The mock AI server runs here and not in the app, because a Worker can not start a `node:http` server. Playwright + // runs the returned function as teardown. + return startMockAiServer(); } diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/next.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/next.config.ts index 8f6c5edfbcca..c49f53a2c851 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/next.config.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/next.config.ts @@ -5,6 +5,9 @@ import type { NextConfig } from 'next'; process.env.VERCEL = '1'; const nextConfig: NextConfig = { + // `pg` requires `pg-cloudflare` on Workers. The file tracing only copies its Node.js build, and OpenNext copies the + // whole package (with its `workerd` build) only for packages listed here. + serverExternalPackages: ['pg-cloudflare'], experimental: { sri: { algorithm: 'sha256', diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/open-next.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/open-next.config.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/open-next.config.ts rename to dev-packages/e2e-tests/test-applications/nextjs-16/open-next.config.ts diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/package.json b/dev-packages/e2e-tests/test-applications/nextjs-16/package.json index 408cd3be5a38..fcc8aa64f9d8 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/package.json @@ -20,10 +20,19 @@ "test:build-latest-webpack": "pnpm install && pnpm add next@latest && pnpm build-webpack", "test:build-canary-webpack": "pnpm install && pnpm add next@canary && pnpm build-webpack", "test:assert": "pnpm test:prod && pnpm test:dev", - "test:assert-webpack": "pnpm test:prod && pnpm test:dev-webpack" + "test:assert-webpack": "pnpm test:prod && pnpm test:dev-webpack", + "test:assert:bun": "RUNTIME=bun pnpm test:prod", + "test:assert:deno": "RUNTIME=deno pnpm test:prod", + "test:build:cloudflare": "pnpm install && opennextjs-cloudflare build", + "test:build-latest:cloudflare": "pnpm install && pnpm add next@latest && opennextjs-cloudflare build", + "test:assert:cloudflare": "RUNTIME=cloudflare pnpm test:prod", + "start:cloudflare": "opennextjs-cloudflare preview --var \"E2E_TEST_DSN:$E2E_TEST_DSN\"" }, "//": "Pin `ioredis` to 5.10.1: it's the last version before the driver publishes its own native diagnostics channels; orchestrion's config covers `ioredis <5.11.0`.", "dependencies": { + "@opennextjs/cloudflare": "^1.14.9", + "@sentry/bun": "file:../../packed/sentry-bun-packed.tgz", + "@sentry/deno": "file:../../packed/sentry-deno-packed.tgz", "@sentry/nextjs": "file:../../packed/sentry-nextjs-packed.tgz", "@sentry/core": "file:../../packed/sentry-core-packed.tgz", "@vercel/queue": "^0.1.3", @@ -47,7 +56,8 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "^16", - "typescript": "^5" + "typescript": "^5", + "wrangler": "^4.61.0" }, "volta": { "extends": "../../package.json" @@ -67,6 +77,20 @@ { "build-command": "pnpm test:build-latest", "label": "nextjs-16 (latest, turbopack)" + }, + { + "assert-command": "pnpm test:assert:bun", + "label": "nextjs-16 (bun)" + }, + { + "build-command": "pnpm test:build:cloudflare", + "assert-command": "pnpm test:assert:cloudflare", + "label": "nextjs-16 (cloudflare)" + }, + { + "build-command": "pnpm test:build-latest:cloudflare", + "assert-command": "pnpm test:assert:cloudflare", + "label": "nextjs-16 (cloudflare, latest)" } ], "optionalVariants": [ @@ -79,6 +103,11 @@ "build-command": "pnpm test:build-canary-webpack", "label": "nextjs-16 (canary, webpack)", "assert-command": "pnpm test:assert-webpack" + }, + { + "assert-command": "pnpm test:assert:deno", + "deno-version": "v2.9.0", + "label": "nextjs-16 (deno)" } ] } diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/nextjs-16/playwright.config.mjs index cbac80f5b7e7..7e356d81488b 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/playwright.config.mjs +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/playwright.config.mjs @@ -1,10 +1,17 @@ -import { getPlaywrightConfig } from '@sentry-internal/test-utils'; +import { getPlaywrightConfig, getRuntime } from '@sentry-internal/test-utils'; const testEnv = process.env.TEST_ENV; if (!testEnv) { throw new Error('No test env defined'); } +const productionStartCommands = { + node: 'pnpm next start -p 3030', + bun: 'bun --bun ./node_modules/next/dist/bin/next start -p 3030', + deno: 'deno run -A ./node_modules/next/dist/bin/next start -p 3030', + cloudflare: 'pnpm start:cloudflare --port 3030', +}; + const getStartCommand = () => { if (testEnv === 'development-webpack') { return 'pnpm next dev -p 3030 --webpack 2>&1 | tee .tmp_dev_server_logs'; @@ -15,7 +22,7 @@ const getStartCommand = () => { } if (testEnv === 'production') { - return 'pnpm next start -p 3030'; + return productionStartCommands[getRuntime()]; } throw new Error(`Unknown test env: ${testEnv}`); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/sentry.server.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/sentry.server.config.ts index 4507c799f9f3..960f49a15533 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/sentry.server.config.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/sentry.server.config.ts @@ -1,3 +1,5 @@ +import { bunHttpServerIntegration, fetchIntegration as bunFetchIntegration } from '@sentry/bun'; +import { fetchIntegration as denoFetchIntegration } from '@sentry/deno'; import * as Sentry from '@sentry/nextjs'; import { Log } from '@sentry/nextjs'; @@ -6,8 +8,17 @@ Sentry.init({ dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, tunnel: `http://localhost:3031/`, // proxy server tracesSampleRate: 1.0, + tracePropagationTargets: ['http://localhost:3030/propagation/test-outgoing-fetch/check'], // debug: true, - integrations: [Sentry.vercelAIIntegration(), Sentry.nodeRuntimeMetricsIntegration({ collectionIntervalMs: 1_000 })], + integrations: [ + Sentry.vercelAIIntegration(), + Sentry.nodeRuntimeMetricsIntegration({ collectionIntervalMs: 1_000 }), + // Bun does not emit the `node:http` diagnostics channel the Node SDK uses to isolate incoming requests, so each + // request would otherwise share one trace. Next.js emits its own server spans, hence `spans: false`. + ...(process.versions.bun ? [bunHttpServerIntegration({ spans: false }), bunFetchIntegration()] : []), + // The `fetch` of Deno is not undici, so the Node SDK does not see outgoing requests. + ...(process.versions.deno ? [denoFetchIntegration()] : []), + ], // Verify Log type is available beforeSendLog(log: Log) { return log; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/cloudflare-runtime.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/cloudflare-runtime.test.ts similarity index 79% rename from dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/cloudflare-runtime.test.ts rename to dev-packages/e2e-tests/test-applications/nextjs-16/tests/cloudflare-runtime.test.ts index d088edd32dcb..bf1c773190a7 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/cloudflare-runtime.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/cloudflare-runtime.test.ts @@ -1,9 +1,11 @@ import { expect, test } from '@playwright/test'; -import { waitForError } from '@sentry-internal/test-utils'; +import { getRuntime, waitForError } from '@sentry-internal/test-utils'; + +test.skip(getRuntime() !== 'cloudflare', 'Checks the runtime context of the Cloudflare Worker'); test.describe('Cloudflare Runtime', () => { test('Should report cloudflare as the runtime in API route error events', async ({ request }) => { - const errorEventPromise = waitForError('nextjs-16-cf-workers', errorEvent => { + const errorEventPromise = waitForError('nextjs-16', errorEvent => { return !!errorEvent?.exception?.values?.some(value => value.value?.includes('This is a test error from an API route'), ); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/d1-binding.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/d1-binding.test.ts similarity index 80% rename from dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/d1-binding.test.ts rename to dev-packages/e2e-tests/test-applications/nextjs-16/tests/d1-binding.test.ts index f562907e08f5..5639e1ab41a7 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/d1-binding.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/d1-binding.test.ts @@ -1,9 +1,11 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpansUntilSegment, getSpanOp } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getRuntime, getSpanOp } from '@sentry-internal/test-utils'; + +test.skip(getRuntime() !== 'cloudflare', 'D1 is a binding of Cloudflare Workers'); // `withSentry` passes an instrumented `env` to the OpenNext handler, which `getCloudflareContext()` returns. test('Should create a span for a D1 query through the `env` of `getCloudflareContext`', async ({ request }) => { - const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-cf-workers', 'GET /api/d1'); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-16', 'GET /api/d1'); const response = await request.get('/api/d1'); expect(await response.json()).toStrictEqual([{ one: 1 }]); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/db-page.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/db-page.test.ts index 968891a085c4..eb564116a132 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/db-page.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/db-page.test.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getRuntime } from '@sentry-internal/test-utils'; test('Instruments DB calls made during server-side rendering of a page', async ({ page }) => { // The db spans are children of the segment span, which ends last. @@ -12,19 +12,22 @@ test('Instruments DB calls made during server-side rendering of a page', async ( const spans = await spansPromise; // One page render produces spans from both injection paths: pg (externalized → runtime module - // hook) and ioredis (bundle-safe allowlisted → build-time loader). - expect(spans).toContainEqual( - expect.objectContaining({ - name: 'SELECT', - status: 'ok', - attributes: expect.objectContaining({ - 'sentry.op': { value: 'db', type: 'string' }, - 'sentry.origin': { value: 'auto.db.postgres', type: 'string' }, - 'db.system.name': { value: 'postgresql', type: 'string' }, - 'db.query.text': { value: 'SELECT ? + ? AS answer', type: 'string' }, + // hook) and ioredis (bundle-safe allowlisted → build-time loader). Bun and Workers have no runtime module + // hook, so pg creates no spans there. + if (getRuntime() !== 'bun' && getRuntime() !== 'cloudflare') { + expect(spans).toContainEqual( + expect.objectContaining({ + name: 'SELECT', + status: 'ok', + attributes: expect.objectContaining({ + 'sentry.op': { value: 'db', type: 'string' }, + 'sentry.origin': { value: 'auto.db.postgres', type: 'string' }, + 'db.system.name': { value: 'postgresql', type: 'string' }, + 'db.query.text': { value: 'SELECT ? + ? AS answer', type: 'string' }, + }), }), - }), - ); + ); + } expect(spans).toContainEqual( expect.objectContaining({ name: 'set localhost:6379', diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/metrics.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/metrics.test.ts index 43edb917d526..3b002afcddd2 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/metrics.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/metrics.test.ts @@ -1,7 +1,11 @@ import { expect, test } from '@playwright/test'; -import { waitForMetric } from '@sentry-internal/test-utils'; +import { getRuntime, waitForMetric } from '@sentry-internal/test-utils'; test('Should emit metrics from server and client', async ({ request, page }) => { + // The client of `@sentry/cloudflare` has no `serverName`, so its metrics have no `server.address`. + const serverAddress = + getRuntime() === 'cloudflare' ? {} : { 'server.address': { value: expect.any(String), type: 'string' } }; + const clientCountPromise = waitForMetric('nextjs-16', async metric => { return metric.name === 'test.page.count'; }); @@ -90,7 +94,7 @@ test('Should emit metrics from server and client', async ({ request, page }) => type: 'counter', value: 1, attributes: { - 'server.address': { value: expect.any(String), type: 'string' }, + ...serverAddress, 'random.attribute': { value: 'Potatoes', type: 'string' }, endpoint: { value: '/metrics/route-handler', type: 'string' }, 'sentry.environment': { value: 'qa', type: 'string' }, @@ -106,7 +110,7 @@ test('Should emit metrics from server and client', async ({ request, page }) => type: 'distribution', value: 100, attributes: { - 'server.address': { value: expect.any(String), type: 'string' }, + ...serverAddress, 'random.attribute': { value: 'Patatas', type: 'string' }, endpoint: { value: '/metrics/route-handler', type: 'string' }, 'sentry.environment': { value: 'qa', type: 'string' }, @@ -122,7 +126,7 @@ test('Should emit metrics from server and client', async ({ request, page }) => type: 'gauge', value: 200, attributes: { - 'server.address': { value: expect.any(String), type: 'string' }, + ...serverAddress, 'random.attribute': { value: 'Patate', type: 'string' }, endpoint: { value: '/metrics/route-handler', type: 'string' }, 'sentry.environment': { value: 'qa', type: 'string' }, diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/middleware.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/middleware.test.ts index f91168e54038..9359c0a96c7c 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/middleware.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/middleware.test.ts @@ -2,12 +2,15 @@ import { expect, test } from '@playwright/test'; import { collectStreamedSpans, collectStreamedSpansUntilSegment, + getRuntime, getSpanOp, waitForStreamedSpan, } from '@sentry-internal/test-utils'; import { isDevMode } from './isDevMode'; test('Should create a span for middleware', async ({ request }) => { + test.skip(getRuntime() === 'cloudflare', 'On Workers the middleware span is a child of the request span'); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-16', 'middleware GET'); const routeSpanPromise = waitForStreamedSpan('nextjs-16', span => { @@ -44,8 +47,32 @@ test('Should create a span for middleware', async ({ request }) => { expect(routeSpan.attributes['isolation_scope.has_proxy_marker']).toEqual({ value: false, type: 'boolean' }); }); +// `withSentry` from `@sentry/nextjs/cloudflare` wraps the Worker entry, so middleware runs inside the request's +// `http.server` segment and its span is a child of it rather than a segment of its own. +test('Should create a span for middleware inside the request segment of the Worker', async ({ request }) => { + test.skip(getRuntime() !== 'cloudflare', 'Only on Workers the request span of `withSentry` is the segment'); + + const spansPromise = collectStreamedSpansUntilSegment('nextjs-16', 'GET /api/endpoint-behind-middleware'); + + const response = await request.get('/api/endpoint-behind-middleware'); + expect(await response.json()).toStrictEqual({ name: 'John Doe' }); + + const spans = await spansPromise; + const segmentSpan = spans.find(span => span.is_segment)!; + const middlewareSpan = spans.find(span => span.name === 'middleware GET')!; + + expect(segmentSpan.attributes['url.path']?.value).toBe('/api/endpoint-behind-middleware'); + expect(segmentSpan.attributes['sentry.segment.name.source']?.value).toBe('route'); + expect(segmentSpan.attributes['isolation_scope.is_default']).toEqual({ value: false, type: 'boolean' }); + expect(middlewareSpan.is_segment).toBe(false); + expect(spans.some(span => span.span_id === middlewareSpan.parent_span_id)).toBe(true); + expect(middlewareSpan.status).toBe('ok'); + expect(getSpanOp(middlewareSpan)).toBe('middleware'); +}); + test('Faulty middlewares', async ({ request }) => { test.skip(isDevMode, 'Throwing crashes the dev server atm'); // https://github.com/vercel/next.js/issues/85261 + test.skip(getRuntime() === 'cloudflare', 'On Workers the middleware span is a child of the request span'); const middlewareSpanPromise = waitForStreamedSpan('nextjs-16', span => { return span.name === 'middleware GET' && span.is_segment; }); @@ -65,6 +92,21 @@ test('Faulty middlewares', async ({ request }) => { // await test.step('should record exceptions', async () => { ... }); }); +test('Faulty middlewares inside the request segment of the Worker', async ({ request }) => { + test.skip(getRuntime() !== 'cloudflare', 'Only on Workers the request span of `withSentry` is the segment'); + const middlewareSpanPromise = waitForStreamedSpan('nextjs-16', span => { + return span.name === 'middleware GET' && span.status === 'error'; + }); + + request.get('/api/endpoint-behind-middleware', { headers: { 'x-should-throw': '1' } }).catch(() => { + // Noop + }); + + const middlewareSpan = await middlewareSpanPromise; + expect(middlewareSpan.is_segment).toBe(false); + expect(getSpanOp(middlewareSpan)).toBe('middleware'); +}); + test('Should trace outgoing fetch requests inside middleware', async ({ request }) => { test.skip(isDevMode, 'The fetch requests ends up in a separate tx in dev atm'); @@ -86,14 +128,20 @@ test('Should trace outgoing fetch requests inside middleware', async ({ request expect(fetchSpan.attributes).toMatchObject({ 'http.request.method': { value: 'GET', type: 'string' }, 'http.response.status_code': { value: 200, type: 'integer' }, - 'sentry.kind': { value: 'client', type: 'string' }, 'sentry.op': { value: 'http.client', type: 'string' }, - 'sentry.origin': { value: 'auto.http.node_fetch', type: 'string' }, 'server.address': { value: 'localhost', type: 'string' }, 'server.port': { value: 3030, type: 'integer' }, 'url.domain': { value: 'localhost', type: 'string' }, 'url.full': { value: 'http://localhost:3030/', type: 'string' }, - 'url.path': { value: '/', type: 'string' }, - 'url.scheme': { value: 'http', type: 'string' }, + // Only Node.js has the undici diagnostics channel. On the other runtimes the fetch integration of the runtime + // SDK creates this span. + ...(getRuntime() === 'node' + ? { + 'sentry.kind': { value: 'client', type: 'string' }, + 'sentry.origin': { value: 'auto.http.node_fetch', type: 'string' }, + 'url.path': { value: '/', type: 'string' }, + 'url.scheme': { value: 'http', type: 'string' }, + } + : { 'sentry.origin': { value: 'auto.http.fetch', type: 'string' } }), }); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/node-runtime-metrics.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/node-runtime-metrics.test.ts index 5295101f1e20..7f611cf3daa9 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/node-runtime-metrics.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/node-runtime-metrics.test.ts @@ -1,5 +1,7 @@ import { expect, test } from '@playwright/test'; -import { waitForMetric } from '@sentry-internal/test-utils'; +import { getRuntime, waitForMetric } from '@sentry-internal/test-utils'; + +test.skip(getRuntime() === 'cloudflare', 'Node.js runtime metrics do not exist on Workers'); const EXPECTED_ATTRIBUTES = { 'sentry.environment': { value: 'qa', type: 'string' }, diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/otel-context.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/otel-context.test.ts similarity index 95% rename from dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/otel-context.test.ts rename to dev-packages/e2e-tests/test-applications/nextjs-16/tests/otel-context.test.ts index f4fee5dbc61e..7ed91bec5393 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/otel-context.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/otel-context.test.ts @@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test'; import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; test('Keeps OpenTelemetry context values and parents', async ({ request }) => { - const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-cf-workers', 'GET /api/otel-context'); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-16', 'GET /api/otel-context'); const response = await request.get('/api/otel-context'); expect(await response.json()).toStrictEqual({ ok: true }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/propagation.test.ts similarity index 82% rename from dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/propagation.test.ts rename to dev-packages/e2e-tests/test-applications/nextjs-16/tests/propagation.test.ts index e82fb6453474..72da87388bfc 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/propagation.test.ts @@ -5,12 +5,14 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL, request } // Inbound span, outbound span and the http.client span in between all share one trace, and // `collectStreamedSpans` evaluates a single trace at a time, so requiring all three together // keeps them paired. - const spansPromise = collectStreamedSpans('nextjs-16-bun', spans => { + const spansPromise = collectStreamedSpans('nextjs-16', spans => { return ( spans.some(span => span.name === 'GET /propagation/test-outgoing-fetch' && span.is_segment) && spans.some(span => span.name === 'GET /propagation/test-outgoing-fetch/check' && span.is_segment) && spans.some( - span => getSpanOp(span) === 'http.client' && span.attributes['sentry.origin']?.value === 'auto.http.fetch', + span => + getSpanOp(span) === 'http.client' && + String(span.attributes['url.full']?.value).endsWith('/propagation/test-outgoing-fetch/check'), ) ); }); @@ -23,7 +25,9 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL, request } span => span.name === 'GET /propagation/test-outgoing-fetch/check' && span.is_segment, )!; const httpClientSpan = spans.find( - span => getSpanOp(span) === 'http.client' && span.attributes['sentry.origin']?.value === 'auto.http.fetch', + span => + getSpanOp(span) === 'http.client' && + String(span.attributes['url.full']?.value).endsWith('/propagation/test-outgoing-fetch/check'), ); expect(inboundSpan.trace_id).toStrictEqual(expect.any(String)); @@ -44,11 +48,11 @@ test('Does not propagate outgoing fetch requests not covered by tracePropagation request, }) => { // These two spans are deliberately in different traces, so they are matched by their unique names. - const inboundSpanPromise = waitForStreamedSpan('nextjs-16-bun', span => { + const inboundSpanPromise = waitForStreamedSpan('nextjs-16', span => { return span.name === 'GET /propagation/test-outgoing-fetch-external-disallowed/check' && span.is_segment; }); - const outboundSpanPromise = waitForStreamedSpan('nextjs-16-bun', span => { + const outboundSpanPromise = waitForStreamedSpan('nextjs-16', span => { return span.name === 'GET /propagation/test-outgoing-fetch-external-disallowed' && span.is_segment; }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/server-components.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/server-components.test.ts index b271cfd1ac52..1d7b0d59ffe7 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/server-components.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/server-components.test.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpansUntilSegment, getSpanOp } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getRuntime, getSpanOp } from '@sentry-internal/test-utils'; import { isTurbopackDevMode } from './isDevMode'; // Next.js emits these spans itself. The SDK attaches no op, description or function name to @@ -9,11 +9,15 @@ const nextjsSpan = { op: undefined, description: undefined, codeFunctionName: un test('Sends a span for a request to app router with URL', async ({ page }) => { test.skip(isTurbopackDevMode, 'Turbopack intermittently returns 404 for nested dynamic routes in dev mode'); + // On Workers the segment is the request span of `withSentry` from `@sentry/nextjs/cloudflare`, with the attributes + // of `@sentry/cloudflare`. On the other runtimes it is the request span of Next.js. + const pathAttribute = getRuntime() === 'cloudflare' ? 'url.path' : 'http.target'; + const spansPromise = collectStreamedSpansUntilSegment( 'nextjs-16', span => span.name === 'GET /parameterized/[one]/beep/[two]' && - String(span.attributes['http.target']?.value).startsWith('/parameterized/1337/beep/42'), + String(span.attributes[pathAttribute]?.value).startsWith('/parameterized/1337/beep/42'), ); await page.goto('/parameterized/1337/beep/42'); @@ -23,7 +27,7 @@ test('Sends a span for a request to app router with URL', async ({ page }) => { span => span.name === 'GET /parameterized/[one]/beep/[two]' && span.is_segment && - String(span.attributes['http.target']?.value).startsWith('/parameterized/1337/beep/42'), + String(span.attributes[pathAttribute]?.value).startsWith('/parameterized/1337/beep/42'), )!; expect(segmentSpan.span_id).toEqual(expect.stringMatching(/[a-f0-9]{16}/)); @@ -31,20 +35,29 @@ test('Sends a span for a request to app router with URL', async ({ page }) => { expect(segmentSpan.status).toBe('ok'); expect(segmentSpan.attributes).toMatchObject({ 'sentry.op': { value: 'http.server', type: 'string' }, - 'sentry.origin': { value: 'auto', type: 'string' }, 'sentry.sample_rate': { value: 1, type: 'integer' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, - 'http.method': { value: 'GET', type: 'string' }, 'http.response.status_code': { value: 200, type: 'integer' }, 'http.route': { value: '/parameterized/[one]/beep/[two]', type: 'string' }, - 'http.status_code': { value: 200, type: 'integer' }, - 'http.target': { value: '/parameterized/1337/beep/42', type: 'string' }, - 'sentry.kind': { value: 'server', type: 'string' }, 'next.route': { value: '/parameterized/[one]/beep/[two]', type: 'string' }, + ...(getRuntime() === 'cloudflare' + ? { + 'sentry.origin': { value: 'auto.http.cloudflare', type: 'string' }, + 'http.request.method': { value: 'GET', type: 'string' }, + 'url.path': { value: '/parameterized/1337/beep/42', type: 'string' }, + } + : { + 'sentry.origin': { value: 'auto', type: 'string' }, + 'http.method': { value: 'GET', type: 'string' }, + 'http.status_code': { value: 200, type: 'integer' }, + 'http.target': { value: '/parameterized/1337/beep/42', type: 'string' }, + 'sentry.kind': { value: 'server', type: 'string' }, + }), }); // No child span should share the segment span's name expect(spans.filter(span => !span.is_segment && span.name === segmentSpan.name)).toHaveLength(0); + expect(spans.filter(span => getSpanOp(span) === 'http.server')).toEqual([segmentSpan]); }); test('Will create spans for every server component and metadata generation functions when visiting a page', async ({ diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/trace-continuation.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/trace-continuation.test.ts similarity index 58% rename from dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/trace-continuation.test.ts rename to dev-packages/e2e-tests/test-applications/nextjs-16/tests/trace-continuation.test.ts index 447aa7df0cb6..7971e3975dbb 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/trace-continuation.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/trace-continuation.test.ts @@ -1,18 +1,21 @@ import { expect, test } from '@playwright/test'; -import { collectStreamedSpans } from '@sentry-internal/test-utils'; +import { collectStreamedSpans, getRuntime } from '@sentry-internal/test-utils'; -test('Continues an incoming trace in the segment of the Worker request', async ({ request }) => { +// On Cloudflare the segment is the request span of `withSentry`. It is matched by its origin, because Next.js can +// start its own request span under the upstream span too, which would be a second segment. +test('Continues an incoming trace in the segment of the request', async ({ request }) => { const traceId = crypto.randomUUID().replace(/-/g, ''); const parentSpanId = traceId.slice(0, 16); const spansPromise = collectStreamedSpans( - 'nextjs-16-cf-workers', + 'nextjs-16', spans => spans.some( span => span.trace_id === traceId && span.is_segment && - span.attributes['sentry.origin']?.value === 'auto.http.cloudflare', + span.parent_span_id === parentSpanId && + (getRuntime() !== 'cloudflare' || span.attributes['sentry.origin']?.value === 'auto.http.cloudflare'), ) && spans.some( span => span.trace_id === traceId && span.name === 'render route (app) /parameterized/[one]/beep/[two]', @@ -28,11 +31,13 @@ test('Continues an incoming trace in the segment of the Worker request', async ( const spans = await spansPromise; const segmentSpan = spans.find( - span => span.is_segment && span.attributes['sentry.origin']?.value === 'auto.http.cloudflare', + span => + span.is_segment && + span.parent_span_id === parentSpanId && + (getRuntime() !== 'cloudflare' || span.attributes['sentry.origin']?.value === 'auto.http.cloudflare'), )!; const renderSpan = spans.find(span => span.name === 'render route (app) /parameterized/[one]/beep/[two]')!; - expect(segmentSpan.parent_span_id).toBe(parentSpanId); expect(segmentSpan.name).toBe('GET /parameterized/[one]/beep/[two]'); expect(renderSpan.attributes['sentry.segment.id']?.value).toBe(segmentSpan.span_id); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/worker-bundle.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/worker-bundle.test.ts similarity index 95% rename from dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/worker-bundle.test.ts rename to dev-packages/e2e-tests/test-applications/nextjs-16/tests/worker-bundle.test.ts index dbd927e2053b..af16d1add37d 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/worker-bundle.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/worker-bundle.test.ts @@ -1,9 +1,12 @@ import { expect, test } from '@playwright/test'; +import { getRuntime } from '@sentry-internal/test-utils'; import * as fs from 'fs'; import { createRequire } from 'module'; import * as path from 'path'; import { isDevMode } from './isDevMode'; +test.skip(getRuntime() !== 'cloudflare', 'Checks the worker bundle of OpenNext'); + /** * The orchestrion bundler plugins are build-time-only, and their module-scope side effects break * on Workers (an unawaited `WebAssembly.compile()` crashed every cold start, issue #22794). The diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/worker.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/worker.ts similarity index 87% rename from dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/worker.ts rename to dev-packages/e2e-tests/test-applications/nextjs-16/worker.ts index 6718e015a32c..d57c23a07c81 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/worker.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/worker.ts @@ -11,6 +11,7 @@ export default Sentry.withSentry( dsn: env.E2E_TEST_DSN, tunnel: `http://localhost:3031/`, // proxy server tracesSampleRate: 1.0, + tracePropagationTargets: ['http://localhost:3030/propagation/test-outgoing-fetch/check'], integrations: [Sentry.vercelAIIntegration()], }), handler, diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/wrangler.jsonc b/dev-packages/e2e-tests/test-applications/nextjs-16/wrangler.jsonc new file mode 100644 index 000000000000..f6bfe6a4ec38 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/wrangler.jsonc @@ -0,0 +1,36 @@ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "name": "nextjs-16", + "main": "./worker.ts", + // From 2026-02-19 on, wrangler uses the native `process` of workerd, whose `cwd()` is `/bundle` during a request. + // Next.js then misses its router server context and extracts the incoming trace again, see trace-continuation.test.ts. + "compatibility_date": "2026-02-19", + "compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"], + "assets": { + "binding": "ASSETS", + "directory": ".open-next/assets", + }, + "images": { + // Enable image optimization + // see https://opennext.js.org/cloudflare/howtos/image + "binding": "IMAGES", + }, + "services": [ + { + // Self-reference service binding, the service name must match the worker name + // see https://opennext.js.org/cloudflare/caching + "binding": "WORKER_SELF_REFERENCE", + "service": "nextjs-16", + }, + ], + "d1_databases": [ + { + "binding": "DB", + "database_name": "e2e", + "database_id": "00000000-0000-0000-0000-000000000000", + }, + ], + "observability": { + "enabled": true, + }, +}