Skip to content

Commit cba23b3

Browse files
chargomeclaude
andauthored
test(e2e): Port the nestjs-8 E2E app to span streaming (#24097)
Removes the `traceLifecycle: 'static'` pin and rewrites the specs against streamed spans. Ref: #23801 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 59496e0 commit cba23b3

4 files changed

Lines changed: 279 additions & 687 deletions

File tree

‎dev-packages/e2e-tests/test-applications/nestjs-8/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-8/tests/errors.test.ts‎

Lines changed: 21 additions & 28 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-8';
35

46
test('Sends exception to Sentry', async ({ baseURL }) => {
5-
const errorEventPromise = waitForError('nestjs-8', 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

@@ -37,38 +39,35 @@ test('Sends exception to Sentry', async ({ baseURL }) => {
3739
test('Does not send HttpExceptions to Sentry', async ({ baseURL }) => {
3840
let errorEventOccurred = false;
3941

40-
waitForError('nestjs-8', event => {
42+
waitForError(APP_NAME, event => {
4143
if (!event.type && event.exception?.values?.[0]?.value === 'This is an expected 400 exception with id 123') {
4244
errorEventOccurred = true;
4345
}
4446

4547
return event?.transaction === 'GET /test-expected-400-exception/:id';
4648
});
4749

48-
waitForError('nestjs-8', event => {
50+
waitForError(APP_NAME, event => {
4951
if (!event.type && event.exception?.values?.[0]?.value === 'This is an expected 500 exception with id 123') {
5052
errorEventOccurred = true;
5153
}
5254

5355
return event?.transaction === 'GET /test-expected-500-exception/:id';
5456
});
5557

56-
const transactionEventPromise400 = waitForTransaction('nestjs-8', transactionEvent => {
57-
return transactionEvent?.transaction === 'GET /test-expected-400-exception/:id';
58-
});
59-
60-
const transactionEventPromise500 = waitForTransaction('nestjs-8', transactionEvent => {
61-
return transactionEvent?.transaction === 'GET /test-expected-500-exception/:id';
62-
});
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');
6362

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

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

70-
await transactionEventPromise400;
71-
await transactionEventPromise500;
69+
await spansPromise400;
70+
await spansPromise500;
7271

7372
(await fetch(`${baseURL}/flush`)).text();
7473

@@ -78,22 +77,20 @@ test('Does not send HttpExceptions to Sentry', async ({ baseURL }) => {
7877
test('Does not send RpcExceptions to Sentry', async ({ baseURL }) => {
7978
let errorEventOccurred = false;
8079

81-
waitForError('nestjs-8', event => {
80+
waitForError(APP_NAME, event => {
8281
if (!event.type && event.exception?.values?.[0]?.value === 'This is an expected RPC exception with id 123') {
8382
errorEventOccurred = true;
8483
}
8584

8685
return event?.transaction === 'GET /test-expected-rpc-exception/:id';
8786
});
8887

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

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

96-
await transactionEventPromise;
93+
await spansPromise;
9794

9895
(await fetch(`${baseURL}/flush`)).text();
9996

@@ -105,17 +102,15 @@ test('Global exception filter registered in main module is applied and exception
105102
}) => {
106103
let errorEventOccurred = false;
107104

108-
waitForError('nestjs-8', event => {
105+
waitForError(APP_NAME, event => {
109106
if (!event.type && event.exception?.values?.[0]?.value === 'Example exception was handled by global filter!') {
110107
errorEventOccurred = true;
111108
}
112109

113110
return event?.transaction === 'GET /example-exception-global-filter';
114111
});
115112

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

120115
const response = await fetch(`${baseURL}/example-exception-global-filter`);
121116
const responseBody = await response.json();
@@ -128,7 +123,7 @@ test('Global exception filter registered in main module is applied and exception
128123
message: 'Example exception was handled by global filter!',
129124
});
130125

131-
await transactionEventPromise;
126+
await spansPromise;
132127

133128
(await fetch(`${baseURL}/flush`)).text();
134129

@@ -140,17 +135,15 @@ test('Local exception filter registered in main module is applied and exception
140135
}) => {
141136
let errorEventOccurred = false;
142137

143-
waitForError('nestjs-8', event => {
138+
waitForError(APP_NAME, event => {
144139
if (!event.type && event.exception?.values?.[0]?.value === 'Example exception was handled by local filter!') {
145140
errorEventOccurred = true;
146141
}
147142

148143
return event?.transaction === 'GET /example-exception-local-filter';
149144
});
150145

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

155148
const response = await fetch(`${baseURL}/example-exception-local-filter`);
156149
const responseBody = await response.json();
@@ -163,7 +156,7 @@ test('Local exception filter registered in main module is applied and exception
163156
message: 'Example exception was handled by local filter!',
164157
});
165158

166-
await transactionEventPromise;
159+
await spansPromise;
167160

168161
(await fetch(`${baseURL}/flush`)).text();
169162

‎dev-packages/e2e-tests/test-applications/nestjs-8/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-8', 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-8';
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-8', 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)