Skip to content

Commit e8fe668

Browse files
authored
test(replay): Add test for session max age handling (#7362)
1 parent 7ab581b commit e8fe668

File tree

15 files changed

+1047
-13
lines changed

15 files changed

+1047
-13
lines changed

packages/integration-tests/suites/replay/errorResponse/test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,5 @@ sentryTest('should stop recording after receiving an error response', async ({ g
3939

4040
const replay = await getReplaySnapshot(page);
4141

42-
// @ts-ignore private API
4342
expect(replay._isEnabled).toBe(false);
4443
});

packages/integration-tests/suites/replay/sessionInactive/test.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,7 @@ sentryTest('handles an inactive session', async ({ getLocalTestPath, page }) =>
5252

5353
// nothing happened because no activity/inactivity was detected
5454
const replay = await getReplaySnapshot(page);
55-
// @ts-ignore private api
5655
expect(replay._isEnabled).toEqual(true);
57-
// @ts-ignore private api
5856
expect(replay._isPaused).toEqual(false);
5957

6058
// Now we trigger a blur event, which should move the session to paused mode
@@ -63,19 +61,15 @@ sentryTest('handles an inactive session', async ({ getLocalTestPath, page }) =>
6361
});
6462

6563
const replay2 = await getReplaySnapshot(page);
66-
// @ts-ignore private api
6764
expect(replay2._isEnabled).toEqual(true);
68-
// @ts-ignore private api
6965
expect(replay2._isPaused).toEqual(true);
7066

7167
// Trigger an action, should re-start the recording
7268
await page.click('#button2');
7369
const req1 = await reqPromise1;
7470

7571
const replay3 = await getReplaySnapshot(page);
76-
// @ts-ignore private api
7772
expect(replay3._isEnabled).toEqual(true);
78-
// @ts-ignore private api
7973
expect(replay3._isPaused).toEqual(false);
8074

8175
const replayEvent1 = getReplayEvent(req1);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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://[email protected]/1337',
11+
sampleRate: 0,
12+
replaysSessionSampleRate: 1.0,
13+
replaysOnErrorSampleRate: 0.0,
14+
debug: true,
15+
16+
integrations: [window.Replay],
17+
});
18+
19+
window.Replay._replay.timeouts = {
20+
sessionIdle: 300000, // default: 5min
21+
maxSessionLife: 4000, // this is usually 60min, but we want to test this with shorter times
22+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
<body>
7+
<button onclick="console.log('Test log 1')" id="button1">Click me</button>
8+
<button onclick="console.log('Test log 2')" id="button2">Click me</button>
9+
</body>
10+
</html>
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../utils/fixtures';
4+
import { getExpectedReplayEvent } from '../../../utils/replayEventTemplates';
5+
import {
6+
getFullRecordingSnapshots,
7+
getReplayEvent,
8+
getReplaySnapshot,
9+
normalize,
10+
shouldSkipReplayTest,
11+
waitForReplayRequest,
12+
} from '../../../utils/replayHelpers';
13+
14+
// Session should be max. 4s long
15+
const SESSION_MAX_AGE = 4000;
16+
17+
/*
18+
The main difference between this and sessionExpiry test, is that here we wait for the overall time (4s)
19+
in multiple steps (2s, 2s) instead of waiting for the whole time at once (4s).
20+
*/
21+
sentryTest('handles session that exceeds max age', async ({ getLocalTestPath, page }) => {
22+
if (shouldSkipReplayTest()) {
23+
sentryTest.skip();
24+
}
25+
26+
const reqPromise0 = waitForReplayRequest(page, 0);
27+
const reqPromise1 = waitForReplayRequest(page, 1);
28+
29+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
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+
41+
const replay0 = await getReplaySnapshot(page);
42+
// We use the `initialTimestamp` of the replay to do any time based calculations
43+
const startTimestamp = replay0._context.initialTimestamp;
44+
45+
const req0 = await reqPromise0;
46+
47+
const replayEvent0 = getReplayEvent(req0);
48+
expect(replayEvent0).toEqual(getExpectedReplayEvent({}));
49+
50+
const fullSnapshots0 = getFullRecordingSnapshots(req0);
51+
expect(fullSnapshots0.length).toEqual(1);
52+
const stringifiedSnapshot = normalize(fullSnapshots0[0]);
53+
expect(stringifiedSnapshot).toMatchSnapshot('snapshot-0.json');
54+
55+
// Wait again for a new segment 0 (=new session)
56+
const reqPromise2 = waitForReplayRequest(page, 0);
57+
58+
// Wait for an incremental snapshot
59+
// Wait half of the session max age (after initial flush), but account for potentially slow runners
60+
const timePassed1 = Date.now() - startTimestamp;
61+
await new Promise(resolve => setTimeout(resolve, Math.max(SESSION_MAX_AGE / 2 - timePassed1, 0)));
62+
await page.click('#button1');
63+
64+
const req1 = await reqPromise1;
65+
const replayEvent1 = getReplayEvent(req1);
66+
67+
expect(replayEvent1).toEqual(getExpectedReplayEvent({ replay_start_timestamp: undefined, segment_id: 1, urls: [] }));
68+
69+
const replay1 = await getReplaySnapshot(page);
70+
const oldSessionId = replay1.session?.id;
71+
72+
// Wait for session to expire
73+
const timePassed2 = Date.now() - startTimestamp;
74+
await new Promise(resolve => setTimeout(resolve, Math.max(SESSION_MAX_AGE - timePassed2, 0)));
75+
await page.click('#button2');
76+
77+
const req2 = await reqPromise2;
78+
const replay2 = await getReplaySnapshot(page);
79+
80+
expect(replay2.session?.id).not.toEqual(oldSessionId);
81+
82+
const replayEvent2 = getReplayEvent(req2);
83+
expect(replayEvent2).toEqual(getExpectedReplayEvent({}));
84+
85+
const fullSnapshots2 = getFullRecordingSnapshots(req2);
86+
expect(fullSnapshots2.length).toEqual(1);
87+
const stringifiedSnapshot2 = normalize(fullSnapshots2[0]);
88+
expect(stringifiedSnapshot2).toMatchSnapshot('snapshot-2.json');
89+
});
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
{
2+
"type": 2,
3+
"data": {
4+
"node": {
5+
"type": 0,
6+
"childNodes": [
7+
{
8+
"type": 1,
9+
"name": "html",
10+
"publicId": "",
11+
"systemId": "",
12+
"id": 2
13+
},
14+
{
15+
"type": 2,
16+
"tagName": "html",
17+
"attributes": {},
18+
"childNodes": [
19+
{
20+
"type": 2,
21+
"tagName": "head",
22+
"attributes": {},
23+
"childNodes": [
24+
{
25+
"type": 2,
26+
"tagName": "meta",
27+
"attributes": {
28+
"charset": "utf-8"
29+
},
30+
"childNodes": [],
31+
"id": 5
32+
}
33+
],
34+
"id": 4
35+
},
36+
{
37+
"type": 3,
38+
"textContent": "\n ",
39+
"id": 6
40+
},
41+
{
42+
"type": 2,
43+
"tagName": "body",
44+
"attributes": {},
45+
"childNodes": [
46+
{
47+
"type": 3,
48+
"textContent": "\n ",
49+
"id": 8
50+
},
51+
{
52+
"type": 2,
53+
"tagName": "button",
54+
"attributes": {
55+
"onclick": "console.log('Test log 1')",
56+
"id": "button1"
57+
},
58+
"childNodes": [
59+
{
60+
"type": 3,
61+
"textContent": "***** **",
62+
"id": 10
63+
}
64+
],
65+
"id": 9
66+
},
67+
{
68+
"type": 3,
69+
"textContent": "\n ",
70+
"id": 11
71+
},
72+
{
73+
"type": 2,
74+
"tagName": "button",
75+
"attributes": {
76+
"onclick": "console.log('Test log 2')",
77+
"id": "button2"
78+
},
79+
"childNodes": [
80+
{
81+
"type": 3,
82+
"textContent": "***** **",
83+
"id": 13
84+
}
85+
],
86+
"id": 12
87+
},
88+
{
89+
"type": 3,
90+
"textContent": "\n ",
91+
"id": 14
92+
},
93+
{
94+
"type": 3,
95+
"textContent": "\n\n",
96+
"id": 15
97+
}
98+
],
99+
"id": 7
100+
}
101+
],
102+
"id": 3
103+
}
104+
],
105+
"id": 1
106+
},
107+
"initialOffset": {
108+
"left": 0,
109+
"top": 0
110+
}
111+
},
112+
"timestamp": [timestamp]
113+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
{
2+
"type": 2,
3+
"data": {
4+
"node": {
5+
"type": 0,
6+
"childNodes": [
7+
{
8+
"type": 1,
9+
"name": "html",
10+
"publicId": "",
11+
"systemId": "",
12+
"id": 2
13+
},
14+
{
15+
"type": 2,
16+
"tagName": "html",
17+
"attributes": {},
18+
"childNodes": [
19+
{
20+
"type": 2,
21+
"tagName": "head",
22+
"attributes": {},
23+
"childNodes": [
24+
{
25+
"type": 2,
26+
"tagName": "meta",
27+
"attributes": {
28+
"charset": "utf-8"
29+
},
30+
"childNodes": [],
31+
"id": 5
32+
}
33+
],
34+
"id": 4
35+
},
36+
{
37+
"type": 3,
38+
"textContent": "\n ",
39+
"id": 6
40+
},
41+
{
42+
"type": 2,
43+
"tagName": "body",
44+
"attributes": {},
45+
"childNodes": [
46+
{
47+
"type": 3,
48+
"textContent": "\n ",
49+
"id": 8
50+
},
51+
{
52+
"type": 2,
53+
"tagName": "button",
54+
"attributes": {
55+
"onclick": "console.log('Test log 1')",
56+
"id": "button1"
57+
},
58+
"childNodes": [
59+
{
60+
"type": 3,
61+
"textContent": "***** **",
62+
"id": 10
63+
}
64+
],
65+
"id": 9
66+
},
67+
{
68+
"type": 3,
69+
"textContent": "\n ",
70+
"id": 11
71+
},
72+
{
73+
"type": 2,
74+
"tagName": "button",
75+
"attributes": {
76+
"onclick": "console.log('Test log 2')",
77+
"id": "button2"
78+
},
79+
"childNodes": [
80+
{
81+
"type": 3,
82+
"textContent": "***** **",
83+
"id": 13
84+
}
85+
],
86+
"id": 12
87+
},
88+
{
89+
"type": 3,
90+
"textContent": "\n ",
91+
"id": 14
92+
},
93+
{
94+
"type": 3,
95+
"textContent": "\n\n",
96+
"id": 15
97+
}
98+
],
99+
"id": 7
100+
}
101+
],
102+
"id": 3
103+
}
104+
],
105+
"id": 1
106+
},
107+
"initialOffset": {
108+
"left": 0,
109+
"top": 0
110+
}
111+
},
112+
"timestamp": [timestamp]
113+
}

0 commit comments

Comments
 (0)