@vitest/browser 4.0.0-beta.13 → 4.0.0-beta.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/context.d.ts +50 -9
- package/context.js +3 -2
- package/dist/client/.vite/manifest.json +2 -2
- package/dist/client/__vitest__/assets/{index-C15NF4dG.js → index-CKAjAT2u.js} +1 -1
- package/dist/client/__vitest__/index.html +1 -1
- package/dist/client/__vitest_browser__/{orchestrator-Pdu7HCGX.js → orchestrator-Ce7D5fGP.js} +8 -6
- package/dist/client/__vitest_browser__/{tester-DYNLfPFH.js → tester-Vm4ppAv-.js} +4 -1
- package/dist/client/orchestrator.html +1 -1
- package/dist/client/tester/tester.html +1 -1
- package/dist/client.js +1 -1
- package/dist/context.js +70 -12
- package/dist/expect-element.js +1 -1
- package/dist/{public-utils-B6exS8fl.js → index-BnLTaCRv.js} +3 -3
- package/dist/index.d.ts +36 -172
- package/dist/index.js +515 -1487
- package/dist/{locators/index.d.ts → locators.d.ts} +25 -2
- package/dist/locators.js +1 -0
- package/jest-dom.d.ts +5 -5
- package/matchers.d.ts +1 -1
- package/package.json +11 -33
- package/utils.d.ts +5 -5
- package/dist/index-BPDFwkoW.js +0 -1
- package/dist/index-CwoiDq7G.js +0 -6
- package/dist/locators/index.js +0 -1
- package/dist/locators/playwright.js +0 -1
- package/dist/locators/preview.js +0 -1
- package/dist/locators/webdriverio.js +0 -1
- package/dist/providers/playwright.d.ts +0 -105
- package/dist/providers/playwright.js +0 -385
- package/dist/providers/preview.d.ts +0 -16
- package/dist/providers/preview.js +0 -44
- package/dist/providers/webdriverio.d.ts +0 -51
- package/dist/providers/webdriverio.js +0 -206
- package/dist/utils.js +0 -1
- package/providers.d.ts +0 -7
package/dist/index.js
CHANGED
|
@@ -2,27 +2,23 @@ import { ManualMockedModule, RedirectedModule, AutomockedModule, AutospiedModule
|
|
|
2
2
|
import { dynamicImportPlugin, ServerMockResolver, interceptorPlugin } from '@vitest/mocker/node';
|
|
3
3
|
import c from 'tinyrainbow';
|
|
4
4
|
import { isValidApiRequest, isFileServingAllowed, distDir, resolveApiServerConfig, resolveFsAllow, rolldownVersion, createDebugger, createViteLogger, createViteServer } from 'vitest/node';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import fs, { readFileSync, lstatSync, createReadStream, promises, existsSync } from 'node:fs';
|
|
6
7
|
import { createRequire } from 'node:module';
|
|
7
|
-
import { slash as slash$1, toArray,
|
|
8
|
+
import { slash as slash$1, toArray, deepMerge } from '@vitest/utils/helpers';
|
|
8
9
|
import MagicString from 'magic-string';
|
|
9
10
|
import sirv from 'sirv';
|
|
10
11
|
import { coverageConfigDefaults } from 'vitest/config';
|
|
11
|
-
import { fileURLToPath } from 'node:url';
|
|
12
12
|
import crypto from 'node:crypto';
|
|
13
|
-
import {
|
|
13
|
+
import { readFile as readFile$1, mkdir, writeFile as writeFile$1 } from 'node:fs/promises';
|
|
14
14
|
import { parseErrorStacktrace, parseStacktrace } from '@vitest/utils/source-map';
|
|
15
|
-
import {
|
|
16
|
-
import { WebdriverBrowserProvider } from './providers/webdriverio.js';
|
|
17
|
-
import { resolve as resolve$1, basename as basename$1, dirname as dirname$1, normalize as normalize$1 } from 'node:path';
|
|
18
|
-
import * as nodeos from 'node:os';
|
|
15
|
+
import { resolve as resolve$1, basename as basename$1, dirname as dirname$1 } from 'node:path';
|
|
19
16
|
import { platform } from 'node:os';
|
|
20
17
|
import { PNG } from 'pngjs';
|
|
21
18
|
import pm from 'pixelmatch';
|
|
22
19
|
import { WebSocketServer } from 'ws';
|
|
23
|
-
import { performance } from 'node:perf_hooks';
|
|
24
20
|
|
|
25
|
-
var version = "4.0.0-beta.
|
|
21
|
+
var version = "4.0.0-beta.14";
|
|
26
22
|
|
|
27
23
|
const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
|
|
28
24
|
function normalizeWindowsPath(input = "") {
|
|
@@ -324,54 +320,372 @@ const stringify = (value, replacer, space) => {
|
|
|
324
320
|
}
|
|
325
321
|
};
|
|
326
322
|
|
|
327
|
-
function
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
323
|
+
var DOM_KEY_LOCATION = /*#__PURE__*/ function(DOM_KEY_LOCATION) {
|
|
324
|
+
DOM_KEY_LOCATION[DOM_KEY_LOCATION["STANDARD"] = 0] = "STANDARD";
|
|
325
|
+
DOM_KEY_LOCATION[DOM_KEY_LOCATION["LEFT"] = 1] = "LEFT";
|
|
326
|
+
DOM_KEY_LOCATION[DOM_KEY_LOCATION["RIGHT"] = 2] = "RIGHT";
|
|
327
|
+
DOM_KEY_LOCATION[DOM_KEY_LOCATION["NUMPAD"] = 3] = "NUMPAD";
|
|
328
|
+
return DOM_KEY_LOCATION;
|
|
329
|
+
}({});
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Mapping for a default US-104-QWERTY keyboard
|
|
333
|
+
*/ const defaultKeyMap = [
|
|
334
|
+
// alphanumeric block - writing system
|
|
335
|
+
...'0123456789'.split('').map((c)=>({
|
|
336
|
+
code: `Digit${c}`,
|
|
337
|
+
key: c
|
|
338
|
+
})),
|
|
339
|
+
...')!@#$%^&*('.split('').map((c, i)=>({
|
|
340
|
+
code: `Digit${i}`,
|
|
341
|
+
key: c,
|
|
342
|
+
shiftKey: true
|
|
343
|
+
})),
|
|
344
|
+
...'abcdefghijklmnopqrstuvwxyz'.split('').map((c)=>({
|
|
345
|
+
code: `Key${c.toUpperCase()}`,
|
|
346
|
+
key: c
|
|
347
|
+
})),
|
|
348
|
+
...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').map((c)=>({
|
|
349
|
+
code: `Key${c}`,
|
|
350
|
+
key: c,
|
|
351
|
+
shiftKey: true
|
|
352
|
+
})),
|
|
353
|
+
{
|
|
354
|
+
code: 'BracketLeft',
|
|
355
|
+
key: '['
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
code: 'BracketLeft',
|
|
359
|
+
key: '{',
|
|
360
|
+
shiftKey: true
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
code: 'BracketRight',
|
|
364
|
+
key: ']'
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
code: 'BracketRight',
|
|
368
|
+
key: '}',
|
|
369
|
+
shiftKey: true
|
|
370
|
+
},
|
|
371
|
+
// alphanumeric block - functional
|
|
372
|
+
{
|
|
373
|
+
code: 'Space',
|
|
374
|
+
key: ' '
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
code: 'AltLeft',
|
|
378
|
+
key: 'Alt',
|
|
379
|
+
location: DOM_KEY_LOCATION.LEFT
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
code: 'AltRight',
|
|
383
|
+
key: 'Alt',
|
|
384
|
+
location: DOM_KEY_LOCATION.RIGHT
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
code: 'ShiftLeft',
|
|
388
|
+
key: 'Shift',
|
|
389
|
+
location: DOM_KEY_LOCATION.LEFT
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
code: 'ShiftRight',
|
|
393
|
+
key: 'Shift',
|
|
394
|
+
location: DOM_KEY_LOCATION.RIGHT
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
code: 'ControlLeft',
|
|
398
|
+
key: 'Control',
|
|
399
|
+
location: DOM_KEY_LOCATION.LEFT
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
code: 'ControlRight',
|
|
403
|
+
key: 'Control',
|
|
404
|
+
location: DOM_KEY_LOCATION.RIGHT
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
code: 'MetaLeft',
|
|
408
|
+
key: 'Meta',
|
|
409
|
+
location: DOM_KEY_LOCATION.LEFT
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
code: 'MetaRight',
|
|
413
|
+
key: 'Meta',
|
|
414
|
+
location: DOM_KEY_LOCATION.RIGHT
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
code: 'OSLeft',
|
|
418
|
+
key: 'OS',
|
|
419
|
+
location: DOM_KEY_LOCATION.LEFT
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
code: 'OSRight',
|
|
423
|
+
key: 'OS',
|
|
424
|
+
location: DOM_KEY_LOCATION.RIGHT
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
code: 'ContextMenu',
|
|
428
|
+
key: 'ContextMenu'
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
code: 'Tab',
|
|
432
|
+
key: 'Tab'
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
code: 'CapsLock',
|
|
436
|
+
key: 'CapsLock'
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
code: 'Backspace',
|
|
440
|
+
key: 'Backspace'
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
code: 'Enter',
|
|
444
|
+
key: 'Enter'
|
|
445
|
+
},
|
|
446
|
+
// function
|
|
447
|
+
{
|
|
448
|
+
code: 'Escape',
|
|
449
|
+
key: 'Escape'
|
|
450
|
+
},
|
|
451
|
+
// arrows
|
|
452
|
+
{
|
|
453
|
+
code: 'ArrowUp',
|
|
454
|
+
key: 'ArrowUp'
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
code: 'ArrowDown',
|
|
458
|
+
key: 'ArrowDown'
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
code: 'ArrowLeft',
|
|
462
|
+
key: 'ArrowLeft'
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
code: 'ArrowRight',
|
|
466
|
+
key: 'ArrowRight'
|
|
467
|
+
},
|
|
468
|
+
// control pad
|
|
469
|
+
{
|
|
470
|
+
code: 'Home',
|
|
471
|
+
key: 'Home'
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
code: 'End',
|
|
475
|
+
key: 'End'
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
code: 'Delete',
|
|
479
|
+
key: 'Delete'
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
code: 'PageUp',
|
|
483
|
+
key: 'PageUp'
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
code: 'PageDown',
|
|
487
|
+
key: 'PageDown'
|
|
488
|
+
},
|
|
489
|
+
// Special keys that are not part of a default US-layout but included for specific behavior
|
|
490
|
+
{
|
|
491
|
+
code: 'Fn',
|
|
492
|
+
key: 'Fn'
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
code: 'Symbol',
|
|
496
|
+
key: 'Symbol'
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
code: 'AltRight',
|
|
500
|
+
key: 'AltGraph'
|
|
501
|
+
}
|
|
502
|
+
];
|
|
503
|
+
|
|
504
|
+
var bracketDict = /*#__PURE__*/ function(bracketDict) {
|
|
505
|
+
bracketDict["{"] = "}";
|
|
506
|
+
bracketDict["["] = "]";
|
|
507
|
+
return bracketDict;
|
|
508
|
+
}(bracketDict || {});
|
|
509
|
+
/**
|
|
510
|
+
* Read the next key definition from user input
|
|
511
|
+
*
|
|
512
|
+
* Describe key per `{descriptor}` or `[descriptor]`.
|
|
513
|
+
* Everything else will be interpreted as a single character as descriptor - e.g. `a`.
|
|
514
|
+
* Brackets `{` and `[` can be escaped by doubling - e.g. `foo[[bar` translates to `foo[bar`.
|
|
515
|
+
* A previously pressed key can be released per `{/descriptor}`.
|
|
516
|
+
* Keeping the key pressed can be written as `{descriptor>}`.
|
|
517
|
+
* When keeping the key pressed you can choose how long the key is pressed `{descriptor>3}`.
|
|
518
|
+
* You can then release the key per `{descriptor>3/}` or keep it pressed and continue with the next key.
|
|
519
|
+
*/ function readNextDescriptor(text, context) {
|
|
520
|
+
let pos = 0;
|
|
521
|
+
const startBracket = text[pos] in bracketDict ? text[pos] : '';
|
|
522
|
+
pos += startBracket.length;
|
|
523
|
+
const isEscapedChar = new RegExp(`^\\${startBracket}{2}`).test(text);
|
|
524
|
+
const type = isEscapedChar ? '' : startBracket;
|
|
525
|
+
return {
|
|
526
|
+
type,
|
|
527
|
+
...type === '' ? readPrintableChar(text, pos) : readTag(text, pos, type)
|
|
528
|
+
};
|
|
352
529
|
}
|
|
353
|
-
function
|
|
354
|
-
|
|
530
|
+
function readPrintableChar(text, pos, context) {
|
|
531
|
+
const descriptor = text[pos];
|
|
532
|
+
assertDescriptor(descriptor, text, pos);
|
|
533
|
+
pos += descriptor.length;
|
|
534
|
+
return {
|
|
535
|
+
consumedLength: pos,
|
|
536
|
+
descriptor,
|
|
537
|
+
releasePrevious: false,
|
|
538
|
+
releaseSelf: true,
|
|
539
|
+
repeat: 1
|
|
540
|
+
};
|
|
355
541
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
542
|
+
function readTag(text, pos, startBracket, context) {
|
|
543
|
+
var _text_slice_match, _text_slice_match1;
|
|
544
|
+
const releasePreviousModifier = text[pos] === '/' ? '/' : '';
|
|
545
|
+
pos += releasePreviousModifier.length;
|
|
546
|
+
const escapedDescriptor = startBracket === '{' && text[pos] === '\\';
|
|
547
|
+
pos += Number(escapedDescriptor);
|
|
548
|
+
const descriptor = escapedDescriptor ? text[pos] : (_text_slice_match = text.slice(pos).match(startBracket === '{' ? /^\w+|^[^}>/]/ : /^\w+/)) === null || _text_slice_match === undefined ? undefined : _text_slice_match[0];
|
|
549
|
+
assertDescriptor(descriptor, text, pos);
|
|
550
|
+
pos += descriptor.length;
|
|
551
|
+
var _text_slice_match_;
|
|
552
|
+
const repeatModifier = (_text_slice_match_ = (_text_slice_match1 = text.slice(pos).match(/^>\d+/)) === null || _text_slice_match1 === undefined ? undefined : _text_slice_match1[0]) !== null && _text_slice_match_ !== undefined ? _text_slice_match_ : '';
|
|
553
|
+
pos += repeatModifier.length;
|
|
554
|
+
const releaseSelfModifier = text[pos] === '/' || !repeatModifier && text[pos] === '>' ? text[pos] : '';
|
|
555
|
+
pos += releaseSelfModifier.length;
|
|
556
|
+
const expectedEndBracket = bracketDict[startBracket];
|
|
557
|
+
const endBracket = text[pos] === expectedEndBracket ? expectedEndBracket : '';
|
|
558
|
+
if (!endBracket) {
|
|
559
|
+
throw new Error(getErrorMessage([
|
|
560
|
+
!repeatModifier && 'repeat modifier',
|
|
561
|
+
!releaseSelfModifier && 'release modifier',
|
|
562
|
+
`"${expectedEndBracket}"`
|
|
563
|
+
].filter(Boolean).join(' or '), text[pos], text));
|
|
564
|
+
}
|
|
565
|
+
pos += endBracket.length;
|
|
566
|
+
return {
|
|
567
|
+
consumedLength: pos,
|
|
568
|
+
descriptor,
|
|
569
|
+
releasePrevious: !!releasePreviousModifier,
|
|
570
|
+
repeat: repeatModifier ? Math.max(Number(repeatModifier.substr(1)), 1) : 1,
|
|
571
|
+
releaseSelf: hasReleaseSelf(releaseSelfModifier, repeatModifier)
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
function assertDescriptor(descriptor, text, pos, context) {
|
|
575
|
+
if (!descriptor) {
|
|
576
|
+
throw new Error(getErrorMessage('key descriptor', text[pos], text));
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
function hasReleaseSelf(releaseSelfModifier, repeatModifier) {
|
|
580
|
+
if (releaseSelfModifier) {
|
|
581
|
+
return releaseSelfModifier === '/';
|
|
582
|
+
}
|
|
583
|
+
if (repeatModifier) {
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
function getErrorMessage(expected, found, text, context) {
|
|
588
|
+
return `Expected ${expected} but found "${found !== null && found !== undefined ? found : ''}" in "${text}"
|
|
589
|
+
See ${`https://testing-library.com/docs/user-event/keyboard`}
|
|
590
|
+
for more information about how userEvent parses your input.`;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Parse key definitions per `keyboardMap`
|
|
595
|
+
*
|
|
596
|
+
* Keys can be referenced by `{key}` or `{special}` as well as physical locations per `[code]`.
|
|
597
|
+
* Everything else will be interpreted as a typed character - e.g. `a`.
|
|
598
|
+
* Brackets `{` and `[` can be escaped by doubling - e.g. `foo[[bar` translates to `foo[bar`.
|
|
599
|
+
* Keeping the key pressed can be written as `{key>}`.
|
|
600
|
+
* When keeping the key pressed you can choose how long (how many keydown and keypress) the key is pressed `{key>3}`.
|
|
601
|
+
* You can then release the key per `{key>3/}` or keep it pressed and continue with the next key.
|
|
602
|
+
*/ function parseKeyDef$1(keyboardMap, text) {
|
|
603
|
+
const defs = [];
|
|
604
|
+
do {
|
|
605
|
+
const { type, descriptor, consumedLength, releasePrevious, releaseSelf = true, repeat } = readNextDescriptor(text);
|
|
606
|
+
var _keyboardMap_find;
|
|
607
|
+
const keyDef = (_keyboardMap_find = keyboardMap.find((def)=>{
|
|
608
|
+
if (type === '[') {
|
|
609
|
+
var _def_code;
|
|
610
|
+
return ((_def_code = def.code) === null || _def_code === undefined ? undefined : _def_code.toLowerCase()) === descriptor.toLowerCase();
|
|
611
|
+
} else if (type === '{') {
|
|
612
|
+
var _def_key;
|
|
613
|
+
return ((_def_key = def.key) === null || _def_key === undefined ? undefined : _def_key.toLowerCase()) === descriptor.toLowerCase();
|
|
614
|
+
}
|
|
615
|
+
return def.key === descriptor;
|
|
616
|
+
})) !== null && _keyboardMap_find !== undefined ? _keyboardMap_find : {
|
|
617
|
+
key: 'Unknown',
|
|
618
|
+
code: 'Unknown',
|
|
619
|
+
[type === '[' ? 'code' : 'key']: descriptor
|
|
620
|
+
};
|
|
621
|
+
defs.push({
|
|
622
|
+
keyDef,
|
|
623
|
+
releasePrevious,
|
|
624
|
+
releaseSelf,
|
|
625
|
+
repeat
|
|
626
|
+
});
|
|
627
|
+
text = text.slice(consumedLength);
|
|
628
|
+
}while (text)
|
|
629
|
+
return defs;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function parseKeyDef(text) {
|
|
633
|
+
return parseKeyDef$1(defaultKeyMap, text);
|
|
634
|
+
}
|
|
635
|
+
function replacer(code, values) {
|
|
636
|
+
return code.replace(/\{\s*(\w+)\s*\}/g, (_, key) => values[key] ?? _);
|
|
637
|
+
}
|
|
638
|
+
function resolveScreenshotPath(testPath, name, config, customPath) {
|
|
639
|
+
if (customPath) {
|
|
640
|
+
return resolve(dirname(testPath), customPath);
|
|
641
|
+
}
|
|
642
|
+
const dir = dirname(testPath);
|
|
643
|
+
const base = basename(testPath);
|
|
644
|
+
if (config.browser.screenshotDirectory) {
|
|
645
|
+
return resolve(config.browser.screenshotDirectory, relative(config.root, dir), base, name);
|
|
646
|
+
}
|
|
647
|
+
return resolve(dir, "__screenshots__", base, name);
|
|
648
|
+
}
|
|
649
|
+
async function getBrowserProvider(options, project) {
|
|
650
|
+
const browser = project.config.browser.name;
|
|
651
|
+
const name = project.name ? `[${project.name}] ` : "";
|
|
652
|
+
if (!browser) {
|
|
653
|
+
throw new Error(`${name}Browser name is required. Please, set \`test.browser.instances[].browser\` option manually.`);
|
|
654
|
+
}
|
|
655
|
+
if (options.provider == null) {
|
|
656
|
+
throw new Error(`Browser Mode requires the "provider" to always be specified.`);
|
|
657
|
+
}
|
|
658
|
+
const supportedBrowsers = options.provider.supportedBrowser || [];
|
|
659
|
+
if (supportedBrowsers.length && !supportedBrowsers.includes(browser)) {
|
|
660
|
+
throw new Error(`${name}Browser "${browser}" is not supported by the browser provider "${options.provider.name}". Supported browsers: ${supportedBrowsers.join(", ")}.`);
|
|
661
|
+
}
|
|
662
|
+
if (typeof options.provider.providerFactory !== "function") {
|
|
663
|
+
throw new TypeError(`The "${name}" browser provider does not provide a "providerFactory" function. Received ${typeof options.provider.providerFactory}.`);
|
|
664
|
+
}
|
|
665
|
+
return options.provider.providerFactory(project);
|
|
666
|
+
}
|
|
667
|
+
function slash(path) {
|
|
668
|
+
return path.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
async function resolveOrchestrator(globalServer, url, res) {
|
|
672
|
+
let sessionId = url.searchParams.get("sessionId");
|
|
673
|
+
// it's possible to open the page without a context
|
|
674
|
+
if (!sessionId) {
|
|
675
|
+
const contexts = [...globalServer.children].flatMap((p) => [...p.state.orchestrators.keys()]);
|
|
676
|
+
sessionId = contexts.at(-1) ?? "none";
|
|
677
|
+
}
|
|
678
|
+
// it's ok to not have a session here, especially in the preview provider
|
|
679
|
+
// because the user could refresh the page which would remove the session id from the url
|
|
680
|
+
const session = globalServer.vitest._browserSessions.getSession(sessionId);
|
|
681
|
+
const browserProject = session?.project.browser || [...globalServer.children][0];
|
|
682
|
+
if (!browserProject) {
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
// ignore unknown pages
|
|
686
|
+
if (sessionId && sessionId !== "none" && !globalServer.vitest._browserSessions.sessionIds.has(sessionId)) {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
375
689
|
const injectorJs = typeof globalServer.injectorJs === "string" ? globalServer.injectorJs : await globalServer.injectorJs;
|
|
376
690
|
const injector = replacer(injectorJs, {
|
|
377
691
|
__VITEST_PROVIDER__: JSON.stringify(browserProject.config.browser.provider?.name || "preview"),
|
|
@@ -514,36 +828,58 @@ function createTesterMiddleware(browserServer) {
|
|
|
514
828
|
};
|
|
515
829
|
}
|
|
516
830
|
|
|
517
|
-
const VIRTUAL_ID_CONTEXT = "\
|
|
518
|
-
const ID_CONTEXT = "
|
|
831
|
+
const VIRTUAL_ID_CONTEXT = "\0vitest/browser";
|
|
832
|
+
const ID_CONTEXT = "vitest/browser";
|
|
833
|
+
// for libraries that use an older import but are not type checked
|
|
834
|
+
const DEPRECATED_ID_CONTEXT = "@vitest/browser/context";
|
|
835
|
+
const DEPRECATED_VIRTUAL_ID_UTILS = "\0@vitest/browser/utils";
|
|
836
|
+
const DEPRECATED_ID_UTILS = "@vitest/browser/utils";
|
|
519
837
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
520
838
|
function BrowserContext(globalServer) {
|
|
521
839
|
return {
|
|
522
840
|
name: "vitest:browser:virtual-module:context",
|
|
523
841
|
enforce: "pre",
|
|
524
|
-
resolveId(id) {
|
|
842
|
+
resolveId(id, importer) {
|
|
525
843
|
if (id === ID_CONTEXT) {
|
|
526
844
|
return VIRTUAL_ID_CONTEXT;
|
|
527
845
|
}
|
|
846
|
+
if (id === DEPRECATED_ID_CONTEXT) {
|
|
847
|
+
if (importer) {
|
|
848
|
+
globalServer.vitest.logger.deprecate(`${importer} tries to load a deprecated "${id}" module. ` + `This import will stop working in the next major version. ` + `Please, use "vitest/browser" instead.`);
|
|
849
|
+
}
|
|
850
|
+
return VIRTUAL_ID_CONTEXT;
|
|
851
|
+
}
|
|
852
|
+
if (id === DEPRECATED_ID_UTILS) {
|
|
853
|
+
return DEPRECATED_VIRTUAL_ID_UTILS;
|
|
854
|
+
}
|
|
528
855
|
},
|
|
529
856
|
load(id) {
|
|
530
857
|
if (id === VIRTUAL_ID_CONTEXT) {
|
|
531
858
|
return generateContextFile.call(this, globalServer);
|
|
532
859
|
}
|
|
860
|
+
if (id === DEPRECATED_VIRTUAL_ID_UTILS) {
|
|
861
|
+
return `
|
|
862
|
+
import { utils } from 'vitest/browser'
|
|
863
|
+
export const getElementLocatorSelectors = utils.getElementLocatorSelectors
|
|
864
|
+
export const debug = utils.debug
|
|
865
|
+
export const prettyDOM = utils.prettyDOM
|
|
866
|
+
export const getElementError = utils.getElementError
|
|
867
|
+
`;
|
|
868
|
+
}
|
|
533
869
|
}
|
|
534
870
|
};
|
|
535
871
|
}
|
|
536
872
|
async function generateContextFile(globalServer) {
|
|
537
873
|
const commands = Object.keys(globalServer.commands);
|
|
538
|
-
const provider = [...globalServer.children][0].provider
|
|
539
|
-
const providerName = provider
|
|
874
|
+
const provider = [...globalServer.children][0].provider;
|
|
875
|
+
const providerName = provider?.name || "preview";
|
|
540
876
|
const commandsCode = commands.filter((command) => !command.startsWith("__vitest")).map((command) => {
|
|
541
877
|
return ` ["${command}"]: (...args) => __vitest_browser_runner__.commands.triggerCommand("${command}", args),`;
|
|
542
878
|
}).join("\n");
|
|
543
|
-
const userEventNonProviderImport = await getUserEventImport(
|
|
879
|
+
const userEventNonProviderImport = await getUserEventImport(provider, this.resolve.bind(this));
|
|
544
880
|
const distContextPath = slash$1(`/@fs/${resolve(__dirname, "context.js")}`);
|
|
545
881
|
return `
|
|
546
|
-
import { page, createUserEvent, cdp, locators } from '${distContextPath}'
|
|
882
|
+
import { page, createUserEvent, cdp, locators, utils } from '${distContextPath}'
|
|
547
883
|
${userEventNonProviderImport}
|
|
548
884
|
|
|
549
885
|
export const server = {
|
|
@@ -558,16 +894,17 @@ export const server = {
|
|
|
558
894
|
}
|
|
559
895
|
export const commands = server.commands
|
|
560
896
|
export const userEvent = createUserEvent(_userEventSetup)
|
|
561
|
-
export { page, cdp, locators }
|
|
897
|
+
export { page, cdp, locators, utils }
|
|
562
898
|
`;
|
|
563
899
|
}
|
|
564
900
|
async function getUserEventImport(provider, resolve) {
|
|
565
|
-
if (provider !== "preview") {
|
|
901
|
+
if (!provider || provider.name !== "preview") {
|
|
566
902
|
return "const _userEventSetup = undefined";
|
|
567
903
|
}
|
|
568
|
-
const
|
|
904
|
+
const previewDistRoot = provider.distRoot;
|
|
905
|
+
const resolved = await resolve("@testing-library/user-event", previewDistRoot);
|
|
569
906
|
if (!resolved) {
|
|
570
|
-
throw new Error(`Failed to resolve user-event package from ${
|
|
907
|
+
throw new Error(`Failed to resolve user-event package from ${previewDistRoot}`);
|
|
571
908
|
}
|
|
572
909
|
return `\
|
|
573
910
|
import { userEvent as __vitest_user_event__ } from '${slash$1(`/@fs/${resolved.id}`)}'
|
|
@@ -728,9 +1065,9 @@ var BrowserPlugin = (parentServer, base = "/") => {
|
|
|
728
1065
|
];
|
|
729
1066
|
const exclude = [
|
|
730
1067
|
"vitest",
|
|
1068
|
+
"vitest/browser",
|
|
731
1069
|
"vitest/internal/browser",
|
|
732
1070
|
"vitest/runners",
|
|
733
|
-
"@vitest/browser",
|
|
734
1071
|
"@vitest/browser/client",
|
|
735
1072
|
"@vitest/utils",
|
|
736
1073
|
"@vitest/utils/source-map",
|
|
@@ -772,10 +1109,12 @@ var BrowserPlugin = (parentServer, base = "/") => {
|
|
|
772
1109
|
const include = [
|
|
773
1110
|
"vitest > expect-type",
|
|
774
1111
|
"vitest > @vitest/snapshot > magic-string",
|
|
775
|
-
"vitest > @vitest/expect > chai"
|
|
776
|
-
"@vitest/browser > @testing-library/user-event",
|
|
777
|
-
"@vitest/browser > @testing-library/dom"
|
|
1112
|
+
"vitest > @vitest/expect > chai"
|
|
778
1113
|
];
|
|
1114
|
+
const provider = parentServer.config.browser.provider || [...parentServer.children][0]?.provider;
|
|
1115
|
+
if (provider?.name === "preview") {
|
|
1116
|
+
include.push("@vitest/browser-preview > @testing-library/user-event", "@vitest/browser-preview > @testing-library/dom");
|
|
1117
|
+
}
|
|
779
1118
|
const fileRoot = browserTestFiles[0] ? dirname(browserTestFiles[0]) : project.config.root;
|
|
780
1119
|
const svelte = isPackageExists("vitest-browser-svelte", fileRoot);
|
|
781
1120
|
if (svelte) {
|
|
@@ -989,14 +1328,14 @@ body {
|
|
|
989
1328
|
},
|
|
990
1329
|
injectTo: "head"
|
|
991
1330
|
},
|
|
992
|
-
parentServer.
|
|
1331
|
+
...parentServer.initScripts.map((script) => ({
|
|
993
1332
|
tag: "script",
|
|
994
1333
|
attrs: {
|
|
995
1334
|
type: "module",
|
|
996
|
-
src:
|
|
1335
|
+
src: join("/@fs/", script)
|
|
997
1336
|
},
|
|
998
1337
|
injectTo: "head"
|
|
999
|
-
}
|
|
1338
|
+
})),
|
|
1000
1339
|
...testerTags
|
|
1001
1340
|
].filter((s) => s != null);
|
|
1002
1341
|
}
|
|
@@ -1110,114 +1449,6 @@ class BrowserServerCDPHandler {
|
|
|
1110
1449
|
}
|
|
1111
1450
|
}
|
|
1112
1451
|
|
|
1113
|
-
const clear = async (context, selector) => {
|
|
1114
|
-
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
1115
|
-
const { iframe } = context;
|
|
1116
|
-
const element = iframe.locator(selector);
|
|
1117
|
-
await element.clear();
|
|
1118
|
-
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
1119
|
-
const browser = context.browser;
|
|
1120
|
-
const element = await browser.$(selector);
|
|
1121
|
-
await element.clearValue();
|
|
1122
|
-
} else {
|
|
1123
|
-
throw new TypeError(`Provider "${context.provider.name}" does not support clearing elements`);
|
|
1124
|
-
}
|
|
1125
|
-
};
|
|
1126
|
-
|
|
1127
|
-
const click = async (context, selector, options = {}) => {
|
|
1128
|
-
const provider = context.provider;
|
|
1129
|
-
if (provider instanceof PlaywrightBrowserProvider) {
|
|
1130
|
-
const tester = context.iframe;
|
|
1131
|
-
await tester.locator(selector).click(options);
|
|
1132
|
-
} else if (provider instanceof WebdriverBrowserProvider) {
|
|
1133
|
-
const browser = context.browser;
|
|
1134
|
-
await browser.$(selector).click(options);
|
|
1135
|
-
} else {
|
|
1136
|
-
throw new TypeError(`Provider "${provider.name}" doesn't support click command`);
|
|
1137
|
-
}
|
|
1138
|
-
};
|
|
1139
|
-
const dblClick = async (context, selector, options = {}) => {
|
|
1140
|
-
const provider = context.provider;
|
|
1141
|
-
if (provider instanceof PlaywrightBrowserProvider) {
|
|
1142
|
-
const tester = context.iframe;
|
|
1143
|
-
await tester.locator(selector).dblclick(options);
|
|
1144
|
-
} else if (provider instanceof WebdriverBrowserProvider) {
|
|
1145
|
-
const browser = context.browser;
|
|
1146
|
-
await browser.$(selector).doubleClick();
|
|
1147
|
-
} else {
|
|
1148
|
-
throw new TypeError(`Provider "${provider.name}" doesn't support dblClick command`);
|
|
1149
|
-
}
|
|
1150
|
-
};
|
|
1151
|
-
const tripleClick = async (context, selector, options = {}) => {
|
|
1152
|
-
const provider = context.provider;
|
|
1153
|
-
if (provider instanceof PlaywrightBrowserProvider) {
|
|
1154
|
-
const tester = context.iframe;
|
|
1155
|
-
await tester.locator(selector).click({
|
|
1156
|
-
...options,
|
|
1157
|
-
clickCount: 3
|
|
1158
|
-
});
|
|
1159
|
-
} else if (provider instanceof WebdriverBrowserProvider) {
|
|
1160
|
-
const browser = context.browser;
|
|
1161
|
-
await browser.action("pointer", { parameters: { pointerType: "mouse" } }).move({ origin: browser.$(selector) }).down().up().pause(50).down().up().pause(50).down().up().pause(50).perform();
|
|
1162
|
-
} else {
|
|
1163
|
-
throw new TypeError(`Provider "${provider.name}" doesn't support tripleClick command`);
|
|
1164
|
-
}
|
|
1165
|
-
};
|
|
1166
|
-
|
|
1167
|
-
const dragAndDrop = async (context, source, target, options_) => {
|
|
1168
|
-
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
1169
|
-
const frame = await context.frame();
|
|
1170
|
-
await frame.dragAndDrop(source, target, options_);
|
|
1171
|
-
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
1172
|
-
const $source = context.browser.$(source);
|
|
1173
|
-
const $target = context.browser.$(target);
|
|
1174
|
-
const options = options_ || {};
|
|
1175
|
-
const duration = options.duration ?? 10;
|
|
1176
|
-
// https://github.com/webdriverio/webdriverio/issues/8022#issuecomment-1700919670
|
|
1177
|
-
await context.browser.action("pointer").move({
|
|
1178
|
-
duration: 0,
|
|
1179
|
-
origin: $source,
|
|
1180
|
-
x: options.sourceX ?? 0,
|
|
1181
|
-
y: options.sourceY ?? 0
|
|
1182
|
-
}).down({ button: 0 }).move({
|
|
1183
|
-
duration: 0,
|
|
1184
|
-
origin: "pointer",
|
|
1185
|
-
x: 0,
|
|
1186
|
-
y: 0
|
|
1187
|
-
}).pause(duration).move({
|
|
1188
|
-
duration: 0,
|
|
1189
|
-
origin: $target,
|
|
1190
|
-
x: options.targetX ?? 0,
|
|
1191
|
-
y: options.targetY ?? 0
|
|
1192
|
-
}).move({
|
|
1193
|
-
duration: 0,
|
|
1194
|
-
origin: "pointer",
|
|
1195
|
-
x: 1,
|
|
1196
|
-
y: 0
|
|
1197
|
-
}).move({
|
|
1198
|
-
duration: 0,
|
|
1199
|
-
origin: "pointer",
|
|
1200
|
-
x: -1,
|
|
1201
|
-
y: 0
|
|
1202
|
-
}).up({ button: 0 }).perform();
|
|
1203
|
-
} else {
|
|
1204
|
-
throw new TypeError(`Provider "${context.provider.name}" does not support dragging elements`);
|
|
1205
|
-
}
|
|
1206
|
-
};
|
|
1207
|
-
|
|
1208
|
-
const fill = async (context, selector, text, options = {}) => {
|
|
1209
|
-
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
1210
|
-
const { iframe } = context;
|
|
1211
|
-
const element = iframe.locator(selector);
|
|
1212
|
-
await element.fill(text, options);
|
|
1213
|
-
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
1214
|
-
const browser = context.browser;
|
|
1215
|
-
await browser.$(selector).setValue(text);
|
|
1216
|
-
} else {
|
|
1217
|
-
throw new TypeError(`Provider "${context.provider.name}" does not support filling inputs`);
|
|
1218
|
-
}
|
|
1219
|
-
};
|
|
1220
|
-
|
|
1221
1452
|
const types = {
|
|
1222
1453
|
'application/andrew-inset': ['ez'],
|
|
1223
1454
|
'application/appinstaller': ['appinstaller'],
|
|
@@ -1668,796 +1899,55 @@ class Mime {
|
|
|
1668
1899
|
}
|
|
1669
1900
|
_Mime_extensionToType = new WeakMap(), _Mime_typeToExtension = new WeakMap(), _Mime_typeToExtensions = new WeakMap();
|
|
1670
1901
|
|
|
1671
|
-
var mime = new Mime(types)._freeze();
|
|
1672
|
-
|
|
1673
|
-
function assertFileAccess(path, project) {
|
|
1674
|
-
if (!isFileServingAllowed(path, project.vite) && !isFileServingAllowed(path, project.vitest.vite)) {
|
|
1675
|
-
throw new Error(`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`);
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
const readFile = async ({ project }, path, options = {}) => {
|
|
1679
|
-
const filepath = resolve$1(project.config.root, path);
|
|
1680
|
-
assertFileAccess(filepath, project);
|
|
1681
|
-
// never return a Buffer
|
|
1682
|
-
if (typeof options === "object" && !options.encoding) {
|
|
1683
|
-
options.encoding = "utf-8";
|
|
1684
|
-
}
|
|
1685
|
-
return promises.readFile(filepath, options);
|
|
1686
|
-
};
|
|
1687
|
-
const writeFile = async ({ project }, path, data, options) => {
|
|
1688
|
-
const filepath = resolve$1(project.config.root, path);
|
|
1689
|
-
assertFileAccess(filepath, project);
|
|
1690
|
-
const dir = dirname$1(filepath);
|
|
1691
|
-
if (!fs.existsSync(dir)) {
|
|
1692
|
-
await promises.mkdir(dir, { recursive: true });
|
|
1693
|
-
}
|
|
1694
|
-
await promises.writeFile(filepath, data, options);
|
|
1695
|
-
};
|
|
1696
|
-
const removeFile = async ({ project }, path) => {
|
|
1697
|
-
const filepath = resolve$1(project.config.root, path);
|
|
1698
|
-
assertFileAccess(filepath, project);
|
|
1699
|
-
await promises.rm(filepath);
|
|
1700
|
-
};
|
|
1701
|
-
const _fileInfo = async ({ project }, path, encoding) => {
|
|
1702
|
-
const filepath = resolve$1(project.config.root, path);
|
|
1703
|
-
assertFileAccess(filepath, project);
|
|
1704
|
-
const content = await promises.readFile(filepath, encoding || "base64");
|
|
1705
|
-
return {
|
|
1706
|
-
content,
|
|
1707
|
-
basename: basename$1(filepath),
|
|
1708
|
-
mime: mime.getType(filepath)
|
|
1709
|
-
};
|
|
1710
|
-
};
|
|
1711
|
-
|
|
1712
|
-
const hover = async (context, selector, options = {}) => {
|
|
1713
|
-
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
1714
|
-
await context.iframe.locator(selector).hover(options);
|
|
1715
|
-
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
1716
|
-
const browser = context.browser;
|
|
1717
|
-
await browser.$(selector).moveTo(options);
|
|
1718
|
-
} else {
|
|
1719
|
-
throw new TypeError(`Provider "${context.provider.name}" does not support hover`);
|
|
1720
|
-
}
|
|
1721
|
-
};
|
|
1722
|
-
|
|
1723
|
-
var DOM_KEY_LOCATION = /*#__PURE__*/ function(DOM_KEY_LOCATION) {
|
|
1724
|
-
DOM_KEY_LOCATION[DOM_KEY_LOCATION["STANDARD"] = 0] = "STANDARD";
|
|
1725
|
-
DOM_KEY_LOCATION[DOM_KEY_LOCATION["LEFT"] = 1] = "LEFT";
|
|
1726
|
-
DOM_KEY_LOCATION[DOM_KEY_LOCATION["RIGHT"] = 2] = "RIGHT";
|
|
1727
|
-
DOM_KEY_LOCATION[DOM_KEY_LOCATION["NUMPAD"] = 3] = "NUMPAD";
|
|
1728
|
-
return DOM_KEY_LOCATION;
|
|
1729
|
-
}({});
|
|
1730
|
-
|
|
1731
|
-
/**
|
|
1732
|
-
* Mapping for a default US-104-QWERTY keyboard
|
|
1733
|
-
*/ const defaultKeyMap = [
|
|
1734
|
-
// alphanumeric block - writing system
|
|
1735
|
-
...'0123456789'.split('').map((c)=>({
|
|
1736
|
-
code: `Digit${c}`,
|
|
1737
|
-
key: c
|
|
1738
|
-
})),
|
|
1739
|
-
...')!@#$%^&*('.split('').map((c, i)=>({
|
|
1740
|
-
code: `Digit${i}`,
|
|
1741
|
-
key: c,
|
|
1742
|
-
shiftKey: true
|
|
1743
|
-
})),
|
|
1744
|
-
...'abcdefghijklmnopqrstuvwxyz'.split('').map((c)=>({
|
|
1745
|
-
code: `Key${c.toUpperCase()}`,
|
|
1746
|
-
key: c
|
|
1747
|
-
})),
|
|
1748
|
-
...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').map((c)=>({
|
|
1749
|
-
code: `Key${c}`,
|
|
1750
|
-
key: c,
|
|
1751
|
-
shiftKey: true
|
|
1752
|
-
})),
|
|
1753
|
-
{
|
|
1754
|
-
code: 'BracketLeft',
|
|
1755
|
-
key: '['
|
|
1756
|
-
},
|
|
1757
|
-
{
|
|
1758
|
-
code: 'BracketLeft',
|
|
1759
|
-
key: '{',
|
|
1760
|
-
shiftKey: true
|
|
1761
|
-
},
|
|
1762
|
-
{
|
|
1763
|
-
code: 'BracketRight',
|
|
1764
|
-
key: ']'
|
|
1765
|
-
},
|
|
1766
|
-
{
|
|
1767
|
-
code: 'BracketRight',
|
|
1768
|
-
key: '}',
|
|
1769
|
-
shiftKey: true
|
|
1770
|
-
},
|
|
1771
|
-
// alphanumeric block - functional
|
|
1772
|
-
{
|
|
1773
|
-
code: 'Space',
|
|
1774
|
-
key: ' '
|
|
1775
|
-
},
|
|
1776
|
-
{
|
|
1777
|
-
code: 'AltLeft',
|
|
1778
|
-
key: 'Alt',
|
|
1779
|
-
location: DOM_KEY_LOCATION.LEFT
|
|
1780
|
-
},
|
|
1781
|
-
{
|
|
1782
|
-
code: 'AltRight',
|
|
1783
|
-
key: 'Alt',
|
|
1784
|
-
location: DOM_KEY_LOCATION.RIGHT
|
|
1785
|
-
},
|
|
1786
|
-
{
|
|
1787
|
-
code: 'ShiftLeft',
|
|
1788
|
-
key: 'Shift',
|
|
1789
|
-
location: DOM_KEY_LOCATION.LEFT
|
|
1790
|
-
},
|
|
1791
|
-
{
|
|
1792
|
-
code: 'ShiftRight',
|
|
1793
|
-
key: 'Shift',
|
|
1794
|
-
location: DOM_KEY_LOCATION.RIGHT
|
|
1795
|
-
},
|
|
1796
|
-
{
|
|
1797
|
-
code: 'ControlLeft',
|
|
1798
|
-
key: 'Control',
|
|
1799
|
-
location: DOM_KEY_LOCATION.LEFT
|
|
1800
|
-
},
|
|
1801
|
-
{
|
|
1802
|
-
code: 'ControlRight',
|
|
1803
|
-
key: 'Control',
|
|
1804
|
-
location: DOM_KEY_LOCATION.RIGHT
|
|
1805
|
-
},
|
|
1806
|
-
{
|
|
1807
|
-
code: 'MetaLeft',
|
|
1808
|
-
key: 'Meta',
|
|
1809
|
-
location: DOM_KEY_LOCATION.LEFT
|
|
1810
|
-
},
|
|
1811
|
-
{
|
|
1812
|
-
code: 'MetaRight',
|
|
1813
|
-
key: 'Meta',
|
|
1814
|
-
location: DOM_KEY_LOCATION.RIGHT
|
|
1815
|
-
},
|
|
1816
|
-
{
|
|
1817
|
-
code: 'OSLeft',
|
|
1818
|
-
key: 'OS',
|
|
1819
|
-
location: DOM_KEY_LOCATION.LEFT
|
|
1820
|
-
},
|
|
1821
|
-
{
|
|
1822
|
-
code: 'OSRight',
|
|
1823
|
-
key: 'OS',
|
|
1824
|
-
location: DOM_KEY_LOCATION.RIGHT
|
|
1825
|
-
},
|
|
1826
|
-
{
|
|
1827
|
-
code: 'ContextMenu',
|
|
1828
|
-
key: 'ContextMenu'
|
|
1829
|
-
},
|
|
1830
|
-
{
|
|
1831
|
-
code: 'Tab',
|
|
1832
|
-
key: 'Tab'
|
|
1833
|
-
},
|
|
1834
|
-
{
|
|
1835
|
-
code: 'CapsLock',
|
|
1836
|
-
key: 'CapsLock'
|
|
1837
|
-
},
|
|
1838
|
-
{
|
|
1839
|
-
code: 'Backspace',
|
|
1840
|
-
key: 'Backspace'
|
|
1841
|
-
},
|
|
1842
|
-
{
|
|
1843
|
-
code: 'Enter',
|
|
1844
|
-
key: 'Enter'
|
|
1845
|
-
},
|
|
1846
|
-
// function
|
|
1847
|
-
{
|
|
1848
|
-
code: 'Escape',
|
|
1849
|
-
key: 'Escape'
|
|
1850
|
-
},
|
|
1851
|
-
// arrows
|
|
1852
|
-
{
|
|
1853
|
-
code: 'ArrowUp',
|
|
1854
|
-
key: 'ArrowUp'
|
|
1855
|
-
},
|
|
1856
|
-
{
|
|
1857
|
-
code: 'ArrowDown',
|
|
1858
|
-
key: 'ArrowDown'
|
|
1859
|
-
},
|
|
1860
|
-
{
|
|
1861
|
-
code: 'ArrowLeft',
|
|
1862
|
-
key: 'ArrowLeft'
|
|
1863
|
-
},
|
|
1864
|
-
{
|
|
1865
|
-
code: 'ArrowRight',
|
|
1866
|
-
key: 'ArrowRight'
|
|
1867
|
-
},
|
|
1868
|
-
// control pad
|
|
1869
|
-
{
|
|
1870
|
-
code: 'Home',
|
|
1871
|
-
key: 'Home'
|
|
1872
|
-
},
|
|
1873
|
-
{
|
|
1874
|
-
code: 'End',
|
|
1875
|
-
key: 'End'
|
|
1876
|
-
},
|
|
1877
|
-
{
|
|
1878
|
-
code: 'Delete',
|
|
1879
|
-
key: 'Delete'
|
|
1880
|
-
},
|
|
1881
|
-
{
|
|
1882
|
-
code: 'PageUp',
|
|
1883
|
-
key: 'PageUp'
|
|
1884
|
-
},
|
|
1885
|
-
{
|
|
1886
|
-
code: 'PageDown',
|
|
1887
|
-
key: 'PageDown'
|
|
1888
|
-
},
|
|
1889
|
-
// Special keys that are not part of a default US-layout but included for specific behavior
|
|
1890
|
-
{
|
|
1891
|
-
code: 'Fn',
|
|
1892
|
-
key: 'Fn'
|
|
1893
|
-
},
|
|
1894
|
-
{
|
|
1895
|
-
code: 'Symbol',
|
|
1896
|
-
key: 'Symbol'
|
|
1897
|
-
},
|
|
1898
|
-
{
|
|
1899
|
-
code: 'AltRight',
|
|
1900
|
-
key: 'AltGraph'
|
|
1901
|
-
}
|
|
1902
|
-
];
|
|
1903
|
-
|
|
1904
|
-
var bracketDict = /*#__PURE__*/ function(bracketDict) {
|
|
1905
|
-
bracketDict["{"] = "}";
|
|
1906
|
-
bracketDict["["] = "]";
|
|
1907
|
-
return bracketDict;
|
|
1908
|
-
}(bracketDict || {});
|
|
1909
|
-
/**
|
|
1910
|
-
* Read the next key definition from user input
|
|
1911
|
-
*
|
|
1912
|
-
* Describe key per `{descriptor}` or `[descriptor]`.
|
|
1913
|
-
* Everything else will be interpreted as a single character as descriptor - e.g. `a`.
|
|
1914
|
-
* Brackets `{` and `[` can be escaped by doubling - e.g. `foo[[bar` translates to `foo[bar`.
|
|
1915
|
-
* A previously pressed key can be released per `{/descriptor}`.
|
|
1916
|
-
* Keeping the key pressed can be written as `{descriptor>}`.
|
|
1917
|
-
* When keeping the key pressed you can choose how long the key is pressed `{descriptor>3}`.
|
|
1918
|
-
* You can then release the key per `{descriptor>3/}` or keep it pressed and continue with the next key.
|
|
1919
|
-
*/ function readNextDescriptor(text, context) {
|
|
1920
|
-
let pos = 0;
|
|
1921
|
-
const startBracket = text[pos] in bracketDict ? text[pos] : '';
|
|
1922
|
-
pos += startBracket.length;
|
|
1923
|
-
const isEscapedChar = new RegExp(`^\\${startBracket}{2}`).test(text);
|
|
1924
|
-
const type = isEscapedChar ? '' : startBracket;
|
|
1925
|
-
return {
|
|
1926
|
-
type,
|
|
1927
|
-
...type === '' ? readPrintableChar(text, pos) : readTag(text, pos, type)
|
|
1928
|
-
};
|
|
1929
|
-
}
|
|
1930
|
-
function readPrintableChar(text, pos, context) {
|
|
1931
|
-
const descriptor = text[pos];
|
|
1932
|
-
assertDescriptor(descriptor, text, pos);
|
|
1933
|
-
pos += descriptor.length;
|
|
1934
|
-
return {
|
|
1935
|
-
consumedLength: pos,
|
|
1936
|
-
descriptor,
|
|
1937
|
-
releasePrevious: false,
|
|
1938
|
-
releaseSelf: true,
|
|
1939
|
-
repeat: 1
|
|
1940
|
-
};
|
|
1941
|
-
}
|
|
1942
|
-
function readTag(text, pos, startBracket, context) {
|
|
1943
|
-
var _text_slice_match, _text_slice_match1;
|
|
1944
|
-
const releasePreviousModifier = text[pos] === '/' ? '/' : '';
|
|
1945
|
-
pos += releasePreviousModifier.length;
|
|
1946
|
-
const escapedDescriptor = startBracket === '{' && text[pos] === '\\';
|
|
1947
|
-
pos += Number(escapedDescriptor);
|
|
1948
|
-
const descriptor = escapedDescriptor ? text[pos] : (_text_slice_match = text.slice(pos).match(startBracket === '{' ? /^\w+|^[^}>/]/ : /^\w+/)) === null || _text_slice_match === undefined ? undefined : _text_slice_match[0];
|
|
1949
|
-
assertDescriptor(descriptor, text, pos);
|
|
1950
|
-
pos += descriptor.length;
|
|
1951
|
-
var _text_slice_match_;
|
|
1952
|
-
const repeatModifier = (_text_slice_match_ = (_text_slice_match1 = text.slice(pos).match(/^>\d+/)) === null || _text_slice_match1 === undefined ? undefined : _text_slice_match1[0]) !== null && _text_slice_match_ !== undefined ? _text_slice_match_ : '';
|
|
1953
|
-
pos += repeatModifier.length;
|
|
1954
|
-
const releaseSelfModifier = text[pos] === '/' || !repeatModifier && text[pos] === '>' ? text[pos] : '';
|
|
1955
|
-
pos += releaseSelfModifier.length;
|
|
1956
|
-
const expectedEndBracket = bracketDict[startBracket];
|
|
1957
|
-
const endBracket = text[pos] === expectedEndBracket ? expectedEndBracket : '';
|
|
1958
|
-
if (!endBracket) {
|
|
1959
|
-
throw new Error(getErrorMessage([
|
|
1960
|
-
!repeatModifier && 'repeat modifier',
|
|
1961
|
-
!releaseSelfModifier && 'release modifier',
|
|
1962
|
-
`"${expectedEndBracket}"`
|
|
1963
|
-
].filter(Boolean).join(' or '), text[pos], text));
|
|
1964
|
-
}
|
|
1965
|
-
pos += endBracket.length;
|
|
1966
|
-
return {
|
|
1967
|
-
consumedLength: pos,
|
|
1968
|
-
descriptor,
|
|
1969
|
-
releasePrevious: !!releasePreviousModifier,
|
|
1970
|
-
repeat: repeatModifier ? Math.max(Number(repeatModifier.substr(1)), 1) : 1,
|
|
1971
|
-
releaseSelf: hasReleaseSelf(releaseSelfModifier, repeatModifier)
|
|
1972
|
-
};
|
|
1973
|
-
}
|
|
1974
|
-
function assertDescriptor(descriptor, text, pos, context) {
|
|
1975
|
-
if (!descriptor) {
|
|
1976
|
-
throw new Error(getErrorMessage('key descriptor', text[pos], text));
|
|
1977
|
-
}
|
|
1978
|
-
}
|
|
1979
|
-
function hasReleaseSelf(releaseSelfModifier, repeatModifier) {
|
|
1980
|
-
if (releaseSelfModifier) {
|
|
1981
|
-
return releaseSelfModifier === '/';
|
|
1982
|
-
}
|
|
1983
|
-
if (repeatModifier) {
|
|
1984
|
-
return false;
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
function getErrorMessage(expected, found, text, context) {
|
|
1988
|
-
return `Expected ${expected} but found "${found !== null && found !== undefined ? found : ''}" in "${text}"
|
|
1989
|
-
See ${`https://testing-library.com/docs/user-event/keyboard`}
|
|
1990
|
-
for more information about how userEvent parses your input.`;
|
|
1991
|
-
}
|
|
1992
|
-
|
|
1993
|
-
/**
|
|
1994
|
-
* Parse key definitions per `keyboardMap`
|
|
1995
|
-
*
|
|
1996
|
-
* Keys can be referenced by `{key}` or `{special}` as well as physical locations per `[code]`.
|
|
1997
|
-
* Everything else will be interpreted as a typed character - e.g. `a`.
|
|
1998
|
-
* Brackets `{` and `[` can be escaped by doubling - e.g. `foo[[bar` translates to `foo[bar`.
|
|
1999
|
-
* Keeping the key pressed can be written as `{key>}`.
|
|
2000
|
-
* When keeping the key pressed you can choose how long (how many keydown and keypress) the key is pressed `{key>3}`.
|
|
2001
|
-
* You can then release the key per `{key>3/}` or keep it pressed and continue with the next key.
|
|
2002
|
-
*/ function parseKeyDef(keyboardMap, text) {
|
|
2003
|
-
const defs = [];
|
|
2004
|
-
do {
|
|
2005
|
-
const { type, descriptor, consumedLength, releasePrevious, releaseSelf = true, repeat } = readNextDescriptor(text);
|
|
2006
|
-
var _keyboardMap_find;
|
|
2007
|
-
const keyDef = (_keyboardMap_find = keyboardMap.find((def)=>{
|
|
2008
|
-
if (type === '[') {
|
|
2009
|
-
var _def_code;
|
|
2010
|
-
return ((_def_code = def.code) === null || _def_code === undefined ? undefined : _def_code.toLowerCase()) === descriptor.toLowerCase();
|
|
2011
|
-
} else if (type === '{') {
|
|
2012
|
-
var _def_key;
|
|
2013
|
-
return ((_def_key = def.key) === null || _def_key === undefined ? undefined : _def_key.toLowerCase()) === descriptor.toLowerCase();
|
|
2014
|
-
}
|
|
2015
|
-
return def.key === descriptor;
|
|
2016
|
-
})) !== null && _keyboardMap_find !== undefined ? _keyboardMap_find : {
|
|
2017
|
-
key: 'Unknown',
|
|
2018
|
-
code: 'Unknown',
|
|
2019
|
-
[type === '[' ? 'code' : 'key']: descriptor
|
|
2020
|
-
};
|
|
2021
|
-
defs.push({
|
|
2022
|
-
keyDef,
|
|
2023
|
-
releasePrevious,
|
|
2024
|
-
releaseSelf,
|
|
2025
|
-
repeat
|
|
2026
|
-
});
|
|
2027
|
-
text = text.slice(consumedLength);
|
|
2028
|
-
}while (text)
|
|
2029
|
-
return defs;
|
|
2030
|
-
}
|
|
2031
|
-
|
|
2032
|
-
const keyboard = async (context, text, state) => {
|
|
2033
|
-
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
2034
|
-
const frame = await context.frame();
|
|
2035
|
-
await frame.evaluate(focusIframe);
|
|
2036
|
-
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
2037
|
-
await context.browser.execute(focusIframe);
|
|
2038
|
-
}
|
|
2039
|
-
const pressed = new Set(state.unreleased);
|
|
2040
|
-
await keyboardImplementation(pressed, context.provider, context.sessionId, text, async () => {
|
|
2041
|
-
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
2042
|
-
const frame = await context.frame();
|
|
2043
|
-
await frame.evaluate(selectAll);
|
|
2044
|
-
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
2045
|
-
await context.browser.execute(selectAll);
|
|
2046
|
-
} else {
|
|
2047
|
-
throw new TypeError(`Provider "${context.provider.name}" does not support selecting all text`);
|
|
2048
|
-
}
|
|
2049
|
-
}, true);
|
|
2050
|
-
return { unreleased: Array.from(pressed) };
|
|
2051
|
-
};
|
|
2052
|
-
const keyboardCleanup = async (context, state) => {
|
|
2053
|
-
const { provider, sessionId } = context;
|
|
2054
|
-
if (!state.unreleased) {
|
|
2055
|
-
return;
|
|
2056
|
-
}
|
|
2057
|
-
if (provider instanceof PlaywrightBrowserProvider) {
|
|
2058
|
-
const page = provider.getPage(sessionId);
|
|
2059
|
-
for (const key of state.unreleased) {
|
|
2060
|
-
await page.keyboard.up(key);
|
|
2061
|
-
}
|
|
2062
|
-
} else if (provider instanceof WebdriverBrowserProvider) {
|
|
2063
|
-
const keyboard = provider.browser.action("key");
|
|
2064
|
-
for (const key of state.unreleased) {
|
|
2065
|
-
keyboard.up(key);
|
|
2066
|
-
}
|
|
2067
|
-
await keyboard.perform();
|
|
2068
|
-
} else {
|
|
2069
|
-
throw new TypeError(`Provider "${context.provider.name}" does not support keyboard api`);
|
|
2070
|
-
}
|
|
2071
|
-
};
|
|
2072
|
-
// fallback to insertText for non US key
|
|
2073
|
-
// https://github.com/microsoft/playwright/blob/50775698ae13642742f2a1e8983d1d686d7f192d/packages/playwright-core/src/server/input.ts#L95
|
|
2074
|
-
const VALID_KEYS = new Set([
|
|
2075
|
-
"Escape",
|
|
2076
|
-
"F1",
|
|
2077
|
-
"F2",
|
|
2078
|
-
"F3",
|
|
2079
|
-
"F4",
|
|
2080
|
-
"F5",
|
|
2081
|
-
"F6",
|
|
2082
|
-
"F7",
|
|
2083
|
-
"F8",
|
|
2084
|
-
"F9",
|
|
2085
|
-
"F10",
|
|
2086
|
-
"F11",
|
|
2087
|
-
"F12",
|
|
2088
|
-
"Backquote",
|
|
2089
|
-
"`",
|
|
2090
|
-
"~",
|
|
2091
|
-
"Digit1",
|
|
2092
|
-
"1",
|
|
2093
|
-
"!",
|
|
2094
|
-
"Digit2",
|
|
2095
|
-
"2",
|
|
2096
|
-
"@",
|
|
2097
|
-
"Digit3",
|
|
2098
|
-
"3",
|
|
2099
|
-
"#",
|
|
2100
|
-
"Digit4",
|
|
2101
|
-
"4",
|
|
2102
|
-
"$",
|
|
2103
|
-
"Digit5",
|
|
2104
|
-
"5",
|
|
2105
|
-
"%",
|
|
2106
|
-
"Digit6",
|
|
2107
|
-
"6",
|
|
2108
|
-
"^",
|
|
2109
|
-
"Digit7",
|
|
2110
|
-
"7",
|
|
2111
|
-
"&",
|
|
2112
|
-
"Digit8",
|
|
2113
|
-
"8",
|
|
2114
|
-
"*",
|
|
2115
|
-
"Digit9",
|
|
2116
|
-
"9",
|
|
2117
|
-
"(",
|
|
2118
|
-
"Digit0",
|
|
2119
|
-
"0",
|
|
2120
|
-
")",
|
|
2121
|
-
"Minus",
|
|
2122
|
-
"-",
|
|
2123
|
-
"_",
|
|
2124
|
-
"Equal",
|
|
2125
|
-
"=",
|
|
2126
|
-
"+",
|
|
2127
|
-
"Backslash",
|
|
2128
|
-
"\\",
|
|
2129
|
-
"|",
|
|
2130
|
-
"Backspace",
|
|
2131
|
-
"Tab",
|
|
2132
|
-
"KeyQ",
|
|
2133
|
-
"q",
|
|
2134
|
-
"Q",
|
|
2135
|
-
"KeyW",
|
|
2136
|
-
"w",
|
|
2137
|
-
"W",
|
|
2138
|
-
"KeyE",
|
|
2139
|
-
"e",
|
|
2140
|
-
"E",
|
|
2141
|
-
"KeyR",
|
|
2142
|
-
"r",
|
|
2143
|
-
"R",
|
|
2144
|
-
"KeyT",
|
|
2145
|
-
"t",
|
|
2146
|
-
"T",
|
|
2147
|
-
"KeyY",
|
|
2148
|
-
"y",
|
|
2149
|
-
"Y",
|
|
2150
|
-
"KeyU",
|
|
2151
|
-
"u",
|
|
2152
|
-
"U",
|
|
2153
|
-
"KeyI",
|
|
2154
|
-
"i",
|
|
2155
|
-
"I",
|
|
2156
|
-
"KeyO",
|
|
2157
|
-
"o",
|
|
2158
|
-
"O",
|
|
2159
|
-
"KeyP",
|
|
2160
|
-
"p",
|
|
2161
|
-
"P",
|
|
2162
|
-
"BracketLeft",
|
|
2163
|
-
"[",
|
|
2164
|
-
"{",
|
|
2165
|
-
"BracketRight",
|
|
2166
|
-
"]",
|
|
2167
|
-
"}",
|
|
2168
|
-
"CapsLock",
|
|
2169
|
-
"KeyA",
|
|
2170
|
-
"a",
|
|
2171
|
-
"A",
|
|
2172
|
-
"KeyS",
|
|
2173
|
-
"s",
|
|
2174
|
-
"S",
|
|
2175
|
-
"KeyD",
|
|
2176
|
-
"d",
|
|
2177
|
-
"D",
|
|
2178
|
-
"KeyF",
|
|
2179
|
-
"f",
|
|
2180
|
-
"F",
|
|
2181
|
-
"KeyG",
|
|
2182
|
-
"g",
|
|
2183
|
-
"G",
|
|
2184
|
-
"KeyH",
|
|
2185
|
-
"h",
|
|
2186
|
-
"H",
|
|
2187
|
-
"KeyJ",
|
|
2188
|
-
"j",
|
|
2189
|
-
"J",
|
|
2190
|
-
"KeyK",
|
|
2191
|
-
"k",
|
|
2192
|
-
"K",
|
|
2193
|
-
"KeyL",
|
|
2194
|
-
"l",
|
|
2195
|
-
"L",
|
|
2196
|
-
"Semicolon",
|
|
2197
|
-
";",
|
|
2198
|
-
":",
|
|
2199
|
-
"Quote",
|
|
2200
|
-
"'",
|
|
2201
|
-
"\"",
|
|
2202
|
-
"Enter",
|
|
2203
|
-
"\n",
|
|
2204
|
-
"\r",
|
|
2205
|
-
"ShiftLeft",
|
|
2206
|
-
"Shift",
|
|
2207
|
-
"KeyZ",
|
|
2208
|
-
"z",
|
|
2209
|
-
"Z",
|
|
2210
|
-
"KeyX",
|
|
2211
|
-
"x",
|
|
2212
|
-
"X",
|
|
2213
|
-
"KeyC",
|
|
2214
|
-
"c",
|
|
2215
|
-
"C",
|
|
2216
|
-
"KeyV",
|
|
2217
|
-
"v",
|
|
2218
|
-
"V",
|
|
2219
|
-
"KeyB",
|
|
2220
|
-
"b",
|
|
2221
|
-
"B",
|
|
2222
|
-
"KeyN",
|
|
2223
|
-
"n",
|
|
2224
|
-
"N",
|
|
2225
|
-
"KeyM",
|
|
2226
|
-
"m",
|
|
2227
|
-
"M",
|
|
2228
|
-
"Comma",
|
|
2229
|
-
",",
|
|
2230
|
-
"<",
|
|
2231
|
-
"Period",
|
|
2232
|
-
".",
|
|
2233
|
-
">",
|
|
2234
|
-
"Slash",
|
|
2235
|
-
"/",
|
|
2236
|
-
"?",
|
|
2237
|
-
"ShiftRight",
|
|
2238
|
-
"ControlLeft",
|
|
2239
|
-
"Control",
|
|
2240
|
-
"MetaLeft",
|
|
2241
|
-
"Meta",
|
|
2242
|
-
"AltLeft",
|
|
2243
|
-
"Alt",
|
|
2244
|
-
"Space",
|
|
2245
|
-
" ",
|
|
2246
|
-
"AltRight",
|
|
2247
|
-
"AltGraph",
|
|
2248
|
-
"MetaRight",
|
|
2249
|
-
"ContextMenu",
|
|
2250
|
-
"ControlRight",
|
|
2251
|
-
"PrintScreen",
|
|
2252
|
-
"ScrollLock",
|
|
2253
|
-
"Pause",
|
|
2254
|
-
"PageUp",
|
|
2255
|
-
"PageDown",
|
|
2256
|
-
"Insert",
|
|
2257
|
-
"Delete",
|
|
2258
|
-
"Home",
|
|
2259
|
-
"End",
|
|
2260
|
-
"ArrowLeft",
|
|
2261
|
-
"ArrowUp",
|
|
2262
|
-
"ArrowRight",
|
|
2263
|
-
"ArrowDown",
|
|
2264
|
-
"NumLock",
|
|
2265
|
-
"NumpadDivide",
|
|
2266
|
-
"NumpadMultiply",
|
|
2267
|
-
"NumpadSubtract",
|
|
2268
|
-
"Numpad7",
|
|
2269
|
-
"Numpad8",
|
|
2270
|
-
"Numpad9",
|
|
2271
|
-
"Numpad4",
|
|
2272
|
-
"Numpad5",
|
|
2273
|
-
"Numpad6",
|
|
2274
|
-
"NumpadAdd",
|
|
2275
|
-
"Numpad1",
|
|
2276
|
-
"Numpad2",
|
|
2277
|
-
"Numpad3",
|
|
2278
|
-
"Numpad0",
|
|
2279
|
-
"NumpadDecimal",
|
|
2280
|
-
"NumpadEnter",
|
|
2281
|
-
"ControlOrMeta"
|
|
2282
|
-
]);
|
|
2283
|
-
async function keyboardImplementation(pressed, provider, sessionId, text, selectAll, skipRelease) {
|
|
2284
|
-
if (provider instanceof PlaywrightBrowserProvider) {
|
|
2285
|
-
const page = provider.getPage(sessionId);
|
|
2286
|
-
const actions = parseKeyDef(defaultKeyMap, text);
|
|
2287
|
-
for (const { releasePrevious, releaseSelf, repeat, keyDef } of actions) {
|
|
2288
|
-
const key = keyDef.key;
|
|
2289
|
-
// TODO: instead of calling down/up for each key, join non special
|
|
2290
|
-
// together, and call `type` once for all non special keys,
|
|
2291
|
-
// and then `press` for special keys
|
|
2292
|
-
if (pressed.has(key)) {
|
|
2293
|
-
if (VALID_KEYS.has(key)) {
|
|
2294
|
-
await page.keyboard.up(key);
|
|
2295
|
-
}
|
|
2296
|
-
pressed.delete(key);
|
|
2297
|
-
}
|
|
2298
|
-
if (!releasePrevious) {
|
|
2299
|
-
if (key === "selectall") {
|
|
2300
|
-
await selectAll();
|
|
2301
|
-
continue;
|
|
2302
|
-
}
|
|
2303
|
-
for (let i = 1; i <= repeat; i++) {
|
|
2304
|
-
if (VALID_KEYS.has(key)) {
|
|
2305
|
-
await page.keyboard.down(key);
|
|
2306
|
-
} else {
|
|
2307
|
-
await page.keyboard.insertText(key);
|
|
2308
|
-
}
|
|
2309
|
-
}
|
|
2310
|
-
if (releaseSelf) {
|
|
2311
|
-
if (VALID_KEYS.has(key)) {
|
|
2312
|
-
await page.keyboard.up(key);
|
|
2313
|
-
}
|
|
2314
|
-
} else {
|
|
2315
|
-
pressed.add(key);
|
|
2316
|
-
}
|
|
2317
|
-
}
|
|
2318
|
-
}
|
|
2319
|
-
if (!skipRelease && pressed.size) {
|
|
2320
|
-
for (const key of pressed) {
|
|
2321
|
-
if (VALID_KEYS.has(key)) {
|
|
2322
|
-
await page.keyboard.up(key);
|
|
2323
|
-
}
|
|
2324
|
-
}
|
|
2325
|
-
}
|
|
2326
|
-
} else if (provider instanceof WebdriverBrowserProvider) {
|
|
2327
|
-
const { Key } = await import('webdriverio');
|
|
2328
|
-
const browser = provider.browser;
|
|
2329
|
-
const actions = parseKeyDef(defaultKeyMap, text);
|
|
2330
|
-
let keyboard = browser.action("key");
|
|
2331
|
-
for (const { releasePrevious, releaseSelf, repeat, keyDef } of actions) {
|
|
2332
|
-
let key = keyDef.key;
|
|
2333
|
-
const special = Key[key];
|
|
2334
|
-
if (special) {
|
|
2335
|
-
key = special;
|
|
2336
|
-
}
|
|
2337
|
-
if (pressed.has(key)) {
|
|
2338
|
-
keyboard.up(key);
|
|
2339
|
-
pressed.delete(key);
|
|
2340
|
-
}
|
|
2341
|
-
if (!releasePrevious) {
|
|
2342
|
-
if (key === "selectall") {
|
|
2343
|
-
await keyboard.perform();
|
|
2344
|
-
keyboard = browser.action("key");
|
|
2345
|
-
await selectAll();
|
|
2346
|
-
continue;
|
|
2347
|
-
}
|
|
2348
|
-
for (let i = 1; i <= repeat; i++) {
|
|
2349
|
-
keyboard.down(key);
|
|
2350
|
-
}
|
|
2351
|
-
if (releaseSelf) {
|
|
2352
|
-
keyboard.up(key);
|
|
2353
|
-
} else {
|
|
2354
|
-
pressed.add(key);
|
|
2355
|
-
}
|
|
2356
|
-
}
|
|
2357
|
-
}
|
|
2358
|
-
// seems like webdriverio doesn't release keys automatically if skipRelease is true and all events are keyUp
|
|
2359
|
-
const allRelease = keyboard.toJSON().actions.every((action) => action.type === "keyUp");
|
|
2360
|
-
await keyboard.perform(allRelease ? false : skipRelease);
|
|
1902
|
+
var mime = new Mime(types)._freeze();
|
|
1903
|
+
|
|
1904
|
+
function assertFileAccess(path, project) {
|
|
1905
|
+
if (!isFileServingAllowed(path, project.vite) && !isFileServingAllowed(path, project.vitest.vite)) {
|
|
1906
|
+
throw new Error(`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`);
|
|
2361
1907
|
}
|
|
2362
|
-
return { pressed };
|
|
2363
1908
|
}
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
1909
|
+
const readFile = async ({ project }, path, options = {}) => {
|
|
1910
|
+
const filepath = resolve$1(project.config.root, path);
|
|
1911
|
+
assertFileAccess(filepath, project);
|
|
1912
|
+
// never return a Buffer
|
|
1913
|
+
if (typeof options === "object" && !options.encoding) {
|
|
1914
|
+
options.encoding = "utf-8";
|
|
2367
1915
|
}
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
1916
|
+
return promises.readFile(filepath, options);
|
|
1917
|
+
};
|
|
1918
|
+
const writeFile = async ({ project }, path, data, options) => {
|
|
1919
|
+
const filepath = resolve$1(project.config.root, path);
|
|
1920
|
+
assertFileAccess(filepath, project);
|
|
1921
|
+
const dir = dirname$1(filepath);
|
|
1922
|
+
if (!fs.existsSync(dir)) {
|
|
1923
|
+
await promises.mkdir(dir, { recursive: true });
|
|
2373
1924
|
}
|
|
2374
|
-
|
|
1925
|
+
await promises.writeFile(filepath, data, options);
|
|
1926
|
+
};
|
|
1927
|
+
const removeFile = async ({ project }, path) => {
|
|
1928
|
+
const filepath = resolve$1(project.config.root, path);
|
|
1929
|
+
assertFileAccess(filepath, project);
|
|
1930
|
+
await promises.rm(filepath);
|
|
1931
|
+
};
|
|
1932
|
+
const _fileInfo = async ({ project }, path, encoding) => {
|
|
1933
|
+
const filepath = resolve$1(project.config.root, path);
|
|
1934
|
+
assertFileAccess(filepath, project);
|
|
1935
|
+
const content = await promises.readFile(filepath, encoding || "base64");
|
|
1936
|
+
return {
|
|
1937
|
+
content,
|
|
1938
|
+
basename: basename$1(filepath),
|
|
1939
|
+
mime: mime.getType(filepath)
|
|
1940
|
+
};
|
|
1941
|
+
};
|
|
2375
1942
|
|
|
2376
1943
|
const screenshot = async (context, name, options = {}) => {
|
|
2377
1944
|
options.save ??= true;
|
|
2378
1945
|
if (!options.save) {
|
|
2379
1946
|
options.base64 = true;
|
|
2380
1947
|
}
|
|
2381
|
-
const { buffer, path } = await
|
|
1948
|
+
const { buffer, path } = await context.triggerCommand("__vitest_takeScreenshot", name, options);
|
|
2382
1949
|
return returnResult(options, path, buffer);
|
|
2383
1950
|
};
|
|
2384
|
-
/**
|
|
2385
|
-
* Takes a screenshot using the provided browser context and returns a buffer and the expected screenshot path.
|
|
2386
|
-
*
|
|
2387
|
-
* **Note**: the returned `path` indicates where the screenshot *might* be found.
|
|
2388
|
-
* It is not guaranteed to exist, especially if `options.save` is `false`.
|
|
2389
|
-
*
|
|
2390
|
-
* @throws {Error} If the function is not called within a test or if the browser provider does not support screenshots.
|
|
2391
|
-
*/
|
|
2392
|
-
async function takeScreenshot(context, name, options) {
|
|
2393
|
-
if (!context.testPath) {
|
|
2394
|
-
throw new Error(`Cannot take a screenshot without a test path`);
|
|
2395
|
-
}
|
|
2396
|
-
const path = resolveScreenshotPath(context.testPath, name, context.project.config, options.path);
|
|
2397
|
-
// playwright does not need a screenshot path if we don't intend to save it
|
|
2398
|
-
let savePath;
|
|
2399
|
-
if (options.save) {
|
|
2400
|
-
savePath = normalize(path);
|
|
2401
|
-
await mkdir(dirname(savePath), { recursive: true });
|
|
2402
|
-
}
|
|
2403
|
-
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
2404
|
-
const mask = options.mask?.map((selector) => context.iframe.locator(selector));
|
|
2405
|
-
if (options.element) {
|
|
2406
|
-
const { element: selector,...config } = options;
|
|
2407
|
-
const element = context.iframe.locator(selector);
|
|
2408
|
-
const buffer = await element.screenshot({
|
|
2409
|
-
...config,
|
|
2410
|
-
mask,
|
|
2411
|
-
path: savePath
|
|
2412
|
-
});
|
|
2413
|
-
return {
|
|
2414
|
-
buffer,
|
|
2415
|
-
path
|
|
2416
|
-
};
|
|
2417
|
-
}
|
|
2418
|
-
const buffer = await context.iframe.locator("body").screenshot({
|
|
2419
|
-
...options,
|
|
2420
|
-
mask,
|
|
2421
|
-
path: savePath
|
|
2422
|
-
});
|
|
2423
|
-
return {
|
|
2424
|
-
buffer,
|
|
2425
|
-
path
|
|
2426
|
-
};
|
|
2427
|
-
}
|
|
2428
|
-
if (context.provider instanceof WebdriverBrowserProvider) {
|
|
2429
|
-
// webdriverio needs a path, so if one is not already set we create a temporary one
|
|
2430
|
-
if (savePath === undefined) {
|
|
2431
|
-
savePath = resolve(context.project.tmpDir, nanoid$1());
|
|
2432
|
-
await mkdir(context.project.tmpDir, { recursive: true });
|
|
2433
|
-
}
|
|
2434
|
-
const page = context.provider.browser;
|
|
2435
|
-
const element = !options.element ? await page.$("body") : await page.$(`${options.element}`);
|
|
2436
|
-
// webdriverio expects the path to contain the extension and only works with PNG files
|
|
2437
|
-
const savePathWithExtension = savePath.endsWith(".png") ? savePath : `${savePath}.png`;
|
|
2438
|
-
// there seems to be a bug in webdriverio, `X:/` gets appended to cwd, so we convert to `X:\`
|
|
2439
|
-
const buffer = await element.saveScreenshot(normalize$1(savePathWithExtension));
|
|
2440
|
-
if (!options.save) {
|
|
2441
|
-
await rm(savePathWithExtension, { force: true });
|
|
2442
|
-
}
|
|
2443
|
-
return {
|
|
2444
|
-
buffer,
|
|
2445
|
-
path
|
|
2446
|
-
};
|
|
2447
|
-
}
|
|
2448
|
-
throw new Error(`Provider "${context.provider.name}" does not support screenshots`);
|
|
2449
|
-
}
|
|
2450
|
-
function resolveScreenshotPath(testPath, name, config, customPath) {
|
|
2451
|
-
if (customPath) {
|
|
2452
|
-
return resolve(dirname(testPath), customPath);
|
|
2453
|
-
}
|
|
2454
|
-
const dir = dirname(testPath);
|
|
2455
|
-
const base = basename(testPath);
|
|
2456
|
-
if (config.browser.screenshotDirectory) {
|
|
2457
|
-
return resolve(config.browser.screenshotDirectory, relative(config.root, dir), base, name);
|
|
2458
|
-
}
|
|
2459
|
-
return resolve(dir, "__screenshots__", base, name);
|
|
2460
|
-
}
|
|
2461
1951
|
function returnResult(options, path, buffer) {
|
|
2462
1952
|
if (!options.save) {
|
|
2463
1953
|
return buffer.toString("base64");
|
|
@@ -2685,7 +2175,7 @@ function sanitizeArg(input) {
|
|
|
2685
2175
|
* @returns `Promise` resolving to the decoded screenshot data
|
|
2686
2176
|
*/
|
|
2687
2177
|
function takeDecodedScreenshot({ codec, context, element, name, screenshotOptions }) {
|
|
2688
|
-
return
|
|
2178
|
+
return context.triggerCommand("__vitest_takeScreenshot", name, {
|
|
2689
2179
|
...screenshotOptions,
|
|
2690
2180
|
save: false,
|
|
2691
2181
|
element
|
|
@@ -2866,248 +2356,13 @@ async function getStableScreenshots({ codec, context, comparator, comparatorOpti
|
|
|
2866
2356
|
};
|
|
2867
2357
|
}
|
|
2868
2358
|
|
|
2869
|
-
const selectOptions = async (context, selector, userValues, options = {}) => {
|
|
2870
|
-
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
2871
|
-
const value = userValues;
|
|
2872
|
-
const { iframe } = context;
|
|
2873
|
-
const selectElement = iframe.locator(selector);
|
|
2874
|
-
const values = await Promise.all(value.map(async (v) => {
|
|
2875
|
-
if (typeof v === "string") {
|
|
2876
|
-
return v;
|
|
2877
|
-
}
|
|
2878
|
-
const elementHandler = await iframe.locator(v.element).elementHandle();
|
|
2879
|
-
if (!elementHandler) {
|
|
2880
|
-
throw new Error(`Element not found: ${v.element}`);
|
|
2881
|
-
}
|
|
2882
|
-
return elementHandler;
|
|
2883
|
-
}));
|
|
2884
|
-
await selectElement.selectOption(values, options);
|
|
2885
|
-
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
2886
|
-
const values = userValues;
|
|
2887
|
-
if (!values.length) {
|
|
2888
|
-
return;
|
|
2889
|
-
}
|
|
2890
|
-
const browser = context.browser;
|
|
2891
|
-
if (values.length === 1 && "index" in values[0]) {
|
|
2892
|
-
const selectElement = browser.$(selector);
|
|
2893
|
-
await selectElement.selectByIndex(values[0].index);
|
|
2894
|
-
} else {
|
|
2895
|
-
throw new Error("Provider \"webdriverio\" doesn't support selecting multiple values at once");
|
|
2896
|
-
}
|
|
2897
|
-
} else {
|
|
2898
|
-
throw new TypeError(`Provider "${context.provider.name}" doesn't support selectOptions command`);
|
|
2899
|
-
}
|
|
2900
|
-
};
|
|
2901
|
-
|
|
2902
|
-
const tab = async (context, options = {}) => {
|
|
2903
|
-
const provider = context.provider;
|
|
2904
|
-
if (provider instanceof PlaywrightBrowserProvider) {
|
|
2905
|
-
const page = context.page;
|
|
2906
|
-
await page.keyboard.press(options.shift === true ? "Shift+Tab" : "Tab");
|
|
2907
|
-
return;
|
|
2908
|
-
}
|
|
2909
|
-
if (provider instanceof WebdriverBrowserProvider) {
|
|
2910
|
-
const { Key } = await import('webdriverio');
|
|
2911
|
-
const browser = context.browser;
|
|
2912
|
-
await browser.keys(options.shift === true ? [Key.Shift, Key.Tab] : [Key.Tab]);
|
|
2913
|
-
return;
|
|
2914
|
-
}
|
|
2915
|
-
throw new Error(`Provider "${provider.name}" doesn't support tab command`);
|
|
2916
|
-
};
|
|
2917
|
-
|
|
2918
|
-
const startTracing = async ({ context, project, provider, sessionId }) => {
|
|
2919
|
-
if (provider instanceof PlaywrightBrowserProvider) {
|
|
2920
|
-
if (provider.tracingContexts.has(sessionId)) {
|
|
2921
|
-
return;
|
|
2922
|
-
}
|
|
2923
|
-
provider.tracingContexts.add(sessionId);
|
|
2924
|
-
const options = project.config.browser.trace;
|
|
2925
|
-
await context.tracing.start({
|
|
2926
|
-
screenshots: options.screenshots ?? true,
|
|
2927
|
-
snapshots: options.snapshots ?? true,
|
|
2928
|
-
sources: false
|
|
2929
|
-
}).catch(() => {
|
|
2930
|
-
provider.tracingContexts.delete(sessionId);
|
|
2931
|
-
});
|
|
2932
|
-
return;
|
|
2933
|
-
}
|
|
2934
|
-
throw new TypeError(`The ${provider.name} provider does not support tracing.`);
|
|
2935
|
-
};
|
|
2936
|
-
const startChunkTrace = async (command, { name, title }) => {
|
|
2937
|
-
const { provider, sessionId, testPath, context } = command;
|
|
2938
|
-
if (!testPath) {
|
|
2939
|
-
throw new Error(`stopChunkTrace cannot be called outside of the test file.`);
|
|
2940
|
-
}
|
|
2941
|
-
if (provider instanceof PlaywrightBrowserProvider) {
|
|
2942
|
-
if (!provider.tracingContexts.has(sessionId)) {
|
|
2943
|
-
await startTracing(command);
|
|
2944
|
-
}
|
|
2945
|
-
const path = resolveTracesPath(command, name);
|
|
2946
|
-
provider.pendingTraces.set(path, sessionId);
|
|
2947
|
-
await context.tracing.startChunk({
|
|
2948
|
-
name,
|
|
2949
|
-
title
|
|
2950
|
-
});
|
|
2951
|
-
return;
|
|
2952
|
-
}
|
|
2953
|
-
throw new TypeError(`The ${provider.name} provider does not support tracing.`);
|
|
2954
|
-
};
|
|
2955
|
-
const stopChunkTrace = async (context, { name }) => {
|
|
2956
|
-
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
2957
|
-
const path = resolveTracesPath(context, name);
|
|
2958
|
-
context.provider.pendingTraces.delete(path);
|
|
2959
|
-
await context.context.tracing.stopChunk({ path });
|
|
2960
|
-
return { tracePath: path };
|
|
2961
|
-
}
|
|
2962
|
-
throw new TypeError(`The ${context.provider.name} provider does not support tracing.`);
|
|
2963
|
-
};
|
|
2964
|
-
function resolveTracesPath({ testPath, project }, name) {
|
|
2965
|
-
if (!testPath) {
|
|
2966
|
-
throw new Error(`This command can only be called inside a test file.`);
|
|
2967
|
-
}
|
|
2968
|
-
const options = project.config.browser.trace;
|
|
2969
|
-
const sanitizedName = `${project.name.replace(/[^a-z0-9]/gi, "-")}-${name}.trace.zip`;
|
|
2970
|
-
if (options.tracesDir) {
|
|
2971
|
-
return resolve(options.tracesDir, sanitizedName);
|
|
2972
|
-
}
|
|
2973
|
-
const dir = dirname(testPath);
|
|
2974
|
-
const base = basename(testPath);
|
|
2975
|
-
return resolve(dir, "__traces__", base, `${project.name.replace(/[^a-z0-9]/gi, "-")}-${name}.trace.zip`);
|
|
2976
|
-
}
|
|
2977
|
-
const deleteTracing = async (context, { traces }) => {
|
|
2978
|
-
if (!context.testPath) {
|
|
2979
|
-
throw new Error(`stopChunkTrace cannot be called outside of the test file.`);
|
|
2980
|
-
}
|
|
2981
|
-
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
2982
|
-
return Promise.all(traces.map((trace) => unlink(trace).catch((err) => {
|
|
2983
|
-
if (err.code === "ENOENT") {
|
|
2984
|
-
// Ignore the error if the file doesn't exist
|
|
2985
|
-
return;
|
|
2986
|
-
}
|
|
2987
|
-
// Re-throw other errors
|
|
2988
|
-
throw err;
|
|
2989
|
-
})));
|
|
2990
|
-
}
|
|
2991
|
-
throw new Error(`provider ${context.provider.name} is not supported`);
|
|
2992
|
-
};
|
|
2993
|
-
const annotateTraces = async ({ project }, { testId, traces }) => {
|
|
2994
|
-
const vitest = project.vitest;
|
|
2995
|
-
await Promise.all(traces.map((trace) => {
|
|
2996
|
-
const entity = vitest.state.getReportedEntityById(testId);
|
|
2997
|
-
return vitest._testRun.annotate(testId, {
|
|
2998
|
-
message: relative(project.config.root, trace),
|
|
2999
|
-
type: "traces",
|
|
3000
|
-
attachment: {
|
|
3001
|
-
path: trace,
|
|
3002
|
-
contentType: "application/octet-stream"
|
|
3003
|
-
},
|
|
3004
|
-
location: entity?.location ? {
|
|
3005
|
-
file: entity.module.moduleId,
|
|
3006
|
-
line: entity.location.line,
|
|
3007
|
-
column: entity.location.column
|
|
3008
|
-
} : undefined
|
|
3009
|
-
});
|
|
3010
|
-
}));
|
|
3011
|
-
};
|
|
3012
|
-
|
|
3013
|
-
const type = async (context, selector, text, options = {}) => {
|
|
3014
|
-
const { skipClick = false, skipAutoClose = false } = options;
|
|
3015
|
-
const unreleased = new Set(Reflect.get(options, "unreleased") ?? []);
|
|
3016
|
-
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
3017
|
-
const { iframe } = context;
|
|
3018
|
-
const element = iframe.locator(selector);
|
|
3019
|
-
if (!skipClick) {
|
|
3020
|
-
await element.focus();
|
|
3021
|
-
}
|
|
3022
|
-
await keyboardImplementation(unreleased, context.provider, context.sessionId, text, () => element.selectText(), skipAutoClose);
|
|
3023
|
-
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
3024
|
-
const browser = context.browser;
|
|
3025
|
-
const element = browser.$(selector);
|
|
3026
|
-
if (!skipClick && !await element.isFocused()) {
|
|
3027
|
-
await element.click();
|
|
3028
|
-
}
|
|
3029
|
-
await keyboardImplementation(unreleased, context.provider, context.sessionId, text, () => browser.execute(() => {
|
|
3030
|
-
const element = document.activeElement;
|
|
3031
|
-
if (element && typeof element.select === "function") {
|
|
3032
|
-
element.select();
|
|
3033
|
-
}
|
|
3034
|
-
}), skipAutoClose);
|
|
3035
|
-
} else {
|
|
3036
|
-
throw new TypeError(`Provider "${context.provider.name}" does not support typing`);
|
|
3037
|
-
}
|
|
3038
|
-
return { unreleased: Array.from(unreleased) };
|
|
3039
|
-
};
|
|
3040
|
-
|
|
3041
|
-
const upload = async (context, selector, files, options) => {
|
|
3042
|
-
const testPath = context.testPath;
|
|
3043
|
-
if (!testPath) {
|
|
3044
|
-
throw new Error(`Cannot upload files outside of a test`);
|
|
3045
|
-
}
|
|
3046
|
-
const root = context.project.config.root;
|
|
3047
|
-
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
3048
|
-
const { iframe } = context;
|
|
3049
|
-
const playwrightFiles = files.map((file) => {
|
|
3050
|
-
if (typeof file === "string") {
|
|
3051
|
-
return resolve(root, file);
|
|
3052
|
-
}
|
|
3053
|
-
return {
|
|
3054
|
-
name: file.name,
|
|
3055
|
-
mimeType: file.mimeType,
|
|
3056
|
-
buffer: Buffer.from(file.base64, "base64")
|
|
3057
|
-
};
|
|
3058
|
-
});
|
|
3059
|
-
await iframe.locator(selector).setInputFiles(playwrightFiles, options);
|
|
3060
|
-
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
3061
|
-
for (const file of files) {
|
|
3062
|
-
if (typeof file !== "string") {
|
|
3063
|
-
throw new TypeError(`The "${context.provider.name}" provider doesn't support uploading files objects. Provide a file path instead.`);
|
|
3064
|
-
}
|
|
3065
|
-
}
|
|
3066
|
-
const element = context.browser.$(selector);
|
|
3067
|
-
for (const file of files) {
|
|
3068
|
-
const filepath = resolve(root, file);
|
|
3069
|
-
const remoteFilePath = await context.browser.uploadFile(filepath);
|
|
3070
|
-
await element.addValue(remoteFilePath);
|
|
3071
|
-
}
|
|
3072
|
-
} else {
|
|
3073
|
-
throw new TypeError(`Provider "${context.provider.name}" does not support uploading files via userEvent.upload`);
|
|
3074
|
-
}
|
|
3075
|
-
};
|
|
3076
|
-
|
|
3077
|
-
const viewport = async (context, options) => {
|
|
3078
|
-
if (context.provider instanceof WebdriverBrowserProvider) {
|
|
3079
|
-
await context.provider.setViewport(options);
|
|
3080
|
-
} else {
|
|
3081
|
-
throw new TypeError(`Provider ${context.provider.name} doesn't support "viewport" command`);
|
|
3082
|
-
}
|
|
3083
|
-
};
|
|
3084
|
-
|
|
3085
2359
|
var builtinCommands = {
|
|
3086
2360
|
readFile,
|
|
3087
2361
|
removeFile,
|
|
3088
2362
|
writeFile,
|
|
3089
2363
|
__vitest_fileInfo: _fileInfo,
|
|
3090
|
-
__vitest_upload: upload,
|
|
3091
|
-
__vitest_click: click,
|
|
3092
|
-
__vitest_dblClick: dblClick,
|
|
3093
|
-
__vitest_tripleClick: tripleClick,
|
|
3094
2364
|
__vitest_screenshot: screenshot,
|
|
3095
|
-
|
|
3096
|
-
__vitest_clear: clear,
|
|
3097
|
-
__vitest_fill: fill,
|
|
3098
|
-
__vitest_tab: tab,
|
|
3099
|
-
__vitest_keyboard: keyboard,
|
|
3100
|
-
__vitest_selectOptions: selectOptions,
|
|
3101
|
-
__vitest_dragAndDrop: dragAndDrop,
|
|
3102
|
-
__vitest_hover: hover,
|
|
3103
|
-
__vitest_cleanup: keyboardCleanup,
|
|
3104
|
-
__vitest_viewport: viewport,
|
|
3105
|
-
__vitest_screenshotMatcher: screenshotMatcher,
|
|
3106
|
-
__vitest_deleteTracing: deleteTracing,
|
|
3107
|
-
__vitest_startChunkTrace: startChunkTrace,
|
|
3108
|
-
__vitest_startTracing: startTracing,
|
|
3109
|
-
__vitest_stopChunkTrace: stopChunkTrace,
|
|
3110
|
-
__vitest_annotateTraces: annotateTraces
|
|
2365
|
+
__vitest_screenshotMatcher: screenshotMatcher
|
|
3111
2366
|
};
|
|
3112
2367
|
|
|
3113
2368
|
class BrowserServerState {
|
|
@@ -3141,6 +2396,22 @@ class ProjectBrowser {
|
|
|
3141
2396
|
get vite() {
|
|
3142
2397
|
return this.parent.vite;
|
|
3143
2398
|
}
|
|
2399
|
+
commands = {};
|
|
2400
|
+
registerCommand(name, cb) {
|
|
2401
|
+
if (!/^[a-z_$][\w$]*$/i.test(name)) {
|
|
2402
|
+
throw new Error(`Invalid command name "${name}". Only alphanumeric characters, $ and _ are allowed.`);
|
|
2403
|
+
}
|
|
2404
|
+
this.commands[name] = cb;
|
|
2405
|
+
}
|
|
2406
|
+
triggerCommand = ((name, context, ...args) => {
|
|
2407
|
+
if (name in this.commands) {
|
|
2408
|
+
return this.commands[name](context, ...args);
|
|
2409
|
+
}
|
|
2410
|
+
if (name in this.parent.commands) {
|
|
2411
|
+
return this.parent.commands[name](context, ...args);
|
|
2412
|
+
}
|
|
2413
|
+
throw new Error(`Provider ${this.provider.name} does not support command "${name}".`);
|
|
2414
|
+
});
|
|
3144
2415
|
wrapSerializedConfig() {
|
|
3145
2416
|
const config = wrapConfig(this.project.serializedConfig);
|
|
3146
2417
|
config.env ??= {};
|
|
@@ -3152,6 +2423,16 @@ class ProjectBrowser {
|
|
|
3152
2423
|
return;
|
|
3153
2424
|
}
|
|
3154
2425
|
this.provider = await getBrowserProvider(project.config.browser, project);
|
|
2426
|
+
if (this.provider.initScripts) {
|
|
2427
|
+
this.parent.initScripts = this.provider.initScripts;
|
|
2428
|
+
// make sure the script can be imported
|
|
2429
|
+
this.provider.initScripts.forEach((script) => {
|
|
2430
|
+
const allow = this.parent.vite.config.server.fs.allow;
|
|
2431
|
+
if (!allow.includes(script)) {
|
|
2432
|
+
allow.push(script);
|
|
2433
|
+
}
|
|
2434
|
+
});
|
|
2435
|
+
}
|
|
3155
2436
|
}
|
|
3156
2437
|
parseErrorStacktrace(e, options = {}) {
|
|
3157
2438
|
return this.parent.parseErrorStacktrace(e, options);
|
|
@@ -3184,6 +2465,7 @@ class ParentBrowserProject {
|
|
|
3184
2465
|
locatorsUrl;
|
|
3185
2466
|
matchersUrl;
|
|
3186
2467
|
stateJs;
|
|
2468
|
+
initScripts = [];
|
|
3187
2469
|
commands = {};
|
|
3188
2470
|
children = new Set();
|
|
3189
2471
|
vitest;
|
|
@@ -3255,15 +2537,6 @@ class ParentBrowserProject {
|
|
|
3255
2537
|
this.orchestratorHtml = (project.config.browser.ui ? readFile$1(resolve(distRoot, "client/__vitest__/index.html"), "utf8") : readFile$1(resolve(distRoot, "client/orchestrator.html"), "utf8")).then((html) => this.orchestratorHtml = html);
|
|
3256
2538
|
this.injectorJs = readFile$1(resolve(distRoot, "client/esm-client-injector.js"), "utf8").then((js) => this.injectorJs = js);
|
|
3257
2539
|
this.errorCatcherUrl = join("/@fs/", resolve(distRoot, "client/error-catcher.js"));
|
|
3258
|
-
const builtinProviders = [
|
|
3259
|
-
"playwright",
|
|
3260
|
-
"webdriverio",
|
|
3261
|
-
"preview"
|
|
3262
|
-
];
|
|
3263
|
-
const providerName = project.config.browser.provider?.name || "preview";
|
|
3264
|
-
if (builtinProviders.includes(providerName)) {
|
|
3265
|
-
this.locatorsUrl = join("/@fs/", distRoot, "locators", `${providerName}.js`);
|
|
3266
|
-
}
|
|
3267
2540
|
this.matchersUrl = join("/@fs/", distRoot, "expect-element.js");
|
|
3268
2541
|
this.stateJs = readFile$1(resolve(distRoot, "state.js"), "utf-8").then((js) => this.stateJs = js);
|
|
3269
2542
|
}
|
|
@@ -3550,7 +2823,7 @@ function nanoid(size = 21) {
|
|
|
3550
2823
|
return id;
|
|
3551
2824
|
}
|
|
3552
2825
|
|
|
3553
|
-
const debug
|
|
2826
|
+
const debug = createDebugger("vitest:browser:api");
|
|
3554
2827
|
const BROWSER_API_PATH = "/__vitest_browser_api__";
|
|
3555
2828
|
function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
3556
2829
|
const vite = globalServer.vite;
|
|
@@ -3598,9 +2871,9 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
|
3598
2871
|
const state = project.browser.state;
|
|
3599
2872
|
const clients = type === "tester" ? state.testers : state.orchestrators;
|
|
3600
2873
|
clients.set(rpcId, rpc);
|
|
3601
|
-
debug
|
|
2874
|
+
debug?.("[%s] Browser API connected to %s", rpcId, type);
|
|
3602
2875
|
ws.on("close", () => {
|
|
3603
|
-
debug
|
|
2876
|
+
debug?.("[%s] Browser API disconnected from %s", rpcId, type);
|
|
3604
2877
|
clients.delete(rpcId);
|
|
3605
2878
|
globalServer.removeCDPHandler(rpcId);
|
|
3606
2879
|
if (type === "orchestrator") {
|
|
@@ -3725,23 +2998,22 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
|
3725
2998
|
}
|
|
3726
2999
|
},
|
|
3727
3000
|
async triggerCommand(sessionId, command, testPath, payload) {
|
|
3728
|
-
debug
|
|
3001
|
+
debug?.("[%s] Triggering command \"%s\"", sessionId, command);
|
|
3729
3002
|
const provider = project.browser.provider;
|
|
3730
3003
|
if (!provider) {
|
|
3731
3004
|
throw new Error("Commands are only available for browser tests.");
|
|
3732
3005
|
}
|
|
3733
|
-
const commands = globalServer.commands;
|
|
3734
|
-
if (!commands || !commands[command]) {
|
|
3735
|
-
throw new Error(`Unknown command "${command}".`);
|
|
3736
|
-
}
|
|
3737
3006
|
const context = Object.assign({
|
|
3738
3007
|
testPath,
|
|
3739
3008
|
project,
|
|
3740
3009
|
provider,
|
|
3741
3010
|
contextId: sessionId,
|
|
3742
|
-
sessionId
|
|
3011
|
+
sessionId,
|
|
3012
|
+
triggerCommand: (name, ...args) => {
|
|
3013
|
+
return project.browser.triggerCommand(name, context, ...args);
|
|
3014
|
+
}
|
|
3743
3015
|
}, provider.getCommandsContext(sessionId));
|
|
3744
|
-
return await
|
|
3016
|
+
return await project.browser.triggerCommand(command, context, ...payload);
|
|
3745
3017
|
},
|
|
3746
3018
|
resolveMock(rawId, importer, options) {
|
|
3747
3019
|
return mockResolver.resolveMock(rawId, importer, options);
|
|
@@ -3847,276 +3119,12 @@ function stringifyReplace(key, value) {
|
|
|
3847
3119
|
}
|
|
3848
3120
|
}
|
|
3849
3121
|
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
const providers = new Set();
|
|
3853
|
-
const numCpus = typeof nodeos.availableParallelism === "function" ? nodeos.availableParallelism() : nodeos.cpus().length;
|
|
3854
|
-
const threadsCount = vitest.config.watch ? Math.max(Math.floor(numCpus / 2), 1) : Math.max(numCpus - 1, 1);
|
|
3855
|
-
const projectPools = new WeakMap();
|
|
3856
|
-
const ensurePool = (project) => {
|
|
3857
|
-
if (projectPools.has(project)) {
|
|
3858
|
-
return projectPools.get(project);
|
|
3859
|
-
}
|
|
3860
|
-
debug?.("creating pool for project %s", project.name);
|
|
3861
|
-
const resolvedUrls = project.browser.vite.resolvedUrls;
|
|
3862
|
-
const origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0];
|
|
3863
|
-
if (!origin) {
|
|
3864
|
-
throw new Error(`Can't find browser origin URL for project "${project.name}"`);
|
|
3865
|
-
}
|
|
3866
|
-
const pool = new BrowserPool(project, {
|
|
3867
|
-
maxWorkers: getThreadsCount(project),
|
|
3868
|
-
origin
|
|
3869
|
-
});
|
|
3870
|
-
projectPools.set(project, pool);
|
|
3871
|
-
vitest.onCancel(() => {
|
|
3872
|
-
pool.cancel();
|
|
3873
|
-
});
|
|
3874
|
-
return pool;
|
|
3875
|
-
};
|
|
3876
|
-
const runWorkspaceTests = async (method, specs) => {
|
|
3877
|
-
const groupedFiles = new Map();
|
|
3878
|
-
for (const { project, moduleId } of specs) {
|
|
3879
|
-
const files = groupedFiles.get(project) || [];
|
|
3880
|
-
files.push(moduleId);
|
|
3881
|
-
groupedFiles.set(project, files);
|
|
3882
|
-
}
|
|
3883
|
-
let isCancelled = false;
|
|
3884
|
-
vitest.onCancel(() => {
|
|
3885
|
-
isCancelled = true;
|
|
3886
|
-
});
|
|
3887
|
-
const initialisedPools = await Promise.all([...groupedFiles.entries()].map(async ([project, files]) => {
|
|
3888
|
-
await project._initBrowserProvider();
|
|
3889
|
-
if (!project.browser) {
|
|
3890
|
-
throw new TypeError(`The browser server was not initialized${project.name ? ` for the "${project.name}" project` : ""}. This is a bug in Vitest. Please, open a new issue with reproduction.`);
|
|
3891
|
-
}
|
|
3892
|
-
if (isCancelled) {
|
|
3893
|
-
return;
|
|
3894
|
-
}
|
|
3895
|
-
debug?.("provider is ready for %s project", project.name);
|
|
3896
|
-
const pool = ensurePool(project);
|
|
3897
|
-
vitest.state.clearFiles(project, files);
|
|
3898
|
-
providers.add(project.browser.provider);
|
|
3899
|
-
return {
|
|
3900
|
-
pool,
|
|
3901
|
-
provider: project.browser.provider,
|
|
3902
|
-
runTests: () => pool.runTests(method, files)
|
|
3903
|
-
};
|
|
3904
|
-
}));
|
|
3905
|
-
if (isCancelled) {
|
|
3906
|
-
return;
|
|
3907
|
-
}
|
|
3908
|
-
const parallelPools = [];
|
|
3909
|
-
const nonParallelPools = [];
|
|
3910
|
-
for (const pool of initialisedPools) {
|
|
3911
|
-
if (!pool) {
|
|
3912
|
-
// this means it was cancelled
|
|
3913
|
-
return;
|
|
3914
|
-
}
|
|
3915
|
-
if (pool.provider.mocker && pool.provider.supportsParallelism) {
|
|
3916
|
-
parallelPools.push(pool.runTests);
|
|
3917
|
-
} else {
|
|
3918
|
-
nonParallelPools.push(pool.runTests);
|
|
3919
|
-
}
|
|
3920
|
-
}
|
|
3921
|
-
await Promise.all(parallelPools.map((runTests) => runTests()));
|
|
3922
|
-
for (const runTests of nonParallelPools) {
|
|
3923
|
-
if (isCancelled) {
|
|
3924
|
-
return;
|
|
3925
|
-
}
|
|
3926
|
-
await runTests();
|
|
3927
|
-
}
|
|
3928
|
-
};
|
|
3929
|
-
function getThreadsCount(project) {
|
|
3930
|
-
const config = project.config.browser;
|
|
3931
|
-
if (!config.headless || !config.fileParallelism || !project.browser.provider.supportsParallelism) {
|
|
3932
|
-
return 1;
|
|
3933
|
-
}
|
|
3934
|
-
if (project.config.maxWorkers) {
|
|
3935
|
-
return project.config.maxWorkers;
|
|
3936
|
-
}
|
|
3937
|
-
return threadsCount;
|
|
3938
|
-
}
|
|
3939
|
-
return {
|
|
3940
|
-
name: "browser",
|
|
3941
|
-
async close() {
|
|
3942
|
-
await Promise.all([...providers].map((provider) => provider.close()));
|
|
3943
|
-
vitest._browserSessions.sessionIds.clear();
|
|
3944
|
-
providers.clear();
|
|
3945
|
-
vitest.projects.forEach((project) => {
|
|
3946
|
-
project.browser?.state.orchestrators.forEach((orchestrator) => {
|
|
3947
|
-
orchestrator.$close();
|
|
3948
|
-
});
|
|
3949
|
-
});
|
|
3950
|
-
debug?.("browser pool closed all providers");
|
|
3951
|
-
},
|
|
3952
|
-
runTests: (files) => runWorkspaceTests("run", files),
|
|
3953
|
-
collectTests: (files) => runWorkspaceTests("collect", files)
|
|
3954
|
-
};
|
|
3955
|
-
}
|
|
3956
|
-
function escapePathToRegexp(path) {
|
|
3957
|
-
return path.replace(/[/\\.?*()^${}|[\]+]/g, "\\$&");
|
|
3958
|
-
}
|
|
3959
|
-
class BrowserPool {
|
|
3960
|
-
_queue = [];
|
|
3961
|
-
_promise;
|
|
3962
|
-
_providedContext;
|
|
3963
|
-
readySessions = new Set();
|
|
3964
|
-
constructor(project, options) {
|
|
3965
|
-
this.project = project;
|
|
3966
|
-
this.options = options;
|
|
3967
|
-
}
|
|
3968
|
-
cancel() {
|
|
3969
|
-
this._queue = [];
|
|
3970
|
-
}
|
|
3971
|
-
reject(error) {
|
|
3972
|
-
this._promise?.reject(error);
|
|
3973
|
-
this._promise = undefined;
|
|
3974
|
-
this.cancel();
|
|
3975
|
-
}
|
|
3976
|
-
get orchestrators() {
|
|
3977
|
-
return this.project.browser.state.orchestrators;
|
|
3978
|
-
}
|
|
3979
|
-
async runTests(method, files) {
|
|
3980
|
-
this._promise ??= createDefer();
|
|
3981
|
-
if (!files.length) {
|
|
3982
|
-
debug?.("no tests found, finishing test run immediately");
|
|
3983
|
-
this._promise.resolve();
|
|
3984
|
-
return this._promise;
|
|
3985
|
-
}
|
|
3986
|
-
this._providedContext = stringify(this.project.getProvidedContext());
|
|
3987
|
-
this._queue.push(...files);
|
|
3988
|
-
this.readySessions.forEach((sessionId) => {
|
|
3989
|
-
if (this._queue.length) {
|
|
3990
|
-
this.readySessions.delete(sessionId);
|
|
3991
|
-
this.runNextTest(method, sessionId);
|
|
3992
|
-
}
|
|
3993
|
-
});
|
|
3994
|
-
if (this.orchestrators.size >= this.options.maxWorkers) {
|
|
3995
|
-
debug?.("all orchestrators are ready, not creating more");
|
|
3996
|
-
return this._promise;
|
|
3997
|
-
}
|
|
3998
|
-
// open the minimum amount of tabs
|
|
3999
|
-
// if there is only 1 file running, we don't need 8 tabs running
|
|
4000
|
-
const workerCount = Math.min(this.options.maxWorkers - this.orchestrators.size, files.length);
|
|
4001
|
-
const promises = [];
|
|
4002
|
-
for (let i = 0; i < workerCount; i++) {
|
|
4003
|
-
const sessionId = crypto.randomUUID();
|
|
4004
|
-
this.project.vitest._browserSessions.sessionIds.add(sessionId);
|
|
4005
|
-
const project = this.project.name;
|
|
4006
|
-
debug?.("[%s] creating session for %s", sessionId, project);
|
|
4007
|
-
const page = this.openPage(sessionId).then(() => {
|
|
4008
|
-
// start running tests on the page when it's ready
|
|
4009
|
-
this.runNextTest(method, sessionId);
|
|
4010
|
-
});
|
|
4011
|
-
promises.push(page);
|
|
4012
|
-
}
|
|
4013
|
-
await Promise.all(promises);
|
|
4014
|
-
debug?.("all sessions are created");
|
|
4015
|
-
return this._promise;
|
|
4016
|
-
}
|
|
4017
|
-
async openPage(sessionId) {
|
|
4018
|
-
const sessionPromise = this.project.vitest._browserSessions.createSession(sessionId, this.project, this);
|
|
4019
|
-
const browser = this.project.browser;
|
|
4020
|
-
const url = new URL("/__vitest_test__/", this.options.origin);
|
|
4021
|
-
url.searchParams.set("sessionId", sessionId);
|
|
4022
|
-
const pagePromise = browser.provider.openPage(sessionId, url.toString());
|
|
4023
|
-
await Promise.all([sessionPromise, pagePromise]);
|
|
4024
|
-
}
|
|
4025
|
-
getOrchestrator(sessionId) {
|
|
4026
|
-
const orchestrator = this.orchestrators.get(sessionId);
|
|
4027
|
-
if (!orchestrator) {
|
|
4028
|
-
throw new Error(`Orchestrator not found for session ${sessionId}. This is a bug in Vitest. Please, open a new issue with reproduction.`);
|
|
4029
|
-
}
|
|
4030
|
-
return orchestrator;
|
|
4031
|
-
}
|
|
4032
|
-
finishSession(sessionId) {
|
|
4033
|
-
this.readySessions.add(sessionId);
|
|
4034
|
-
// the last worker finished running tests
|
|
4035
|
-
if (this.readySessions.size === this.orchestrators.size) {
|
|
4036
|
-
this._promise?.resolve();
|
|
4037
|
-
this._promise = undefined;
|
|
4038
|
-
debug?.("[%s] all tests finished running", sessionId);
|
|
4039
|
-
} else {
|
|
4040
|
-
debug?.(`did not finish sessions for ${sessionId}: |ready - %s| |overall - %s|`, [...this.readySessions].join(", "), [...this.orchestrators.keys()].join(", "));
|
|
4041
|
-
}
|
|
4042
|
-
}
|
|
4043
|
-
runNextTest(method, sessionId) {
|
|
4044
|
-
const file = this._queue.shift();
|
|
4045
|
-
if (!file) {
|
|
4046
|
-
debug?.("[%s] no more tests to run", sessionId);
|
|
4047
|
-
const isolate = this.project.config.browser.isolate;
|
|
4048
|
-
// we don't need to cleanup testers if isolation is enabled,
|
|
4049
|
-
// because cleanup is done at the end of every test
|
|
4050
|
-
if (isolate) {
|
|
4051
|
-
this.finishSession(sessionId);
|
|
4052
|
-
return;
|
|
4053
|
-
}
|
|
4054
|
-
// we need to cleanup testers first because there is only
|
|
4055
|
-
// one iframe and it does the cleanup only after everything is completed
|
|
4056
|
-
const orchestrator = this.getOrchestrator(sessionId);
|
|
4057
|
-
orchestrator.cleanupTesters().catch((error) => this.reject(error)).finally(() => this.finishSession(sessionId));
|
|
4058
|
-
return;
|
|
4059
|
-
}
|
|
4060
|
-
if (!this._promise) {
|
|
4061
|
-
throw new Error(`Unexpected empty queue`);
|
|
4062
|
-
}
|
|
4063
|
-
const startTime = performance.now();
|
|
4064
|
-
const orchestrator = this.getOrchestrator(sessionId);
|
|
4065
|
-
debug?.("[%s] run test %s", sessionId, file);
|
|
4066
|
-
this.setBreakpoint(sessionId, file).then(() => {
|
|
4067
|
-
// this starts running tests inside the orchestrator
|
|
4068
|
-
orchestrator.createTesters({
|
|
4069
|
-
method,
|
|
4070
|
-
files: [file],
|
|
4071
|
-
providedContext: this._providedContext || "[{}]",
|
|
4072
|
-
startTime
|
|
4073
|
-
}).then(() => {
|
|
4074
|
-
debug?.("[%s] test %s finished running", sessionId, file);
|
|
4075
|
-
this.runNextTest(method, sessionId);
|
|
4076
|
-
}).catch((error) => {
|
|
4077
|
-
// if user cancels the test run manually, ignore the error and exit gracefully
|
|
4078
|
-
if (this.project.vitest.isCancelling && error instanceof Error && error.message.startsWith("Browser connection was closed while running tests")) {
|
|
4079
|
-
this.cancel();
|
|
4080
|
-
this._promise?.resolve();
|
|
4081
|
-
this._promise = undefined;
|
|
4082
|
-
debug?.("[%s] browser connection was closed", sessionId);
|
|
4083
|
-
return;
|
|
4084
|
-
}
|
|
4085
|
-
debug?.("[%s] error during %s test run: %s", sessionId, file, error);
|
|
4086
|
-
this.reject(error);
|
|
4087
|
-
});
|
|
4088
|
-
}).catch((err) => this.reject(err));
|
|
4089
|
-
}
|
|
4090
|
-
async setBreakpoint(sessionId, file) {
|
|
4091
|
-
if (!this.project.config.inspector.waitForDebugger) {
|
|
4092
|
-
return;
|
|
4093
|
-
}
|
|
4094
|
-
const provider = this.project.browser.provider;
|
|
4095
|
-
const browser = this.project.config.browser.name;
|
|
4096
|
-
if (shouldIgnoreDebugger(provider.name, browser)) {
|
|
4097
|
-
debug?.("[$s] ignoring debugger in %s browser because it is not supported", sessionId, browser);
|
|
4098
|
-
return;
|
|
4099
|
-
}
|
|
4100
|
-
if (!provider.getCDPSession) {
|
|
4101
|
-
throw new Error("Unable to set breakpoint, CDP not supported");
|
|
4102
|
-
}
|
|
4103
|
-
debug?.("[%s] set breakpoint for %s", sessionId, file);
|
|
4104
|
-
const session = await provider.getCDPSession(sessionId);
|
|
4105
|
-
await session.send("Debugger.enable", {});
|
|
4106
|
-
await session.send("Debugger.setBreakpointByUrl", {
|
|
4107
|
-
lineNumber: 0,
|
|
4108
|
-
urlRegex: escapePathToRegexp(file)
|
|
4109
|
-
});
|
|
4110
|
-
}
|
|
4111
|
-
}
|
|
4112
|
-
function shouldIgnoreDebugger(provider, browser) {
|
|
4113
|
-
if (provider === "webdriverio") {
|
|
4114
|
-
return browser !== "chrome" && browser !== "edge";
|
|
4115
|
-
}
|
|
4116
|
-
return browser !== "chromium";
|
|
3122
|
+
function defineBrowserCommand(fn) {
|
|
3123
|
+
return fn;
|
|
4117
3124
|
}
|
|
4118
|
-
|
|
4119
|
-
|
|
3125
|
+
const createBrowserServer = async (options) => {
|
|
3126
|
+
const project = options.project;
|
|
3127
|
+
const configFile = project.vite.config.configFile;
|
|
4120
3128
|
if (project.vitest.version !== version) {
|
|
4121
3129
|
project.vitest.logger.warn(c.yellow(`Loaded ${c.inverse(c.yellow(` vitest@${project.vitest.version} `))} and ${c.inverse(c.yellow(` @vitest/browser@${version} `))}.` + "\nRunning mixed versions is not supported and may lead into bugs" + "\nUpdate your dependencies and make sure the versions match."));
|
|
4122
3130
|
}
|
|
@@ -4125,6 +3133,7 @@ async function createBrowserServer(project, configFile, prePlugins = [], postPlu
|
|
|
4125
3133
|
const logLevel = process.env.VITEST_BROWSER_DEBUG ?? "info";
|
|
4126
3134
|
const logger = createViteLogger(project.vitest.logger, logLevel, { allowClearScreen: false });
|
|
4127
3135
|
const mockerRegistry = new MockerRegistry();
|
|
3136
|
+
let cacheDir;
|
|
4128
3137
|
const vite = await createViteServer({
|
|
4129
3138
|
...project.options,
|
|
4130
3139
|
base: "/",
|
|
@@ -4148,16 +3157,35 @@ async function createBrowserServer(project, configFile, prePlugins = [], postPlu
|
|
|
4148
3157
|
},
|
|
4149
3158
|
cacheDir: project.vite.config.cacheDir,
|
|
4150
3159
|
plugins: [
|
|
4151
|
-
|
|
3160
|
+
{
|
|
3161
|
+
name: "vitest-internal:browser-cacheDir",
|
|
3162
|
+
configResolved(config) {
|
|
3163
|
+
cacheDir = config.cacheDir;
|
|
3164
|
+
}
|
|
3165
|
+
},
|
|
3166
|
+
...options.mocksPlugins({ filter(id) {
|
|
3167
|
+
if (id.includes(distRoot) || id.includes(cacheDir)) {
|
|
3168
|
+
return false;
|
|
3169
|
+
}
|
|
3170
|
+
return true;
|
|
3171
|
+
} }),
|
|
3172
|
+
options.metaEnvReplacer(),
|
|
4152
3173
|
...project.options?.plugins || [],
|
|
4153
3174
|
BrowserPlugin(server),
|
|
4154
3175
|
interceptorPlugin({ registry: mockerRegistry }),
|
|
4155
|
-
|
|
3176
|
+
options.coveragePlugin()
|
|
4156
3177
|
]
|
|
4157
3178
|
});
|
|
4158
3179
|
await vite.listen();
|
|
4159
3180
|
setupBrowserRpc(server, mockerRegistry);
|
|
4160
3181
|
return server;
|
|
3182
|
+
};
|
|
3183
|
+
function defineBrowserProvider(options) {
|
|
3184
|
+
return {
|
|
3185
|
+
...options,
|
|
3186
|
+
options: options.options || {},
|
|
3187
|
+
serverFactory: createBrowserServer
|
|
3188
|
+
};
|
|
4161
3189
|
}
|
|
4162
3190
|
|
|
4163
|
-
export {
|
|
3191
|
+
export { createBrowserServer, defineBrowserCommand, defineBrowserProvider, parseKeyDef, resolveScreenshotPath };
|