Skip to content

test(replay): Add integration tests for input masking on change #7260

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 4 commits into from
Mar 9, 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
19 changes: 19 additions & 0 deletions packages/integration-tests/suites/replay/privacyInput/init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as Sentry from '@sentry/browser';
import { Replay } from '@sentry/replay';

window.Sentry = Sentry;
window.Replay = new Replay({
flushMinDelay: 200,
flushMaxDelay: 200,
useCompression: false,
maskAllInputs: false,
});

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

integrations: [window.Replay],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<input id="input" />
<input id="input-masked" data-sentry-mask />
<input id="input-ignore" data-sentry-ignore />

<textarea id="textarea"></textarea>
<textarea id="textarea-masked" data-sentry-mask></textarea>
<textarea id="textarea-ignore" data-sentry-ignore></textarea>
</body>
</html>
111 changes: 111 additions & 0 deletions packages/integration-tests/suites/replay/privacyInput/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { expect } from '@playwright/test';
import { IncrementalSource } from '@sentry-internal/rrweb';
import type { inputData } from '@sentry-internal/rrweb/typings/types';

import { sentryTest } from '../../../utils/fixtures';
import type { IncrementalRecordingSnapshot } from '../../../utils/replayHelpers';
import {
getIncrementalRecordingSnapshots,
shouldSkipReplayTest,
waitForReplayRequest,
} from '../../../utils/replayHelpers';

function isInputMutation(
snap: IncrementalRecordingSnapshot,
): snap is IncrementalRecordingSnapshot & { data: inputData } {
return snap.data.source == IncrementalSource.Input;
}

