From fc17dae983205d05b1093ea14e58a4e856e49d4f Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Fri, 18 Jul 2025 13:52:48 +0200 Subject: [PATCH 1/3] test(astro): Add E2E test for catch-all routes --- .../src/pages/catchAll/[...path].astro | 12 +++++ .../astro-5/tests/tracing.dynamic.test.ts | 49 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 dev-packages/e2e-tests/test-applications/astro-5/src/pages/catchAll/[...path].astro diff --git a/dev-packages/e2e-tests/test-applications/astro-5/src/pages/catchAll/[...path].astro b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/catchAll/[...path].astro new file mode 100644 index 000000000000..bb225c166241 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/catchAll/[...path].astro @@ -0,0 +1,12 @@ +--- +import Layout from '../../layouts/Layout.astro'; + +export const prerender = false; + +const params = Astro.params; + +--- + + +

params: {params}

+
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts index 36f32cd5dc0e..f98672830d32 100644 --- a/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts +++ b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts @@ -262,4 +262,53 @@ test.describe('nested SSR routes (client, server, server request)', () => { request: { url: expect.stringContaining('/api/user/myUsername123.json') }, }); }); + + test('sends parametrized pageload and server transaction names for catch-all routes', async ({ page }) => { + const clientPageloadTxnPromise = waitForTransaction('astro-5', txnEvent => { + return txnEvent?.transaction?.startsWith('/catchAll/') ?? false; + }); + + const serverPageRequestTxnPromise = waitForTransaction('astro-5', txnEvent => { + return txnEvent?.transaction?.startsWith('GET /catchAll/') ?? false; + }); + + await page.goto('/catchAll/hell0/whatever-do'); + + const clientPageloadTxn = await clientPageloadTxnPromise; + const serverPageRequestTxn = await serverPageRequestTxnPromise; + + expect(clientPageloadTxn).toMatchObject({ + transaction: '/catchAll/hell0/whatever-do', // todo: parametrize to '/catchAll/[...path]' + transaction_info: { source: 'url' }, + contexts: { + trace: { + op: 'pageload', + origin: 'auto.pageload.browser', + data: { + 'sentry.op': 'pageload', + 'sentry.origin': 'auto.pageload.browser', + 'sentry.source': 'url', + }, + }, + }, + }); + + expect(serverPageRequestTxn).toMatchObject({ + transaction: 'GET /catchAll/[path]', + transaction_info: { source: 'route' }, + contexts: { + trace: { + op: 'http.server', + origin: 'auto.http.astro', + data: { + 'sentry.op': 'http.server', + 'sentry.origin': 'auto.http.astro', + 'sentry.source': 'route', + url: expect.stringContaining('/catchAll/hell0/whatever-do'), + }, + }, + }, + request: { url: expect.stringContaining('/catchAll/hell0/whatever-do') }, + }); + }); }); From fda4f240978ee6e99a546c0344aa5d86a2de9b6d Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Fri, 18 Jul 2025 13:55:11 +0200 Subject: [PATCH 2/3] test(astro): Add E2E test for catch-all routes --- .../test-applications/astro-5/tests/tracing.dynamic.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts index f98672830d32..9315d3d3ea84 100644 --- a/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts +++ b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts @@ -233,7 +233,7 @@ test.describe('nested SSR routes (client, server, server request)', () => { expect(serverRequestHTTPClientSpan).toMatchObject({ op: 'http.client', origin: 'auto.http.otel.node_fetch', - description: 'GET http://localhost:3030/api/user/myUsername123.json', // todo: parametrize (this is just a span though - no transaction) + description: 'GET http://localhost:3030/api/user/myUsername123.json', // http.client does not need to be parametrized data: { 'sentry.op': 'http.client', 'sentry.origin': 'auto.http.otel.node_fetch', From d12fb74479486ef937d89f74e05c4126c44a2e1c Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Mon, 21 Jul 2025 11:07:26 +0200 Subject: [PATCH 3/3] test(astro): Add dynamic routes astro-4 tests --- .../src/pages/api/user/[userId].json.js | 10 + .../src/pages/catchAll/[...path].astro | 12 ++ .../src/pages/user-page/[userId].astro | 17 ++ .../astro-4/tests/tracing.dynamic.test.ts | 193 ++++++++++++++++++ 4 files changed, 232 insertions(+) create mode 100644 dev-packages/e2e-tests/test-applications/astro-4/src/pages/api/user/[userId].json.js create mode 100644 dev-packages/e2e-tests/test-applications/astro-4/src/pages/catchAll/[...path].astro create mode 100644 dev-packages/e2e-tests/test-applications/astro-4/src/pages/user-page/[userId].astro diff --git a/dev-packages/e2e-tests/test-applications/astro-4/src/pages/api/user/[userId].json.js b/dev-packages/e2e-tests/test-applications/astro-4/src/pages/api/user/[userId].json.js new file mode 100644 index 000000000000..562bbf7b2cb4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/src/pages/api/user/[userId].json.js @@ -0,0 +1,10 @@ +export const prerender = false; + +export function GET({ params }) { + return new Response( + JSON.stringify({ + greeting: `Hello ${params.userId}`, + userId: params.userId, + }), + ); +} diff --git a/dev-packages/e2e-tests/test-applications/astro-4/src/pages/catchAll/[...path].astro b/dev-packages/e2e-tests/test-applications/astro-4/src/pages/catchAll/[...path].astro new file mode 100644 index 000000000000..bb225c166241 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/src/pages/catchAll/[...path].astro @@ -0,0 +1,12 @@ +--- +import Layout from '../../layouts/Layout.astro'; + +export const prerender = false; + +const params = Astro.params; + +--- + + +

params: {params}

+
diff --git a/dev-packages/e2e-tests/test-applications/astro-4/src/pages/user-page/[userId].astro b/dev-packages/e2e-tests/test-applications/astro-4/src/pages/user-page/[userId].astro new file mode 100644 index 000000000000..e35bd3a34d97 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/src/pages/user-page/[userId].astro @@ -0,0 +1,17 @@ +--- +import Layout from '../../layouts/Layout.astro'; + +export const prerender = false; + +const { userId } = Astro.params; + +const response = await fetch(Astro.url.origin + `/api/user/${userId}.json`) +const data = await response.json(); + +--- + + +

{data.greeting}

+ +

data: {JSON.stringify(data)}

+
diff --git a/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.dynamic.test.ts b/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.dynamic.test.ts index 644afc377545..2699e827718e 100644 --- a/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.dynamic.test.ts +++ b/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.dynamic.test.ts @@ -120,3 +120,196 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => { }); }); }); + +test.describe('nested SSR routes (client, server, server request)', () => { + /** The user-page route fetches from an endpoint and creates a deeply nested span structure: + * pageload — /user-page/myUsername123 + * ├── browser.** — multiple browser spans + * └── browser.request — /user-page/myUsername123 + * └── http.server — GET /user-page/[userId] (SSR page request) + * └── http.client — GET /api/user/myUsername123.json (executing fetch call from SSR page - span) + * └── http.server — GET /api/user/myUsername123.json (server request) + */ + test('sends connected server and client pageload and request spans with the same trace id', async ({ page }) => { + const clientPageloadTxnPromise = waitForTransaction('astro-4', txnEvent => { + return txnEvent?.transaction?.startsWith('/user-page/') ?? false; + }); + + const serverPageRequestTxnPromise = waitForTransaction('astro-4', txnEvent => { + return txnEvent?.transaction?.startsWith('GET /user-page/') ?? false; + }); + + const serverHTTPServerRequestTxnPromise = waitForTransaction('astro-4', txnEvent => { + return txnEvent?.transaction?.startsWith('GET /api/user/') ?? false; + }); + + await page.goto('/user-page/myUsername123'); + + const clientPageloadTxn = await clientPageloadTxnPromise; + const serverPageRequestTxn = await serverPageRequestTxnPromise; + const serverHTTPServerRequestTxn = await serverHTTPServerRequestTxnPromise; + const serverRequestHTTPClientSpan = serverPageRequestTxn.spans?.find( + span => span.op === 'http.client' && span.description?.includes('/api/user/'), + ); + + const clientPageloadTraceId = clientPageloadTxn.contexts?.trace?.trace_id; + + // Verify all spans have the same trace ID + expect(clientPageloadTraceId).toEqual(serverPageRequestTxn.contexts?.trace?.trace_id); + expect(clientPageloadTraceId).toEqual(serverHTTPServerRequestTxn.contexts?.trace?.trace_id); + expect(clientPageloadTraceId).toEqual(serverRequestHTTPClientSpan?.trace_id); + + // serverPageRequest has no parent (root span) + expect(serverPageRequestTxn.contexts?.trace?.parent_span_id).toBeUndefined(); + + // clientPageload's parent and serverRequestHTTPClient's parent is serverPageRequest + const serverPageRequestSpanId = serverPageRequestTxn.contexts?.trace?.span_id; + expect(clientPageloadTxn.contexts?.trace?.parent_span_id).toEqual(serverPageRequestSpanId); + expect(serverRequestHTTPClientSpan?.parent_span_id).toEqual(serverPageRequestSpanId); + + // serverHTTPServerRequest's parent is serverRequestHTTPClient + expect(serverHTTPServerRequestTxn.contexts?.trace?.parent_span_id).toEqual(serverRequestHTTPClientSpan?.span_id); + }); + + test('sends parametrized pageload, server and API request transaction names', async ({ page }) => { + const clientPageloadTxnPromise = waitForTransaction('astro-4', txnEvent => { + return txnEvent?.transaction?.startsWith('/user-page/') ?? false; + }); + + const serverPageRequestTxnPromise = waitForTransaction('astro-4', txnEvent => { + return txnEvent?.transaction?.startsWith('GET /user-page/') ?? false; + }); + + const serverHTTPServerRequestTxnPromise = waitForTransaction('astro-4', txnEvent => { + return txnEvent?.transaction?.startsWith('GET /api/user/') ?? false; + }); + + await page.goto('/user-page/myUsername123'); + + const clientPageloadTxn = await clientPageloadTxnPromise; + const serverPageRequestTxn = await serverPageRequestTxnPromise; + const serverHTTPServerRequestTxn = await serverHTTPServerRequestTxnPromise; + + const serverRequestHTTPClientSpan = serverPageRequestTxn.spans?.find( + span => span.op === 'http.client' && span.description?.includes('/api/user/'), + ); + + // Client pageload transaction - actual URL with pageload operation + expect(clientPageloadTxn).toMatchObject({ + transaction: '/user-page/myUsername123', // todo: parametrize + transaction_info: { source: 'url' }, + contexts: { + trace: { + op: 'pageload', + origin: 'auto.pageload.browser', + data: { + 'sentry.op': 'pageload', + 'sentry.origin': 'auto.pageload.browser', + 'sentry.source': 'url', + }, + }, + }, + }); + + // Server page request transaction - parametrized transaction name with actual URL in data + expect(serverPageRequestTxn).toMatchObject({ + transaction: 'GET /user-page/[userId]', + transaction_info: { source: 'route' }, + contexts: { + trace: { + op: 'http.server', + origin: 'auto.http.astro', + data: { + 'sentry.op': 'http.server', + 'sentry.origin': 'auto.http.astro', + 'sentry.source': 'route', + url: expect.stringContaining('/user-page/myUsername123'), + }, + }, + }, + request: { url: expect.stringContaining('/user-page/myUsername123') }, + }); + + // HTTP client span - actual API URL with client operation + expect(serverRequestHTTPClientSpan).toMatchObject({ + op: 'http.client', + origin: 'auto.http.otel.node_fetch', + description: 'GET http://localhost:3030/api/user/myUsername123.json', // http.client does not need to be parametrized + data: { + 'sentry.op': 'http.client', + 'sentry.origin': 'auto.http.otel.node_fetch', + 'url.full': expect.stringContaining('/api/user/myUsername123.json'), + 'url.path': '/api/user/myUsername123.json', + url: expect.stringContaining('/api/user/myUsername123.json'), + }, + }); + + // Server HTTP request transaction - should be parametrized + expect(serverHTTPServerRequestTxn).toMatchObject({ + transaction: 'GET /api/user/myUsername123.json', // todo: parametrize + transaction_info: { source: 'route' }, + contexts: { + trace: { + op: 'http.server', + origin: 'auto.http.astro', + data: { + 'sentry.op': 'http.server', + 'sentry.origin': 'auto.http.astro', + 'sentry.source': 'route', + url: expect.stringContaining('/api/user/myUsername123.json'), + }, + }, + }, + request: { url: expect.stringContaining('/api/user/myUsername123.json') }, + }); + }); + + test('sends parametrized pageload and server transaction names for catch-all routes', async ({ page }) => { + const clientPageloadTxnPromise = waitForTransaction('astro-4', txnEvent => { + return txnEvent?.transaction?.startsWith('/catchAll/') ?? false; + }); + + const serverPageRequestTxnPromise = waitForTransaction('astro-4', txnEvent => { + return txnEvent?.transaction?.startsWith('GET /catchAll/') ?? false; + }); + + await page.goto('/catchAll/hell0/whatever-do'); + + const clientPageloadTxn = await clientPageloadTxnPromise; + const serverPageRequestTxn = await serverPageRequestTxnPromise; + + expect(clientPageloadTxn).toMatchObject({ + transaction: '/catchAll/hell0/whatever-do', // todo: parametrize + transaction_info: { source: 'url' }, + contexts: { + trace: { + op: 'pageload', + origin: 'auto.pageload.browser', + data: { + 'sentry.op': 'pageload', + 'sentry.origin': 'auto.pageload.browser', + 'sentry.source': 'url', + }, + }, + }, + }); + + expect(serverPageRequestTxn).toMatchObject({ + transaction: 'GET /catchAll/[path]', + transaction_info: { source: 'route' }, + contexts: { + trace: { + op: 'http.server', + origin: 'auto.http.astro', + data: { + 'sentry.op': 'http.server', + 'sentry.origin': 'auto.http.astro', + 'sentry.source': 'route', + url: expect.stringContaining('/catchAll/hell0/whatever-do'), + }, + }, + }, + request: { url: expect.stringContaining('/catchAll/hell0/whatever-do') }, + }); + }); +});