Skip to content

Commit 8d095dd

Browse files
author
Luca Forstner
authored
Merge pull request #8120 from getsentry/prepare-release/7.52.1
2 parents 77aeaf3 + e4d011e commit 8d095dd

File tree

11 files changed

+936
-4
lines changed

11 files changed

+936
-4
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
66

7+
## 7.52.1
8+
9+
- feat(replay): Capture slow clicks (experimental) (#8052)
10+
711

812
## 7.52.0
913

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

0 commit comments

Comments
 (0)