Skip to content

Commit 2f41282

Browse files
committed
handle more elements
1 parent 1402cff commit 2f41282

File tree

3 files changed

+165
-1
lines changed

3 files changed

+165
-1
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
import { getCustomRecordingEvents, shouldSkipReplayTest, waitForReplayRequest } from '../../../../utils/replayHelpers';
5+
6+
[
7+
{
8+
id: 'link',
9+
slowClick: true,
10+
},
11+
{
12+
id: 'linkExternal',
13+
slowClick: false,
14+
},
15+
{
16+
id: 'linkDownload',
17+
slowClick: false,
18+
},
19+
{
20+
id: 'inputButton',
21+
slowClick: true,
22+
},
23+
{
24+
id: 'inputSubmit',
25+
slowClick: true,
26+
},
27+
{
28+
id: 'inputText',
29+
slowClick: false,
30+
},
31+
].forEach(({ id, slowClick }) => {
32+
if (slowClick) {
33+
sentryTest(`slow click is captured for ${id}`, async ({ getLocalTestUrl, page }) => {
34+
if (shouldSkipReplayTest()) {
35+
sentryTest.skip();
36+
}
37+
38+
page.on('console', msg => console.log(msg.text()));
39+
40+
const reqPromise0 = waitForReplayRequest(page, 0);
41+
42+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
43+
return route.fulfill({
44+
status: 200,
45+
contentType: 'application/json',
46+
body: JSON.stringify({ id: 'test-id' }),
47+
});
48+
});
49+
50+
const url = await getLocalTestUrl({ testDir: __dirname });
51+
52+
await page.goto(url);
53+
await reqPromise0;
54+
55+
const reqPromise1 = waitForReplayRequest(page, (event, res) => {
56+
const { breadcrumbs } = getCustomRecordingEvents(res);
57+
58+
return breadcrumbs.some(breadcrumb => breadcrumb.category === 'ui.slowClickDetected');
59+
});
60+
61+
await page.click(`#${id}`);
62+
63+
const { breadcrumbs } = getCustomRecordingEvents(await reqPromise1);
64+
65+
const slowClickBreadcrumbs = breadcrumbs.filter(breadcrumb => breadcrumb.category === 'ui.slowClickDetected');
66+
67+
expect(slowClickBreadcrumbs).toEqual([
68+
{
69+
category: 'ui.slowClickDetected',
70+
data: {
71+
endReason: 'timeout',
72+
node: {
73+
attributes: expect.objectContaining({
74+
id,
75+
}),
76+
id: expect.any(Number),
77+
tagName: expect.any(String),
78+
textContent: expect.any(String),
79+
},
80+
nodeId: expect.any(Number),
81+
timeAfterClickMs: expect.any(Number),
82+
url: expect.any(String),
83+
},
84+
message: expect.any(String),
85+
timestamp: expect.any(Number),
86+
},
87+
]);
88+
});
89+
} else {
90+
sentryTest(`slow click is not captured for ${id}`, async ({ getLocalTestUrl, page }) => {
91+
if (shouldSkipReplayTest()) {
92+
sentryTest.skip();
93+
}
94+
95+
const reqPromise0 = waitForReplayRequest(page, 0);
96+
97+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
98+
return route.fulfill({
99+
status: 200,
100+
contentType: 'application/json',
101+
body: JSON.stringify({ id: 'test-id' }),
102+
});
103+
});
104+
105+
const url = await getLocalTestUrl({ testDir: __dirname });
106+
107+
await page.goto(url);
108+
await reqPromise0;
109+
110+
const reqPromise1 = waitForReplayRequest(page, (event, res) => {
111+
const { breadcrumbs } = getCustomRecordingEvents(res);
112+
113+
return breadcrumbs.some(breadcrumb => breadcrumb.category === 'ui.click');
114+
});
115+
116+
await page.click(`#${id}`);
117+
118+
const { breadcrumbs } = getCustomRecordingEvents(await reqPromise1);
119+
120+
expect(breadcrumbs).toEqual([
121+
{
122+
category: 'ui.click',
123+
data: {
124+
node: {
125+
attributes: expect.objectContaining({
126+
id,
127+
}),
128+
id: expect.any(Number),
129+
tagName: expect.any(String),
130+
textContent: expect.any(String),
131+
},
132+
nodeId: expect.any(Number),
133+
},
134+
message: expect.any(String),
135+
timestamp: expect.any(Number),
136+
type: 'default',
137+
},
138+
]);
139+
});
140+
}
141+
});

packages/browser-integration-tests/suites/replay/slowClick/template.html

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919
<button id="scrollLateButton">Trigger scroll late</button>
2020
<button id="mutationIgnoreButton" class="ignore-class">Trigger scroll late</button>
2121

22+
<a href="#" id="link">Link</a>
23+
<a href="#" target="_blank" id="linkExternal">Link external</a>
24+
<a href="" download id="linkDownload">Link download</a>
25+
<input type="button" id="inputButton" value="Input button" />
26+
<input type="submit" id="inputSubmit" value="Input submit" />
27+
<input type="text" id="inputText" />
28+
2229
<h1 id="h1">Heading</h1>
2330

2431
<div id="out" style="min-height: 1000px"></div>
@@ -62,6 +69,15 @@ <h1 id="h2">Bottom</h1>
6269
console.log('DONE');
6370
}, 400);
6471
});
72+
73+
// Do nothing on these elements
74+
document
75+
.querySelectorAll('#link,#linkExternal,#linkDownload,#inputButton,#inputSubmit,#inputText')
76+
.forEach(link => {
77+
link.addEventListener('click', e => {
78+
e.preventDefault();
79+
});
80+
});
6581
</script>
6682
</body>
6783
</html>

packages/replay/src/coreHandlers/handleSlowClick.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,10 @@ function handleSlowClick(
114114
addBreadcrumbEvent(replay, breadcrumb);
115115
}
116116

117+
const SLOW_CLICK_POSSIBLE_TAGS = ['BUTTON', 'A', 'INPUT'];
118+
117119
function ignoreElement(node: HTMLElement, config: SlowClickConfig): boolean {
118-
if (node.tagName !== 'BUTTON' && node.tagName !== 'A') {
120+
if (!SLOW_CLICK_POSSIBLE_TAGS.includes(node.tagName)) {
119121
return true;
120122
}
121123

@@ -129,6 +131,11 @@ function ignoreElement(node: HTMLElement, config: SlowClickConfig): boolean {
129131
return true;
130132
}
131133

134+
// If <input> tag, we only want to consider input[type='submit'] & input[type='button']
135+
if (node.tagName === 'INPUT' && !['submit', 'button'].includes(node.getAttribute('type') || '')) {
136+
return true;
137+
}
138+
132139
if (config.ignoreSelector && node.matches(config.ignoreSelector)) {
133140
return true;
134141
}

0 commit comments

Comments
 (0)