Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/core/test/domReplay.test.ts
1029 views
1
import Core, { Session } from '@secret-agent/core';
2
import { Helpers } from '@secret-agent/testing';
3
import { InteractionCommand } from '@secret-agent/interfaces/IInteractions';
4
import Puppet from '@secret-agent/puppet';
5
import Log from '@secret-agent/commons/Logger';
6
import { ITestKoaServer } from '@secret-agent/testing/helpers';
7
import ConnectionToClient from '../server/ConnectionToClient';
8
import InjectedScripts from '../lib/InjectedScripts';
9
10
const { log } = Log(module);
11
12
const getContentScript = `(() => {
13
let retVal = '';
14
if (document.doctype)
15
retVal = new XMLSerializer().serializeToString(document.doctype);
16
if (document.documentElement)
17
retVal += document.documentElement.outerHTML;
18
return retVal;
19
})()`;
20
21
let koaServer: ITestKoaServer;
22
let connectionToClient: ConnectionToClient;
23
beforeAll(async () => {
24
connectionToClient = Core.addConnection();
25
Helpers.onClose(() => connectionToClient.disconnect(), true);
26
koaServer = await Helpers.runKoaServer();
27
koaServer.get('/empty', ctx => {
28
ctx.body = `<html></html>`;
29
});
30
});
31
afterAll(Helpers.afterAll);
32
afterEach(Helpers.afterEach);
33
34
describe('basic Dom Replay tests', () => {
35
it('basic replay test', async () => {
36
koaServer.get('/replay-test', ctx => {
37
ctx.body = `<body>
38
<div>
39
<h1>This is the starting point</h1>
40
<ul>
41
<li>1</li>
42
</ul>
43
<a href="#" onclick="clicky()">Clickeroo</a>
44
</div>
45
<div id="div2"></div>
46
<script>
47
let clicks = 0;
48
const child = document.createElement('li');
49
const child2 = document.createElement('li');
50
const child3 = document.createElement('li');
51
const parent = document.querySelector('ul');
52
53
function clicky(){
54
clicks += 1;
55
56
if (clicks === 1) {
57
child.textContent = 'Another one ' + parent.children.length;
58
parent.append(child, child2, child3);
59
}
60
if (clicks === 2) {
61
parent.removeChild(child2);
62
}
63
if (clicks === 3) {
64
parent.append(child);
65
}
66
if (clicks === 4) {
67
document.querySelector('#div2').setAttribute('data', "{ data: true }");
68
document.querySelector('#div2').setAttribute('trial', '1');
69
}
70
if (clicks === 5) {
71
child.textContent = 'Li 2';
72
}
73
if (clicks === 6){
74
// test inserting a bunch at once
75
const div2 = document.createElement('div');
76
div2.innerHTML = "<p>This is para 1</p><br/><p>This is para 2</p>";
77
document.body.insertBefore(div2, document.querySelector('script'))
78
}
79
return false;
80
}
81
</script>
82
</body>`;
83
});
84
const meta = await connectionToClient.createSession();
85
const tab = Session.getTab(meta);
86
await tab.goto(`${koaServer.baseUrl}/replay-test`);
87
await tab.waitForLoad('DomContentLoaded');
88
const session = tab.session;
89
90
const mirrorChrome = new Puppet(session.browserEngine);
91
await mirrorChrome.start();
92
Helpers.onClose(() => mirrorChrome.close());
93
94
const context = await mirrorChrome.newContext(session.plugins, log.createChild(module));
95
// context.on('devtools-message', console.log);
96
const mirrorPage = await context.newPage();
97
const debug = false;
98
if (debug) {
99
// eslint-disable-next-line no-console
100
mirrorPage.on('page-error', console.log);
101
// eslint-disable-next-line no-console
102
mirrorPage.on('console', console.log);
103
}
104
await Promise.all([
105
mirrorPage.navigate(`${koaServer.baseUrl}/empty`),
106
mirrorPage.mainFrame.waitOn('frame-lifecycle', event => event.name === 'load'),
107
]);
108
const sourceHtml = await tab.puppetPage.mainFrame.evaluate(getContentScript, false);
109
110
const mainPageChanges = await tab.getMainFrameDomChanges();
111
await InjectedScripts.restoreDom(mirrorPage, mainPageChanges);
112
113
const mirrorHtml = await mirrorPage.mainFrame.evaluate(getContentScript, false);
114
if (mirrorHtml !== sourceHtml) {
115
// eslint-disable-next-line no-console
116
console.log('Mirror Page mismatch', mainPageChanges);
117
}
118
expect(mirrorHtml).toBe(sourceHtml);
119
120
let lastCommandId = tab.lastCommandId;
121
for (let i = 1; i <= 6; i += 1) {
122
await tab.interact([
123
{
124
command: InteractionCommand.click,
125
mousePosition: ['window', 'document', ['querySelector', 'a']],
126
},
127
]);
128
129
await new Promise(resolve => setTimeout(resolve, 100));
130
131
const pageChangesByFrame = await tab.getMainFrameDomChanges(lastCommandId);
132
lastCommandId = tab.lastCommandId;
133
await InjectedScripts.restoreDom(mirrorPage, pageChangesByFrame);
134
// replay happens on animation tick now
135
await new Promise(setImmediate);
136
137
const sourceHtmlNext = await tab.puppetPage.mainFrame.evaluate(getContentScript, false);
138
const mirrorHtmlNext = await mirrorPage.mainFrame.evaluate(getContentScript, false);
139
expect(mirrorHtmlNext).toBe(sourceHtmlNext);
140
}
141
}, 45e3);
142
143
it('can replay data attributes', async () => {
144
koaServer.get('/data-attr', ctx => {
145
ctx.body = `<body>
146
<div>
147
<h1>This is the starting point</h1>
148
<a href="#" onclick="clicker()">click</a>
149
<span id="tester" class="a-declarative" data-action="open-sheet:style_name">test</span>
150
</div>
151
<script>
152
153
function clicker(){
154
document.querySelector('div').setAttribute('data-sheet:style_name',"{}");
155
return false;
156
}
157
</script>
158
</body>`;
159
});
160
const meta = await connectionToClient.createSession();
161
const tab = Session.getTab(meta);
162
await tab.goto(`${koaServer.baseUrl}/data-attr`);
163
await tab.waitForLoad('DomContentLoaded');
164
const session = tab.session;
165
166
const mirrorChrome = new Puppet(session.browserEngine);
167
await mirrorChrome.start();
168
Helpers.onClose(() => mirrorChrome.close());
169
170
const context = await mirrorChrome.newContext(session.plugins, log.createChild(module));
171
// context.on('devtools-message', console.log);
172
// context.on('devtools-message', console.log);
173
const mirrorPage = await context.newPage();
174
const debug = true;
175
if (debug) {
176
// eslint-disable-next-line no-console
177
mirrorPage.on('page-error', console.log);
178
// eslint-disable-next-line no-console
179
mirrorPage.on('console', console.log);
180
}
181
await Promise.all([
182
mirrorPage.navigate(`${koaServer.baseUrl}/empty`),
183
mirrorPage.mainFrame.waitOn('frame-lifecycle', event => event.name === 'load'),
184
]);
185
const sourceHtml = await tab.puppetPage.mainFrame.evaluate(getContentScript, false);
186
187
const mainPageChanges = await tab.getMainFrameDomChanges();
188
await InjectedScripts.restoreDom(mirrorPage, mainPageChanges);
189
190
const mirrorHtml = await mirrorPage.mainFrame.evaluate(getContentScript, false);
191
if (mirrorHtml !== sourceHtml) {
192
// eslint-disable-next-line no-console
193
console.log('Mirror Page mismatch', mainPageChanges);
194
}
195
expect(mirrorHtml).toBe(sourceHtml);
196
197
const lastCommandId = tab.lastCommandId;
198
await tab.interact([
199
{
200
command: InteractionCommand.click,
201
mousePosition: ['window', 'document', ['querySelector', 'a']],
202
},
203
]);
204
205
await new Promise(resolve => setTimeout(resolve, 200));
206
207
const domChanges = await tab.getMainFrameDomChanges(lastCommandId);
208
expect(domChanges).toHaveLength(2);
209
await InjectedScripts.restoreDom(mirrorPage, domChanges);
210
// replay happens on animation tick now
211
await new Promise(setImmediate);
212
213
const sourceHtmlNext = await tab.puppetPage.mainFrame.evaluate(getContentScript, false);
214
const mirrorHtmlNext = await mirrorPage.mainFrame.evaluate(getContentScript, false);
215
expect(mirrorHtmlNext).toBe(sourceHtmlNext);
216
}, 45e3);
217
218
it('should support multiple tabs', async () => {
219
koaServer.get('/tab1', ctx => {
220
ctx.body = `<body>
221
<div>
222
<h1>This is the starting point</h1>
223
<ul>
224
<li>1</li>
225
</ul>
226
<a href="/tab2" target="_blank">Clickeroo</a>
227
</div>
228
</body>`;
229
});
230
koaServer.get('/tab2', ctx => {
231
ctx.body = `
232
<html>
233
<head>
234
<script type="text/javascript">
235
(() => {
236
console.log('Ran!');
237
})();
238
</script>
239
<meta charset="utf-8">
240
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
241
<title>Dom Replay Test</title>
242
</head>
243
<body>
244
<div>
245
<h1>This is tab 2</h1>
246
</div>
247
</body>
248
</html>`;
249
});
250
const meta = await connectionToClient.createSession();
251
const tab = Session.getTab(meta);
252
await tab.goto(`${koaServer.baseUrl}/tab1`);
253
await tab.waitForLoad('DomContentLoaded');
254
const session = tab.session;
255
256
const mirrorChrome = new Puppet(session.browserEngine);
257
await mirrorChrome.start();
258
Helpers.onClose(() => mirrorChrome.close());
259
260
const mirrorContext = await mirrorChrome.newContext(session.plugins, log.createChild(module));
261
const mirrorPage = await mirrorContext.newPage();
262
263
await Promise.all([
264
mirrorPage.navigate(`${koaServer.baseUrl}/empty`),
265
mirrorPage.mainFrame.waitOn('frame-lifecycle', event => event.name === 'load'),
266
]);
267
const sourceHtml = await tab.puppetPage.mainFrame.evaluate(getContentScript, false);
268
269
{
270
const changes = await tab.getMainFrameDomChanges();
271
expect(changes).toHaveLength(21);
272
await InjectedScripts.restoreDom(mirrorPage, changes);
273
// replay happens on animation tick now
274
await new Promise(setImmediate);
275
}
276
277
const mirrorHtml = await mirrorPage.mainFrame.evaluate(getContentScript, false);
278
expect(mirrorHtml).toBe(sourceHtml);
279
280
await tab.interact([
281
{
282
command: InteractionCommand.click,
283
mousePosition: ['window', 'document', ['querySelector', 'a']],
284
},
285
]);
286
const newTab = await tab.waitForNewTab();
287
await newTab.waitForLoad('PaintingStable');
288
const newTabHtml = await newTab.puppetPage.mainFrame.evaluate(getContentScript, false);
289
const pageChanges = await newTab.getMainFrameDomChanges();
290
expect(pageChanges.length).toBeGreaterThan(10);
291
292
const newTabMirrorPage = await mirrorContext.newPage();
293
await Promise.all([
294
newTabMirrorPage.navigate(`${koaServer.baseUrl}/empty`),
295
newTabMirrorPage.mainFrame.waitOn('frame-lifecycle', event => event.name === 'load'),
296
]);
297
298
await InjectedScripts.restoreDom(newTabMirrorPage, pageChanges);
299
300
const mirrorNewTabHtml = await newTabMirrorPage.mainFrame.evaluate(getContentScript, false);
301
expect(mirrorNewTabHtml).toBe(newTabHtml);
302
}, 45e3);
303
});
304
305