Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/full-client/test/document.test.ts
1028 views
1
import { Helpers } from '@secret-agent/testing';
2
import { XPathResult } from '@secret-agent/interfaces/AwaitedDom';
3
import { ITestKoaServer } from '@secret-agent/testing/helpers';
4
import { FrameEnvironment, LocationStatus } from '@secret-agent/client';
5
import Dialog from '@secret-agent/client/lib/Dialog';
6
import { Handler } from '../index';
7
8
let koaServer: ITestKoaServer;
9
let handler: Handler;
10
beforeAll(async () => {
11
handler = new Handler();
12
Helpers.onClose(() => handler.close(), true);
13
koaServer = await Helpers.runKoaServer();
14
});
15
afterAll(Helpers.afterAll);
16
afterEach(Helpers.afterEach);
17
18
describe('basic Document tests', () => {
19
it('runs goto', async () => {
20
const agent = await openBrowser('/');
21
const url = await agent.document.location.host;
22
const html = await agent.document.body.outerHTML;
23
const linkText = await agent.document.querySelector('a').textContent;
24
expect(html).toMatch('Example Domain');
25
expect(linkText).toBe('More information...');
26
expect(url).toBe(koaServer.baseHost);
27
});
28
29
it('can iterate over multiple querySelectorElements', async () => {
30
koaServer.get('/page', ctx => {
31
ctx.body = `
32
<body>
33
<a href="#page1">Click Me</a>
34
<a href="#page2">Click Me</a>
35
<a href="#page3">Click Me</a>
36
</body>
37
`;
38
});
39
const agent = await openBrowser(`/page`);
40
const links = await agent.document.querySelectorAll('a');
41
42
for (const link of links) {
43
await agent.interact({ click: link, waitForElementVisible: link });
44
await agent.waitForLocation('change');
45
}
46
const finalUrl = await agent.url;
47
expect(finalUrl).toBe(`${koaServer.baseUrl}/page#page3`);
48
});
49
50
it('can refresh an element list', async () => {
51
koaServer.get('/refresh', ctx => {
52
ctx.body = `
53
<body>
54
<a href="javascript:void(0);" onclick="clicker()">Click Me</a>
55
56
<script>
57
function clicker() {
58
const elem = document.createElement('A');
59
document.querySelector('a').after(elem)
60
}
61
</script>
62
</body>
63
`;
64
});
65
const agent = await openBrowser(`/refresh`);
66
const links = agent.document.querySelectorAll('a');
67
const links1 = await links;
68
expect([...links1]).toHaveLength(1);
69
expect([...(await links1.values())]).toHaveLength(1);
70
await agent.click([...(await links1.values())][0]);
71
72
expect([...(await links)]).toHaveLength(2);
73
expect([...(await links1)]).toHaveLength(1);
74
expect([...(await links1.values())]).toHaveLength(1);
75
});
76
77
it('must call await on a NodeList to re-iterate', async () => {
78
koaServer.get('/reiterate', ctx => {
79
ctx.body = `
80
<body>
81
<ul>
82
<li>1</li>
83
<li>2</li>
84
<li>3</li>
85
</ul>
86
<a href="javascript:void(0)" onclick="clicker()">link</a>
87
<script>
88
function clicker() {
89
document.querySelector('ul').append('<li>4</li>');
90
}
91
</script>
92
</body>
93
`;
94
});
95
const agent = await openBrowser(`/reiterate`);
96
const ul = await agent.document.querySelector('ul');
97
const lis = await ul.getElementsByTagName('li');
98
expect(Array.from(lis)).toHaveLength(3);
99
100
const link = await agent.document.querySelector('a');
101
await agent.click(link);
102
try {
103
// should throw
104
for (const child of lis) {
105
expect(child).not.toBeTruthy();
106
}
107
} catch (error) {
108
// eslint-disable-next-line jest/no-try-expect
109
expect(String(error)).toMatch(/Please add an await/);
110
}
111
});
112
113
it('can re-await an element to refresh the underlying nodePointer ids', async () => {
114
koaServer.get('/refresh-element', ctx => {
115
ctx.body = `
116
<body>
117
<a id="first" href="javascript:void(0);" onclick="clicker()">Click Me</a>
118
119
<script>
120
function clicker() {
121
const elem = document.createElement('A');
122
elem.setAttribute('id', 'number2');
123
document.body.prepend(elem)
124
}
125
</script>
126
</body>
127
`;
128
});
129
const agent = await openBrowser('/refresh-element');
130
await agent.waitForPaintingStable();
131
const lastChild = await agent.document.body.firstElementChild;
132
expect(await lastChild.getAttribute('id')).toBe('first');
133
await agent.click(lastChild);
134
135
const refreshedChild = await lastChild;
136
expect(await refreshedChild.getAttribute('id')).toBe('first');
137
138
const updatedChild = await agent.document.body.firstElementChild;
139
expect(await updatedChild.getAttribute('id')).toBe('number2');
140
});
141
142
it('should be able to access a NodeList by index', async () => {
143
koaServer.get('/index', ctx => {
144
ctx.body = `
145
<body>
146
<ul>
147
<li>1</li>
148
<li>2</li>
149
<li>3</li>
150
</ul>
151
</body>
152
`;
153
});
154
const agent = await openBrowser(`/index`);
155
156
const element2Text = await agent.document.querySelectorAll('li')[1].textContent;
157
expect(element2Text).toBe('2');
158
});
159
160
it('can execute xpath', async () => {
161
koaServer.get('/xpath', ctx => {
162
ctx.body = `
163
<body>
164
<h2>Here I am</h2>
165
<ul>
166
<li>1</li>
167
<li>2</li>
168
<li>3</li>
169
</ul>
170
<h2>Also me</h2>
171
</body>
172
`;
173
});
174
const agent = await openBrowser(`/xpath`);
175
176
const headings = await agent.document.evaluate(
177
'/html/body//h2',
178
agent.document,
179
null,
180
XPathResult.ANY_TYPE,
181
null,
182
);
183
const nextHeading = headings.iterateNext();
184
expect(await nextHeading.textContent).toBe('Here I am');
185
const heading2 = headings.iterateNext();
186
expect(await heading2.textContent).toBe('Also me');
187
});
188
189
it('can wait for xpath elements', async () => {
190
koaServer.get('/xpath-wait', ctx => {
191
ctx.body = `
192
<body>
193
<h2 style="display: none">Here I am not</h2>
194
<h2>Also me</h2>
195
<script>
196
setTimeout(() => {
197
const h2 = document.querySelector('h2');
198
h2.style.display = '';
199
h2.textContent = 'Here I am'
200
}, 500)
201
</script>
202
</body>
203
`;
204
});
205
const agent = await openBrowser(`/xpath-wait`);
206
207
const headings = agent.document.evaluate(
208
'/html/body//h2',
209
agent.document,
210
null,
211
XPathResult.FIRST_ORDERED_NODE_TYPE,
212
null,
213
);
214
await agent.waitForElement(headings.singleNodeValue, { waitForVisible: true });
215
await expect(headings.singleNodeValue.textContent).resolves.toBe('Here I am');
216
});
217
218
it("returns null for elements that don't exist", async () => {
219
const agent = await openBrowser(`/`);
220
const { document } = agent;
221
const element = await document.querySelector('#this-element-aint-there');
222
expect(element).toBe(null);
223
});
224
225
it("returns null for xpath elements that don't exist", async () => {
226
const agent = await openBrowser(`/`);
227
const { document } = agent;
228
const element = await document.evaluate(
229
'//div[@id="this-element-aint-there"]',
230
agent.document,
231
null,
232
XPathResult.FIRST_ORDERED_NODE_TYPE,
233
null,
234
).singleNodeValue;
235
expect(element).toBe(null);
236
});
237
238
it('returns null while iterating nodes', async () => {
239
koaServer.get('/xpath-nodes', ctx => {
240
ctx.body = `
241
<body>
242
<div id="div1">Div 1</div>
243
<div id="div2">Div 2</div>
244
<div id="div3">Div 3</div>
245
</body>
246
`;
247
});
248
const agent = await openBrowser(`/xpath-nodes`);
249
const { document } = agent;
250
const iterator = await document.evaluate(
251
'//div',
252
agent.document,
253
null,
254
XPathResult.ORDERED_NODE_ITERATOR_TYPE,
255
null,
256
);
257
await expect(iterator.iterateNext()).resolves.toBeTruthy();
258
await expect(iterator.iterateNext()).resolves.toBeTruthy();
259
await expect(iterator.iterateNext()).resolves.toBeTruthy();
260
await expect(iterator.iterateNext()).resolves.toBe(null);
261
});
262
263
it('can determine if an element is visible', async () => {
264
koaServer.get('/isVisible', ctx => {
265
ctx.body = `
266
<body>
267
<div id="elem-1">Element 1</div>
268
<div style="visibility: hidden">
269
<div id="elem-2">Visibility none</div>
270
</div>
271
<div style="visibility: visible">
272
<div id="elem-3">Visibility visible</div>
273
</div>
274
<div style="display:none" id="elem-4">No display</div>
275
<div style="opacity: 0" id="elem-5">Opacity 0</div>
276
<div style="opacity: 0.1" id="elem-6">Opacity 0.1</div>
277
<div style="position: relative; width: 100px">
278
<div id="elem-7" style="position: absolute; left: 0; width: 20px; top; 0; height:20px;">Showing Element</div>
279
<div id="elem-8" style="position: absolute; left: 20px; width: 20px; top; 0; height:20px;">Showing Element</div>
280
<div style="position: absolute; left: 21px; width: 10px; top; 0; height:20px;">Overlay Element</div>
281
</div>
282
</body>
283
`;
284
});
285
const agent = await openBrowser(`/isVisible`);
286
const { document } = agent;
287
await expect(
288
agent.getComputedVisibility(document.querySelector('#elem-1')),
289
).resolves.toMatchObject({
290
isVisible: true,
291
});
292
// visibility
293
await expect(
294
agent.getComputedVisibility(document.querySelector('#elem-2')),
295
).resolves.toMatchObject({
296
isVisible: false,
297
hasCssVisibility: false,
298
});
299
await expect(
300
agent.getComputedVisibility(document.querySelector('#elem-3')),
301
).resolves.toMatchObject({
302
isVisible: true,
303
});
304
// layout
305
await expect(
306
agent.getComputedVisibility(document.querySelector('#elem-4')),
307
).resolves.toMatchObject({
308
isVisible: false,
309
hasDimensions: false,
310
});
311
// opacity
312
await expect(
313
agent.getComputedVisibility(document.querySelector('#elem-5')),
314
).resolves.toMatchObject({
315
isVisible: false,
316
hasCssOpacity: false,
317
});
318
await expect(
319
agent.getComputedVisibility(document.querySelector('#elem-6')),
320
).resolves.toMatchObject({
321
isVisible: true,
322
});
323
// overlay
324
await expect(
325
agent.getComputedVisibility(document.querySelector('#elem-7')),
326
).resolves.toMatchObject({
327
isVisible: true,
328
isUnobstructedByOtherElements: true,
329
});
330
await expect(
331
agent.getComputedVisibility(document.querySelector('#elem-8')),
332
).resolves.toMatchObject({
333
isVisible: false,
334
isUnobstructedByOtherElements: false,
335
});
336
});
337
338
it('can get computed styles', async () => {
339
koaServer.get('/computedStyle', ctx => {
340
ctx.body = `<body>
341
<div style="opacity: 0" id="elem-1">Opacity 0</div>
342
<div style="opacity: 0.1" id="elem-2">Opacity 0.1</div>
343
</body>`;
344
});
345
const agent = await openBrowser(`/computedStyle`);
346
const { document } = agent;
347
const elem1Style = agent.activeTab.getComputedStyle(document.querySelector('#elem-1'));
348
const opacity = await elem1Style.getPropertyValue('opacity');
349
expect(opacity).toBe('0');
350
351
const elem2Style = agent.activeTab.getComputedStyle(document.querySelector('#elem-2'));
352
const opacity2 = await elem2Style.getPropertyValue('opacity');
353
expect(opacity2).toBe('0.1');
354
});
355
356
it('can get a data url of a canvas', async () => {
357
koaServer.get('/canvas', ctx => {
358
ctx.body = `
359
<body>
360
<label>This is a canvas</label>
361
<canvas id="canvas"></canvas>
362
<script>
363
const c = document.getElementById("canvas");
364
const ctx = c.getContext("2d");
365
ctx.moveTo(0, 0);
366
ctx.lineTo(200, 100);
367
ctx.stroke();
368
</script>
369
</body>
370
`;
371
});
372
const agent = await openBrowser(`/canvas`);
373
const { document } = agent;
374
const dataUrl = await document.querySelector('canvas').toDataURL();
375
expect(dataUrl).toMatch(/data:image\/png.+/);
376
});
377
378
it('can dismiss dialogs', async () => {
379
koaServer.get('/dialog', ctx => {
380
ctx.body = `
381
<body>
382
<h1>Dialog page</h1>
383
<script type="text/javascript">
384
setTimeout(() => confirm('Do you want to do this'), 500);
385
</script>
386
</body>
387
`;
388
});
389
const agent = await openBrowser(`/dialog`);
390
const { document } = agent;
391
const dialogPromise = new Promise<Dialog>(resolve => agent.activeTab.on('dialog', resolve));
392
await expect(dialogPromise).resolves.toBeTruthy();
393
const dialog = await dialogPromise;
394
await (await dialog).dismiss(true);
395
// test that we don't hang here
396
await expect(document.querySelector('h1').textContent).resolves.toBeTruthy();
397
});
398
399
it('can get a dataset attribute', async () => {
400
koaServer.get('/dataset', ctx => {
401
ctx.body = `
402
<body>
403
<div id="main" data-id="1" data-name="name">This is a div</div>
404
</body>
405
`;
406
});
407
const agent = await openBrowser(`/dataset`);
408
const { document } = agent;
409
const dataset = await document.querySelector('#main').dataset;
410
expect(dataset).toEqual({ id: '1', name: 'name' });
411
});
412
413
it('allows you to run shadow dom query selectors', async () => {
414
koaServer.get('/shadow', ctx => {
415
ctx.body = `
416
<body>
417
<header id="header"></header>
418
<script>
419
const header = document.getElementById('header');
420
const shadowRoot = header.attachShadow({ mode: 'closed' });
421
shadowRoot.innerHTML = \`<div>
422
<h1>Hello Shadow DOM</h1>
423
<ul>
424
<li>1</li>
425
<li>2</li>
426
<li>3</li>
427
</ul>
428
</div>\`;
429
</script>
430
</body>
431
`;
432
});
433
const agent = await openBrowser(`/shadow`);
434
const { document } = agent;
435
const shadowRoot = document.querySelector('#header').shadowRoot;
436
const h1Text = await shadowRoot.querySelector('h1').textContent;
437
expect(h1Text).toBe('Hello Shadow DOM');
438
439
const lis = await shadowRoot.querySelectorAll('li').length;
440
expect(lis).toBe(3);
441
});
442
443
it('allows selectors in iframes', async () => {
444
koaServer.get('/iframePage', ctx => {
445
ctx.body = `
446
<body>
447
<h1>Iframe Page</h1>
448
<iframe src="/subFrame"></iframe>
449
</body>
450
`;
451
});
452
koaServer.get('/subFrame', ctx => {
453
ctx.body = `
454
<body>
455
<h1>Subframe Page</h1>
456
<div>This is content inside the frame</div>
457
</body>
458
`;
459
});
460
461
const agent = await openBrowser(`/iframePage`);
462
463
const outerH1 = await agent.document.querySelector('h1').textContent;
464
expect(outerH1).toBe('Iframe Page');
465
466
let innerFrame: FrameEnvironment;
467
for (const frame of await agent.activeTab.frameEnvironments) {
468
await frame.waitForLoad(LocationStatus.DomContentLoaded);
469
const url = await frame.url;
470
if (url.endsWith('/subFrame')) {
471
innerFrame = frame;
472
break;
473
}
474
}
475
476
const innerH1 = await innerFrame.document.querySelector('h1').textContent;
477
expect(innerH1).toBe('Subframe Page');
478
479
await agent.close();
480
});
481
482
it('can find the Frame object for an iframe', async () => {
483
koaServer.get('/iframePage2', ctx => {
484
ctx.body = `
485
<body>
486
<h1>Iframe Page</h1>
487
<iframe src="/subFrame1" name="frame1"></iframe>
488
<iframe src="/subFrame2" id="frame2"></iframe>
489
</body>
490
`;
491
});
492
koaServer.get('/subFrame1', ctx => {
493
ctx.body = `<body><h1>Subframe Page 1</h1></body>`;
494
});
495
koaServer.get('/subFrame2', ctx => {
496
ctx.body = `<body><h1>Subframe Page 2</h1>
497
<iframe src="/subFrame1" id="nested"></iframe>
498
</body>`;
499
});
500
501
const agent = await openBrowser(`/iframePage2`);
502
503
const frameElement2 = agent.document.querySelector('#frame2');
504
await agent.waitForElement(frameElement2);
505
const frame2Env = await agent.activeTab.getFrameEnvironment(frameElement2);
506
507
expect(frame2Env).toBeTruthy();
508
await frame2Env.waitForLoad(LocationStatus.AllContentLoaded);
509
await expect(frame2Env.document.querySelector('h1').textContent).resolves.toBe(
510
'Subframe Page 2',
511
);
512
513
const nestedFrameElement = frame2Env.document.querySelector('iframe');
514
const nestedFrameEnv = await frame2Env.getFrameEnvironment(nestedFrameElement);
515
expect(nestedFrameEnv).toBeTruthy();
516
517
await nestedFrameEnv.waitForLoad(LocationStatus.AllContentLoaded);
518
await expect(nestedFrameEnv.document.body.innerHTML).resolves.toBe('<h1>Subframe Page 1</h1>');
519
520
await agent.close();
521
}, 130e3);
522
});
523
524
async function openBrowser(path: string) {
525
const agent = await handler.createAgent();
526
Helpers.needsClosing.push(agent);
527
await agent.goto(`${koaServer.baseUrl}${path}`);
528
await agent.waitForPaintingStable();
529
return agent;
530
}
531
532