Skip to content

Commit e9fe733

Browse files
andreiborzaclaude
andauthored
test(e2e): Port Next.js routing test apps to span streaming (#23797)
## What Ports the three Next.js client-routing E2E apps (`nextjs-15-basepath`, `nextjs-15-intl`, `nextjs-16-trailing-slash`) to span streaming: the `traceLifecycle: 'static'` pin from #22590 is removed and the specs now assert on streamed spans instead of transactions. ## Why Span streaming is the default, so the default E2E suite should exercise it. This is the first slice of #23513; these three apps were picked because they are near-identical and cover pageload and navigation route parameterization, which is where span naming changed. Closes: #23816 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent bdae903 commit e9fe733

13 files changed

Lines changed: 165 additions & 266 deletions

‎dev-packages/e2e-tests/test-applications/nextjs-15-basepath/instrumentation-client.ts‎

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

33
Sentry.init({
4-
traceLifecycle: 'static',
54
environment: 'qa', // dynamic sampling bias to keep transactions
65
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
76
tunnel: `http://localhost:3031/`, // proxy server

‎dev-packages/e2e-tests/test-applications/nextjs-15-basepath/sentry.edge.config.ts‎

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

33
Sentry.init({
4-
traceLifecycle: 'static',
54
environment: 'qa', // dynamic sampling bias to keep transactions
65
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
76
tunnel: `http://localhost:3031/`, // proxy server

‎dev-packages/e2e-tests/test-applications/nextjs-15-basepath/sentry.server.config.ts‎

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

33
Sentry.init({
4-
traceLifecycle: 'static',
54
environment: 'qa', // dynamic sampling bias to keep transactions
65
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
76
tunnel: `http://localhost:3031/`, // proxy server
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { expect, test } from '@playwright/test';
2+
import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
3+
4+
test('Creates a pageload span for basePath root route with prefix', async ({ page }) => {
5+
const pageloadSpanPromise = waitForStreamedSpan('nextjs-15-basepath', span => {
6+
return span.name === '/my-app' && getSpanOp(span) === 'pageload' && span.is_segment;
7+
});
8+
9+
await page.goto('/my-app');
10+
11+
expect(await pageloadSpanPromise).toBeDefined();
12+
});
13+
14+
test('Creates a dynamic pageload span for basePath dynamic route with prefix', async ({ page }) => {
15+
const randomRoute = String(Math.random());
16+
17+
const pageloadSpanPromise = waitForStreamedSpan('nextjs-15-basepath', span => {
18+
return span.name === '/my-app/dynamic/:parameter' && getSpanOp(span) === 'pageload' && span.is_segment;
19+
});
20+
21+
await page.goto(`/my-app/dynamic/${randomRoute}`);
22+
23+
expect(await pageloadSpanPromise).toBeDefined();
24+
});
25+
26+
test('Creates a dynamic pageload span for basePath dynamic catch-all route with prefix', async ({ page }) => {
27+
const randomRoute = String(Math.random());
28+
29+
const pageloadSpanPromise = waitForStreamedSpan('nextjs-15-basepath', span => {
30+
return span.name === '/my-app/dynamic/:parameters*' && getSpanOp(span) === 'pageload' && span.is_segment;
31+
});
32+
33+
await page.goto(`/my-app/dynamic/${randomRoute}/foo/bar/baz`);
34+
35+
expect(await pageloadSpanPromise).toBeDefined();
36+
});
37+
38+
test('Creates a navigation span for basePath router with prefix', async ({ page }) => {
39+
const navigationSpanPromise = waitForStreamedSpan('nextjs-15-basepath', span => {
40+
return (
41+
span.name === '/my-app/navigation/:param/router-push' &&
42+
getSpanOp(span) === 'navigation' &&
43+
span.is_segment &&
44+
span.attributes['navigation.type']?.value === 'router.push'
45+
);
46+
});
47+
48+
await page.goto('/my-app/navigation');
49+
await page.waitForTimeout(1000);
50+
await page.getByText('router.push()').click();
51+
52+
const navigationSpan = await navigationSpanPromise;
53+
expect(navigationSpan).toBeDefined();
54+
55+
expect(navigationSpan.attributes).toMatchObject({
56+
'sentry.op': { value: 'navigation', type: 'string' },
57+
'sentry.origin': { value: 'auto.navigation.nextjs.app_router_instrumentation', type: 'string' },
58+
'sentry.segment.name.source': { value: 'route', type: 'string' },
59+
'url.full': {
60+
value: expect.stringMatching(/^https?:\/\/localhost:\d+\/my-app\/navigation\/42\/router-push$/),
61+
type: 'string',
62+
},
63+
'url.path': { value: '/my-app/navigation/42/router-push', type: 'string' },
64+
'url.template': { value: '/my-app/navigation/:param/router-push', type: 'string' },
65+
});
66+
});
67+
68+
test('Creates a navigation span for basePath <Link> with prefix', async ({ page }) => {
69+
const navigationSpanPromise = waitForStreamedSpan('nextjs-15-basepath', span => {
70+
return (
71+
span.name === '/my-app/navigation/:param/link' &&
72+
getSpanOp(span) === 'navigation' &&
73+
span.is_segment &&
74+
span.attributes['navigation.type']?.value === 'router.push'
75+
);
76+
});
77+
78+
await page.goto('/my-app/navigation');
79+
await page.waitForTimeout(1000);
80+
await page.getByText('Normal Link').click();
81+
82+
expect(await navigationSpanPromise).toBeDefined();
83+
});

‎dev-packages/e2e-tests/test-applications/nextjs-15-basepath/tests/routing-basepath-transaction.test.ts‎

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

‎dev-packages/e2e-tests/test-applications/nextjs-15-intl/instrumentation-client.ts‎

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

33
Sentry.init({
4-
traceLifecycle: 'static',
54
environment: 'qa',
65
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
76
tunnel: `http://localhost:3031/`,

‎dev-packages/e2e-tests/test-applications/nextjs-15-intl/sentry.edge.config.ts‎

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

33
Sentry.init({
4-
traceLifecycle: 'static',
54
environment: 'qa',
65
dsn: process.env.SENTRY_DSN,
76
tunnel: `http://localhost:3031/`,

‎dev-packages/e2e-tests/test-applications/nextjs-15-intl/sentry.server.config.ts‎

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

33
Sentry.init({
4-
traceLifecycle: 'static',
54
environment: 'qa',
65
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
76
tunnel: `http://localhost:3031/`,
Lines changed: 27 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,54 @@
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

4-
test('should create consistent parameterized transaction for default locale without prefix', async ({ page }) => {
5-
const transactionPromise = waitForTransaction('nextjs-15-intl', async transactionEvent => {
6-
return transactionEvent.transaction === '/:locale/i18n-test' && transactionEvent.contexts?.trace?.op === 'pageload';
4+
test('should create consistent parameterized span for default locale without prefix', async ({ page }) => {
5+
const spanPromise = waitForStreamedSpan('nextjs-15-intl', span => {
6+
return span.name === '/:locale/i18n-test' && getSpanOp(span) === 'pageload' && span.is_segment;
77
});
88

99
await page.goto(`/i18n-test`);
1010

11-
const transaction = await transactionPromise;
12-
13-
expect(transaction).toMatchObject({
14-
transaction: '/:locale/i18n-test',
15-
transaction_info: { source: 'route' },
16-
contexts: {
17-
trace: {
18-
data: {
19-
'sentry.segment.name.source': 'route',
20-
},
21-
},
22-
},
23-
});
11+
const span = await spanPromise;
12+
13+
expect(span.name).toBe('/:locale/i18n-test');
14+
expect(span.attributes['sentry.segment.name.source']?.value).toBe('route');
2415
});
2516

26-
test('should create consistent parameterized transaction for non-default locale with prefix', async ({ page }) => {
27-
const transactionPromise = waitForTransaction('nextjs-15-intl', async transactionEvent => {
28-
return transactionEvent.transaction === '/:locale/i18n-test' && transactionEvent.contexts?.trace?.op === 'pageload';
17+
test('should create consistent parameterized span for non-default locale with prefix', async ({ page }) => {
18+
const spanPromise = waitForStreamedSpan('nextjs-15-intl', span => {
19+
return span.name === '/:locale/i18n-test' && getSpanOp(span) === 'pageload' && span.is_segment;
2920
});
3021

3122
await page.goto(`/ar/i18n-test`);
3223

33-
const transaction = await transactionPromise;
34-
35-
expect(transaction).toMatchObject({
36-
transaction: '/:locale/i18n-test',
37-
transaction_info: { source: 'route' },
38-
contexts: {
39-
trace: {
40-
data: {
41-
'sentry.segment.name.source': 'route',
42-
},
43-
},
44-
},
45-
});
24+
const span = await spanPromise;
25+
26+
expect(span.name).toBe('/:locale/i18n-test');
27+
expect(span.attributes['sentry.segment.name.source']?.value).toBe('route');
4628
});
4729

4830
test('should parameterize locale root page correctly for default locale without prefix', async ({ page }) => {
49-
const transactionPromise = waitForTransaction('nextjs-15-intl', async transactionEvent => {
50-
return transactionEvent.transaction === '/:locale' && transactionEvent.contexts?.trace?.op === 'pageload';
31+
const spanPromise = waitForStreamedSpan('nextjs-15-intl', span => {
32+
return span.name === '/:locale' && getSpanOp(span) === 'pageload' && span.is_segment;
5133
});
5234

5335
await page.goto(`/`);
5436

55-
const transaction = await transactionPromise;
56-
57-
expect(transaction).toMatchObject({
58-
transaction: '/:locale',
59-
transaction_info: { source: 'route' },
60-
contexts: {
61-
trace: {
62-
data: {
63-
'sentry.segment.name.source': 'route',
64-
},
65-
},
66-
},
67-
});
37+
const span = await spanPromise;
38+
39+
expect(span.name).toBe('/:locale');
40+
expect(span.attributes['sentry.segment.name.source']?.value).toBe('route');
6841
});
6942

7043
test('should parameterize locale root page correctly for non-default locale with prefix', async ({ page }) => {
71-
const transactionPromise = waitForTransaction('nextjs-15-intl', async transactionEvent => {
72-
return transactionEvent.transaction === '/:locale' && transactionEvent.contexts?.trace?.op === 'pageload';
44+
const spanPromise = waitForStreamedSpan('nextjs-15-intl', span => {
45+
return span.name === '/:locale' && getSpanOp(span) === 'pageload' && span.is_segment;
7346
});
7447

7548
await page.goto(`/fr`);
7649

77-
const transaction = await transactionPromise;
78-
79-
expect(transaction).toMatchObject({
80-
transaction: '/:locale',
81-
transaction_info: { source: 'route' },
82-
contexts: {
83-
trace: {
84-
data: {
85-
'sentry.segment.name.source': 'route',
86-
},
87-
},
88-
},
89-
});
50+
const span = await spanPromise;
51+
52+
expect(span.name).toBe('/:locale');
53+
expect(span.attributes['sentry.segment.name.source']?.value).toBe('route');
9054
});

‎dev-packages/e2e-tests/test-applications/nextjs-16-trailing-slash/instrumentation-client.ts‎

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

33
Sentry.init({
4-
traceLifecycle: 'static',
54
environment: 'qa', // dynamic sampling bias to keep transactions
65
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
76
tunnel: `http://localhost:3031/`, // proxy server

0 commit comments

Comments
 (0)