Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

meta(changelog): Update changelog for 7.56.0 #8356

Merged
merged 9 commits into from
Jun 19, 2023
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@

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

## 7.56.0

- feat(replay): Rework slow click & multi click detection (#8322)
- feat(replay): Stop replay when event buffer exceeds max. size (#8315)
- feat(replay): Consider `window.open` for slow clicks (#8308)
- fix(core): Temporarily store debug IDs in stack frame and only put them into `debug_meta` before sending (#8347)
- fix(remix): Extract deferred responses correctly in root loaders. (#8305)
- fix(vue): Don't call `next` in Vue router 4 instrumentation (#8351)

## 7.55.2

- fix(replay): Stop exporting `EventType` from `@sentry-internal/rrweb` (#8334)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,10 @@ import { getCustomRecordingEvents, shouldSkipReplayTest, waitForReplayRequest }
expect(slowClickBreadcrumbs).toEqual([
{
category: 'ui.slowClickDetected',
type: 'default',
data: {
endReason: 'timeout',
clickCount: 1,
node: {
attributes: expect.objectContaining({
id,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { expect } from '@playwright/test';

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

sentryTest('captures multi click when not detecting slow click', 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.multiClick');
});

await page.click('#mutationButtonImmediately', { clickCount: 4 });

const { breadcrumbs } = getCustomRecordingEvents(await reqPromise1);

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

expect(slowClickBreadcrumbs).toEqual([
{
category: 'ui.multiClick',
type: 'default',
data: {
clickCount: 4,
metric: true,
node: {
attributes: {
id: 'mutationButtonImmediately',
},
id: expect.any(Number),
tagName: 'button',
textContent: '******* ******** ***********',
},
nodeId: expect.any(Number),
url: 'http://sentry-test.io/index.html',
},
message: 'body > button#mutationButtonImmediately',
timestamp: expect.any(Number),
},
]);
});
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ sentryTest('mutation after threshold results in slow click', async ({ getLocalTe
return breadcrumbs.some(breadcrumb => breadcrumb.category === 'ui.slowClickDetected');
});

// Trigger this twice, sometimes this was flaky otherwise...
await page.click('#mutationButton');
await page.click('#mutationButton');

const { breadcrumbs } = getCustomRecordingEvents(await reqPromise1);
Expand All @@ -40,8 +38,71 @@ sentryTest('mutation after threshold results in slow click', async ({ getLocalTe
expect(slowClickBreadcrumbs).toEqual([
{
category: 'ui.slowClickDetected',
type: 'default',
data: {
endReason: 'mutation',
clickCount: 1,
node: {
attributes: {
id: 'mutationButton',
},
id: expect.any(Number),
tagName: 'button',
textContent: '******* ********',
},
nodeId: expect.any(Number),
timeAfterClickMs: expect.any(Number),
url: 'http://sentry-test.io/index.html',
},
message: 'body > button#mutationButton',
timestamp: expect.any(Number),
},
]);

expect(slowClickBreadcrumbs[0]?.data?.timeAfterClickMs).toBeGreaterThan(3000);
expect(slowClickBreadcrumbs[0]?.data?.timeAfterClickMs).toBeLessThan(3100);
});

sentryTest('multiple clicks are counted', 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');
});

void page.click('#mutationButton', { clickCount: 4 });

const { breadcrumbs } = getCustomRecordingEvents(await reqPromise1);

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

expect(slowClickBreadcrumbs).toEqual([
{
category: 'ui.slowClickDetected',
type: 'default',
data: {
endReason: 'mutation',
clickCount: 4,
node: {
attributes: {
id: 'mutationButton',
Expand All @@ -58,6 +119,7 @@ sentryTest('mutation after threshold results in slow click', async ({ getLocalTe
timestamp: expect.any(Number),
},
]);
expect(multiClickBreadcrumbs.length).toEqual(0);

expect(slowClickBreadcrumbs[0]?.data?.timeAfterClickMs).toBeGreaterThan(3000);
expect(slowClickBreadcrumbs[0]?.data?.timeAfterClickMs).toBeLessThan(3100);
Expand Down Expand Up @@ -165,3 +227,55 @@ sentryTest('inline click handler does not trigger slow click', async ({ getLocal
},
]);
});

sentryTest('mouseDown events are considered', async ({ browserName, getLocalTestUrl, page }) => {
// This test seems to only be flakey on firefox
if (shouldSkipReplayTest() || ['firefox'].includes(browserName)) {
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('#mouseDownButton');

const { breadcrumbs } = getCustomRecordingEvents(await reqPromise1);

expect(breadcrumbs).toEqual([
{
category: 'ui.click',
data: {
node: {
attributes: {
id: 'mouseDownButton',
},
id: expect.any(Number),
tagName: 'button',
textContent: '******* ******** ** ***** ****',
},
nodeId: expect.any(Number),
},
message: 'body > button#mouseDownButton',
timestamp: expect.any(Number),
type: 'default',
},
]);
});
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,10 @@ sentryTest('late scroll triggers slow click', async ({ getLocalTestUrl, page })
expect(slowClickBreadcrumbs).toEqual([
{
category: 'ui.slowClickDetected',
type: 'default',
data: {
endReason: 'timeout',
clickCount: 1,
node: {
attributes: {
id: 'scrollLateButton',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
<button id="scrollButton">Trigger scroll</button>
<button id="scrollLateButton">Trigger scroll late</button>
<button id="mutationIgnoreButton" class="ignore-class">Trigger scroll late</button>
<button id="mouseDownButton">Trigger mutation on mouse down</button>
<button id="windowOpenButton">Window open</button>

<a href="#" id="link">Link</a>
<a href="#" target="_blank" id="linkExternal">Link external</a>
Expand Down Expand Up @@ -69,6 +71,12 @@ <h1 id="h2">Bottom</h1>
console.log('DONE');
}, 3001);
});
document.getElementById('mouseDownButton').addEventListener('mousedown', () => {
document.getElementById('out').innerHTML += 'mutationButton clicked<br>';
});
document.getElementById('windowOpenButton').addEventListener('click', () => {
window.open('https://example.com/', '_blank');
});

// Do nothing on these elements
document
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ sentryTest('mutation after timeout results in slow click', async ({ getLocalTest
expect(slowClickBreadcrumbs).toEqual([
{
category: 'ui.slowClickDetected',
type: 'default',
data: {
endReason: 'timeout',
clickCount: 1,
node: {
attributes: {
id: 'mutationButtonLate',
Expand Down Expand Up @@ -93,8 +95,10 @@ sentryTest('console.log results in slow click', async ({ getLocalTestUrl, page }
expect(slowClickBreadcrumbs).toEqual([
{
category: 'ui.slowClickDetected',
type: 'default',
data: {
endReason: 'timeout',
clickCount: 1,
node: {
attributes: {
id: 'consoleLogButton',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { expect } from '@playwright/test';

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

sentryTest('window.open() is considered for slow click', async ({ getLocalTestUrl, page, browser }) => {
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');
});

// Ensure window.open() still works as expected
const context = browser.contexts()[0];
const waitForNewPage = context.waitForEvent('page');

await page.click('#windowOpenButton');

const { breadcrumbs } = getCustomRecordingEvents(await reqPromise1);

// Filter out potential blur breadcrumb, as otherwise this can be flaky
const filteredBreadcrumb = breadcrumbs.filter(breadcrumb => breadcrumb.category !== 'ui.blur');

expect(filteredBreadcrumb).toEqual([
{
category: 'ui.click',
data: {
node: {
attributes: {
id: 'windowOpenButton',
},
id: expect.any(Number),
tagName: 'button',
textContent: '****** ****',
},
nodeId: expect.any(Number),
},
message: 'body > button#windowOpenButton',
timestamp: expect.any(Number),
type: 'default',
},
]);

await waitForNewPage;

const pages = context.pages();

expect(pages.length).toBe(2);
expect(pages[1].url()).toBe('https://example.com/');
});