Skip to content

Commit 80153ba

Browse files
committed
add tests
1 parent 5dc453f commit 80153ba

File tree

5 files changed

+205
-6
lines changed

5 files changed

+205
-6
lines changed
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+
window.Replay = new Sentry.Replay({
5+
flushMinDelay: 200,
6+
flushMaxDelay: 200,
7+
minReplayDuration: 0,
8+
slowClickTimeout: 3500,
9+
});
10+
11+
Sentry.init({
12+
dsn: 'https://[email protected]/1337',
13+
sampleRate: 1,
14+
replaysSessionSampleRate: 0.0,
15+
replaysOnErrorSampleRate: 1.0,
16+
17+
integrations: [window.Replay],
18+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
<body>
7+
<button id="buttonError">Trigger error</button>
8+
<button id="buttonErrorMutation">Trigger error</button>
9+
10+
<script>
11+
document.getElementById('buttonError').addEventListener('click', () => {
12+
throw new Error('test error happened');
13+
});
14+
15+
document.getElementById('buttonErrorMutation').addEventListener('click', () => {
16+
document.getElementById('buttonErrorMutation').innerText = 'Test error happened!';
17+
18+
throw new Error('test error happened');
19+
});
20+
</script>
21+
</body>
22+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
import {
5+
getCustomRecordingEvents,
6+
getReplayEventFromRequest,
7+
shouldSkipReplayTest,
8+
waitForReplayRequest,
9+
} from '../../../../utils/replayHelpers';
10+
11+
sentryTest('slow click that triggers error is captured', async ({ getLocalTestUrl, page }) => {
12+
if (shouldSkipReplayTest()) {
13+
sentryTest.skip();
14+
}
15+
16+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
17+
return route.fulfill({
18+
status: 200,
19+
contentType: 'application/json',
20+
body: JSON.stringify({ id: 'test-id' }),
21+
});
22+
});
23+
24+
const url = await getLocalTestUrl({ testDir: __dirname });
25+
26+
await page.goto(url);
27+
28+
const [req0] = await Promise.all([
29+
waitForReplayRequest(page, (_event, res) => {
30+
const { breadcrumbs } = getCustomRecordingEvents(res);
31+
32+
return breadcrumbs.some(breadcrumb => breadcrumb.category === 'ui.slowClickDetected');
33+
}),
34+
page.click('#buttonError'),
35+
]);
36+
37+
const { breadcrumbs } = getCustomRecordingEvents(req0);
38+
39+
const slowClickBreadcrumbs = breadcrumbs.filter(breadcrumb => breadcrumb.category === 'ui.slowClickDetected');
40+
41+
expect(slowClickBreadcrumbs).toEqual([
42+
{
43+
category: 'ui.slowClickDetected',
44+
type: 'default',
45+
data: {
46+
endReason: 'timeout',
47+
clickCount: 1,
48+
node: {
49+
attributes: {
50+
id: 'buttonError',
51+
},
52+
id: expect.any(Number),
53+
tagName: 'button',
54+
textContent: '******* *****',
55+
},
56+
nodeId: expect.any(Number),
57+
timeAfterClickMs: 3500,
58+
url: 'http://sentry-test.io/index.html',
59+
},
60+
message: 'body > button#buttonError',
61+
timestamp: expect.any(Number),
62+
},
63+
]);
64+
});
65+
66+
sentryTest(
67+
'click that triggers error & mutation is not captured',
68+
async ({ getLocalTestUrl, page, forceFlushReplay }) => {
69+
if (shouldSkipReplayTest()) {
70+
sentryTest.skip();
71+
}
72+
73+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
74+
return route.fulfill({
75+
status: 200,
76+
contentType: 'application/json',
77+
body: JSON.stringify({ id: 'test-id' }),
78+
});
79+
});
80+
81+
const url = await getLocalTestUrl({ testDir: __dirname });
82+
83+
await page.goto(url);
84+
85+
let slowClickCount = 0;
86+
87+
page.on('response', res => {
88+
const req = res.request();
89+
90+
const event = getReplayEventFromRequest(req);
91+
92+
if (!event) {
93+
return;
94+
}
95+
96+
const { breadcrumbs } = getCustomRecordingEvents(res);
97+
98+
const slowClicks = breadcrumbs.filter(breadcrumb => breadcrumb.category === 'ui.slowClickDetected');
99+
slowClickCount += slowClicks.length;
100+
});
101+
102+
const [req1] = await Promise.all([
103+
waitForReplayRequest(page, (_event, res) => {
104+
const { breadcrumbs } = getCustomRecordingEvents(res);
105+
106+
return breadcrumbs.some(breadcrumb => breadcrumb.category === 'ui.click');
107+
}),
108+
page.click('#buttonErrorMutation'),
109+
]);
110+
111+
const { breadcrumbs } = getCustomRecordingEvents(req1);
112+
113+
expect(breadcrumbs).toEqual([
114+
{
115+
category: 'ui.click',
116+
data: {
117+
node: {
118+
attributes: {
119+
id: 'buttonErrorMutation',
120+
},
121+
id: expect.any(Number),
122+
tagName: 'button',
123+
textContent: '******* *****',
124+
},
125+
nodeId: expect.any(Number),
126+
},
127+
message: 'body > button#buttonErrorMutation',
128+
timestamp: expect.any(Number),
129+
type: 'default',
130+
},
131+
]);
132+
133+
// Ensure we wait for timeout, to make sure no slow click is created
134+
// Waiting for 3500 + 1s rounding room
135+
await new Promise(resolve => setTimeout(resolve, 4500));
136+
await forceFlushReplay();
137+
138+
expect(slowClickCount).toBe(0);
139+
},
140+
);

