From 1a5cac7f1c309d0f15c5e1e0a369c565a50bb288 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 17 Feb 2023 15:53:00 +0100 Subject: [PATCH 1/4] test(replay): Add test for fetch and XHR performance spans --- .../suites/replay/requests/init.js | 17 +++++ .../suites/replay/requests/subject.js | 16 ++++ .../suites/replay/requests/template.html | 11 +++ .../suites/replay/requests/test.ts | 73 +++++++++++++++++++ .../utils/replayEventTemplates.ts | 22 ++++++ 5 files changed, 139 insertions(+) create mode 100644 packages/integration-tests/suites/replay/requests/init.js create mode 100644 packages/integration-tests/suites/replay/requests/subject.js create mode 100644 packages/integration-tests/suites/replay/requests/template.html create mode 100644 packages/integration-tests/suites/replay/requests/test.ts diff --git a/packages/integration-tests/suites/replay/requests/init.js b/packages/integration-tests/suites/replay/requests/init.js new file mode 100644 index 000000000000..639cf05628e4 --- /dev/null +++ b/packages/integration-tests/suites/replay/requests/init.js @@ -0,0 +1,17 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; +window.Replay = new Sentry.Replay({ + flushMinDelay: 500, + flushMaxDelay: 500, + useCompression: true, +}); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + sampleRate: 0, + replaysSessionSampleRate: 1.0, + replaysOnErrorSampleRate: 0.0, + + integrations: [window.Replay], +}); diff --git a/packages/integration-tests/suites/replay/requests/subject.js b/packages/integration-tests/suites/replay/requests/subject.js new file mode 100644 index 000000000000..7e5b305eabc3 --- /dev/null +++ b/packages/integration-tests/suites/replay/requests/subject.js @@ -0,0 +1,16 @@ +document.getElementById('go-background').addEventListener('click', () => { + Object.defineProperty(document, 'hidden', { value: true, writable: true }); + const ev = document.createEvent('Event'); + ev.initEvent('visibilitychange'); + document.dispatchEvent(ev); +}); + +document.getElementById('fetch').addEventListener('click', () => { + fetch('https://example.com'); +}); + +document.getElementById('xhr').addEventListener('click', () => { + const xhr = new XMLHttpRequest(); + xhr.open('GET', 'https://example.com'); + xhr.send(); +}); diff --git a/packages/integration-tests/suites/replay/requests/template.html b/packages/integration-tests/suites/replay/requests/template.html new file mode 100644 index 000000000000..3b6aadb4fa5e --- /dev/null +++ b/packages/integration-tests/suites/replay/requests/template.html @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/packages/integration-tests/suites/replay/requests/test.ts b/packages/integration-tests/suites/replay/requests/test.ts new file mode 100644 index 000000000000..e60a225bc49e --- /dev/null +++ b/packages/integration-tests/suites/replay/requests/test.ts @@ -0,0 +1,73 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../utils/fixtures'; +import { expectedFetchPerformanceSpan, expectedXHRPerformanceSpan } from '../../../utils/replayEventTemplates'; +import { getReplayRecordingContent, shouldSkipReplayTest, waitForReplayRequest } from '../../../utils/replayHelpers'; + +sentryTest('replay recording should contain fetch request span', async ({ getLocalTestPath, page }) => { + if (shouldSkipReplayTest()) { + sentryTest.skip(); + } + + // we're only interested in segment 1 which should contain the fetch span + const reqPromise1 = waitForReplayRequest(page, 1); + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + await page.route('https://example.com', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: 'hello world', + }); + }); + + const url = await getLocalTestPath({ testDir: __dirname }); + + await page.goto(url); + await page.click('#fetch'); + + const { performanceSpans } = getReplayRecordingContent(await reqPromise1); + + expect(performanceSpans).toContainEqual(expectedFetchPerformanceSpan); +}); + +sentryTest('replay recording should contain XHR request span', async ({ getLocalTestPath, page }) => { + if (shouldSkipReplayTest()) { + sentryTest.skip(); + } + + // we're only interested in segment 1 which should contain the fetch span + const reqPromise1 = waitForReplayRequest(page, 1); + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + await page.route('https://example.com', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: 'hello world', + }); + }); + + const url = await getLocalTestPath({ testDir: __dirname }); + + await page.goto(url); + await page.click('#xhr'); + + const { performanceSpans } = getReplayRecordingContent(await reqPromise1); + + expect(performanceSpans).toContainEqual(expectedXHRPerformanceSpan); +}); diff --git a/packages/integration-tests/utils/replayEventTemplates.ts b/packages/integration-tests/utils/replayEventTemplates.ts index 3a4b4b5f9789..6ab8ebb8e52b 100644 --- a/packages/integration-tests/utils/replayEventTemplates.ts +++ b/packages/integration-tests/utils/replayEventTemplates.ts @@ -130,6 +130,28 @@ export const expectedFPPerformanceSpan = { endTimestamp: expect.any(Number), }; +export const expectedFetchPerformanceSpan = { + op: 'resource.fetch', + description: expect.any(String), + startTimestamp: expect.any(Number), + endTimestamp: expect.any(Number), + data: { + method: expect.any(String), + statusCode: expect.any(Number), + }, +}; + +export const expectedXHRPerformanceSpan = { + op: 'resource.xhr', + description: expect.any(String), + startTimestamp: expect.any(Number), + endTimestamp: expect.any(Number), + data: { + method: expect.any(String), + statusCode: expect.any(Number), + }, +}; + /* Breadcrumbs */ export const expectedClickBreadcrumb = { From e3788ac724396b131b8510489a44998ab335ad9f Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 17 Feb 2023 16:05:03 +0100 Subject: [PATCH 2/4] go to background to avoid flakes --- packages/integration-tests/suites/replay/requests/test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/integration-tests/suites/replay/requests/test.ts b/packages/integration-tests/suites/replay/requests/test.ts index e60a225bc49e..3b5a005130e1 100644 --- a/packages/integration-tests/suites/replay/requests/test.ts +++ b/packages/integration-tests/suites/replay/requests/test.ts @@ -32,6 +32,7 @@ sentryTest('replay recording should contain fetch request span', async ({ getLoc await page.goto(url); await page.click('#fetch'); + await page.click('#go-background'); const { performanceSpans } = getReplayRecordingContent(await reqPromise1); From 67c47d61f2dcd65f17b396f6268ec606ae219b67 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 17 Feb 2023 16:27:54 +0100 Subject: [PATCH 3/4] collect both requests to reduce flakiness --- .../integration-tests/suites/replay/requests/test.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/integration-tests/suites/replay/requests/test.ts b/packages/integration-tests/suites/replay/requests/test.ts index 3b5a005130e1..454ed47deecd 100644 --- a/packages/integration-tests/suites/replay/requests/test.ts +++ b/packages/integration-tests/suites/replay/requests/test.ts @@ -9,7 +9,7 @@ sentryTest('replay recording should contain fetch request span', async ({ getLoc sentryTest.skip(); } - // we're only interested in segment 1 which should contain the fetch span + const reqPromise0 = waitForReplayRequest(page, 0); const reqPromise1 = waitForReplayRequest(page, 1); await page.route('https://dsn.ingest.sentry.io/**/*', route => { @@ -34,7 +34,9 @@ sentryTest('replay recording should contain fetch request span', async ({ getLoc await page.click('#fetch'); await page.click('#go-background'); - const { performanceSpans } = getReplayRecordingContent(await reqPromise1); + const { performanceSpans: spans0 } = getReplayRecordingContent(await reqPromise0); + const { performanceSpans: spans1 } = getReplayRecordingContent(await reqPromise1); + const performanceSpans = [...spans0, ...spans1]; expect(performanceSpans).toContainEqual(expectedFetchPerformanceSpan); }); @@ -44,7 +46,7 @@ sentryTest('replay recording should contain XHR request span', async ({ getLocal sentryTest.skip(); } - // we're only interested in segment 1 which should contain the fetch span + const reqPromise0 = waitForReplayRequest(page, 0); const reqPromise1 = waitForReplayRequest(page, 1); await page.route('https://dsn.ingest.sentry.io/**/*', route => { @@ -68,7 +70,9 @@ sentryTest('replay recording should contain XHR request span', async ({ getLocal await page.goto(url); await page.click('#xhr'); - const { performanceSpans } = getReplayRecordingContent(await reqPromise1); + const { performanceSpans: spans0 } = getReplayRecordingContent(await reqPromise0); + const { performanceSpans: spans1 } = getReplayRecordingContent(await reqPromise1); + const performanceSpans = [...spans0, ...spans1]; expect(performanceSpans).toContainEqual(expectedXHRPerformanceSpan); }); From 014497114ba3b0a0464f4bfa6796bc45d527c2f1 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 17 Feb 2023 16:37:09 +0100 Subject: [PATCH 4/4] got to background in xhr test, too --- packages/integration-tests/suites/replay/requests/test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/integration-tests/suites/replay/requests/test.ts b/packages/integration-tests/suites/replay/requests/test.ts index 454ed47deecd..fa5a1bdcf7c0 100644 --- a/packages/integration-tests/suites/replay/requests/test.ts +++ b/packages/integration-tests/suites/replay/requests/test.ts @@ -69,6 +69,7 @@ sentryTest('replay recording should contain XHR request span', async ({ getLocal await page.goto(url); await page.click('#xhr'); + await page.click('#go-background'); const { performanceSpans: spans0 } = getReplayRecordingContent(await reqPromise0); const { performanceSpans: spans1 } = getReplayRecordingContent(await reqPromise1);