Skip to content

test(replay): Streamline replay test naming & modules #6656

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 11, 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
2 changes: 1 addition & 1 deletion packages/replay/src/replay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { handleGlobalEventListener } from './coreHandlers/handleGlobalEvent';
import { handleHistorySpanListener } from './coreHandlers/handleHistory';
import { handleXhrSpanListener } from './coreHandlers/handleXhr';
import { setupPerformanceObserver } from './coreHandlers/performanceObserver';
import { createPerformanceEntries } from './createPerformanceEntry';
import { createEventBuffer } from './eventBuffer';
import { getSession } from './session/getSession';
import { saveSession } from './session/saveSession';
Expand All @@ -39,6 +38,7 @@ import type {
import { addEvent } from './util/addEvent';
import { addMemoryEntry } from './util/addMemoryEntry';
import { createBreadcrumb } from './util/createBreadcrumb';
import { createPerformanceEntries } from './util/createPerformanceEntries';
import { createPerformanceSpans } from './util/createPerformanceSpans';
import { createRecordingData } from './util/createRecordingData';
import { createReplayEnvelope } from './util/createReplayEnvelope';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { browserPerformanceTimeOrigin } from '@sentry/utils';
import { record } from 'rrweb';

import { WINDOW } from './constants';
import { WINDOW } from '../constants';
import type {
AllPerformanceEntry,
PerformanceNavigationTiming,
PerformancePaintTiming,
ReplayPerformanceEntry,
} from './types';
} from '../types';

// Map entryType -> function to normalize data for event
// @ts-ignore TODO: entry type does not fit the create* functions entry type
Expand All @@ -18,7 +18,7 @@ const ENTRY_TYPES: Record<string, (entry: AllPerformanceEntry) => null | ReplayP
// @ts-ignore TODO: entry type does not fit the create* functions entry type
navigation: createNavigationEntry,
// @ts-ignore TODO: entry type does not fit the create* functions entry type
'largest-contentful-paint': createLargestContentfulPaint,
['largest-contentful-paint']: createLargestContentfulPaint,
};

