Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ test('Sends a pageload transaction to Sentry', async ({ page }) => {
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
op: 'pageload',
origin: 'auto.pageload.nextjs.pages_router_instrumentation',
status: 'ok',
data: expect.objectContaining({
'sentry.idle_span_finish_reason': 'idleTimeout',
'sentry.op': 'pageload',
Expand Down Expand Up @@ -69,6 +70,7 @@ test('captures a navigation transaction to Sentry', async ({ page }) => {
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
op: 'navigation',
origin: 'auto.navigation.nextjs.pages_router_instrumentation',
status: 'ok',
data: expect.objectContaining({
'sentry.idle_span_finish_reason': 'idleTimeout',
'sentry.op': 'navigation',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ test('Sends a pageload transaction', async ({ page }) => {
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
op: 'pageload',
origin: 'auto.pageload.nextjs.app_router_instrumentation',
status: 'ok',
data: expect.objectContaining({
'sentry.op': 'pageload',
'sentry.origin': 'auto.pageload.nextjs.app_router_instrumentation',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ test('Sends a pageload transaction', async ({ page }) => {
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
op: 'pageload',
origin: 'auto.pageload.nextjs.pages_router_instrumentation',
status: 'ok',
data: expect.objectContaining({
'sentry.op': 'pageload',
'sentry.origin': 'auto.pageload.nextjs.pages_router_instrumentation',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ test('Captures a pageload transaction', async ({ page }) => {
span_id: expect.stringMatching(/[a-f0-9]{16}/),
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
origin: 'auto.pageload.react.reactrouter_v6',
status: 'ok',
}),
);
});
Expand Down Expand Up @@ -72,6 +73,7 @@ test('Captures a navigation transaction', async ({ page }) => {
span_id: expect.stringMatching(/[a-f0-9]{16}/),
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
origin: 'auto.navigation.react.reactrouter_v6',
status: 'ok',
});

expect(transactionEvent).toEqual(
Expand Down Expand Up @@ -107,6 +109,7 @@ test('Captures a lazy pageload transaction', async ({ page }) => {
span_id: expect.stringMatching(/[a-f0-9]{16}/),
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
origin: 'auto.pageload.react.reactrouter_v6',
status: 'ok',
});

expect(transactionEvent).toEqual(
Expand Down Expand Up @@ -169,6 +172,7 @@ test('Captures a lazy navigation transaction', async ({ page }) => {
span_id: expect.stringMatching(/[a-f0-9]{16}/),
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
origin: 'auto.navigation.react.reactrouter_v6',
status: 'ok',
});

expect(transactionEvent).toEqual(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ test('Captures a pageload transaction', async ({ page }) => {
span_id: expect.stringMatching(/[a-f0-9]{16}/),
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
origin: 'auto.pageload.react.reactrouter_v6',
status: 'ok',
});

expect(transactionEvent).toEqual(
Expand Down Expand Up @@ -136,6 +137,7 @@ test('Captures a navigation transaction', async ({ page }) => {
span_id: expect.stringMatching(/[a-f0-9]{16}/),
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
origin: 'auto.navigation.react.reactrouter_v6',
status: 'ok',
});

expect(transactionEvent).toEqual(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ test('Captures a pageload transaction', async ({ page }) => {
span_id: expect.stringMatching(/[a-f0-9]{16}/),
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
origin: 'auto.pageload.react.reactrouter_v6',
status: 'ok',
}),
);
});
Expand Down Expand Up @@ -69,6 +70,7 @@ test('Captures a navigation transaction', async ({ page }) => {
span_id: expect.stringMatching(/[a-f0-9]{16}/),
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
origin: 'auto.navigation.react.reactrouter_v6',
status: 'ok',
});

expect(transactionEvent).toEqual(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ test('Creates a pageload transaction with parameterized route', async ({ page })
expect(event.transaction).toBe('/lazy/inner/:id/:anotherId/:someAnotherId');
expect(event.type).toBe('transaction');
expect(event.contexts?.trace?.op).toBe('pageload');
expect(event.contexts?.trace?.status).toBe('ok');
});

test('Does not create a navigation transaction on initial load to deep lazy route', async ({ page }) => {
Expand Down Expand Up @@ -82,6 +83,7 @@ test('Creates a navigation transaction inside a lazy route', async ({ page }) =>
expect(event.transaction).toBe('/lazy/inner/:id/:anotherId/:someAnotherId');
expect(event.type).toBe('transaction');
expect(event.contexts?.trace?.op).toBe('navigation');
expect(event.contexts?.trace?.status).toBe('ok');
});

test('Creates navigation transactions between two different lazy routes', async ({ page }) => {
Expand Down Expand Up @@ -498,3 +500,90 @@ test('Updates navigation transaction name correctly when span is cancelled early
expect(['externalFinish', 'cancelled']).toContain(idleSpanFinishReason);
}
});

test('Creates separate transactions for rapid consecutive navigations', async ({ page }) => {
await page.goto('/');

// First navigation: / -> /lazy/inner/:id/:anotherId/:someAnotherId
const firstTransactionPromise = waitForTransaction('react-router-7-lazy-routes', async transactionEvent => {
return (
!!transactionEvent?.transaction &&
transactionEvent.contexts?.trace?.op === 'navigation' &&
transactionEvent.transaction === '/lazy/inner/:id/:anotherId/:someAnotherId'
);
});

const navigationToInner = page.locator('id=navigation');
await expect(navigationToInner).toBeVisible();
await navigationToInner.click();

const firstEvent = await firstTransactionPromise;

// Verify first transaction
expect(firstEvent.transaction).toBe('/lazy/inner/:id/:anotherId/:someAnotherId');
expect(firstEvent.contexts?.trace?.op).toBe('navigation');
expect(firstEvent.contexts?.trace?.status).toBe('ok');
const firstTraceId = firstEvent.contexts?.trace?.trace_id;
const firstSpanId = firstEvent.contexts?.trace?.span_id;

// Second navigation: /lazy/inner -> /another-lazy/sub/:id/:subId
const secondTransactionPromise = waitForTransaction('react-router-7-lazy-routes', async transactionEvent => {
return (
!!transactionEvent?.transaction &&
transactionEvent.contexts?.trace?.op === 'navigation' &&
transactionEvent.transaction === '/another-lazy/sub/:id/:subId'
);
});

const navigationToAnother = page.locator('id=navigate-to-another-from-inner');
await expect(navigationToAnother).toBeVisible();
await navigationToAnother.click();

const secondEvent = await secondTransactionPromise;

// Verify second transaction
expect(secondEvent.transaction).toBe('/another-lazy/sub/:id/:subId');
expect(secondEvent.contexts?.trace?.op).toBe('navigation');
expect(secondEvent.contexts?.trace?.status).toBe('ok');
const secondTraceId = secondEvent.contexts?.trace?.trace_id;
const secondSpanId = secondEvent.contexts?.trace?.span_id;

// Third navigation: /another-lazy -> /lazy/inner/:id/:anotherId/:someAnotherId (back to same route as first)
const thirdTransactionPromise = waitForTransaction('react-router-7-lazy-routes', async transactionEvent => {
return (
!!transactionEvent?.transaction &&
transactionEvent.contexts?.trace?.op === 'navigation' &&
transactionEvent.transaction === '/lazy/inner/:id/:anotherId/:someAnotherId' &&
// Ensure we're not matching the first transaction again
transactionEvent.contexts?.trace?.trace_id !== firstTraceId
);
});

const navigationBackToInner = page.locator('id=navigate-to-inner-from-deep');
await expect(navigationBackToInner).toBeVisible();
await navigationBackToInner.click();

const thirdEvent = await thirdTransactionPromise;

// Verify third transaction
expect(thirdEvent.transaction).toBe('/lazy/inner/:id/:anotherId/:someAnotherId');
expect(thirdEvent.contexts?.trace?.op).toBe('navigation');
expect(thirdEvent.contexts?.trace?.status).toBe('ok');
const thirdTraceId = thirdEvent.contexts?.trace?.trace_id;
const thirdSpanId = thirdEvent.contexts?.trace?.span_id;

// Verify each navigation created a separate transaction with unique trace and span IDs
expect(firstTraceId).toBeDefined();
expect(secondTraceId).toBeDefined();
expect(thirdTraceId).toBeDefined();

// All trace IDs should be unique
expect(firstTraceId).not.toBe(secondTraceId);
expect(secondTraceId).not.toBe(thirdTraceId);
expect(firstTraceId).not.toBe(thirdTraceId);

// All span IDs should be unique
expect(firstSpanId).not.toBe(secondSpanId);
expect(secondSpanId).not.toBe(thirdSpanId);
expect(firstSpanId).not.toBe(thirdSpanId);
});
8 changes: 7 additions & 1 deletion packages/core/src/tracing/idleSpan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { timestampInSeconds } from '../utils/time';
import { freezeDscOnSpan, getDynamicSamplingContextFromSpan } from './dynamicSamplingContext';
import { SentryNonRecordingSpan } from './sentryNonRecordingSpan';
import { SentrySpan } from './sentrySpan';
import { SPAN_STATUS_ERROR } from './spanstatus';
import { SPAN_STATUS_ERROR, SPAN_STATUS_OK } from './spanstatus';
import { startInactiveSpan } from './trace';

export const TRACING_DEFAULTS = {
Expand Down Expand Up @@ -302,6 +302,12 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_IDLE_SPAN_FINISH_REASON, _finishReason);
}

// Set span status to 'ok' if it hasn't been explicitly set to an error status
const currentStatus = spanJSON.status;
if (!currentStatus || currentStatus === 'unknown') {
span.setStatus({ code: SPAN_STATUS_OK });
}

debug.log(`[Tracing] Idle span "${spanJSON.op}" finished`);

const childSpans = getSpanDescendants(span).filter(child => child !== span);
Expand Down
Loading