diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json index 8fb2ce61b0ea..e090e9e788bf 100644 --- a/dev-packages/browser-integration-tests/package.json +++ b/dev-packages/browser-integration-tests/package.json @@ -45,7 +45,7 @@ "dependencies": { "@babel/preset-typescript": "^7.16.7", "@playwright/test": "^1.31.1", - "@sentry-internal/rrweb": "2.8.0", + "@sentry-internal/rrweb": "2.9.0", "@sentry/browser": "7.93.0", "@sentry/tracing": "7.93.0", "axios": "1.6.0", diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/manualSnapshot/init.js b/dev-packages/browser-integration-tests/suites/replay/canvas/manualSnapshot/init.js new file mode 100644 index 000000000000..244f951588c6 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/manualSnapshot/init.js @@ -0,0 +1,18 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; +window.Replay = new Sentry.Replay({ + flushMinDelay: 50, + flushMaxDelay: 50, + minReplayDuration: 0, +}); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + sampleRate: 0, + replaysSessionSampleRate: 1.0, + replaysOnErrorSampleRate: 0.0, + debug: true, + + integrations: [window.Replay, new Sentry.ReplayCanvas({ enableManualSnapshot: true })], +}); diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/manualSnapshot/template.html b/dev-packages/browser-integration-tests/suites/replay/canvas/manualSnapshot/template.html new file mode 100644 index 000000000000..5f23d569fcc2 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/manualSnapshot/template.html @@ -0,0 +1,26 @@ + + +
+ + + + + + + + + + diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/manualSnapshot/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/manualSnapshot/test.ts new file mode 100644 index 000000000000..583ff672e590 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/manualSnapshot/test.ts @@ -0,0 +1,79 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { getReplayRecordingContent, shouldSkipReplayTest, waitForReplayRequest } from '../../../../utils/replayHelpers'; + +sentryTest('can manually snapshot canvas', async ({ getLocalTestUrl, page, browserName }) => { + if (shouldSkipReplayTest() || browserName === 'webkit' || (process.env.PW_BUNDLE || '').startsWith('bundle')) { + sentryTest.skip(); + } + + const reqPromise0 = waitForReplayRequest(page, 0); + const reqPromise1 = waitForReplayRequest(page, 1); + const reqPromise2 = waitForReplayRequest(page, 2); + const reqPromise3 = waitForReplayRequest(page, 3); + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.goto(url); + await reqPromise0; + await Promise.all([page.click('#draw'), reqPromise1]); + + const { incrementalSnapshots } = getReplayRecordingContent(await reqPromise2); + expect(incrementalSnapshots).toEqual([]); + + await page.evaluate(() => { + (window as any).Sentry.getClient().getIntegrationById('ReplayCanvas').snapshot(); + }); + + const { incrementalSnapshots: incrementalSnapshotsManual } = getReplayRecordingContent(await reqPromise3); + expect(incrementalSnapshotsManual).toEqual( + expect.arrayContaining([ + { + data: { + commands: [ + { + args: [0, 0, 150, 150], + property: 'clearRect', + }, + { + args: [ + { + args: [ + { + data: [ + { + base64: expect.any(String), + rr_type: 'ArrayBuffer', + }, + ], + rr_type: 'Blob', + type: 'image/webp', + }, + ], + rr_type: 'ImageBitmap', + }, + 0, + 0, + ], + property: 'drawImage', + }, + ], + id: 9, + source: 9, + type: 0, + }, + timestamp: 0, + type: 3, + }, + ]), + ); +}); diff --git a/packages/replay-canvas/package.json b/packages/replay-canvas/package.json index 81a70f18c20d..0a495945f2d1 100644 --- a/packages/replay-canvas/package.json +++ b/packages/replay-canvas/package.json @@ -53,7 +53,7 @@ "homepage": "https://docs.sentry.io/platforms/javascript/session-replay/", "devDependencies": { "@babel/core": "^7.17.5", - "@sentry-internal/rrweb": "2.8.0" + "@sentry-internal/rrweb": "2.9.0" }, "dependencies": { "@sentry/core": "7.93.0", diff --git a/packages/replay-canvas/src/canvas.ts b/packages/replay-canvas/src/canvas.ts index 90b65e5ccd35..ba3ec85ebcfb 100644 --- a/packages/replay-canvas/src/canvas.ts +++ b/packages/replay-canvas/src/canvas.ts @@ -4,11 +4,13 @@ import type { CanvasManagerInterface, CanvasManagerOptions } from '@sentry/repla import type { Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; interface ReplayCanvasOptions { + enableManualSnapshot?: boolean; quality: 'low' | 'medium' | 'high'; } type GetCanvasManager = (options: CanvasManagerOptions) => CanvasManagerInterface; export interface ReplayCanvasIntegrationOptions { + enableManualSnapshot?: boolean; recordCanvas: true; getCanvasManager: GetCanvasManager; sampling: { @@ -58,21 +60,34 @@ const INTEGRATION_NAME = 'ReplayCanvas'; const replayCanvasIntegration = ((options: Partial