Skip to content

Commit 20b46a8

Browse files
authored
feat(replay): Add request_body_size & response_body_size to fetch/xhr (#7407)
1 parent 7aa20d0 commit 20b46a8

File tree

37 files changed

+1209
-135
lines changed

37 files changed

+1209
-135
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const xhr = new XMLHttpRequest();
2+
3+
fetch('http://localhost:7654/foo').then(() => {
4+
Sentry.captureException('test error');
5+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { expect } from '@playwright/test';
2+
import type { Event } from '@sentry/types';
3+
4+
import { sentryTest } from '../../../../../utils/fixtures';
5+
import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers';
6+
7+
sentryTest('captures Breadcrumb for basic GET request', async ({ getLocalTestPath, page }) => {
8+
const url = await getLocalTestPath({ testDir: __dirname });
9+
10+
await page.route('**/foo', route => {
11+
return route.fulfill({
12+
status: 200,
13+
body: JSON.stringify({
14+
userNames: ['John', 'Jane'],
15+
}),
16+
headers: {
17+
'Content-Type': 'application/json',
18+
},
19+
});
20+
});
21+
22+
const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);
23+
24+
expect(eventData.exception?.values).toHaveLength(1);
25+
26+
expect(eventData?.breadcrumbs?.length).toBe(1);
27+
expect(eventData!.breadcrumbs![0]).toEqual({
28+
timestamp: expect.any(Number),
29+
category: 'fetch',
30+
type: 'http',
31+
data: {
32+
method: 'GET',
33+
status_code: 200,
34+
url: 'http://localhost:7654/foo',
35+
},
36+
});
37+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://[email protected]/1337',
7+
defaultIntegrations: false,
8+
integrations: [new Sentry.Integrations.Breadcrumbs()],
9+
sampleRate: 1,
10+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const xhr = new XMLHttpRequest();
2+
3+
fetch('http://localhost:7654/foo', {
4+
method: 'POST',
5+
body: '{"my":"body"}',
6+
headers: {
7+
Accept: 'application/json',
8+
'Content-Type': 'application/json',
9+
Cache: 'no-cache',
10+
},
11+
}).then(() => {
12+
Sentry.captureException('test error');
13+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { expect } from '@playwright/test';
2+
import type { Event } from '@sentry/types';
3+
4+
import { sentryTest } from '../../../../../utils/fixtures';
5+
import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers';
6+
7+
sentryTest('captures Breadcrumb for POST request', async ({ getLocalTestPath, page }) => {
8+
const url = await getLocalTestPath({ testDir: __dirname });
9+
10+
await page.route('**/foo', route => {
11+
return route.fulfill({
12+
status: 200,
13+
body: JSON.stringify({
14+
userNames: ['John', 'Jane'],
15+
}),
16+
headers: {
17+
'Content-Type': 'application/json',
18+
},
19+
});
20+
});
21+
22+
const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);
23+
24+
expect(eventData.exception?.values).toHaveLength(1);
25+
26+
expect(eventData?.breadcrumbs?.length).toBe(1);
27+
expect(eventData!.breadcrumbs![0]).toEqual({
28+
timestamp: expect.any(Number),
29+
category: 'fetch',
30+
type: 'http',
31+
data: {
32+
method: 'POST',
33+
status_code: 200,
34+
url: 'http://localhost:7654/foo',
35+
},
36+
});
37+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const xhr = new XMLHttpRequest();
2+
3+
xhr.open('GET', 'http://localhost:7654/foo');
4+
xhr.send();
5+
6+
xhr.addEventListener('readystatechange', function () {
7+
if (xhr.readyState === 4) {
8+
Sentry.captureException('test error');
9+
}
10+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { expect } from '@playwright/test';
2+
import type { Event } from '@sentry/types';
3+
4+
import { sentryTest } from '../../../../../utils/fixtures';
5+
import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers';
6+
7+
sentryTest('captures Breadcrumb for basic GET request', async ({ getLocalTestPath, page }) => {
8+
const url = await getLocalTestPath({ testDir: __dirname });
9+
10+
await page.route('**/foo', route => {
11+
return route.fulfill({
12+
status: 200,
13+
body: JSON.stringify({
14+
userNames: ['John', 'Jane'],
15+
}),
16+
headers: {
17+
'Content-Type': 'application/json',
18+
'Content-Length': '',
19+
},
20+
});
21+
});
22+
23+
const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);
24+
25+
expect(eventData.exception?.values).toHaveLength(1);
26+
27+
expect(eventData?.breadcrumbs?.length).toBe(1);
28+
expect(eventData!.breadcrumbs![0]).toEqual({
29+
timestamp: expect.any(Number),
30+
category: 'xhr',
31+
type: 'http',
32+
data: {
33+
method: 'GET',
34+
status_code: 200,
35+
url: 'http://localhost:7654/foo',
36+
},
37+
});
38+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://[email protected]/1337',
7+
defaultIntegrations: false,
8+
integrations: [new Sentry.Integrations.Breadcrumbs()],
9+
sampleRate: 1,
10+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const xhr = new XMLHttpRequest();
2+
3+
xhr.open('POST', 'http://localhost:7654/foo');
4+
xhr.setRequestHeader('Accept', 'application/json');
5+
xhr.setRequestHeader('Content-Type', 'application/json');
6+
xhr.send('{"my":"body"}');
7+
8+
xhr.addEventListener('readystatechange', function () {
9+
if (xhr.readyState === 4) {
10+
Sentry.captureException('test error');
11+
}
12+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { expect } from '@playwright/test';
2+
import type { Event } from '@sentry/types';
3+
4+
import { sentryTest } from '../../../../../utils/fixtures';
5+
import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers';
6+
7+
sentryTest('captures Breadcrumb for POST request', async ({ getLocalTestPath, page }) => {
8+
const url = await getLocalTestPath({ testDir: __dirname });
9+
10+
await page.route('**/foo', route => {
11+
return route.fulfill({
12+
status: 200,
13+
body: JSON.stringify({
14+
userNames: ['John', 'Jane'],
15+
}),
16+
headers: {
17+
'Content-Type': 'application/json',
18+
},
19+
});
20+
});
21+
22+
const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);
23+
24+
expect(eventData.exception?.values).toHaveLength(1);
25+
26+
expect(eventData?.breadcrumbs?.length).toBe(1);
27+
expect(eventData!.breadcrumbs![0]).toEqual({
28+
timestamp: expect.any(Number),
29+
category: 'xhr',
30+
type: 'http',
31+
data: {
32+
method: 'POST',
33+
status_code: 200,
34+
url: 'http://localhost:7654/foo',
35+
},
36+
});
37+
});

packages/browser-integration-tests/suites/replay/errors/errorsInSession/test.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212

1313
sentryTest(
1414
'[session-mode] replay event should contain an error id of an error that occurred during session recording',
15-
async ({ getLocalTestPath, page, browserName }) => {
15+
async ({ getLocalTestPath, page, browserName, forceFlushReplay }) => {
1616
// TODO(replay): This is flakey on firefox where clicks are flakey
1717
if (shouldSkipReplayTest() || ['firefox'].includes(browserName)) {
1818
sentryTest.skip();
@@ -43,7 +43,7 @@ sentryTest(
4343
const req0 = await reqPromise0;
4444

4545
await page.click('#error');
46-
await page.click('#go-background');
46+
await forceFlushReplay();
4747
const req1 = await reqPromise1;
4848

4949
const event0 = getReplayEvent(req0);
@@ -86,7 +86,7 @@ sentryTest(
8686

8787
sentryTest(
8888
'[session-mode] replay event should not contain an error id of a dropped error while recording',
89-
async ({ getLocalTestPath, page }) => {
89+
async ({ getLocalTestPath, page, forceFlushReplay }) => {
9090
if (shouldSkipReplayTest()) {
9191
sentryTest.skip();
9292
}
@@ -108,7 +108,7 @@ sentryTest(
108108
await reqPromise0;
109109

110110
await page.click('#drop');
111-
await page.click('#go-background');
111+
await forceFlushReplay();
112112
const req1 = await reqPromise1;
113113

114114
const event1 = getReplayEvent(req1);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../../../utils/fixtures';
4+
import { envelopeRequestParser, waitForErrorRequest } from '../../../../../utils/helpers';
5+
import { shouldSkipReplayTest } from '../../../../../utils/replayHelpers';
6+
7+
sentryTest('parses response_body_size from Content-Length header if available', async ({ getLocalTestPath, page }) => {
8+
if (shouldSkipReplayTest()) {
9+
sentryTest.skip();
10+
}
11+
12+
await page.route('**/foo', route => {
13+
return route.fulfill({
14+
status: 200,
15+
body: JSON.stringify({
16+
userNames: ['John', 'Jane'],
17+
}),
18+
headers: {
19+
'Content-Type': 'application/json',
20+
'Content-Length': '789',
21+
},
22+
});
23+
});
24+
25+
const requestPromise = waitForErrorRequest(page);
26+
const url = await getLocalTestPath({ testDir: __dirname });
27+
await page.goto(url);
28+
29+
await page.evaluate(() => {
30+
/* eslint-disable */
31+
fetch('http://localhost:7654/foo', {
32+
headers: {
33+
Accept: 'application/json',
34+
'Content-Type': 'application/json',
35+
Cache: 'no-cache',
36+
},
37+
}).then(() => {
38+
// @ts-ignore Sentry is a global
39+
Sentry.captureException('test error');
40+
});
41+
/* eslint-enable */
42+
});
43+
44+
const request = await requestPromise;
45+
const eventData = envelopeRequestParser(request);
46+
47+
expect(eventData.exception?.values).toHaveLength(1);
48+
49+
expect(eventData?.breadcrumbs?.length).toBe(1);
50+
expect(eventData!.breadcrumbs![0]).toEqual({
51+
timestamp: expect.any(Number),
52+
category: 'fetch',
53+
type: 'http',
54+
data: {
55+
method: 'GET',
56+
response_body_size: 789,
57+
status_code: 200,
58+
url: 'http://localhost:7654/foo',
59+
},
60+
});
61+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
window.Replay = new Sentry.Replay({
5+
flushMinDelay: 200,
6+
flushMaxDelay: 200,
7+
});
8+
9+
Sentry.init({
10+
dsn: 'https://[email protected]/1337',
11+
sampleRate: 1,
12+
// We ensure to sample for errors, so by default nothing is sent
13+
replaysSessionSampleRate: 0.0,
14+
replaysOnErrorSampleRate: 1.0,
15+
16+
integrations: [window.Replay],
17+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../../../utils/fixtures';
4+
import { envelopeRequestParser, waitForErrorRequest } from '../../../../../utils/helpers';
5+
import { shouldSkipReplayTest } from '../../../../../utils/replayHelpers';
6+
7+
sentryTest('does not capture response_body_size without Content-Length header', async ({ getLocalTestPath, page }) => {
8+
if (shouldSkipReplayTest()) {
9+
sentryTest.skip();
10+
}
11+
12+
await page.route('**/foo', route => {
13+
return route.fulfill({
14+
status: 200,
15+
body: JSON.stringify({
16+
userNames: ['John', 'Jane'],
17+
}),
18+
headers: {
19+
'Content-Type': 'application/json',
20+
'Content-Length': '',
21+
},
22+
});
23+
});
24+
25+
const requestPromise = waitForErrorRequest(page);
26+
const url = await getLocalTestPath({ testDir: __dirname });
27+
await page.goto(url);
28+
29+
await page.evaluate(() => {
30+
/* eslint-disable */
31+
fetch('http://localhost:7654/foo', {
32+
headers: {
33+
Accept: 'application/json',
34+
'Content-Type': 'application/json',
35+
Cache: 'no-cache',
36+
},
37+
}).then(() => {
38+
// @ts-ignore Sentry is a global
39+
Sentry.captureException('test error');
40+
});
41+
/* eslint-enable */
42+
});
43+
44+
const request = await requestPromise;
45+
const eventData = envelopeRequestParser(request);
46+
47+
expect(eventData.exception?.values).toHaveLength(1);
48+
49+
expect(eventData?.breadcrumbs?.length).toBe(1);
50+
expect(eventData!.breadcrumbs![0]).toEqual({
51+
timestamp: expect.any(Number),
52+
category: 'fetch',
53+
type: 'http',
54+
data: {
55+
method: 'GET',
56+
status_code: 200,
57+
url: 'http://localhost:7654/foo',
58+
},
59+
});
60+
});

0 commit comments

Comments
 (0)