Skip to content

Commit 75dab6a

Browse files
committed
test(e2e): Port React Router framework E2E apps to span streaming
Removes the `traceLifecycle: 'static'` pin from `react-router-7-framework`, `react-router-8-framework` and `react-router-7-framework-spa`, and rewrites their specs against streamed span v2. Envelope-level metadata has no span equivalent, so the SDK assertions read the `sentry.sdk.*` and `sentry.environment` attributes instead, and the `runtime` tag assertions are dropped. The trace-connection spec matches the server segment on its op rather than its name, because an unparameterized streamed server span is named after the method alone.
1 parent 19aca83 commit 75dab6a

21 files changed

Lines changed: 615 additions & 1027 deletions

dev-packages/e2e-tests/test-applications/react-router-7-framework-spa/app/entry.client.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { hydrateRoot } from 'react-dom/client';
44
import { HydratedRouter } from 'react-router/dom';
55

66
Sentry.init({
7-
traceLifecycle: 'static',
87
environment: 'qa', // dynamic sampling bias to keep transactions
98
// todo: get this from env
109
dsn: 'https://username@domain/123',
Lines changed: 29 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,46 @@
11
import { expect, test } from '@playwright/test';
2-
import { waitForTransaction } from '@sentry-internal/test-utils';
2+
import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
33
import { APP_NAME } from '../constants';
44

55
test.describe('client - navigation performance', () => {
6-
test('should update navigation transaction for dynamic routes', async ({ page }) => {
7-
const txPromise = waitForTransaction(APP_NAME, async transactionEvent => {
8-
return (
9-
transactionEvent.transaction === '/performance/with/:param' &&
10-
transactionEvent.contexts?.trace?.op === 'navigation'
11-
);
6+
test('should update navigation span for dynamic routes', async ({ page }) => {
7+
const navigationSpanPromise = waitForStreamedSpan(APP_NAME, span => {
8+
return span.name === '/performance/with/:param' && getSpanOp(span) === 'navigation' && span.is_segment;
129
});
1310

14-
const pageloadTxPromise = waitForTransaction(APP_NAME, async transactionEvent => {
15-
return transactionEvent.transaction === '/performance' && transactionEvent.contexts?.trace?.op === 'pageload';
11+
const pageloadSpanPromise = waitForStreamedSpan(APP_NAME, span => {
12+
return span.name === '/performance' && getSpanOp(span) === 'pageload' && span.is_segment;
1613
});
1714

1815
await page.goto(`/performance`); // pageload
19-
await pageloadTxPromise;
16+
await pageloadSpanPromise;
2017
await page.getByRole('link', { name: 'With Param Page' }).click(); // navigation
2118

22-
const transaction = await txPromise;
19+
const span = await navigationSpanPromise;
2320

24-
expect(transaction).toMatchObject({
25-
contexts: {
26-
trace: {
27-
span_id: expect.any(String),
28-
trace_id: expect.any(String),
29-
data: {
30-
'sentry.origin': 'auto.navigation.react_router',
31-
'sentry.op': 'navigation',
32-
'sentry.segment.name.source': 'route',
33-
'url.template': '/performance/with/:param',
34-
'url.path': '/performance/with/sentry',
35-
'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/performance\/with\/sentry$/),
36-
},
37-
op: 'navigation',
38-
origin: 'auto.navigation.react_router',
39-
},
40-
},
41-
spans: expect.any(Array),
21+
expect(span).toMatchObject({
22+
span_id: expect.any(String),
23+
trace_id: expect.any(String),
4224
start_timestamp: expect.any(Number),
43-
timestamp: expect.any(Number),
44-
transaction: '/performance/with/:param',
45-
type: 'transaction',
46-
transaction_info: { source: 'route' },
47-
platform: 'javascript',
48-
request: {
49-
url: expect.stringContaining('/performance/with/sentry'),
50-
headers: expect.any(Object),
51-
},
52-
event_id: expect.any(String),
53-
environment: 'qa',
54-
sdk: {
55-
integrations: expect.arrayContaining([expect.any(String)]),
56-
name: 'sentry.javascript.react-router',
57-
version: expect.any(String),
58-
packages: [
59-
{ name: 'npm:@sentry/react-router', version: expect.any(String) },
60-
{ name: 'npm:@sentry/browser', version: expect.any(String) },
61-
],
25+
end_timestamp: expect.any(Number),
26+
is_segment: true,
27+
status: 'ok',
28+
});
29+
30+
expect(span.attributes).toMatchObject({
31+
'sentry.origin': { value: 'auto.navigation.react_router', type: 'string' },
32+
'sentry.op': { value: 'navigation', type: 'string' },
33+
'sentry.segment.name.source': { value: 'route', type: 'string' },
34+
'sentry.environment': { value: 'qa', type: 'string' },
35+
'sentry.sdk.name': { value: 'sentry.javascript.react-router', type: 'string' },
36+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
37+
'sentry.sdk.integrations': { value: expect.arrayContaining([expect.any(String)]), type: 'array' },
38+
'url.template': { value: '/performance/with/:param', type: 'string' },
39+
'url.path': { value: '/performance/with/sentry', type: 'string' },
40+
'url.full': {
41+
value: expect.stringMatching(/^https?:\/\/localhost:\d+\/performance\/with\/sentry$/),
42+
type: 'string',
6243
},
63-
tags: { runtime: 'browser' },
6444
});
6545
});
6646
});
Lines changed: 40 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,116 +1,63 @@
11
import { expect, test } from '@playwright/test';
2-
import { waitForTransaction } from '@sentry-internal/test-utils';
2+
import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
33
import { APP_NAME } from '../constants';
44

55
test.describe('client - pageload performance', () => {
6-
test('should send pageload transaction', async ({ page }) => {
7-
const txPromise = waitForTransaction(APP_NAME, async transactionEvent => {
8-
return transactionEvent.transaction === '/performance' && transactionEvent.contexts?.trace?.op === 'pageload';
6+
test('should send pageload span', async ({ page }) => {
7+
const spanPromise = waitForStreamedSpan(APP_NAME, span => {
8+
return span.name === '/performance' && getSpanOp(span) === 'pageload' && span.is_segment;
99
});
1010

1111
await page.goto(`/performance`);
1212
await page.getByRole('heading', { name: 'Performance Page' }).waitFor();
1313

14-
const transaction = await txPromise;
14+
const span = await spanPromise;
1515

16-
expect(transaction).toMatchObject({
17-
contexts: {
18-
trace: {
19-
span_id: expect.any(String),
20-
trace_id: expect.any(String),
21-
data: {
22-
'sentry.origin': 'auto.pageload.react_router',
23-
'sentry.op': 'pageload',
24-
'sentry.segment.name.source': 'route',
25-
'url.template': '/performance',
26-
'url.path': '/performance',
27-
'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/performance$/),
28-
},
29-
op: 'pageload',
30-
origin: 'auto.pageload.react_router',
31-
},
32-
},
33-
spans: expect.any(Array),
16+
expect(span).toMatchObject({
17+
span_id: expect.any(String),
18+
trace_id: expect.any(String),
3419
start_timestamp: expect.any(Number),
35-
timestamp: expect.any(Number),
36-
transaction: '/performance',
37-
type: 'transaction',
38-
transaction_info: { source: 'route' },
39-
measurements: expect.any(Object),
40-
platform: 'javascript',
41-
request: {
42-
url: expect.stringContaining('/performance'),
43-
headers: expect.any(Object),
44-
},
45-
event_id: expect.any(String),
46-
environment: 'qa',
47-
sdk: {
48-
integrations: expect.arrayContaining([expect.any(String)]),
49-
name: 'sentry.javascript.react-router',
50-
version: expect.any(String),
51-
packages: [
52-
{ name: 'npm:@sentry/react-router', version: expect.any(String) },
53-
{ name: 'npm:@sentry/browser', version: expect.any(String) },
54-
],
55-
},
56-
tags: { runtime: 'browser' },
20+
end_timestamp: expect.any(Number),
21+
is_segment: true,
22+
status: 'ok',
23+
});
24+
25+
expect(span.attributes).toMatchObject({
26+
'sentry.origin': { value: 'auto.pageload.react_router', type: 'string' },
27+
'sentry.op': { value: 'pageload', type: 'string' },
28+
'sentry.segment.name.source': { value: 'route', type: 'string' },
29+
'sentry.environment': { value: 'qa', type: 'string' },
30+
'sentry.sdk.name': { value: 'sentry.javascript.react-router', type: 'string' },
31+
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
32+
'sentry.sdk.integrations': { value: expect.arrayContaining([expect.any(String)]), type: 'array' },
33+
'url.template': { value: '/performance', type: 'string' },
34+
'url.path': { value: '/performance', type: 'string' },
35+
'url.full': { value: expect.stringMatching(/^https?:\/\/localhost:\d+\/performance$/), type: 'string' },
5736
});
5837
});
5938

60-
test('should update pageload transaction for dynamic routes', async ({ page }) => {
61-
const txPromise = waitForTransaction(APP_NAME, async transactionEvent => {
62-
return (
63-
transactionEvent.transaction === '/performance/with/:param' &&
64-
transactionEvent.contexts?.trace?.op === 'pageload'
65-
);
39+
test('should update pageload span for dynamic routes', async ({ page }) => {
40+
const spanPromise = waitForStreamedSpan(APP_NAME, span => {
41+
return span.name === '/performance/with/:param' && getSpanOp(span) === 'pageload' && span.is_segment;
6642
});
6743

6844
await page.goto(`/performance/with/sentry`);
6945
await page.getByRole('heading', { name: 'Dynamic Parameter Page' }).waitFor();
7046

71-
const transaction = await txPromise;
72-
73-
expect(transaction).toMatchObject({
74-
contexts: {
75-
trace: {
76-
span_id: expect.any(String),
77-
trace_id: expect.any(String),
78-
data: {
79-
'sentry.origin': 'auto.pageload.react_router',
80-
'sentry.op': 'pageload',
81-
'sentry.segment.name.source': 'route',
82-
'url.template': '/performance/with/:param',
83-
'url.path': '/performance/with/sentry',
84-
'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/performance\/with\/sentry$/),
85-
},
86-
op: 'pageload',
87-
origin: 'auto.pageload.react_router',
88-
},
89-
},
90-
spans: expect.any(Array),
91-
start_timestamp: expect.any(Number),
92-
timestamp: expect.any(Number),
93-
transaction: '/performance/with/:param',
94-
type: 'transaction',
95-
transaction_info: { source: 'route' },
96-
measurements: expect.any(Object),
97-
platform: 'javascript',
98-
request: {
99-
url: expect.stringContaining('/performance/with/sentry'),
100-
headers: expect.any(Object),
101-
},
102-
event_id: expect.any(String),
103-
environment: 'qa',
104-
sdk: {
105-
integrations: expect.arrayContaining([expect.any(String)]),
106-
name: 'sentry.javascript.react-router',
107-
version: expect.any(String),
108-
packages: [
109-
{ name: 'npm:@sentry/react-router', version: expect.any(String) },
110-
{ name: 'npm:@sentry/browser', version: expect.any(String) },
111-
],
47+
const span = await spanPromise;
48+
49+
expect(span.attributes).toMatchObject({
50+
'sentry.origin': { value: 'auto.pageload.react_router', type: 'string' },
51+
'sentry.op': { value: 'pageload', type: 'string' },
52+
'sentry.segment.name.source': { value: 'route', type: 'string' },
53+
'sentry.environment': { value: 'qa', type: 'string' },
54+
'sentry.sdk.name': { value: 'sentry.javascript.react-router', type: 'string' },
55+
'url.template': { value: '/performance/with/:param', type: 'string' },
56+
'url.path': { value: '/performance/with/sentry', type: 'string' },
57+
'url.full': {
58+
value: expect.stringMatching(/^https?:\/\/localhost:\d+\/performance\/with\/sentry$/),
59+
type: 'string',
11260
},
113-
tags: { runtime: 'browser' },
11461
});
11562
});
11663
});

dev-packages/e2e-tests/test-applications/react-router-7-framework/app/entry.client.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { hydrateRoot } from 'react-dom/client';
44
import { HydratedRouter } from 'react-router/dom';
55

66
Sentry.init({
7-
traceLifecycle: 'static',
87
environment: 'qa', // dynamic sampling bias to keep transactions
98
// todo: get this from env
109
dsn: 'https://username@domain/123',

dev-packages/e2e-tests/test-applications/react-router-7-framework/instrument.mjs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as Sentry from '@sentry/react-router';
22

33
Sentry.init({
4-
traceLifecycle: 'static',
54
dsn: 'https://username@domain/123',
65
environment: 'qa', // dynamic sampling bias to keep transactions
76
tracesSampleRate: 1.0,
Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
import { expect, test } from '@playwright/test';
2-
import { waitForTransaction } from '@sentry-internal/test-utils';
2+
import type { SerializedStreamedSpan } from '@sentry-internal/test-utils';
3+
import { getSpanOp, waitForStreamedSpan, waitForStreamedSpans } from '@sentry-internal/test-utils';
34
import { APP_NAME } from '../constants';
45

5-
test.describe('low-quality transaction filter', () => {
6-
test('does not send a server transaction for /__manifest? requests', async ({ page }) => {
7-
const serverTxns: Array<{ contexts?: { trace?: { data?: Record<string, unknown> } } }> = [];
6+
test.describe('low-quality span filter', () => {
7+
test('does not send a server span for /__manifest? requests', async ({ page }) => {
8+
const streamedSpans: SerializedStreamedSpan[] = [];
89

9-
const navigationPromise = waitForTransaction(APP_NAME, async transactionEvent => {
10-
return (
11-
transactionEvent.transaction === '/performance/ssr' && transactionEvent.contexts?.trace?.op === 'navigation'
12-
);
10+
const navigationPromise = waitForStreamedSpan(APP_NAME, span => {
11+
return span.name === '/performance/ssr' && getSpanOp(span) === 'navigation' && span.is_segment;
1312
});
1413

15-
waitForTransaction(APP_NAME, async evt => {
16-
serverTxns.push(evt);
14+
waitForStreamedSpans(APP_NAME, spans => {
15+
streamedSpans.push(...spans);
1716
return false;
1817
});
1918

@@ -23,12 +22,13 @@ test.describe('low-quality transaction filter', () => {
2322

2423
await navigationPromise;
2524

26-
// Force the server to flush any in-flight transactions before we assert
25+
// Force the server to flush any in-flight spans before we assert
2726
await page.evaluate(() => fetch('/__sentry-flush'));
2827

29-
const targetIsManifest = (t: (typeof serverTxns)[number]) =>
30-
typeof t.contexts?.trace?.data?.['url.path'] === 'string' &&
31-
(t.contexts.trace.data['url.path'] as string).includes('/__manifest');
32-
expect(serverTxns.some(targetIsManifest)).toBe(false);
28+
const targetIsManifest = (span: SerializedStreamedSpan) => {
29+
const urlPath = span.attributes['url.path']?.value;
30+
return typeof urlPath === 'string' && urlPath.includes('/__manifest');
31+
};
32+
expect(streamedSpans.some(targetIsManifest)).toBe(false);
3333
});
3434
});
Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,33 @@
11
import { expect, test } from '@playwright/test';
2-
import { waitForTransaction } from '@sentry-internal/test-utils';
2+
import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
33
import { APP_NAME } from '../constants';
44

55
test.describe('server - middleware', () => {
6-
test('should send middleware transaction on pageload', async ({ page }) => {
7-
const serverTxPromise = waitForTransaction(APP_NAME, async transactionEvent => {
8-
return transactionEvent.transaction === 'GET /performance/with-middleware';
6+
test('should send middleware span on pageload', async ({ page }) => {
7+
const serverSpansPromise = collectStreamedSpans(APP_NAME, spans => {
8+
return (
9+
spans.some(span => span.name === 'GET /performance/with-middleware' && span.is_segment) &&
10+
spans.some(span => span.name === 'authMiddleware')
11+
);
912
});
1013

11-
const pageloadTxPromise = waitForTransaction(APP_NAME, async transactionEvent => {
12-
return transactionEvent.transaction === '/performance/with-middleware';
13-
});
14-
15-
const customMiddlewareTxPromise = waitForTransaction(APP_NAME, async transactionEvent => {
16-
return transactionEvent.transaction === 'authMiddleware';
14+
const pageloadSpanPromise = waitForStreamedSpan(APP_NAME, span => {
15+
return span.name === '/performance/with-middleware' && getSpanOp(span) === 'pageload' && span.is_segment;
1716
});
1817

1918
await page.goto(`/performance/with-middleware`);
2019

21-
const serverTx = await serverTxPromise;
22-
const pageloadTx = await pageloadTxPromise;
23-
const customMiddlewareTx = await customMiddlewareTxPromise;
20+
const serverSpans = await serverSpansPromise;
21+
const pageloadSpan = await pageloadSpanPromise;
2422

25-
const traceIds = {
26-
server: serverTx?.contexts?.trace?.trace_id,
27-
pageload: pageloadTx?.contexts?.trace?.trace_id,
28-
customMiddleware: customMiddlewareTx?.contexts?.trace?.trace_id,
29-
};
23+
const serverSpan = serverSpans.find(span => span.name === 'GET /performance/with-middleware' && span.is_segment)!;
24+
const customMiddlewareSpan = serverSpans.find(span => span.name === 'authMiddleware')!;
3025

31-
expect(pageloadTx).toBeDefined();
32-
expect(customMiddlewareTx).toBeDefined();
26+
expect(pageloadSpan).toBeDefined();
27+
expect(customMiddlewareSpan).toBeDefined();
3328

34-
// Assert that all transactions belong to the same trace
35-
expect(traceIds.server).toBe(traceIds.pageload);
36-
expect(traceIds.server).toBe(traceIds.customMiddleware);
29+
// Assert that all spans belong to the same trace
30+
expect(serverSpan.trace_id).toBe(pageloadSpan.trace_id);
31+
expect(serverSpan.trace_id).toBe(customMiddlewareSpan.trace_id);
3732
});
3833
});

0 commit comments

Comments
 (0)