sentryTest(
'should mask input initial value and its changes',
async ({ browserName, forceFlushReplay, getLocalTestPath, page }) => {
// TODO(replay): This is flakey on firefox and webkit (~1%) where we do not always get the latest mutation.
if (shouldSkipReplayTest() || ['firefox', 'webkit'].includes(browserName)) {
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 getLocalTestPath({ testDir: __dirname });

await page.goto(url);

await reqPromise0;

const text = 'test';

await page.locator('#input').fill(text);
await forceFlushReplay();
const snapshots = getIncrementalRecordingSnapshots(await reqPromise1).filter(isInputMutation);
const lastSnapshot = snapshots[snapshots.length - 1];
expect(lastSnapshot.data.text).toBe(text);

await page.locator('#input-masked').fill(text);
await forceFlushReplay();
const snapshots2 = getIncrementalRecordingSnapshots(await reqPromise2).filter(isInputMutation);
const lastSnapshot2 = snapshots2[snapshots2.length - 1];
expect(lastSnapshot2.data.text).toBe('*'.repeat(text.length));

await page.locator('#input-ignore').fill(text);
await forceFlushReplay();
const snapshots3 = getIncrementalRecordingSnapshots(await reqPromise3).filter(isInputMutation);
expect(snapshots3.length).toBe(0);
},
);

sentryTest(
'should mask textarea initial value and its changes',
async ({ browserName, forceFlushReplay, getLocalTestPath, page }) => {
// TODO(replay): This is flakey on firefox and webkit (~1%) where we do not always get the latest mutation.
if (shouldSkipReplayTest() || ['firefox', 'webkit'].includes(browserName)) {
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 getLocalTestPath({ testDir: __dirname });

await page.goto(url);
await reqPromise0;

const text = 'test';
await page.locator('#textarea').fill(text);
await forceFlushReplay();
const snapshots = getIncrementalRecordingSnapshots(await reqPromise1).filter(isInputMutation);
const lastSnapshot = snapshots[snapshots.length - 1];
expect(lastSnapshot.data.text).toBe(text);

await page.locator('#textarea-masked').fill(text);
await forceFlushReplay();
const snapshots2 = getIncrementalRecordingSnapshots(await reqPromise2).filter(isInputMutation);
const lastSnapshot2 = snapshots2[snapshots2.length - 1];
expect(lastSnapshot2.data.text).toBe('*'.repeat(text.length));

await page.locator('#textarea-ignore').fill(text);
await forceFlushReplay();
const snapshots3 = getIncrementalRecordingSnapshots(await reqPromise3).filter(isInputMutation);
expect(snapshots3.length).toBe(0);
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as Sentry from '@sentry/browser';
import { Replay } from '@sentry/replay';

window.Sentry = Sentry;
window.Replay = new Replay({
flushMinDelay: 200,
flushMaxDelay: 200,
useCompression: false,
maskAllInputs: true,
});

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

integrations: [window.Replay],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<input id="input" />
<input id="input-unmasked" data-sentry-unmask />

<textarea id="textarea"></textarea>
<textarea id="textarea-unmasked" data-sentry-unmask></textarea>
</body>
</html>
135 changes: 135 additions & 0 deletions packages/integration-tests/suites/replay/privacyInputMaskAll/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { expect } from '@playwright/test';
import { IncrementalSource } from '@sentry-internal/rrweb';
import type { inputData } from '@sentry-internal/rrweb/typings/types';

import { sentryTest } from '../../../utils/fixtures';
import type { IncrementalRecordingSnapshot } from '../../../utils/replayHelpers';
import {
getIncrementalRecordingSnapshots,
shouldSkipReplayTest,
waitForReplayRequest,
} from '../../../utils/replayHelpers';

function isInputMutation(
snap: IncrementalRecordingSnapshot,
): snap is IncrementalRecordingSnapshot & { data: inputData } {
return snap.data.source == IncrementalSource.Input;
}

sentryTest(
'should mask input initial value and its changes from `maskAllInputs` and allow unmasked selector',
async ({ browserName, forceFlushReplay, getLocalTestPath, page }) => {
// TODO(replay): This is flakey on firefox and webkit (~1%) where we do not always get the latest mutation.
if (shouldSkipReplayTest() || ['firefox', 'webkit'].includes(browserName)) {
sentryTest.skip();
}

// We want to ensure to check the correct event payloads
let firstInputMutationSegmentId: number | undefined = undefined;
const reqPromise0 = waitForReplayRequest(page, 0);
const reqPromise1 = waitForReplayRequest(page, (event, res) => {
const check =
firstInputMutationSegmentId === undefined && getIncrementalRecordingSnapshots(res).some(isInputMutation);

if (check) {
firstInputMutationSegmentId = event.segment_id;
}

return check;
});
const reqPromise2 = waitForReplayRequest(page, (event, res) => {
return (
typeof firstInputMutationSegmentId === 'number' &&
firstInputMutationSegmentId < event.segment_id &&
getIncrementalRecordingSnapshots(res).some(isInputMutation)
);
});

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 reqPromise0;

const text = 'test';

await page.locator('#input').fill(text);
await forceFlushReplay();

const snapshots = getIncrementalRecordingSnapshots(await reqPromise1).filter(isInputMutation);
const lastSnapshot = snapshots[snapshots.length - 1];
expect(lastSnapshot.data.text).toBe('*'.repeat(text.length));

await page.locator('#input-unmasked').fill(text);
await forceFlushReplay();
const snapshots2 = getIncrementalRecordingSnapshots(await reqPromise2).filter(isInputMutation);
const lastSnapshot2 = snapshots2[snapshots2.length - 1];
expect(lastSnapshot2.data.text).toBe(text);
},
);

sentryTest(
'should mask textarea initial value and its changes from `maskAllInputs` and allow unmasked selector',
async ({ browserName, forceFlushReplay, getLocalTestPath, page }) => {
// TODO(replay): This is flakey on firefox and webkit (~1%) where we do not always get the latest mutation.
if (shouldSkipReplayTest() || ['firefox', 'webkit'].includes(browserName)) {
sentryTest.skip();
}

// We want to ensure to check the correct event payloads
let firstInputMutationSegmentId: number | undefined = undefined;
const reqPromise0 = waitForReplayRequest(page, 0);
const reqPromise1 = waitForReplayRequest(page, (event, res) => {
const check =
firstInputMutationSegmentId === undefined && getIncrementalRecordingSnapshots(res).some(isInputMutation);

if (check) {
firstInputMutationSegmentId = event.segment_id;
}

return check;
});
const reqPromise2 = waitForReplayRequest(page, (event, res) => {
return (
typeof firstInputMutationSegmentId === 'number' &&
firstInputMutationSegmentId < event.segment_id &&
getIncrementalRecordingSnapshots(res).some(isInputMutation)
);
});

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 reqPromise0;

const text = 'test';

await page.locator('#textarea').fill(text);
await forceFlushReplay();
const snapshots = getIncrementalRecordingSnapshots(await reqPromise1).filter(isInputMutation);
const lastSnapshot = snapshots[snapshots.length - 1];
expect(lastSnapshot.data.text).toBe('*'.repeat(text.length));

await page.locator('#textarea-unmasked').fill(text);
await forceFlushReplay();
const snapshots2 = getIncrementalRecordingSnapshots(await reqPromise2).filter(isInputMutation);
const lastSnapshot2 = snapshots2[snapshots2.length - 1];
expect(lastSnapshot2.data.text).toBe(text);
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
// Session should expire after 2s - keep in sync with init.js
const SESSION_TIMEOUT = 2000;

sentryTest('handles an expired session RUN', async ({ getLocalTestPath, page }) => {
sentryTest('handles an expired session', async ({ getLocalTestPath, page }) => {
if (shouldSkipReplayTest()) {
sentryTest.skip();
}
Expand Down
15 changes: 15 additions & 0 deletions packages/integration-tests/utils/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type TestFixtures = {
_autoSnapshotSuffix: void;
testDir: string;
getLocalTestPath: (options: { testDir: string }) => Promise<string>;
forceFlushReplay: () => Promise<string>;
runInChromium: (fn: (...args: unknown[]) => unknown, args?: unknown[]) => unknown;
runInFirefox: (fn: (...args: unknown[]) => unknown, args?: unknown[]) => unknown;
runInWebkit: (fn: (...args: unknown[]) => unknown, args?: unknown[]) => unknown;
Expand Down Expand Up @@ -92,6 +93,20 @@ const sentryTest = base.extend<TestFixtures>({
return fn(...args);
});
},

forceFlushReplay: ({ page }, use) => {
return use(() =>
page.evaluate(`
Object.defineProperty(document, 'visibilityState', {
configurable: true,
get: function () {
return 'hidden';
},
});
document.dispatchEvent(new Event('visibilitychange'));
`),
);
},
});

export { sentryTest };
Loading