* 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.
288 lines
9.8 KiB
TypeScript
288 lines
9.8 KiB
TypeScript
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import * as puppeteer from 'puppeteer';
|
|
import { assertSnapshot, launchPuppeteer } from './utils';
|
|
import { Suite } from 'mocha';
|
|
import { expect } from 'chai';
|
|
import { recordOptions, eventWithTime } from '../src/types';
|
|
|
|
interface ISuite extends Suite {
|
|
code: string;
|
|
browser: puppeteer.Browser;
|
|
}
|
|
|
|
describe('record integration tests', function (this: ISuite) {
|
|
this.timeout(10_000);
|
|
|
|
const getHtml = (
|
|
fileName: string,
|
|
options: recordOptions<eventWithTime> = {},
|
|
): string => {
|
|
const filePath = path.resolve(__dirname, `./html/${fileName}`);
|
|
const html = fs.readFileSync(filePath, 'utf8');
|
|
return html.replace(
|
|
'</body>',
|
|
`
|
|
<script>
|
|
${this.code}
|
|
window.Date.now = () => new Date(Date.UTC(2018, 10, 15, 8)).valueOf();
|
|
window.snapshots = [];
|
|
rrweb.record({
|
|
emit: event => {
|
|
console.log(event);
|
|
window.snapshots.push(event);
|
|
},
|
|
maskAllInputs: ${options.maskAllInputs},
|
|
maskInputOptions: ${JSON.stringify(options.maskAllInputs)}
|
|
});
|
|
</script>
|
|
</body>
|
|
`,
|
|
);
|
|
};
|
|
|
|
before(async () => {
|
|
this.browser = await launchPuppeteer();
|
|
|
|
const bundlePath = path.resolve(__dirname, '../dist/rrweb.min.js');
|
|
this.code = fs.readFileSync(bundlePath, 'utf8');
|
|
});
|
|
|
|
after(async () => {
|
|
await this.browser.close();
|
|
});
|
|
|
|
it('can record form interactions', async () => {
|
|
const page: puppeteer.Page = await this.browser.newPage();
|
|
await page.goto('about:blank');
|
|
await page.setContent(getHtml.call(this, 'form.html'));
|
|
|
|
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, 'form');
|
|
});
|
|
|
|
it('can record childList mutations', async () => {
|
|
const page: puppeteer.Page = await this.browser.newPage();
|
|
await page.goto('about:blank');
|
|
await page.setContent(getHtml.call(this, 'mutation-observer.html'));
|
|
|
|
await page.evaluate(() => {
|
|
const li = document.createElement('li');
|
|
const ul = document.querySelector('ul') as HTMLUListElement;
|
|
ul.appendChild(li);
|
|
document.body.removeChild(ul);
|
|
const p = document.querySelector('p') as HTMLParagraphElement;
|
|
p.appendChild(document.createElement('span'));
|
|
});
|
|
|
|
const snapshots = await page.evaluate('window.snapshots');
|
|
assertSnapshot(snapshots, __filename, 'child-list');
|
|
});
|
|
|
|
it('can record character data muatations', async () => {
|
|
const page: puppeteer.Page = await this.browser.newPage();
|
|
await page.goto('about:blank');
|
|
await page.setContent(getHtml.call(this, 'mutation-observer.html'));
|
|
|
|
await page.evaluate(() => {
|
|
const li = document.createElement('li');
|
|
const ul = document.querySelector('ul') as HTMLUListElement;
|
|
ul.appendChild(li);
|
|
li.innerText = 'new list item';
|
|
li.innerText = 'new list item edit';
|
|
document.body.removeChild(ul);
|
|
const p = document.querySelector('p') as HTMLParagraphElement;
|
|
p.innerText = 'mutated';
|
|
});
|
|
|
|
const snapshots = await page.evaluate('window.snapshots');
|
|
assertSnapshot(snapshots, __filename, 'character-data');
|
|
});
|
|
|
|
it('can record attribute mutation', async () => {
|
|
const page: puppeteer.Page = await this.browser.newPage();
|
|
await page.goto('about:blank');
|
|
await page.setContent(getHtml.call(this, 'mutation-observer.html'));
|
|
|
|
await page.evaluate(() => {
|
|
const li = document.createElement('li');
|
|
const ul = document.querySelector('ul') as HTMLUListElement;
|
|
ul.appendChild(li);
|
|
li.setAttribute('foo', 'bar');
|
|
document.body.removeChild(ul);
|
|
document.body.setAttribute('test', 'true');
|
|
});
|
|
|
|
const snapshots = await page.evaluate('window.snapshots');
|
|
assertSnapshot(snapshots, __filename, 'attributes');
|
|
});
|
|
|
|
it('can record node mutations', async () => {
|
|
const page: puppeteer.Page = await this.browser.newPage();
|
|
await page.goto('about:blank');
|
|
await page.setContent(getHtml.call(this, 'select2.html'), {
|
|
waitUntil: 'networkidle0',
|
|
});
|
|
|
|
// toggle the select box
|
|
await page.click('.select2-container');
|
|
await page.click('.select2-container');
|
|
|
|
const snapshots = await page.evaluate('window.snapshots');
|
|
assertSnapshot(snapshots, __filename, 'select2');
|
|
});
|
|
|
|
it('should not record input events on ignored elements', async () => {
|
|
const page: puppeteer.Page = await this.browser.newPage();
|
|
await page.goto('about:blank');
|
|
await page.setContent(getHtml.call(this, 'ignore.html'));
|
|
|
|
await page.type('input[type="password"]', 'password');
|
|
await page.type('.rr-ignore', 'secret');
|
|
|
|
const snapshots = await page.evaluate('window.snapshots');
|
|
assertSnapshot(snapshots, __filename, 'ignore');
|
|
});
|
|
|
|
it('should not record input values if maskAllInputs is enabled', async () => {
|
|
const page: puppeteer.Page = await this.browser.newPage();
|
|
await page.goto('about:blank');
|
|
await page.setContent(
|
|
getHtml.call(this, 'form.html', { maskAllInputs: true }),
|
|
);
|
|
|
|
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, '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');
|
|
await page.setContent(getHtml.call(this, 'block.html'));
|
|
|
|
await page.type('input', 'should not be record');
|
|
await page.evaluate(`document.getElementById('text').innerText = '1'`);
|
|
await page.click('#text');
|
|
|
|
const snapshots = await page.evaluate('window.snapshots');
|
|
assertSnapshot(snapshots, __filename, 'block');
|
|
});
|
|
|
|
it('should record DOM node movement 1', async () => {
|
|
const page: puppeteer.Page = await this.browser.newPage();
|
|
await page.goto('about:blank');
|
|
await page.setContent(getHtml.call(this, 'move-node.html'));
|
|
|
|
await page.evaluate(() => {
|
|
const div = document.querySelector('div')!;
|
|
const p = document.querySelector('p')!;
|
|
const span = document.querySelector('span')!;
|
|
document.body.removeChild(span);
|
|
p.appendChild(span);
|
|
p.removeChild(span);
|
|
div.appendChild(span);
|
|
});
|
|
const snapshots = await page.evaluate('window.snapshots');
|
|
assertSnapshot(snapshots, __filename, 'move-node-1');
|
|
});
|
|
|
|
it('should record DOM node movement 2', async () => {
|
|
const page: puppeteer.Page = await this.browser.newPage();
|
|
await page.goto('about:blank');
|
|
await page.setContent(getHtml.call(this, 'move-node.html'));
|
|
|
|
await page.evaluate(() => {
|
|
const div = document.createElement('div');
|
|
const span = document.querySelector('span')!;
|
|
document.body.appendChild(div);
|
|
div.appendChild(span);
|
|
});
|
|
const snapshots = await page.evaluate('window.snapshots');
|
|
assertSnapshot(snapshots, __filename, 'move-node-2');
|
|
});
|
|
|
|
it('should record dynamic CSS changes', async () => {
|
|
const page: puppeteer.Page = await this.browser.newPage();
|
|
await page.goto('about:blank');
|
|
await page.setContent(getHtml.call(this, 'react-styled-components.html'));
|
|
await page.click('.toggle');
|
|
const snapshots = await page.evaluate('window.snapshots');
|
|
assertSnapshot(snapshots, __filename, 'react-styled-components');
|
|
});
|
|
|
|
it('will serialize node before record', async () => {
|
|
const page: puppeteer.Page = await this.browser.newPage();
|
|
await page.goto('about:blank');
|
|
await page.setContent(getHtml.call(this, 'mutation-observer.html'));
|
|
|
|
await page.evaluate(() => {
|
|
const ul = document.querySelector('ul') as HTMLUListElement;
|
|
let count = 3;
|
|
while (count > 0) {
|
|
count--;
|
|
const li = document.createElement('li');
|
|
ul.appendChild(li);
|
|
}
|
|
});
|
|
|
|
const snapshots = await page.evaluate('window.snapshots');
|
|
assertSnapshot(snapshots, __filename, 'serialize-before-record');
|
|
});
|
|
|
|
it('will defer missing next node mutation', async () => {
|
|
const page: puppeteer.Page = await this.browser.newPage();
|
|
await page.goto('about:blank');
|
|
await page.setContent(getHtml.call(this, 'shuffle.html'));
|
|
|
|
const text = await page.evaluate(() => {
|
|
const els = Array.prototype.slice.call(document.querySelectorAll('li'));
|
|
const parent = document.querySelector('ul')!;
|
|
parent.removeChild(els[3]);
|
|
parent.removeChild(els[2]);
|
|
parent.removeChild(els[1]);
|
|
parent.removeChild(els[0]);
|
|
parent.insertBefore(els[3], els[4]);
|
|
parent.insertBefore(els[2], els[4]);
|
|
parent.insertBefore(els[1], els[4]);
|
|
parent.insertBefore(els[0], els[4]);
|
|
return parent.innerText;
|
|
});
|
|
|
|
expect(text).to.equal('4\n3\n2\n1\n5');
|
|
});
|
|
});
|