Skip to content

Commit 2ed771f

Browse files
committed
test(e2e): Port React Router SPA E2E apps to span streaming
Removes the `traceLifecycle: 'static'` pin from `react-router-7-spa`, `react-router-8-spa`, `react-router-7-cross-usage`, `react-router-8-cross-usage` and `react-router-8-cloudflare`, and rewrites their specs against streamed span v2. The Cloudflare mysql spec matches on `db.query.text` now: under streaming a mysql span is named after its query summary, so both queries in that route are called `SELECT`.
1 parent d2e75d6 commit 2ed771f

17 files changed

Lines changed: 390 additions & 509 deletions

File tree

dev-packages/e2e-tests/test-applications/react-router-7-cross-usage/src/index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import Index from './pages/Index';
1818
const replay = Sentry.replayIntegration();
1919

2020
Sentry.init({
21-
traceLifecycle: 'static',
2221
environment: 'qa', // dynamic sampling bias to keep transactions
2322
dsn: process.env.REACT_APP_E2E_TEST_DSN,
2423
integrations: [
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { expect, test } from '@playwright/test';
2+
import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
3+
4+
test('sends a pageload span with a parameterized URL', async ({ page }) => {
5+
const spanPromise = waitForStreamedSpan('react-router-7-cross-usage', span => {
6+
return getSpanOp(span) === 'pageload' && span.is_segment;
7+
});
8+
9+
await page.goto(`/projects/123/views/234/567`);
10+
11+
const span = await spanPromise;
12+
13+
expect((await page.innerHTML('#root')).includes('Details')).toBe(true);
14+
expect(span.name).toBe('/projects/:projectId/views/:viewId/:detailId');
15+
expect(span.attributes).toMatchObject({
16+
'sentry.op': { value: 'pageload', type: 'string' },
17+
'sentry.origin': { value: 'auto.pageload.react.reactrouter', type: 'string' },
18+
'sentry.segment.name.source': { value: 'route', type: 'string' },
19+
'url.template': { value: '/projects/:projectId/views/:viewId/:detailId', type: 'string' },
20+
'url.path': { value: '/projects/123/views/234/567', type: 'string' },
21+
'url.full': {
22+
value: expect.stringMatching(/^https?:\/\/localhost:\d+\/projects\/123\/views\/234\/567$/),
23+
type: 'string',
24+
},
25+
});
26+
});
27+
28+
test('sends a pageload span with a parameterized URL - alternative route', async ({ page }) => {
29+
const spanPromise = waitForStreamedSpan('react-router-7-cross-usage', span => {
30+
return getSpanOp(span) === 'pageload' && span.is_segment;
31+
});
32+
33+
await page.goto(`/projects/234/old-views/234/567`);
34+
35+
const span = await spanPromise;
36+
37+
expect((await page.innerHTML('#root')).includes('Details')).toBe(true);
38+
expect(span.name).toBe('/projects/:projectId/old-views/:viewId/:detailId');
39+
expect(span.attributes).toMatchObject({
40+
'sentry.op': { value: 'pageload', type: 'string' },
41+
'sentry.origin': { value: 'auto.pageload.react.reactrouter', type: 'string' },
42+
'sentry.segment.name.source': { value: 'route', type: 'string' },
43+
'url.template': { value: '/projects/:projectId/old-views/:viewId/:detailId', type: 'string' },
44+
'url.path': { value: '/projects/234/old-views/234/567', type: 'string' },
45+
'url.full': {
46+
value: expect.stringMatching(/^https?:\/\/localhost:\d+\/projects\/234\/old-views\/234\/567$/),
47+
type: 'string',
48+
},
49+
});
50+
});
51+
52+
test('sends a navigation span with a parameterized URL', async ({ page }) => {
53+
const pageloadSpanPromise = waitForStreamedSpan('react-router-7-cross-usage', span => {
54+
return getSpanOp(span) === 'pageload' && span.is_segment;
55+
});
56+
57+
const navigationSpanPromise = waitForStreamedSpan('react-router-7-cross-usage', span => {
58+
return getSpanOp(span) === 'navigation' && span.is_segment;
59+
});
60+
61+
await page.goto(`/`);
62+
const pageloadSpan = await pageloadSpanPromise;
63+
64+
expect(pageloadSpan.name).toBe('/');
65+
expect(pageloadSpan.attributes).toMatchObject({
66+
'sentry.op': { value: 'pageload', type: 'string' },
67+
'sentry.origin': { value: 'auto.pageload.react.reactrouter', type: 'string' },
68+
'sentry.segment.name.source': { value: 'route', type: 'string' },
69+
'url.template': { value: '/', type: 'string' },
70+
'url.path': { value: '/', type: 'string' },
71+
'url.full': { value: expect.stringMatching(/^https?:\/\/localhost:\d+\/$/), type: 'string' },
72+
});
73+
74+
const linkElement = page.locator('id=navigation');
75+
76+
const [_, navigationSpan] = await Promise.all([linkElement.click(), navigationSpanPromise]);
77+
78+
expect((await page.innerHTML('#root')).includes('Details')).toBe(true);
79+
expect(navigationSpan.name).toBe('/projects/:projectId/views/:viewId/:detailId');
80+
expect(navigationSpan.attributes).toMatchObject({
81+
'sentry.op': { value: 'navigation', type: 'string' },
82+
'sentry.origin': { value: 'auto.navigation.react.reactrouter', type: 'string' },
83+
'sentry.segment.name.source': { value: 'route', type: 'string' },
84+
'url.template': { value: '/projects/:projectId/views/:viewId/:detailId', type: 'string' },
85+
'url.path': { value: '/projects/123/views/456/789', type: 'string' },
86+
'url.full': {
87+
value: expect.stringMatching(/^https?:\/\/localhost:\d+\/projects\/123\/views\/456\/789$/),
88+
type: 'string',
89+
},
90+
});
91+
});
92+
93+
test('sends a navigation span with a parameterized URL - alternative route', async ({ page }) => {
94+
const pageloadSpanPromise = waitForStreamedSpan('react-router-7-cross-usage', span => {
95+
return getSpanOp(span) === 'pageload' && span.is_segment;
96+
});
97+
98+
const navigationSpanPromise = waitForStreamedSpan('react-router-7-cross-usage', span => {
99+
return getSpanOp(span) === 'navigation' && span.is_segment;
100+
});
101+
102+
await page.goto(`/`);
103+
const pageloadSpan = await pageloadSpanPromise;
104+
105+
expect(pageloadSpan.name).toBe('/');
106+
expect(pageloadSpan.attributes).toMatchObject({
107+
'sentry.op': { value: 'pageload', type: 'string' },
108+
'sentry.origin': { value: 'auto.pageload.react.reactrouter', type: 'string' },
109+
'sentry.segment.name.source': { value: 'route', type: 'string' },
110+
'url.template': { value: '/', type: 'string' },
111+
'url.path': { value: '/', type: 'string' },
112+
'url.full': { value: expect.stringMatching(/^https?:\/\/localhost:\d+\/$/), type: 'string' },
113+
});
114+
115+
const linkElement = page.locator('id=old-navigation');
116+
117+
const [_, navigationSpan] = await Promise.all([linkElement.click(), navigationSpanPromise]);
118+
119+
expect((await page.innerHTML('#root')).includes('Details')).toBe(true);
120+
expect(navigationSpan.name).toBe('/projects/:projectId/old-views/:viewId/:detailId');
121+
expect(navigationSpan.attributes).toMatchObject({
122+
'sentry.op': { value: 'navigation', type: 'string' },
123+
'sentry.origin': { value: 'auto.navigation.react.reactrouter', type: 'string' },
124+
'sentry.segment.name.source': { value: 'route', type: 'string' },
125+
'url.template': { value: '/projects/:projectId/old-views/:viewId/:detailId', type: 'string' },
126+
'url.path': { value: '/projects/123/old-views/345/654', type: 'string' },
127+
'url.full': {
128+
value: expect.stringMatching(/^https?:\/\/localhost:\d+\/projects\/123\/old-views\/345\/654$/),
129+
type: 'string',
130+
},
131+
});
132+
});

dev-packages/e2e-tests/test-applications/react-router-7-cross-usage/tests/transactions.test.ts

Lines changed: 0 additions & 174 deletions
This file was deleted.

dev-packages/e2e-tests/test-applications/react-router-7-spa/src/main.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import User from './pages/User';
1818
const replay = Sentry.replayIntegration();
1919

2020
Sentry.init({
21-
traceLifecycle: 'static',
2221
environment: 'qa', // dynamic sampling bias to keep transactions
2322
dsn: import.meta.env.PUBLIC_E2E_TEST_DSN,
2423
integrations: [

dev-packages/e2e-tests/test-applications/react-router-7-spa/tests/errors.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect, test } from '@playwright/test';
2-
import { waitForError, waitForTransaction } from '@sentry-internal/test-utils';
2+
import { getSpanOp, waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils';
33

44
test('Sends correct error event', async ({ page, baseURL }) => {
55
const errorEventPromise = waitForError('react-router-7-spa', event => {
@@ -30,18 +30,18 @@ test('Sends correct error event', async ({ page, baseURL }) => {
3030
});
3131

3232
test('Sets correct transactionName', async ({ page }) => {
33-
const transactionPromise = waitForTransaction('react-router-7-spa', async transactionEvent => {
34-
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
33+
const pageloadSpanPromise = waitForStreamedSpan('react-router-7-spa', span => {
34+
return getSpanOp(span) === 'pageload' && span.is_segment;
3535
});
3636

3737
const errorEventPromise = waitForError('react-router-7-spa', event => {
3838
return !event.type && event.exception?.values?.[0]?.value === 'I am an error!';
3939
});
4040

4141
await page.goto('/');
42-
const transactionEvent = await transactionPromise;
42+
const pageloadSpan = await pageloadSpanPromise;
4343

44-
// Only capture error once transaction was sent
44+
// Only capture error once the pageload span was sent
4545
const exceptionButton = page.locator('id=exception-button');
4646
await exceptionButton.click();
4747

@@ -53,7 +53,7 @@ test('Sets correct transactionName', async ({ page }) => {
5353
expect(errorEvent.transaction).toEqual('/');
5454

5555
expect(errorEvent.contexts?.trace).toEqual({
56-
trace_id: transactionEvent.contexts?.trace?.trace_id,
57-
span_id: expect.not.stringContaining(transactionEvent.contexts?.trace?.span_id || ''),
56+
trace_id: pageloadSpan.trace_id,
57+
span_id: expect.not.stringContaining(pageloadSpan.span_id),
5858
});
5959
});

0 commit comments

Comments
 (0)