Skip to content

test(replay): Add basic replay integration tests #6735

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<button onclick="console.log('Test log')">Click me</button>
</body>
</html>
67 changes: 67 additions & 0 deletions packages/integration-tests/suites/replay/captureReplay/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { expect } from '@playwright/test';
import { SDK_VERSION } from '@sentry/browser';
import type { Event } from '@sentry/types';

import { sentryTest } from '../../../utils/fixtures';
import { getFirstSentryEnvelopeRequest } from '../../../utils/helpers';

sentryTest('captureReplay', async ({ getLocalTestPath, page }) => {
// Currently bundle tests are not supported for replay
if (process.env.PW_BUNDLE && process.env.PW_BUNDLE.startsWith('bundle_')) {
sentryTest.skip();
}

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 getLocalTestPath({ testDir: __dirname });
await page.goto(url);

await page.click('button');
await page.waitForTimeout(200);

const replayEvent = await getFirstSentryEnvelopeRequest<Event>(page, url);

expect(replayEvent).toBeDefined();
expect(replayEvent).toEqual({
type: 'replay_event',
timestamp: expect.any(Number),
error_ids: [],
trace_ids: [],
urls: [expect.stringContaining('/dist/index.html')],
replay_id: expect.stringMatching(/\w{32}/),
segment_id: 2,
replay_type: 'session',
event_id: expect.stringMatching(/\w{32}/),
environment: 'production',
sdk: {
integrations: [
'InboundFilters',
'FunctionToString',
'TryCatch',
'Breadcrumbs',
'GlobalHandlers',
'LinkedErrors',
'Dedupe',
'HttpContext',
'Replay',
],
version: SDK_VERSION,
name: 'sentry.javascript.browser',
},
sdkProcessingMetadata: {},
request: {
url: expect.stringContaining('/dist/index.html'),
headers: {
'User-Agent': expect.stringContaining(''),
},
},
platform: 'javascript',
tags: { sessionSampleRate: 1, errorSampleRate: 0 },
});
});
16 changes: 16 additions & 0 deletions packages/integration-tests/suites/replay/init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;
window.Replay = new Sentry.Replay({
flushMinDelay: 200,
initialFlushDelay: 200,
});

Sentry.init({
dsn: 'https://[email protected]/1337',
sampleRate: 0,
replaysSessionSampleRate: 1.0,
replaysOnErrorSampleRate: 0.0,

integrations: [window.Replay],
});
16 changes: 16 additions & 0 deletions packages/integration-tests/suites/replay/sampling/init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;
window.Replay = new Sentry.Replay({
flushMinDelay: 200,
initialFlushDelay: 200,
});

Sentry.init({
dsn: 'https://[email protected]/1337',
sampleRate: 0,
replaysSessionSampleRate: 0.0,
replaysOnErrorSampleRate: 0.0,

integrations: [window.Replay],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<button onclick="console.log('Test log')">Click me</button>
</body>
</html>
34 changes: 34 additions & 0 deletions packages/integration-tests/suites/replay/sampling/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { expect } from '@playwright/test';

import { sentryTest } from '../../../utils/fixtures';
import { getReplaySnapshot } from '../../../utils/helpers';

sentryTest('sampling', async ({ getLocalTestPath, page }) => {
// Currently bundle tests are not supported for replay
if (process.env.PW_BUNDLE && process.env.PW_BUNDLE.startsWith('bundle_')) {
sentryTest.skip();
}

await page.route('https://dsn.ingest.sentry.io/**/*', route => {
// This should never be called!
expect(true).toBe(false);

return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ id: 'test-id' }),
});
});

const url = await getLocalTestPath({ testDir: __dirname });
await page.goto(url);

await page.click('button');
await page.waitForTimeout(200);

const replay = await getReplaySnapshot(page);

expect(replay.session?.sampled).toBe(false);

// Cannot wait on getFirstSentryEnvelopeRequest, as that never resolves
});
31 changes: 24 additions & 7 deletions packages/integration-tests/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Page, Request } from '@playwright/test';
import type { ReplayContainer } from '@sentry/replay/build/npm/types/types';
import type { Event, EventEnvelopeHeaders } from '@sentry/types';

const envelopeUrlRegex = /\.sentry\.io\/api\/\d+\/envelope\//;
Expand All @@ -8,7 +9,13 @@ const envelopeRequestParser = (request: Request | null): Event => {
const envelope = request?.postData() || '';

// Third row of the envelop is the event payload.
return envelope.split('\n').map(line => JSON.parse(line))[2];
return envelope.split('\n').map(line => {
try {
return JSON.parse(line);
} catch (error) {
return line;
}
})[2];
};

export const envelopeHeaderRequestParser = (request: Request | null): EventEnvelopeHeaders => {
Expand Down Expand Up @@ -46,24 +53,34 @@ async function getSentryEvents(page: Page, url?: string): Promise<Array<Event>>
return eventsHandle.jsonValue();
}

/**
* This returns the replay container (assuming it exists).
* Note that due to how this works with playwright, this is a POJO copy of replay.
* This means that we cannot access any methods on it, and also not mutate it in any way.
*/
export async function getReplaySnapshot(page: Page): Promise<ReplayContainer> {
const replayIntegration = await page.evaluate<{ _replay: ReplayContainer }>('window.Replay');
return replayIntegration._replay;
}

/**
* Waits until a number of requests matching urlRgx at the given URL arrive.
* If the timout option is configured, this function will abort waiting, even if it hasn't reveived the configured
* amount of requests, and returns all the events recieved up to that point in time.
*/
async function getMultipleRequests(
async function getMultipleRequests<T>(
page: Page,
count: number,
urlRgx: RegExp,
requestParser: (req: Request) => Event,
requestParser: (req: Request) => T,
options?: {
url?: string;
timeout?: number;
},
): Promise<Event[]> {
const requests: Promise<Event[]> = new Promise((resolve, reject) => {
): Promise<T[]> {
const requests: Promise<T[]> = new Promise((resolve, reject) => {
let reqCount = count;
const requestData: Event[] = [];
const requestData: T[] = [];
let timeoutId: NodeJS.Timeout | undefined = undefined;

function requestHandler(request: Request): void {
Expand Down Expand Up @@ -115,7 +132,7 @@ async function getMultipleSentryEnvelopeRequests<T>(
): Promise<T[]> {
// TODO: This is not currently checking the type of envelope, just casting for now.
// We can update this to include optional type-guarding when we have types for Envelope.
return getMultipleRequests(page, count, envelopeUrlRegex, requestParser, options) as Promise<T[]>;
return getMultipleRequests<T>(page, count, envelopeUrlRegex, requestParser, options) as Promise<T[]>;
}

/**
Expand Down