From aaca89d84c7f4b018487f1e52af902029580d8f5 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Thu, 11 Jun 2026 11:10:10 +0200 Subject: [PATCH] test instr api --- .../app/routes.ts | 1 + .../app/routes/performance/redis.tsx | 22 ++++++++++++ .../docker-compose.yml | 12 +++++++ .../global-setup.mjs | 9 +++++ .../package.json | 1 + .../playwright.config.mjs | 13 ++++--- .../tests/performance/pageload.client.test.ts | 25 +++++++++++++ .../performance/performance.server.test.ts | 16 +++++++++ .../tests/performance/redis.server.test.ts | 35 +++++++++++++++++++ 9 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/app/routes/performance/redis.tsx create mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/docker-compose.yml create mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/global-setup.mjs create mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/redis.server.test.ts diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/app/routes.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/app/routes.ts index f0c389733cfa..2754c153ad2f 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/app/routes.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/app/routes.ts @@ -16,5 +16,6 @@ export default [ route('error-middleware', 'routes/performance/error-middleware.tsx'), route('lazy-route', 'routes/performance/lazy-route.tsx'), route('fetcher-test', 'routes/performance/fetcher-test.tsx'), + route('redis', 'routes/performance/redis.tsx'), ]), ] satisfies RouteConfig; diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/app/routes/performance/redis.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/app/routes/performance/redis.tsx new file mode 100644 index 000000000000..cba8275fcf63 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/app/routes/performance/redis.tsx @@ -0,0 +1,22 @@ +import Redis from 'ioredis'; +import type { Route } from './+types/redis'; + +const redis = new Redis(); + +export async function loader() { + const key = 'cache:greeting'; + await redis.set(key, 'hello from react-router'); + const value = await redis.get(key); + + return { value }; +} + +export default function RedisPage({ loaderData }: Route.ComponentProps) { + const { value } = loaderData; + return ( +
+

Redis Page

+
{value}
+
+ ); +} diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/docker-compose.yml b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/docker-compose.yml new file mode 100644 index 000000000000..a2cb7ab5f088 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/docker-compose.yml @@ -0,0 +1,12 @@ +services: + redis: + image: redis:8 + restart: always + container_name: e2e-tests-react-router-7-instrumentation-redis + ports: + - '6379:6379' + healthcheck: + test: ['CMD', 'redis-cli', 'ping'] + interval: 1s + timeout: 3s + retries: 30 diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/global-setup.mjs b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/global-setup.mjs new file mode 100644 index 000000000000..2ecb0ef1e3fd --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/global-setup.mjs @@ -0,0 +1,9 @@ +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() { + execSync('docker compose up -d --wait', { cwd: __dirname, stdio: 'inherit' }); +} diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/package.json b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/package.json index b7e2fd8de655..3e15f15b6633 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/package.json +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/package.json @@ -7,6 +7,7 @@ "@react-router/node": "latest", "@react-router/serve": "latest", "@sentry/react-router": "file:../../packed/sentry-react-router-packed.tgz", + "ioredis": "^5.4.1", "isbot": "^5.1.17", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/playwright.config.mjs index 3ed5721107a7..70e62c2b9e3b 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/playwright.config.mjs +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/playwright.config.mjs @@ -1,8 +1,13 @@ import { getPlaywrightConfig } from '@sentry-internal/test-utils'; +import { fileURLToPath } from 'url'; -const config = getPlaywrightConfig({ - startCommand: `PORT=3030 pnpm start`, - port: 3030, -}); +const config = getPlaywrightConfig( + { + startCommand: `PORT=3030 pnpm start`, + port: 3030, + }, + // Boot Redis before the tests run, outside the webServer startup-timeout window. + { globalSetup: fileURLToPath(new URL('./global-setup.mjs', import.meta.url)) }, +); export default config; diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/pageload.client.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/pageload.client.test.ts index 0e1bf552b995..abf4bb73033a 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/pageload.client.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/pageload.client.test.ts @@ -25,6 +25,31 @@ test.describe('client - instrumentation API pageload', () => { }); }); + test('parameterizes the pageload transaction for dynamic routes', async ({ page }) => { + const txPromise = waitForTransaction(APP_NAME, async transactionEvent => { + return ( + transactionEvent.transaction === '/performance/with/:param' && + transactionEvent.contexts?.trace?.op === 'pageload' + ); + }); + + await page.goto(`/performance/with/some-param`); + + const transaction = await txPromise; + + expect(transaction).toMatchObject({ + contexts: { + trace: { + op: 'pageload', + data: { 'sentry.source': 'route' }, + }, + }, + transaction: '/performance/with/:param', + type: 'transaction', + transaction_info: { source: 'route' }, + }); + }); + test('should link server and client transactions with same trace_id', async ({ page }) => { const serverTxPromise = waitForTransaction(APP_NAME, async transactionEvent => { return ( diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/performance.server.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/performance.server.test.ts index 6deac7cf83b2..582dd4771cd9 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/performance.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/performance.server.test.ts @@ -151,4 +151,20 @@ test.describe('server - instrumentation API performance', () => { origin: 'auto.function.react_router.instrumentation_api', }); }); + + test('sends exactly one http.server transaction per request (no double-instrumentation)', async ({ page }) => { + const httpServerTransactions: Array = []; + void waitForTransaction(APP_NAME, async transactionEvent => { + if (transactionEvent.contexts?.trace?.op === 'http.server') { + httpServerTransactions.push(transactionEvent.transaction); + } + return false; + }); + + await page.goto(`/performance`); + // Give any (erroneous) duplicate transaction time to arrive before asserting. + await page.waitForTimeout(3000); + + expect(httpServerTransactions).toEqual(['GET /performance']); + }); }); diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/redis.server.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/redis.server.test.ts new file mode 100644 index 000000000000..5980ed2bddeb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/redis.server.test.ts @@ -0,0 +1,35 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; +import { APP_NAME } from '../constants'; + +test.describe('server - redis db spans (instrumentation API)', () => { + test('OTel db.redis spans nest under the native instrumentation-API http.server transaction', async ({ page }) => { + const txPromise = waitForTransaction(APP_NAME, async transactionEvent => { + return ( + transactionEvent.transaction === 'GET /performance/redis' && + (transactionEvent.spans?.some(span => span.op === 'db.redis') ?? false) + ); + }); + + await page.goto('/performance/redis'); + + const transaction = await txPromise; + + // The server transaction must come from the native instrumentation API (not the legacy handler), + // proving auto-instrumented OTel spans still share context with the React Router server span. + expect(transaction.contexts?.trace?.op).toBe('http.server'); + expect(transaction.contexts?.trace?.origin).toBe('auto.http.react_router.instrumentation_api'); + + const redisSpans = transaction.spans!.filter(span => span.op === 'db.redis'); + + // loader runs SET then GET => at least two redis command spans + expect(redisSpans.length).toBeGreaterThanOrEqual(2); + expect(redisSpans.every(span => span.data?.['db.system'] === 'redis')).toBe(true); + expect(redisSpans.every(span => typeof span.parent_span_id === 'string')).toBe(true); + expect(redisSpans.some(span => span.data?.['net.peer.port'] === 6379)).toBe(true); + + const statements = redisSpans.map(span => String(span.data?.['db.statement'] ?? '').toLowerCase()); + expect(statements.some(statement => statement.startsWith('set'))).toBe(true); + expect(statements.some(statement => statement.startsWith('get'))).toBe(true); + }); +});