Skip to content

Commit cb8e92e

Browse files
committed
feat(browser): Detect redirects when emitting navigation spans
1 parent 76b7f3a commit cb8e92e

File tree

19 files changed

+439
-41
lines changed

19 files changed

+439
-41
lines changed

.size-limit.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ module.exports = [
120120
import: createImport('init', 'ErrorBoundary', 'reactRouterV6BrowserTracingIntegration'),
121121
ignore: ['react/jsx-runtime'],
122122
gzip: true,
123-
limit: '40.5 KB',
123+
limit: '41 KB',
124124
},
125125
// Vue SDK (ESM)
126126
{

dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces/negatively-sampled/init.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ window.Sentry = Sentry;
44

55
Sentry.init({
66
dsn: 'https://[email protected]/1337',
7-
integrations: [Sentry.browserTracingIntegration()],
7+
// We want to ignore redirects for this test
8+
integrations: [Sentry.browserTracingIntegration({ detectRedirects: false })],
89
tracesSampler: ctx => {
910
if (ctx.attributes['sentry.origin'] === 'auto.pageload.browser') {
1011
return 0;

dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/navigation-aborting-pageload/init.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ window.Sentry = Sentry;
44

55
Sentry.init({
66
dsn: 'https://[email protected]/1337',
7-
integrations: [Sentry.browserTracingIntegration()],
7+
integrations: [Sentry.browserTracingIntegration({ idleTimeout: 2000 })],
88
tracesSampleRate: 1,
99
});
1010

11-
// Immediately navigate to a new page to abort the pageload
12-
window.history.pushState({}, '', '/sub-page');
11+
// Navigate to a new page to abort the pageload
12+
// We have to wait >300ms to avoid the redirect handling
13+
setTimeout(() => {
14+
window.history.pushState({}, '', '/sub-page');
15+
}, 500);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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.browserTracingIntegration()],
8+
tracesSampleRate: 1,
9+
debug: true,
10+
});
11+
12+
document.getElementById('btn1').addEventListener('click', () => {
13+
// Trigger navigation later than click, so the last click is more than 300ms ago
14+
setTimeout(() => {
15+
window.history.pushState({}, '', '/sub-page');
16+
17+
// then trigger redirect inside of this navigation, which should be detected as a redirect
18+
// because the last click was more than 300ms ago
19+
setTimeout(() => {
20+
window.history.pushState({}, '', '/sub-page-redirect');
21+
}, 100);
22+
}, 400);
23+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
<button id="btn1">Trigger navigation</button>
7+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { expect } from '@playwright/test';
2+
import { sentryTest } from '../../../../../utils/fixtures';
3+
import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers';
4+
5+
sentryTest(
6+
'should create a navigation.redirect span if a click happened more than 300ms before navigation',
7+
async ({ getLocalTestUrl, page }) => {
8+
if (shouldSkipTracingTest()) {
9+
sentryTest.skip();
10+
}
11+
12+
const url = await getLocalTestUrl({ testDir: __dirname });
13+
14+
const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload');
15+
const navigationRequestPromise = waitForTransactionRequest(
16+
page,
17+
event => event.contexts?.trace?.op === 'navigation',
18+
);
19+
20+
await page.goto(url);
21+
22+
await pageloadRequestPromise;
23+
24+
// Now trigger navigation, and then a redirect in the navigation, with
25+
await page.click('#btn1');
26+
27+
const navigationRequest = envelopeRequestParser(await navigationRequestPromise);
28+
29+
expect(navigationRequest.contexts?.trace?.op).toBe('navigation');
30+
expect(navigationRequest.transaction).toEqual('/sub-page');
31+
32+
const spans = navigationRequest.spans || [];
33+
34+
expect(spans).toContainEqual(
35+
expect.objectContaining({
36+
op: 'navigation.redirect',
37+
description: '/sub-page-redirect',
38+
}),
39+
);
40+
},
41+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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.browserTracingIntegration()],
8+
tracesSampleRate: 1,
9+
debug: true,
10+
});
11+
12+
document.getElementById('btn1').addEventListener('click', () => {
13+
// trigger redirect immediately
14+
window.history.pushState({}, '', '/sub-page');
15+
});
16+
17+
// Now trigger click, whic should trigger navigation
18+
document.getElementById('btn1').click();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
<button id="btn1">Trigger navigation</button>
7+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { expect } from '@playwright/test';
2+
import { sentryTest } from '../../../../../utils/fixtures';
3+
import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers';
4+
5+
sentryTest(
6+
'should not create a navigation.redirect span if a click happened before navigation',
7+
async ({ getLocalTestUrl, page }) => {
8+
if (shouldSkipTracingTest()) {
9+
sentryTest.skip();
10+
}
11+
12+
const url = await getLocalTestUrl({ testDir: __dirname });
13+
14+
const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload');
15+
const navigationRequestPromise = waitForTransactionRequest(
16+
page,
17+
event => event.contexts?.trace?.op === 'navigation',
18+
);
19+
20+
await page.goto(url);
21+
22+
const pageloadRequest = envelopeRequestParser(await pageloadRequestPromise);
23+
// Ensure a navigation span is sent, too
24+
await navigationRequestPromise;
25+
26+
const spans = pageloadRequest.spans || [];
27+
28+
expect(spans).not.toContainEqual(
29+
expect.objectContaining({
30+
op: 'navigation.redirect',
31+
}),
32+
);
33+
},
34+
);
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://[email protected]/1337',
7+
integrations: [Sentry.browserTracingIntegration()],
8+
tracesSampleRate: 1,
9+
});
10+
11+
// trigger redirect immediately
12+
window.history.pushState({}, '', '/sub-page');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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/core';
8+
import { sentryTest } from '../../../../../utils/fixtures';
9+
import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers';
10+
11+
sentryTest('should create a pageload transaction with navigation.redirect span', async ({ getLocalTestUrl, page }) => {
12+
if (shouldSkipTracingTest()) {
13+
sentryTest.skip();
14+
}
15+
16+
const url = await getLocalTestUrl({ testDir: __dirname });
17+
18+
const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload');
19+
20+
await page.goto(url);
21+
22+
const pageloadRequest = envelopeRequestParser(await pageloadRequestPromise);
23+
24+
expect(pageloadRequest.contexts?.trace?.op).toBe('pageload');
25+
26+
expect(pageloadRequest.contexts?.trace?.data).toMatchObject({
27+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.browser',
28+
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
29+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
30+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
31+
['sentry.idle_span_finish_reason']: 'idleTimeout',
32+
});
33+
34+
expect(pageloadRequest.request).toEqual({
35+
headers: {
36+
'User-Agent': expect.any(String),
37+
},
38+
url: 'http://sentry-test.io/index.html',
39+
});
40+
41+
const spans = pageloadRequest.spans || [];
42+
43+
expect(spans).toContainEqual(
44+
expect.objectContaining({
45+
op: 'navigation.redirect',
46+
}),
47+
);
48+
49+
const navigationSpan = spans.find(span => span.op === 'navigation.redirect');
50+
expect(navigationSpan?.timestamp).toEqual(navigationSpan?.start_timestamp);
51+
expect(navigationSpan).toEqual({
52+
data: {
53+
'sentry.op': 'navigation.redirect',
54+
'sentry.origin': 'auto.navigation.browser',
55+
'sentry.source': 'url',
56+
},
57+
description: '/sub-page',
58+
op: 'navigation.redirect',
59+
origin: 'auto.navigation.browser',
60+
parent_span_id: pageloadRequest.contexts!.trace!.span_id,
61+
span_id: expect.any(String),
62+
start_timestamp: expect.any(Number),
63+
timestamp: expect.any(Number),
64+
trace_id: expect.any(String),
65+
});
66+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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.browserTracingIntegration()],
8+
tracesSampleRate: 1,
9+
});
10+
11+
// trigger redirect later
12+
setTimeout(() => {
13+
window.history.pushState({}, '', '/sub-page');
14+
}, 400);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { expect } from '@playwright/test';
2+
import { sentryTest } from '../../../../../utils/fixtures';
3+
import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers';
4+
5+
sentryTest(
6+
'should not create a navigation.redirect span if redirect happened more than 300ms after pageload',
7+
async ({ getLocalTestUrl, page }) => {
8+
if (shouldSkipTracingTest()) {
9+
sentryTest.skip();
10+
}
11+
12+
const url = await getLocalTestUrl({ testDir: __dirname });
13+
14+
const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload');
15+
const navigationRequestPromise = waitForTransactionRequest(
16+
page,
17+
event => event.contexts?.trace?.op === 'navigation',
18+
);
19+
20+
await page.goto(url);
21+
22+
const pageloadRequest = envelopeRequestParser(await pageloadRequestPromise);
23+
// Ensure a navigation span is sent, too
24+
await navigationRequestPromise;
25+
26+
const spans = pageloadRequest.spans || [];
27+
28+
expect(spans).not.toContainEqual(
29+
expect.objectContaining({
30+
op: 'navigation.redirect',
31+
}),
32+
);
33+
},
34+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://[email protected]/1337',
7+
integrations: [
8+
Sentry.browserTracingIntegration({
9+
detectRedirects: false,
10+
}),
11+
],
12+
tracesSampleRate: 1,
13+
});
14+
15+
// trigger redirect immediately
16+
window.history.pushState({}, '', '/sub-page');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { expect } from '@playwright/test';
2+
import { sentryTest } from '../../../../../utils/fixtures';
3+
import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../../utils/helpers';
4+
5+
sentryTest(
6+
'should not create a navigation.redirect span if `detectRedirects` is set to false',
7+
async ({ getLocalTestUrl, page }) => {
8+
if (shouldSkipTracingTest()) {
9+
sentryTest.skip();
10+
}
11+
12+
const url = await getLocalTestUrl({ testDir: __dirname });
13+
14+
const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload');
15+
const navigationRequestPromise = waitForTransactionRequest(
16+
page,
17+
event => event.contexts?.trace?.op === 'navigation',
18+
);
19+
20+
await page.goto(url);
21+
22+
const pageloadRequest = envelopeRequestParser(await pageloadRequestPromise);
23+
// Ensure a navigation span is sent, too
24+
await navigationRequestPromise;
25+
26+
const spans = pageloadRequest.spans || [];
27+
28+
expect(spans).not.toContainEqual(
29+
expect.objectContaining({
30+
op: 'navigation.redirect',
31+
}),
32+
);
33+
},
34+
);

0 commit comments

Comments
 (0)