Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/puppet/test/BrowserContext.test.ts
1030 views
1
import BrowserEmulator from '@secret-agent/default-browser-emulator';
2
import { URL } from 'url';
3
import Log from '@secret-agent/commons/Logger';
4
import IPuppetContext from '@secret-agent/interfaces/IPuppetContext';
5
import CorePlugins from '@secret-agent/core/lib/CorePlugins';
6
import { IBoundLog } from '@secret-agent/interfaces/ILog';
7
import Core from '@secret-agent/core';
8
import { IBrowserEmulatorConfig } from '@secret-agent/interfaces/ICorePlugin';
9
import { TestServer } from './server';
10
import Puppet from '../index';
11
import { createTestPage, ITestPage } from './TestPage';
12
import CustomBrowserEmulator from './_CustomBrowserEmulator';
13
14
const { log } = Log(module);
15
const browserEmulatorId = CustomBrowserEmulator.id;
16
17
describe('BrowserContext', () => {
18
let server: TestServer;
19
let puppet: Puppet;
20
const needsClosing = [];
21
22
beforeAll(async () => {
23
Core.use(CustomBrowserEmulator);
24
server = await TestServer.create(0);
25
puppet = new Puppet(BrowserEmulator.selectBrowserMeta().browserEngine);
26
await puppet.start();
27
});
28
29
afterAll(async () => {
30
await server.stop();
31
await puppet.close();
32
});
33
34
afterEach(async () => {
35
server.reset();
36
await Promise.all(needsClosing.map(x => x.close()));
37
needsClosing.length = 0;
38
});
39
40
describe('basic', () => {
41
it('should create new context', async () => {
42
const plugins = new CorePlugins({ browserEmulatorId }, log as IBoundLog);
43
const context = await puppet.newContext(plugins, log);
44
needsClosing.push(context);
45
expect(context).toBeTruthy();
46
await context.close();
47
});
48
49
it('should isolate localStorage and cookies', async () => {
50
// Create two incognito contexts.
51
const plugins1 = new CorePlugins({ browserEmulatorId }, log as IBoundLog);
52
const context1 = await puppet.newContext(plugins1, log);
53
needsClosing.push(context1);
54
55
const plugins2 = new CorePlugins({ browserEmulatorId }, log as IBoundLog);
56
const context2 = await puppet.newContext(plugins2, log);
57
needsClosing.push(context2);
58
59
// Create a page in first incognito context.
60
const page1 = await context1.newPage();
61
needsClosing.push(page1);
62
await page1.navigate(server.emptyPage);
63
await page1.evaluate(`
64
localStorage.setItem('name', 'page1');
65
document.cookie = 'name=page1';
66
`);
67
68
// Create a page in second incognito context.
69
const page2 = await context2.newPage();
70
needsClosing.push(page2);
71
await page2.navigate(server.emptyPage);
72
await page2.evaluate(`
73
localStorage.setItem('name', 'page2');
74
document.cookie = 'name=page2';
75
`);
76
77
// Make sure pages don't share localstorage or cookies.
78
expect(await page1.evaluate(`localStorage.getItem('name')`)).toBe('page1');
79
expect(await page1.evaluate(`document.cookie`)).toBe('name=page1');
80
expect(await page2.evaluate(`localStorage.getItem('name')`)).toBe('page2');
81
expect(await page2.evaluate(`document.cookie`)).toBe('name=page2');
82
83
// Cleanup contexts.
84
await Promise.all([context1.close(), context2.close()]);
85
});
86
87
it('close() should work for empty context', async () => {
88
const plugins = new CorePlugins({ browserEmulatorId }, log as IBoundLog);
89
const context = await puppet.newContext(plugins, log);
90
needsClosing.push(context);
91
await expect(context.close()).resolves.toBe(undefined);
92
});
93
94
it('close() should be callable twice', async () => {
95
const plugins = new CorePlugins({ browserEmulatorId }, log as IBoundLog);
96
const context = await puppet.newContext(plugins, log);
97
needsClosing.push(context);
98
await Promise.all([context.close(), context.close()]);
99
await expect(context.close()).resolves.toBe(undefined);
100
});
101
});
102
103
describe('emulator', () => {
104
it('should set for all pages', async () => {
105
{
106
const plugins = new CorePlugins({ browserEmulatorId }, log as IBoundLog);
107
const { browserEmulator } = plugins;
108
const context = await puppet.newContext(plugins, log);
109
const page = await context.newPage();
110
const config: IBrowserEmulatorConfig = {};
111
plugins.configure(config);
112
needsClosing.push(context);
113
needsClosing.push(page);
114
expect(await page.evaluate(`navigator.userAgent`)).toBe(browserEmulator.userAgentString);
115
expect(await page.evaluate(`navigator.platform`)).toBe(
116
browserEmulator.operatingSystemPlatform,
117
);
118
expect(await page.evaluate(`navigator.languages`)).toStrictEqual(['en']);
119
expect(await page.evaluate('screen.height')).toBe(config.viewport?.height);
120
await context.close();
121
}
122
{
123
const plugins = new CorePlugins({ browserEmulatorId }, log as IBoundLog);
124
plugins.browserEmulator.operatingSystemPlatform = 'Windows';
125
plugins.browserEmulator.userAgentString = 'foobar';
126
plugins.configure({
127
locale: 'de',
128
viewport: {
129
screenHeight: 901,
130
screenWidth: 1024,
131
positionY: 1,
132
positionX: 0,
133
height: 900,
134
width: 1024,
135
},
136
});
137
138
const context = await puppet.newContext(plugins, log);
139
needsClosing.push(context);
140
const page = await context.newPage();
141
needsClosing.push(page);
142
const [request] = await Promise.all([
143
server.waitForRequest('/empty.html'),
144
page.navigate(server.emptyPage),
145
]);
146
expect(request.headers['user-agent']).toBe('foobar');
147
expect(await page.evaluate(`navigator.userAgent`)).toBe('foobar');
148
expect(await page.evaluate(`navigator.platform`)).toBe('Windows');
149
expect(await page.evaluate(`navigator.languages`)).toStrictEqual(['de']);
150
expect(await page.evaluate('screen.height')).toBe(901);
151
await context.close();
152
}
153
});
154
155
it('should work for subframes', async () => {
156
{
157
const plugins = new CorePlugins({ browserEmulatorId }, log as IBoundLog);
158
const context = await puppet.newContext(plugins, log);
159
needsClosing.push(context);
160
const page = await context.newPage();
161
needsClosing.push(page);
162
expect(await page.evaluate(`navigator.userAgent`)).toContain(
163
plugins.browserEmulator.userAgentString,
164
);
165
await context.close();
166
}
167
{
168
const plugins = new CorePlugins({ browserEmulatorId }, log as IBoundLog);
169
plugins.browserEmulator.userAgentString = 'foobar';
170
const context = await puppet.newContext(plugins, log);
171
needsClosing.push(context);
172
const page = await context.newPage();
173
needsClosing.push(page);
174
const [request] = await Promise.all([
175
server.waitForRequest('/empty.html'),
176
page.evaluate(`(async () => {
177
const frame = document.createElement('iframe');
178
frame.src = '${server.emptyPage}';
179
frame.id = 'frame1';
180
document.body.appendChild(frame);
181
await new Promise(x => frame.onload = x);
182
})()`),
183
]);
184
expect((request as any).headers['user-agent']).toBe('foobar');
185
await context.close();
186
}
187
});
188
});
189
190
describe('cookies', () => {
191
let context: IPuppetContext;
192
let page: ITestPage;
193
beforeEach(async () => {
194
const plugins = new CorePlugins({ browserEmulatorId }, log as IBoundLog);
195
context = await puppet.newContext(plugins, log);
196
page = createTestPage(await context.newPage());
197
});
198
afterEach(async () => {
199
await page.close();
200
await context.close();
201
});
202
203
it('should work', async () => {
204
await page.navigate(server.emptyPage);
205
await context.addCookies(
206
[
207
{
208
domain: 'localhost',
209
name: 'password',
210
value: '123456',
211
},
212
],
213
[server.emptyPage],
214
);
215
expect(await page.evaluate(`document.cookie`)).toEqual('password=123456');
216
});
217
218
it('should roundtrip cookie', async () => {
219
await page.navigate(server.emptyPage);
220
// @see https://en.wikipedia.org/wiki/Year_2038_problem
221
const date = +new Date('1/1/2038');
222
const documentCookie = await page.evaluate(`(() => {
223
const date = new Date(${date});
224
document.cookie = 'username=John Doe;expires=' + date.toUTCString();
225
return document.cookie;
226
})()`);
227
expect(documentCookie).toBe('username=John Doe');
228
const cookies = await context.getCookies();
229
await page.close();
230
await context.addCookies(cookies, [server.emptyPage]);
231
expect(await context.getCookies()).toEqual(cookies);
232
});
233
234
it('should send cookie header', async () => {
235
let cookie = '';
236
server.setRoute('/empty.html', (req, res) => {
237
cookie = req.headers.cookie;
238
res.end();
239
});
240
await context.addCookies(
241
[{ url: server.emptyPage, name: 'cookie', value: 'value' }],
242
[server.emptyPage],
243
);
244
const page2 = await context.newPage();
245
await page2.navigate(server.emptyPage);
246
expect(cookie).toBe('cookie=value');
247
await page2.close();
248
});
249
250
it('should set multiple cookies', async () => {
251
await page.goto(server.emptyPage);
252
await context.addCookies(
253
[
254
{
255
url: server.emptyPage,
256
name: 'multiple-1',
257
value: '123456',
258
},
259
{
260
url: server.emptyPage,
261
name: 'multiple-2',
262
value: 'bar',
263
},
264
],
265
[server.emptyPage],
266
);
267
expect(
268
await page.evaluate(`(() =>{
269
const cookies = document.cookie.split(';');
270
return cookies.map(cookie => cookie.trim()).sort();
271
})()`),
272
).toEqual(['multiple-1=123456', 'multiple-2=bar']);
273
});
274
275
it('should have |expires| set to |-1| for session cookies', async () => {
276
await context.addCookies(
277
[
278
{
279
url: server.emptyPage,
280
name: 'expires',
281
value: '123456',
282
},
283
],
284
[server.emptyPage],
285
);
286
const cookies = await context.getCookies();
287
expect(cookies[0].expires).toBe('-1');
288
});
289
290
it('should set a cookie with a path', async () => {
291
await page.goto(`${server.baseUrl}/grid.html`);
292
await context.addCookies([
293
{
294
domain: 'localhost',
295
path: '/grid.html',
296
name: 'gridcookie',
297
value: 'GRID',
298
},
299
]);
300
expect(await context.getCookies()).toStrictEqual([
301
{
302
name: 'gridcookie',
303
value: 'GRID',
304
domain: 'localhost',
305
path: '/grid.html',
306
expires: '-1',
307
secure: false,
308
httpOnly: false,
309
sameSite: 'None',
310
},
311
]);
312
expect(await page.evaluate('document.cookie')).toBe('gridcookie=GRID');
313
await page.goto(server.emptyPage);
314
expect(await page.evaluate('document.cookie')).toBe('');
315
await page.goto(`${server.baseUrl}/grid.html`);
316
expect(await page.evaluate('document.cookie')).toBe('gridcookie=GRID');
317
});
318
319
it('should set cookies for a frame', async () => {
320
await page.goto(server.emptyPage);
321
await context.addCookies([{ url: server.baseUrl, name: 'frame-cookie', value: 'value' }]);
322
await page.evaluate(`((src) => {
323
let fulfill;
324
const promise = new Promise(x => (fulfill = x));
325
const iframe = document.createElement('iframe');
326
document.body.appendChild(iframe);
327
iframe.onload = fulfill;
328
iframe.src = src;
329
return promise;
330
})('${server.baseUrl}/grid.html')`);
331
332
expect(await page.frames[1].evaluate('document.cookie', false)).toBe('frame-cookie=value');
333
});
334
335
it('should(not) block third party cookies', async () => {
336
await page.goto(server.emptyPage);
337
await page.evaluate(`((src) => {
338
let fulfill;
339
const promise = new Promise(x => (fulfill = x));
340
const iframe = document.createElement('iframe');
341
document.body.appendChild(iframe);
342
iframe.onload = fulfill;
343
iframe.src = src;
344
return promise;
345
})('${server.crossProcessBaseUrl}/grid.html')`);
346
await page.frames[1].evaluate(`document.cookie = 'username=John Doe'`);
347
await new Promise(resolve => setTimeout(resolve, 2e3));
348
const allowsThirdParty = false; // options.CHROME || options.FIREFOX;
349
const cookies = await context.getCookies(new URL(`${server.crossProcessBaseUrl}/grid.html}`));
350
if (allowsThirdParty) {
351
expect(cookies).toEqual([
352
{
353
domain: '127.0.0.1',
354
expires: -1,
355
httpOnly: false,
356
name: 'username',
357
path: '/',
358
sameSite: 'None',
359
secure: false,
360
value: 'John Doe',
361
},
362
]);
363
}
364
});
365
366
it('should get a cookie', async () => {
367
await page.navigate(server.emptyPage);
368
const documentCookie = await page.evaluate(`(() => {
369
document.cookie = 'username=John Doe';
370
return document.cookie;
371
})()`);
372
expect(documentCookie).toBe('username=John Doe');
373
expect(await context.getCookies()).toEqual([
374
{
375
name: 'username',
376
value: 'John Doe',
377
domain: 'localhost',
378
path: '/',
379
expires: '-1',
380
httpOnly: false,
381
secure: false,
382
sameSite: 'None',
383
},
384
]);
385
});
386
387
it('should get a non-session cookie', async () => {
388
await page.navigate(server.emptyPage);
389
// @see https://en.wikipedia.org/wiki/Year_2038_problem
390
const date = +new Date('1/1/2038');
391
const documentCookie = await page.evaluate(`(()=>{
392
const date = new Date(${date});
393
document.cookie = 'username=John Doe;expires=' + date.toUTCString();
394
return document.cookie;
395
})()`);
396
expect(documentCookie).toBe('username=John Doe');
397
expect(await context.getCookies()).toEqual([
398
{
399
name: 'username',
400
value: 'John Doe',
401
domain: 'localhost',
402
path: '/',
403
expires: String(date / 1000),
404
httpOnly: false,
405
secure: false,
406
sameSite: 'None',
407
},
408
]);
409
});
410
411
it('should properly report "Strict" sameSite cookie', async () => {
412
server.setRoute('/empty.html', (req, res) => {
413
res.setHeader('Set-Cookie', 'name=value;SameSite=Strict');
414
res.end();
415
});
416
await page.navigate(server.emptyPage);
417
const cookies = await context.getCookies();
418
expect(cookies.length).toBe(1);
419
expect(cookies[0].sameSite).toBe('Strict');
420
});
421
422
it('should properly report "Lax" sameSite cookie', async () => {
423
server.setRoute('/empty.html', (req, res) => {
424
res.setHeader('Set-Cookie', 'name=value;SameSite=Lax');
425
res.end();
426
});
427
await page.navigate(server.emptyPage);
428
const cookies = await context.getCookies();
429
expect(cookies.length).toBe(1);
430
expect(cookies[0].sameSite).toBe('Lax');
431
});
432
433
it('should get cookies for a url', async () => {
434
await context.addCookies([
435
{
436
url: 'https://foo.com',
437
name: 'doggo',
438
value: 'woofs',
439
},
440
{
441
url: 'https://baz.com',
442
name: 'catto',
443
value: 'purrs',
444
},
445
{
446
url: 'https://baz.com',
447
domain: '.baz.com',
448
name: 'birdo',
449
value: 'tweets',
450
},
451
]);
452
const cookies = await context.getCookies(new URL('https://foo.com'));
453
cookies.sort((a, b) => a.name.localeCompare(b.name));
454
expect(cookies).toEqual([
455
{
456
name: 'doggo',
457
value: 'woofs',
458
domain: 'foo.com',
459
path: '/',
460
expires: '-1',
461
httpOnly: false,
462
secure: true,
463
sameSite: 'None',
464
},
465
]);
466
await expect(context.getCookies(new URL('https://baz.com'))).resolves.toHaveLength(2);
467
});
468
});
469
});
470
471