Skip to content

Commit 38f2efb

Browse files
committed
test(profiling): Add tests for current state of profiling
1 parent 018db43 commit 38f2efb

File tree

2 files changed

+123
-0
lines changed

2 files changed

+123
-0
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://[email protected]/1337',
7+
integrations: [Sentry.browserProfilingIntegration()],
8+
tracesSampleRate: 1,
9+
profilesSampleRate: 1,
10+
});
11+
12+
function fibonacci(n) {
13+
if (n <= 1) {
14+
return n;
15+
}
16+
return fibonacci(n - 1) + fibonacci(n - 2);
17+
}
18+
19+
await Sentry.startSpanManual({ name: 'root-fibonacci-2', parentSpan: null, forceTransaction: true }, async span => {
20+
fibonacci(30);
21+
22+
// Timeout to prevent flaky tests. Integration samples every 20ms, if function is too fast it might not get sampled
23+
await new Promise(resolve => setTimeout(resolve, 21));
24+
span.end();
25+
});
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { expect } from '@playwright/test';
2+
import type { Event, Profile } from '@sentry/core';
3+
import { sentryTest } from '../../../utils/fixtures';
4+
import { properEnvelopeRequestParser, waitForTransactionRequestOnUrl } from '../../../utils/helpers';
5+
6+
sentryTest('does not send profile envelope when document-policy is not set', async ({ page, getLocalTestUrl }) => {
7+
const url = await getLocalTestUrl({ testDir: __dirname });
8+
await page.goto(url);
9+
10+
const req = await waitForTransactionRequestOnUrl(page, url);
11+
const transactionEvent = properEnvelopeRequestParser<Event>(req, 0);
12+
const profileEvent = properEnvelopeRequestParser<Profile>(req, 1);
13+
14+
expect(transactionEvent).toBeDefined();
15+
16+
expect(profileEvent).toBeUndefined();
17+
});
18+
19+
sentryTest('sends profile envelope in legacy mode', async ({ page, getLocalTestUrl }) => {
20+
const url = await getLocalTestUrl({ testDir: __dirname, responseHeaders: { 'Document-Policy': 'js-profiling' } });
21+
await page.goto(url);
22+
23+
const req = await waitForTransactionRequestOnUrl(page, url);
24+
const profileEvent = properEnvelopeRequestParser<Profile>(req, 1);
25+
26+
const profile = profileEvent.profile;
27+
28+
expect(profileEvent).toBeDefined();
29+
expect(profileEvent.profile).toBeDefined();
30+
31+
expect(profile.samples).toBeDefined();
32+
expect(profile.stacks).toBeDefined();
33+
expect(profile.frames).toBeDefined();
34+
expect(profile.thread_metadata).toBeDefined();
35+
36+
// Samples
37+
expect(profile.samples.length).toBeGreaterThanOrEqual(2);
38+
for (const sample of profile.samples) {
39+
expect(typeof sample.elapsed_since_start_ns).toBe('string');
40+
expect(sample.elapsed_since_start_ns).toMatch(/^\d+$/); // Numeric string
41+
expect(parseInt(sample.elapsed_since_start_ns, 10)).toBeGreaterThanOrEqual(0);
42+
43+
expect(typeof sample.stack_id).toBe('number');
44+
expect(sample.stack_id).toBeGreaterThanOrEqual(0);
45+
expect(sample.thread_id).toBe('0'); // Should be main thread
46+
}
47+
48+
// Stacks
49+
expect(profile.stacks.length).toBeGreaterThan(0);
50+
for (const stack of profile.stacks) {
51+
expect(Array.isArray(stack)).toBe(true);
52+
for (const frameIndex of stack) {
53+
expect(typeof frameIndex).toBe('number');
54+
expect(frameIndex).toBeGreaterThanOrEqual(0);
55+
expect(frameIndex).toBeLessThan(profile.frames.length);
56+
}
57+
}
58+
59+
// Frames
60+
expect(profile.frames.length).toBeGreaterThan(0);
61+
for (const frame of profile.frames) {
62+
expect(frame).toHaveProperty('function');
63+
expect(frame).toHaveProperty('abs_path');
64+
expect(frame).toHaveProperty('lineno');
65+
expect(frame).toHaveProperty('colno');
66+
67+
expect(typeof frame.function).toBe('string');
68+
expect(typeof frame.abs_path).toBe('string');
69+
expect(typeof frame.lineno).toBe('number');
70+
expect(typeof frame.colno).toBe('number');
71+
}
72+
73+
const functionNames = profile.frames.map(frame => frame.function).filter(name => name !== '');
74+
75+
expect(functionNames).toEqual(
76+
expect.arrayContaining([
77+
'_startRootSpan',
78+
'withScope',
79+
'createChildOrRootSpan',
80+
'startSpanManual',
81+
'startProfileForSpan',
82+
'startJSSelfProfile',
83+
]),
84+
);
85+
86+
expect(profile.thread_metadata).toHaveProperty('0');
87+
expect(profile.thread_metadata['0']).toHaveProperty('name');
88+
expect(profile.thread_metadata['0'].name).toBe('main');
89+
90+
// Test that profile duration makes sense (should be > 20ms based on test setup)
91+
const startTime = parseInt(profile.samples[0].elapsed_since_start_ns, 10);
92+
const endTime = parseInt(profile.samples[profile.samples.length - 1].elapsed_since_start_ns, 10);
93+
const durationNs = endTime - startTime;
94+
const durationMs = durationNs / 1_000_000; // Convert ns to ms
95+
96+
// Should be at least 20ms based on our setTimeout(21) in the test
97+
expect(durationMs).toBeGreaterThan(20);
98+
});

0 commit comments

Comments
 (0)