Skip to content

Commit a32d8e5

Browse files
committed
more integration tests
1 parent dae8d5b commit a32d8e5

File tree

10 files changed

+307
-40
lines changed

10 files changed

+307
-40
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
7+
traceLifecycle: 'stream',
8+
integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()],
9+
tracePropagationTargets: ['http://sentry-test-site.example'],
10+
tracesSampleRate: 1,
11+
sendDefaultPii: true,
12+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
document.getElementById('go-background').addEventListener('click', () => {
2+
setTimeout(() => {
3+
Object.defineProperty(document, 'hidden', { value: true, writable: true });
4+
const ev = document.createEvent('Event');
5+
ev.initEvent('visibilitychange');
6+
document.dispatchEvent(ev);
7+
}, 250);
8+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
<body>
7+
<button id="go-background">New Tab</button>
8+
</body>
9+
</html>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { expect } from '@playwright/test';
2+
import { sentryTest } from '../../../utils/fixtures';
3+
import { shouldSkipTracingTest } from '../../../utils/helpers';
4+
import { getSpanOp, waitForV2Spans } from '../../../utils/spanFirstUtils';
5+
6+
sentryTest('ends pageload span when the page goes to background', async ({ getLocalTestUrl, page }) => {
7+
if (shouldSkipTracingTest()) {
8+
sentryTest.skip();
9+
}
10+
const url = await getLocalTestUrl({ testDir: __dirname });
11+
12+
const spanPromise = waitForV2Spans(page, spans => !!spans.find(span => getSpanOp(span) === 'pageload'));
13+
14+
await page.goto(url);
15+
await page.locator('#go-background').click();
16+
17+
const pageloadSpan = (await spanPromise).find(span => getSpanOp(span) === 'pageload');
18+
19+
expect(pageloadSpan?.status).toBe('error'); // a cancelled span previously mapped to status error with message cancelled.
20+
expect(pageloadSpan?.attributes?.['sentry.op']?.value).toBe('pageload');
21+
expect(pageloadSpan?.attributes?.['sentry.cancellation_reason']?.value).toBe('document.hidden');
22+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
7+
traceLifecycle: 'stream',
8+
integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()],
9+
tracePropagationTargets: ['http://sentry-test-site.example'],
10+
tracesSampleRate: 1,
11+
sendDefaultPii: true,
12+
debug: true,
13+
});
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { expect } from '@playwright/test';
2+
import type { Event } from '@sentry/core';
3+
import { sentryTest } from '../../../utils/fixtures';
4+
import {
5+
envelopeRequestParser,
6+
runScriptInSandbox,
7+
shouldSkipTracingTest,
8+
waitForErrorRequest,
9+
} from '../../../utils/helpers';
10+
import { getSpanOp, waitForV2Spans } from '../../../utils/spanFirstUtils';
11+
12+
sentryTest(
13+
'puts the pageload span name onto an error event caught during pageload',
14+
async ({ getLocalTestUrl, page, browserName }) => {
15+
if (browserName === 'webkit') {
16+
// This test fails on Webkit as errors thrown from `runScriptInSandbox` are Script Errors and skipped by Sentry
17+
sentryTest.skip();
18+
}
19+
20+
if (shouldSkipTracingTest()) {
21+
sentryTest.skip();
22+
}
23+
24+
const url = await getLocalTestUrl({ testDir: __dirname });
25+
26+
const errorEventPromise = waitForErrorRequest(page);
27+
const spanPromise = waitForV2Spans(page, spans => !!spans.find(span => getSpanOp(span) === 'pageload'));
28+
29+
await page.goto(url);
30+
31+
await runScriptInSandbox(page, {
32+
content: `
33+
throw new Error('Error during pageload');
34+
`,
35+
});
36+
37+
const errorEvent = envelopeRequestParser<Event>(await errorEventPromise);
38+
const pageloadSpan = (await spanPromise).find(span => getSpanOp(span) === 'pageload');
39+
40+
expect(pageloadSpan?.attributes?.['sentry.op']?.value).toEqual('pageload');
41+
expect(errorEvent.exception?.values?.[0]).toBeDefined();
42+
43+
expect(pageloadSpan?.name).toEqual('/index.html');
44+
45+
expect(pageloadSpan?.status).toBe('error');
46+
expect(pageloadSpan?.attributes?.['sentry.idle_span_finish_reason']?.value).toBe('idleTimeout');
47+
48+
expect(errorEvent.transaction).toEqual(pageloadSpan?.name);
49+
},
50+
);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
7+
traceLifecycle: 'stream',
8+
integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()],
9+
tracePropagationTargets: ['http://sentry-test-site.example'],
10+
tracesSampleRate: 1,
11+
sendDefaultPii: true,
12+
});
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { expect } from '@playwright/test';
2+
import { sentryTest } from '../../../utils/fixtures';
3+
import { shouldSkipTracingTest } from '../../../utils/helpers';
4+
import { getSpanOp, waitForSpanV2Envelope } from '../../../utils/spanFirstUtils';
5+
6+
sentryTest('sends a span v2 envelope for the pageload', async ({ getLocalTestUrl, page }) => {
7+
if (shouldSkipTracingTest()) {
8+
sentryTest.skip();
9+
}
10+
11+
const spanEnvelopePromise = waitForSpanV2Envelope(page);
12+
13+
const url = await getLocalTestUrl({ testDir: __dirname });
14+
15+
await page.goto(url);
16+
17+
const spanEnvelope = await spanEnvelopePromise;
18+
19+
const envelopeHeaders = spanEnvelope[0];
20+
21+
const envelopeItem0 = spanEnvelope[1][0];
22+
const envelopeItemHeader = envelopeItem0[0];
23+
const envelopeItem = envelopeItem0[1];
24+
25+
expect(envelopeHeaders).toEqual({
26+
sent_at: expect.any(String),
27+
trace: {
28+
environment: 'production',
29+
public_key: 'public',
30+
trace_id: expect.stringMatching(/^[\da-f]{32}$/),
31+
sampled: 'true',
32+
sample_rand: expect.any(String),
33+
sample_rate: '1',
34+
},
35+
sdk: {
36+
name: 'sentry.javascript.browser',
37+
packages: [
38+
{
39+
name: 'npm:@sentry/browser',
40+
version: expect.any(String),
41+
},
42+
],
43+
version: expect.any(String),
44+
settings: {
45+
infer_ip: 'auto',
46+
},
47+
},
48+
});
49+
50+
expect(envelopeItemHeader).toEqual({
51+
content_type: 'application/vnd.sentry.items.span.v2+json',
52+
item_count: expect.any(Number),
53+
type: 'span',
54+
});
55+
56+
// test the shape of the item first, then the content
57+
expect(envelopeItem).toEqual({
58+
items: expect.any(Array),
59+
});
60+
61+
expect(envelopeItem.items.length).toBe(envelopeItemHeader.item_count);
62+
63+
const pageloadSpan = envelopeItem.items.find(item => getSpanOp(item) === 'pageload');
64+
65+
expect(pageloadSpan).toBeDefined();
66+
67+
expect(pageloadSpan).toEqual({
68+
attributes: expect.objectContaining({
69+
'performance.activationStart': {
70+
type: 'integer',
71+
value: 0,
72+
},
73+
'performance.timeOrigin': {
74+
type: 'double',
75+
value: expect.any(Number),
76+
},
77+
'sentry.op': {
78+
type: 'string',
79+
value: 'pageload',
80+
},
81+
'sentry.origin': {
82+
type: 'string',
83+
value: 'auto.pageload.browser',
84+
},
85+
'sentry.sample_rate': {
86+
type: 'integer',
87+
value: 1,
88+
},
89+
'sentry.sdk.name': {
90+
type: 'string',
91+
value: 'sentry.javascript.browser',
92+
},
93+
'sentry.sdk.version': {
94+
type: 'string',
95+
value: expect.any(String),
96+
},
97+
'sentry.segment.id': {
98+
type: 'string',
99+
value: pageloadSpan?.span_id, // pageload is always the segment
100+
},
101+
'sentry.segment.name': {
102+
type: 'string',
103+
value: '/index.html',
104+
},
105+
'sentry.source': {
106+
type: 'string',
107+
value: 'url',
108+
},
109+
}),
110+
trace_id: expect.stringMatching(/^[a-f\d]{32}$/),
111+
span_id: expect.stringMatching(/^[a-f\d]{16}$/),
112+
name: '/index.html',
113+
status: 'ok',
114+
is_segment: true,
115+
start_timestamp: expect.any(Number),
116+
end_timestamp: expect.any(Number),
117+
});
118+
});

