Skip to content

Commit e57d55c

Browse files
chargomeclaude
andauthored
test(e2e): Port the nestjs-fastify E2E app to span streaming (#24100)
Removes the `traceLifecycle: 'static'` pin and rewrites the specs against streamed spans. Fastify names both its request span and its middie route-handler span after the route, so the spec tells them apart by attribute rather than by name. Ref: #23801 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 02151f5 commit e57d55c

4 files changed

Lines changed: 336 additions & 751 deletions

File tree

dev-packages/e2e-tests/test-applications/nestjs-fastify/src/instrument.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/nestjs';
22

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

dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/errors.test.ts

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { expect, test } from '@playwright/test';
2-
import { waitForError, waitForTransaction } from '@sentry-internal/test-utils';
2+
import { collectStreamedSpansUntilSegment, waitForError } from '@sentry-internal/test-utils';
3+
4+
const APP_NAME = 'nestjs-fastify';
35

46
test('Sends exception to Sentry', async ({ baseURL }) => {
5-
const errorEventPromise = waitForError('nestjs-fastify', event => {
7+
const errorEventPromise = waitForError(APP_NAME, event => {
68
return !event.type && event.exception?.values?.[0]?.value === 'This is an exception with id 123';
79
});
810

@@ -13,7 +15,6 @@ test('Sends exception to Sentry', async ({ baseURL }) => {
1315

1416
expect(errorEvent.exception?.values).toHaveLength(1);
1517
expect(errorEvent.exception?.values?.[0]?.value).toBe('This is an exception with id 123');
16-
1718
expect(errorEvent.exception?.values?.[0]?.mechanism).toEqual({
1819
handled: false,
1920
type: 'auto.http.nestjs.global_filter',
@@ -29,47 +30,44 @@ test('Sends exception to Sentry', async ({ baseURL }) => {
2930
expect(errorEvent.transaction).toEqual('GET /test-exception/:id');
3031

3132
expect(errorEvent.contexts?.trace).toEqual({
32-
parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
3333
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
3434
span_id: expect.stringMatching(/[a-f0-9]{16}/),
35+
parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
3536
});
3637
});
3738

3839
test('Does not send HttpExceptions to Sentry', async ({ baseURL }) => {
3940
let errorEventOccurred = false;
4041

41-
waitForError('nestjs-fastify', event => {
42+
waitForError(APP_NAME, event => {
4243
if (!event.type && event.exception?.values?.[0]?.value === 'This is an expected 400 exception with id 123') {
4344
errorEventOccurred = true;
4445
}
4546

4647
return event?.transaction === 'GET /test-expected-400-exception/:id';
4748
});
4849

49-
waitForError('nestjs-fastify', event => {
50+
waitForError(APP_NAME, event => {
5051
if (!event.type && event.exception?.values?.[0]?.value === 'This is an expected 500 exception with id 123') {
5152
errorEventOccurred = true;
5253
}
5354

5455
return event?.transaction === 'GET /test-expected-500-exception/:id';
5556
});
5657

57-
const transactionEventPromise400 = waitForTransaction('nestjs-fastify', transactionEvent => {
58-
return transactionEvent?.transaction === 'GET /test-expected-400-exception/:id';
59-
});
60-
61-
const transactionEventPromise500 = waitForTransaction('nestjs-fastify', transactionEvent => {
62-
return transactionEvent?.transaction === 'GET /test-expected-500-exception/:id';
63-
});
58+
// Waiting for each request's segment span is how this spec knows the request finished and
59+
// any error it would have produced had its chance to be sent.
60+
const spansPromise400 = collectStreamedSpansUntilSegment(APP_NAME, 'GET /test-expected-400-exception/:id');
61+
const spansPromise500 = collectStreamedSpansUntilSegment(APP_NAME, 'GET /test-expected-500-exception/:id');
6462

6563
const response400 = await fetch(`${baseURL}/test-expected-400-exception/123`);
6664
expect(response400.status).toBe(400);
6765

6866
const response500 = await fetch(`${baseURL}/test-expected-500-exception/123`);
6967
expect(response500.status).toBe(500);
7068

71-
await transactionEventPromise400;
72-
await transactionEventPromise500;
69+
await spansPromise400;
70+
await spansPromise500;
7371

7472
(await fetch(`${baseURL}/flush`)).text();
7573

@@ -79,22 +77,20 @@ test('Does not send HttpExceptions to Sentry', async ({ baseURL }) => {
7977
test('Does not send RpcExceptions to Sentry', async ({ baseURL }) => {
8078
let errorEventOccurred = false;
8179

82-
waitForError('nestjs-fastify', event => {
80+
waitForError(APP_NAME, event => {
8381
if (!event.type && event.exception?.values?.[0]?.value === 'This is an expected RPC exception with id 123') {
8482
errorEventOccurred = true;
8583
}
8684

8785
return event?.transaction === 'GET /test-expected-rpc-exception/:id';
8886
});
8987

90-
const transactionEventPromise = waitForTransaction('nestjs-fastify', transactionEvent => {
91-
return transactionEvent?.transaction === 'GET /test-expected-rpc-exception/:id';
92-
});
88+
const spansPromise = collectStreamedSpansUntilSegment(APP_NAME, 'GET /test-expected-rpc-exception/:id');
9389

9490
const response = await fetch(`${baseURL}/test-expected-rpc-exception/123`);
9591
expect(response.status).toBe(500);
9692

97-
await transactionEventPromise;
93+
await spansPromise;
9894

9995
(await fetch(`${baseURL}/flush`)).text();
10096

@@ -106,17 +102,15 @@ test('Global exception filter registered in main module is applied and exception
106102
}) => {
107103
let errorEventOccurred = false;
108104

109-
waitForError('nestjs-fastify', event => {
105+
waitForError(APP_NAME, event => {
110106
if (!event.type && event.exception?.values?.[0]?.value === 'Example exception was handled by global filter!') {
111107
errorEventOccurred = true;
112108
}
113109

114110
return event?.transaction === 'GET /example-exception-global-filter';
115111
});
116112

117-
const transactionEventPromise = waitForTransaction('nestjs-fastify', transactionEvent => {
118-
return transactionEvent?.transaction === 'GET /example-exception-global-filter';
119-
});
113+
const spansPromise = collectStreamedSpansUntilSegment(APP_NAME, 'GET /example-exception-global-filter');
120114

121115
const response = await fetch(`${baseURL}/example-exception-global-filter`);
122116
const responseBody = await response.json();
@@ -129,7 +123,7 @@ test('Global exception filter registered in main module is applied and exception
129123
message: 'Example exception was handled by global filter!',
130124
});
131125

132-
await transactionEventPromise;
126+
await spansPromise;
133127

134128
(await fetch(`${baseURL}/flush`)).text();
135129

@@ -141,17 +135,15 @@ test('Local exception filter registered in main module is applied and exception
141135
}) => {
142136
let errorEventOccurred = false;
143137

144-
waitForError('nestjs-fastify', event => {
138+
waitForError(APP_NAME, event => {
145139
if (!event.type && event.exception?.values?.[0]?.value === 'Example exception was handled by local filter!') {
146140
errorEventOccurred = true;
147141
}
148142

149143
return event?.transaction === 'GET /example-exception-local-filter';
150144
});
151145

152-
const transactionEventPromise = waitForTransaction('nestjs-fastify', transactionEvent => {
153-
return transactionEvent?.transaction === 'GET /example-exception-local-filter';
154-
});
146+
const spansPromise = collectStreamedSpansUntilSegment(APP_NAME, 'GET /example-exception-local-filter');
155147

156148
const response = await fetch(`${baseURL}/example-exception-local-filter`);
157149
const responseBody = await response.json();
@@ -164,7 +156,7 @@ test('Local exception filter registered in main module is applied and exception
164156
message: 'Example exception was handled by local filter!',
165157
});
166158

167-
await transactionEventPromise;
159+
await spansPromise;
168160

169161
(await fetch(`${baseURL}/flush`)).text();
170162

dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/span-decorator.test.ts

Lines changed: 27 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,51 @@
11
import { expect, test } from '@playwright/test';
2-
import { waitForTransaction } from '@sentry-internal/test-utils';
2+
import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils';
33

4-
test('Transaction includes span and correct value for decorated async function', async ({ baseURL }) => {
5-
const transactionEventPromise = waitForTransaction('nestjs-fastify', transactionEvent => {
6-
return (
7-
transactionEvent?.contexts?.trace?.op === 'http.server' &&
8-
transactionEvent?.transaction === 'GET /test-span-decorator-async'
9-
);
10-
});
4+
const APP_NAME = 'nestjs-fastify';
5+
6+
test('Trace includes span and correct value for decorated async function', async ({ baseURL }) => {
7+
const spansPromise = collectStreamedSpansUntilSegment(APP_NAME, 'GET /test-span-decorator-async');
118

129
const response = await fetch(`${baseURL}/test-span-decorator-async`);
1310
const body = await response.json();
1411

1512
expect(body.result).toEqual('test');
1613

17-
const transactionEvent = await transactionEventPromise;
14+
const spans = await spansPromise;
1815

19-
expect(transactionEvent.spans).toEqual(
20-
expect.arrayContaining([
21-
expect.objectContaining({
22-
span_id: expect.stringMatching(/[a-f0-9]{16}/),
23-
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
24-
data: {
25-
'sentry.origin': 'auto.function.nestjs.sentry_traced',
26-
'sentry.op': 'wait and return a string',
27-
},
28-
description: 'wait',
29-
parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
30-
start_timestamp: expect.any(Number),
31-
status: 'ok',
32-
op: 'wait and return a string',
33-
origin: 'auto.function.nestjs.sentry_traced',
16+
expect(spans).toContainEqual(
17+
expect.objectContaining({
18+
name: 'wait',
19+
is_segment: false,
20+
status: 'ok',
21+
attributes: expect.objectContaining({
22+
'sentry.origin': { type: 'string', value: 'auto.function.nestjs.sentry_traced' },
23+
'sentry.op': { type: 'string', value: 'wait and return a string' },
3424
}),
35-
]),
25+
}),
3626
);
3727
});
3828

39-
test('Transaction includes span and correct value for decorated sync function', async ({ baseURL }) => {
40-
const transactionEventPromise = waitForTransaction('nestjs-fastify', transactionEvent => {
41-
return (
42-
transactionEvent?.contexts?.trace?.op === 'http.server' &&
43-
transactionEvent?.transaction === 'GET /test-span-decorator-sync'
44-
);
45-
});
29+
test('Trace includes span and correct value for decorated sync function', async ({ baseURL }) => {
30+
const spansPromise = collectStreamedSpansUntilSegment(APP_NAME, 'GET /test-span-decorator-sync');
4631

4732
const response = await fetch(`${baseURL}/test-span-decorator-sync`);
4833
const body = await response.json();
4934

5035
expect(body.result).toEqual('test');
5136

52-
const transactionEvent = await transactionEventPromise;
37+
const spans = await spansPromise;
5338

54-
expect(transactionEvent.spans).toEqual(
55-
expect.arrayContaining([
56-
expect.objectContaining({
57-
span_id: expect.stringMatching(/[a-f0-9]{16}/),
58-
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
59-
data: {
60-
'sentry.origin': 'auto.function.nestjs.sentry_traced',
61-
'sentry.op': 'return a string',
62-
},
63-
description: 'getString',
64-
parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
65-
start_timestamp: expect.any(Number),
66-
status: 'ok',
67-
op: 'return a string',
68-
origin: 'auto.function.nestjs.sentry_traced',
39+
expect(spans).toContainEqual(
40+
expect.objectContaining({
41+
name: 'getString',
42+
is_segment: false,
43+
status: 'ok',
44+
attributes: expect.objectContaining({
45+
'sentry.origin': { type: 'string', value: 'auto.function.nestjs.sentry_traced' },
46+
'sentry.op': { type: 'string', value: 'return a string' },
6947
}),
70-
]),
48+
}),
7149
);
7250
});
7351

0 commit comments

Comments
 (0)