packages/browser-integration-tests/suites/replay/slowClick/mutation/test.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,17 @@ sentryTest('immediate mutation does not trigger slow click', async ({ forceFlush
141141
await Promise.all([waitForReplayRequest(page, 0), page.goto(url)]);
142142
await forceFlushReplay();
143143

144+
let slowClickCount = 0;
145+
146+
page.on('response', res => {
147+
const { breadcrumbs } = getCustomRecordingEvents(res);
148+
149+
const slowClicks = breadcrumbs.filter(breadcrumb => breadcrumb.category === 'ui.slowClickDetected');
150+
slowClickCount += slowClicks.length;
151+
});
152+
144153
const [req1] = await Promise.all([
145-
waitForReplayRequest(page, (event, res) => {
154+
waitForReplayRequest(page, (_event, res) => {
146155
const { breadcrumbs } = getCustomRecordingEvents(res);
147156

148157
return breadcrumbs.some(breadcrumb => breadcrumb.category === 'ui.click');
@@ -171,6 +180,13 @@ sentryTest('immediate mutation does not trigger slow click', async ({ forceFlush
171180
type: 'default',
172181
},
173182
]);
183+
184+
// Ensure we wait for timeout, to make sure no slow click is created
185+
// Waiting for 3500 + 1s rounding room
186+
await new Promise(resolve => setTimeout(resolve, 4500));
187+
await forceFlushReplay();
188+
189+
expect(slowClickCount).toBe(0);
174190
});
175191

176192
sentryTest('inline click handler does not trigger slow click', async ({ forceFlushReplay, getLocalTestUrl, page }) => {

packages/replay/src/coreHandlers/handleClick.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type {
1010
ReplaySlowClickFrame,
1111
SlowClickConfig,
1212
} from '../types';
13-
import { ReplayEventTypeFullSnapshot, ReplayEventTypeIncrementalSnapshot } from '../types';
13+
import { ReplayEventTypeIncrementalSnapshot } from '../types';
1414
import { timestampToS } from '../util/timestamp';
1515
import { addBreadcrumbEvent } from './util/addBreadcrumbEvent';
1616
import { getClosestInteractive } from './util/domUtils';
@@ -310,10 +310,13 @@ function nowInSeconds(): number {
310310
/** Update the click detector based on a recording event of rrweb. */
311311
export function updateClickDetectorForRecordingEvent(clickDetector: ReplayClickDetector, event: RecordingEvent): void {
312312
try {
313-
// We interpret a full snapshot as a mutation (this may not be true, but there is no way for us to know)
314-
if (event.type === ReplayEventTypeFullSnapshot) {
315-
clickDetector.registerMutation(event.timestamp);
316-
}
313+
// note: We only consider incremental snapshots here
314+
// This means that any full snapshot is ignored for mutation detection - the reason is that we simply cannot know if a mutation happened here.
315+
// E.g. think that we are buffering, an error happens and we take a full snapshot because we switched to session mode -
316+
// in this scenario, we would not know if a dead click happened because of the error, which is a key dead click scenario.
317+
// Instead, by ignoring full snapshots, we have the risk that we generate a false positive
318+
// (if a mutation _did_ happen but was "swallowed" by the full snapshot)
319+
// But this should be more unlikely as we'd generally capture the incremental snapshot right away
317320

318321
if (!isIncrementalEvent(event)) {
319322
return;

0 commit comments

Comments
 (0)