dev-packages/browser-integration-tests/utils/helpers.ts

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import type {
99
EventEnvelope,
1010
EventEnvelopeHeaders,
1111
SessionContext,
12-
SpanV2Envelope,
1312
TransactionEvent,
1413
} from '@sentry/core';
1514
import { parseEnvelope } from '@sentry/core';
@@ -63,7 +62,7 @@ export const eventAndTraceHeaderRequestParser = (request: Request | null): Event
6362
return getEventAndTraceHeader(envelope);
6463
};
6564

66-
const properFullEnvelopeParser = <T extends Envelope>(request: Request | null): T => {
65+
export const properFullEnvelopeParser = <T extends Envelope>(request: Request | null): T => {
6766
// https://develop.sentry.dev/sdk/envelopes/
6867
const envelope = request?.postData() || '';
6968

@@ -259,44 +258,6 @@ export function waitForTransactionRequest(
259258
});
260259
}
261260

262-
/**
263-
* Wait for a span v2 envelope
264-
*/
265-
export async function waitForSpanV2Envelope(
266-
page: Page,
267-
callback?: (spanEnvelope: SpanV2Envelope) => boolean,
268-
): Promise<SpanV2Envelope> {
269-
const req = await page.waitForRequest(req => {
270-
const postData = req.postData();
271-
if (!postData) {
272-
return false;
273-
}
274-
275-
try {
276-
const spanEnvelope = properFullEnvelopeParser<SpanV2Envelope>(req);
277-
278-
const envelopeItemHeader = spanEnvelope[1][0][0];
279-
280-
if (
281-
envelopeItemHeader?.type !== 'span' ||
282-
envelopeItemHeader?.content_type !== 'application/vnd.sentry.items.span.v2+json'
283-
) {
284-
return false;
285-
}
286-
287-
if (callback) {
288-
return callback(spanEnvelope);
289-
}
290-
291-
return true;
292-
} catch {
293-
return false;
294-
}
295-
});
296-
297-
return properFullEnvelopeParser<SpanV2Envelope>(req);
298-
}
299-
300261
export function waitForClientReportRequest(page: Page, callback?: (report: ClientReport) => boolean): Promise<Request> {
301262
return page.waitForRequest(req => {
302263
const postData = req.postData();
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import type { Page } from '@playwright/test';
2+
import type { SpanV2Envelope, SpanV2JSON } from '@sentry/core';
3+
import { properFullEnvelopeParser } from './helpers';
4+
5+
/**
6+
* Wait for a span v2 envelope
7+
*/
8+
export async function waitForSpanV2Envelope(
9+
page: Page,
10+
callback?: (spanEnvelope: SpanV2Envelope) => boolean,
11+
): Promise<SpanV2Envelope> {
12+
const req = await page.waitForRequest(req => {
13+
const postData = req.postData();
14+
if (!postData) {
15+
return false;
16+
}
17+
18+
try {
19+
const spanEnvelope = properFullEnvelopeParser<SpanV2Envelope>(req);
20+
21+
const envelopeItemHeader = spanEnvelope[1][0][0];
22+
23+
if (
24+
envelopeItemHeader?.type !== 'span' ||
25+
envelopeItemHeader?.content_type !== 'application/vnd.sentry.items.span.v2+json'
26+
) {
27+
return false;
28+
}
29+
30+
if (callback) {
31+
return callback(spanEnvelope);
32+
}
33+
34+
return true;
35+
} catch {
36+
return false;
37+
}
38+
});
39+
40+
return properFullEnvelopeParser<SpanV2Envelope>(req);
41+
}
42+
43+
/**
44+
* Wait for v2 spans sent in one envelope.
45+
* (We might need a more sophisticated helper that waits for N envelopes and buckets by traceId)
46+
* For now, this should do.
47+
* @param page
48+
* @param callback - Callback being called with all spans
49+
*/
50+
export async function waitForV2Spans(page: Page, callback?: (spans: SpanV2JSON[]) => boolean): Promise<SpanV2JSON[]> {
51+
const spanEnvelope = await waitForSpanV2Envelope(page, envelope => {
52+
if (callback) {
53+
return callback(envelope[1][0][1].items);
54+
}
55+
return true;
56+
});
57+
return spanEnvelope[1][0][1].items;
58+
}
59+
60+
export function getSpanOp(span: SpanV2JSON): string | undefined {
61+
return span.attributes?.['sentry.op']?.type === 'string' ? span.attributes?.['sentry.op']?.value : undefined;
62+
}

0 commit comments

Comments
 (0)