Skip to content

test(replay): Streamline replay test imports #6486

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
Dec 12, 2022
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
8 changes: 6 additions & 2 deletions packages/replay/test/mocks/mockSdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { ReplayConfiguration } from '../../src/types';
export interface MockSdkParams {
replayOptions?: ReplayConfiguration;
sentryOptions?: BrowserOptions;
autoStart?: boolean;
}

class MockTransport implements Transport {
Expand Down Expand Up @@ -35,7 +36,7 @@ class MockTransport implements Transport {
}
}

export async function mockSdk({ replayOptions, sentryOptions }: MockSdkParams = {}): Promise<{
export async function mockSdk({ replayOptions, sentryOptions, autoStart = true }: MockSdkParams = {}): Promise<{
replay: ReplayContainer;
integration: ReplayIntegration;
}> {
Expand Down Expand Up @@ -75,7 +76,10 @@ export async function mockSdk({ replayOptions, sentryOptions }: MockSdkParams =

// Instead of `setupOnce`, which is tricky to test, we call this manually here
replayIntegration['_setup']();
replayIntegration.start();

if (autoStart) {
replayIntegration.start();
}

const replay = replayIntegration['_replay']!;

Expand Down
8 changes: 1 addition & 7 deletions packages/replay/test/mocks/resetSdkMock.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { getCurrentHub } from '@sentry/core';

import type { ReplayContainer } from '../../src/replay';
import { BASE_TIMESTAMP, RecordMock } from './../index';
import type { DomHandler, MockTransportSend } from './../types';
import type { DomHandler } from './../types';
import { mockSdk, MockSdkParams } from './mockSdk';

export async function resetSdkMock({ replayOptions, sentryOptions }: MockSdkParams): Promise<{
domHandler: DomHandler;
mockRecord: RecordMock;
mockTransportSend: MockTransportSend;
replay: ReplayContainer;
}> {
let domHandler: DomHandler;
Expand All @@ -33,8 +30,6 @@ export async function resetSdkMock({ replayOptions, sentryOptions }: MockSdkPara
sentryOptions,
});

const mockTransportSend = getCurrentHub()?.getClient()?.getTransport()?.send as MockTransportSend;

// XXX: This is needed to ensure `domHandler` is set
jest.runAllTimers();
await new Promise(process.nextTick);
Expand All @@ -44,7 +39,6 @@ export async function resetSdkMock({ replayOptions, sentryOptions }: MockSdkPara
// @ts-ignore use before assign
domHandler,
mockRecord,
mockTransportSend,
replay,
};
}
3 changes: 0 additions & 3 deletions packages/replay/test/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
import { Transport } from '@sentry/types';

export type MockTransportSend = jest.MockedFunction<Transport['send']>;
export type DomHandler = (args: any) => any;
2 changes: 0 additions & 2 deletions packages/replay/test/unit/coreHandlers/handleFetch.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { handleFetch } from '../../../src/coreHandlers/handleFetch';
import { mockSdk } from './../../index';

jest.unmock('@sentry/browser');

beforeAll(function () {
mockSdk();
});
Expand Down
2 changes: 0 additions & 2 deletions packages/replay/test/unit/createPerformanceEntry.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { createPerformanceEntries } from '../../src/createPerformanceEntry';
import { mockSdk } from './../index';

jest.unmock('@sentry/browser');

beforeAll(function () {
mockSdk();
});
Expand Down
132 changes: 68 additions & 64 deletions packages/replay/test/unit/index-errorSampleRate.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { captureException } from '@sentry/core';
import { captureException, getCurrentHub } from '@sentry/core';

import { REPLAY_SESSION_KEY, VISIBILITY_CHANGE_TIMEOUT, WINDOW } from '../../src/constants';
import { addEvent } from '../../src/util/addEvent';
import { ReplayContainer } from './../../src/replay';
import { PerformanceEntryResource } from './../fixtures/performanceEntry/resource';
import { BASE_TIMESTAMP, RecordMock } from './../index';
import { resetSdkMock } from './../mocks/resetSdkMock';
import { DomHandler, MockTransportSend } from './../types';
import { DomHandler } from './../types';
import { useFakeTimers } from './../utils/use-fake-timers';

useFakeTimers();
Expand All @@ -19,11 +19,11 @@ async function advanceTimers(time: number) {
describe('Replay (errorSampleRate)', () => {
let replay: ReplayContainer;
let mockRecord: RecordMock;
let mockTransportSend: MockTransportSend;

let domHandler: DomHandler;

beforeEach(async () => {
({ mockRecord, mockTransportSend, domHandler, replay } = await resetSdkMock({
({ mockRecord, domHandler, replay } = await resetSdkMock({
replayOptions: {
stickySession: true,
},
Expand Down Expand Up @@ -86,9 +86,6 @@ describe('Replay (errorSampleRate)', () => {
]),
});

mockTransportSend.mockClear();
expect(replay).not.toHaveSentReplay();

jest.runAllTimers();
await new Promise(process.nextTick);
jest.runAllTimers();
Expand Down Expand Up @@ -310,63 +307,6 @@ describe('Replay (errorSampleRate)', () => {
});
});

/**
* This is testing a case that should only happen with error-only sessions.
* Previously we had assumed that loading a session from session storage meant
* that the session was not new. However, this is not the case with error-only
* sampling since we can load a saved session that did not have an error (and
* thus no replay was created).
*/
it('sends a replay after loading the session multiple times', async () => {
// Pretend that a session is already saved before loading replay
WINDOW.sessionStorage.setItem(
REPLAY_SESSION_KEY,
`{"segmentId":0,"id":"fd09adfc4117477abc8de643e5a5798a","sampled":"error","started":${BASE_TIMESTAMP},"lastActivity":${BASE_TIMESTAMP}}`,
);
({ mockRecord, mockTransportSend, replay } = await resetSdkMock({
replayOptions: {
stickySession: true,
},
}));
replay.start();

jest.runAllTimers();

await new Promise(process.nextTick);
const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 };
mockRecord._emitter(TEST_EVENT);

expect(replay).not.toHaveSentReplay();

captureException(new Error('testing'));
jest.runAllTimers();
await new Promise(process.nextTick);

expect(replay).toHaveSentReplay({
events: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP, type: 2 }, TEST_EVENT]),
});

mockTransportSend.mockClear();
expect(replay).not.toHaveSentReplay();

jest.runAllTimers();
await new Promise(process.nextTick);
jest.runAllTimers();
await new Promise(process.nextTick);

// New checkout when we call `startRecording` again after uploading segment
// after an error occurs
expect(replay).toHaveSentReplay({
events: JSON.stringify([
{
data: { isCheckout: true },
timestamp: BASE_TIMESTAMP + 10000 + 20,
type: 2,
},
]),
});
});

it('has correct timestamps when error occurs much later than initial pageload/checkout', async () => {
const ELAPSED = 60000;
const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 };
Expand Down Expand Up @@ -415,3 +355,67 @@ describe('Replay (errorSampleRate)', () => {
});
});
});

/**
* This is testing a case that should only happen with error-only sessions.
* Previously we had assumed that loading a session from session storage meant
* that the session was not new. However, this is not the case with error-only
* sampling since we can load a saved session that did not have an error (and
* thus no replay was created).
*/
it('sends a replay after loading the session multiple times', async () => {
// Pretend that a session is already saved before loading replay
WINDOW.sessionStorage.setItem(
REPLAY_SESSION_KEY,
`{"segmentId":0,"id":"fd09adfc4117477abc8de643e5a5798a","sampled":"error","started":${BASE_TIMESTAMP},"lastActivity":${BASE_TIMESTAMP}}`,
);
const { mockRecord, replay } = await resetSdkMock({
replayOptions: {
stickySession: true,
},
autoStart: false,
});

const fn = getCurrentHub()?.getClient()?.getTransport()?.send;
const mockTransportSend = fn
? (jest.spyOn(getCurrentHub().getClient()!.getTransport()!, 'send') as jest.MockedFunction<any>)
: jest.fn();

replay.start();

jest.runAllTimers();

await new Promise(process.nextTick);
const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 };
mockRecord._emitter(TEST_EVENT);

expect(replay).not.toHaveSentReplay();

captureException(new Error('testing'));
jest.runAllTimers();
await new Promise(process.nextTick);

expect(replay).toHaveSentReplay({
events: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP, type: 2 }, TEST_EVENT]),
});

