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..fa5a1bdcf7c0 --- /dev/null +++ b/packages/integration-tests/suites/replay/requests/test.ts @@ -0,0 +1,79 @@ +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(); + } + + const reqPromise0 = waitForReplayRequest(page, 0); + 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'); + await page.click('#go-background'); + + const { performanceSpans: spans0 } = getReplayRecordingContent(await reqPromise0); + const { performanceSpans: spans1 } = getReplayRecordingContent(await reqPromise1); + const performanceSpans = [...spans0, ...spans1]; + + expect(performanceSpans).toContainEqual(expectedFetchPerformanceSpan); +}); + +sentryTest('replay recording should contain XHR request span', async ({ getLocalTestPath, page }) => { + if (shouldSkipReplayTest()) { + sentryTest.skip(); + } + + const reqPromise0 = waitForReplayRequest(page, 0); + 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'); + await page.click('#go-background'); + + const { performanceSpans: spans0 } = getReplayRecordingContent(await reqPromise0); + const { performanceSpans: spans1 } = getReplayRecordingContent(await reqPromise1); + const performanceSpans = [...spans0, ...spans1]; + + 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 = {