Buffer modifications to virtual stylesheets (#618)
* Fix sheet insertion Restore skip duration Use virtualStyleRulesMap to re-populate stylesheet on Flush event Clear virtualStyleRulesMap after flush applied * Support rule deletion in virtual processing * Simply restoreNodeSheet with early aborts * Encountered a bug where firstFullSnapshot was played twice because timer was immediately started and reached the snapshot before the setTimeout returned * Ignoring a FullSnapshot needs to be a one-time only thing, as otherwise we'll ignore it after scrubbing (restarting play head at a particular time). This is a problem if mutations have altered the player state, and we try to replay those mutations, so we e.g. try to remove an element that has already been removed because we haven't reset the FullSnapshot state * Some `npm run typings` related fixups * add basic html snapshot functionality * move restoreNodeSheet to it's own module * Refactor virtual style rules to buffer changes. Only applies changes on flush. `virtualStyleRulesMap` now works with strings instead of CSSRules. CSSRules can only be via made `.insertRule` on CSSStyleSheet in most browsers. And `new CSSStyleSheet()` only works in Chrome currently. * remove unused code * move VirtualStyleRules from CSSRule to string in tests * correct paths for tests * naming * create and restore style snapshots for virtual nodes * update replayer snapshot * move storeCSSRules to virtual-styles.ts * try/catch access to .sheet in case of access errors * clean up tests Co-authored-by: Vladimir Milenko <vladimir.milenko@uber.com> Co-authored-by: Eoghan Murray <eoghan@getthere.ie>
This commit is contained in:
187
test/__snapshots__/replayer.test.ts.snap
Normal file
187
test/__snapshots__/replayer.test.ts.snap
Normal file
@@ -0,0 +1,187 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`style-sheet-remove-events-play-at-2500 1`] = `
|
||||
"file-frame-1
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv=\\"Content-Type\\" content=\\"text/html; charset=UTF-8\\">
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<div class=\\"replayer-wrapper\\">
|
||||
<div class=\\"replayer-mouse\\"></div>
|
||||
<canvas class=\\"replayer-mouse-tail\\" width=\\"1000\\" height=\\"800\\" style=
|
||||
\\"display: inherit;\\"></canvas><iframe sandbox=\\"allow-same-origin\\" scrolling=
|
||||
\\"no\\" width=\\"1000\\" height=\\"800\\" style=
|
||||
\\"display: inherit; pointer-events: none;\\"></iframe>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
file-frame-2
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv=\\"Content-Type\\" content=\\"text/html; charset=UTF-8\\">
|
||||
<link rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"file-cid-0\\">
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
file-cid-0
|
||||
@charset \\"utf-8\\";
|
||||
|
||||
.rr-block { background: rgb(204, 204, 204); }
|
||||
|
||||
noscript { display: none !important; }
|
||||
|
||||
html.rrweb-paused * { animation-play-state: paused !important; }
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`style-sheet-rule-events-pause-at-2500 1`] = `
|
||||
"file-frame-4
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv=\\"Content-Type\\" content=\\"text/html; charset=UTF-8\\">
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<div class=\\"replayer-wrapper\\">
|
||||
<div class=\\"replayer-mouse\\"></div>
|
||||
<canvas class=\\"replayer-mouse-tail\\" width=\\"1000\\" height=\\"800\\" style=
|
||||
\\"display: inherit;\\"></canvas><iframe sandbox=\\"allow-same-origin\\" scrolling=
|
||||
\\"no\\" width=\\"1000\\" height=\\"800\\" style=
|
||||
\\"display: inherit; pointer-events: none;\\"></iframe>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
file-frame-5
|
||||
<!DOCTYPE html>
|
||||
<html lang=\\"en\\">
|
||||
<head>
|
||||
<meta http-equiv=\\"Content-Type\\" content=\\"text/html; charset=UTF-8\\">
|
||||
<link rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"file-cid-0\\">
|
||||
<link rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"file-cid-1\\">
|
||||
<link rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"file-cid-2\\">
|
||||
<link rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"file-cid-3\\">
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<a class=\\"css-added-at-1000-deleted-at-2500\\">string</a>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
file-cid-0
|
||||
@charset \\"utf-8\\";
|
||||
|
||||
.rr-block { background: rgb(204, 204, 204); }
|
||||
|
||||
noscript { display: none !important; }
|
||||
|
||||
html.rrweb-paused * { animation-play-state: paused !important; }
|
||||
|
||||
|
||||
file-cid-1
|
||||
@charset \\"utf-8\\";
|
||||
|
||||
.c011xx { padding: 1.3125rem; flex: 0 0 auto; width: 100%; }
|
||||
|
||||
|
||||
file-cid-2
|
||||
@charset \\"utf-8\\";
|
||||
|
||||
.c01x { opacity: 1; transform: translateX(0px); }
|
||||
|
||||
.css-added-at-400 { border: 1px solid blue; }
|
||||
|
||||
|
||||
file-cid-3
|
||||
@charset \\"utf-8\\";
|
||||
|
||||
.css-1uxxxx3 { position: fixed; top: 0px; right: 0px; left: 4rem; z-index: 15; flex-shrink: 0; height: 0.25rem; overflow: hidden; background-color: rgb(17, 171, 209); }
|
||||
|
||||
.css-1c9xxxx { height: 0.25rem; background-color: rgb(190, 232, 242); opacity: 0; transition: opacity 0.5s ease 0s; }
|
||||
|
||||
.css-lsxxx { padding-left: 4rem; }
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`style-sheet-rule-events-play-at-1500 1`] = `
|
||||
"file-frame-4
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv=\\"Content-Type\\" content=\\"text/html; charset=UTF-8\\">
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<div class=\\"replayer-wrapper\\">
|
||||
<div class=\\"replayer-mouse\\"></div>
|
||||
<canvas class=\\"replayer-mouse-tail\\" width=\\"1000\\" height=\\"800\\" style=
|
||||
\\"display: inherit;\\"></canvas><iframe sandbox=\\"allow-same-origin\\" scrolling=
|
||||
\\"no\\" width=\\"1000\\" height=\\"800\\" style=
|
||||
\\"display: inherit; pointer-events: none;\\"></iframe>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
file-frame-5
|
||||
<!DOCTYPE html>
|
||||
<html lang=\\"en\\">
|
||||
<head>
|
||||
<meta http-equiv=\\"Content-Type\\" content=\\"text/html; charset=UTF-8\\">
|
||||
<link rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"file-cid-0\\">
|
||||
<link rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"file-cid-1\\">
|
||||
<link rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"file-cid-2\\">
|
||||
<link rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"file-cid-3\\">
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<a class=\\"css-added-at-1000-deleted-at-2500\\">string</a>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
file-cid-0
|
||||
@charset \\"utf-8\\";
|
||||
|
||||
.rr-block { background: rgb(204, 204, 204); }
|
||||
|
||||
noscript { display: none !important; }
|
||||
|
||||
html.rrweb-paused * { animation-play-state: paused !important; }
|
||||
|
||||
|
||||
file-cid-1
|
||||
@charset \\"utf-8\\";
|
||||
|
||||
.c011xx { padding: 1.3125rem; flex: 0 0 auto; width: 100%; }
|
||||
|
||||
|
||||
file-cid-2
|
||||
@charset \\"utf-8\\";
|
||||
|
||||
.c01x { opacity: 1; transform: translateX(0px); }
|
||||
|
||||
.css-added-at-400 { border: 1px solid blue; }
|
||||
|
||||
|
||||
file-cid-3
|
||||
@charset \\"utf-8\\";
|
||||
|
||||
.css-1uxxxx3 { position: fixed; top: 0px; right: 0px; left: 4rem; z-index: 15; flex-shrink: 0; height: 0.25rem; overflow: hidden; background-color: rgb(17, 171, 209); }
|
||||
|
||||
.css-1c9xxxx { height: 0.25rem; background-color: rgb(190, 232, 242); opacity: 0; transition: opacity 0.5s ease 0s; }
|
||||
|
||||
.css-added-at-1000-deleted-at-2500 { display: flex; flex-direction: column; min-width: 60rem; min-height: 100vh; color: blue; }
|
||||
|
||||
.css-lsxxx { padding-left: 4rem; }
|
||||
"
|
||||
`;
|
||||
@@ -46,8 +46,7 @@ const events: eventWithTime[] = [
|
||||
type: 2,
|
||||
tagName: 'style',
|
||||
attributes: {
|
||||
'data-jss': '',
|
||||
'data-meta': 'sk, Unthemed, Static',
|
||||
'data-meta': 'from full-snapshot, gets rule added at 500',
|
||||
},
|
||||
childNodes: [
|
||||
{
|
||||
@@ -79,7 +78,23 @@ const events: eventWithTime[] = [
|
||||
type: 2,
|
||||
tagName: 'body',
|
||||
attributes: {},
|
||||
childNodes: [],
|
||||
childNodes: [
|
||||
{
|
||||
id: 108,
|
||||
type: 2,
|
||||
tagName: 'a',
|
||||
attributes: {
|
||||
class: 'css-added-at-1000-deleted-at-2500',
|
||||
},
|
||||
childNodes: [
|
||||
{
|
||||
id: 109,
|
||||
type: 3,
|
||||
textContent: 'string',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -90,6 +105,21 @@ const events: eventWithTime[] = [
|
||||
type: EventType.FullSnapshot,
|
||||
timestamp: now + 100,
|
||||
},
|
||||
// mutation that adds style rule to existing stylesheet
|
||||
{
|
||||
data: {
|
||||
id: 101,
|
||||
adds: [
|
||||
{
|
||||
rule: '.css-added-at-400{border: 1px solid blue;}',
|
||||
index: 1,
|
||||
},
|
||||
],
|
||||
source: IncrementalSource.StyleSheetRule,
|
||||
},
|
||||
type: EventType.IncrementalSnapshot,
|
||||
timestamp: now + 400,
|
||||
},
|
||||
// mutation that adds stylesheet
|
||||
{
|
||||
data: {
|
||||
@@ -132,7 +162,7 @@ const events: eventWithTime[] = [
|
||||
adds: [
|
||||
{
|
||||
rule:
|
||||
'.css-1fbxx79{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;min-width:60rem;min-height:100vh;}',
|
||||
'.css-added-at-1000-deleted-at-2500{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;min-width:60rem;min-height:100vh;color:blue;}',
|
||||
index: 2,
|
||||
},
|
||||
],
|
||||
@@ -141,6 +171,19 @@ const events: eventWithTime[] = [
|
||||
type: EventType.IncrementalSnapshot,
|
||||
timestamp: now + 1000,
|
||||
},
|
||||
{
|
||||
data: {
|
||||
id: 105,
|
||||
removes: [
|
||||
{
|
||||
index: 2,
|
||||
},
|
||||
],
|
||||
source: IncrementalSource.StyleSheetRule,
|
||||
},
|
||||
type: EventType.IncrementalSnapshot,
|
||||
timestamp: now + 2500,
|
||||
},
|
||||
];
|
||||
|
||||
export default events;
|
||||
|
||||
113
test/replay/virtual-styles.test.ts
Normal file
113
test/replay/virtual-styles.test.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { expect } from 'chai';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import {
|
||||
applyVirtualStyleRulesToNode,
|
||||
StyleRuleType,
|
||||
VirtualStyleRules,
|
||||
} from '../../src/replay/virtual-styles';
|
||||
|
||||
describe('virtual styles', () => {
|
||||
describe('applyVirtualStyleRulesToNode', () => {
|
||||
it('should insert rule at index 0 in empty sheet', () => {
|
||||
const dom = new JSDOM(`
|
||||
<style></style>
|
||||
`);
|
||||
const styleEl = dom.window.document.getElementsByTagName('style')[0];
|
||||
|
||||
const cssText = '.added-rule {border: 1px solid yellow;}';
|
||||
|
||||
const virtualStyleRules: VirtualStyleRules = [
|
||||
{ cssText, index: 0, type: StyleRuleType.Insert },
|
||||
];
|
||||
applyVirtualStyleRulesToNode(virtualStyleRules, styleEl);
|
||||
|
||||
expect(styleEl.sheet?.cssRules?.length).to.equal(1);
|
||||
expect(styleEl.sheet?.cssRules[0].cssText).to.equal(cssText);
|
||||
});
|
||||
|
||||
it('should insert rule at index 0 and keep exsisting rules', () => {
|
||||
const dom = new JSDOM(`
|
||||
<style>
|
||||
a {color: blue}
|
||||
div {color: black}
|
||||
</style>
|
||||
`);
|
||||
const styleEl = dom.window.document.getElementsByTagName('style')[0];
|
||||
|
||||
const cssText = '.added-rule {border: 1px solid yellow;}';
|
||||
const virtualStyleRules: VirtualStyleRules = [
|
||||
{ cssText, index: 0, type: StyleRuleType.Insert },
|
||||
];
|
||||
applyVirtualStyleRulesToNode(virtualStyleRules, styleEl);
|
||||
|
||||
expect(styleEl.sheet?.cssRules?.length).to.equal(3);
|
||||
expect(styleEl.sheet?.cssRules[0].cssText).to.equal(cssText);
|
||||
});
|
||||
|
||||
it('should delete rule at index 1', () => {
|
||||
const dom = new JSDOM(`
|
||||
<style>
|
||||
a {color: blue;}
|
||||
div {color: black;}
|
||||
</style>
|
||||
`);
|
||||
const styleEl = dom.window.document.getElementsByTagName('style')[0];
|
||||
|
||||
const virtualStyleRules: VirtualStyleRules = [
|
||||
{ index: 0, type: StyleRuleType.Remove },
|
||||
];
|
||||
applyVirtualStyleRulesToNode(virtualStyleRules, styleEl);
|
||||
|
||||
expect(styleEl.sheet?.cssRules?.length).to.equal(1);
|
||||
expect(styleEl.sheet?.cssRules[0].cssText).to.equal(
|
||||
'div {color: black;}',
|
||||
);
|
||||
});
|
||||
|
||||
it('should restore a snapshot by inserting missing rules', () => {
|
||||
const dom = new JSDOM(`
|
||||
<style>
|
||||
a {color: blue;}
|
||||
.deleted-rule {color: pink;}
|
||||
div {color: black;}
|
||||
</style>
|
||||
`);
|
||||
const styleEl = dom.window.document.getElementsByTagName('style')[0];
|
||||
|
||||
const virtualStyleRules: VirtualStyleRules = [
|
||||
{
|
||||
cssTexts: ['a {color: blue;}', 'div {color: black;}'],
|
||||
type: StyleRuleType.Snapshot,
|
||||
},
|
||||
];
|
||||
applyVirtualStyleRulesToNode(virtualStyleRules, styleEl);
|
||||
|
||||
expect(styleEl.sheet?.cssRules?.length).to.equal(2);
|
||||
});
|
||||
|
||||
it('should restore a snapshot by fixing order of rules', () => {
|
||||
const dom = new JSDOM(`
|
||||
<style>
|
||||
div {color: black;}
|
||||
a {color: blue;}
|
||||
</style>
|
||||
`);
|
||||
const styleEl = dom.window.document.getElementsByTagName('style')[0];
|
||||
|
||||
const cssTexts = ['a {color: blue;}', 'div {color: black;}'];
|
||||
|
||||
const virtualStyleRules: VirtualStyleRules = [
|
||||
{
|
||||
cssTexts,
|
||||
type: StyleRuleType.Snapshot,
|
||||
},
|
||||
];
|
||||
applyVirtualStyleRulesToNode(virtualStyleRules, styleEl);
|
||||
|
||||
expect(styleEl.sheet?.cssRules?.length).to.equal(2);
|
||||
expect(
|
||||
Array.from(styleEl.sheet?.cssRules || []).map((rule) => rule.cssText),
|
||||
).to.have.ordered.members(cssTexts);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -6,6 +6,7 @@ import * as puppeteer from 'puppeteer';
|
||||
import { expect } from 'chai';
|
||||
import { Suite } from 'mocha';
|
||||
import {
|
||||
assertDomSnapshot,
|
||||
launchPuppeteer,
|
||||
sampleEvents as events,
|
||||
sampleStyleSheetRemoveEvents as stylesheetRemoveEvents,
|
||||
@@ -146,11 +147,35 @@ describe('replayer', function (this: ISuite) {
|
||||
replayer.play(1500);
|
||||
replayer['timer']['actions'].length;
|
||||
`);
|
||||
|
||||
expect(actionLength).to.equal(
|
||||
styleSheetRuleEvents.filter(
|
||||
(e) => e.timestamp - styleSheetRuleEvents[0].timestamp >= 1500,
|
||||
).length,
|
||||
);
|
||||
|
||||
await assertDomSnapshot(
|
||||
this.page,
|
||||
__filename,
|
||||
'style-sheet-rule-events-play-at-1500',
|
||||
);
|
||||
});
|
||||
|
||||
it('should apply fast forwarded StyleSheetRules that where added', async () => {
|
||||
await this.page.evaluate(
|
||||
`events = ${JSON.stringify(styleSheetRuleEvents)}`,
|
||||
);
|
||||
const result = await this.page.evaluate(`
|
||||
const { Replayer } = rrweb;
|
||||
const replayer = new Replayer(events);
|
||||
replayer.pause(1500);
|
||||
const rules = [...replayer.iframe.contentDocument.styleSheets].map(
|
||||
(sheet) => [...sheet.rules],
|
||||
).flat();
|
||||
rules.some((x) => x.selectorText === '.css-added-at-1000-deleted-at-2500');
|
||||
`);
|
||||
|
||||
expect(result).to.equal(true);
|
||||
});
|
||||
|
||||
it('can handle removing style elements', async () => {
|
||||
@@ -168,6 +193,47 @@ describe('replayer', function (this: ISuite) {
|
||||
(e) => e.timestamp - stylesheetRemoveEvents[0].timestamp >= 2500,
|
||||
).length,
|
||||
);
|
||||
|
||||
await assertDomSnapshot(
|
||||
this.page,
|
||||
__filename,
|
||||
'style-sheet-remove-events-play-at-2500',
|
||||
);
|
||||
});
|
||||
|
||||
it('can fast forward past StyleSheetRule deletion on virtual elements', async () => {
|
||||
await this.page.evaluate(
|
||||
`events = ${JSON.stringify(styleSheetRuleEvents)}`,
|
||||
);
|
||||
const actionLength = await this.page.evaluate(`
|
||||
const { Replayer } = rrweb;
|
||||
const replayer = new Replayer(events);
|
||||
replayer.play(2500);
|
||||
replayer['timer']['actions'].length;
|
||||
`);
|
||||
|
||||
await assertDomSnapshot(
|
||||
this.page,
|
||||
__filename,
|
||||
'style-sheet-rule-events-play-at-2500',
|
||||
);
|
||||
});
|
||||
|
||||
it('should delete fast forwarded StyleSheetRules that where removed', async () => {
|
||||
await this.page.evaluate(
|
||||
`events = ${JSON.stringify(styleSheetRuleEvents)}`,
|
||||
);
|
||||
const result = await this.page.evaluate(`
|
||||
const { Replayer } = rrweb;
|
||||
const replayer = new Replayer(events);
|
||||
replayer.pause(3000);
|
||||
const rules = [...replayer.iframe.contentDocument.styleSheets].map(
|
||||
(sheet) => [...sheet.rules],
|
||||
).flat();
|
||||
rules.some((x) => x.selectorText === '.css-added-at-1000-deleted-at-2500');
|
||||
`);
|
||||
|
||||
expect(result).to.equal(false);
|
||||
});
|
||||
|
||||
it('can stream events in live mode', async () => {
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
MouseInteractions,
|
||||
} from '../src/types';
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import { TidyDoc } from 'node-libtidy';
|
||||
|
||||
export async function launchPuppeteer() {
|
||||
return await puppeteer.launch({
|
||||
@@ -61,7 +62,7 @@ function stringifySnapshots(snapshots: eventWithTime[]): string {
|
||||
s.data.href = 'about:blank';
|
||||
}
|
||||
// FIXME: travis coordinates seems different with my laptop
|
||||
const coordinatesReg = /(bottom|top|left|right|width|height): \d+(\.\d+)?px/g
|
||||
const coordinatesReg = /(bottom|top|left|right|width|height): \d+(\.\d+)?px/g;
|
||||
if (
|
||||
s.type === EventType.IncrementalSnapshot &&
|
||||
s.data.source === IncrementalSource.MouseInteraction
|
||||
@@ -78,7 +79,10 @@ function stringifySnapshots(snapshots: eventWithTime[]): string {
|
||||
'style' in a.attributes &&
|
||||
coordinatesReg.test(a.attributes.style!)
|
||||
) {
|
||||
a.attributes.style = a.attributes.style!.replace(coordinatesReg, '$1: Npx');
|
||||
a.attributes.style = a.attributes.style!.replace(
|
||||
coordinatesReg,
|
||||
'$1: Npx',
|
||||
);
|
||||
}
|
||||
});
|
||||
s.data.adds.forEach((add) => {
|
||||
@@ -88,7 +92,10 @@ function stringifySnapshots(snapshots: eventWithTime[]): string {
|
||||
typeof add.node.attributes.style === 'string' &&
|
||||
coordinatesReg.test(add.node.attributes.style)
|
||||
) {
|
||||
add.node.attributes.style = add.node.attributes.style.replace(coordinatesReg, '$1: Npx');
|
||||
add.node.attributes.style = add.node.attributes.style.replace(
|
||||
coordinatesReg,
|
||||
'$1: Npx',
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -100,6 +107,49 @@ function stringifySnapshots(snapshots: eventWithTime[]): string {
|
||||
);
|
||||
}
|
||||
|
||||
function stringifyDomSnapshot(mhtml: string): string {
|
||||
const { Parser } = require('fast-mhtml');
|
||||
const resources: string[] = [];
|
||||
const p = new Parser({
|
||||
rewriteFn: (filename: string): string => {
|
||||
const index = resources.indexOf(filename);
|
||||
const prefix = /^\w+/.exec(filename);
|
||||
if (index !== -1) {
|
||||
return `file-${prefix}-${index}`;
|
||||
} else {
|
||||
return `file-${prefix}-${resources.push(filename) - 1}`;
|
||||
}
|
||||
},
|
||||
});
|
||||
const result = p
|
||||
.parse(mhtml) // parse file
|
||||
.rewrite() // rewrite all links
|
||||
.spit(); // return all contents
|
||||
|
||||
const newResult: { filename: string; content: string }[] = result.map(
|
||||
(asset: { filename: string; content: string }) => {
|
||||
let { filename, content } = asset;
|
||||
let res: string | undefined;
|
||||
if (filename.includes('frame')) {
|
||||
const doc = TidyDoc();
|
||||
doc.options = {
|
||||
indent: 'auto',
|
||||
indent_spaces: 2,
|
||||
wrap: 80,
|
||||
markup: true,
|
||||
quote_marks: true,
|
||||
break_before_br: true,
|
||||
tidy_mark: false,
|
||||
};
|
||||
doc.parseBufferSync(Buffer.from(content));
|
||||
res = doc.saveBufferSync().toString();
|
||||
}
|
||||
return { filename, content: res || content };
|
||||
},
|
||||
);
|
||||
return newResult.map((asset) => Object.values(asset).join('\n')).join('\n\n');
|
||||
}
|
||||
|
||||
export function assertSnapshot(
|
||||
snapshots: eventWithTime[],
|
||||
filename: string,
|
||||
@@ -109,6 +159,20 @@ export function assertSnapshot(
|
||||
assert(result.pass, result.pass ? '' : result.report());
|
||||
}
|
||||
|
||||
export async function assertDomSnapshot(
|
||||
page: puppeteer.Page,
|
||||
filename: string,
|
||||
name: string,
|
||||
) {
|
||||
const cdp = await page.target().createCDPSession();
|
||||
const { data } = await cdp.send('Page.captureSnapshot', {
|
||||
format: 'mhtml',
|
||||
});
|
||||
|
||||
const result = matchSnapshot(stringifyDomSnapshot(data), filename, name);
|
||||
assert(result.pass, result.pass ? '' : result.report());
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
export const sampleEvents: eventWithTime[] = [
|
||||
{
|
||||
@@ -241,23 +305,23 @@ export const sampleStyleSheetRemoveEvents: eventWithTime[] = [
|
||||
childNodes: [
|
||||
{
|
||||
type: 2,
|
||||
tagName: "style",
|
||||
tagName: 'style',
|
||||
attributes: {
|
||||
"data-jss": "",
|
||||
"data-meta": "OverlayDrawer",
|
||||
_cssText: ".OverlayDrawer-modal-187 { }.OverlayDrawer-paper-188 { width: 100%; }@media (min-width: 48em) {\n .OverlayDrawer-paper-188 { width: 38rem; }\n}@media (min-width: 48em) {\n}@media (min-width: 48em) {\n}"
|
||||
'data-jss': '',
|
||||
'data-meta': 'OverlayDrawer',
|
||||
_cssText:
|
||||
'.OverlayDrawer-modal-187 { }.OverlayDrawer-paper-188 { width: 100%; }@media (min-width: 48em) {\n .OverlayDrawer-paper-188 { width: 38rem; }\n}@media (min-width: 48em) {\n}@media (min-width: 48em) {\n}',
|
||||
},
|
||||
childNodes: [
|
||||
{
|
||||
type: 3,
|
||||
textContent: "\n",
|
||||
textContent: '\n',
|
||||
isStyle: true,
|
||||
id: 5
|
||||
id: 5,
|
||||
},
|
||||
],
|
||||
id: 4
|
||||
id: 4,
|
||||
},
|
||||
|
||||
],
|
||||
id: 3,
|
||||
},
|
||||
@@ -290,10 +354,10 @@ export const sampleStyleSheetRemoveEvents: eventWithTime[] = [
|
||||
removes: [
|
||||
{
|
||||
parentId: 3,
|
||||
id: 4
|
||||
}
|
||||
id: 4,
|
||||
},
|
||||
],
|
||||
adds: []
|
||||
adds: [],
|
||||
},
|
||||
timestamp: now + 2000,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user