Skip to content

Commit c7a12b5

Browse files
committed
feat(replay): Add ReplayCanvas integration
Adding this integration in addition to `Replay` will set up canvas recording.
1 parent 91a6b4e commit c7a12b5

File tree

16 files changed

+317
-4
lines changed

16 files changed

+317
-4
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
<body>
7+
<button onclick="console.log('Test log')">Click me</button>
8+
</body>
9+
</html>
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: 200,
6+
flushMaxDelay: 200,
7+
minReplayDuration: 0,
8+
});
9+
10+
Sentry.init({
11+
dsn: 'https://[email protected]/1337',
12+
sampleRate: 0,
13+
replaysSessionSampleRate: 1.0,
14+
replaysOnErrorSampleRate: 0.0,
15+
debug: true,
16+
17+
integrations: [new Sentry.ReplayCanvas(), window.Replay],
18+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
import { getReplaySnapshot, shouldSkipReplayTest } from '../../../../utils/replayHelpers';
5+
6+
sentryTest('sets up canvas when adding ReplayCanvas integration first', async ({ getLocalTestUrl, page }) => {
7+
if (shouldSkipReplayTest()) {
8+
sentryTest.skip();
9+
}
10+
11+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
12+
return route.fulfill({
13+
status: 200,
14+
contentType: 'application/json',
15+
body: JSON.stringify({ id: 'test-id' }),
16+
});
17+
});
18+
19+
const url = await getLocalTestUrl({ testDir: __dirname });
20+
21+
await page.goto(url);
22+
23+
const replay = await getReplaySnapshot(page);
24+
const canvasOptions = replay._options._experiments?.canvas;
25+
expect(canvasOptions.fps).toBe(4);
26+
expect(canvasOptions.quality).toBe(0.6);
27+
expect(replay._hasCanvas).toBe(true);
28+
});
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: 200,
6+
flushMaxDelay: 200,
7+
minReplayDuration: 0,
8+
});
9+
10+
Sentry.init({
11+
dsn: 'https://[email protected]/1337',
12+
sampleRate: 0,
13+
replaysSessionSampleRate: 1.0,
14+
replaysOnErrorSampleRate: 0.0,
15+
debug: true,
16+
17+
integrations: [window.Replay, new Sentry.ReplayCanvas()],
18+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
import { getReplaySnapshot, shouldSkipReplayTest } from '../../../../utils/replayHelpers';
5+
6+
sentryTest('sets up canvas when adding ReplayCanvas integration after Replay', async ({ getLocalTestUrl, page }) => {
7+
if (shouldSkipReplayTest()) {
8+
sentryTest.skip();
9+
}
10+
11+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
12+
return route.fulfill({
13+
status: 200,
14+
contentType: 'application/json',
15+
body: JSON.stringify({ id: 'test-id' }),
16+
});
17+
});
18+
19+
const url = await getLocalTestUrl({ testDir: __dirname });
20+
21+
await page.goto(url);
22+
23+
const replay = await getReplaySnapshot(page);
24+
const canvasOptions = replay._options._experiments?.canvas;
25+
expect(canvasOptions.fps).toBe(4);
26+
expect(canvasOptions.quality).toBe(0.6);
27+
expect(replay._hasCanvas).toBe(true);
28+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
minReplayDuration: 0,
8+
_experiments: {
9+
captureExceptions: true,
10+
},
11+
});
12+
13+
Sentry.init({
14+
dsn: 'https://[email protected]/1337',
15+
sampleRate: 0,
16+
replaysSessionSampleRate: 1.0,
17+
replaysOnErrorSampleRate: 0.0,
18+
debug: true,
19+
20+
integrations: [new Sentry.ReplayCanvas({ fps: 10, quality: 0.1 }), window.Replay],
21+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
import { getReplaySnapshot, shouldSkipReplayTest } from '../../../../utils/replayHelpers';
5+
6+
sentryTest('sets up canvas with custom options for ReplayCanvas integration', async ({ getLocalTestUrl, page }) => {
7+
if (shouldSkipReplayTest()) {
8+
sentryTest.skip();
9+
}
10+
11+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
12+
return route.fulfill({
13+
status: 200,
14+
contentType: 'application/json',
15+
body: JSON.stringify({ id: 'test-id' }),
16+
});
17+
});
18+
19+
const url = await getLocalTestUrl({ testDir: __dirname });
20+
21+
await page.goto(url);
22+
23+
const replay = await getReplaySnapshot(page);
24+
expect(replay._options._experiments).toEqual({
25+
// other options here are kept
26+
captureExceptions: true,
27+
canvas: {
28+
fps: 10,
29+
quality: 0.1,
30+
// manager is not serialized, as it is a function
31+
},
32+
});
33+
expect(replay._hasCanvas).toBe(true);
34+
});
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: 200,
6+
flushMaxDelay: 200,
7+
minReplayDuration: 0,
8+
});
9+
10+
Sentry.init({
11+
dsn: 'https://[email protected]/1337',
12+
sampleRate: 0,
13+
replaysSessionSampleRate: 1.0,
14+
replaysOnErrorSampleRate: 0.0,
15+
debug: true,
16+
17+
integrations: [window.Replay],
18+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
import { getReplaySnapshot, shouldSkipReplayTest } from '../../../../utils/replayHelpers';
5+
6+
sentryTest('does not setup up canvas without ReplayCanvas integration', async ({ getLocalTestUrl, page }) => {
7+
if (shouldSkipReplayTest()) {
8+
sentryTest.skip();
9+
}
10+
11+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
12+
return route.fulfill({
13+
status: 200,
14+
contentType: 'application/json',
15+
body: JSON.stringify({ id: 'test-id' }),
16+
});
17+
});
18+
19+
const url = await getLocalTestUrl({ testDir: __dirname });
20+
21+
await page.goto(url);
22+
23+
const replay = await getReplaySnapshot(page);
24+
const canvasOptions = replay._options._experiments?.canvas;
25+
expect(canvasOptions).toBe(undefined);
26+
expect(replay._hasCanvas).toBe(false);
27+
});

packages/browser-integration-tests/utils/replayHelpers.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
/* eslint-disable max-lines */
12
import type { fullSnapshotEvent, incrementalSnapshotEvent } from '@sentry-internal/rrweb';
23
import { EventType } from '@sentry-internal/rrweb';
34
import type { ReplayEventWithTime } from '@sentry/browser';
45
import type {
56
InternalEventContext,
67
RecordingEvent,
78
ReplayContainer,
9+
ReplayPluginOptions,
810
Session,
911
} from '@sentry/replay/build/npm/types/types';
1012
import type { Breadcrumb, Event, ReplayEvent, ReplayRecordingMode } from '@sentry/types';
@@ -176,6 +178,8 @@ export function getReplaySnapshot(page: Page): Promise<{
176178
_isPaused: boolean;
177179
_isEnabled: boolean;
178180
_context: InternalEventContext;
181+
_options: ReplayPluginOptions;
182+
_hasCanvas: boolean;
179183
session: Session | undefined;
180184
recordingMode: ReplayRecordingMode;
181185
}> {
@@ -187,6 +191,9 @@ export function getReplaySnapshot(page: Page): Promise<{
187191
_isPaused: replay.isPaused(),
188192
_isEnabled: replay.isEnabled(),
189193
_context: replay.getContext(),
194+
_options: replay.getOptions(),
195+
// We cannot pass the function through as this is serialized
196+
_hasCanvas: typeof replay.getOptions()._experiments.canvas?.manager === 'function',
190197
session: replay.session,
191198
recordingMode: replay.recordingMode,
192199
};

0 commit comments

Comments
 (0)