Skip to content

Commit

Permalink
feat(replay): Capture slow clicks (experimental) (#8052)
Browse files Browse the repository at this point in the history
  • Loading branch information
mydea committed May 15, 2023
1 parent 4c42bd5 commit bd48cf2
Show file tree
Hide file tree
Showing 10 changed files with 932 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { expect } from '@playwright/test';

import { sentryTest } from '../../../../utils/fixtures';
import { getCustomRecordingEvents, shouldSkipReplayTest, waitForReplayRequest } from '../../../../utils/replayHelpers';

[
{
id: 'link',
slowClick: true,
},
{
id: 'linkExternal',
slowClick: false,
},
{
id: 'linkDownload',
slowClick: false,
},
{
id: 'inputButton',
slowClick: true,
},
{
id: 'inputSubmit',
slowClick: true,
},
{
id: 'inputText',
slowClick: false,
},
].forEach(({ id, slowClick }) => {
if (slowClick) {
sentryTest(`slow click is captured for ${id}`, async ({ getLocalTestUrl, page }) => {
if (shouldSkipReplayTest()) {
sentryTest.skip();
}

const reqPromise0 = waitForReplayRequest(page, 0);

await page.route('https://dsn.ingest.sentry.io/**/*', route => {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ id: 'test-id' }),
});
});

const url = await getLocalTestUrl({ testDir: __dirname });

await page.goto(url);
await reqPromise0;

const reqPromise1 = waitForReplayRequest(page, (event, res) => {
const { breadcrumbs } = getCustomRecordingEvents(res);

return breadcrumbs.some(breadcrumb => breadcrumb.category === 'ui.slowClickDetected');
});

await page.click(`#${id}`);

const { breadcrumbs } = getCustomRecordingEvents(await reqPromise1);

const slowClickBreadcrumbs = breadcrumbs.filter(breadcrumb => breadcrumb.category === 'ui.slowClickDetected');

expect(slowClickBreadcrumbs).toEqual([
{
category: 'ui.slowClickDetected',
data: {
endReason: 'timeout',
node: {
attributes: expect.objectContaining({
id,
}),
id: expect.any(Number),
tagName: expect.any(String),
textContent: expect.any(String),
},
nodeId: expect.any(Number),
timeAfterClickMs: expect.any(Number),
url: expect.any(String),
},
message: expect.any(String),
timestamp: expect.any(Number),
},
]);
});
} else {
sentryTest(`slow click is not captured for ${id}`, async ({ getLocalTestUrl, page }) => {
if (shouldSkipReplayTest()) {
sentryTest.skip();
}

const reqPromise0 = waitForReplayRequest(page, 0);

await page.route('https://dsn.ingest.sentry.io/**/*', route => {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ id: 'test-id' }),
});
});

const url = await getLocalTestUrl({ testDir: __dirname });

await page.goto(url);
await reqPromise0;

const reqPromise1 = waitForReplayRequest(page, (event, res) => {
const { breadcrumbs } = getCustomRecordingEvents(res);

return breadcrumbs.some(breadcrumb => breadcrumb.category === 'ui.click');
});

await page.click(`#${id}`);

const { breadcrumbs } = getCustomRecordingEvents(await reqPromise1);

expect(breadcrumbs).toEqual([
{
category: 'ui.click',
data: {
node: {
attributes: expect.objectContaining({
id,
}),
id: expect.any(Number),
tagName: expect.any(String),
textContent: expect.any(String),
},
nodeId: expect.any(Number),
},
message: expect.any(String),
timestamp: expect.any(Number),
type: 'default',
},
]);
});
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { expect } from '@playwright/test';

import { sentryTest } from '../../../../utils/fixtures';
import { getCustomRecordingEvents, shouldSkipReplayTest, waitForReplayRequest } from '../../../../utils/replayHelpers';

sentryTest('click is ignored on ignoreSelectors', async ({ getLocalTestUrl, page }) => {
if (shouldSkipReplayTest()) {
sentryTest.skip();
}

const reqPromise0 = waitForReplayRequest(page, 0);

await page.route('https://dsn.ingest.sentry.io/**/*', route => {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ id: 'test-id' }),
});
});

const url = await getLocalTestUrl({ testDir: __dirname });

await page.goto(url);
await reqPromise0;

const reqPromise1 = waitForReplayRequest(page, (event, res) => {
const { breadcrumbs } = getCustomRecordingEvents(res);

return breadcrumbs.some(breadcrumb => breadcrumb.category === 'ui.click');
});

await page.click('#mutationIgnoreButton');

const { breadcrumbs } = getCustomRecordingEvents(await reqPromise1);

expect(breadcrumbs).toEqual([
{
category: 'ui.click',
data: {
node: {
attributes: {
class: 'ignore-class',
id: 'mutationIgnoreButton',
},
id: expect.any(Number),
tagName: 'button',
textContent: '******* ****** ****',
},
nodeId: expect.any(Number),
},
message: 'body > button#mutationIgnoreButton.ignore-class',
timestamp: expect.any(Number),
type: 'default',
},
]);
});
24 changes: 24 additions & 0 deletions packages/browser-integration-tests/suites/replay/slowClick/init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;
window.Replay = new Sentry.Replay({
flushMinDelay: 500,
flushMaxDelay: 500,
_experiments: {
slowClicks: {
threshold: 300,
scrollThreshold: 300,
timeout: 2000,
ignoreSelectors: ['.ignore-class', '[ignore-attribute]'],
},
},
});

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
sampleRate: 0,
replaysSessionSampleRate: 1.0,
replaysOnErrorSampleRate: 0.0,

integrations: [window.Replay],
});

0 comments on commit bd48cf2

Please sign in to comment.