mockTransportSend.mockClear();
expect(replay).not.toHaveSentReplay();

jest.runAllTimers();
await new Promise(process.nextTick);
jest.runAllTimers();
await new Promise(process.nextTick);

// New checkout when we call `startRecording` again after uploading segment
// after an error occurs
expect(replay).toHaveSentReplay({
events: JSON.stringify([
{
data: { isCheckout: true },
timestamp: BASE_TIMESTAMP + 10000 + 20,
type: 2,
},
]),
});
});
2 changes: 0 additions & 2 deletions packages/replay/test/unit/index-sampling.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
jest.unmock('@sentry/browser');

// mock functions need to be imported first
import { mockRrweb, mockSdk } from './../index';
import { useFakeTimers } from './../utils/use-fake-timers';
Expand Down
9 changes: 6 additions & 3 deletions packages/replay/test/unit/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getCurrentHub } from '@sentry/core';
import { EventType } from 'rrweb';

import { MAX_SESSION_LIFE, REPLAY_SESSION_KEY, VISIBILITY_CHANGE_TIMEOUT, WINDOW } from '../../src/constants';
Expand All @@ -9,7 +10,7 @@ import { useFakeTimers } from '../utils/use-fake-timers';
import { PerformanceEntryResource } from './../fixtures/performanceEntry/resource';
import { BASE_TIMESTAMP, RecordMock } from './../index';
import { resetSdkMock } from './../mocks/resetSdkMock';
import { DomHandler, MockTransportSend } from './../types';
import { DomHandler } from './../types';

useFakeTimers();

Expand Down Expand Up @@ -92,7 +93,7 @@ describe('Replay with custom mock', () => {
describe('Replay', () => {
let replay: ReplayContainer;
let mockRecord: RecordMock;
let mockTransportSend: MockTransportSend;
let mockTransportSend: jest.SpyInstance<any>;
let domHandler: DomHandler;
const prevLocation = WINDOW.location;

Expand All @@ -105,12 +106,14 @@ describe('Replay', () => {
});

beforeEach(async () => {
({ mockRecord, mockTransportSend, domHandler, replay } = await resetSdkMock({
({ mockRecord, domHandler, replay } = await resetSdkMock({
replayOptions: {
stickySession: false,
},
}));

mockTransportSend = jest.spyOn(getCurrentHub().getClient()!.getTransport()!, 'send');

jest.spyOn(replay, 'flush');
jest.spyOn(replay, 'runFlush');
jest.spyOn(replay, 'sendReplayRequest');
Expand Down