/**
Expand All @@ -42,7 +42,9 @@ function getAbsoluteTime(time: number): number {
return ((browserPerformanceTimeOrigin || WINDOW.performance.timeOrigin) + time) / 1000;
}

function createPaintEntry(entry: PerformancePaintTiming): ReplayPerformanceEntry {
// TODO: type definition!
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function createPaintEntry(entry: PerformancePaintTiming) {
const { duration, entryType, name, startTime } = entry;

const start = getAbsoluteTime(startTime);
Expand All @@ -54,7 +56,9 @@ function createPaintEntry(entry: PerformancePaintTiming): ReplayPerformanceEntry
};
}

function createNavigationEntry(entry: PerformanceNavigationTiming): ReplayPerformanceEntry | null {
// TODO: type definition!
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function createNavigationEntry(entry: PerformanceNavigationTiming) {
// TODO: There looks to be some more interesting bits in here (domComplete, domContentLoaded)
const { entryType, name, duration, domComplete, startTime, transferSize, type } = entry;

Expand All @@ -75,7 +79,9 @@ function createNavigationEntry(entry: PerformanceNavigationTiming): ReplayPerfor
};
}

function createResourceEntry(entry: PerformanceResourceTiming): ReplayPerformanceEntry | null {
// TODO: type definition!
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function createResourceEntry(entry: PerformanceResourceTiming) {
const { entryType, initiatorType, name, responseEnd, startTime, encodedBodySize, transferSize } = entry;

// Core SDK handles these
Expand All @@ -95,9 +101,9 @@ function createResourceEntry(entry: PerformanceResourceTiming): ReplayPerformanc
};
}

function createLargestContentfulPaint(
entry: PerformanceEntry & { size: number; element: Node },
): ReplayPerformanceEntry {
// TODO: type definition!
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function createLargestContentfulPaint(entry: PerformanceEntry & { size: number; element: Node }) {
const { duration, entryType, startTime, size } = entry;

const start = getAbsoluteTime(startTime);
Expand Down
57 changes: 57 additions & 0 deletions packages/replay/test/integration/autoSaveSession.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { EventType } from 'rrweb';

import type { RecordingEvent } from '../../src/types';
import { addEvent } from '../../src/util/addEvent';
import { resetSdkMock } from '../mocks/resetSdkMock';
import { useFakeTimers } from '../utils/use-fake-timers';

useFakeTimers();

describe('Integration | autoSaveSession', () => {
afterEach(() => {
jest.clearAllMocks();
});

test.each([
['with stickySession=true', true, 1],
['with stickySession=false', false, 0],
])('%s', async (_: string, stickySession: boolean, addSummand: number) => {
let saveSessionSpy;

jest.mock('../../src/session/saveSession', () => {
saveSessionSpy = jest.fn();

return {
saveSession: saveSessionSpy,
};
});

const { replay } = await resetSdkMock({
replayOptions: {
stickySession,
},
});

// Initially called up to three times: once for start, then once for replay.updateSessionActivity & once for segmentId increase
expect(saveSessionSpy).toHaveBeenCalledTimes(addSummand * 3);

replay.updateSessionActivity();

expect(saveSessionSpy).toHaveBeenCalledTimes(addSummand * 4);

// In order for runFlush to actually do something, we need to add an event
const event = {
type: EventType.Custom,
data: {
tag: 'test custom',
},
timestamp: new Date().valueOf(),
} as RecordingEvent;

addEvent(replay, event);

await replay.runFlush();

expect(saveSessionSpy).toHaveBeenCalledTimes(addSummand * 5);
});
});
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { getCurrentHub } from '@sentry/core';
import { Event } from '@sentry/types';

import { REPLAY_EVENT_NAME } from '../../src/constants';
import { handleGlobalEventListener } from '../../src/coreHandlers/handleGlobalEvent';
import { overwriteRecordDroppedEvent, restoreRecordDroppedEvent } from '../../src/util/monkeyPatchRecordDroppedEvent';
import { ReplayContainer } from './../../src/replay';
import { Error } from './../fixtures/error';
import { Transaction } from './../fixtures/transaction';
import { resetSdkMock } from './../mocks/resetSdkMock';
import { useFakeTimers } from './../utils/use-fake-timers';
import { REPLAY_EVENT_NAME } from '../../../src/constants';
import { handleGlobalEventListener } from '../../../src/coreHandlers/handleGlobalEvent';
import { ReplayContainer } from '../../../src/replay';
import {
overwriteRecordDroppedEvent,
restoreRecordDroppedEvent,
} from '../../../src/util/monkeyPatchRecordDroppedEvent';
import { Error } from '../../fixtures/error';
import { Transaction } from '../../fixtures/transaction';
import { resetSdkMock } from '../../mocks/resetSdkMock';
import { useFakeTimers } from '../../utils/use-fake-timers';

useFakeTimers();
let replay: ReplayContainer;

describe('handleGlobalEvent', () => {
describe('Integration | coreHandlers | handleGlobalEvent', () => {
beforeEach(async () => {
({ replay } = await resetSdkMock({
replayOptions: {
Expand Down
29 changes: 29 additions & 0 deletions packages/replay/test/integration/coreHandlers/handleScope.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { getCurrentHub } from '@sentry/core';

import * as HandleScope from '../../../src/coreHandlers/handleScope';
import { mockSdk } from './../../index';

jest.useFakeTimers();

describe('Integration | coreHandlers | handleScope', () => {
beforeAll(async function () {
await mockSdk();
jest.runAllTimers();
});

it('returns a breadcrumb only if last breadcrumb has changed', function () {
const mockHandleScope = jest.spyOn(HandleScope, 'handleScope');
getCurrentHub().getScope()?.addBreadcrumb({ message: 'testing' });

expect(mockHandleScope).toHaveBeenCalledTimes(1);
expect(mockHandleScope).toHaveReturnedWith(expect.objectContaining({ message: 'testing' }));

mockHandleScope.mockClear();

// This will trigger breadcrumb/scope listener, but handleScope should return
// null because breadcrumbs has not changed
getCurrentHub().getScope()?.setUser({ email: '[email protected]' });
expect(mockHandleScope).toHaveBeenCalledTimes(1);
expect(mockHandleScope).toHaveReturnedWith(null);
});
});
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { captureException } from '@sentry/core';

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

useFakeTimers();

Expand All @@ -17,7 +17,7 @@ async function advanceTimers(time: number) {
await new Promise(process.nextTick);
}

describe('Replay (errorSampleRate)', () => {
describe('Integration | errorSampleRate', () => {
let replay: ReplayContainer;
let mockRecord: RecordMock;
let domHandler: DomHandler;
Expand Down
82 changes: 82 additions & 0 deletions packages/replay/test/integration/eventProcessors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { getCurrentHub } from '@sentry/core';
import { Event, Hub, Scope } from '@sentry/types';

import { BASE_TIMESTAMP } from '..';
import { resetSdkMock } from '../mocks/resetSdkMock';
import { useFakeTimers } from '../utils/use-fake-timers';

useFakeTimers();

describe('Integration | eventProcessors', () => {
let hub: Hub;
let scope: Scope;

beforeEach(() => {
hub = getCurrentHub();
scope = hub.pushScope();
});

afterEach(() => {
hub.popScope();
jest.resetAllMocks();
});

it('handles event processors properly', async () => {
const MUTATED_TIMESTAMP = BASE_TIMESTAMP + 3000;

const { mockRecord } = await resetSdkMock({
replayOptions: {
stickySession: false,
},
});

const client = hub.getClient()!;

jest.runAllTimers();
const mockTransportSend = jest.spyOn(client.getTransport()!, 'send');
mockTransportSend.mockReset();

const handler1 = jest.fn((event: Event): Event | null => {
event.timestamp = MUTATED_TIMESTAMP;

return event;
});

const handler2 = jest.fn((): Event | null => {
return null;
});

scope.addEventProcessor(handler1);

const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 };

mockRecord._emitter(TEST_EVENT);
jest.runAllTimers();
jest.advanceTimersByTime(1);
await new Promise(process.nextTick);

expect(mockTransportSend).toHaveBeenCalledTimes(1);

scope.addEventProcessor(handler2);

const TEST_EVENT2 = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 };

mockRecord._emitter(TEST_EVENT2);
jest.runAllTimers();
jest.advanceTimersByTime(1);
await new Promise(process.nextTick);

expect(mockTransportSend).toHaveBeenCalledTimes(1);

expect(handler1).toHaveBeenCalledTimes(2);
expect(handler2).toHaveBeenCalledTimes(1);

// This receives an envelope, which is a deeply nested array
// We only care about the fact that the timestamp was mutated
expect(mockTransportSend).toHaveBeenCalledWith(
expect.arrayContaining([
expect.arrayContaining([expect.arrayContaining([expect.objectContaining({ timestamp: MUTATED_TIMESTAMP })])]),
]),
);
});
});
Loading