{
- let shellRendered = false;
- const userAgent = request.headers.get('user-agent');
-
- const body = await renderToReadableStream(, {
- signal: request.signal,
- onError(error: unknown) {
- responseStatusCode = 500;
- // Errors thrown after the shell has flushed can't change the status code, so surface them.
- if (shellRendered) {
- // eslint-disable-next-line no-console
- console.error(error);
- }
- },
- });
- shellRendered = true;
-
- // Bots need complete markup rather than a streamed shell.
- if (userAgent && isbot(userAgent)) {
- await body.allReady;
- }
-
- responseHeaders.set('Content-Type', 'text/html');
-
- return new Response(Sentry.injectTraceMetaTags(body), {
- headers: responseHeaders,
- status: responseStatusCode,
- });
-}
-
-export const handleError: HandleErrorFunction = (error, { request }) => {
- // React Router aborts interrupted requests, don't report those.
- if (!request.signal.aborted) {
- Sentry.captureException(error);
- // eslint-disable-next-line no-console
- console.error(error);
- }
-};
-
-export default Sentry.wrapSentryHandleRequest(handleRequest);
diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/app/root.tsx b/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/app/root.tsx
deleted file mode 100644
index c09b53b99d46..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/app/root.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { Links, Meta, Outlet, Scripts, ScrollRestoration } from 'react-router';
-
-export function Layout({ children }: { children: React.ReactNode }) {
- return (
-
-
-
-
-
-
-
-
- {children}
-
-
-
-
- );
-}
-
-export default function App() {
- return ;
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/app/routes.ts b/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/app/routes.ts
deleted file mode 100644
index 744ffc9485de..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/app/routes.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { index, prefix, route, type RouteConfig } from '@react-router/dev/routes';
-
-export default [
- index('routes/home.tsx'),
- ...prefix('performance', [route('db-mysql', 'routes/performance/db-mysql.tsx')]),
-] satisfies RouteConfig;
diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/app/routes/home.tsx b/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/app/routes/home.tsx
deleted file mode 100644
index 7e59685f3987..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/app/routes/home.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Link } from 'react-router';
-
-export default function Home() {
- return (
-
-
react-router-8-cloudflare
- db-mysql
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/docker-compose.yml b/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/docker-compose.yml
deleted file mode 100644
index c03828e36b43..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/docker-compose.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-services:
- db:
- image: mysql:8.0
- restart: always
- container_name: e2e-tests-react-router-8-cloudflare-mysql
- # The `mysql` 2.x driver doesn't speak MySQL 8's default
- # `caching_sha2_password` auth, so force the legacy plugin.
- command: ['--default-authentication-plugin=mysql_native_password']
- ports:
- - '3306:3306'
- environment:
- MYSQL_ROOT_PASSWORD: docker
- healthcheck:
- test: ['CMD-SHELL', 'mysqladmin ping -h 127.0.0.1 -uroot -pdocker']
- interval: 2s
- timeout: 3s
- retries: 30
- start_period: 10s
diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/global-setup.mjs b/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/global-setup.mjs
deleted file mode 100644
index b0695e3731f7..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/global-setup.mjs
+++ /dev/null
@@ -1,13 +0,0 @@
-import { execSync } from 'child_process';
-import { dirname } from 'path';
-import { fileURLToPath } from 'url';
-
-const __dirname = dirname(fileURLToPath(import.meta.url));
-
-export default async function globalSetup() {
- // `--wait` blocks until the healthcheck passes, so the first request can connect.
- execSync('docker compose up -d --wait', {
- cwd: __dirname,
- stdio: 'inherit',
- });
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/global-teardown.mjs b/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/global-teardown.mjs
deleted file mode 100644
index 2742279431ad..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/global-teardown.mjs
+++ /dev/null
@@ -1,12 +0,0 @@
-import { execSync } from 'child_process';
-import { dirname } from 'path';
-import { fileURLToPath } from 'url';
-
-const __dirname = dirname(fileURLToPath(import.meta.url));
-
-export default async function globalTeardown() {
- execSync('docker compose down --volumes', {
- cwd: __dirname,
- stdio: 'inherit',
- });
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/package.json b/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/package.json
deleted file mode 100644
index 094e2e4ebfbe..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/package.json
+++ /dev/null
@@ -1,45 +0,0 @@
-{
- "name": "react-router-8-cloudflare",
- "version": "0.1.0",
- "type": "module",
- "private": true,
- "dependencies": {
- "@sentry/cloudflare": "file:../../packed/sentry-cloudflare-packed.tgz",
- "@sentry/react-router": "file:../../packed/sentry-react-router-packed.tgz",
- "isbot": "^5.1.17",
- "mysql": "^2.18.1",
- "react": "^19.2.0",
- "react-dom": "^19.2.0",
- "react-router": "^8"
- },
- "devDependencies": {
- "@cloudflare/vite-plugin": "^1.35.0",
- "@cloudflare/workers-types": "^4.20260504.0",
- "@playwright/test": "~1.63.0",
- "@react-router/dev": "^8",
- "@sentry-internal/test-utils": "link:../../../test-utils",
- "@types/mysql": "^2.15.26",
- "@types/react": "^19.2.0",
- "@types/react-dom": "^19.2.0",
- "typescript": "^5.9.0",
- "vite": "7.3.5",
- "wrangler": "^4.120.0"
- },
- "scripts": {
- "build": "react-router build",
- "dev": "react-router dev",
- "preview": "wrangler dev --var \"E2E_TEST_DSN:$E2E_TEST_DSN\" --port 3030",
- "proxy": "node start-event-proxy.mjs",
- "typecheck": "react-router typegen && tsc",
- "clean": "npx rimraf node_modules pnpm-lock.yaml",
- "test:build": "pnpm install && pnpm build",
- "test:assert": "pnpm typecheck && TEST_ENV=production playwright test"
- },
- "volta": {
- "node": "24.15.0",
- "extends": "../../package.json"
- },
- "sentryTest": {
- "optional": true
- }
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/playwright.config.mjs
deleted file mode 100644
index 579222812424..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/playwright.config.mjs
+++ /dev/null
@@ -1,14 +0,0 @@
-import { getPlaywrightConfig } from '@sentry-internal/test-utils';
-
-const config = getPlaywrightConfig(
- {
- startCommand: 'pnpm preview',
- port: 3030,
- },
- {
- globalSetup: './global-setup.mjs',
- globalTeardown: './global-teardown.mjs',
- },
-);
-
-export default config;
diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/public/favicon.ico b/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/public/favicon.ico
deleted file mode 100644
index 5dbdfcddcb14..000000000000
Binary files a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/public/favicon.ico and /dev/null differ
diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/react-router.config.ts b/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/react-router.config.ts
deleted file mode 100644
index 51e8967770b3..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/react-router.config.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import type { Config } from '@react-router/dev/config';
-
-export default {
- ssr: true,
-} satisfies Config;
diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/start-event-proxy.mjs
deleted file mode 100644
index d78c58fa1714..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/start-event-proxy.mjs
+++ /dev/null
@@ -1,6 +0,0 @@
-import { startEventProxyServer } from '@sentry-internal/test-utils';
-
-startEventProxyServer({
- port: 3031,
- proxyServerName: 'react-router-8-cloudflare',
-});
diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/tests/db.test.ts b/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/tests/db.test.ts
deleted file mode 100644
index 1dfa920d1625..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/tests/db.test.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { collectStreamedSpans, getSpanOp } from '@sentry-internal/test-utils';
-
-// Under span streaming the mysql span name is the query summary, so both queries here are named
-// `SELECT`. `db.query.text` is what tells them apart.
-test('a real mysql query emits a db span with orchestrion-channel attributes', async ({ request }) => {
- const spansPromise = collectStreamedSpans(
- 'react-router-8-cloudflare',
- spansOfTrace =>
- spansOfTrace.some(span => getSpanOp(span) === 'http.server' && span.is_segment) &&
- spansOfTrace.some(span => getSpanOp(span) === 'db'),
- );
-
- const res = await request.get('/performance/db-mysql');
- expect(res.status()).toBe(200);
-
- const spans = await spansPromise;
- const dbSpans = spans.filter(span => getSpanOp(span) === 'db');
-
- const firstQuery = dbSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT ? + ? AS solution');
- expect(firstQuery).toBeDefined();
- expect(firstQuery!.name).toBe('SELECT');
- expect(firstQuery!.attributes['sentry.origin']?.value).toBe('auto.db.mysql');
- expect(firstQuery!.attributes['db.system.name']?.value).toBe('mysql');
- expect(firstQuery!.attributes['server.address']?.value).toBe('127.0.0.1');
- expect(firstQuery!.attributes['server.port']?.value).toBe(3306);
- expect(firstQuery!.attributes['db.user']?.value).toBe('root');
-});
-
-test('a nested query lands on the same segment (async context restored)', async ({ request }) => {
- const spansPromise = collectStreamedSpans(
- 'react-router-8-cloudflare',
- spansOfTrace =>
- spansOfTrace.some(span => getSpanOp(span) === 'http.server' && span.is_segment) &&
- spansOfTrace.filter(span => getSpanOp(span) === 'db').length >= 2,
- );
-
- const res = await request.get('/performance/db-mysql');
- expect(res.status()).toBe(200);
-
- const spans = await spansPromise;
-
- // These are scoped to the http.server segment's trace, so both queries landing here is what proves
- // the nested one kept the async context.
- const queryTexts = spans
- .filter(span => getSpanOp(span) === 'db')
- .map(span => span.attributes['db.query.text']?.value);
- expect(queryTexts).toContain('SELECT ? + ? AS solution');
- expect(queryTexts).toContain('SELECT NOW()');
-});
diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/tsconfig.json b/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/tsconfig.json
deleted file mode 100644
index 573bcd3ad911..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/tsconfig.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "compilerOptions": {
- "lib": ["DOM", "DOM.Iterable", "ES2022"],
- // workers-types rather than node: the server runs in workerd.
- "types": ["@cloudflare/workers-types", "vite/client"],
- "target": "ES2022",
- "module": "ES2022",
- "moduleResolution": "bundler",
- "jsx": "react-jsx",
- "rootDirs": [".", "./.react-router/types"],
- "baseUrl": ".",
-
- "esModuleInterop": true,
- "verbatimModuleSyntax": true,
- "noEmit": true,
- "resolveJsonModule": true,
- "skipLibCheck": true,
- "strict": true
- },
- "include": ["**/*", "**/.server/**/*", "**/.client/**/*", ".react-router/types/**/*"]
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/vite.config.ts b/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/vite.config.ts
deleted file mode 100644
index cd07d7a3fe03..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/vite.config.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { cloudflare } from '@cloudflare/vite-plugin';
-import { reactRouter } from '@react-router/dev/vite';
-import { sentryReactRouter } from '@sentry/react-router/vite';
-import { defineConfig } from 'vite';
-
-export default defineConfig(async config => ({
- plugins: [
- cloudflare({ viteEnvironment: { name: 'ssr' } }),
- reactRouter(),
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ...((await sentryReactRouter({ sourcemaps: { disable: true } }, config)) as any[]),
- ],
-}));
diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/workers/app.ts b/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/workers/app.ts
deleted file mode 100644
index d9a6c5451d72..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/workers/app.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import * as Sentry from '@sentry/cloudflare';
-import { createRequestHandler } from 'react-router';
-
-const requestHandler = createRequestHandler(() => import('virtual:react-router/server-build'), import.meta.env.MODE);
-
-interface Env {
- E2E_TEST_DSN: string;
-}
-
-// `withSentry` is what reads the build-time orchestrion marker; without it the injected
-// `diagnostics_channel` publishers would fire with nobody subscribed.
-export default Sentry.withSentry(
- (env: Env) => ({
- dsn: env.E2E_TEST_DSN,
- tunnel: 'http://localhost:3031/',
- tracesSampleRate: 1.0,
- environment: 'qa', // dynamic sampling bias to keep transactions
- }),
- {
- // No load context: React Router 8 takes a `RouterContextProvider`, not v7's `{ cloudflare }`
- // object, and nothing here reads bindings from a loader.
- async fetch(request) {
- return requestHandler(request);
- },
- } satisfies ExportedHandler,
-);
diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/wrangler.jsonc b/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/wrangler.jsonc
deleted file mode 100644
index 32521671d64e..000000000000
--- a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/wrangler.jsonc
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "$schema": "node_modules/wrangler/config-schema.json",
- "name": "react-router-8-cloudflare",
- "compatibility_date": "2026-06-29",
- "compatibility_flags": ["nodejs_compat"],
- "main": "./workers/app.ts",
- "assets": {
- "directory": "./build/client",
- },
- "observability": {
- "enabled": true,
- },
-}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-framework/app/routes.ts b/dev-packages/e2e-tests/test-applications/react-router-8-framework/app/routes.ts
index 3e3839295b1d..1e2b1b1dfd10 100644
--- a/dev-packages/e2e-tests/test-applications/react-router-8-framework/app/routes.ts
+++ b/dev-packages/e2e-tests/test-applications/react-router-8-framework/app/routes.ts
@@ -20,5 +20,6 @@ export default [
route('server-action', 'routes/performance/server-action.tsx'),
route('with-middleware', 'routes/performance/with-middleware.tsx'),
route('redis', 'routes/performance/redis.tsx'),
+ route('db-mysql', 'routes/performance/db-mysql.tsx'),
]),
] satisfies RouteConfig;
diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/app/routes/performance/db-mysql.tsx b/dev-packages/e2e-tests/test-applications/react-router-8-framework/app/routes/performance/db-mysql.tsx
similarity index 87%
rename from dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/app/routes/performance/db-mysql.tsx
rename to dev-packages/e2e-tests/test-applications/react-router-8-framework/app/routes/performance/db-mysql.tsx
index ea2ac1fcea61..55d73a44004c 100644
--- a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/app/routes/performance/db-mysql.tsx
+++ b/dev-packages/e2e-tests/test-applications/react-router-8-framework/app/routes/performance/db-mysql.tsx
@@ -1,8 +1,8 @@
import mysql from 'mysql';
import type { Route } from './+types/db-mysql';
-// These queries produce `db` spans from the build-time orchestrion transform alone — workerd can't
-// monkey-patch requires, so there's no OTel hook involved.
+// On Cloudflare these `db` spans come from the build-time orchestrion transform alone, because workerd
+// can't monkey-patch requires.
export async function loader(): Promise<{ status: string }> {
// Connect inside the loader: workerd forbids I/O in global scope.
const connection = mysql.createConnection({
diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-framework/docker-compose.yml b/dev-packages/e2e-tests/test-applications/react-router-8-framework/docker-compose.yml
index 27367c5ae12a..dfb0c2de26c0 100644
--- a/dev-packages/e2e-tests/test-applications/react-router-8-framework/docker-compose.yml
+++ b/dev-packages/e2e-tests/test-applications/react-router-8-framework/docker-compose.yml
@@ -10,3 +10,20 @@ services:
interval: 1s
timeout: 3s
retries: 30
+ mysql:
+ image: mysql:8.0
+ restart: always
+ container_name: e2e-tests-react-router-8-mysql
+ # The `mysql` 2.x driver doesn't speak MySQL 8's default
+ # `caching_sha2_password` auth, so force the legacy plugin.
+ command: ['--default-authentication-plugin=mysql_native_password']
+ ports:
+ - '3306:3306'
+ environment:
+ MYSQL_ROOT_PASSWORD: docker
+ healthcheck:
+ test: ['CMD-SHELL', 'mysqladmin ping -h 127.0.0.1 -uroot -pdocker']
+ interval: 2s
+ timeout: 3s
+ retries: 30
+ start_period: 10s
diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-framework/global-setup.mjs b/dev-packages/e2e-tests/test-applications/react-router-8-framework/global-setup.mjs
index 55af1feaa14c..a7ab404749f5 100644
--- a/dev-packages/e2e-tests/test-applications/react-router-8-framework/global-setup.mjs
+++ b/dev-packages/e2e-tests/test-applications/react-router-8-framework/global-setup.mjs
@@ -13,7 +13,7 @@ export default async function globalSetup() {
// part of the same project - but the container name is fixed, so the daemon
// still refuses to create a new one. Force-remove any stale leftover first.
try {
- execSync('docker rm -f e2e-tests-react-router-8-redis', { stdio: 'ignore' });
+ execSync('docker rm -f e2e-tests-react-router-8-redis e2e-tests-react-router-8-mysql', { stdio: 'ignore' });
} catch {
// no stale container to remove
}
diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-framework/package.json b/dev-packages/e2e-tests/test-applications/react-router-8-framework/package.json
index 55a78a9f66c7..9df6c921ef55 100644
--- a/dev-packages/e2e-tests/test-applications/react-router-8-framework/package.json
+++ b/dev-packages/e2e-tests/test-applications/react-router-8-framework/package.json
@@ -12,10 +12,12 @@
"@sentry/cloudflare": "file:../../packed/sentry-cloudflare-packed.tgz",
"@sentry/react-router": "file:../../packed/sentry-react-router-packed.tgz",
"ioredis": "^5.11.1",
- "isbot": "^5.1.43"
+ "isbot": "^5.1.43",
+ "mysql": "^2.18.1"
},
"devDependencies": {
"@cloudflare/vite-plugin": "^1.35.0",
+ "@types/mysql": "^2.15.26",
"@types/react": "19.2.17",
"@types/react-dom": "19.2.3",
"@types/node": "^22",
diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-framework/tests/performance/mysql.server.test.ts b/dev-packages/e2e-tests/test-applications/react-router-8-framework/tests/performance/mysql.server.test.ts
new file mode 100644
index 000000000000..20377d10c47d
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/react-router-8-framework/tests/performance/mysql.server.test.ts
@@ -0,0 +1,53 @@
+import { expect, test } from '@playwright/test';
+import { collectStreamedSpans, getSpanOp } from '@sentry-internal/test-utils';
+import { APP_NAME } from '../constants';
+
+// Under span streaming the mysql span name is the query summary, so both queries here are named
+// `SELECT`. `db.query.text` is what tells them apart.
+test.describe('server - mysql db spans', () => {
+ test('a real mysql query emits a db span with orchestrion-channel attributes', async ({ request }) => {
+ const spansPromise = collectStreamedSpans(
+ APP_NAME,
+ spansOfTrace =>
+ spansOfTrace.some(span => getSpanOp(span) === 'http.server' && span.is_segment) &&
+ spansOfTrace.some(span => getSpanOp(span) === 'db'),
+ );
+
+ const res = await request.get('/performance/db-mysql');
+ expect(res.status()).toBe(200);
+
+ const spans = await spansPromise;
+ const dbSpans = spans.filter(span => getSpanOp(span) === 'db');
+
+ const firstQuery = dbSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT ? + ? AS solution');
+ expect(firstQuery).toBeDefined();
+ expect(firstQuery!.name).toBe('SELECT');
+ expect(firstQuery!.attributes['sentry.origin']?.value).toBe('auto.db.mysql');
+ expect(firstQuery!.attributes['db.system.name']?.value).toBe('mysql');
+ expect(firstQuery!.attributes['server.address']?.value).toBe('127.0.0.1');
+ expect(firstQuery!.attributes['server.port']?.value).toBe(3306);
+ expect(firstQuery!.attributes['db.user']?.value).toBe('root');
+ });
+
+ test('a nested query lands on the same segment (async context restored)', async ({ request }) => {
+ const spansPromise = collectStreamedSpans(
+ APP_NAME,
+ spansOfTrace =>
+ spansOfTrace.some(span => getSpanOp(span) === 'http.server' && span.is_segment) &&
+ spansOfTrace.filter(span => getSpanOp(span) === 'db').length >= 2,
+ );
+
+ const res = await request.get('/performance/db-mysql');
+ expect(res.status()).toBe(200);
+
+ const spans = await spansPromise;
+
+ // These are scoped to the http.server segment's trace, so both queries landing here is what proves
+ // the nested one kept the async context.
+ const queryTexts = spans
+ .filter(span => getSpanOp(span) === 'db')
+ .map(span => span.attributes['db.query.text']?.value);
+ expect(queryTexts).toContain('SELECT ? + ? AS solution');
+ expect(queryTexts).toContain('SELECT NOW()');
+ });
+});