Skip to content

Commit 9a2570b

Browse files
0Caloriesanonrig
andauthored
feat(replay): Send component names in replay breadcrumbs (#9947)
One of the PRs scoped from #9855 Sends component names on Replay UI breadcrumbs so they can be ingested and indexed. This will allow for searching for Replays by component name in the future. --------- Co-authored-by: Yagiz Nizipli <[email protected]>
1 parent f433a55 commit 9a2570b

File tree

4 files changed

+110
-0
lines changed

4 files changed

+110
-0
lines changed
Lines changed: 16 additions & 0 deletions
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+
window.Replay = new Sentry.Replay({
5+
flushMinDelay: 200,
6+
flushMaxDelay: 200,
7+
minReplayDuration: 0,
8+
});
9+
10+
Sentry.init({
11+
dsn: 'https://[email protected]/1337',
12+
sampleRate: 0,
13+
replaysSessionSampleRate: 1.0,
14+
replaysOnErrorSampleRate: 0.0,
15+
integrations: [window.Replay],
16+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
<body>
7+
<button data-sentry-component="MyCoolButton" id="button">😎</button>
8+
<input data-sentry-component="MyCoolInput" id="input" />
9+
</body>
10+
</html>
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../utils/fixtures';
4+
import { getCustomRecordingEvents, shouldSkipReplayTest, waitForReplayRequest } from '../../../utils/replayHelpers';
5+
6+
sentryTest('captures component name attribute when available', async ({ forceFlushReplay, getLocalTestPath, 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 getLocalTestPath({ testDir: __dirname });
22+
23+
await page.goto(url);
24+
await reqPromise0;
25+
await forceFlushReplay();
26+
27+
const reqPromise1 = waitForReplayRequest(page, (event, res) => {
28+
return getCustomRecordingEvents(res).breadcrumbs.some(breadcrumb => breadcrumb.category === 'ui.click');
29+
});
30+
const reqPromise2 = waitForReplayRequest(page, (event, res) => {
31+
return getCustomRecordingEvents(res).breadcrumbs.some(breadcrumb => breadcrumb.category === 'ui.input');
32+
});
33+
34+
await page.locator('#button').click();
35+
36+
await page.locator('#input').focus();
37+
await page.keyboard.press('Control+A');
38+
await page.keyboard.type('Hello', { delay: 10 });
39+
40+
await forceFlushReplay();
41+
const { breadcrumbs } = getCustomRecordingEvents(await reqPromise1);
42+
const { breadcrumbs: breadcrumbs2 } = getCustomRecordingEvents(await reqPromise2);
43+
44+
// Combine the two together
45+
breadcrumbs2.forEach(breadcrumb => {
46+
if (!breadcrumbs.some(b => b.category === breadcrumb.category && b.timestamp === breadcrumb.timestamp)) {
47+
breadcrumbs.push(breadcrumb);
48+
}
49+
});
50+
51+
expect(breadcrumbs).toEqual([
52+
{
53+
timestamp: expect.any(Number),
54+
type: 'default',
55+
category: 'ui.click',
56+
message: 'body > MyCoolButton',
57+
data: {
58+
nodeId: expect.any(Number),
59+
node: {
60+
attributes: { id: 'button', 'data-sentry-component': 'MyCoolButton' },
61+
id: expect.any(Number),
62+
tagName: 'button',
63+
textContent: '**',
64+
},
65+
},
66+
},
67+
{
68+
timestamp: expect.any(Number),
69+
type: 'default',
70+
category: 'ui.input',
71+
message: 'body > MyCoolInput',
72+
data: {
73+
nodeId: expect.any(Number),
74+
node: {
75+
attributes: { id: 'input', 'data-sentry-component': 'MyCoolInput' },
76+
id: expect.any(Number),
77+
tagName: 'input',
78+
textContent: '',
79+
},
80+
},
81+
},
82+
]);
83+
});

packages/replay/src/coreHandlers/util/getAttributesToRecord.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const ATTRIBUTES_TO_RECORD = new Set([
1212
'data-testid',
1313
'disabled',
1414
'aria-disabled',
15+
'data-sentry-component',
1516
]);
1617

1718
/**

0 commit comments

Comments
 (0)