Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit f729509

Browse files
authored
test(browser): Add test for INP target name after navigation or DOM changes (#18033)
This test displays the current behavior of getting the element target name for INP when the DOM changes after clicking on a navigation link. When the DOM changes after clicking on an element, the element name from before the navigation is not captured: ```js description: '<unknown>', // FIXME: currently unable to get the target name when element is removed from DOM ```
1 parent 993303c commit f729509

File tree

4 files changed

+261
-0
lines changed

4 files changed

+261
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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+
integrations: [
8+
Sentry.browserTracingIntegration({
9+
idleTimeout: 1000,
10+
enableLongTask: false,
11+
enableInp: true,
12+
instrumentPageLoad: false,
13+
instrumentNavigation: false,
14+
}),
15+
],
16+
tracesSampleRate: 1,
17+
});
18+
19+
const client = Sentry.getClient();
20+
21+
// Force page load transaction name to a testable value
22+
Sentry.startBrowserTracingPageLoadSpan(client, {
23+
name: 'test-url',
24+
attributes: {
25+
[Sentry.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
26+
},
27+
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const simulateNavigationKeepDOM = e => {
2+
const startTime = Date.now();
3+
4+
function getElapsed() {
5+
const time = Date.now();
6+
return time - startTime;
7+
}
8+
9+
while (getElapsed() < 100) {
10+
// Block UI for 100ms to simulate some processing work during navigation
11+
}
12+
13+
const contentDiv = document.getElementById('content');
14+
contentDiv.innerHTML = '<h1>Page 1</h1><p>Successfully navigated!</p>';
15+
16+
contentDiv.classList.add('navigated');
17+
};
18+
19+
const simulateNavigationChangeDOM = e => {
20+
const startTime = Date.now();
21+
22+
function getElapsed() {
23+
const time = Date.now();
24+
return time - startTime;
25+
}
26+
27+
while (getElapsed() < 100) {
28+
// Block UI for 100ms to simulate some processing work during navigation
29+
}
30+
31+
const navigationHTML =
32+
' <nav id="navigation">\n' +
33+
' <a href="#page1" data-test-id="nav-link-keepDOM" data-sentry-element="NavigationLink">Go to Page 1</a>\n' +
34+
' <a href="#page2" data-test-id="nav-link-changeDOM" data-sentry-element="NavigationLink">Go to Page 2</a>\n' +
35+
' </nav>';
36+
37+
const body = document.querySelector('body');
38+
body.innerHTML = `${navigationHTML}<div id="content"><h1>Page 2</h1><p>Successfully navigated!</p></div>`;
39+
40+
body.classList.add('navigated');
41+
};
42+
43+
document.querySelector('[data-test-id=nav-link-keepDOM]').addEventListener('click', simulateNavigationKeepDOM);
44+
document.querySelector('[data-test-id=nav-link-changeDOM]').addEventListener('click', simulateNavigationChangeDOM);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
<body>
7+
<nav id="navigation">
8+
<a href="#page1" data-test-id="nav-link-keepDOM" data-sentry-element="NavigationLink">Go to Page 1</a>
9+
<a href="#page2" data-test-id="nav-link-changeDOM" data-sentry-element="NavigationLink">Go to Page 2</a>
10+
</nav>
11+
<div id="content">
12+
<h1>Home Page</h1>
13+
<p>Click the navigation link to simulate a route change</p>
14+
</div>
15+
</body>
16+
</html>
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import { expect } from '@playwright/test';
2+
import type { Event as SentryEvent, SpanEnvelope } from '@sentry/core';
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
import {
5+
getFirstSentryEnvelopeRequest,
6+
getMultipleSentryEnvelopeRequests,
7+
hidePage,
8+
properFullEnvelopeRequestParser,
9+
shouldSkipTracingTest,
10+
} from '../../../../utils/helpers';
11+
12+
const supportedBrowsers = ['chromium'];
13+
14+
sentryTest(
15+
'should capture INP with correct target name when navigation keeps DOM element',
16+
async ({ browserName, getLocalTestUrl, page }) => {
17+
if (shouldSkipTracingTest() || !supportedBrowsers.includes(browserName)) {
18+
sentryTest.skip();
19+
}
20+
21+
const url = await getLocalTestUrl({ testDir: __dirname });
22+
23+
await page.goto(url);
24+
await getFirstSentryEnvelopeRequest<SentryEvent>(page); // wait for page load
25+
26+
const spanEnvelopePromise = getMultipleSentryEnvelopeRequests<SpanEnvelope>(
27+
page,
28+
1,
29+
{ envelopeType: 'span' },
30+
properFullEnvelopeRequestParser,
31+
);
32+
33+
// Simulating route change (keeping <nav> in DOM)
34+
await page.locator('[data-test-id=nav-link-keepDOM]').click();
35+
await page.locator('.navigated').isVisible();
36+
37+
await page.waitForTimeout(500);
38+
39+
// Page hide to trigger INP
40+
await hidePage(page);
41+
42+
// Get the INP span envelope
43+
const spanEnvelope = (await spanEnvelopePromise)[0];
44+
45+
const spanEnvelopeHeaders = spanEnvelope[0];
46+
const spanEnvelopeItem = spanEnvelope[1][0][1];
47+
48+
const traceId = spanEnvelopeHeaders.trace!.trace_id;
49+
expect(traceId).toMatch(/[a-f0-9]{32}/);
50+
51+
expect(spanEnvelopeHeaders).toEqual({
52+
sent_at: expect.any(String),
53+
trace: {
54+
environment: 'production',
55+
public_key: 'public',
56+
sample_rate: '1',
57+
sampled: 'true',
58+
trace_id: traceId,
59+
sample_rand: expect.any(String),
60+
},
61+
});
62+
63+
const inpValue = spanEnvelopeItem.measurements?.inp.value;
64+
expect(inpValue).toBeGreaterThan(0);
65+
66+
expect(spanEnvelopeItem).toEqual({
67+
data: {
68+
'sentry.exclusive_time': inpValue,
69+
'sentry.op': 'ui.interaction.click',
70+
'sentry.origin': 'auto.http.browser.inp',
71+
'sentry.source': 'custom',
72+
transaction: 'test-url',
73+
'user_agent.original': expect.stringContaining('Chrome'),
74+
},
75+
measurements: {
76+
inp: {
77+
unit: 'millisecond',
78+
value: inpValue,
79+
},
80+
},
81+
description: 'body > nav#navigation > NavigationLink',
82+
exclusive_time: inpValue,
83+
op: 'ui.interaction.click',
84+
origin: 'auto.http.browser.inp',
85+
is_segment: true,
86+
segment_id: spanEnvelopeItem.span_id,
87+
span_id: expect.stringMatching(/[a-f0-9]{16}/),
88+
start_timestamp: expect.any(Number),
89+
timestamp: expect.any(Number),
90+
trace_id: traceId,
91+
});
92+
},
93+
);
94+
95+
sentryTest(
96+
'should capture INP with unknown target name when navigation removes element from DOM',
97+
async ({ browserName, getLocalTestUrl, page }) => {
98+
if (shouldSkipTracingTest() || !supportedBrowsers.includes(browserName)) {
99+
sentryTest.skip();
100+
}
101+
102+
const url = await getLocalTestUrl({ testDir: __dirname });
103+
104+
await page.goto(url);
105+
await getFirstSentryEnvelopeRequest<SentryEvent>(page); // wait for page load
106+
107+
const spanEnvelopePromise = getMultipleSentryEnvelopeRequests<SpanEnvelope>(
108+
page,
109+
1,
110+
{ envelopeType: 'span' },
111+
properFullEnvelopeRequestParser,
112+
);
113+
114+
// Simulating route change (also changing <nav> in DOM)
115+
await page.locator('[data-test-id=nav-link-changeDOM]').click();
116+
await page.locator('.navigated').isVisible();
117+
118+
await page.waitForTimeout(500);
119+
120+
// Page hide to trigger INP
121+
await hidePage(page);
122+
123+
// Get the INP span envelope
124+
const spanEnvelope = (await spanEnvelopePromise)[0];
125+
126+
const spanEnvelopeHeaders = spanEnvelope[0];
127+
const spanEnvelopeItem = spanEnvelope[1][0][1];
128+
129+
const traceId = spanEnvelopeHeaders.trace!.trace_id;
130+
expect(traceId).toMatch(/[a-f0-9]{32}/);
131+
132+
expect(spanEnvelopeHeaders).toEqual({
133+
sent_at: expect.any(String),
134+
trace: {
135+
environment: 'production',
136+
public_key: 'public',
137+
sample_rate: '1',
138+
sampled: 'true',
139+
trace_id: traceId,
140+
sample_rand: expect.any(String),
141+
},
142+
});
143+
144+
const inpValue = spanEnvelopeItem.measurements?.inp.value;
145+
expect(inpValue).toBeGreaterThan(0);
146+
147+
expect(spanEnvelopeItem).toEqual({
148+
data: {
149+
'sentry.exclusive_time': inpValue,
150+
'sentry.op': 'ui.interaction.click',
151+
'sentry.origin': 'auto.http.browser.inp',
152+
'sentry.source': 'custom',
153+
transaction: 'test-url',
154+
'user_agent.original': expect.stringContaining('Chrome'),
155+
},
156+
measurements: {
157+
inp: {
158+
unit: 'millisecond',
159+
value: inpValue,
160+
},
161+
},
162+
description: '<unknown>', // FIXME: currently unable to get the target name when element is removed from DOM
163+
exclusive_time: inpValue,
164+
op: 'ui.interaction.click',
165+
origin: 'auto.http.browser.inp',
166+
is_segment: true,
167+
segment_id: spanEnvelopeItem.span_id,
168+
span_id: expect.stringMatching(/[a-f0-9]{16}/),
169+
start_timestamp: expect.any(Number),
170+
timestamp: expect.any(Number),
171+
trace_id: traceId,
172+
});
173+
},
174+
);

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /