diff --git a/packages/replay/src/coreHandlers/handleClick.ts b/packages/replay/src/coreHandlers/handleClick.ts index cc1d816c5435..c4243c1e7e1b 100644 --- a/packages/replay/src/coreHandlers/handleClick.ts +++ b/packages/replay/src/coreHandlers/handleClick.ts @@ -136,6 +136,14 @@ export class ClickDetector implements ReplayClickDetector { clickCount: 0, node, }; + + // If there was a click in the last 1s on the same element, ignore it - only keep a single reference per second + if ( + this._clicks.some(click => click.node === newClick.node && Math.abs(click.timestamp - newClick.timestamp) < 1) + ) { + return; + } + this._clicks.push(newClick); // If this is the first new click, set a timeout to check for multi clicks diff --git a/packages/replay/test/unit/coreHandlers/handleClick.test.ts b/packages/replay/test/unit/coreHandlers/handleClick.test.ts index f2980e60df69..ae52d2076293 100644 --- a/packages/replay/test/unit/coreHandlers/handleClick.test.ts +++ b/packages/replay/test/unit/coreHandlers/handleClick.test.ts @@ -71,6 +71,97 @@ describe('Unit | coreHandlers | handleClick', () => { expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(1); }); + test('it captures multiple clicks', async () => { + const replay = { + getCurrentRoute: () => 'test-route', + } as ReplayContainer; + + const mockAddBreadcrumbEvent = jest.fn(); + + const detector = new ClickDetector( + replay, + { + threshold: 1_000, + timeout: 3_000, + scrollTimeout: 200, + ignoreSelector: '', + }, + mockAddBreadcrumbEvent, + ); + + const breadcrumb1: Breadcrumb = { + timestamp: BASE_TIMESTAMP / 1000, + data: { + nodeId: 1, + }, + }; + const breadcrumb2: Breadcrumb = { + timestamp: (BASE_TIMESTAMP + 200) / 1000, + data: { + nodeId: 1, + }, + }; + const breadcrumb3: Breadcrumb = { + timestamp: (BASE_TIMESTAMP + 1200) / 1000, + data: { + nodeId: 1, + }, + }; + const node = document.createElement('button'); + detector.handleClick(breadcrumb1, node); + detector.handleClick(breadcrumb2, node); + detector.handleClick(breadcrumb3, node); + + expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); + + jest.advanceTimersByTime(1_000); + + expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); + + jest.advanceTimersByTime(1_000); + + expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); + + jest.advanceTimersByTime(1_000); + + expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(1); + expect(mockAddBreadcrumbEvent).toHaveBeenCalledWith(replay, { + category: 'ui.slowClickDetected', + type: 'default', + data: { + clickCount: 1, + endReason: 'timeout', + nodeId: 1, + route: 'test-route', + timeAfterClickMs: 3000, + url: 'http://localhost/', + }, + message: undefined, + timestamp: BASE_TIMESTAMP / 1000, + }); + + jest.advanceTimersByTime(2_000); + + expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(2); + expect(mockAddBreadcrumbEvent).toHaveBeenLastCalledWith(replay, { + category: 'ui.slowClickDetected', + type: 'default', + data: { + clickCount: 1, + endReason: 'timeout', + nodeId: 1, + route: 'test-route', + timeAfterClickMs: 3000, + url: 'http://localhost/', + }, + message: undefined, + timestamp: (BASE_TIMESTAMP + 1200) / 1000, + }); + + jest.advanceTimersByTime(5_000); + expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(2); + }); + test('it captures clicks on different elements', async () => { const replay = { getCurrentRoute: () => 'test-route',