Skip to content

Commit 00f76d6

Browse files
committed
test(replay): Add tests for error-mode and error linking
1 parent e25c067 commit 00f76d6

File tree

9 files changed

+417
-0
lines changed

9 files changed

+417
-0
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
window.Replay = new Sentry.Replay({
5+
flushMinDelay: 500,
6+
flushMaxDelay: 500,
7+
});
8+
9+
Sentry.init({
10+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
11+
sampleRate: 1,
12+
replaysSessionSampleRate: 0.0,
13+
replaysOnErrorSampleRate: 1.0,
14+
beforeSend(_) {
15+
return null;
16+
},
17+
integrations: [window.Replay],
18+
});
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
import {
5+
expectedClickBreadcrumb,
6+
expectedConsoleBreadcrumb,
7+
getExpectedReplayEvent,
8+
} from '../../../../utils/replayEventTemplates';
9+
import {
10+
getReplayEvent,
11+
getReplayRecordingContent,
12+
shouldSkipReplayTest,
13+
waitForReplayRequest,
14+
} from '../../../../utils/replayHelpers';
15+
16+
sentryTest(
17+
'[error-mode] should start recording if an error occurred although the error was dropped',
18+
async ({ getLocalTestPath, page }) => {
19+
if (shouldSkipReplayTest()) {
20+
sentryTest.skip();
21+
}
22+
23+
let callsToSentry = 0;
24+
const reqPromise0 = waitForReplayRequest(page, 0);
25+
const reqPromise1 = waitForReplayRequest(page, 1);
26+
const reqPromise2 = waitForReplayRequest(page, 2);
27+
28+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
29+
callsToSentry++;
30+
return route.fulfill({
31+
status: 200,
32+
contentType: 'application/json',
33+
body: JSON.stringify({ id: 'test-id' }),
34+
});
35+
});
36+
37+
const url = await getLocalTestPath({ testDir: __dirname });
38+
39+
await page.goto(url);
40+
await page.click('#go-background');
41+
expect(callsToSentry).toEqual(0);
42+
43+
await page.click('#error');
44+
const req0 = await reqPromise0;
45+
46+
await page.click('#go-background');
47+
const req1 = await reqPromise1;
48+
expect(callsToSentry).toEqual(2); // 2 replay events
49+
50+
await page.click('#log');
51+
await page.click('#go-background');
52+
const req2 = await reqPromise2;
53+
54+
const event0 = getReplayEvent(req0);
55+
const content0 = getReplayRecordingContent(req0);
56+
57+
const event1 = getReplayEvent(req1);
58+
const content1 = getReplayRecordingContent(req1);
59+
60+
const event2 = getReplayEvent(req2);
61+
const content2 = getReplayRecordingContent(req2);
62+
63+
expect(event0).toEqual(
64+
getExpectedReplayEvent({
65+
contexts: { replay: { error_sample_rate: 1, session_sample_rate: 0 } },
66+
// This is by design. A dropped error shouldn't be in this list.
67+
error_ids: [],
68+
replay_type: 'error',
69+
}),
70+
);
71+
72+
// The first event should have both, full and incremental snapshots,
73+
// as we recorded and kept all events in the buffer
74+
expect(content0.fullSnapshots).toHaveLength(1);
75+
expect(content0.incrementalSnapshots).toHaveLength(10);
76+
// We want to make sure that the event that triggered the error was recorded.
77+
expect(content0.breadcrumbs).toEqual(
78+
expect.arrayContaining([
79+
{
80+
...expectedClickBreadcrumb,
81+
message: 'body > button#error',
82+
},
83+
]),
84+
);
85+
86+
expect(event1).toEqual(
87+
getExpectedReplayEvent({
88+
contexts: { replay: { error_sample_rate: 1, session_sample_rate: 0 } },
89+
// @ts-ignore this is fine
90+
replay_type: 'error', // although we're in session mode, we still send 'error' as replay_type
91+
replay_start_timestamp: undefined,
92+
segment_id: 1,
93+
urls: [],
94+
}),
95+
);
96+
97+
// Also the second snapshot should have a full snapshot, as we switched from error to session
98+
// mode which triggers another checkout
99+
expect(content1.fullSnapshots).toHaveLength(1);
100+
expect(content1.incrementalSnapshots).toHaveLength(0);
101+
102+
// The next event should just be a normal replay event as we're now in session mode and
103+
// we continue recording everything
104+
expect(event2).toEqual(
105+
getExpectedReplayEvent({
106+
contexts: { replay: { error_sample_rate: 1, session_sample_rate: 0 } },
107+
// @ts-ignore this is fine
108+
replay_type: 'error',
109+
replay_start_timestamp: undefined,
110+
segment_id: 2,
111+
urls: [],
112+
}),
113+
);
114+
115+
expect(content2.breadcrumbs).toEqual(
116+
expect.arrayContaining([
117+
{ ...expectedClickBreadcrumb, message: 'body > button#log' },
118+
{ ...expectedConsoleBreadcrumb, level: 'log', message: 'Some message' },
119+
]),
120+
);
121+
},
122+
);
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
import { envelopeRequestParser } from '../../../../utils/helpers';
5+
import {
6+
expectedClickBreadcrumb,
7+
expectedConsoleBreadcrumb,
8+
getExpectedReplayEvent,
9+
} from '../../../../utils/replayEventTemplates';
10+
import {
11+
getReplayEvent,
12+
getReplayRecordingContent,
13+
shouldSkipReplayTest,
14+
waitForReplayRequest,
15+
} from '../../../../utils/replayHelpers';
16+
17+
sentryTest(
18+
'[error-mode] should start recording and switch to session mode once an error is thrown',
19+
async ({ getLocalTestPath, page }) => {
20+
if (shouldSkipReplayTest()) {
21+
sentryTest.skip();
22+
}
23+
24+
let callsToSentry = 0;
25+
let errorEventId: string | undefined;
26+
const reqPromise0 = waitForReplayRequest(page, 0);
27+
const reqPromise1 = waitForReplayRequest(page, 1);
28+
const reqPromise2 = waitForReplayRequest(page, 2);
29+
30+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
31+
const event = envelopeRequestParser(route.request());
32+
// error event have no type field
33+
if (event && !event.type && event.event_id) {
34+
errorEventId = event.event_id;
35+
}
36+
callsToSentry++;
37+
38+
return route.fulfill({
39+
status: 200,
40+
contentType: 'application/json',
41+
body: JSON.stringify({ id: 'test-id' }),
42+
});
43+
});
44+
45+
const url = await getLocalTestPath({ testDir: __dirname });
46+
47+
await page.goto(url);
48+
await page.click('#go-background');
49+
expect(callsToSentry).toEqual(0);
50+
51+
await page.click('#error');
52+
const req0 = await reqPromise0;
53+
54+
await page.click('#go-background');
55+
const req1 = await reqPromise1;
56+
57+
expect(callsToSentry).toEqual(3); // 1 error, 2 replay events
58+
59+
await page.click('#log');
60+
await page.click('#go-background');
61+
const req2 = await reqPromise2;
62+
63+
const event0 = getReplayEvent(req0);
64+
const content0 = getReplayRecordingContent(req0);
65+
66+
const event1 = getReplayEvent(req1);
67+
const content1 = getReplayRecordingContent(req1);
68+
69+
const event2 = getReplayEvent(req2);
70+
const content2 = getReplayRecordingContent(req2);
71+
72+
expect(event0).toEqual(
73+
getExpectedReplayEvent({
74+
contexts: { replay: { error_sample_rate: 1, session_sample_rate: 0 } },
75+
// @ts-ignore this is fine
76+
error_ids: [errorEventId],
77+
replay_type: 'error',
78+
}),
79+
);
80+
81+
// The first event should have both, full and incremental snapshots,
82+
// as we recorded and kept all events in the buffer
83+
expect(content0.fullSnapshots).toHaveLength(1);
84+
expect(content0.incrementalSnapshots).toHaveLength(10);
85+
// We want to make sure that the event that triggered the error was recorded.
86+
expect(content0.breadcrumbs).toEqual(
87+
expect.arrayContaining([
88+
{
89+
...expectedClickBreadcrumb,
90+
message: 'body > button#error',
91+
},
92+
]),
93+
);
94+
95+
expect(event1).toEqual(
96+
getExpectedReplayEvent({
97+
contexts: { replay: { error_sample_rate: 1, session_sample_rate: 0 } },
98+
// @ts-ignore this is fine
99+
replay_type: 'error', // although we're in session mode, we still send 'error' as replay_type
100+
replay_start_timestamp: undefined,
101+
segment_id: 1,
102+
urls: [],
103+
}),
104+
);
105+
106+
// Also the second snapshot should have a full snapshot, as we switched from error to session
107+
// mode which triggers another checkout
108+
expect(content1.fullSnapshots).toHaveLength(1);
109+
expect(content1.incrementalSnapshots).toHaveLength(0);
110+
111+
// The next event should just be a normal replay event as we're now in session mode and
112+
// we continue recording everything
113+
expect(event2).toEqual(
114+
getExpectedReplayEvent({
115+
contexts: { replay: { error_sample_rate: 1, session_sample_rate: 0 } },
116+
// @ts-ignore this is fine
117+
replay_type: 'error',
118+
replay_start_timestamp: undefined,
119+
segment_id: 2,
120+
urls: [],
121+
}),
122+
);
123+
124+
expect(content2.breadcrumbs).toEqual(
125+
expect.arrayContaining([
126+
{ ...expectedClickBreadcrumb, message: 'body > button#log' },
127+
{ ...expectedConsoleBreadcrumb, level: 'log', message: 'Some message' },
128+
]),
129+
);
130+
},
131+
);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
window.Replay = new Sentry.Replay({
5+
flushMinDelay: 500,
6+
flushMaxDelay: 500,
7+
});
8+
9+
Sentry.init({
10+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
11+
sampleRate: 1,
12+
replaysSessionSampleRate: 1.0,
13+
replaysOnErrorSampleRate: 0.0,
14+
15+
integrations: [window.Replay],
16+
});
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
import { envelopeRequestParser } from '../../../../utils/helpers';
5+
import { expectedClickBreadcrumb, getExpectedReplayEvent } from '../../../../utils/replayEventTemplates';
6+
import {
7+
getReplayEvent,
8+
getReplayRecordingContent,
9+
shouldSkipReplayTest,
10+
waitForReplayRequest,
11+
} from '../../../../utils/replayHelpers';
12+
13+
sentryTest(
14+
'[session-mode] replay event should contain an error id of an error that occurred during session recording',
15+
async ({ getLocalTestPath, page }) => {
16+
if (shouldSkipReplayTest()) {
17+
sentryTest.skip();
18+
}
19+
20+
let errorEventId: string = 'invalid_id';
21+
22+
const reqPromise0 = waitForReplayRequest(page, 0);
23+
const reqPromise1 = waitForReplayRequest(page, 1);
24+
25+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
26+
const event = envelopeRequestParser(route.request());
27+
// error event have no type field
28+
if (event && !event.type && event.event_id) {
29+
errorEventId = event.event_id;
30+
}
31+
32+
return route.fulfill({
33+
status: 200,
34+
contentType: 'application/json',
35+
body: JSON.stringify({ id: 'test-id' }),
36+
});
37+
});
38+
39+
const url = await getLocalTestPath({ testDir: __dirname });
40+
41+
await page.goto(url);
42+
await page.click('#go-background');
43+
const req0 = await reqPromise0;
44+
45+
await page.click('#error');
46+
await page.click('#go-background');
47+
const req1 = await reqPromise1;
48+
49+
const event0 = getReplayEvent(req0);
50+
const content0 = getReplayRecordingContent(req0);
51+
52+
const event1 = getReplayEvent(req1);
53+
const content1 = getReplayRecordingContent(req1);
54+
55+
expect(event0).toEqual(getExpectedReplayEvent());
56+
57+
// The first event should have both, full and incremental snapshots,
58+
// as we recorded and kept all events in the buffer
59+
expect(content0.fullSnapshots).toHaveLength(1);
60+
expect(content0.incrementalSnapshots).toHaveLength(0);
61+
62+
expect(event1).toEqual(
63+
getExpectedReplayEvent({
64+
replay_start_timestamp: undefined,
65+
segment_id: 1,
66+
// @ts-ignore this is fine
67+
error_ids: [errorEventId],
68+
urls: [],
69+
}),
70+
);
71+
72+
// Also the second snapshot should have a full snapshot, as we switched from error to session
73+
// mode which triggers another checkout
74+
expect(content1.fullSnapshots).toHaveLength(0);
75+
expect(content1.breadcrumbs).toEqual(expect.arrayContaining([expectedClickBreadcrumb]));
76+
},
77+
);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
window.Replay = new Sentry.Replay({
5+
flushMinDelay: 500,
6+
flushMaxDelay: 500,
7+
});
8+
9+
Sentry.init({
10+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
11+
sampleRate: 1,
12+
replaysSessionSampleRate: 0.0,
13+
replaysOnErrorSampleRate: 1.0,
14+
15+
integrations: [window.Replay],
16+
});

0 commit comments

Comments
 (0)