Skip to content

Commit 3cbefca

Browse files
s1gr1dclaude
andauthored
test(e2e): Migrate nuxt-3-top-level-import to span streaming (#23948)
Reference #23804 One change beyond the mechanical port: the server error test read scope tags off the transaction event. Streamed spans do not serialize scope tags, so those assertions are gone. The error event's tags still cover the request-isolation scenario the test cares about. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 9871c8e commit 3cbefca

6 files changed

Lines changed: 159 additions & 178 deletions

File tree

‎dev-packages/e2e-tests/test-applications/nuxt-3-top-level-import/sentry.client.config.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import * as Sentry from '@sentry/nuxt';
22
import { useRuntimeConfig } from '#imports';
33

44
Sentry.init({
5-
traceLifecycle: 'static',
65
environment: 'qa', // dynamic sampling bias to keep transactions
76
dsn: useRuntimeConfig().public.sentry.dsn,
87
tunnel: `http://localhost:3031/`, // proxy server

‎dev-packages/e2e-tests/test-applications/nuxt-3-top-level-import/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/nuxt';
22

33
Sentry.init({
4-
traceLifecycle: 'static',
54
dsn: 'https://public@dsn.ingest.sentry.io/1337',
65
environment: 'qa', // dynamic sampling bias to keep transactions
76
tracesSampleRate: 1.0, // Capture 100% of the transactions

‎dev-packages/e2e-tests/test-applications/nuxt-3-top-level-import/tests/errors.server.test.ts‎

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
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.describe('server-side errors', async () => {
55
test('captures api fetch error (fetched on click)', async ({ page }) => {
6-
const transactionEventPromise = waitForTransaction('nuxt-3-top-level-import', async transactionEvent => {
7-
return transactionEvent?.transaction === 'GET /api/server-error';
6+
// The exact-match API route is not parametrized, so the segment keeps a method-only name and
7+
// has to be selected via its `url.path` attribute.
8+
const serverSpanPromise = waitForStreamedSpan('nuxt-3-top-level-import', span => {
9+
return (
10+
span.is_segment &&
11+
getSpanOp(span) === 'http.server' &&
12+
span.attributes['url.path']?.value === '/api/server-error'
13+
);
814
});
915

1016
const errorPromise = waitForError('nuxt-3-top-level-import', async errorEvent => {
@@ -14,7 +20,7 @@ test.describe('server-side errors', async () => {
1420
await page.goto(`/fetch-server-error`);
1521
await page.getByText('Fetch Server API Error', { exact: true }).click();
1622

17-
const transactionEvent = await transactionEventPromise;
23+
const serverSpan = await serverSpanPromise;
1824
const error = await errorPromise;
1925

2026
expect(error.transaction).toEqual('GET /api/server-error');
@@ -40,15 +46,19 @@ test.describe('server-side errors', async () => {
4046
exception_id: 0,
4147
});
4248

49+
// Streamed spans carry no scope tags, so isolation is asserted on the error event only
50+
expect(serverSpan.name).toBe('GET');
4351
expect(error.tags?.['my-isolated-tag']).toBe(true);
4452
expect(error.tags?.['my-global-scope-isolated-tag']).not.toBeDefined();
45-
expect(transactionEvent.tags?.['my-isolated-tag']).toBe(true);
46-
expect(transactionEvent.tags?.['my-global-scope-isolated-tag']).not.toBeDefined();
4753
});
4854

4955
test('isolates requests', async ({ page }) => {
50-
const transactionEventPromise = waitForTransaction('nuxt-3-top-level-import', async transactionEvent => {
51-
return transactionEvent?.transaction === 'GET /api/server-error';
56+
const serverSpanPromise = waitForStreamedSpan('nuxt-3-top-level-import', span => {
57+
return (
58+
span.is_segment &&
59+
getSpanOp(span) === 'http.server' &&
60+
span.attributes['url.path']?.value === '/api/server-error'
61+
);
5262
});
5363

5464
const errorPromise = waitForError('nuxt-3-top-level-import', async errorEvent => {
@@ -58,13 +68,12 @@ test.describe('server-side errors', async () => {
5868
await page.goto(`/fetch-server-error`);
5969
await page.getByText('Fetch Server API Error', { exact: true }).click();
6070

61-
const transactionEvent = await transactionEventPromise;
71+
await serverSpanPromise;
6272
const error = await errorPromise;
6373

74+
// Streamed spans carry no scope tags, so isolation is asserted on the error event only
6475
expect(error.tags?.['my-isolated-tag']).toBe(true);
6576
expect(error.tags?.['my-global-scope-isolated-tag']).not.toBeDefined();
66-
expect(transactionEvent.tags?.['my-isolated-tag']).toBe(true);
67-
expect(transactionEvent.tags?.['my-global-scope-isolated-tag']).not.toBeDefined();
6877
});
6978

7079
test('captures api fetch error (fetched on click) with parametrized route', async ({ page }) => {
Lines changed: 30 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,49 @@
11
import { expect, test } from '@playwright/test';
2-
import { waitForTransaction } from '@sentry-internal/test-utils';
3-
import type { Span } from '@sentry/nuxt';
2+
import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
43

54
test('sends a pageload root span with a parameterized URL', async ({ page }) => {
6-
const transactionPromise = waitForTransaction('nuxt-3-top-level-import', async transactionEvent => {
7-
return transactionEvent.transaction === '/test-param/:param()';
5+
const pageloadSpanPromise = waitForStreamedSpan('nuxt-3-top-level-import', span => {
6+
return getSpanOp(span) === 'pageload' && span.is_segment;
87
});
98

109
await page.goto(`/test-param/1234`);
1110

12-
const rootSpan = await transactionPromise;
13-
14-
expect(rootSpan).toMatchObject({
15-
contexts: {
16-
trace: {
17-
data: {
18-
'sentry.segment.name.source': 'route',
19-
'sentry.origin': 'auto.pageload.vue',
20-
'sentry.op': 'pageload',
21-
'params.param': '1234',
22-
'url.template': '/test-param/:param()',
23-
'url.path': '/test-param/1234',
24-
'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/test-param\/1234$/),
25-
},
26-
op: 'pageload',
27-
origin: 'auto.pageload.vue',
28-
},
29-
},
30-
transaction: '/test-param/:param()',
31-
transaction_info: {
32-
source: 'route',
33-
},
11+
const pageloadSpan = await pageloadSpanPromise;
12+
13+
expect(pageloadSpan.name).toBe('/test-param/:param()');
14+
expect(pageloadSpan.status).toBe('ok');
15+
expect(pageloadSpan.attributes).toMatchObject({
16+
'sentry.segment.name.source': { type: 'string', value: 'route' },
17+
'sentry.origin': { type: 'string', value: 'auto.pageload.vue' },
18+
'sentry.op': { type: 'string', value: 'pageload' },
19+
'params.param': { type: 'string', value: '1234' },
20+
'url.template': { type: 'string', value: '/test-param/:param()' },
21+
'url.path': { type: 'string', value: '/test-param/1234' },
22+
'url.full': { type: 'string', value: expect.stringMatching(/^https?:\/\/localhost:\d+\/test-param\/1234$/) },
3423
});
3524
});
3625

3726
test('sends component tracking spans when `trackComponents` is enabled', async ({ page }) => {
38-
const transactionPromise = waitForTransaction('nuxt-3-top-level-import', async transactionEvent => {
39-
return transactionEvent.transaction === '/client-error';
40-
});
27+
const spansPromise = collectStreamedSpans('nuxt-3-top-level-import', spans =>
28+
spans.some(span => span.name === '/client-error' && span.is_segment && getSpanOp(span) === 'pageload'),
29+
);
4130

4231
await page.goto(`/client-error`);
4332

44-
const rootSpan = await transactionPromise;
45-
const errorButtonSpan = rootSpan.spans.find((span: Span) => span.description === 'Vue <ErrorButton>');
33+
const spans = await spansPromise;
34+
const errorButtonSpan = spans.find(span => span.name === 'Vue <ErrorButton>');
4635

47-
const expected = {
48-
data: { 'sentry.origin': 'auto.ui.vue', 'sentry.op': 'ui.mount' },
49-
description: 'Vue <ErrorButton>',
50-
op: 'ui.mount',
36+
expect(errorButtonSpan).toMatchObject({
37+
name: 'Vue <ErrorButton>',
38+
is_segment: false,
5139
parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
5240
span_id: expect.stringMatching(/[a-f0-9]{16}/),
53-
start_timestamp: expect.any(Number),
54-
timestamp: expect.any(Number),
5541
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
56-
origin: 'auto.ui.vue',
57-
};
58-
59-
expect(errorButtonSpan).toMatchObject(expected);
42+
start_timestamp: expect.any(Number),
43+
end_timestamp: expect.any(Number),
44+
attributes: expect.objectContaining({
45+
'sentry.op': { type: 'string', value: 'ui.mount' },
46+
'sentry.origin': { type: 'string', value: 'auto.ui.vue' },
47+
}),
48+
});
6049
});
Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,39 @@
11
import { expect, test } from '@playwright/test';
2-
import { waitForTransaction } from '@sentry-internal/test-utils';
3-
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/nuxt';
2+
import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
43

5-
test('sends a server action transaction on pageload', async ({ page }) => {
6-
const transactionPromise = waitForTransaction('nuxt-3-top-level-import', transactionEvent => {
7-
return transactionEvent.transaction.includes('GET /test-param/');
4+
test('sends a server root span on pageload', async ({ page }) => {
5+
const serverSpanPromise = waitForStreamedSpan('nuxt-3-top-level-import', span => {
6+
return span.is_segment && span.name.includes('GET /test-param/');
87
});
98

109
await page.goto('/test-param/1234');
1110

12-
const transaction = await transactionPromise;
11+
const serverSpan = await serverSpanPromise;
1312

14-
expect(transaction.contexts.trace).toEqual(
15-
expect.objectContaining({
16-
data: expect.objectContaining({
17-
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server',
18-
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.http_server',
19-
}),
20-
}),
21-
);
13+
expect(getSpanOp(serverSpan)).toBe('http.server');
14+
expect(serverSpan.attributes['sentry.origin']?.value).toBe('auto.http.http_server');
2215
});
2316

24-
test('does not send transactions for build asset folder "_nuxt"', async ({ page }) => {
17+
test('does not send spans for build asset folder "_nuxt"', async ({ page }) => {
2518
let buildAssetFolderOccurred = false;
2619

27-
waitForTransaction('nuxt-3-top-level-import', transactionEvent => {
28-
if (transactionEvent.transaction?.match(/^GET \/_nuxt\//)) {
20+
waitForStreamedSpan('nuxt-3-top-level-import', span => {
21+
if (span.is_segment && /^GET \/_nuxt\//.test(span.name)) {
2922
buildAssetFolderOccurred = true;
3023
}
3124
return false; // expects to return a boolean (but not relevant here)
3225
});
3326

34-
const transactionEventPromise = waitForTransaction('nuxt-3-top-level-import', transactionEvent => {
35-
return transactionEvent.transaction.includes('GET /test-param/');
27+
const serverSpanPromise = waitForStreamedSpan('nuxt-3-top-level-import', span => {
28+
return span.is_segment && span.name.includes('GET /test-param/');
3629
});
3730

3831
await page.goto('/test-param/1234');
3932

40-
const transactionEvent = await transactionEventPromise;
33+
const serverSpan = await serverSpanPromise;
4134

4235
expect(buildAssetFolderOccurred).toBe(false);
4336

44-
expect(transactionEvent.transaction).toBe('GET /test-param/:param()');
37+
expect(serverSpan.name).toBe('GET /test-param/:param()');
38+
expect(serverSpan.attributes['sentry.segment.name.source']?.value).toBe('route');
4539
});

0 commit comments

Comments
 (0)