diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tests/client-transactions.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tests/client-transactions.test.ts index d85d9d82747d..8a62126dbe15 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tests/client-transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tests/client-transactions.test.ts @@ -11,6 +11,16 @@ test('Sends a pageload transaction to Sentry', async ({ page }) => { const transactionEvent = await transactionPromise; expect(transactionEvent).toBeDefined(); + expect(transactionEvent.contexts?.trace?.data).toEqual( + expect.objectContaining({ + 'sentry.origin': 'auto.pageload.remix', + 'sentry.source': 'url', + 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/$/), + 'url.path': '/', + }), + ); + // no url.template because the route isn't parameterized (sentry.source: 'url') + expect(transactionEvent.contexts?.trace?.data).not.toHaveProperty('url.template'); }); test('Sends a navigation transaction to Sentry', async ({ page }) => { @@ -26,6 +36,14 @@ test('Sends a navigation transaction to Sentry', async ({ page }) => { const transactionEvent = await transactionPromise; expect(transactionEvent).toBeDefined(); + expect(transactionEvent.contexts?.trace?.data).toEqual( + expect.objectContaining({ + 'sentry.source': 'route', + 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/user\/5$/), + 'url.path': '/user/5', + 'url.template': '/user/:id', + }), + ); }); test('Sends a navigation transaction with parameterized route to Sentry', async ({ page }) => { @@ -41,7 +59,15 @@ test('Sends a navigation transaction with parameterized route to Sentry', async const transactionEvent = await transactionPromise; expect(transactionEvent).toBeDefined(); - expect(transactionEvent.transaction).toBeTruthy(); + expect(transactionEvent.transaction).toBe('/user/:id'); + expect(transactionEvent.contexts?.trace?.data).toEqual( + expect.objectContaining({ + 'sentry.source': 'route', + 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/user\/5$/), + 'url.path': '/user/5', + 'url.template': '/user/:id', + }), + ); }); test('Renders `sentry-trace` and `baggage` meta tags for the root route', async ({ page }) => { diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express/tests/client-transactions.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-express/tests/client-transactions.test.ts index c468a2276fb3..f47a001fa7c7 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-express/tests/client-transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express/tests/client-transactions.test.ts @@ -11,6 +11,16 @@ test('Sends a pageload transaction to Sentry', async ({ page }) => { const transactionEvent = await transactionPromise; expect(transactionEvent).toBeDefined(); + expect(transactionEvent.contexts?.trace?.data).toEqual( + expect.objectContaining({ + 'sentry.origin': 'auto.pageload.remix', + 'sentry.source': 'url', + 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/$/), + 'url.path': '/', + }), + ); + // no url.template because the route isn't parameterized (sentry.source: 'url') + expect(transactionEvent.contexts?.trace?.data).not.toHaveProperty('url.template'); }); test('Sends a navigation transaction to Sentry', async ({ page }) => { @@ -26,6 +36,14 @@ test('Sends a navigation transaction to Sentry', async ({ page }) => { const transactionEvent = await transactionPromise; expect(transactionEvent).toBeDefined(); + expect(transactionEvent.contexts?.trace?.data).toEqual( + expect.objectContaining({ + 'sentry.source': 'route', + 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/user\/5$/), + 'url.path': '/user/5', + 'url.template': '/user/:id', + }), + ); }); test('Renders `sentry-trace` and `baggage` meta tags for the root route', async ({ page }) => { diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-non-vite/tests/client-transactions.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-non-vite/tests/client-transactions.test.ts index cf5098686759..93d47e335600 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-non-vite/tests/client-transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-non-vite/tests/client-transactions.test.ts @@ -11,6 +11,16 @@ test('Sends a pageload transaction to Sentry', async ({ page }) => { const transactionEvent = await transactionPromise; expect(transactionEvent).toBeDefined(); + expect(transactionEvent.contexts?.trace?.data).toEqual( + expect.objectContaining({ + // No manifest available (legacy app without the Sentry Vite plugin), so source falls back to 'route' + // and url.template uses the route id instead of a parameterized URL path. + 'sentry.source': 'route', + 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/$/), + 'url.path': '/', + 'url.template': 'routes/_index', + }), + ); }); test('Sends a navigation transaction to Sentry', async ({ page }) => { @@ -26,6 +36,14 @@ test('Sends a navigation transaction to Sentry', async ({ page }) => { const transactionEvent = await transactionPromise; expect(transactionEvent).toBeDefined(); + expect(transactionEvent.contexts?.trace?.data).toEqual( + expect.objectContaining({ + 'sentry.source': 'route', + 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/user\/5$/), + 'url.path': '/user/5', + 'url.template': 'routes/user.$id', + }), + ); }); test('Renders `sentry-trace` and `baggage` meta tags for the root route', async ({ page }) => { diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/tests/client-transactions.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/tests/client-transactions.test.ts index 3619368f81bb..111902c7dd3c 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/tests/client-transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/tests/client-transactions.test.ts @@ -11,6 +11,15 @@ test('Sends a pageload transaction to Sentry', async ({ page }) => { const transactionEvent = await transactionPromise; expect(transactionEvent).toBeDefined(); + expect(transactionEvent.contexts?.trace?.data).toEqual( + expect.objectContaining({ + 'sentry.source': 'url', + 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/$/), + 'url.path': '/', + }), + ); + // no url.template because the route isn't parameterized (sentry.source: 'url') + expect(transactionEvent.contexts?.trace?.data).not.toHaveProperty('url.template'); }); test('Sends a navigation transaction to Sentry', async ({ page }) => { @@ -26,6 +35,14 @@ test('Sends a navigation transaction to Sentry', async ({ page }) => { const transactionEvent = await transactionPromise; expect(transactionEvent).toBeDefined(); + expect(transactionEvent.contexts?.trace?.data).toEqual( + expect.objectContaining({ + 'sentry.source': 'route', + 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/user\/5$/), + 'url.path': '/user/5', + 'url.template': '/user/:id', + }), + ); }); test('Renders `sentry-trace` and `baggage` meta tags for the root route', async ({ page }) => { diff --git a/dev-packages/e2e-tests/test-applications/remix-hydrogen/tests/client-transactions.test.ts b/dev-packages/e2e-tests/test-applications/remix-hydrogen/tests/client-transactions.test.ts index 549383810633..90745d9b777b 100644 --- a/dev-packages/e2e-tests/test-applications/remix-hydrogen/tests/client-transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/remix-hydrogen/tests/client-transactions.test.ts @@ -11,6 +11,20 @@ test('Sends a pageload transaction to Sentry', async ({ page }) => { const transactionEvent = await transactionPromise; expect(transactionEvent).toBeDefined(); + expect(transactionEvent).toMatchObject({ + transaction: '/', + contexts: { + trace: { + data: { + 'sentry.source': 'url', + 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/$/), + 'url.path': '/', + }, + }, + }, + }); + // no url.template because the route isn't parameterized (sentry.source: 'url') + expect(transactionEvent.contexts?.trace?.data).not.toHaveProperty('url.template'); }); test('Sends a navigation transaction to Sentry', async ({ page }) => { @@ -39,6 +53,16 @@ test('Sends a navigation transaction to Sentry', async ({ page }) => { expect(transactionEvent).toBeDefined(); expect(transactionEvent).toMatchObject({ transaction: '/user/:id', + contexts: { + trace: { + data: { + 'sentry.source': 'route', + 'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/user\/5$/), + 'url.path': '/user/5', + 'url.template': '/user/:id', + }, + }, + }, }); }); diff --git a/packages/remix/src/client/performance.tsx b/packages/remix/src/client/performance.tsx index b3cde64d72de..f91197a38736 100644 --- a/packages/remix/src/client/performance.tsx +++ b/packages/remix/src/client/performance.tsx @@ -13,6 +13,7 @@ import { getClient, startBrowserTracingNavigationSpan, startBrowserTracingPageLo import * as React from 'react'; import { DEBUG_BUILD } from '../utils/debug-build'; import { hasManifest, maybeParameterizeRemixRoute } from './remixRouteParameterization'; +import { URL_TEMPLATE } from '@sentry/conventions/attributes'; export type Params = { readonly [key in Key]: string | undefined; @@ -103,6 +104,7 @@ export function startPageloadSpan(client: Client): void { attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.remix', [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, + ...(source === 'route' && { [URL_TEMPLATE]: spanName }), }, }; @@ -126,6 +128,7 @@ function startNavigationSpan(matches: RouteMatch[], location: ReturnType attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.remix', [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, + ...(source === 'route' && { [URL_TEMPLATE]: name }), }, }; @@ -182,6 +185,9 @@ export function withSentry

, R extends React.Co if (transaction) { transaction.updateName(name); transaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source); + if (source === 'route') { + transaction.setAttribute(URL_TEMPLATE, name); + } } } }