Skip to content

Commit 86febfa

Browse files
msonnbcodex
andcommitted
test(e2e): Migrate Firebase and Electron fixtures to span streaming
Co-Authored-By: GPT-6 <codex@openai.com>
1 parent 37118dd commit 86febfa

6 files changed

Lines changed: 137 additions & 189 deletions

File tree

‎dev-packages/e2e-tests/test-applications/node-firebase/firestore-app/src/init.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import * as Sentry from '@sentry/node';
44
// default. This file is imported before `app.ts` imports `firebase/firestore/lite`, so the
55
// channel-injection hooks are installed before firestore loads.
66
Sentry.init({
7-
traceLifecycle: 'static',
87
dsn: 'https://public@dsn.ingest.sentry.io/1337',
98
release: '1.0',
109
tracesSampleRate: 1.0,

‎dev-packages/e2e-tests/test-applications/node-firebase/functions/src/init.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/node';
22

33
Sentry.init({
4-
traceLifecycle: 'static',
54
dsn: 'https://public@dsn.ingest.sentry.io/1337',
65
release: '1.0',
76
tracesSampleRate: 1.0,
Lines changed: 86 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1,154 +1,107 @@
11
import { expect, test } from '@playwright/test';
2-
import { waitForError, waitForTransaction } from '@sentry-internal/test-utils';
2+
import { collectStreamedSpans, getSpanOp, waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils';
33

44
// FIXME: firebase-functions runs inside the Firebase emulator, where the channel-injection runtime
55
// module hook doesn't transform the emulator-loaded handlers, so no channel spans are produced.
66
// (Firestore in a plain Node process works.) Deferred; tracked separately for a channel firebase-
77
// functions emulator fix.
88
test.fixme('should only call the function once without any extra calls', async () => {
9-
const serverTransactionPromise = waitForTransaction('node-firebase', span => {
10-
return span.transaction === 'firebase.function.http.request';
11-
});
12-
13-
await fetch(`http://localhost:5001/demo-functions/default/helloWorld`);
9+
const spanPromise = waitForStreamedSpan(
10+
'node-firebase',
11+
span =>
12+
span.is_segment &&
13+
span.name === 'firebase.function.http.request' &&
14+
span.attributes['faas.name']?.value === 'helloWorld',
15+
);
1416

15-
const transactionEvent = await serverTransactionPromise;
17+
await fetch('http://localhost:5001/demo-functions/default/helloWorld');
1618

17-
expect(transactionEvent.transaction).toEqual('firebase.function.http.request');
18-
expect(transactionEvent.contexts).toEqual(
19-
expect.objectContaining({
20-
trace: expect.objectContaining({
21-
data: {
22-
'cloud.project_id': 'demo-functions',
23-
'faas.name': 'helloWorld',
24-
'faas.provider': 'firebase',
25-
'faas.trigger': 'http.request',
26-
'sentry.kind': 'server',
27-
'sentry.op': 'http.request',
28-
'sentry.origin': 'auto.firebase.functions',
29-
'sentry.sample_rate': expect.any(Number),
30-
'sentry.segment.name.source': 'route',
31-
},
32-
op: 'http.request',
33-
origin: 'auto.firebase.functions',
34-
span_id: expect.any(String),
35-
status: 'ok',
36-
trace_id: expect.any(String),
37-
}),
19+
const span = await spanPromise;
20+
expect(getSpanOp(span)).toBe('http.request');
21+
expect(span).toMatchObject({
22+
name: 'firebase.function.http.request',
23+
status: 'ok',
24+
span_id: expect.any(String),
25+
trace_id: expect.any(String),
26+
attributes: expect.objectContaining({
27+
'cloud.project_id': { value: 'demo-functions', type: 'string' },
28+
'faas.name': { value: 'helloWorld', type: 'string' },
29+
'faas.provider': { value: 'firebase', type: 'string' },
30+
'faas.trigger': { value: 'http.request', type: 'string' },
31+
'sentry.kind': { value: 'server', type: 'string' },
32+
'sentry.origin': { value: 'auto.firebase.functions', type: 'string' },
33+
'sentry.sample_rate': { value: expect.any(Number), type: 'integer' },
34+
'sentry.segment.name.source': { value: 'route', type: 'string' },
3835
}),
39-
);
40-
});
41-
42-
test.fixme('should send failed transaction when the function fails', async () => {
43-
const errorEventPromise = waitForError('node-firebase', () => true);
44-
const serverTransactionPromise = waitForTransaction('node-firebase', span => {
45-
return !!span.transaction;
4636
});
37+
});
4738

48-
await fetch(`http://localhost:5001/demo-functions/default/unhandeledError`);
39+
test.fixme('should send failed span when the function fails', async () => {
40+
const errorPromise = waitForError(
41+
'node-firebase',
42+
event => event.exception?.values?.[0]?.value === 'There is an error!',
43+
);
44+
const spanPromise = waitForStreamedSpan(
45+
'node-firebase',
46+
span =>
47+
span.is_segment &&
48+
span.name === 'firebase.function.http.request' &&
49+
span.attributes['faas.name']?.value === 'unhandeledError',
50+
);
4951

50-
const transactionEvent = await serverTransactionPromise;
51-
const errorEvent = await errorEventPromise;
52+
await fetch('http://localhost:5001/demo-functions/default/unhandeledError');
5253

53-
expect(transactionEvent.transaction).toEqual('firebase.function.http.request');
54-
expect(transactionEvent.contexts?.trace?.trace_id).toEqual(errorEvent.contexts?.trace?.trace_id);
55-
expect(errorEvent).toMatchObject({
56-
exception: {
57-
values: [
58-
{
59-
type: 'Error',
60-
value: 'There is an error!',
61-
mechanism: {
62-
type: 'auto.firebase.functions',
63-
handled: false,
64-
},
65-
},
66-
],
67-
},
68-
});
54+
const span = await spanPromise;
55+
const error = await errorPromise;
56+
expect(span.status).toBe('error');
57+
expect(span.trace_id).toBe(error.contexts?.trace?.trace_id);
58+
expect(error.exception?.values).toEqual([
59+
expect.objectContaining({
60+
type: 'Error',
61+
value: 'There is an error!',
62+
mechanism: { type: 'auto.firebase.functions', handled: false },
63+
}),
64+
]);
6965
});
7066

7167
test.fixme('should create a document and trigger onDocumentCreated and another with authContext', async () => {
72-
const serverTransactionPromise = waitForTransaction('node-firebase', span => {
73-
return span.transaction === 'firebase.function.http.request';
74-
});
75-
76-
const serverTransactionOnDocumentCreatePromise = waitForTransaction('node-firebase', span => {
77-
return (
78-
span.transaction === 'firebase.function.firestore.document.created' &&
79-
span.contexts?.trace?.data?.['faas.name'] === 'onDocumentCreate'
80-
);
81-
});
82-
83-
const serverTransactionOnDocumentWithAuthContextCreatePromise = waitForTransaction('node-firebase', span => {
84-
return (
85-
span.transaction === 'firebase.function.firestore.document.created' &&
86-
span.contexts?.trace?.data?.['faas.name'] === 'onDocumentCreateWithAuthContext'
87-
);
88-
});
89-
90-
await fetch(`http://localhost:5001/demo-functions/default/onCallSomething`);
68+
const functions = [
69+
{ name: 'onCallSomething', trigger: 'http.request', children: 3 },
70+
{ name: 'onDocumentCreate', trigger: 'firestore.document.created', children: 2 },
71+
{ name: 'onDocumentCreateWithAuthContext', trigger: 'firestore.document.created', children: 0 },
72+
];
73+
const spanPromises = functions.map(({ name, children }) =>
74+
collectStreamedSpans(
75+
'node-firebase',
76+
spans =>
77+
spans.some(span => span.is_segment && span.attributes['faas.name']?.value === name) &&
78+
spans.filter(span => !span.is_segment).length >= children,
79+
),
80+
);
9181

92-
const transactionEvent = await serverTransactionPromise;
93-
const transactionEventOnDocumentCreate = await serverTransactionOnDocumentCreatePromise;
94-
const transactionEventOnDocumentWithAuthContextCreate = await serverTransactionOnDocumentWithAuthContextCreatePromise;
82+
await fetch('http://localhost:5001/demo-functions/default/onCallSomething');
9583

96-
expect(transactionEvent.transaction).toEqual('firebase.function.http.request');
97-
expect(transactionEvent.contexts?.trace).toEqual({
98-
data: {
99-
'cloud.project_id': 'demo-functions',
100-
'faas.name': 'onCallSomething',
101-
'faas.provider': 'firebase',
102-
'faas.trigger': 'http.request',
103-
'sentry.kind': 'server',
104-
'sentry.op': 'http.request',
105-
'sentry.origin': 'auto.firebase.functions',
106-
'sentry.sample_rate': expect.any(Number),
107-
'sentry.segment.name.source': 'route',
108-
},
109-
op: 'http.request',
110-
origin: 'auto.firebase.functions',
111-
span_id: expect.any(String),
112-
status: 'ok',
113-
trace_id: expect.any(String),
114-
});
115-
expect(transactionEvent.spans).toHaveLength(3);
116-
expect(transactionEventOnDocumentCreate.contexts?.trace).toEqual({
117-
data: {
118-
'cloud.project_id': 'demo-functions',
119-
'faas.name': 'onDocumentCreate',
120-
'faas.provider': 'firebase',
121-
'faas.trigger': 'firestore.document.created',
122-
'sentry.kind': 'server',
123-
'sentry.op': expect.any(String),
124-
'sentry.origin': 'auto.firebase.functions',
125-
'sentry.sample_rate': expect.any(Number),
126-
'sentry.segment.name.source': 'route',
127-
},
128-
op: expect.any(String),
129-
origin: 'auto.firebase.functions',
130-
span_id: expect.any(String),
131-
status: 'ok',
132-
trace_id: expect.any(String),
133-
});
134-
expect(transactionEventOnDocumentCreate.spans).toHaveLength(2);
135-
expect(transactionEventOnDocumentWithAuthContextCreate.contexts?.trace).toEqual({
136-
data: {
137-
'cloud.project_id': 'demo-functions',
138-
'faas.name': 'onDocumentCreateWithAuthContext',
139-
'faas.provider': 'firebase',
140-
'faas.trigger': 'firestore.document.created',
141-
'sentry.kind': 'server',
142-
'sentry.op': expect.any(String),
143-
'sentry.origin': 'auto.firebase.functions',
144-
'sentry.sample_rate': expect.any(Number),
145-
'sentry.segment.name.source': 'route',
146-
},
147-
op: expect.any(String),
148-
origin: 'auto.firebase.functions',
149-
span_id: expect.any(String),
150-
status: 'ok',
151-
trace_id: expect.any(String),
84+
const traces = await Promise.all(spanPromises);
85+
functions.forEach(({ name, trigger, children }, index) => {
86+
const spans = traces[index]!;
87+
const segment = spans.find(span => span.is_segment && span.attributes['faas.name']?.value === name)!;
88+
expect(segment).toMatchObject({
89+
name: `firebase.function.${trigger}`,
90+
status: 'ok',
91+
span_id: expect.any(String),
92+
trace_id: expect.any(String),
93+
attributes: expect.objectContaining({
94+
'cloud.project_id': { value: 'demo-functions', type: 'string' },
95+
'faas.name': { value: name, type: 'string' },
96+
'faas.provider': { value: 'firebase', type: 'string' },
97+
'faas.trigger': { value: trigger, type: 'string' },
98+
'sentry.kind': { value: 'server', type: 'string' },
99+
'sentry.op': { value: expect.any(String), type: 'string' },
100+
'sentry.origin': { value: 'auto.firebase.functions', type: 'string' },
101+
'sentry.sample_rate': { value: expect.any(Number), type: 'integer' },
102+
'sentry.segment.name.source': { value: 'route', type: 'string' },
103+
}),
104+
});
105+
expect(spans.filter(span => !span.is_segment)).toHaveLength(children);
152106
});
153-
expect(transactionEventOnDocumentWithAuthContextCreate.spans).toHaveLength(0);
154107
});
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { expect, test } from '@playwright/test';
2+
import { collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils';
3+
4+
// The orchestrion spans are Sentry-native, so they carry no span-kind attribute (`sentry.kind`).
5+
const origin = 'auto.firebase.firestore';
6+
7+
function firestoreSpan(operation: string): unknown {
8+
return expect.objectContaining({
9+
name: `${operation} cities`,
10+
is_segment: false,
11+
parent_span_id: expect.any(String),
12+
trace_id: expect.any(String),
13+
span_id: expect.any(String),
14+
end_timestamp: expect.any(Number),
15+
start_timestamp: expect.any(Number),
16+
status: 'ok',
17+
attributes: expect.objectContaining({
18+
'db.collection.name': { value: 'cities', type: 'string' },
19+
'db.namespace': { value: '[DEFAULT]', type: 'string' },
20+
'db.operation.name': { value: operation, type: 'string' },
21+
'db.system.name': { value: 'firebase.firestore', type: 'string' },
22+
'firebase.firestore.options.projectId': { value: 'sentry-15d85', type: 'string' },
23+
'firebase.firestore.type': { value: 'collection', type: 'string' },
24+
'server.address': { value: '127.0.0.1', type: 'string' },
25+
'server.port': { value: 8080, type: 'integer' },
26+
'sentry.origin': { value: origin, type: 'string' },
27+
'sentry.op': { value: 'db.query', type: 'string' },
28+
}),
29+
});
30+
}
31+
32+
const spanAddDoc = firestoreSpan('addDoc');
33+
const spanSetDocs = firestoreSpan('setDoc');
34+
const spanGetDocs = firestoreSpan('getDocs');
35+
const spanDeleteDoc = firestoreSpan('deleteDoc');
36+
37+
test('should add, set, get and delete document', async ({ baseURL }) => {
38+
const serverSegmentPromise = collectStreamedSpansUntilSegment('node-firebase', 'Test Transaction');
39+
40+
await fetch(`${baseURL}/test`);
41+
42+
const segmentEventSpans = await serverSegmentPromise;
43+
const segmentEvent = segmentEventSpans.find(segment => segment.is_segment && segment.name === 'Test Transaction')!;
44+
45+
expect(segmentEvent.name).toEqual('Test Transaction');
46+
const children = segmentEventSpans.filter(
47+
span => !span.is_segment && span.attributes['sentry.segment.id']?.value === segmentEvent.span_id,
48+
);
49+
expect(children).toHaveLength(4);
50+
expect(children).toEqual(expect.arrayContaining([spanAddDoc, spanSetDocs, spanGetDocs, spanDeleteDoc]));
51+
});

‎dev-packages/e2e-tests/test-applications/node-firebase/tests/transactions.test.ts‎

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

‎dev-packages/e2e-tests/test-applications/node-profiling-electron/index.electron.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ const Sentry = require('@sentry/electron/main');
44
const path = require('node:path');
55

66
Sentry.init({
7-
traceLifecycle: 'static',
87
dsn: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302',
98
debug: true,
109
tracesSampleRate: 1.0,

0 commit comments

Comments
 (0)