Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/full-client/test/emulate.test.ts
1028 views
1
import * as Fs from 'fs';
2
import { Helpers } from '@secret-agent/testing';
3
import { GlobalPool } from '@secret-agent/core';
4
import { ITestKoaServer } from '@secret-agent/testing/helpers';
5
import Resolvable from '@secret-agent/commons/Resolvable';
6
import Viewports from '@secret-agent/default-browser-emulator/lib/Viewports';
7
import { Handler } from '../index';
8
9
let koaServer: ITestKoaServer;
10
let handler: Handler;
11
beforeAll(async () => {
12
handler = new Handler();
13
Helpers.onClose(() => handler.close(), true);
14
koaServer = await Helpers.runKoaServer(true);
15
GlobalPool.maxConcurrentAgentsCount = 3;
16
});
17
afterAll(Helpers.afterAll);
18
afterEach(Helpers.afterEach);
19
20
describe('basic Emulator tests', () => {
21
it('should be able to set a timezoneId', async () => {
22
const agent = await handler.createAgent({
23
timezoneId: 'America/Los_Angeles',
24
});
25
Helpers.needsClosing.push(agent);
26
await new Promise(resolve => setTimeout(resolve, 1e3));
27
28
await agent.goto(`${koaServer.baseUrl}`);
29
30
const formatted = 'Sat Nov 19 2016 10:12:34 GMT-0800 (Pacific Standard Time)';
31
const timezoneOffset = await agent.getJsValue('new Date(1479579154987).toString()');
32
expect(timezoneOffset).toBe(formatted);
33
});
34
35
it('should affect accept-language header', async () => {
36
const agent = await handler.createAgent({ locale: 'en-GB,en' });
37
Helpers.needsClosing.push(agent);
38
39
let acceptLanguage = '';
40
koaServer.get('/headers', ctx => {
41
acceptLanguage = ctx.get('accept-language');
42
ctx.body = '<html></html>';
43
});
44
45
await agent.goto(`${koaServer.baseUrl}/headers`);
46
expect(acceptLanguage).toBe('en-GB,en;q=0.9');
47
});
48
49
it('should affect navigator.language', async () => {
50
const agent = await handler.createAgent({ locale: 'fr-CH,fr-CA' });
51
Helpers.needsClosing.push(agent);
52
53
await agent.goto(`${koaServer.baseUrl}`);
54
const result = await agent.getJsValue(`navigator.language`);
55
expect(result).toBe('fr-CH');
56
57
const result2 = await agent.getJsValue(`navigator.languages`);
58
expect(result2).toStrictEqual(['fr-CH', 'fr-CA']);
59
});
60
61
it('should format number', async () => {
62
{
63
const agent = await handler.createAgent({ locale: 'en-US,en;q=0.9' });
64
Helpers.needsClosing.push(agent);
65
66
await agent.goto(`${koaServer.baseUrl}`);
67
const result = await agent.getJsValue(`(1000000.5).toLocaleString()`);
68
expect(result).toBe('1,000,000.5');
69
}
70
{
71
const agent = await handler.createAgent({ locale: 'fr-CH' });
72
Helpers.needsClosing.push(agent);
73
74
await agent.goto(`${koaServer.baseUrl}`);
75
76
const result = await agent.getJsValue(`(1000000.5).toLocaleString()`);
77
expect(result).toBe('1 000 000,5');
78
}
79
});
80
81
it('should format date', async () => {
82
{
83
const agent = await handler.createAgent({
84
locale: 'en-US',
85
timezoneId: 'America/Los_Angeles',
86
});
87
Helpers.needsClosing.push(agent);
88
89
await agent.goto(`${koaServer.baseUrl}`);
90
91
const formatted = 'Sat Nov 19 2016 10:12:34 GMT-0800 (Pacific Standard Time)';
92
93
const result = await agent.getJsValue(`new Date(1479579154987).toString()`);
94
expect(result).toBe(formatted);
95
}
96
{
97
const agent = await handler.createAgent({
98
locale: 'de-DE',
99
timezoneId: 'Europe/Berlin',
100
});
101
Helpers.needsClosing.push(agent);
102
103
await agent.goto(`${koaServer.baseUrl}`);
104
105
const formatted = 'Sat Nov 19 2016 19:12:34 GMT+0100 (Mitteleuropäische Normalzeit)';
106
const result = await agent.getJsValue(`new Date(1479579154987).toString()`);
107
expect(result).toBe(formatted);
108
}
109
});
110
});
111
112
describe('setScreensize', () => {
113
it('should set the proper viewport size', async () => {
114
const windowFraming = {
115
screenGapLeft: 0,
116
screenGapTop: 0,
117
screenGapRight: 0,
118
screenGapBottom: 0,
119
frameBorderWidth: 0,
120
frameBorderHeight: 0,
121
};
122
const viewport = Viewports.getDefault(windowFraming, windowFraming);
123
const agent = await handler.createAgent({
124
viewport,
125
});
126
Helpers.needsClosing.push(agent);
127
128
await agent.goto(`${koaServer.baseUrl}`);
129
const screenWidth = await agent.getJsValue('screen.width');
130
expect(screenWidth).toBe(viewport.screenWidth);
131
const screenHeight = await agent.getJsValue('screen.height');
132
expect(screenHeight).toBe(viewport.screenHeight);
133
134
const screenX = await agent.getJsValue('screenX');
135
expect(screenX).toBe(viewport.positionX);
136
const screenY = await agent.getJsValue('screenY');
137
expect(screenY).toBe(viewport.positionY);
138
139
const innerWidth = await agent.getJsValue('innerWidth');
140
expect(innerWidth).toBe(viewport.width);
141
const innerHeight = await agent.getJsValue('innerHeight');
142
expect(innerHeight).toBe(viewport.height);
143
});
144
145
it('should support Media Queries', async () => {
146
const agent = await handler.createAgent({
147
viewport: {
148
width: 200,
149
height: 200,
150
screenWidth: 200,
151
screenHeight: 200,
152
positionY: 0,
153
positionX: 0,
154
},
155
});
156
Helpers.needsClosing.push(agent);
157
158
expect(await agent.getJsValue(`matchMedia('(min-device-width: 100px)').matches`)).toBe(true);
159
expect(await agent.getJsValue(`matchMedia('(min-device-width: 300px)').matches`)).toBe(false);
160
expect(await agent.getJsValue(`matchMedia('(max-device-width: 100px)').matches`)).toBe(false);
161
expect(await agent.getJsValue(`matchMedia('(max-device-width: 300px)').matches`)).toBe(true);
162
expect(await agent.getJsValue(`matchMedia('(device-width: 500px)').matches`)).toBe(false);
163
expect(await agent.getJsValue(`matchMedia('(device-width: 200px)').matches`)).toBe(true);
164
165
expect(await agent.getJsValue(`matchMedia('(min-device-height: 100px)').matches`)).toBe(true);
166
expect(await agent.getJsValue(`matchMedia('(min-device-height: 300px)').matches`)).toBe(false);
167
expect(await agent.getJsValue(`matchMedia('(max-device-height: 100px)').matches`)).toBe(false);
168
expect(await agent.getJsValue(`matchMedia('(max-device-height: 300px)').matches`)).toBe(true);
169
expect(await agent.getJsValue(`matchMedia('(device-height: 500px)').matches`)).toBe(false);
170
expect(await agent.getJsValue(`matchMedia('(device-height: 200px)').matches`)).toBe(true);
171
});
172
});
173
174
describe('mouse', () => {
175
it('should emulate the hover media feature', async () => {
176
const agent = await handler.createAgent();
177
Helpers.needsClosing.push(agent);
178
179
expect(await agent.getJsValue(`matchMedia('(hover: none)').matches`)).toBe(false);
180
expect(await agent.getJsValue(`matchMedia('(hover: hover)').matches`)).toBe(true);
181
expect(await agent.getJsValue(`matchMedia('(any-hover: none)').matches`)).toBe(false);
182
expect(await agent.getJsValue(`matchMedia('(any-hover: hover)').matches`)).toBe(true);
183
expect(await agent.getJsValue(`matchMedia('(pointer: coarse)').matches`)).toBe(false);
184
expect(await agent.getJsValue(`matchMedia('(pointer: fine)').matches`)).toBe(true);
185
expect(await agent.getJsValue(`matchMedia('(any-pointer: coarse)').matches`)).toBe(false);
186
expect(await agent.getJsValue(`matchMedia('(any-pointer: fine)').matches`)).toBe(true);
187
});
188
});
189
190
describe('geolocation', () => {
191
it('should be able to set a geolocation', async () => {
192
const agent = await handler.createAgent({ geolocation: { longitude: 10, latitude: 10 } });
193
Helpers.needsClosing.push(agent);
194
await agent.goto(koaServer.baseUrl);
195
196
const geolocation = await agent.getJsValue(`new Promise(resolve => navigator.geolocation.getCurrentPosition(position => {
197
resolve({ latitude: position.coords.latitude, longitude: position.coords.longitude });
198
}))`);
199
expect(geolocation).toEqual({
200
latitude: 10,
201
longitude: 10,
202
});
203
});
204
});
205
206
describe('user agent and platform', () => {
207
const propsToGet = `appVersion, platform, userAgent, deviceMemory`.split(',').map(x => x.trim());
208
209
it('should be able to configure a userAgent', async () => {
210
const userAgent =
211
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4472.124 Safari/537.36';
212
const agent = await handler.createAgent({
213
userAgent,
214
});
215
Helpers.needsClosing.push(agent);
216
217
const agentMeta = await agent.meta;
218
expect(agentMeta.userAgentString).toBe(userAgent);
219
});
220
221
it('should be able to configure a userAgent with a range', async () => {
222
const agent = await handler.createAgent({
223
userAgent: '~ chrome >= 88 && chrome < 89',
224
});
225
Helpers.needsClosing.push(agent);
226
227
const agentMeta = await agent.meta;
228
const chromeMatch = agentMeta.userAgentString.match(/Chrome\/(\d+)/);
229
expect(chromeMatch).toBeTruthy();
230
const version = Number(chromeMatch[1]);
231
expect(version).toBe(88);
232
});
233
234
it('should be able to configure a userAgent with a wildcard', async () => {
235
const agent = await handler.createAgent({
236
userAgent: '~ chrome = 88.x',
237
});
238
Helpers.needsClosing.push(agent);
239
240
const agentMeta = await agent.meta;
241
const chromeMatch = agentMeta.userAgentString.match(/Chrome\/(\d+)/);
242
expect(chromeMatch).toBeTruthy();
243
const version = Number(chromeMatch[1]);
244
expect(version).toBe(88);
245
});
246
247
it('should add user agent and platform to window & frames', async () => {
248
const agent = await handler.createAgent();
249
Helpers.needsClosing.push(agent);
250
251
const agentMeta = await agent.meta;
252
253
const requestUserAgentStrings: string[] = [];
254
255
koaServer.get('/agent-test', ctx => {
256
requestUserAgentStrings.push(ctx.get('user-agent'));
257
ctx.body = `<html lang="en">
258
<h1>Agent Test</h1>
259
<iframe src="/frame"></iframe>
260
</html>`;
261
});
262
263
koaServer.get('/frame', ctx => {
264
requestUserAgentStrings.push(ctx.get('user-agent'));
265
ctx.body = `<html><body>
266
<script>
267
const { ${propsToGet.join(',')} } = navigator;
268
fetch('${koaServer.baseUrl}/frame-xhr', {
269
method: 'POST',
270
body: JSON.stringify({ ${propsToGet.join(',')} })
271
});
272
</script>
273
</body></html>`;
274
});
275
276
const frameXhr = new Promise<object>(resolve => {
277
koaServer.post('/frame-xhr', async ctx => {
278
requestUserAgentStrings.push(ctx.get('user-agent'));
279
const body = JSON.parse((await Helpers.readableToBuffer(ctx.req)).toString());
280
resolve(body);
281
ctx.body = 'Ok';
282
});
283
});
284
285
/////// TEST BEGIN /////
286
287
await agent.goto(`${koaServer.baseUrl}/agent-test`);
288
const frameParams = await frameXhr;
289
290
for (const useragent of requestUserAgentStrings) {
291
expect(useragent).toBe(agentMeta.userAgentString);
292
}
293
294
async function getJsValue(jsValue: string) {
295
const result = await agent.getJsValue(jsValue);
296
return result;
297
}
298
299
const windowParams: any = {};
300
for (const prop of propsToGet) {
301
windowParams[prop] = await getJsValue(`navigator.${prop}`);
302
}
303
304
expect(agentMeta.userAgentString).toBe(windowParams.userAgent);
305
expect(agentMeta.operatingSystemPlatform).toBe(windowParams.platform);
306
307
for (const prop of propsToGet) {
308
expect(`${prop}=${frameParams[prop]}`).toStrictEqual(`${prop}=${windowParams[prop]}`);
309
}
310
});
311
312
it('should maintain user agent and platform across navigations', async () => {
313
const agent = await handler.createAgent();
314
Helpers.needsClosing.push(agent);
315
316
const agentMeta = await agent.meta;
317
318
const requestUserAgentStrings: string[] = [];
319
320
const httpsServer = await Helpers.runHttpsServer(async (req, res) => {
321
requestUserAgentStrings.push(req.headers['user-agent']);
322
if (req.url === '/s2-page1') {
323
res.end(
324
`<html lang="en">
325
<script>
326
const { ${propsToGet.join(',')} } = navigator;
327
var startPageVars = { ${propsToGet.join(',')} };
328
</script>
329
<body><a href="${koaServer.baseUrl}/page2">link</a></body></html>`,
330
);
331
} else {
332
res.writeHead(404).end();
333
}
334
});
335
336
koaServer.get('/page1', ctx => {
337
requestUserAgentStrings.push(ctx.get('user-agent'));
338
ctx.body = `<html lang="en"><body><a href="${httpsServer.baseUrl}/s2-page1">link</a></body></html>`;
339
});
340
341
koaServer.get('/page2', ctx => {
342
requestUserAgentStrings.push(ctx.get('user-agent'));
343
ctx.body = `<html lang="en"><body><h1>Last Page</h1></body></html>`;
344
});
345
346
async function getParams() {
347
const windowParams: any = {};
348
for (const prop of propsToGet) {
349
windowParams[prop] = await agent.getJsValue(`navigator.${prop}`);
350
}
351
return windowParams;
352
}
353
354
await agent.goto(`${koaServer.baseUrl}/page1`);
355
356
const page1WindowParams = await getParams();
357
358
expect(agentMeta.userAgentString).toBe(page1WindowParams.userAgent);
359
expect(agentMeta.operatingSystemPlatform).toBe(page1WindowParams.platform);
360
361
await agent.click(agent.document.querySelector('a'));
362
363
const page2WindowParams = await getParams();
364
for (const prop of propsToGet) {
365
expect(`${prop}=${page2WindowParams[prop]}`).toStrictEqual(
366
`${prop}=${page1WindowParams[prop]}`,
367
);
368
}
369
370
const page2StartParams = await agent.getJsValue('startPageVars');
371
for (const prop of propsToGet) {
372
expect(`${prop}=${page2StartParams[prop]}`).toStrictEqual(
373
`${prop}=${page1WindowParams[prop]}`,
374
);
375
}
376
377
await agent.click(agent.document.querySelector('a'));
378
const page3WindowParams = await getParams();
379
for (const key of propsToGet) {
380
expect(page3WindowParams[key]).toBe(page1WindowParams[key]);
381
}
382
383
await agent.goBack();
384
const backParams = await getParams();
385
for (const key of propsToGet) {
386
expect(backParams[key]).toBe(page1WindowParams[key]);
387
}
388
for (const useragent of requestUserAgentStrings) {
389
expect(useragent).toBe(agentMeta.userAgentString);
390
}
391
});
392
393
it('should add user agent and platform to dedicated workers', async () => {
394
const agent = await handler.createAgent();
395
Helpers.needsClosing.push(agent);
396
397
const agentMeta = await agent.meta;
398
399
const requestUserAgentStrings: string[] = [];
400
401
koaServer.get('/workers-test', ctx => {
402
requestUserAgentStrings.push(ctx.get('user-agent'));
403
ctx.body = `<html lang="en">
404
<script>new Worker("worker.js").postMessage('');</script>
405
</html>`;
406
});
407
408
koaServer.get('/worker.js', ctx => {
409
requestUserAgentStrings.push(ctx.get('user-agent'));
410
ctx.set('content-type', 'application/javascript');
411
ctx.body = `onmessage = () => {
412
413
const { ${propsToGet.join(',')} } = navigator;
414
fetch('/worker-xhr', {
415
method: 'POST',
416
body: JSON.stringify({ ${propsToGet.join(',')} })
417
});
418
}`;
419
});
420
421
const xhr = new Promise<object>(resolve => {
422
koaServer.post('/worker-xhr', async ctx => {
423
requestUserAgentStrings.push(ctx.get('user-agent'));
424
const body = JSON.parse((await Helpers.readableToBuffer(ctx.req)).toString());
425
resolve(body);
426
ctx.body = 'Ok';
427
});
428
});
429
430
await agent.goto(`${koaServer.baseUrl}/workers-test`);
431
const params = await xhr;
432
433
for (const useragent of requestUserAgentStrings) {
434
expect(useragent).toBe(agentMeta.userAgentString);
435
}
436
437
for (const prop of propsToGet) {
438
const windowValue = await agent.getJsValue(`navigator.${prop}`);
439
expect(params[prop]).toStrictEqual(windowValue);
440
expect(`${prop}=${params[prop]}`).toStrictEqual(`${prop}=${windowValue}`);
441
}
442
});
443
444
it('should add user agent and platform to service workers', async () => {
445
const agent = await handler.createAgent();
446
Helpers.needsClosing.push(agent);
447
448
const agentMeta = await agent.meta;
449
450
const requestUserAgentStrings: string[] = [];
451
452
koaServer.get('/sw-test', ctx => {
453
requestUserAgentStrings.push(ctx.get('user-agent'));
454
ctx.body = `<html lang="en">
455
<h1>Service Worker Test</h1>
456
<script>
457
navigator.serviceWorker.register('./service-worker.js');
458
navigator.serviceWorker.ready.then((reg) => {
459
if (reg.active) {
460
reg.active.postMessage("send");
461
}
462
});
463
navigator.serviceWorker.addEventListener("message", (event) => {
464
fetch('/service-xhr', {
465
method: 'POST',
466
body: event.data
467
});
468
});
469
</script>
470
</html>`;
471
});
472
473
koaServer.get('/service-worker.js', ctx => {
474
requestUserAgentStrings.push(ctx.get('user-agent'));
475
ctx.set('content-type', 'application/javascript');
476
ctx.body = `
477
self.addEventListener("install", (event) => {
478
event.waitUntil(self.skipWaiting());
479
});
480
481
self.addEventListener('activate', event => {
482
event.waitUntil(self.clients.claim());
483
});
484
485
self.addEventListener('message', async event => {
486
if (event.data !== 'send') return;
487
self.skipWaiting();
488
self.clients.claim();
489
const clients = await self.clients.matchAll();
490
491
const { ${propsToGet.join(',')} } = navigator;
492
const data = JSON.stringify({ ${propsToGet.join(',')} });
493
494
clients.forEach(client => client.postMessage(data));
495
});`;
496
});
497
498
const xhr = new Promise<object>(resolve => {
499
koaServer.post('/service-xhr', async ctx => {
500
requestUserAgentStrings.push(ctx.get('user-agent'));
501
const body = JSON.parse((await Helpers.readableToBuffer(ctx.req)).toString());
502
resolve(body);
503
ctx.body = 'Ok';
504
});
505
});
506
507
/////// TEST BEGIN /////
508
509
await agent.goto(`${koaServer.baseUrl}/sw-test`);
510
const params = await xhr;
511
512
for (const useragent of requestUserAgentStrings) {
513
expect(useragent).toBe(agentMeta.userAgentString);
514
}
515
516
for (const prop of propsToGet) {
517
const windowValue = await agent.getJsValue(`navigator.${prop}`);
518
expect(params[prop]).toStrictEqual(windowValue);
519
}
520
});
521
522
it('should be able to load the creep-js phantom worker tests', async () => {
523
let jsonResult = new Resolvable<string>();
524
const httpsServer = await Helpers.runHttpsServer(async (req, res) => {
525
res.setHeader('access-control-allow-origin', '*');
526
if (req.url.match('/creepjs/tests/workers.html')) {
527
res.end(`<!DOCTYPE html>
528
<html lang="en">
529
<body>
530
<div id="fingerprint-data"></div>
531
<script src="workers.js"></script>
532
</body>
533
</html>`);
534
} else if (req.url.includes('worker-result')) {
535
const result = await Helpers.readableToBuffer(req);
536
jsonResult.resolve(result.toString());
537
res.end('');
538
} else {
539
await new Promise(resolve => setTimeout(resolve, 50));
540
const body = Fs.readFileSync(`${__dirname}/html/worker.js`);
541
res.setHeader('etag', 'W/"602f25aa-573c"');
542
res.setHeader('content-type', 'application/javascript; charset=utf-8');
543
res.end(body);
544
}
545
});
546
547
jsonResult = new Resolvable<string>();
548
549
const agent = await handler.createAgent();
550
Helpers.needsClosing.push(agent);
551
await agent.goto(`${httpsServer.baseUrl}/creepjs/tests/workers.html`);
552
553
const result = JSON.parse(await jsonResult.promise);
554
expect(result).toBeTruthy();
555
556
const { windowScope, dedicatedWorker, sharedWorker, serviceWorker } = result;
557
expect(windowScope.userAgent).toBe(dedicatedWorker.userAgent);
558
expect(windowScope.userAgent).toBe(serviceWorker.userAgent);
559
expect(windowScope.userAgent).toBe(sharedWorker.userAgent);
560
expect(windowScope.memory).toBe(dedicatedWorker.memory);
561
expect(windowScope.memory).toBe(serviceWorker.memory);
562
expect(windowScope.memory).toBe(sharedWorker.memory);
563
await agent.close();
564
});
565
566
// eslint-disable-next-line jest/no-disabled-tests
567
it.skip('should load creepjs', async () => {
568
const agent = await handler.createAgent();
569
Helpers.needsClosing.push(agent);
570
await agent.goto('https://abrahamjuliot.github.io/creepjs/tests/workers.html');
571
await agent.waitForPaintingStable();
572
const cols = await agent.document.querySelectorAll('.col-six');
573
for (const col of cols) {
574
const background = await col.getAttribute('style');
575
expect(background.trim()).toBe('background: none');
576
}
577
});
578
});
579
580