Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/core/test/navigation.test.ts
1029 views
1
import * as Fs from 'fs';
2
import { Helpers } from '@secret-agent/testing';
3
import { LocationStatus, LocationTrigger } from '@secret-agent/interfaces/Location';
4
import { InteractionCommand } from '@secret-agent/interfaces/IInteractions';
5
import { getLogo, ITestKoaServer } from '@secret-agent/testing/helpers';
6
import ISessionCreateOptions from '@secret-agent/interfaces/ISessionCreateOptions';
7
import HumanEmulator from '@secret-agent/plugin-utils/lib/HumanEmulator';
8
import Core, { Tab } from '../index';
9
import ConnectionToClient from '../server/ConnectionToClient';
10
import Session from '../lib/Session';
11
import FrameNavigationsObserver from '../lib/FrameNavigationsObserver';
12
import CoreServer from '../server';
13
14
let koaServer: ITestKoaServer;
15
let connectionToClient: ConnectionToClient;
16
beforeAll(async () => {
17
const coreServer = new CoreServer();
18
Helpers.needsClosing.push(coreServer);
19
await coreServer.listen({ port: 0 });
20
Core.use(
21
class BasicHumanEmulator extends HumanEmulator {
22
static id = 'basic';
23
},
24
);
25
connectionToClient = Core.addConnection();
26
await connectionToClient.connect();
27
Helpers.onClose(() => connectionToClient.disconnect(), true);
28
koaServer = await Helpers.runKoaServer();
29
});
30
31
afterAll(Helpers.afterAll);
32
afterEach(Helpers.afterEach);
33
34
describe('basic Navigation tests', () => {
35
it('handles unformatted urls', async () => {
36
const unformattedUrl = koaServer.baseUrl;
37
const { tab } = await createSession();
38
await tab.goto(unformattedUrl);
39
const formattedUrl = await tab.getLocationHref();
40
41
expect(formattedUrl).toBe(`${unformattedUrl}/`);
42
});
43
44
it('handles urls with a hash', async () => {
45
koaServer.get('/hash', ctx => {
46
ctx.body = 'done';
47
});
48
const { tab } = await createSession();
49
await expect(tab.goto(`${koaServer.baseUrl}/hash#hash`)).resolves.toBeTruthy();
50
});
51
52
it('works without explicit waitForLocation', async () => {
53
const { tab } = await createSession();
54
await tab.goto(koaServer.baseUrl);
55
56
const elem = await tab.execJsPath(['document', ['querySelector', 'a'], 'nodeName']);
57
const hrefAttribute = await tab.execJsPath(['document', ['querySelector', 'a'], 'href']);
58
expect(elem.value).toBe('A');
59
expect(hrefAttribute.value).toBe('https://www.iana.org/domains/example');
60
});
61
62
it('times out a goto', async () => {
63
const startingUrl = `${koaServer.baseUrl}/timeout`;
64
let timeoutResolve = () => null;
65
koaServer.get('/timeout', async ctx => {
66
await new Promise<void>(resolve => {
67
timeoutResolve = resolve;
68
});
69
ctx.body = 'done';
70
});
71
const { tab } = await createSession();
72
await expect(tab.goto(startingUrl, 100)).rejects.toThrowError('Timeout');
73
timeoutResolve();
74
});
75
76
it('can load a cached page multiple times', async () => {
77
const startingUrl = `${koaServer.baseUrl}/etag`;
78
koaServer.get('/etag', ctx => {
79
ctx.set('ETag', `W/\\"d02-48a7cf4b62c40\\"`);
80
ctx.set('Last-Modified', `Sat, 03 Jul 2010 14:59:53 GMT`);
81
ctx.body = `<html><body>
82
<img src="/img.jpeg"/>
83
<a href="/etagPage"></a>
84
</body></html>`;
85
});
86
koaServer.get('/img.jpeg', async ctx => {
87
ctx.set('ETag', `W/\\"d02-48a7cf4b62c41\\"`);
88
ctx.set('Last-Modified', `Sat, 03 Jul 2010 14:59:53 GMT`);
89
ctx.body = await getLogo();
90
});
91
const { tab } = await createSession();
92
93
for (let i = 0; i < 10; i += 1) {
94
await tab.goto(startingUrl);
95
await tab.waitForLoad('PaintingStable');
96
const hrefAttribute = await tab.execJsPath(['document', ['querySelector', 'a'], 'href']);
97
expect(hrefAttribute.value).toBe(`${koaServer.baseUrl}/etagPage`);
98
}
99
100
// need to give the last image a second to show that it loaded from cache
101
await new Promise(resolve => setTimeout(resolve, 100));
102
103
const resources = tab.sessionState.getResources(tab.id);
104
expect(resources).toHaveLength(20);
105
});
106
107
it('can goto a page multiple times', async () => {
108
const startingUrl = `${koaServer.baseUrl}/etag2`;
109
koaServer.get('/img2.jpeg', async ctx => {
110
ctx.body = await getLogo();
111
});
112
koaServer.get('/etag2', ctx => {
113
ctx.body = `<html><body>
114
<img src="/img2.jpeg"/>
115
<a href="/etagPage">Etag Page</a>
116
<script>
117
for (let i = 0; i< 100; i+=1) {
118
const elements = document.querySelectorAll('a');
119
const newElement = document.createElement('div');
120
newElement.textContent = 'hi';
121
elements[0].append(newElement)
122
}
123
</script>
124
</body></html>`;
125
});
126
const { tab } = await createSession();
127
128
for (let i = 0; i < 15; i += 1) {
129
await tab.goto(startingUrl);
130
await tab.waitForLoad('PaintingStable');
131
const hrefAttribute = await tab.execJsPath(['document', ['querySelector', 'a'], 'href']);
132
expect(hrefAttribute.value).toBe(`${koaServer.baseUrl}/etagPage`);
133
}
134
});
135
136
it('handles page reloading itself', async () => {
137
const startingUrl = `${koaServer.baseUrl}/reload`;
138
const { tab } = await createSession();
139
140
let hasReloaded = false;
141
koaServer.get('/reload', ctx => {
142
if (hasReloaded) {
143
ctx.body = '<body>Reloaded</body>';
144
} else {
145
ctx.body = '<body><script>window.location.reload()</script></body>';
146
hasReloaded = true;
147
}
148
});
149
150
await tab.goto(startingUrl);
151
await tab.waitForLocation(LocationTrigger.reload);
152
153
const text = await tab.execJsPath(['document', 'body', 'textContent']);
154
155
expect(text.value).toBe('Reloaded');
156
});
157
158
it('can reload a page', async () => {
159
const startingUrl = `${koaServer.baseUrl}/pagex`;
160
const { tab } = await createSession();
161
162
let counter = 0;
163
koaServer.get('/pagex', ctx => {
164
if (counter === 0) {
165
ctx.body = '<body>First Load</body>';
166
} else {
167
ctx.body = '<body>Second Load</body>';
168
}
169
counter += 1;
170
});
171
172
const gotoResource = await tab.goto(startingUrl);
173
await tab.waitForLoad(LocationStatus.PaintingStable);
174
175
const text = await tab.execJsPath(['document', 'body', 'textContent']);
176
expect(text.value).toBe('First Load');
177
178
const reloadResource = await tab.reload();
179
const text2 = await tab.execJsPath(['document', 'body', 'textContent']);
180
expect(text2.value).toBe('Second Load');
181
expect(reloadResource.id).not.toBe(gotoResource.id);
182
expect(reloadResource.url).toBe(gotoResource.url);
183
184
await tab.waitForLoad(LocationStatus.PaintingStable);
185
});
186
187
it('can go back and forward', async () => {
188
const { tab } = await createSession();
189
190
koaServer.get('/backAndForth', ctx => {
191
ctx.body = `<html><body><h1>Page 2</h1></body></html>`;
192
});
193
194
await tab.goto(`${koaServer.baseUrl}/`);
195
196
expect(await tab.getLocationHref()).toBe(`${koaServer.baseUrl}/`);
197
198
await tab.goto(`${koaServer.baseUrl}/backAndForth`);
199
expect(await tab.getLocationHref()).toBe(`${koaServer.baseUrl}/backAndForth`);
200
201
const pages = tab.navigations;
202
expect(pages.history).toHaveLength(2);
203
expect(pages.currentUrl).toBe(`${koaServer.baseUrl}/backAndForth`);
204
205
await tab.goBack();
206
expect(pages.history).toHaveLength(3);
207
expect(pages.currentUrl).toBe(`${koaServer.baseUrl}/`);
208
209
await tab.goForward();
210
expect(pages.history).toHaveLength(4);
211
expect(pages.top.stateChanges.has('Load') || pages.top.stateChanges.has('ContentPaint')).toBe(
212
true,
213
);
214
expect(pages.currentUrl).toBe(`${koaServer.baseUrl}/backAndForth`);
215
});
216
217
it('handles page that navigates to another url', async () => {
218
const startingUrl = `${koaServer.baseUrl}/navigate`;
219
const navigateToUrl = `${koaServer.baseUrl}/`;
220
const { tab } = await createSession();
221
222
koaServer.get('/navigate', ctx => {
223
ctx.body = `<body><script>window.location = '${navigateToUrl}'</script></body>`;
224
});
225
226
await tab.goto(startingUrl);
227
await tab.waitForLocation(LocationTrigger.change);
228
229
const currentUrl = await tab.getLocationHref();
230
231
expect(currentUrl).toBe(navigateToUrl);
232
});
233
234
it('handles submitting a form', async () => {
235
const startingUrl = `${koaServer.baseUrl}/form`;
236
const navigateToUrl = `${koaServer.baseUrl}/`;
237
const { tab } = await createSession();
238
239
koaServer.get('/form', ctx => {
240
ctx.body = `<body><form action="${navigateToUrl}" method="post"><input type="submit" id="button"></form></body>`;
241
});
242
243
await tab.goto(startingUrl);
244
245
await tab.waitForLoad(LocationStatus.PaintingStable);
246
await tab.interact([
247
{
248
command: InteractionCommand.click,
249
mousePosition: ['window', 'document', ['querySelector', '#button']],
250
},
251
]);
252
253
await tab.waitForLocation(LocationTrigger.change);
254
255
const currentUrl = await tab.getLocationHref();
256
257
expect(currentUrl).toBe(navigateToUrl);
258
}, 60e3);
259
260
it('handles navigation via link clicks', async () => {
261
const startingUrl = `${koaServer.baseUrl}/click`;
262
const navigateToUrl = `${koaServer.baseUrl}/`;
263
const { tab } = await createSession();
264
265
koaServer.get('/click', ctx => {
266
ctx.body = `<body><a href='${navigateToUrl}'>Clicker</a></body>`;
267
});
268
269
await tab.goto(startingUrl);
270
271
await tab.waitForLoad(LocationStatus.PaintingStable);
272
await tab.interact([
273
{
274
command: InteractionCommand.click,
275
mousePosition: ['window', 'document', ['querySelector', 'a']],
276
},
277
]);
278
279
await tab.waitForLocation(LocationTrigger.change);
280
281
const currentUrl = await tab.getLocationHref();
282
283
expect(currentUrl).toBe(navigateToUrl);
284
});
285
286
it('handles an in-page navigation change', async () => {
287
const startingUrl = `${koaServer.baseUrl}/inpage`;
288
const navigateToUrl = `${koaServer.baseUrl}/inpage#location2`;
289
const { tab } = await createSession();
290
291
koaServer.get('/inpage', ctx => {
292
ctx.body = `<body>
293
<a href='#location2'>Clicker</a>
294
295
<div id="location2">
296
<h2>Destination</h2>
297
</div>
298
299
</body>`;
300
});
301
302
await tab.goto(startingUrl);
303
304
await tab.waitForLoad(LocationStatus.PaintingStable);
305
await tab.interact([
306
{
307
command: InteractionCommand.click,
308
mousePosition: ['window', 'document', ['querySelector', 'a']],
309
},
310
]);
311
312
await tab.waitForLocation(LocationTrigger.change);
313
314
const currentUrl = await tab.getLocationHref();
315
316
expect(currentUrl).toBe(navigateToUrl);
317
318
const pages = tab.navigations;
319
expect(pages.history).toHaveLength(2);
320
});
321
322
it('handles an in-page navigation change that happens before page load', async () => {
323
const startingUrl = `${koaServer.baseUrl}/instant-hash`;
324
const navigateToUrl = `${koaServer.baseUrl}/instant-hash#id=12343`;
325
const { tab } = await createSession();
326
327
koaServer.get('/instant-hash', ctx => {
328
ctx.body = `<body>
329
<h1>Title</h1>
330
<script>
331
location.hash= '#id=12343';
332
setTimeout(function() {
333
history.replaceState(null, null, ' ')
334
})
335
</script>
336
337
</body>`;
338
});
339
340
await tab.goto(startingUrl);
341
342
await tab.waitForLoad(LocationStatus.PaintingStable);
343
await tab.waitForMillis(50);
344
345
const pages = tab.navigations;
346
expect(pages.history).toHaveLength(3);
347
expect(pages.history[0].stateChanges.has('DomContentLoaded')).toBe(true);
348
expect(pages.history[1].stateChanges.has('DomContentLoaded')).toBe(true);
349
350
expect(pages.history.map(x => x.finalUrl ?? x.requestedUrl)).toStrictEqual([
351
startingUrl,
352
navigateToUrl,
353
startingUrl,
354
]);
355
356
const currentUrl = await tab.getLocationHref();
357
expect(currentUrl).toBe(pages.top.finalUrl);
358
});
359
360
it('handles in-page history change that happens before page load', async () => {
361
const navigateToUrl = `${koaServer.baseUrl}/inpagenav/1`;
362
const { tab } = await createSession();
363
364
koaServer.get('/inpagenav', ctx => {
365
ctx.body = `<body><script>
366
history.pushState({}, '', '/inpagenav/1');
367
</script>
368
</body>`;
369
});
370
371
await tab.goto(`${koaServer.baseUrl}/inpagenav`);
372
await tab.waitForLoad(LocationStatus.PaintingStable);
373
374
const currentUrl = await tab.getLocationHref();
375
376
expect(currentUrl).toBe(navigateToUrl);
377
const pages = tab.navigations;
378
expect(pages.history).toHaveLength(2);
379
expect(pages.history[0].stateChanges.has('DomContentLoaded')).toBe(true);
380
expect(pages.history[1].stateChanges.has('DomContentLoaded')).toBe(true);
381
});
382
383
it.todo('handles going to about:blank');
384
385
it('can wait for another tab', async () => {
386
let userAgentString1: string;
387
let userAgentString2: string;
388
koaServer.get('/tabTest', ctx => {
389
userAgentString1 = ctx.get('user-agent');
390
ctx.body = `<body>
391
<a target="_blank" href="/tabTestDest">Nothing really here</a>
392
</body>`;
393
});
394
koaServer.get('/tabTestDest', ctx => {
395
userAgentString2 = ctx.get('user-agent');
396
ctx.body = `<body><h1 id="newTabHeader">You are here</h1></body>`;
397
});
398
const { tab } = await createSession();
399
await tab.goto(`${koaServer.baseUrl}/tabTest`);
400
await tab.interact([
401
{
402
command: InteractionCommand.click,
403
mousePosition: ['window', 'document', ['querySelector', 'a']],
404
},
405
]);
406
407
const session = tab.session;
408
409
const newTab = await tab.waitForNewTab();
410
expect(session.tabsById.size).toBe(2);
411
await newTab.waitForLoad('PaintingStable');
412
const header = await newTab.execJsPath([
413
'document',
414
['querySelector', '#newTabHeader'],
415
'textContent',
416
]);
417
expect(header.value).toBe('You are here');
418
expect(userAgentString1).toBe(userAgentString2);
419
await newTab.close();
420
});
421
422
it('should not trigger location change for first navigation of new tabs', async () => {
423
const { tab } = await createSession();
424
425
koaServer.get('/newTab', ctx => {
426
ctx.body = `<body><h1>Loaded</h1></body>`;
427
});
428
koaServer.get('/newTabPrompt', ctx => {
429
ctx.body = `<body><a href='${koaServer.baseUrl}/newTab' target="_blank">Popup</a></body>`;
430
});
431
432
await tab.goto(`${koaServer.baseUrl}/newTabPrompt`);
433
await tab.interact([
434
{
435
command: InteractionCommand.click,
436
mousePosition: ['window', 'document', ['querySelector', 'a']],
437
},
438
]);
439
440
const spy = jest.spyOn<any, any>(FrameNavigationsObserver.prototype, 'resolvePendingStatus');
441
442
// clear data before this run
443
const popupTab = await tab.waitForNewTab();
444
await popupTab.waitForLoad(LocationStatus.PaintingStable);
445
446
// can sometimes call for paint event
447
if (spy.mock.calls.length === 1) {
448
expect(spy.mock.calls[0][0]).not.toBe('change');
449
} else {
450
// should not have triggered a navigation change
451
expect(spy).toHaveBeenCalledTimes(0);
452
}
453
});
454
455
it('handles a new tab that redirects', async () => {
456
const { tab } = await createSession();
457
458
koaServer.get('/popup-redirect', async ctx => {
459
await new Promise(resolve => setTimeout(resolve, 25));
460
ctx.redirect('/popup-redirect2');
461
});
462
koaServer.get('/popup-redirect2', async ctx => {
463
ctx.status = 301;
464
await new Promise(resolve => setTimeout(resolve, 25));
465
ctx.set('Location', '/popup-redirect3');
466
});
467
koaServer.get('/popup-redirect3', ctx => {
468
ctx.body = `<body>
469
<h1>Loaded</h1>
470
<script type="text/javascript">
471
const perfObserver = new PerformanceObserver(() => {
472
window.location.href = '/popup-done';
473
});
474
perfObserver.observe({ type: 'largest-contentful-paint', buffered: true });
475
</script>
476
</body>`;
477
});
478
koaServer.get('/popup-done', ctx => {
479
ctx.body = '<body><h1>Long journey!</h1></body>';
480
});
481
koaServer.get('/popup', ctx => {
482
ctx.redirect('/popup-redirect');
483
});
484
koaServer.get('/popup-start', ctx => {
485
ctx.body = `<body><a href='${koaServer.baseUrl}/popup' target="_blank">Popup</a></body>`;
486
});
487
488
await tab.goto(`${koaServer.baseUrl}/popup-start`);
489
await tab.waitForLoad(LocationStatus.PaintingStable);
490
await tab.interact([
491
{
492
command: InteractionCommand.click,
493
mousePosition: ['window', 'document', ['querySelector', 'a']],
494
},
495
]);
496
497
// clear data before this run
498
const popupTab = await tab.waitForNewTab();
499
expect(popupTab.url).toBe(`${koaServer.baseUrl}/popup-redirect3`);
500
const lastCommandId = popupTab.lastCommandId;
501
await popupTab.waitForLoad(LocationStatus.PaintingStable);
502
if (popupTab.url !== `${koaServer.baseUrl}/popup-done`) {
503
await popupTab.waitForLocation('change', { sinceCommandId: lastCommandId });
504
await popupTab.waitForLoad(LocationStatus.DomContentLoaded);
505
}
506
expect(popupTab.url).toBe(`${koaServer.baseUrl}/popup-done`);
507
508
tab.sessionState.db.flush();
509
510
const history = popupTab.navigations.history;
511
expect(history).toHaveLength(5);
512
expect(history.map(x => x.requestedUrl)).toStrictEqual([
513
`${koaServer.baseUrl}/popup`,
514
`${koaServer.baseUrl}/popup-redirect`,
515
`${koaServer.baseUrl}/popup-redirect2`,
516
`${koaServer.baseUrl}/popup-redirect3`,
517
`${koaServer.baseUrl}/popup-done`,
518
]);
519
expect(history.map(x => x.finalUrl)).toStrictEqual([
520
`${koaServer.baseUrl}/popup-redirect`,
521
`${koaServer.baseUrl}/popup-redirect2`,
522
`${koaServer.baseUrl}/popup-redirect3`,
523
`${koaServer.baseUrl}/popup-redirect3`,
524
`${koaServer.baseUrl}/popup-done`,
525
]);
526
527
expect(history[1].stateChanges.has(LocationStatus.HttpRedirected)).toBe(true);
528
expect(history[2].stateChanges.has(LocationStatus.HttpRedirected)).toBe(true);
529
expect(history[3].stateChanges.has('ContentPaint')).toBe(true);
530
});
531
532
it('should return the last redirected url as the "resource" when a goto redirects', async () => {
533
const startingUrl = `${koaServer.baseUrl}/goto-redirect`;
534
koaServer.get('/goto-redirect', async ctx => {
535
await new Promise(resolve => setTimeout(resolve, 100));
536
ctx.redirect('/after-redirect');
537
});
538
koaServer.get('/after-redirect', ctx => {
539
ctx.body = '<html lang="en"><body><h1>Hi</h1></body></html>';
540
});
541
const { tab } = await createSession();
542
const resource = await tab.goto(startingUrl);
543
expect(resource.request.url).toBe(`${koaServer.baseUrl}/after-redirect`);
544
expect(resource.isRedirect).toBe(false);
545
});
546
});
547
548
describe('PaintingStable tests', () => {
549
it('should trigger painting stable after a redirect', async () => {
550
const startingUrl = `${koaServer.baseUrl}/stable-redirect`;
551
koaServer.get('/stable-redirect', async ctx => {
552
await new Promise(resolve => setTimeout(resolve, 100));
553
ctx.redirect('/post-stable-redirect');
554
});
555
koaServer.get('/post-stable-redirect', ctx => {
556
ctx.body = '<html lang="en"><body><h1>So stable</h1></body></html>';
557
});
558
const { tab } = await createSession();
559
const resource = await tab.goto(startingUrl);
560
await expect(tab.waitForLoad(LocationStatus.PaintingStable)).resolves.toBe(undefined);
561
expect(resource.request.url).toBe(`${koaServer.baseUrl}/post-stable-redirect`);
562
expect(resource.isRedirect).toBe(false);
563
});
564
565
it('should trigger a painting stable on a page that never triggers load', async () => {
566
const { tab } = await createSession();
567
568
let completeLongScript: () => void;
569
koaServer.get('/long-script.js', async ctx => {
570
await new Promise<void>(resolve => {
571
completeLongScript = resolve;
572
});
573
ctx.body = '';
574
});
575
koaServer.get('/img.png', ctx => {
576
ctx.body = getLogo();
577
});
578
koaServer.get('/stable-paint1', ctx => {
579
ctx.body = `
580
<html>
581
<body>
582
<h1>This is a test</h1>
583
<img src="/img.png" alt="Image"/>
584
<script src="/long-script.js"></script>
585
</body>
586
</html>`;
587
});
588
589
await tab.goto(`${koaServer.baseUrl}/stable-paint1`);
590
await tab.waitForLoad(LocationStatus.PaintingStable);
591
if (completeLongScript) completeLongScript();
592
expect(tab.navigations.top.stateChanges.has('Load')).toBe(false);
593
expect(tab.navigations.top.stateChanges.has('ContentPaint')).toBe(true);
594
});
595
596
it('should trigger painting stable once a single page app is loaded', async () => {
597
const { tab } = await createSession();
598
599
koaServer.get('/grid/:filename', async ctx => {
600
const filename = ctx.params.filename;
601
if (filename === 'data.json') {
602
await new Promise(resolve => setTimeout(resolve, 100));
603
const records = [];
604
for (let i = 0; i < 200; i += 1) {
605
records.push(
606
{ name: 'Chuck Norris', power: 10e3 },
607
{ name: 'Bruce Lee', power: 9000 },
608
{ name: 'Jackie Chan', power: 7000 },
609
{ name: 'Jet Li', power: 8000 },
610
);
611
}
612
ctx.set('content-type', 'application/json');
613
ctx.body = JSON.stringify({ records });
614
}
615
if (filename === 'vue.min.js') {
616
ctx.set('content-type', 'application/javascript');
617
ctx.body = Fs.createReadStream(require.resolve('vue/dist/vue.min.js'));
618
}
619
if (filename === 'index.html') {
620
ctx.set('content-type', 'text/html');
621
ctx.body = Fs.createReadStream(`${__dirname}/html/grid/index.html`);
622
}
623
if (filename === 'style.css') {
624
ctx.set('content-type', 'text/css');
625
ctx.body = Fs.createReadStream(`${__dirname}/html/grid/style.css`);
626
}
627
});
628
629
await tab.goto(`${koaServer.baseUrl}/grid/index.html`);
630
const trs = await tab.execJsPath<number>([
631
'document',
632
['querySelectorAll', '.record'],
633
'length',
634
]);
635
636
expect(trs.value).toBe(0);
637
await tab.waitForLoad(LocationStatus.PaintingStable);
638
const trs2 = await tab.execJsPath<number>([
639
'document',
640
['querySelectorAll', '.record'],
641
'length',
642
]);
643
expect(trs2.value).toBe(200 * 4);
644
});
645
});
646
647
async function createSession(
648
options?: ISessionCreateOptions,
649
): Promise<{ session: Session; tab: Tab }> {
650
const meta = await connectionToClient.createSession(options);
651
const tab = Session.getTab(meta);
652
Helpers.needsClosing.push(tab.session);
653
return { session: tab.session, tab };
654
}
655
656