mask input options and sampling options (#252)
* part of #80, support mask input options * close #188 enhance sampling options Use a more general sampling strategy interface to describe the configuration of sampling events collection. Implemented mousmove, mouse interaction, scroll and input sampling strategy.
This commit is contained in:
@@ -62,7 +62,7 @@
|
||||
"@xstate/fsm": "^1.4.0",
|
||||
"mitt": "^1.1.3",
|
||||
"pako": "^1.0.11",
|
||||
"rrweb-snapshot": "^0.7.27",
|
||||
"rrweb-snapshot": "^0.8.0",
|
||||
"smoothscroll-polyfill": "^0.4.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { snapshot } from 'rrweb-snapshot';
|
||||
import { snapshot, MaskInputOptions } from 'rrweb-snapshot';
|
||||
import initObservers from './observer';
|
||||
import {
|
||||
mirror,
|
||||
@@ -35,15 +35,44 @@ function record<T = eventWithTime>(
|
||||
blockClass = 'rr-block',
|
||||
ignoreClass = 'rr-ignore',
|
||||
inlineStylesheet = true,
|
||||
maskAllInputs = false,
|
||||
maskAllInputs,
|
||||
maskInputOptions: _maskInputOptions,
|
||||
hooks,
|
||||
mousemoveWait = 50,
|
||||
packFn,
|
||||
sampling = {},
|
||||
mousemoveWait,
|
||||
} = options;
|
||||
// runtime checks for user options
|
||||
if (!emit) {
|
||||
throw new Error('emit function is required');
|
||||
}
|
||||
// move departed options to new options
|
||||
if (mousemoveWait !== undefined && sampling.mousemove === undefined) {
|
||||
sampling.mousemove = mousemoveWait;
|
||||
}
|
||||
|
||||
const maskInputOptions: MaskInputOptions =
|
||||
maskAllInputs === true
|
||||
? {
|
||||
color: true,
|
||||
date: true,
|
||||
'datetime-local': true,
|
||||
email: true,
|
||||
month: true,
|
||||
number: true,
|
||||
range: true,
|
||||
search: true,
|
||||
tel: true,
|
||||
text: true,
|
||||
time: true,
|
||||
url: true,
|
||||
week: true,
|
||||
textarea: true,
|
||||
select: true,
|
||||
}
|
||||
: _maskInputOptions !== undefined
|
||||
? _maskInputOptions
|
||||
: {};
|
||||
|
||||
polyfill();
|
||||
|
||||
@@ -83,7 +112,7 @@ function record<T = eventWithTime>(
|
||||
document,
|
||||
blockClass,
|
||||
inlineStylesheet,
|
||||
maskAllInputs,
|
||||
maskInputOptions,
|
||||
);
|
||||
|
||||
if (!node) {
|
||||
@@ -217,9 +246,9 @@ function record<T = eventWithTime>(
|
||||
),
|
||||
blockClass,
|
||||
ignoreClass,
|
||||
maskAllInputs,
|
||||
maskInputOptions,
|
||||
inlineStylesheet,
|
||||
mousemoveWait,
|
||||
sampling,
|
||||
},
|
||||
hooks,
|
||||
),
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { INode, serializeNodeWithId, transformAttribute } from 'rrweb-snapshot';
|
||||
import {
|
||||
INode,
|
||||
serializeNodeWithId,
|
||||
transformAttribute,
|
||||
MaskInputOptions,
|
||||
} from 'rrweb-snapshot';
|
||||
import {
|
||||
mutationRecord,
|
||||
blockClass,
|
||||
@@ -50,17 +55,17 @@ export default class MutationBuffer {
|
||||
private emissionCallback: mutationCallBack;
|
||||
private blockClass: blockClass;
|
||||
private inlineStylesheet: boolean;
|
||||
private maskAllInputs: boolean;
|
||||
private maskInputOptions: MaskInputOptions;
|
||||
|
||||
constructor(
|
||||
cb: mutationCallBack,
|
||||
blockClass: blockClass,
|
||||
inlineStylesheet: boolean,
|
||||
maskAllInputs: boolean,
|
||||
maskInputOptions: MaskInputOptions,
|
||||
) {
|
||||
this.blockClass = blockClass;
|
||||
this.inlineStylesheet = inlineStylesheet;
|
||||
this.maskAllInputs = maskAllInputs;
|
||||
this.maskInputOptions = maskInputOptions;
|
||||
this.emissionCallback = cb;
|
||||
}
|
||||
|
||||
@@ -89,7 +94,7 @@ export default class MutationBuffer {
|
||||
this.blockClass,
|
||||
true,
|
||||
this.inlineStylesheet,
|
||||
this.maskAllInputs,
|
||||
this.maskInputOptions,
|
||||
)!,
|
||||
});
|
||||
};
|
||||
@@ -130,6 +135,47 @@ export default class MutationBuffer {
|
||||
this.emit();
|
||||
};
|
||||
|
||||
public emit = () => {
|
||||
const payload = {
|
||||
texts: this.texts
|
||||
.map((text) => ({
|
||||
id: mirror.getId(text.node as INode),
|
||||
value: text.value,
|
||||
}))
|
||||
// text mutation's id was not in the mirror map means the target node has been removed
|
||||
.filter((text) => mirror.has(text.id)),
|
||||
attributes: this.attributes
|
||||
.map((attribute) => ({
|
||||
id: mirror.getId(attribute.node as INode),
|
||||
attributes: attribute.attributes,
|
||||
}))
|
||||
// attribute mutation's id was not in the mirror map means the target node has been removed
|
||||
.filter((attribute) => mirror.has(attribute.id)),
|
||||
removes: this.removes,
|
||||
adds: this.adds,
|
||||
};
|
||||
// payload may be empty if the mutations happened in some blocked elements
|
||||
if (
|
||||
!payload.texts.length &&
|
||||
!payload.attributes.length &&
|
||||
!payload.removes.length &&
|
||||
!payload.adds.length
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.emissionCallback(payload);
|
||||
|
||||
// reset
|
||||
this.texts = [];
|
||||
this.attributes = [];
|
||||
this.removes = [];
|
||||
this.adds = [];
|
||||
this.addedSet = new Set<Node>();
|
||||
this.movedSet = new Set<Node>();
|
||||
this.droppedSet = new Set<Node>();
|
||||
this.movedMap = {};
|
||||
};
|
||||
|
||||
private processMutation = (m: mutationRecord) => {
|
||||
switch (m.type) {
|
||||
case 'characterData': {
|
||||
@@ -231,47 +277,6 @@ export default class MutationBuffer {
|
||||
}
|
||||
n.childNodes.forEach((childN) => this.genAdds(childN));
|
||||
};
|
||||
|
||||
public emit = () => {
|
||||
const payload = {
|
||||
texts: this.texts
|
||||
.map((text) => ({
|
||||
id: mirror.getId(text.node as INode),
|
||||
value: text.value,
|
||||
}))
|
||||
// text mutation's id was not in the mirror map means the target node has been removed
|
||||
.filter((text) => mirror.has(text.id)),
|
||||
attributes: this.attributes
|
||||
.map((attribute) => ({
|
||||
id: mirror.getId(attribute.node as INode),
|
||||
attributes: attribute.attributes,
|
||||
}))
|
||||
// attribute mutation's id was not in the mirror map means the target node has been removed
|
||||
.filter((attribute) => mirror.has(attribute.id)),
|
||||
removes: this.removes,
|
||||
adds: this.adds,
|
||||
};
|
||||
// payload may be empty if the mutations happened in some blocked elements
|
||||
if (
|
||||
!payload.texts.length &&
|
||||
!payload.attributes.length &&
|
||||
!payload.removes.length &&
|
||||
!payload.adds.length
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.emissionCallback(payload);
|
||||
|
||||
// reset
|
||||
this.texts = [];
|
||||
this.attributes = [];
|
||||
this.removes = [];
|
||||
this.adds = [];
|
||||
this.addedSet = new Set<Node>();
|
||||
this.movedSet = new Set<Node>();
|
||||
this.droppedSet = new Set<Node>();
|
||||
this.movedMap = {};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { INode } from 'rrweb-snapshot';
|
||||
import { INode, MaskInputOptions } from 'rrweb-snapshot';
|
||||
import {
|
||||
mirror,
|
||||
throttle,
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
Arguments,
|
||||
mediaInteractionCallback,
|
||||
MediaInteractions,
|
||||
SamplingStrategy,
|
||||
} from '../types';
|
||||
import MutationBuffer from './mutation';
|
||||
|
||||
@@ -36,14 +37,14 @@ function initMutationObserver(
|
||||
cb: mutationCallBack,
|
||||
blockClass: blockClass,
|
||||
inlineStylesheet: boolean,
|
||||
maskAllInputs: boolean,
|
||||
maskInputOptions: MaskInputOptions,
|
||||
): MutationObserver {
|
||||
// see mutation.ts for details
|
||||
const mutationBuffer = new MutationBuffer(
|
||||
cb,
|
||||
blockClass,
|
||||
inlineStylesheet,
|
||||
maskAllInputs,
|
||||
maskInputOptions,
|
||||
);
|
||||
const observer = new MutationObserver(mutationBuffer.processMutations);
|
||||
observer.observe(document, {
|
||||
@@ -59,8 +60,15 @@ function initMutationObserver(
|
||||
|
||||
function initMoveObserver(
|
||||
cb: mousemoveCallBack,
|
||||
mousemoveWait: number,
|
||||
sampling: SamplingStrategy,
|
||||
): listenerHandler {
|
||||
if (sampling.mousemove === false) {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
const threshold =
|
||||
typeof sampling.mousemove === 'number' ? sampling.mousemove : 50;
|
||||
|
||||
let positions: mousePosition[] = [];
|
||||
let timeBaseline: number | null;
|
||||
const wrappedCb = throttle((isTouch: boolean) => {
|
||||
@@ -92,7 +100,7 @@ function initMoveObserver(
|
||||
});
|
||||
wrappedCb(isTouchEvent(evt));
|
||||
},
|
||||
mousemoveWait,
|
||||
threshold,
|
||||
{
|
||||
trailing: false,
|
||||
},
|
||||
@@ -109,7 +117,17 @@ function initMoveObserver(
|
||||
function initMouseInteractionObserver(
|
||||
cb: mouseInteractionCallBack,
|
||||
blockClass: blockClass,
|
||||
sampling: SamplingStrategy,
|
||||
): listenerHandler {
|
||||
if (sampling.mouseInteraction === false) {
|
||||
return () => {};
|
||||
}
|
||||
const disableMap: Record<string, boolean | undefined> =
|
||||
sampling.mouseInteraction === true ||
|
||||
sampling.mouseInteraction === undefined
|
||||
? {}
|
||||
: sampling.mouseInteraction;
|
||||
|
||||
const handlers: listenerHandler[] = [];
|
||||
const getHandler = (eventKey: keyof typeof MouseInteractions) => {
|
||||
return (event: MouseEvent | TouchEvent) => {
|
||||
@@ -129,7 +147,12 @@ function initMouseInteractionObserver(
|
||||
};
|
||||
};
|
||||
Object.keys(MouseInteractions)
|
||||
.filter((key) => Number.isNaN(Number(key)) && !key.endsWith('_Departed'))
|
||||
.filter(
|
||||
(key) =>
|
||||
Number.isNaN(Number(key)) &&
|
||||
!key.endsWith('_Departed') &&
|
||||
disableMap[key] !== false,
|
||||
)
|
||||
.forEach((eventKey: keyof typeof MouseInteractions) => {
|
||||
const eventName = eventKey.toLowerCase();
|
||||
const handler = getHandler(eventKey);
|
||||
@@ -143,6 +166,7 @@ function initMouseInteractionObserver(
|
||||
function initScrollObserver(
|
||||
cb: scrollCallback,
|
||||
blockClass: blockClass,
|
||||
sampling: SamplingStrategy,
|
||||
): listenerHandler {
|
||||
const updatePosition = throttle<UIEvent>((evt) => {
|
||||
if (!evt.target || isBlocked(evt.target as Node, blockClass)) {
|
||||
@@ -163,7 +187,7 @@ function initScrollObserver(
|
||||
y: (evt.target as HTMLElement).scrollTop,
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
}, sampling.scroll || 100);
|
||||
return on('scroll', updatePosition);
|
||||
}
|
||||
|
||||
@@ -182,27 +206,13 @@ function initViewportResizeObserver(
|
||||
}
|
||||
|
||||
export const INPUT_TAGS = ['INPUT', 'TEXTAREA', 'SELECT'];
|
||||
export const MASK_TYPES = [
|
||||
'color',
|
||||
'date',
|
||||
'datetime-local',
|
||||
'email',
|
||||
'month',
|
||||
'number',
|
||||
'range',
|
||||
'search',
|
||||
'tel',
|
||||
'text',
|
||||
'time',
|
||||
'url',
|
||||
'week',
|
||||
];
|
||||
const lastInputValueMap: WeakMap<EventTarget, inputValue> = new WeakMap();
|
||||
function initInputObserver(
|
||||
cb: inputCallback,
|
||||
blockClass: blockClass,
|
||||
ignoreClass: string,
|
||||
maskAllInputs: boolean,
|
||||
maskInputOptions: MaskInputOptions,
|
||||
sampling: SamplingStrategy,
|
||||
): listenerHandler {
|
||||
function eventHandler(event: Event) {
|
||||
const { target } = event;
|
||||
@@ -223,11 +233,14 @@ function initInputObserver(
|
||||
}
|
||||
let text = (target as HTMLInputElement).value;
|
||||
let isChecked = false;
|
||||
const hasTextInput =
|
||||
MASK_TYPES.includes(type) || (target as Element).tagName === 'TEXTAREA';
|
||||
if (type === 'radio' || type === 'checkbox') {
|
||||
isChecked = (target as HTMLInputElement).checked;
|
||||
} else if (hasTextInput && maskAllInputs) {
|
||||
} else if (
|
||||
maskInputOptions[
|
||||
(target as Element).tagName.toLowerCase() as keyof MaskInputOptions
|
||||
] ||
|
||||
maskInputOptions[type as keyof MaskInputOptions]
|
||||
) {
|
||||
text = '*'.repeat(text.length);
|
||||
}
|
||||
cbWithDedup(target, { text, isChecked });
|
||||
@@ -262,10 +275,10 @@ function initInputObserver(
|
||||
});
|
||||
}
|
||||
}
|
||||
const handlers: Array<listenerHandler | hookResetter> = [
|
||||
'input',
|
||||
'change',
|
||||
].map((eventName) => on(eventName, eventHandler));
|
||||
const events = sampling.input === 'last' ? ['change'] : ['input', 'change'];
|
||||
const handlers: Array<
|
||||
listenerHandler | hookResetter
|
||||
> = events.map((eventName) => on(eventName, eventHandler));
|
||||
const propertyDescriptor = Object.getOwnPropertyDescriptor(
|
||||
HTMLInputElement.prototype,
|
||||
'value',
|
||||
@@ -414,20 +427,26 @@ export default function initObservers(
|
||||
o.mutationCb,
|
||||
o.blockClass,
|
||||
o.inlineStylesheet,
|
||||
o.maskAllInputs,
|
||||
o.maskInputOptions,
|
||||
);
|
||||
const mousemoveHandler = initMoveObserver(o.mousemoveCb, o.mousemoveWait);
|
||||
const mousemoveHandler = initMoveObserver(o.mousemoveCb, o.sampling);
|
||||
const mouseInteractionHandler = initMouseInteractionObserver(
|
||||
o.mouseInteractionCb,
|
||||
o.blockClass,
|
||||
o.sampling,
|
||||
);
|
||||
const scrollHandler = initScrollObserver(
|
||||
o.scrollCb,
|
||||
o.blockClass,
|
||||
o.sampling,
|
||||
);
|
||||
const scrollHandler = initScrollObserver(o.scrollCb, o.blockClass);
|
||||
const viewportResizeHandler = initViewportResizeObserver(o.viewportResizeCb);
|
||||
const inputHandler = initInputObserver(
|
||||
o.inputCb,
|
||||
o.blockClass,
|
||||
o.ignoreClass,
|
||||
o.maskAllInputs,
|
||||
o.maskInputOptions,
|
||||
o.sampling,
|
||||
);
|
||||
const mediaInteractionHandler = initMediaInteractionObserver(
|
||||
o.mediaInteractionCb,
|
||||
|
||||
38
src/types.ts
38
src/types.ts
@@ -1,4 +1,9 @@
|
||||
import { serializedNodeWithId, idNodeMap, INode } from 'rrweb-snapshot';
|
||||
import {
|
||||
serializedNodeWithId,
|
||||
idNodeMap,
|
||||
INode,
|
||||
MaskInputOptions,
|
||||
} from 'rrweb-snapshot';
|
||||
import { PackFn, UnpackFn } from './packer/base';
|
||||
|
||||
export enum EventType {
|
||||
@@ -126,6 +131,28 @@ export type eventWithTime = event & {
|
||||
|
||||
export type blockClass = string | RegExp;
|
||||
|
||||
export type SamplingStrategy = Partial<{
|
||||
/**
|
||||
* false means not to record mouse/touch move events
|
||||
* number is the throttle threshold of recording mouse/touch move
|
||||
*/
|
||||
mousemove: boolean | number;
|
||||
/**
|
||||
* false means not to record mouse interaction events
|
||||
* can also specify record some kinds of mouse interactions
|
||||
*/
|
||||
mouseInteraction: boolean | Record<string, boolean | undefined>;
|
||||
/**
|
||||
* number is the throttle threshold of recording scroll
|
||||
*/
|
||||
scroll: number;
|
||||
/**
|
||||
* 'all' will record all the input events
|
||||
* 'last' will only record the last input value while input a sequence of chars
|
||||
*/
|
||||
input: 'all' | 'last';
|
||||
}>;
|
||||
|
||||
export type recordOptions<T> = {
|
||||
emit?: (e: T, isCheckout?: boolean) => void;
|
||||
checkoutEveryNth?: number;
|
||||
@@ -133,10 +160,13 @@ export type recordOptions<T> = {
|
||||
blockClass?: blockClass;
|
||||
ignoreClass?: string;
|
||||
maskAllInputs?: boolean;
|
||||
maskInputOptions?: MaskInputOptions;
|
||||
inlineStylesheet?: boolean;
|
||||
hooks?: hooksParam;
|
||||
mousemoveWait?: number;
|
||||
packFn?: PackFn;
|
||||
sampling?: SamplingStrategy;
|
||||
// departed, please use sampling options
|
||||
mousemoveWait?: number;
|
||||
};
|
||||
|
||||
export type observerParam = {
|
||||
@@ -149,10 +179,10 @@ export type observerParam = {
|
||||
mediaInteractionCb: mediaInteractionCallback;
|
||||
blockClass: blockClass;
|
||||
ignoreClass: string;
|
||||
maskAllInputs: boolean;
|
||||
maskInputOptions: MaskInputOptions;
|
||||
inlineStylesheet: boolean;
|
||||
styleSheetRuleCb: styleSheetRuleCallback;
|
||||
mousemoveWait: number;
|
||||
sampling: SamplingStrategy;
|
||||
};
|
||||
|
||||
export type hooksParam = {
|
||||
|
||||
@@ -2288,6 +2288,694 @@ exports[`mask 1`] = `
|
||||
\\"id\\": 37
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 5,
|
||||
\\"text\\": \\"*\\",
|
||||
\\"isChecked\\": false,
|
||||
\\"id\\": 42
|
||||
}
|
||||
}
|
||||
]"
|
||||
`;
|
||||
|
||||
exports[`maskInputOptions 1`] = `
|
||||
"[
|
||||
{
|
||||
\\"type\\": 0,
|
||||
\\"data\\": {}
|
||||
},
|
||||
{
|
||||
\\"type\\": 1,
|
||||
\\"data\\": {}
|
||||
},
|
||||
{
|
||||
\\"type\\": 4,
|
||||
\\"data\\": {
|
||||
\\"href\\": \\"about:blank\\",
|
||||
\\"width\\": 1920,
|
||||
\\"height\\": 1080
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"data\\": {
|
||||
\\"node\\": {
|
||||
\\"type\\": 0,
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 1,
|
||||
\\"name\\": \\"html\\",
|
||||
\\"publicId\\": \\"\\",
|
||||
\\"systemId\\": \\"\\",
|
||||
\\"id\\": 2
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"html\\",
|
||||
\\"attributes\\": {
|
||||
\\"lang\\": \\"en\\"
|
||||
},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"head\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 5
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"meta\\",
|
||||
\\"attributes\\": {
|
||||
\\"charset\\": \\"UTF-8\\"
|
||||
},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 6
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 7
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"meta\\",
|
||||
\\"attributes\\": {
|
||||
\\"name\\": \\"viewport\\",
|
||||
\\"content\\": \\"width=device-width, initial-scale=1.0\\"
|
||||
},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 8
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 9
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"meta\\",
|
||||
\\"attributes\\": {
|
||||
\\"http-equiv\\": \\"X-UA-Compatible\\",
|
||||
\\"content\\": \\"ie=edge\\"
|
||||
},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 10
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 11
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"title\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"form fields\\",
|
||||
\\"id\\": 13
|
||||
}
|
||||
],
|
||||
\\"id\\": 12
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n\\",
|
||||
\\"id\\": 14
|
||||
}
|
||||
],
|
||||
\\"id\\": 4
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n\\\\n\\",
|
||||
\\"id\\": 15
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"body\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 17
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"form\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 19
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"label\\",
|
||||
\\"attributes\\": {
|
||||
\\"for\\": \\"text\\"
|
||||
},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 21
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"input\\",
|
||||
\\"attributes\\": {
|
||||
\\"type\\": \\"text\\"
|
||||
},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 22
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 23
|
||||
}
|
||||
],
|
||||
\\"id\\": 20
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 24
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"label\\",
|
||||
\\"attributes\\": {
|
||||
\\"for\\": \\"radio\\"
|
||||
},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 26
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"input\\",
|
||||
\\"attributes\\": {
|
||||
\\"type\\": \\"radio\\"
|
||||
},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 27
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 28
|
||||
}
|
||||
],
|
||||
\\"id\\": 25
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 29
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"label\\",
|
||||
\\"attributes\\": {
|
||||
\\"for\\": \\"checkbox\\"
|
||||
},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 31
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"input\\",
|
||||
\\"attributes\\": {
|
||||
\\"type\\": \\"checkbox\\"
|
||||
},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 32
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 33
|
||||
}
|
||||
],
|
||||
\\"id\\": 30
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 34
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"label\\",
|
||||
\\"attributes\\": {
|
||||
\\"for\\": \\"textarea\\"
|
||||
},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 36
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"textarea\\",
|
||||
\\"attributes\\": {
|
||||
\\"name\\": \\"\\",
|
||||
\\"id\\": \\"\\",
|
||||
\\"cols\\": \\"30\\",
|
||||
\\"rows\\": \\"10\\"
|
||||
},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 37
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 38
|
||||
}
|
||||
],
|
||||
\\"id\\": 35
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 39
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"label\\",
|
||||
\\"attributes\\": {
|
||||
\\"for\\": \\"select\\"
|
||||
},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 41
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"select\\",
|
||||
\\"attributes\\": {
|
||||
\\"name\\": \\"\\",
|
||||
\\"id\\": \\"\\",
|
||||
\\"value\\": \\"1\\"
|
||||
},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 43
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"option\\",
|
||||
\\"attributes\\": {
|
||||
\\"value\\": \\"1\\",
|
||||
\\"selected\\": true
|
||||
},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"1\\",
|
||||
\\"id\\": 45
|
||||
}
|
||||
],
|
||||
\\"id\\": 44
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 46
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"option\\",
|
||||
\\"attributes\\": {
|
||||
\\"value\\": \\"2\\"
|
||||
},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"2\\",
|
||||
\\"id\\": 48
|
||||
}
|
||||
],
|
||||
\\"id\\": 47
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 49
|
||||
}
|
||||
],
|
||||
\\"id\\": 42
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 50
|
||||
}
|
||||
],
|
||||
\\"id\\": 40
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 51
|
||||
}
|
||||
],
|
||||
\\"id\\": 18
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n\\\\n \\",
|
||||
\\"id\\": 52
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"script\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
|
||||
\\"id\\": 54
|
||||
}
|
||||
],
|
||||
\\"id\\": 53
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\\\n\\",
|
||||
\\"id\\": 55
|
||||
}
|
||||
],
|
||||
\\"id\\": 16
|
||||
}
|
||||
],
|
||||
\\"id\\": 3
|
||||
}
|
||||
],
|
||||
\\"id\\": 1
|
||||
},
|
||||
\\"initialOffset\\": {
|
||||
\\"left\\": 0,
|
||||
\\"top\\": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 2,
|
||||
\\"type\\": 5,
|
||||
\\"id\\": 22
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 5,
|
||||
\\"text\\": \\"t\\",
|
||||
\\"isChecked\\": false,
|
||||
\\"id\\": 22
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 5,
|
||||
\\"text\\": \\"te\\",
|
||||
\\"isChecked\\": false,
|
||||
\\"id\\": 22
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 5,
|
||||
\\"text\\": \\"tes\\",
|
||||
\\"isChecked\\": false,
|
||||
\\"id\\": 22
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 5,
|
||||
\\"text\\": \\"test\\",
|
||||
\\"isChecked\\": false,
|
||||
\\"id\\": 22
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 2,
|
||||
\\"type\\": 1,
|
||||
\\"id\\": 27
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 2,
|
||||
\\"type\\": 6,
|
||||
\\"id\\": 22
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 2,
|
||||
\\"type\\": 5,
|
||||
\\"id\\": 27
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 2,
|
||||
\\"type\\": 0,
|
||||
\\"id\\": 27
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 2,
|
||||
\\"type\\": 2,
|
||||
\\"id\\": 27
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 5,
|
||||
\\"text\\": \\"on\\",
|
||||
\\"isChecked\\": true,
|
||||
\\"id\\": 27
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 2,
|
||||
\\"type\\": 1,
|
||||
\\"id\\": 32
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 2,
|
||||
\\"type\\": 6,
|
||||
\\"id\\": 27
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 2,
|
||||
\\"type\\": 5,
|
||||
\\"id\\": 32
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 2,
|
||||
\\"type\\": 0,
|
||||
\\"id\\": 32
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 2,
|
||||
\\"type\\": 2,
|
||||
\\"id\\": 32
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 5,
|
||||
\\"text\\": \\"on\\",
|
||||
\\"isChecked\\": true,
|
||||
\\"id\\": 32
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 2,
|
||||
\\"type\\": 6,
|
||||
\\"id\\": 32
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 2,
|
||||
\\"type\\": 5,
|
||||
\\"id\\": 37
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 5,
|
||||
\\"text\\": \\"t\\",
|
||||
\\"isChecked\\": false,
|
||||
\\"id\\": 37
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 5,
|
||||
\\"text\\": \\"te\\",
|
||||
\\"isChecked\\": false,
|
||||
\\"id\\": 37
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 5,
|
||||
\\"text\\": \\"tex\\",
|
||||
\\"isChecked\\": false,
|
||||
\\"id\\": 37
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 5,
|
||||
\\"text\\": \\"text\\",
|
||||
\\"isChecked\\": false,
|
||||
\\"id\\": 37
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 5,
|
||||
\\"text\\": \\"texta\\",
|
||||
\\"isChecked\\": false,
|
||||
\\"id\\": 37
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 5,
|
||||
\\"text\\": \\"textar\\",
|
||||
\\"isChecked\\": false,
|
||||
\\"id\\": 37
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 5,
|
||||
\\"text\\": \\"textare\\",
|
||||
\\"isChecked\\": false,
|
||||
\\"id\\": 37
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 5,
|
||||
\\"text\\": \\"textarea\\",
|
||||
\\"isChecked\\": false,
|
||||
\\"id\\": 37
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 5,
|
||||
\\"text\\": \\"textarea \\",
|
||||
\\"isChecked\\": false,
|
||||
\\"id\\": 37
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 5,
|
||||
\\"text\\": \\"textarea t\\",
|
||||
\\"isChecked\\": false,
|
||||
\\"id\\": 37
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 5,
|
||||
\\"text\\": \\"textarea te\\",
|
||||
\\"isChecked\\": false,
|
||||
\\"id\\": 37
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 5,
|
||||
\\"text\\": \\"textarea tes\\",
|
||||
\\"isChecked\\": false,
|
||||
\\"id\\": 37
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 5,
|
||||
\\"text\\": \\"textarea test\\",
|
||||
\\"isChecked\\": false,
|
||||
\\"id\\": 37
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
|
||||
@@ -32,7 +32,8 @@ describe('record integration tests', function (this: ISuite) {
|
||||
console.log(event);
|
||||
window.snapshots.push(event);
|
||||
},
|
||||
maskAllInputs: ${options.maskAllInputs}
|
||||
maskAllInputs: ${options.maskAllInputs},
|
||||
maskInputOptions: ${JSON.stringify(options.maskAllInputs)}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
@@ -166,6 +167,28 @@ describe('record integration tests', function (this: ISuite) {
|
||||
assertSnapshot(snapshots, __filename, 'mask');
|
||||
});
|
||||
|
||||
it('can use maskInputOptions to configure which type of inputs should be masked', async () => {
|
||||
const page: puppeteer.Page = await this.browser.newPage();
|
||||
await page.goto('about:blank');
|
||||
await page.setContent(
|
||||
getHtml.call(this, 'form.html', {
|
||||
maskInputOptions: {
|
||||
text: false,
|
||||
textarea: false,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await page.type('input[type="text"]', 'test');
|
||||
await page.click('input[type="radio"]');
|
||||
await page.click('input[type="checkbox"]');
|
||||
await page.type('textarea', 'textarea test');
|
||||
await page.select('select', '1');
|
||||
|
||||
const snapshots = await page.evaluate('window.snapshots');
|
||||
assertSnapshot(snapshots, __filename, 'maskInputOptions');
|
||||
});
|
||||
|
||||
it('should not record blocked elements and its child nodes', async () => {
|
||||
const page: puppeteer.Page = await this.browser.newPage();
|
||||
await page.goto('about:blank');
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
],
|
||||
"arrow-parens": false,
|
||||
"only-arrow-functions": false,
|
||||
"max-line-length": false
|
||||
"max-line-length": false,
|
||||
"no-empty": false
|
||||
},
|
||||
"rulesDirectory": []
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user