Skip to content

Commit 454f510

Browse files
committed
test(replay): Test expiring session
1 parent ffe504b commit 454f510

File tree

7 files changed

+348
-2
lines changed

7 files changed

+348
-2
lines changed
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: 2000, // this is usually 5min, but we want to test this with shorter times
21+
maxSessionLife: 3600000, // default: 60min
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: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { expect } from '@playwright/test';
2+
import type { ReplayEvent } from '@sentry/types';
3+
4+
import { sentryTest } from '../../../utils/fixtures';
5+
import { envelopeRequestParser } from '../../../utils/helpers';
6+
import { getExpectedReplayEvent } from '../../../utils/replayEventTemplates';
7+
import {
8+
getFullRecordingSnapshots,
9+
getIncrementalRecordingSnapshots,
10+
getReplaySnapshot,
11+
normalize,
12+
shouldSkipReplayTest,
13+
waitForReplayRequest,
14+
} from '../../../utils/replayHelpers';
15+
16+
// Session should expire after 2s - keep in sync with init.js
17+
const SESSION_TIMEOUT = 2000;
18+
19+
sentryTest('handles an expired session', async ({ getLocalTestPath, page }) => {
20+
if (shouldSkipReplayTest()) {
21+
sentryTest.skip();
22+
}
23+
24+
const reqPromise0 = waitForReplayRequest(page, 0);
25+
const reqPromise1 = waitForReplayRequest(page, 1);
26+
27+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
28+
return route.fulfill({
29+
status: 200,
30+
contentType: 'application/json',
31+
body: JSON.stringify({ id: 'test-id' }),
32+
});
33+
});
34+
35+
const url = await getLocalTestPath({ testDir: __dirname });
36+
37+
await page.goto(url);
38+
39+
const replayEvent0 = envelopeRequestParser(await reqPromise0) as ReplayEvent;
40+
expect(replayEvent0).toEqual(getExpectedReplayEvent({}));
41+
42+
const fullSnapshots0 = getFullRecordingSnapshots(await reqPromise0);
43+
expect(fullSnapshots0.length).toEqual(1);
44+
const stringifiedSnapshot = normalize(fullSnapshots0[0]);
45+
expect(stringifiedSnapshot).toMatchSnapshot('snapshot-0.json');
46+
47+
// We wait for another segment 0
48+
const reqPromise2 = waitForReplayRequest(page, 0);
49+
50+
await page.click('#button1');
51+
const replayEvent1 = envelopeRequestParser(await reqPromise1) as ReplayEvent;
52+
expect(replayEvent1).toEqual(getExpectedReplayEvent({ replay_start_timestamp: undefined, segment_id: 1, urls: [] }));
53+
54+
const fullSnapshots1 = getFullRecordingSnapshots(await reqPromise1);
55+
expect(fullSnapshots1.length).toEqual(0);
56+
57+
const incrementalSnapshots1 = getIncrementalRecordingSnapshots(await reqPromise1);
58+
expect(incrementalSnapshots1.length).toEqual(5);
59+
60+
expect(incrementalSnapshots1).toEqual([
61+
{
62+
source: 1,
63+
positions: [
64+
{
65+
id: 9,
66+
timeOffset: expect.any(Number),
67+
x: expect.any(Number),
68+
y: expect.any(Number),
69+
},
70+
],
71+
},
72+
{ source: 2, type: 1, id: 9, x: expect.any(Number), y: expect.any(Number) },
73+
{ source: 2, type: 5, id: 9 },
74+
{ source: 2, type: 0, id: 9, x: expect.any(Number), y: expect.any(Number) },
75+
{ source: 2, type: 2, id: 9, x: expect.any(Number), y: expect.any(Number) },
76+
]);
77+
78+
const replay = await getReplaySnapshot(page);
79+
const oldSessionId = replay.session?.id;
80+
81+
await new Promise(resolve => setTimeout(resolve, SESSION_TIMEOUT));
82+
83+
await page.click('#button2');
84+
85+
const replay2 = await getReplaySnapshot(page);
86+
87+
expect(replay2.session?.id).not.toEqual(oldSessionId);
88+
89+
const replayEvent2 = envelopeRequestParser(await reqPromise2) as ReplayEvent;
90+
expect(replayEvent2).toEqual(getExpectedReplayEvent({}));
91+
92+
const fullSnapshots2 = getFullRecordingSnapshots(await reqPromise2);
93+
expect(fullSnapshots2.length).toEqual(1);
94+
const stringifiedSnapshot2 = normalize(fullSnapshots2[0]);
95+
expect(stringifiedSnapshot2).toMatchSnapshot('snapshot-2.json');
96+
});
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
{
2+
"node": {
3+
"type": 0,
4+
"childNodes": [
5+
{
6+
"type": 1,
7+
"name": "html",
8+
"publicId": "",
9+
"systemId": "",
10+
"id": 2
11+
},
12+
{
13+
"type": 2,
14+
"tagName": "html",
15+
"attributes": {},
16+
"childNodes": [
17+
{
18+
"type": 2,
19+
"tagName": "head",
20+
"attributes": {},
21+
"childNodes": [
22+
{
23+
"type": 2,
24+
"tagName": "meta",
25+
"attributes": {
26+
"charset": "utf-8"
27+
},
28+
"childNodes": [],
29+
"id": 5
30+
}
31+
],
32+
"id": 4
33+
},
34+
{
35+
"type": 3,
36+
"textContent": "\n ",
37+
"id": 6
38+
},
39+
{
40+
"type": 2,
41+
"tagName": "body",
42+
"attributes": {},
43+
"childNodes": [
44+
{
45+
"type": 3,
46+
"textContent": "\n ",
47+
"id": 8
48+
},
49+
{
50+
"type": 2,
51+
"tagName": "button",
52+
"attributes": {
53+
"onclick": "console.log('Test log 1')",
54+
"id": "button1"
55+
},
56+
"childNodes": [
57+
{
58+
"type": 3,
59+
"textContent": "***** **",
60+
"id": 10
61+
}
62+
],
63+
"id": 9
64+
},
65+
{
66+
"type": 3,
67+
"textContent": "\n ",
68+
"id": 11
69+
},
70+
{
71+
"type": 2,
72+
"tagName": "button",
73+
"attributes": {
74+
"onclick": "console.log('Test log 2')",
75+
"id": "button2"
76+
},
77+
"childNodes": [
78+
{
79+
"type": 3,
80+
"textContent": "***** **",
81+
"id": 13
82+
}
83+
],
84+
"id": 12
85+
},
86+
{
87+
"type": 3,
88+
"textContent": "\n ",
89+
"id": 14
90+
},
91+
{
92+
"type": 3,
93+
"textContent": "\n\n",
94+
"id": 15
95+
}
96+
],
97+
"id": 7
98+
}
99+
],
100+
"id": 3
101+
}
102+
],
103+
"id": 1
104+
},
105+
"initialOffset": {
106+
"left": 0,
107+
"top": 0
108+
}
109+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
{
2+
"node": {
3+
"type": 0,
4+
"childNodes": [
5+
{
6+
"type": 1,
7+
"name": "html",
8+
"publicId": "",
9+
"systemId": "",
10+
"id": 2
11+
},
12+
{
13+
"type": 2,
14+
"tagName": "html",
15+
"attributes": {},
16+
"childNodes": [
17+
{
18+
"type": 2,
19+
"tagName": "head",
20+
"attributes": {},
21+
"childNodes": [
22+
{
23+
"type": 2,
24+
"tagName": "meta",
25+
"attributes": {
26+
"charset": "utf-8"
27+
},
28+
"childNodes": [],
29+
"id": 5
30+
}
31+
],
32+
"id": 4
33+
},
34+
{
35+
"type": 3,
36+
"textContent": "\n ",
37+
"id": 6
38+
},
39+
{
40+
"type": 2,
41+
"tagName": "body",
42+
"attributes": {},
43+
"childNodes": [
44+
{
45+
"type": 3,
46+
"textContent": "\n ",
47+
"id": 8
48+
},
49+
{
50+
"type": 2,
51+
"tagName": "button",
52+
"attributes": {
53+
"onclick": "console.log('Test log 1')",
54+
"id": "button1"
55+
},
56+
"childNodes": [
57+
{
58+
"type": 3,
59+
"textContent": "***** **",
60+
"id": 10
61+
}
62+
],
63+
"id": 9
64+
},
65+
{
66+
"type": 3,
67+
"textContent": "\n ",
68+
"id": 11
69+
},
70+
{
71+
"type": 2,
72+
"tagName": "button",
73+
"attributes": {
74+
"onclick": "console.log('Test log 2')",
75+
"id": "button2"
76+
},
77+
"childNodes": [
78+
{
79+
"type": 3,
80+
"textContent": "***** **",
81+
"id": 13
82+
}
83+
],
84+
"id": 12
85+
},
86+
{
87+
"type": 3,
88+
"textContent": "\n ",
89+
"id": 14
90+
},
91+
{
92+
"type": 3,
93+
"textContent": "\n\n",
94+
"id": 15
95+
}
96+
],
97+
"id": 7
98+
}
99+
],
100+
"id": 3
101+
}
102+
],
103+
"id": 1
104+
},
105+
"initialOffset": {
106+
"left": 0,
107+
"top": 0
108+
}
109+
}

packages/integration-tests/utils/replayHelpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export function getFullRecordingSnapshots(replayRequest: Request): RecordingSnap
133133
return events.filter(event => event.type === 2).map(event => event.data as RecordingSnapshot);
134134
}
135135

136-
function getIncrementalRecordingSnapshots(replayRequest: Request): RecordingSnapshot[] {
136+
export function getIncrementalRecordingSnapshots(replayRequest: Request): RecordingSnapshot[] {
137137
const events = getDecompressedRecordingEvents(replayRequest) as RecordingEvent[];
138138
return events.filter(event => event.type === 3).map(event => event.data as RecordingSnapshot);
139139
}

packages/replay/src/replay.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ export class ReplayContainer implements ReplayContainerInterface {
524524
) => {
525525
// If this is false, it means session is expired, create and a new session and wait for checkout
526526
if (!this.checkAndHandleExpiredSession()) {
527-
__DEBUG_BUILD__ && logger.error('[Replay] Received replay event after session expired.');
527+
__DEBUG_BUILD__ && logger.warn('[Replay] Received replay event after session expired.');
528528

529529
return;
530530
}

0 commit comments

Comments
 (0)