Skip to content

Commit c123105

Browse files
authored
feat(browser): Add option to explicitly end pageload span via reportPageLoaded() (#17697)
Add new functionality to `browserTracingIntegration`: By setting the new `enableReportPageLoaded` option to `true`, users can take full control* of the pageload span duration. It will stay active until `Sentry.reportPageLoaded()` is called*. This new functionality is opt-in and nothing changes for the default idle span mechanism.
1 parent f3aa997 commit c123105

File tree

15 files changed

+287
-7
lines changed

15 files changed

+287
-7
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
window._testBaseTimestamp = performance.timeOrigin / 1000;
5+
6+
Sentry.init({
7+
dsn: 'https://[email protected]/1337',
8+
integrations: [Sentry.browserTracingIntegration({ enableReportPageLoaded: true })],
9+
tracesSampleRate: 1,
10+
debug: true,
11+
});
12+
13+
setTimeout(() => {
14+
Sentry.reportPageLoaded();
15+
}, 2500);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { expect } from '@playwright/test';
2+
import {
3+
SEMANTIC_ATTRIBUTE_SENTRY_OP,
4+
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
5+
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
6+
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
7+
} from '@sentry/browser';
8+
import { sentryTest } from '../../../../../utils/fixtures';
9+
import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers';
10+
11+
sentryTest(
12+
'waits for Sentry.reportPageLoaded() to be called when `enableReportPageLoaded` is true',
13+
async ({ getLocalTestUrl, page }) => {
14+
if (shouldSkipTracingTest()) {
15+
sentryTest.skip();
16+
}
17+
18+
const pageloadEventPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload');
19+
20+
const url = await getLocalTestUrl({ testDir: __dirname });
21+
22+
await page.goto(url);
23+
24+
const eventData = envelopeRequestParser(await pageloadEventPromise);
25+
26+
const traceContextData = eventData.contexts?.trace?.data;
27+
const spanDurationSeconds = eventData.timestamp! - eventData.start_timestamp!;
28+
29+
expect(traceContextData).toMatchObject({
30+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.browser',
31+
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
32+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
33+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
34+
['sentry.idle_span_finish_reason']: 'reportPageLoaded',
35+
});
36+
37+
// We wait for 2.5 seconds before calling Sentry.reportPageLoaded()
38+
// the margins are to account for timing weirdness in CI to avoid flakes
39+
expect(spanDurationSeconds).toBeGreaterThan(2);
40+
expect(spanDurationSeconds).toBeLessThan(3);
41+
},
42+
);
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+
window._testBaseTimestamp = performance.timeOrigin / 1000;
5+
6+
Sentry.init({
7+
dsn: 'https://[email protected]/1337',
8+
integrations: [Sentry.browserTracingIntegration({ enableReportPageLoaded: true, finalTimeout: 3000 })],
9+
tracesSampleRate: 1,
10+
debug: true,
11+
});
12+
13+
// not calling Sentry.reportPageLoaded() on purpose!
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { expect } from '@playwright/test';
2+
import {
3+
SEMANTIC_ATTRIBUTE_SENTRY_OP,
4+
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
5+
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
6+
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
7+
} from '@sentry/browser';
8+
import { sentryTest } from '../../../../../utils/fixtures';
9+
import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers';
10+
11+
sentryTest(
12+
'final timeout cancels the pageload span even if `enableReportPageLoaded` is true',
13+
async ({ getLocalTestUrl, page }) => {
14+
if (shouldSkipTracingTest()) {
15+
sentryTest.skip();
16+
}
17+
18+
const pageloadEventPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload');
19+
20+
const url = await getLocalTestUrl({ testDir: __dirname });
21+
22+
await page.goto(url);
23+
24+
const eventData = envelopeRequestParser(await pageloadEventPromise);
25+
26+
const traceContextData = eventData.contexts?.trace?.data;
27+
const spanDurationSeconds = eventData.timestamp! - eventData.start_timestamp!;
28+
29+
expect(traceContextData).toMatchObject({
30+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.browser',
31+
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
32+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
33+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
34+
['sentry.idle_span_finish_reason']: 'finalTimeout',
35+
});
36+
37+
// We wait for 3 seconds before calling Sentry.reportPageLoaded()
38+
// the margins are to account for timing weirdness in CI to avoid flakes
39+
expect(spanDurationSeconds).toBeGreaterThan(2.5);
40+
expect(spanDurationSeconds).toBeLessThan(3.5);
41+
},
42+
);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
window._testBaseTimestamp = performance.timeOrigin / 1000;
5+
6+
Sentry.init({
7+
dsn: 'https://[email protected]/1337',
8+
integrations: [Sentry.browserTracingIntegration({ enableReportPageLoaded: true, instrumentNavigation: false })],
9+
tracesSampleRate: 1,
10+
debug: true,
11+
});
12+
13+
setTimeout(() => {
14+
Sentry.startBrowserTracingNavigationSpan(Sentry.getClient(), { name: 'custom_navigation' });
15+
}, 1000);
16+
17+
setTimeout(() => {
18+
Sentry.reportPageLoaded();
19+
}, 2500);
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { expect } from '@playwright/test';
2+
import {
3+
SEMANTIC_ATTRIBUTE_SENTRY_OP,
4+
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
5+
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
6+
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
7+
} from '@sentry/browser';
8+
import { sentryTest } from '../../../../../utils/fixtures';
9+
import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers';
10+
11+
sentryTest(
12+
'starting a navigation span cancels the pageload span even if `enableReportPageLoaded` is true',
13+
async ({ getLocalTestUrl, page }) => {
14+
if (shouldSkipTracingTest()) {
15+
sentryTest.skip();
16+
}
17+
18+
const pageloadEventPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload');
19+
20+
const url = await getLocalTestUrl({ testDir: __dirname });
21+
22+
await page.goto(url);
23+
24+
const eventData = envelopeRequestParser(await pageloadEventPromise);
25+
26+
const traceContextData = eventData.contexts?.trace?.data;
27+
const spanDurationSeconds = eventData.timestamp! - eventData.start_timestamp!;
28+
29+
expect(traceContextData).toMatchObject({
30+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.browser',
31+
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
32+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
33+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
34+
['sentry.idle_span_finish_reason']: 'cancelled',
35+
});
36+
37+
// ending span after 1s but adding a margin of 0.5s to account for timing weirdness in CI to avoid flakes
38+
expect(spanDurationSeconds).toBeLessThan(1.5);
39+
},
40+
);

packages/browser/src/index.bundle.tracing.replay.feedback.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export {
2323
startBrowserTracingPageLoadSpan,
2424
} from './tracing/browserTracingIntegration';
2525

26+
export { reportPageLoaded } from './tracing/reportPageLoaded';
27+
2628
export { getFeedback, sendFeedback } from '@sentry-internal/feedback';
2729

2830
export { feedbackAsyncIntegration as feedbackAsyncIntegration, feedbackAsyncIntegration as feedbackIntegration };

packages/browser/src/index.bundle.tracing.replay.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ export {
2222
startBrowserTracingNavigationSpan,
2323
startBrowserTracingPageLoadSpan,
2424
} from './tracing/browserTracingIntegration';
25+
26+
export { reportPageLoaded } from './tracing/reportPageLoaded';
27+
2528
export { feedbackIntegrationShim as feedbackAsyncIntegration, feedbackIntegrationShim as feedbackIntegration };
2629

2730
export { replayIntegration, getReplay } from '@sentry-internal/replay';

packages/browser/src/index.bundle.tracing.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export {
2323
startBrowserTracingPageLoadSpan,
2424
} from './tracing/browserTracingIntegration';
2525

26+
export { reportPageLoaded } from './tracing/reportPageLoaded';
27+
2628
export {
2729
feedbackIntegrationShim as feedbackAsyncIntegration,
2830
feedbackIntegrationShim as feedbackIntegration,

packages/browser/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export {
3939
startBrowserTracingNavigationSpan,
4040
startBrowserTracingPageLoadSpan,
4141
} from './tracing/browserTracingIntegration';
42+
export { reportPageLoaded } from './tracing/reportPageLoaded';
4243
export type { RequestInstrumentationOptions } from './tracing/request';
4344
export {
4445
registerSpanErrorInstrumentation,

0 commit comments

Comments
 (0)