@vitest/browser 4.0.0-beta.9 → 4.0.1
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/README.md +6 -14
- package/context.d.ts +86 -29
- package/context.js +3 -2
- package/dist/client/.vite/manifest.json +6 -6
- package/dist/client/__vitest__/assets/index-COTh6lXR.css +1 -0
- package/dist/client/__vitest__/assets/index-DOkKC3NI.js +53 -0
- package/dist/client/__vitest__/index.html +2 -2
- package/dist/client/__vitest_browser__/orchestrator-CFVVvVT1.js +313 -0
- package/dist/client/__vitest_browser__/tester-BNxij3za.js +2133 -0
- package/dist/client/__vitest_browser__/{utils-FY_Qin7d.js → utils-uxqdqUz8.js} +48 -24
- package/dist/client/orchestrator.html +2 -2
- package/dist/client/tester/tester.html +2 -2
- package/dist/client.js +1 -1
- package/dist/context.js +80 -19
- package/dist/expect-element.js +14 -14
- package/dist/index-BnLTaCRv.js +6 -0
- package/dist/index.d.ts +64 -165
- package/dist/index.js +572 -1431
- package/dist/{locators/index.d.ts → locators.d.ts} +27 -3
- package/dist/locators.js +1 -0
- package/dist/state.js +0 -1
- package/dist/types.d.ts +0 -1
- package/jest-dom.d.ts +5 -5
- package/matchers.d.ts +2 -2
- package/package.json +18 -54
- package/utils.d.ts +5 -5
- package/dist/client/__vitest__/assets/index-CBcuRGkf.js +0 -57
- package/dist/client/__vitest__/assets/index-KbpJLW--.css +0 -1
- package/dist/client/__vitest_browser__/orchestrator-C2rrmv36.js +0 -3198
- package/dist/client/__vitest_browser__/tester-mSVktQ7a.js +0 -3282
- package/dist/index-B7Hfmz-h.js +0 -1
- 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.js +0 -47
- package/dist/public-utils-Kx5DUGWa.js +0 -6
- package/dist/utils.js +0 -1
- package/dist/webdriver-AHRa6U3j.js +0 -517
- package/providers/playwright.d.ts +0 -97
- package/providers/webdriverio.d.ts +0 -35
- package/providers.d.ts +0 -7
package/dist/index.js
CHANGED
|
@@ -1,28 +1,24 @@
|
|
|
1
1
|
import { ManualMockedModule, RedirectedModule, AutomockedModule, AutospiedModule, MockerRegistry } from '@vitest/mocker';
|
|
2
2
|
import { dynamicImportPlugin, ServerMockResolver, interceptorPlugin } from '@vitest/mocker/node';
|
|
3
3
|
import c from 'tinyrainbow';
|
|
4
|
-
import { isValidApiRequest, isFileServingAllowed, distDir, resolveApiServerConfig, resolveFsAllow, createDebugger, createViteLogger, createViteServer } from 'vitest/node';
|
|
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, deepMerge
|
|
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
|
-
import * as vite from 'vite';
|
|
11
11
|
import { coverageConfigDefaults } from 'vitest/config';
|
|
12
|
-
import { fileURLToPath } from 'node:url';
|
|
13
12
|
import crypto from 'node:crypto';
|
|
14
|
-
import {
|
|
13
|
+
import { readFile as readFile$1, mkdir, writeFile as writeFile$1 } from 'node:fs/promises';
|
|
15
14
|
import { parseErrorStacktrace, parseStacktrace } from '@vitest/utils/source-map';
|
|
16
|
-
import {
|
|
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.
|
|
21
|
+
var version = "4.0.1";
|
|
26
22
|
|
|
27
23
|
const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
|
|
28
24
|
function normalizeWindowsPath(input = "") {
|
|
@@ -324,64 +320,383 @@ const stringify = (value, replacer, space) => {
|
|
|
324
320
|
}
|
|
325
321
|
};
|
|
326
322
|
|
|
327
|
-
function
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
+
}
|
|
334
502
|
];
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
+
};
|
|
351
529
|
}
|
|
352
|
-
function
|
|
353
|
-
|
|
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
|
+
};
|
|
354
541
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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
|
+
}
|
|
689
|
+
const injectorJs = typeof globalServer.injectorJs === "string" ? globalServer.injectorJs : await globalServer.injectorJs;
|
|
690
|
+
const injector = replacer(injectorJs, {
|
|
691
|
+
__VITEST_PROVIDER__: JSON.stringify(browserProject.config.browser.provider?.name || "preview"),
|
|
692
|
+
__VITEST_CONFIG__: JSON.stringify(browserProject.wrapSerializedConfig()),
|
|
693
|
+
__VITEST_VITE_CONFIG__: JSON.stringify({ root: browserProject.vite.config.root }),
|
|
694
|
+
__VITEST_METHOD__: JSON.stringify("orchestrate"),
|
|
695
|
+
__VITEST_TYPE__: "\"orchestrator\"",
|
|
696
|
+
__VITEST_SESSION_ID__: JSON.stringify(sessionId),
|
|
697
|
+
__VITEST_TESTER_ID__: "\"none\"",
|
|
698
|
+
__VITEST_PROVIDED_CONTEXT__: JSON.stringify(stringify(browserProject.project.getProvidedContext())),
|
|
699
|
+
__VITEST_API_TOKEN__: JSON.stringify(globalServer.vitest.config.api.token)
|
|
385
700
|
});
|
|
386
701
|
// disable CSP for the orchestrator as we are the ones controlling it
|
|
387
702
|
res.removeHeader("Content-Security-Policy");
|
|
@@ -513,36 +828,58 @@ function createTesterMiddleware(browserServer) {
|
|
|
513
828
|
};
|
|
514
829
|
}
|
|
515
830
|
|
|
516
|
-
const VIRTUAL_ID_CONTEXT = "\
|
|
517
|
-
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";
|
|
518
837
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
519
838
|
function BrowserContext(globalServer) {
|
|
520
839
|
return {
|
|
521
840
|
name: "vitest:browser:virtual-module:context",
|
|
522
841
|
enforce: "pre",
|
|
523
|
-
resolveId(id) {
|
|
842
|
+
resolveId(id, importer) {
|
|
524
843
|
if (id === ID_CONTEXT) {
|
|
525
844
|
return VIRTUAL_ID_CONTEXT;
|
|
526
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
|
+
}
|
|
527
855
|
},
|
|
528
856
|
load(id) {
|
|
529
857
|
if (id === VIRTUAL_ID_CONTEXT) {
|
|
530
858
|
return generateContextFile.call(this, globalServer);
|
|
531
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
|
+
}
|
|
532
869
|
}
|
|
533
870
|
};
|
|
534
871
|
}
|
|
535
872
|
async function generateContextFile(globalServer) {
|
|
536
873
|
const commands = Object.keys(globalServer.commands);
|
|
537
|
-
const provider = [...globalServer.children][0].provider
|
|
538
|
-
const providerName = provider
|
|
874
|
+
const provider = [...globalServer.children][0].provider;
|
|
875
|
+
const providerName = provider?.name || "preview";
|
|
539
876
|
const commandsCode = commands.filter((command) => !command.startsWith("__vitest")).map((command) => {
|
|
540
877
|
return ` ["${command}"]: (...args) => __vitest_browser_runner__.commands.triggerCommand("${command}", args),`;
|
|
541
878
|
}).join("\n");
|
|
542
|
-
const userEventNonProviderImport = await getUserEventImport(
|
|
879
|
+
const userEventNonProviderImport = await getUserEventImport(provider, this.resolve.bind(this));
|
|
543
880
|
const distContextPath = slash$1(`/@fs/${resolve(__dirname, "context.js")}`);
|
|
544
881
|
return `
|
|
545
|
-
import { page, createUserEvent, cdp, locators } from '${distContextPath}'
|
|
882
|
+
import { page, createUserEvent, cdp, locators, utils } from '${distContextPath}'
|
|
546
883
|
${userEventNonProviderImport}
|
|
547
884
|
|
|
548
885
|
export const server = {
|
|
@@ -557,16 +894,17 @@ export const server = {
|
|
|
557
894
|
}
|
|
558
895
|
export const commands = server.commands
|
|
559
896
|
export const userEvent = createUserEvent(_userEventSetup)
|
|
560
|
-
export { page, cdp, locators }
|
|
897
|
+
export { page, cdp, locators, utils }
|
|
561
898
|
`;
|
|
562
899
|
}
|
|
563
900
|
async function getUserEventImport(provider, resolve) {
|
|
564
|
-
if (provider !== "preview") {
|
|
901
|
+
if (!provider || provider.name !== "preview") {
|
|
565
902
|
return "const _userEventSetup = undefined";
|
|
566
903
|
}
|
|
567
|
-
const
|
|
904
|
+
const previewDistRoot = provider.distRoot;
|
|
905
|
+
const resolved = await resolve("@testing-library/user-event", previewDistRoot);
|
|
568
906
|
if (!resolved) {
|
|
569
|
-
throw new Error(`Failed to resolve user-event package from ${
|
|
907
|
+
throw new Error(`Failed to resolve user-event package from ${previewDistRoot}`);
|
|
570
908
|
}
|
|
571
909
|
return `\
|
|
572
910
|
import { userEvent as __vitest_user_event__ } from '${slash$1(`/@fs/${resolved.id}`)}'
|
|
@@ -727,9 +1065,12 @@ var BrowserPlugin = (parentServer, base = "/") => {
|
|
|
727
1065
|
];
|
|
728
1066
|
const exclude = [
|
|
729
1067
|
"vitest",
|
|
1068
|
+
"vitest/browser",
|
|
730
1069
|
"vitest/internal/browser",
|
|
731
1070
|
"vitest/runners",
|
|
732
|
-
"
|
|
1071
|
+
"vite/module-runner",
|
|
1072
|
+
"@vitest/browser/utils",
|
|
1073
|
+
"@vitest/browser/context",
|
|
733
1074
|
"@vitest/browser/client",
|
|
734
1075
|
"@vitest/utils",
|
|
735
1076
|
"@vitest/utils/source-map",
|
|
@@ -771,13 +1112,12 @@ var BrowserPlugin = (parentServer, base = "/") => {
|
|
|
771
1112
|
const include = [
|
|
772
1113
|
"vitest > expect-type",
|
|
773
1114
|
"vitest > @vitest/snapshot > magic-string",
|
|
774
|
-
"vitest > @vitest/
|
|
775
|
-
"vitest > @vitest/expect > chai",
|
|
776
|
-
"vitest > @vitest/expect > chai > loupe",
|
|
777
|
-
"vitest > @vitest/utils > loupe",
|
|
778
|
-
"@vitest/browser > @testing-library/user-event",
|
|
779
|
-
"@vitest/browser > @testing-library/dom"
|
|
1115
|
+
"vitest > @vitest/expect > chai"
|
|
780
1116
|
];
|
|
1117
|
+
const provider = parentServer.config.browser.provider || [...parentServer.children][0]?.provider;
|
|
1118
|
+
if (provider?.name === "preview") {
|
|
1119
|
+
include.push("@vitest/browser-preview > @testing-library/user-event", "@vitest/browser-preview > @testing-library/dom");
|
|
1120
|
+
}
|
|
781
1121
|
const fileRoot = browserTestFiles[0] ? dirname(browserTestFiles[0]) : project.config.root;
|
|
782
1122
|
const svelte = isPackageExists("vitest-browser-svelte", fileRoot);
|
|
783
1123
|
if (svelte) {
|
|
@@ -991,20 +1331,21 @@ body {
|
|
|
991
1331
|
},
|
|
992
1332
|
injectTo: "head"
|
|
993
1333
|
},
|
|
994
|
-
parentServer.
|
|
1334
|
+
...parentServer.initScripts.map((script) => ({
|
|
995
1335
|
tag: "script",
|
|
996
1336
|
attrs: {
|
|
997
1337
|
type: "module",
|
|
998
|
-
src:
|
|
1338
|
+
src: join("/@fs/", script)
|
|
999
1339
|
},
|
|
1000
1340
|
injectTo: "head"
|
|
1001
|
-
}
|
|
1341
|
+
})),
|
|
1002
1342
|
...testerTags
|
|
1003
1343
|
].filter((s) => s != null);
|
|
1004
1344
|
}
|
|
1005
1345
|
},
|
|
1006
1346
|
{
|
|
1007
1347
|
name: "vitest:browser:support-testing-library",
|
|
1348
|
+
enforce: "pre",
|
|
1008
1349
|
config() {
|
|
1009
1350
|
const rolldownPlugin = {
|
|
1010
1351
|
name: "vue-test-utils-rewrite",
|
|
@@ -1027,7 +1368,7 @@ body {
|
|
|
1027
1368
|
});
|
|
1028
1369
|
}
|
|
1029
1370
|
};
|
|
1030
|
-
return { optimizeDeps:
|
|
1371
|
+
return { optimizeDeps: rolldownVersion ? { rollupOptions: { plugins: [rolldownPlugin] } } : { esbuildOptions: { plugins: [esbuildPlugin] } } };
|
|
1031
1372
|
}
|
|
1032
1373
|
}
|
|
1033
1374
|
];
|
|
@@ -1112,114 +1453,6 @@ class BrowserServerCDPHandler {
|
|
|
1112
1453
|
}
|
|
1113
1454
|
}
|
|
1114
1455
|
|
|
1115
|
-
const clear = async (context, selector) => {
|
|
1116
|
-
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
1117
|
-
const { iframe } = context;
|
|
1118
|
-
const element = iframe.locator(selector);
|
|
1119
|
-
await element.clear();
|
|
1120
|
-
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
1121
|
-
const browser = context.browser;
|
|
1122
|
-
const element = await browser.$(selector);
|
|
1123
|
-
await element.clearValue();
|
|
1124
|
-
} else {
|
|
1125
|
-
throw new TypeError(`Provider "${context.provider.name}" does not support clearing elements`);
|
|
1126
|
-
}
|
|
1127
|
-
};
|
|
1128
|
-
|
|
1129
|
-
const click = async (context, selector, options = {}) => {
|
|
1130
|
-
const provider = context.provider;
|
|
1131
|
-
if (provider instanceof PlaywrightBrowserProvider) {
|
|
1132
|
-
const tester = context.iframe;
|
|
1133
|
-
await tester.locator(selector).click(options);
|
|
1134
|
-
} else if (provider instanceof WebdriverBrowserProvider) {
|
|
1135
|
-
const browser = context.browser;
|
|
1136
|
-
await browser.$(selector).click(options);
|
|
1137
|
-
} else {
|
|
1138
|
-
throw new TypeError(`Provider "${provider.name}" doesn't support click command`);
|
|
1139
|
-
}
|
|
1140
|
-
};
|
|
1141
|
-
const dblClick = async (context, selector, options = {}) => {
|
|
1142
|
-
const provider = context.provider;
|
|
1143
|
-
if (provider instanceof PlaywrightBrowserProvider) {
|
|
1144
|
-
const tester = context.iframe;
|
|
1145
|
-
await tester.locator(selector).dblclick(options);
|
|
1146
|
-
} else if (provider instanceof WebdriverBrowserProvider) {
|
|
1147
|
-
const browser = context.browser;
|
|
1148
|
-
await browser.$(selector).doubleClick();
|
|
1149
|
-
} else {
|
|
1150
|
-
throw new TypeError(`Provider "${provider.name}" doesn't support dblClick command`);
|
|
1151
|
-
}
|
|
1152
|
-
};
|
|
1153
|
-
const tripleClick = async (context, selector, options = {}) => {
|
|
1154
|
-
const provider = context.provider;
|
|
1155
|
-
if (provider instanceof PlaywrightBrowserProvider) {
|
|
1156
|
-
const tester = context.iframe;
|
|
1157
|
-
await tester.locator(selector).click({
|
|
1158
|
-
...options,
|
|
1159
|
-
clickCount: 3
|
|
1160
|
-
});
|
|
1161
|
-
} else if (provider instanceof WebdriverBrowserProvider) {
|
|
1162
|
-
const browser = context.browser;
|
|
1163
|
-
await browser.action("pointer", { parameters: { pointerType: "mouse" } }).move({ origin: browser.$(selector) }).down().up().pause(50).down().up().pause(50).down().up().pause(50).perform();
|
|
1164
|
-
} else {
|
|
1165
|
-
throw new TypeError(`Provider "${provider.name}" doesn't support tripleClick command`);
|
|
1166
|
-
}
|
|
1167
|
-
};
|
|
1168
|
-
|
|
1169
|
-
const dragAndDrop = async (context, source, target, options_) => {
|
|
1170
|
-
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
1171
|
-
const frame = await context.frame();
|
|
1172
|
-
await frame.dragAndDrop(source, target, options_);
|
|
1173
|
-
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
1174
|
-
const $source = context.browser.$(source);
|
|
1175
|
-
const $target = context.browser.$(target);
|
|
1176
|
-
const options = options_ || {};
|
|
1177
|
-
const duration = options.duration ?? 10;
|
|
1178
|
-
// https://github.com/webdriverio/webdriverio/issues/8022#issuecomment-1700919670
|
|
1179
|
-
await context.browser.action("pointer").move({
|
|
1180
|
-
duration: 0,
|
|
1181
|
-
origin: $source,
|
|
1182
|
-
x: options.sourceX ?? 0,
|
|
1183
|
-
y: options.sourceY ?? 0
|
|
1184
|
-
}).down({ button: 0 }).move({
|
|
1185
|
-
duration: 0,
|
|
1186
|
-
origin: "pointer",
|
|
1187
|
-
x: 0,
|
|
1188
|
-
y: 0
|
|
1189
|
-
}).pause(duration).move({
|
|
1190
|
-
duration: 0,
|
|
1191
|
-
origin: $target,
|
|
1192
|
-
x: options.targetX ?? 0,
|
|
1193
|
-
y: options.targetY ?? 0
|
|
1194
|
-
}).move({
|
|
1195
|
-
duration: 0,
|
|
1196
|
-
origin: "pointer",
|
|
1197
|
-
x: 1,
|
|
1198
|
-
y: 0
|
|
1199
|
-
}).move({
|
|
1200
|
-
duration: 0,
|
|
1201
|
-
origin: "pointer",
|
|
1202
|
-
x: -1,
|
|
1203
|
-
y: 0
|
|
1204
|
-
}).up({ button: 0 }).perform();
|
|
1205
|
-
} else {
|
|
1206
|
-
throw new TypeError(`Provider "${context.provider.name}" does not support dragging elements`);
|
|
1207
|
-
}
|
|
1208
|
-
};
|
|
1209
|
-
|
|
1210
|
-
const fill = async (context, selector, text, options = {}) => {
|
|
1211
|
-
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
1212
|
-
const { iframe } = context;
|
|
1213
|
-
const element = iframe.locator(selector);
|
|
1214
|
-
await element.fill(text, options);
|
|
1215
|
-
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
1216
|
-
const browser = context.browser;
|
|
1217
|
-
await browser.$(selector).setValue(text);
|
|
1218
|
-
} else {
|
|
1219
|
-
throw new TypeError(`Provider "${context.provider.name}" does not support filling inputs`);
|
|
1220
|
-
}
|
|
1221
|
-
};
|
|
1222
|
-
|
|
1223
1456
|
const types = {
|
|
1224
1457
|
'application/andrew-inset': ['ez'],
|
|
1225
1458
|
'application/appinstaller': ['appinstaller'],
|
|
@@ -1638,818 +1871,87 @@ class Mime {
|
|
|
1638
1871
|
const hasDot = ext.length < last.length - 1;
|
|
1639
1872
|
if (!hasDot && hasPath)
|
|
1640
1873
|
return null;
|
|
1641
|
-
return __classPrivateFieldGet(this, _Mime_extensionToType, "f").get(ext) ?? null;
|
|
1642
|
-
}
|
|
1643
|
-
getExtension(type) {
|
|
1644
|
-
if (typeof type !== 'string')
|
|
1645
|
-
return null;
|
|
1646
|
-
type = type?.split?.(';')[0];
|
|
1647
|
-
return ((type && __classPrivateFieldGet(this, _Mime_typeToExtension, "f").get(type.trim().toLowerCase())) ?? null);
|
|
1648
|
-
}
|
|
1649
|
-
getAllExtensions(type) {
|
|
1650
|
-
if (typeof type !== 'string')
|
|
1651
|
-
return null;
|
|
1652
|
-
return __classPrivateFieldGet(this, _Mime_typeToExtensions, "f").get(type.toLowerCase()) ?? null;
|
|
1653
|
-
}
|
|
1654
|
-
_freeze() {
|
|
1655
|
-
this.define = () => {
|
|
1656
|
-
throw new Error('define() not allowed for built-in Mime objects. See https://github.com/broofa/mime/blob/main/README.md#custom-mime-instances');
|
|
1657
|
-
};
|
|
1658
|
-
Object.freeze(this);
|
|
1659
|
-
for (const extensions of __classPrivateFieldGet(this, _Mime_typeToExtensions, "f").values()) {
|
|
1660
|
-
Object.freeze(extensions);
|
|
1661
|
-
}
|
|
1662
|
-
return this;
|
|
1663
|
-
}
|
|
1664
|
-
_getTestState() {
|
|
1665
|
-
return {
|
|
1666
|
-
types: __classPrivateFieldGet(this, _Mime_extensionToType, "f"),
|
|
1667
|
-
extensions: __classPrivateFieldGet(this, _Mime_typeToExtension, "f"),
|
|
1668
|
-
};
|
|
1669
|
-
}
|
|
1670
|
-
}
|
|
1671
|
-
_Mime_extensionToType = new WeakMap(), _Mime_typeToExtension = new WeakMap(), _Mime_typeToExtensions = new WeakMap();
|
|
1672
|
-
|
|
1673
|
-
var mime = new Mime(types)._freeze();
|
|
1674
|
-
|
|
1675
|
-
function assertFileAccess(path, project) {
|
|
1676
|
-
if (!isFileServingAllowed(path, project.vite) && !isFileServingAllowed(path, project.vitest.vite)) {
|
|
1677
|
-
throw new Error(`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`);
|
|
1678
|
-
}
|
|
1679
|
-
}
|
|
1680
|
-
const readFile = async ({ project }, path, options = {}) => {
|
|
1681
|
-
const filepath = resolve$1(project.config.root, path);
|
|
1682
|
-
assertFileAccess(filepath, project);
|
|
1683
|
-
// never return a Buffer
|
|
1684
|
-
if (typeof options === "object" && !options.encoding) {
|
|
1685
|
-
options.encoding = "utf-8";
|
|
1686
|
-
}
|
|
1687
|
-
return promises.readFile(filepath, options);
|
|
1688
|
-
};
|
|
1689
|
-
const writeFile = async ({ project }, path, data, options) => {
|
|
1690
|
-
const filepath = resolve$1(project.config.root, path);
|
|
1691
|
-
assertFileAccess(filepath, project);
|
|
1692
|
-
const dir = dirname$1(filepath);
|
|
1693
|
-
if (!fs.existsSync(dir)) {
|
|
1694
|
-
await promises.mkdir(dir, { recursive: true });
|
|
1695
|
-
}
|
|
1696
|
-
await promises.writeFile(filepath, data, options);
|
|
1697
|
-
};
|
|
1698
|
-
const removeFile = async ({ project }, path) => {
|
|
1699
|
-
const filepath = resolve$1(project.config.root, path);
|
|
1700
|
-
assertFileAccess(filepath, project);
|
|
1701
|
-
await promises.rm(filepath);
|
|
1702
|
-
};
|
|
1703
|
-
const _fileInfo = async ({ project }, path, encoding) => {
|
|
1704
|
-
const filepath = resolve$1(project.config.root, path);
|
|
1705
|
-
assertFileAccess(filepath, project);
|
|
1706
|
-
const content = await promises.readFile(filepath, encoding || "base64");
|
|
1707
|
-
return {
|
|
1708
|
-
content,
|
|
1709
|
-
basename: basename$1(filepath),
|
|
1710
|
-
mime: mime.getType(filepath)
|
|
1711
|
-
};
|
|
1712
|
-
};
|
|
1713
|
-
|
|
1714
|
-
const hover = async (context, selector, options = {}) => {
|
|
1715
|
-
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
1716
|
-
await context.iframe.locator(selector).hover(options);
|
|
1717
|
-
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
1718
|
-
const browser = context.browser;
|
|
1719
|
-
await browser.$(selector).moveTo(options);
|
|
1720
|
-
} else {
|
|
1721
|
-
throw new TypeError(`Provider "${context.provider.name}" does not support hover`);
|
|
1722
|
-
}
|
|
1723
|
-
};
|
|
1724
|
-
|
|
1725
|
-
var DOM_KEY_LOCATION = /*#__PURE__*/ function(DOM_KEY_LOCATION) {
|
|
1726
|
-
DOM_KEY_LOCATION[DOM_KEY_LOCATION["STANDARD"] = 0] = "STANDARD";
|
|
1727
|
-
DOM_KEY_LOCATION[DOM_KEY_LOCATION["LEFT"] = 1] = "LEFT";
|
|
1728
|
-
DOM_KEY_LOCATION[DOM_KEY_LOCATION["RIGHT"] = 2] = "RIGHT";
|
|
1729
|
-
DOM_KEY_LOCATION[DOM_KEY_LOCATION["NUMPAD"] = 3] = "NUMPAD";
|
|
1730
|
-
return DOM_KEY_LOCATION;
|
|
1731
|
-
}({});
|
|
1732
|
-
|
|
1733
|
-
/**
|
|
1734
|
-
* Mapping for a default US-104-QWERTY keyboard
|
|
1735
|
-
*/ const defaultKeyMap = [
|
|
1736
|
-
// alphanumeric block - writing system
|
|
1737
|
-
...'0123456789'.split('').map((c)=>({
|
|
1738
|
-
code: `Digit${c}`,
|
|
1739
|
-
key: c
|
|
1740
|
-
})),
|
|
1741
|
-
...')!@#$%^&*('.split('').map((c, i)=>({
|
|
1742
|
-
code: `Digit${i}`,
|
|
1743
|
-
key: c,
|
|
1744
|
-
shiftKey: true
|
|
1745
|
-
})),
|
|
1746
|
-
...'abcdefghijklmnopqrstuvwxyz'.split('').map((c)=>({
|
|
1747
|
-
code: `Key${c.toUpperCase()}`,
|
|
1748
|
-
key: c
|
|
1749
|
-
})),
|
|
1750
|
-
...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').map((c)=>({
|
|
1751
|
-
code: `Key${c}`,
|
|
1752
|
-
key: c,
|
|
1753
|
-
shiftKey: true
|
|
1754
|
-
})),
|
|
1755
|
-
{
|
|
1756
|
-
code: 'BracketLeft',
|
|
1757
|
-
key: '['
|
|
1758
|
-
},
|
|
1759
|
-
{
|
|
1760
|
-
code: 'BracketLeft',
|
|
1761
|
-
key: '{',
|
|
1762
|
-
shiftKey: true
|
|
1763
|
-
},
|
|
1764
|
-
{
|
|
1765
|
-
code: 'BracketRight',
|
|
1766
|
-
key: ']'
|
|
1767
|
-
},
|
|
1768
|
-
{
|
|
1769
|
-
code: 'BracketRight',
|
|
1770
|
-
key: '}',
|
|
1771
|
-
shiftKey: true
|
|
1772
|
-
},
|
|
1773
|
-
// alphanumeric block - functional
|
|
1774
|
-
{
|
|
1775
|
-
code: 'Space',
|
|
1776
|
-
key: ' '
|
|
1777
|
-
},
|
|
1778
|
-
{
|
|
1779
|
-
code: 'AltLeft',
|
|
1780
|
-
key: 'Alt',
|
|
1781
|
-
location: DOM_KEY_LOCATION.LEFT
|
|
1782
|
-
},
|
|
1783
|
-
{
|
|
1784
|
-
code: 'AltRight',
|
|
1785
|
-
key: 'Alt',
|
|
1786
|
-
location: DOM_KEY_LOCATION.RIGHT
|
|
1787
|
-
},
|
|
1788
|
-
{
|
|
1789
|
-
code: 'ShiftLeft',
|
|
1790
|
-
key: 'Shift',
|
|
1791
|
-
location: DOM_KEY_LOCATION.LEFT
|
|
1792
|
-
},
|
|
1793
|
-
{
|
|
1794
|
-
code: 'ShiftRight',
|
|
1795
|
-
key: 'Shift',
|
|
1796
|
-
location: DOM_KEY_LOCATION.RIGHT
|
|
1797
|
-
},
|
|
1798
|
-
{
|
|
1799
|
-
code: 'ControlLeft',
|
|
1800
|
-
key: 'Control',
|
|
1801
|
-
location: DOM_KEY_LOCATION.LEFT
|
|
1802
|
-
},
|
|
1803
|
-
{
|
|
1804
|
-
code: 'ControlRight',
|
|
1805
|
-
key: 'Control',
|
|
1806
|
-
location: DOM_KEY_LOCATION.RIGHT
|
|
1807
|
-
},
|
|
1808
|
-
{
|
|
1809
|
-
code: 'MetaLeft',
|
|
1810
|
-
key: 'Meta',
|
|
1811
|
-
location: DOM_KEY_LOCATION.LEFT
|
|
1812
|
-
},
|
|
1813
|
-
{
|
|
1814
|
-
code: 'MetaRight',
|
|
1815
|
-
key: 'Meta',
|
|
1816
|
-
location: DOM_KEY_LOCATION.RIGHT
|
|
1817
|
-
},
|
|
1818
|
-
{
|
|
1819
|
-
code: 'OSLeft',
|
|
1820
|
-
key: 'OS',
|
|
1821
|
-
location: DOM_KEY_LOCATION.LEFT
|
|
1822
|
-
},
|
|
1823
|
-
{
|
|
1824
|
-
code: 'OSRight',
|
|
1825
|
-
key: 'OS',
|
|
1826
|
-
location: DOM_KEY_LOCATION.RIGHT
|
|
1827
|
-
},
|
|
1828
|
-
{
|
|
1829
|
-
code: 'ContextMenu',
|
|
1830
|
-
key: 'ContextMenu'
|
|
1831
|
-
},
|
|
1832
|
-
{
|
|
1833
|
-
code: 'Tab',
|
|
1834
|
-
key: 'Tab'
|
|
1835
|
-
},
|
|
1836
|
-
{
|
|
1837
|
-
code: 'CapsLock',
|
|
1838
|
-
key: 'CapsLock'
|
|
1839
|
-
},
|
|
1840
|
-
{
|
|
1841
|
-
code: 'Backspace',
|
|
1842
|
-
key: 'Backspace'
|
|
1843
|
-
},
|
|
1844
|
-
{
|
|
1845
|
-
code: 'Enter',
|
|
1846
|
-
key: 'Enter'
|
|
1847
|
-
},
|
|
1848
|
-
// function
|
|
1849
|
-
{
|
|
1850
|
-
code: 'Escape',
|
|
1851
|
-
key: 'Escape'
|
|
1852
|
-
},
|
|
1853
|
-
// arrows
|
|
1854
|
-
{
|
|
1855
|
-
code: 'ArrowUp',
|
|
1856
|
-
key: 'ArrowUp'
|
|
1857
|
-
},
|
|
1858
|
-
{
|
|
1859
|
-
code: 'ArrowDown',
|
|
1860
|
-
key: 'ArrowDown'
|
|
1861
|
-
},
|
|
1862
|
-
{
|
|
1863
|
-
code: 'ArrowLeft',
|
|
1864
|
-
key: 'ArrowLeft'
|
|
1865
|
-
},
|
|
1866
|
-
{
|
|
1867
|
-
code: 'ArrowRight',
|
|
1868
|
-
key: 'ArrowRight'
|
|
1869
|
-
},
|
|
1870
|
-
// control pad
|
|
1871
|
-
{
|
|
1872
|
-
code: 'Home',
|
|
1873
|
-
key: 'Home'
|
|
1874
|
-
},
|
|
1875
|
-
{
|
|
1876
|
-
code: 'End',
|
|
1877
|
-
key: 'End'
|
|
1878
|
-
},
|
|
1879
|
-
{
|
|
1880
|
-
code: 'Delete',
|
|
1881
|
-
key: 'Delete'
|
|
1882
|
-
},
|
|
1883
|
-
{
|
|
1884
|
-
code: 'PageUp',
|
|
1885
|
-
key: 'PageUp'
|
|
1886
|
-
},
|
|
1887
|
-
{
|
|
1888
|
-
code: 'PageDown',
|
|
1889
|
-
key: 'PageDown'
|
|
1890
|
-
},
|
|
1891
|
-
// Special keys that are not part of a default US-layout but included for specific behavior
|
|
1892
|
-
{
|
|
1893
|
-
code: 'Fn',
|
|
1894
|
-
key: 'Fn'
|
|
1895
|
-
},
|
|
1896
|
-
{
|
|
1897
|
-
code: 'Symbol',
|
|
1898
|
-
key: 'Symbol'
|
|
1899
|
-
},
|
|
1900
|
-
{
|
|
1901
|
-
code: 'AltRight',
|
|
1902
|
-
key: 'AltGraph'
|
|
1903
|
-
}
|
|
1904
|
-
];
|
|
1905
|
-
|
|
1906
|
-
var bracketDict = /*#__PURE__*/ function(bracketDict) {
|
|
1907
|
-
bracketDict["{"] = "}";
|
|
1908
|
-
bracketDict["["] = "]";
|
|
1909
|
-
return bracketDict;
|
|
1910
|
-
}(bracketDict || {});
|
|
1911
|
-
/**
|
|
1912
|
-
* Read the next key definition from user input
|
|
1913
|
-
*
|
|
1914
|
-
* Describe key per `{descriptor}` or `[descriptor]`.
|
|
1915
|
-
* Everything else will be interpreted as a single character as descriptor - e.g. `a`.
|
|
1916
|
-
* Brackets `{` and `[` can be escaped by doubling - e.g. `foo[[bar` translates to `foo[bar`.
|
|
1917
|
-
* A previously pressed key can be released per `{/descriptor}`.
|
|
1918
|
-
* Keeping the key pressed can be written as `{descriptor>}`.
|
|
1919
|
-
* When keeping the key pressed you can choose how long the key is pressed `{descriptor>3}`.
|
|
1920
|
-
* You can then release the key per `{descriptor>3/}` or keep it pressed and continue with the next key.
|
|
1921
|
-
*/ function readNextDescriptor(text, context) {
|
|
1922
|
-
let pos = 0;
|
|
1923
|
-
const startBracket = text[pos] in bracketDict ? text[pos] : '';
|
|
1924
|
-
pos += startBracket.length;
|
|
1925
|
-
const isEscapedChar = new RegExp(`^\\${startBracket}{2}`).test(text);
|
|
1926
|
-
const type = isEscapedChar ? '' : startBracket;
|
|
1927
|
-
return {
|
|
1928
|
-
type,
|
|
1929
|
-
...type === '' ? readPrintableChar(text, pos) : readTag(text, pos, type)
|
|
1930
|
-
};
|
|
1931
|
-
}
|
|
1932
|
-
function readPrintableChar(text, pos, context) {
|
|
1933
|
-
const descriptor = text[pos];
|
|
1934
|
-
assertDescriptor(descriptor, text, pos);
|
|
1935
|
-
pos += descriptor.length;
|
|
1936
|
-
return {
|
|
1937
|
-
consumedLength: pos,
|
|
1938
|
-
descriptor,
|
|
1939
|
-
releasePrevious: false,
|
|
1940
|
-
releaseSelf: true,
|
|
1941
|
-
repeat: 1
|
|
1942
|
-
};
|
|
1943
|
-
}
|
|
1944
|
-
function readTag(text, pos, startBracket, context) {
|
|
1945
|
-
var _text_slice_match, _text_slice_match1;
|
|
1946
|
-
const releasePreviousModifier = text[pos] === '/' ? '/' : '';
|
|
1947
|
-
pos += releasePreviousModifier.length;
|
|
1948
|
-
const escapedDescriptor = startBracket === '{' && text[pos] === '\\';
|
|
1949
|
-
pos += Number(escapedDescriptor);
|
|
1950
|
-
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];
|
|
1951
|
-
assertDescriptor(descriptor, text, pos);
|
|
1952
|
-
pos += descriptor.length;
|
|
1953
|
-
var _text_slice_match_;
|
|
1954
|
-
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_ : '';
|
|
1955
|
-
pos += repeatModifier.length;
|
|
1956
|
-
const releaseSelfModifier = text[pos] === '/' || !repeatModifier && text[pos] === '>' ? text[pos] : '';
|
|
1957
|
-
pos += releaseSelfModifier.length;
|
|
1958
|
-
const expectedEndBracket = bracketDict[startBracket];
|
|
1959
|
-
const endBracket = text[pos] === expectedEndBracket ? expectedEndBracket : '';
|
|
1960
|
-
if (!endBracket) {
|
|
1961
|
-
throw new Error(getErrorMessage([
|
|
1962
|
-
!repeatModifier && 'repeat modifier',
|
|
1963
|
-
!releaseSelfModifier && 'release modifier',
|
|
1964
|
-
`"${expectedEndBracket}"`
|
|
1965
|
-
].filter(Boolean).join(' or '), text[pos], text));
|
|
1966
|
-
}
|
|
1967
|
-
pos += endBracket.length;
|
|
1968
|
-
return {
|
|
1969
|
-
consumedLength: pos,
|
|
1970
|
-
descriptor,
|
|
1971
|
-
releasePrevious: !!releasePreviousModifier,
|
|
1972
|
-
repeat: repeatModifier ? Math.max(Number(repeatModifier.substr(1)), 1) : 1,
|
|
1973
|
-
releaseSelf: hasReleaseSelf(releaseSelfModifier, repeatModifier)
|
|
1974
|
-
};
|
|
1975
|
-
}
|
|
1976
|
-
function assertDescriptor(descriptor, text, pos, context) {
|
|
1977
|
-
if (!descriptor) {
|
|
1978
|
-
throw new Error(getErrorMessage('key descriptor', text[pos], text));
|
|
1979
|
-
}
|
|
1980
|
-
}
|
|
1981
|
-
function hasReleaseSelf(releaseSelfModifier, repeatModifier) {
|
|
1982
|
-
if (releaseSelfModifier) {
|
|
1983
|
-
return releaseSelfModifier === '/';
|
|
1984
|
-
}
|
|
1985
|
-
if (repeatModifier) {
|
|
1986
|
-
return false;
|
|
1987
|
-
}
|
|
1988
|
-
}
|
|
1989
|
-
function getErrorMessage(expected, found, text, context) {
|
|
1990
|
-
return `Expected ${expected} but found "${found !== null && found !== undefined ? found : ''}" in "${text}"
|
|
1991
|
-
See ${`https://testing-library.com/docs/user-event/keyboard`}
|
|
1992
|
-
for more information about how userEvent parses your input.`;
|
|
1993
|
-
}
|
|
1994
|
-
|
|
1995
|
-
/**
|
|
1996
|
-
* Parse key definitions per `keyboardMap`
|
|
1997
|
-
*
|
|
1998
|
-
* Keys can be referenced by `{key}` or `{special}` as well as physical locations per `[code]`.
|
|
1999
|
-
* Everything else will be interpreted as a typed character - e.g. `a`.
|
|
2000
|
-
* Brackets `{` and `[` can be escaped by doubling - e.g. `foo[[bar` translates to `foo[bar`.
|
|
2001
|
-
* Keeping the key pressed can be written as `{key>}`.
|
|
2002
|
-
* When keeping the key pressed you can choose how long (how many keydown and keypress) the key is pressed `{key>3}`.
|
|
2003
|
-
* You can then release the key per `{key>3/}` or keep it pressed and continue with the next key.
|
|
2004
|
-
*/ function parseKeyDef(keyboardMap, text) {
|
|
2005
|
-
const defs = [];
|
|
2006
|
-
do {
|
|
2007
|
-
const { type, descriptor, consumedLength, releasePrevious, releaseSelf = true, repeat } = readNextDescriptor(text);
|
|
2008
|
-
var _keyboardMap_find;
|
|
2009
|
-
const keyDef = (_keyboardMap_find = keyboardMap.find((def)=>{
|
|
2010
|
-
if (type === '[') {
|
|
2011
|
-
var _def_code;
|
|
2012
|
-
return ((_def_code = def.code) === null || _def_code === undefined ? undefined : _def_code.toLowerCase()) === descriptor.toLowerCase();
|
|
2013
|
-
} else if (type === '{') {
|
|
2014
|
-
var _def_key;
|
|
2015
|
-
return ((_def_key = def.key) === null || _def_key === undefined ? undefined : _def_key.toLowerCase()) === descriptor.toLowerCase();
|
|
2016
|
-
}
|
|
2017
|
-
return def.key === descriptor;
|
|
2018
|
-
})) !== null && _keyboardMap_find !== undefined ? _keyboardMap_find : {
|
|
2019
|
-
key: 'Unknown',
|
|
2020
|
-
code: 'Unknown',
|
|
2021
|
-
[type === '[' ? 'code' : 'key']: descriptor
|
|
1874
|
+
return __classPrivateFieldGet(this, _Mime_extensionToType, "f").get(ext) ?? null;
|
|
1875
|
+
}
|
|
1876
|
+
getExtension(type) {
|
|
1877
|
+
if (typeof type !== 'string')
|
|
1878
|
+
return null;
|
|
1879
|
+
type = type?.split?.(';')[0];
|
|
1880
|
+
return ((type && __classPrivateFieldGet(this, _Mime_typeToExtension, "f").get(type.trim().toLowerCase())) ?? null);
|
|
1881
|
+
}
|
|
1882
|
+
getAllExtensions(type) {
|
|
1883
|
+
if (typeof type !== 'string')
|
|
1884
|
+
return null;
|
|
1885
|
+
return __classPrivateFieldGet(this, _Mime_typeToExtensions, "f").get(type.toLowerCase()) ?? null;
|
|
1886
|
+
}
|
|
1887
|
+
_freeze() {
|
|
1888
|
+
this.define = () => {
|
|
1889
|
+
throw new Error('define() not allowed for built-in Mime objects. See https://github.com/broofa/mime/blob/main/README.md#custom-mime-instances');
|
|
2022
1890
|
};
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
1891
|
+
Object.freeze(this);
|
|
1892
|
+
for (const extensions of __classPrivateFieldGet(this, _Mime_typeToExtensions, "f").values()) {
|
|
1893
|
+
Object.freeze(extensions);
|
|
1894
|
+
}
|
|
1895
|
+
return this;
|
|
1896
|
+
}
|
|
1897
|
+
_getTestState() {
|
|
1898
|
+
return {
|
|
1899
|
+
types: __classPrivateFieldGet(this, _Mime_extensionToType, "f"),
|
|
1900
|
+
extensions: __classPrivateFieldGet(this, _Mime_typeToExtension, "f"),
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
2032
1903
|
}
|
|
1904
|
+
_Mime_extensionToType = new WeakMap(), _Mime_typeToExtension = new WeakMap(), _Mime_typeToExtensions = new WeakMap();
|
|
2033
1905
|
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
await context.browser.execute(focusIframe);
|
|
2040
|
-
}
|
|
2041
|
-
const pressed = new Set(state.unreleased);
|
|
2042
|
-
await keyboardImplementation(pressed, context.provider, context.sessionId, text, async () => {
|
|
2043
|
-
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
2044
|
-
const frame = await context.frame();
|
|
2045
|
-
await frame.evaluate(selectAll);
|
|
2046
|
-
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
2047
|
-
await context.browser.execute(selectAll);
|
|
2048
|
-
} else {
|
|
2049
|
-
throw new TypeError(`Provider "${context.provider.name}" does not support selecting all text`);
|
|
2050
|
-
}
|
|
2051
|
-
}, true);
|
|
2052
|
-
return { unreleased: Array.from(pressed) };
|
|
2053
|
-
};
|
|
2054
|
-
const keyboardCleanup = async (context, state) => {
|
|
2055
|
-
const { provider, sessionId } = context;
|
|
2056
|
-
if (!state.unreleased) {
|
|
2057
|
-
return;
|
|
2058
|
-
}
|
|
2059
|
-
if (provider instanceof PlaywrightBrowserProvider) {
|
|
2060
|
-
const page = provider.getPage(sessionId);
|
|
2061
|
-
for (const key of state.unreleased) {
|
|
2062
|
-
await page.keyboard.up(key);
|
|
2063
|
-
}
|
|
2064
|
-
} else if (provider instanceof WebdriverBrowserProvider) {
|
|
2065
|
-
const keyboard = provider.browser.action("key");
|
|
2066
|
-
for (const key of state.unreleased) {
|
|
2067
|
-
keyboard.up(key);
|
|
2068
|
-
}
|
|
2069
|
-
await keyboard.perform();
|
|
2070
|
-
} else {
|
|
2071
|
-
throw new TypeError(`Provider "${context.provider.name}" does not support keyboard api`);
|
|
2072
|
-
}
|
|
2073
|
-
};
|
|
2074
|
-
// fallback to insertText for non US key
|
|
2075
|
-
// https://github.com/microsoft/playwright/blob/50775698ae13642742f2a1e8983d1d686d7f192d/packages/playwright-core/src/server/input.ts#L95
|
|
2076
|
-
const VALID_KEYS = new Set([
|
|
2077
|
-
"Escape",
|
|
2078
|
-
"F1",
|
|
2079
|
-
"F2",
|
|
2080
|
-
"F3",
|
|
2081
|
-
"F4",
|
|
2082
|
-
"F5",
|
|
2083
|
-
"F6",
|
|
2084
|
-
"F7",
|
|
2085
|
-
"F8",
|
|
2086
|
-
"F9",
|
|
2087
|
-
"F10",
|
|
2088
|
-
"F11",
|
|
2089
|
-
"F12",
|
|
2090
|
-
"Backquote",
|
|
2091
|
-
"`",
|
|
2092
|
-
"~",
|
|
2093
|
-
"Digit1",
|
|
2094
|
-
"1",
|
|
2095
|
-
"!",
|
|
2096
|
-
"Digit2",
|
|
2097
|
-
"2",
|
|
2098
|
-
"@",
|
|
2099
|
-
"Digit3",
|
|
2100
|
-
"3",
|
|
2101
|
-
"#",
|
|
2102
|
-
"Digit4",
|
|
2103
|
-
"4",
|
|
2104
|
-
"$",
|
|
2105
|
-
"Digit5",
|
|
2106
|
-
"5",
|
|
2107
|
-
"%",
|
|
2108
|
-
"Digit6",
|
|
2109
|
-
"6",
|
|
2110
|
-
"^",
|
|
2111
|
-
"Digit7",
|
|
2112
|
-
"7",
|
|
2113
|
-
"&",
|
|
2114
|
-
"Digit8",
|
|
2115
|
-
"8",
|
|
2116
|
-
"*",
|
|
2117
|
-
"Digit9",
|
|
2118
|
-
"9",
|
|
2119
|
-
"(",
|
|
2120
|
-
"Digit0",
|
|
2121
|
-
"0",
|
|
2122
|
-
")",
|
|
2123
|
-
"Minus",
|
|
2124
|
-
"-",
|
|
2125
|
-
"_",
|
|
2126
|
-
"Equal",
|
|
2127
|
-
"=",
|
|
2128
|
-
"+",
|
|
2129
|
-
"Backslash",
|
|
2130
|
-
"\\",
|
|
2131
|
-
"|",
|
|
2132
|
-
"Backspace",
|
|
2133
|
-
"Tab",
|
|
2134
|
-
"KeyQ",
|
|
2135
|
-
"q",
|
|
2136
|
-
"Q",
|
|
2137
|
-
"KeyW",
|
|
2138
|
-
"w",
|
|
2139
|
-
"W",
|
|
2140
|
-
"KeyE",
|
|
2141
|
-
"e",
|
|
2142
|
-
"E",
|
|
2143
|
-
"KeyR",
|
|
2144
|
-
"r",
|
|
2145
|
-
"R",
|
|
2146
|
-
"KeyT",
|
|
2147
|
-
"t",
|
|
2148
|
-
"T",
|
|
2149
|
-
"KeyY",
|
|
2150
|
-
"y",
|
|
2151
|
-
"Y",
|
|
2152
|
-
"KeyU",
|
|
2153
|
-
"u",
|
|
2154
|
-
"U",
|
|
2155
|
-
"KeyI",
|
|
2156
|
-
"i",
|
|
2157
|
-
"I",
|
|
2158
|
-
"KeyO",
|
|
2159
|
-
"o",
|
|
2160
|
-
"O",
|
|
2161
|
-
"KeyP",
|
|
2162
|
-
"p",
|
|
2163
|
-
"P",
|
|
2164
|
-
"BracketLeft",
|
|
2165
|
-
"[",
|
|
2166
|
-
"{",
|
|
2167
|
-
"BracketRight",
|
|
2168
|
-
"]",
|
|
2169
|
-
"}",
|
|
2170
|
-
"CapsLock",
|
|
2171
|
-
"KeyA",
|
|
2172
|
-
"a",
|
|
2173
|
-
"A",
|
|
2174
|
-
"KeyS",
|
|
2175
|
-
"s",
|
|
2176
|
-
"S",
|
|
2177
|
-
"KeyD",
|
|
2178
|
-
"d",
|
|
2179
|
-
"D",
|
|
2180
|
-
"KeyF",
|
|
2181
|
-
"f",
|
|
2182
|
-
"F",
|
|
2183
|
-
"KeyG",
|
|
2184
|
-
"g",
|
|
2185
|
-
"G",
|
|
2186
|
-
"KeyH",
|
|
2187
|
-
"h",
|
|
2188
|
-
"H",
|
|
2189
|
-
"KeyJ",
|
|
2190
|
-
"j",
|
|
2191
|
-
"J",
|
|
2192
|
-
"KeyK",
|
|
2193
|
-
"k",
|
|
2194
|
-
"K",
|
|
2195
|
-
"KeyL",
|
|
2196
|
-
"l",
|
|
2197
|
-
"L",
|
|
2198
|
-
"Semicolon",
|
|
2199
|
-
";",
|
|
2200
|
-
":",
|
|
2201
|
-
"Quote",
|
|
2202
|
-
"'",
|
|
2203
|
-
"\"",
|
|
2204
|
-
"Enter",
|
|
2205
|
-
"\n",
|
|
2206
|
-
"\r",
|
|
2207
|
-
"ShiftLeft",
|
|
2208
|
-
"Shift",
|
|
2209
|
-
"KeyZ",
|
|
2210
|
-
"z",
|
|
2211
|
-
"Z",
|
|
2212
|
-
"KeyX",
|
|
2213
|
-
"x",
|
|
2214
|
-
"X",
|
|
2215
|
-
"KeyC",
|
|
2216
|
-
"c",
|
|
2217
|
-
"C",
|
|
2218
|
-
"KeyV",
|
|
2219
|
-
"v",
|
|
2220
|
-
"V",
|
|
2221
|
-
"KeyB",
|
|
2222
|
-
"b",
|
|
2223
|
-
"B",
|
|
2224
|
-
"KeyN",
|
|
2225
|
-
"n",
|
|
2226
|
-
"N",
|
|
2227
|
-
"KeyM",
|
|
2228
|
-
"m",
|
|
2229
|
-
"M",
|
|
2230
|
-
"Comma",
|
|
2231
|
-
",",
|
|
2232
|
-
"<",
|
|
2233
|
-
"Period",
|
|
2234
|
-
".",
|
|
2235
|
-
">",
|
|
2236
|
-
"Slash",
|
|
2237
|
-
"/",
|
|
2238
|
-
"?",
|
|
2239
|
-
"ShiftRight",
|
|
2240
|
-
"ControlLeft",
|
|
2241
|
-
"Control",
|
|
2242
|
-
"MetaLeft",
|
|
2243
|
-
"Meta",
|
|
2244
|
-
"AltLeft",
|
|
2245
|
-
"Alt",
|
|
2246
|
-
"Space",
|
|
2247
|
-
" ",
|
|
2248
|
-
"AltRight",
|
|
2249
|
-
"AltGraph",
|
|
2250
|
-
"MetaRight",
|
|
2251
|
-
"ContextMenu",
|
|
2252
|
-
"ControlRight",
|
|
2253
|
-
"PrintScreen",
|
|
2254
|
-
"ScrollLock",
|
|
2255
|
-
"Pause",
|
|
2256
|
-
"PageUp",
|
|
2257
|
-
"PageDown",
|
|
2258
|
-
"Insert",
|
|
2259
|
-
"Delete",
|
|
2260
|
-
"Home",
|
|
2261
|
-
"End",
|
|
2262
|
-
"ArrowLeft",
|
|
2263
|
-
"ArrowUp",
|
|
2264
|
-
"ArrowRight",
|
|
2265
|
-
"ArrowDown",
|
|
2266
|
-
"NumLock",
|
|
2267
|
-
"NumpadDivide",
|
|
2268
|
-
"NumpadMultiply",
|
|
2269
|
-
"NumpadSubtract",
|
|
2270
|
-
"Numpad7",
|
|
2271
|
-
"Numpad8",
|
|
2272
|
-
"Numpad9",
|
|
2273
|
-
"Numpad4",
|
|
2274
|
-
"Numpad5",
|
|
2275
|
-
"Numpad6",
|
|
2276
|
-
"NumpadAdd",
|
|
2277
|
-
"Numpad1",
|
|
2278
|
-
"Numpad2",
|
|
2279
|
-
"Numpad3",
|
|
2280
|
-
"Numpad0",
|
|
2281
|
-
"NumpadDecimal",
|
|
2282
|
-
"NumpadEnter",
|
|
2283
|
-
"ControlOrMeta"
|
|
2284
|
-
]);
|
|
2285
|
-
async function keyboardImplementation(pressed, provider, sessionId, text, selectAll, skipRelease) {
|
|
2286
|
-
if (provider instanceof PlaywrightBrowserProvider) {
|
|
2287
|
-
const page = provider.getPage(sessionId);
|
|
2288
|
-
const actions = parseKeyDef(defaultKeyMap, text);
|
|
2289
|
-
for (const { releasePrevious, releaseSelf, repeat, keyDef } of actions) {
|
|
2290
|
-
const key = keyDef.key;
|
|
2291
|
-
// TODO: instead of calling down/up for each key, join non special
|
|
2292
|
-
// together, and call `type` once for all non special keys,
|
|
2293
|
-
// and then `press` for special keys
|
|
2294
|
-
if (pressed.has(key)) {
|
|
2295
|
-
if (VALID_KEYS.has(key)) {
|
|
2296
|
-
await page.keyboard.up(key);
|
|
2297
|
-
}
|
|
2298
|
-
pressed.delete(key);
|
|
2299
|
-
}
|
|
2300
|
-
if (!releasePrevious) {
|
|
2301
|
-
if (key === "selectall") {
|
|
2302
|
-
await selectAll();
|
|
2303
|
-
continue;
|
|
2304
|
-
}
|
|
2305
|
-
for (let i = 1; i <= repeat; i++) {
|
|
2306
|
-
if (VALID_KEYS.has(key)) {
|
|
2307
|
-
await page.keyboard.down(key);
|
|
2308
|
-
} else {
|
|
2309
|
-
await page.keyboard.insertText(key);
|
|
2310
|
-
}
|
|
2311
|
-
}
|
|
2312
|
-
if (releaseSelf) {
|
|
2313
|
-
if (VALID_KEYS.has(key)) {
|
|
2314
|
-
await page.keyboard.up(key);
|
|
2315
|
-
}
|
|
2316
|
-
} else {
|
|
2317
|
-
pressed.add(key);
|
|
2318
|
-
}
|
|
2319
|
-
}
|
|
2320
|
-
}
|
|
2321
|
-
if (!skipRelease && pressed.size) {
|
|
2322
|
-
for (const key of pressed) {
|
|
2323
|
-
if (VALID_KEYS.has(key)) {
|
|
2324
|
-
await page.keyboard.up(key);
|
|
2325
|
-
}
|
|
2326
|
-
}
|
|
2327
|
-
}
|
|
2328
|
-
} else if (provider instanceof WebdriverBrowserProvider) {
|
|
2329
|
-
const { Key } = await import('webdriverio');
|
|
2330
|
-
const browser = provider.browser;
|
|
2331
|
-
const actions = parseKeyDef(defaultKeyMap, text);
|
|
2332
|
-
let keyboard = browser.action("key");
|
|
2333
|
-
for (const { releasePrevious, releaseSelf, repeat, keyDef } of actions) {
|
|
2334
|
-
let key = keyDef.key;
|
|
2335
|
-
const special = Key[key];
|
|
2336
|
-
if (special) {
|
|
2337
|
-
key = special;
|
|
2338
|
-
}
|
|
2339
|
-
if (pressed.has(key)) {
|
|
2340
|
-
keyboard.up(key);
|
|
2341
|
-
pressed.delete(key);
|
|
2342
|
-
}
|
|
2343
|
-
if (!releasePrevious) {
|
|
2344
|
-
if (key === "selectall") {
|
|
2345
|
-
await keyboard.perform();
|
|
2346
|
-
keyboard = browser.action("key");
|
|
2347
|
-
await selectAll();
|
|
2348
|
-
continue;
|
|
2349
|
-
}
|
|
2350
|
-
for (let i = 1; i <= repeat; i++) {
|
|
2351
|
-
keyboard.down(key);
|
|
2352
|
-
}
|
|
2353
|
-
if (releaseSelf) {
|
|
2354
|
-
keyboard.up(key);
|
|
2355
|
-
} else {
|
|
2356
|
-
pressed.add(key);
|
|
2357
|
-
}
|
|
2358
|
-
}
|
|
2359
|
-
}
|
|
2360
|
-
// seems like webdriverio doesn't release keys automatically if skipRelease is true and all events are keyUp
|
|
2361
|
-
const allRelease = keyboard.toJSON().actions.every((action) => action.type === "keyUp");
|
|
2362
|
-
await keyboard.perform(allRelease ? false : skipRelease);
|
|
1906
|
+
var mime = new Mime(types)._freeze();
|
|
1907
|
+
|
|
1908
|
+
function assertFileAccess(path, project) {
|
|
1909
|
+
if (!isFileServingAllowed(path, project.vite) && !isFileServingAllowed(path, project.vitest.vite)) {
|
|
1910
|
+
throw new Error(`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`);
|
|
2363
1911
|
}
|
|
2364
|
-
return { pressed };
|
|
2365
1912
|
}
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
1913
|
+
const readFile = async ({ project }, path, options = {}) => {
|
|
1914
|
+
const filepath = resolve$1(project.config.root, path);
|
|
1915
|
+
assertFileAccess(filepath, project);
|
|
1916
|
+
// never return a Buffer
|
|
1917
|
+
if (typeof options === "object" && !options.encoding) {
|
|
1918
|
+
options.encoding = "utf-8";
|
|
2369
1919
|
}
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
1920
|
+
return promises.readFile(filepath, options);
|
|
1921
|
+
};
|
|
1922
|
+
const writeFile = async ({ project }, path, data, options) => {
|
|
1923
|
+
const filepath = resolve$1(project.config.root, path);
|
|
1924
|
+
assertFileAccess(filepath, project);
|
|
1925
|
+
const dir = dirname$1(filepath);
|
|
1926
|
+
if (!fs.existsSync(dir)) {
|
|
1927
|
+
await promises.mkdir(dir, { recursive: true });
|
|
2375
1928
|
}
|
|
2376
|
-
|
|
1929
|
+
await promises.writeFile(filepath, data, options);
|
|
1930
|
+
};
|
|
1931
|
+
const removeFile = async ({ project }, path) => {
|
|
1932
|
+
const filepath = resolve$1(project.config.root, path);
|
|
1933
|
+
assertFileAccess(filepath, project);
|
|
1934
|
+
await promises.rm(filepath);
|
|
1935
|
+
};
|
|
1936
|
+
const _fileInfo = async ({ project }, path, encoding) => {
|
|
1937
|
+
const filepath = resolve$1(project.config.root, path);
|
|
1938
|
+
assertFileAccess(filepath, project);
|
|
1939
|
+
const content = await promises.readFile(filepath, encoding || "base64");
|
|
1940
|
+
return {
|
|
1941
|
+
content,
|
|
1942
|
+
basename: basename$1(filepath),
|
|
1943
|
+
mime: mime.getType(filepath)
|
|
1944
|
+
};
|
|
1945
|
+
};
|
|
2377
1946
|
|
|
2378
1947
|
const screenshot = async (context, name, options = {}) => {
|
|
2379
1948
|
options.save ??= true;
|
|
2380
1949
|
if (!options.save) {
|
|
2381
1950
|
options.base64 = true;
|
|
2382
1951
|
}
|
|
2383
|
-
const { buffer, path } = await
|
|
1952
|
+
const { buffer, path } = await context.triggerCommand("__vitest_takeScreenshot", name, options);
|
|
2384
1953
|
return returnResult(options, path, buffer);
|
|
2385
1954
|
};
|
|
2386
|
-
/**
|
|
2387
|
-
* Takes a screenshot using the provided browser context and returns a buffer and the expected screenshot path.
|
|
2388
|
-
*
|
|
2389
|
-
* **Note**: the returned `path` indicates where the screenshot *might* be found.
|
|
2390
|
-
* It is not guaranteed to exist, especially if `options.save` is `false`.
|
|
2391
|
-
*
|
|
2392
|
-
* @throws {Error} If the function is not called within a test or if the browser provider does not support screenshots.
|
|
2393
|
-
*/
|
|
2394
|
-
async function takeScreenshot(context, name, options) {
|
|
2395
|
-
if (!context.testPath) {
|
|
2396
|
-
throw new Error(`Cannot take a screenshot without a test path`);
|
|
2397
|
-
}
|
|
2398
|
-
const path = resolveScreenshotPath(context.testPath, name, context.project.config, options.path);
|
|
2399
|
-
const savePath = normalize$1(path);
|
|
2400
|
-
await mkdir(dirname(path), { recursive: true });
|
|
2401
|
-
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
2402
|
-
const mask = options.mask?.map((selector) => context.iframe.locator(selector));
|
|
2403
|
-
if (options.element) {
|
|
2404
|
-
const { element: selector,...config } = options;
|
|
2405
|
-
const element = context.iframe.locator(selector);
|
|
2406
|
-
const buffer = await element.screenshot({
|
|
2407
|
-
...config,
|
|
2408
|
-
mask,
|
|
2409
|
-
path: options.save ? savePath : undefined
|
|
2410
|
-
});
|
|
2411
|
-
return {
|
|
2412
|
-
buffer,
|
|
2413
|
-
path
|
|
2414
|
-
};
|
|
2415
|
-
}
|
|
2416
|
-
const buffer = await context.iframe.locator("body").screenshot({
|
|
2417
|
-
...options,
|
|
2418
|
-
mask,
|
|
2419
|
-
path: options.save ? savePath : undefined
|
|
2420
|
-
});
|
|
2421
|
-
return {
|
|
2422
|
-
buffer,
|
|
2423
|
-
path
|
|
2424
|
-
};
|
|
2425
|
-
}
|
|
2426
|
-
if (context.provider instanceof WebdriverBrowserProvider) {
|
|
2427
|
-
const page = context.provider.browser;
|
|
2428
|
-
const element = !options.element ? await page.$("body") : await page.$(`${options.element}`);
|
|
2429
|
-
// webdriverio expects the path to contain the extension and only works with PNG files
|
|
2430
|
-
const savePathWithExtension = savePath.endsWith(".png") ? savePath : `${savePath}.png`;
|
|
2431
|
-
const buffer = await element.saveScreenshot(savePathWithExtension);
|
|
2432
|
-
if (!options.save) {
|
|
2433
|
-
await rm(savePathWithExtension, { force: true });
|
|
2434
|
-
}
|
|
2435
|
-
return {
|
|
2436
|
-
buffer,
|
|
2437
|
-
path
|
|
2438
|
-
};
|
|
2439
|
-
}
|
|
2440
|
-
throw new Error(`Provider "${context.provider.name}" does not support screenshots`);
|
|
2441
|
-
}
|
|
2442
|
-
function resolveScreenshotPath(testPath, name, config, customPath) {
|
|
2443
|
-
if (customPath) {
|
|
2444
|
-
return resolve(dirname(testPath), customPath);
|
|
2445
|
-
}
|
|
2446
|
-
const dir = dirname(testPath);
|
|
2447
|
-
const base = basename(testPath);
|
|
2448
|
-
if (config.browser.screenshotDirectory) {
|
|
2449
|
-
return resolve(config.browser.screenshotDirectory, relative(config.root, dir), base, name);
|
|
2450
|
-
}
|
|
2451
|
-
return resolve(dir, "__screenshots__", base, name);
|
|
2452
|
-
}
|
|
2453
1955
|
function returnResult(options, path, buffer) {
|
|
2454
1956
|
if (!options.save) {
|
|
2455
1957
|
return buffer.toString("base64");
|
|
@@ -2544,10 +2046,14 @@ const pixelmatch = (reference, actual, { createDiff,...options }) => {
|
|
|
2544
2046
|
};
|
|
2545
2047
|
};
|
|
2546
2048
|
|
|
2547
|
-
const comparators =
|
|
2548
|
-
function getComparator(comparator) {
|
|
2549
|
-
if (comparators
|
|
2550
|
-
return comparators
|
|
2049
|
+
const comparators = { pixelmatch };
|
|
2050
|
+
function getComparator(comparator, context) {
|
|
2051
|
+
if (comparator in comparators) {
|
|
2052
|
+
return comparators[comparator];
|
|
2053
|
+
}
|
|
2054
|
+
const customComparators = context.project.config.browser.expect?.toMatchScreenshot?.comparators;
|
|
2055
|
+
if (customComparators && comparator in customComparators) {
|
|
2056
|
+
return customComparators[comparator];
|
|
2551
2057
|
}
|
|
2552
2058
|
throw new Error(`Unrecognized comparator ${comparator}`);
|
|
2553
2059
|
}
|
|
@@ -2602,7 +2108,7 @@ function resolveOptions({ context, name, options, testName }) {
|
|
|
2602
2108
|
};
|
|
2603
2109
|
return {
|
|
2604
2110
|
codec: getCodec(extension),
|
|
2605
|
-
comparator: getComparator(resolvedOptions.comparatorName),
|
|
2111
|
+
comparator: getComparator(resolvedOptions.comparatorName, context),
|
|
2606
2112
|
resolvedOptions,
|
|
2607
2113
|
paths: {
|
|
2608
2114
|
reference: resolvedOptions.resolveScreenshotPath(resolvePathData),
|
|
@@ -2677,7 +2183,7 @@ function sanitizeArg(input) {
|
|
|
2677
2183
|
* @returns `Promise` resolving to the decoded screenshot data
|
|
2678
2184
|
*/
|
|
2679
2185
|
function takeDecodedScreenshot({ codec, context, element, name, screenshotOptions }) {
|
|
2680
|
-
return
|
|
2186
|
+
return context.triggerCommand("__vitest_takeScreenshot", name, {
|
|
2681
2187
|
...screenshotOptions,
|
|
2682
2188
|
save: false,
|
|
2683
2189
|
element
|
|
@@ -2858,147 +2364,12 @@ async function getStableScreenshots({ codec, context, comparator, comparatorOpti
|
|
|
2858
2364
|
};
|
|
2859
2365
|
}
|
|
2860
2366
|
|
|
2861
|
-
const selectOptions = async (context, selector, userValues, options = {}) => {
|
|
2862
|
-
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
2863
|
-
const value = userValues;
|
|
2864
|
-
const { iframe } = context;
|
|
2865
|
-
const selectElement = iframe.locator(selector);
|
|
2866
|
-
const values = await Promise.all(value.map(async (v) => {
|
|
2867
|
-
if (typeof v === "string") {
|
|
2868
|
-
return v;
|
|
2869
|
-
}
|
|
2870
|
-
const elementHandler = await iframe.locator(v.element).elementHandle();
|
|
2871
|
-
if (!elementHandler) {
|
|
2872
|
-
throw new Error(`Element not found: ${v.element}`);
|
|
2873
|
-
}
|
|
2874
|
-
return elementHandler;
|
|
2875
|
-
}));
|
|
2876
|
-
await selectElement.selectOption(values, options);
|
|
2877
|
-
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
2878
|
-
const values = userValues;
|
|
2879
|
-
if (!values.length) {
|
|
2880
|
-
return;
|
|
2881
|
-
}
|
|
2882
|
-
const browser = context.browser;
|
|
2883
|
-
if (values.length === 1 && "index" in values[0]) {
|
|
2884
|
-
const selectElement = browser.$(selector);
|
|
2885
|
-
await selectElement.selectByIndex(values[0].index);
|
|
2886
|
-
} else {
|
|
2887
|
-
throw new Error("Provider \"webdriverio\" doesn't support selecting multiple values at once");
|
|
2888
|
-
}
|
|
2889
|
-
} else {
|
|
2890
|
-
throw new TypeError(`Provider "${context.provider.name}" doesn't support selectOptions command`);
|
|
2891
|
-
}
|
|
2892
|
-
};
|
|
2893
|
-
|
|
2894
|
-
const tab = async (context, options = {}) => {
|
|
2895
|
-
const provider = context.provider;
|
|
2896
|
-
if (provider instanceof PlaywrightBrowserProvider) {
|
|
2897
|
-
const page = context.page;
|
|
2898
|
-
await page.keyboard.press(options.shift === true ? "Shift+Tab" : "Tab");
|
|
2899
|
-
return;
|
|
2900
|
-
}
|
|
2901
|
-
if (provider instanceof WebdriverBrowserProvider) {
|
|
2902
|
-
const { Key } = await import('webdriverio');
|
|
2903
|
-
const browser = context.browser;
|
|
2904
|
-
await browser.keys(options.shift === true ? [Key.Shift, Key.Tab] : [Key.Tab]);
|
|
2905
|
-
return;
|
|
2906
|
-
}
|
|
2907
|
-
throw new Error(`Provider "${provider.name}" doesn't support tab command`);
|
|
2908
|
-
};
|
|
2909
|
-
|
|
2910
|
-
const type = async (context, selector, text, options = {}) => {
|
|
2911
|
-
const { skipClick = false, skipAutoClose = false } = options;
|
|
2912
|
-
const unreleased = new Set(Reflect.get(options, "unreleased") ?? []);
|
|
2913
|
-
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
2914
|
-
const { iframe } = context;
|
|
2915
|
-
const element = iframe.locator(selector);
|
|
2916
|
-
if (!skipClick) {
|
|
2917
|
-
await element.focus();
|
|
2918
|
-
}
|
|
2919
|
-
await keyboardImplementation(unreleased, context.provider, context.sessionId, text, () => element.selectText(), skipAutoClose);
|
|
2920
|
-
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
2921
|
-
const browser = context.browser;
|
|
2922
|
-
const element = browser.$(selector);
|
|
2923
|
-
if (!skipClick && !await element.isFocused()) {
|
|
2924
|
-
await element.click();
|
|
2925
|
-
}
|
|
2926
|
-
await keyboardImplementation(unreleased, context.provider, context.sessionId, text, () => browser.execute(() => {
|
|
2927
|
-
const element = document.activeElement;
|
|
2928
|
-
if (element && typeof element.select === "function") {
|
|
2929
|
-
element.select();
|
|
2930
|
-
}
|
|
2931
|
-
}), skipAutoClose);
|
|
2932
|
-
} else {
|
|
2933
|
-
throw new TypeError(`Provider "${context.provider.name}" does not support typing`);
|
|
2934
|
-
}
|
|
2935
|
-
return { unreleased: Array.from(unreleased) };
|
|
2936
|
-
};
|
|
2937
|
-
|
|
2938
|
-
const upload = async (context, selector, files, options) => {
|
|
2939
|
-
const testPath = context.testPath;
|
|
2940
|
-
if (!testPath) {
|
|
2941
|
-
throw new Error(`Cannot upload files outside of a test`);
|
|
2942
|
-
}
|
|
2943
|
-
const root = context.project.config.root;
|
|
2944
|
-
if (context.provider instanceof PlaywrightBrowserProvider) {
|
|
2945
|
-
const { iframe } = context;
|
|
2946
|
-
const playwrightFiles = files.map((file) => {
|
|
2947
|
-
if (typeof file === "string") {
|
|
2948
|
-
return resolve(root, file);
|
|
2949
|
-
}
|
|
2950
|
-
return {
|
|
2951
|
-
name: file.name,
|
|
2952
|
-
mimeType: file.mimeType,
|
|
2953
|
-
buffer: Buffer.from(file.base64, "base64")
|
|
2954
|
-
};
|
|
2955
|
-
});
|
|
2956
|
-
await iframe.locator(selector).setInputFiles(playwrightFiles, options);
|
|
2957
|
-
} else if (context.provider instanceof WebdriverBrowserProvider) {
|
|
2958
|
-
for (const file of files) {
|
|
2959
|
-
if (typeof file !== "string") {
|
|
2960
|
-
throw new TypeError(`The "${context.provider.name}" provider doesn't support uploading files objects. Provide a file path instead.`);
|
|
2961
|
-
}
|
|
2962
|
-
}
|
|
2963
|
-
const element = context.browser.$(selector);
|
|
2964
|
-
for (const file of files) {
|
|
2965
|
-
const filepath = resolve(root, file);
|
|
2966
|
-
const remoteFilePath = await context.browser.uploadFile(filepath);
|
|
2967
|
-
await element.addValue(remoteFilePath);
|
|
2968
|
-
}
|
|
2969
|
-
} else {
|
|
2970
|
-
throw new TypeError(`Provider "${context.provider.name}" does not support uploading files via userEvent.upload`);
|
|
2971
|
-
}
|
|
2972
|
-
};
|
|
2973
|
-
|
|
2974
|
-
const viewport = async (context, options) => {
|
|
2975
|
-
if (context.provider instanceof WebdriverBrowserProvider) {
|
|
2976
|
-
await context.provider.setViewport(options);
|
|
2977
|
-
} else {
|
|
2978
|
-
throw new TypeError(`Provider ${context.provider.name} doesn't support "viewport" command`);
|
|
2979
|
-
}
|
|
2980
|
-
};
|
|
2981
|
-
|
|
2982
2367
|
var builtinCommands = {
|
|
2983
2368
|
readFile,
|
|
2984
2369
|
removeFile,
|
|
2985
2370
|
writeFile,
|
|
2986
2371
|
__vitest_fileInfo: _fileInfo,
|
|
2987
|
-
__vitest_upload: upload,
|
|
2988
|
-
__vitest_click: click,
|
|
2989
|
-
__vitest_dblClick: dblClick,
|
|
2990
|
-
__vitest_tripleClick: tripleClick,
|
|
2991
2372
|
__vitest_screenshot: screenshot,
|
|
2992
|
-
__vitest_type: type,
|
|
2993
|
-
__vitest_clear: clear,
|
|
2994
|
-
__vitest_fill: fill,
|
|
2995
|
-
__vitest_tab: tab,
|
|
2996
|
-
__vitest_keyboard: keyboard,
|
|
2997
|
-
__vitest_selectOptions: selectOptions,
|
|
2998
|
-
__vitest_dragAndDrop: dragAndDrop,
|
|
2999
|
-
__vitest_hover: hover,
|
|
3000
|
-
__vitest_cleanup: keyboardCleanup,
|
|
3001
|
-
__vitest_viewport: viewport,
|
|
3002
2373
|
__vitest_screenshotMatcher: screenshotMatcher
|
|
3003
2374
|
};
|
|
3004
2375
|
|
|
@@ -3033,6 +2404,22 @@ class ProjectBrowser {
|
|
|
3033
2404
|
get vite() {
|
|
3034
2405
|
return this.parent.vite;
|
|
3035
2406
|
}
|
|
2407
|
+
commands = {};
|
|
2408
|
+
registerCommand(name, cb) {
|
|
2409
|
+
if (!/^[a-z_$][\w$]*$/i.test(name)) {
|
|
2410
|
+
throw new Error(`Invalid command name "${name}". Only alphanumeric characters, $ and _ are allowed.`);
|
|
2411
|
+
}
|
|
2412
|
+
this.commands[name] = cb;
|
|
2413
|
+
}
|
|
2414
|
+
triggerCommand = ((name, context, ...args) => {
|
|
2415
|
+
if (name in this.commands) {
|
|
2416
|
+
return this.commands[name](context, ...args);
|
|
2417
|
+
}
|
|
2418
|
+
if (name in this.parent.commands) {
|
|
2419
|
+
return this.parent.commands[name](context, ...args);
|
|
2420
|
+
}
|
|
2421
|
+
throw new Error(`Provider ${this.provider.name} does not support command "${name}".`);
|
|
2422
|
+
});
|
|
3036
2423
|
wrapSerializedConfig() {
|
|
3037
2424
|
const config = wrapConfig(this.project.serializedConfig);
|
|
3038
2425
|
config.env ??= {};
|
|
@@ -3043,22 +2430,17 @@ class ProjectBrowser {
|
|
|
3043
2430
|
if (this.provider) {
|
|
3044
2431
|
return;
|
|
3045
2432
|
}
|
|
3046
|
-
|
|
3047
|
-
this.provider
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
2433
|
+
this.provider = await getBrowserProvider(project.config.browser, project);
|
|
2434
|
+
if (this.provider.initScripts) {
|
|
2435
|
+
this.parent.initScripts = this.provider.initScripts;
|
|
2436
|
+
// make sure the script can be imported
|
|
2437
|
+
this.provider.initScripts.forEach((script) => {
|
|
2438
|
+
const allow = this.parent.vite.config.server.fs.allow;
|
|
2439
|
+
if (!allow.includes(script)) {
|
|
2440
|
+
allow.push(script);
|
|
2441
|
+
}
|
|
2442
|
+
});
|
|
3056
2443
|
}
|
|
3057
|
-
const providerOptions = project.config.browser.providerOptions;
|
|
3058
|
-
await this.provider.initialize(project, {
|
|
3059
|
-
browser,
|
|
3060
|
-
options: providerOptions
|
|
3061
|
-
});
|
|
3062
2444
|
}
|
|
3063
2445
|
parseErrorStacktrace(e, options = {}) {
|
|
3064
2446
|
return this.parent.parseErrorStacktrace(e, options);
|
|
@@ -3091,6 +2473,7 @@ class ParentBrowserProject {
|
|
|
3091
2473
|
locatorsUrl;
|
|
3092
2474
|
matchersUrl;
|
|
3093
2475
|
stateJs;
|
|
2476
|
+
initScripts = [];
|
|
3094
2477
|
commands = {};
|
|
3095
2478
|
children = new Set();
|
|
3096
2479
|
vitest;
|
|
@@ -3124,18 +2507,19 @@ class ParentBrowserProject {
|
|
|
3124
2507
|
return result?.map;
|
|
3125
2508
|
},
|
|
3126
2509
|
getUrlId: (id) => {
|
|
3127
|
-
const
|
|
2510
|
+
const moduleGraph = this.vite.environments.client.moduleGraph;
|
|
2511
|
+
const mod = moduleGraph.getModuleById(id);
|
|
3128
2512
|
if (mod) {
|
|
3129
2513
|
return id;
|
|
3130
2514
|
}
|
|
3131
2515
|
const resolvedPath = resolve(this.vite.config.root, id.slice(1));
|
|
3132
|
-
const modUrl =
|
|
2516
|
+
const modUrl = moduleGraph.getModuleById(resolvedPath);
|
|
3133
2517
|
if (modUrl) {
|
|
3134
2518
|
return resolvedPath;
|
|
3135
2519
|
}
|
|
3136
2520
|
// some browsers (looking at you, safari) don't report queries in stack traces
|
|
3137
2521
|
// the next best thing is to try the first id that this file resolves to
|
|
3138
|
-
const files =
|
|
2522
|
+
const files = moduleGraph.getModulesByFile(resolvedPath);
|
|
3139
2523
|
if (files && files.size) {
|
|
3140
2524
|
return files.values().next().value.id;
|
|
3141
2525
|
}
|
|
@@ -3161,15 +2545,6 @@ class ParentBrowserProject {
|
|
|
3161
2545
|
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);
|
|
3162
2546
|
this.injectorJs = readFile$1(resolve(distRoot, "client/esm-client-injector.js"), "utf8").then((js) => this.injectorJs = js);
|
|
3163
2547
|
this.errorCatcherUrl = join("/@fs/", resolve(distRoot, "client/error-catcher.js"));
|
|
3164
|
-
const builtinProviders = [
|
|
3165
|
-
"playwright",
|
|
3166
|
-
"webdriverio",
|
|
3167
|
-
"preview"
|
|
3168
|
-
];
|
|
3169
|
-
const providerName = project.config.browser.provider || "preview";
|
|
3170
|
-
if (builtinProviders.includes(providerName)) {
|
|
3171
|
-
this.locatorsUrl = join("/@fs/", distRoot, "locators", `${providerName}.js`);
|
|
3172
|
-
}
|
|
3173
2548
|
this.matchersUrl = join("/@fs/", distRoot, "expect-element.js");
|
|
3174
2549
|
this.stateJs = readFile$1(resolve(distRoot, "state.js"), "utf-8").then((js) => this.stateJs = js);
|
|
3175
2550
|
}
|
|
@@ -3456,7 +2831,7 @@ function nanoid(size = 21) {
|
|
|
3456
2831
|
return id;
|
|
3457
2832
|
}
|
|
3458
2833
|
|
|
3459
|
-
const debug
|
|
2834
|
+
const debug = createDebugger("vitest:browser:api");
|
|
3460
2835
|
const BROWSER_API_PATH = "/__vitest_browser_api__";
|
|
3461
2836
|
function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
3462
2837
|
const vite = globalServer.vite;
|
|
@@ -3504,9 +2879,9 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
|
3504
2879
|
const state = project.browser.state;
|
|
3505
2880
|
const clients = type === "tester" ? state.testers : state.orchestrators;
|
|
3506
2881
|
clients.set(rpcId, rpc);
|
|
3507
|
-
debug
|
|
2882
|
+
debug?.("[%s] Browser API connected to %s", rpcId, type);
|
|
3508
2883
|
ws.on("close", () => {
|
|
3509
|
-
debug
|
|
2884
|
+
debug?.("[%s] Browser API disconnected from %s", rpcId, type);
|
|
3510
2885
|
clients.delete(rpcId);
|
|
3511
2886
|
globalServer.removeCDPHandler(rpcId);
|
|
3512
2887
|
if (type === "orchestrator") {
|
|
@@ -3631,23 +3006,22 @@ function setupBrowserRpc(globalServer, defaultMockerRegistry) {
|
|
|
3631
3006
|
}
|
|
3632
3007
|
},
|
|
3633
3008
|
async triggerCommand(sessionId, command, testPath, payload) {
|
|
3634
|
-
debug
|
|
3009
|
+
debug?.("[%s] Triggering command \"%s\"", sessionId, command);
|
|
3635
3010
|
const provider = project.browser.provider;
|
|
3636
3011
|
if (!provider) {
|
|
3637
3012
|
throw new Error("Commands are only available for browser tests.");
|
|
3638
3013
|
}
|
|
3639
|
-
const commands = globalServer.commands;
|
|
3640
|
-
if (!commands || !commands[command]) {
|
|
3641
|
-
throw new Error(`Unknown command "${command}".`);
|
|
3642
|
-
}
|
|
3643
3014
|
const context = Object.assign({
|
|
3644
3015
|
testPath,
|
|
3645
3016
|
project,
|
|
3646
3017
|
provider,
|
|
3647
3018
|
contextId: sessionId,
|
|
3648
|
-
sessionId
|
|
3019
|
+
sessionId,
|
|
3020
|
+
triggerCommand: (name, ...args) => {
|
|
3021
|
+
return project.browser.triggerCommand(name, context, ...args);
|
|
3022
|
+
}
|
|
3649
3023
|
}, provider.getCommandsContext(sessionId));
|
|
3650
|
-
return await
|
|
3024
|
+
return await project.browser.triggerCommand(command, context, ...payload);
|
|
3651
3025
|
},
|
|
3652
3026
|
resolveMock(rawId, importer, options) {
|
|
3653
3027
|
return mockResolver.resolveMock(rawId, importer, options);
|
|
@@ -3753,265 +3127,12 @@ function stringifyReplace(key, value) {
|
|
|
3753
3127
|
}
|
|
3754
3128
|
}
|
|
3755
3129
|
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
const providers = new Set();
|
|
3759
|
-
const numCpus = typeof nodeos.availableParallelism === "function" ? nodeos.availableParallelism() : nodeos.cpus().length;
|
|
3760
|
-
const threadsCount = vitest.config.watch ? Math.max(Math.floor(numCpus / 2), 1) : Math.max(numCpus - 1, 1);
|
|
3761
|
-
const projectPools = new WeakMap();
|
|
3762
|
-
const ensurePool = (project) => {
|
|
3763
|
-
if (projectPools.has(project)) {
|
|
3764
|
-
return projectPools.get(project);
|
|
3765
|
-
}
|
|
3766
|
-
debug?.("creating pool for project %s", project.name);
|
|
3767
|
-
const resolvedUrls = project.browser.vite.resolvedUrls;
|
|
3768
|
-
const origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0];
|
|
3769
|
-
if (!origin) {
|
|
3770
|
-
throw new Error(`Can't find browser origin URL for project "${project.name}"`);
|
|
3771
|
-
}
|
|
3772
|
-
const pool = new BrowserPool(project, {
|
|
3773
|
-
maxWorkers: getThreadsCount(project),
|
|
3774
|
-
origin
|
|
3775
|
-
});
|
|
3776
|
-
projectPools.set(project, pool);
|
|
3777
|
-
vitest.onCancel(() => {
|
|
3778
|
-
pool.cancel();
|
|
3779
|
-
});
|
|
3780
|
-
return pool;
|
|
3781
|
-
};
|
|
3782
|
-
const runWorkspaceTests = async (method, specs) => {
|
|
3783
|
-
const groupedFiles = new Map();
|
|
3784
|
-
for (const { project, moduleId } of specs) {
|
|
3785
|
-
const files = groupedFiles.get(project) || [];
|
|
3786
|
-
files.push(moduleId);
|
|
3787
|
-
groupedFiles.set(project, files);
|
|
3788
|
-
}
|
|
3789
|
-
let isCancelled = false;
|
|
3790
|
-
vitest.onCancel(() => {
|
|
3791
|
-
isCancelled = true;
|
|
3792
|
-
});
|
|
3793
|
-
const initialisedPools = await Promise.all([...groupedFiles.entries()].map(async ([project, files]) => {
|
|
3794
|
-
await project._initBrowserProvider();
|
|
3795
|
-
if (!project.browser) {
|
|
3796
|
-
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.`);
|
|
3797
|
-
}
|
|
3798
|
-
if (isCancelled) {
|
|
3799
|
-
return;
|
|
3800
|
-
}
|
|
3801
|
-
debug?.("provider is ready for %s project", project.name);
|
|
3802
|
-
const pool = ensurePool(project);
|
|
3803
|
-
vitest.state.clearFiles(project, files);
|
|
3804
|
-
providers.add(project.browser.provider);
|
|
3805
|
-
return {
|
|
3806
|
-
pool,
|
|
3807
|
-
provider: project.browser.provider,
|
|
3808
|
-
runTests: () => pool.runTests(method, files)
|
|
3809
|
-
};
|
|
3810
|
-
}));
|
|
3811
|
-
if (isCancelled) {
|
|
3812
|
-
return;
|
|
3813
|
-
}
|
|
3814
|
-
const parallelPools = [];
|
|
3815
|
-
const nonParallelPools = [];
|
|
3816
|
-
for (const pool of initialisedPools) {
|
|
3817
|
-
if (!pool) {
|
|
3818
|
-
// this means it was cancelled
|
|
3819
|
-
return;
|
|
3820
|
-
}
|
|
3821
|
-
if (pool.provider.mocker && pool.provider.supportsParallelism) {
|
|
3822
|
-
parallelPools.push(pool.runTests);
|
|
3823
|
-
} else {
|
|
3824
|
-
nonParallelPools.push(pool.runTests);
|
|
3825
|
-
}
|
|
3826
|
-
}
|
|
3827
|
-
await Promise.all(parallelPools.map((runTests) => runTests()));
|
|
3828
|
-
for (const runTests of nonParallelPools) {
|
|
3829
|
-
if (isCancelled) {
|
|
3830
|
-
return;
|
|
3831
|
-
}
|
|
3832
|
-
await runTests();
|
|
3833
|
-
}
|
|
3834
|
-
};
|
|
3835
|
-
function getThreadsCount(project) {
|
|
3836
|
-
const config = project.config.browser;
|
|
3837
|
-
if (!config.headless || !config.fileParallelism || !project.browser.provider.supportsParallelism) {
|
|
3838
|
-
return 1;
|
|
3839
|
-
}
|
|
3840
|
-
if (project.config.maxWorkers) {
|
|
3841
|
-
return project.config.maxWorkers;
|
|
3842
|
-
}
|
|
3843
|
-
return threadsCount;
|
|
3844
|
-
}
|
|
3845
|
-
return {
|
|
3846
|
-
name: "browser",
|
|
3847
|
-
async close() {
|
|
3848
|
-
await Promise.all([...providers].map((provider) => provider.close()));
|
|
3849
|
-
vitest._browserSessions.sessionIds.clear();
|
|
3850
|
-
providers.clear();
|
|
3851
|
-
vitest.projects.forEach((project) => {
|
|
3852
|
-
project.browser?.state.orchestrators.forEach((orchestrator) => {
|
|
3853
|
-
orchestrator.$close();
|
|
3854
|
-
});
|
|
3855
|
-
});
|
|
3856
|
-
debug?.("browser pool closed all providers");
|
|
3857
|
-
},
|
|
3858
|
-
runTests: (files) => runWorkspaceTests("run", files),
|
|
3859
|
-
collectTests: (files) => runWorkspaceTests("collect", files)
|
|
3860
|
-
};
|
|
3861
|
-
}
|
|
3862
|
-
function escapePathToRegexp(path) {
|
|
3863
|
-
return path.replace(/[/\\.?*()^${}|[\]+]/g, "\\$&");
|
|
3864
|
-
}
|
|
3865
|
-
class BrowserPool {
|
|
3866
|
-
_queue = [];
|
|
3867
|
-
_promise;
|
|
3868
|
-
_providedContext;
|
|
3869
|
-
readySessions = new Set();
|
|
3870
|
-
constructor(project, options) {
|
|
3871
|
-
this.project = project;
|
|
3872
|
-
this.options = options;
|
|
3873
|
-
}
|
|
3874
|
-
cancel() {
|
|
3875
|
-
this._queue = [];
|
|
3876
|
-
}
|
|
3877
|
-
reject(error) {
|
|
3878
|
-
this._promise?.reject(error);
|
|
3879
|
-
this._promise = undefined;
|
|
3880
|
-
this.cancel();
|
|
3881
|
-
}
|
|
3882
|
-
get orchestrators() {
|
|
3883
|
-
return this.project.browser.state.orchestrators;
|
|
3884
|
-
}
|
|
3885
|
-
async runTests(method, files) {
|
|
3886
|
-
this._promise ??= createDefer();
|
|
3887
|
-
if (!files.length) {
|
|
3888
|
-
debug?.("no tests found, finishing test run immediately");
|
|
3889
|
-
this._promise.resolve();
|
|
3890
|
-
return this._promise;
|
|
3891
|
-
}
|
|
3892
|
-
this._providedContext = stringify(this.project.getProvidedContext());
|
|
3893
|
-
this._queue.push(...files);
|
|
3894
|
-
this.readySessions.forEach((sessionId) => {
|
|
3895
|
-
if (this._queue.length) {
|
|
3896
|
-
this.readySessions.delete(sessionId);
|
|
3897
|
-
this.runNextTest(method, sessionId);
|
|
3898
|
-
}
|
|
3899
|
-
});
|
|
3900
|
-
if (this.orchestrators.size >= this.options.maxWorkers) {
|
|
3901
|
-
debug?.("all orchestrators are ready, not creating more");
|
|
3902
|
-
return this._promise;
|
|
3903
|
-
}
|
|
3904
|
-
// open the minimum amount of tabs
|
|
3905
|
-
// if there is only 1 file running, we don't need 8 tabs running
|
|
3906
|
-
const workerCount = Math.min(this.options.maxWorkers - this.orchestrators.size, files.length);
|
|
3907
|
-
const promises = [];
|
|
3908
|
-
for (let i = 0; i < workerCount; i++) {
|
|
3909
|
-
const sessionId = crypto.randomUUID();
|
|
3910
|
-
this.project.vitest._browserSessions.sessionIds.add(sessionId);
|
|
3911
|
-
const project = this.project.name;
|
|
3912
|
-
debug?.("[%s] creating session for %s", sessionId, project);
|
|
3913
|
-
const page = this.openPage(sessionId).then(() => {
|
|
3914
|
-
// start running tests on the page when it's ready
|
|
3915
|
-
this.runNextTest(method, sessionId);
|
|
3916
|
-
});
|
|
3917
|
-
promises.push(page);
|
|
3918
|
-
}
|
|
3919
|
-
await Promise.all(promises);
|
|
3920
|
-
debug?.("all sessions are created");
|
|
3921
|
-
return this._promise;
|
|
3922
|
-
}
|
|
3923
|
-
async openPage(sessionId) {
|
|
3924
|
-
const sessionPromise = this.project.vitest._browserSessions.createSession(sessionId, this.project, this);
|
|
3925
|
-
const browser = this.project.browser;
|
|
3926
|
-
const url = new URL("/__vitest_test__/", this.options.origin);
|
|
3927
|
-
url.searchParams.set("sessionId", sessionId);
|
|
3928
|
-
const pagePromise = browser.provider.openPage(sessionId, url.toString());
|
|
3929
|
-
await Promise.all([sessionPromise, pagePromise]);
|
|
3930
|
-
}
|
|
3931
|
-
getOrchestrator(sessionId) {
|
|
3932
|
-
const orchestrator = this.orchestrators.get(sessionId);
|
|
3933
|
-
if (!orchestrator) {
|
|
3934
|
-
throw new Error(`Orchestrator not found for session ${sessionId}. This is a bug in Vitest. Please, open a new issue with reproduction.`);
|
|
3935
|
-
}
|
|
3936
|
-
return orchestrator;
|
|
3937
|
-
}
|
|
3938
|
-
finishSession(sessionId) {
|
|
3939
|
-
this.readySessions.add(sessionId);
|
|
3940
|
-
// the last worker finished running tests
|
|
3941
|
-
if (this.readySessions.size === this.orchestrators.size) {
|
|
3942
|
-
this._promise?.resolve();
|
|
3943
|
-
this._promise = undefined;
|
|
3944
|
-
debug?.("[%s] all tests finished running", sessionId);
|
|
3945
|
-
} else {
|
|
3946
|
-
debug?.(`did not finish sessions for ${sessionId}: |ready - %s| |overall - %s|`, [...this.readySessions].join(", "), [...this.orchestrators.keys()].join(", "));
|
|
3947
|
-
}
|
|
3948
|
-
}
|
|
3949
|
-
runNextTest(method, sessionId) {
|
|
3950
|
-
const file = this._queue.shift();
|
|
3951
|
-
if (!file) {
|
|
3952
|
-
debug?.("[%s] no more tests to run", sessionId);
|
|
3953
|
-
const isolate = this.project.config.browser.isolate;
|
|
3954
|
-
// we don't need to cleanup testers if isolation is enabled,
|
|
3955
|
-
// because cleanup is done at the end of every test
|
|
3956
|
-
if (isolate) {
|
|
3957
|
-
this.finishSession(sessionId);
|
|
3958
|
-
return;
|
|
3959
|
-
}
|
|
3960
|
-
// we need to cleanup testers first because there is only
|
|
3961
|
-
// one iframe and it does the cleanup only after everything is completed
|
|
3962
|
-
const orchestrator = this.getOrchestrator(sessionId);
|
|
3963
|
-
orchestrator.cleanupTesters().catch((error) => this.reject(error)).finally(() => this.finishSession(sessionId));
|
|
3964
|
-
return;
|
|
3965
|
-
}
|
|
3966
|
-
if (!this._promise) {
|
|
3967
|
-
throw new Error(`Unexpected empty queue`);
|
|
3968
|
-
}
|
|
3969
|
-
const startTime = performance.now();
|
|
3970
|
-
const orchestrator = this.getOrchestrator(sessionId);
|
|
3971
|
-
debug?.("[%s] run test %s", sessionId, file);
|
|
3972
|
-
this.setBreakpoint(sessionId, file).then(() => {
|
|
3973
|
-
// this starts running tests inside the orchestrator
|
|
3974
|
-
orchestrator.createTesters({
|
|
3975
|
-
method,
|
|
3976
|
-
files: [file],
|
|
3977
|
-
providedContext: this._providedContext || "[{}]",
|
|
3978
|
-
startTime
|
|
3979
|
-
}).then(() => {
|
|
3980
|
-
debug?.("[%s] test %s finished running", sessionId, file);
|
|
3981
|
-
this.runNextTest(method, sessionId);
|
|
3982
|
-
}).catch((error) => {
|
|
3983
|
-
// if user cancels the test run manually, ignore the error and exit gracefully
|
|
3984
|
-
if (this.project.vitest.isCancelling && error instanceof Error && error.message.startsWith("Browser connection was closed while running tests")) {
|
|
3985
|
-
this.cancel();
|
|
3986
|
-
this._promise?.resolve();
|
|
3987
|
-
this._promise = undefined;
|
|
3988
|
-
debug?.("[%s] browser connection was closed", sessionId);
|
|
3989
|
-
return;
|
|
3990
|
-
}
|
|
3991
|
-
debug?.("[%s] error during %s test run: %s", sessionId, file, error);
|
|
3992
|
-
this.reject(error);
|
|
3993
|
-
});
|
|
3994
|
-
}).catch((err) => this.reject(err));
|
|
3995
|
-
}
|
|
3996
|
-
async setBreakpoint(sessionId, file) {
|
|
3997
|
-
if (!this.project.config.inspector.waitForDebugger) {
|
|
3998
|
-
return;
|
|
3999
|
-
}
|
|
4000
|
-
const provider = this.project.browser.provider;
|
|
4001
|
-
if (!provider.getCDPSession) {
|
|
4002
|
-
throw new Error("Unable to set breakpoint, CDP not supported");
|
|
4003
|
-
}
|
|
4004
|
-
debug?.("[%s] set breakpoint for %s", sessionId, file);
|
|
4005
|
-
const session = await provider.getCDPSession(sessionId);
|
|
4006
|
-
await session.send("Debugger.enable", {});
|
|
4007
|
-
await session.send("Debugger.setBreakpointByUrl", {
|
|
4008
|
-
lineNumber: 0,
|
|
4009
|
-
urlRegex: escapePathToRegexp(file)
|
|
4010
|
-
});
|
|
4011
|
-
}
|
|
3130
|
+
function defineBrowserCommand(fn) {
|
|
3131
|
+
return fn;
|
|
4012
3132
|
}
|
|
4013
|
-
|
|
4014
|
-
|
|
3133
|
+
const createBrowserServer = async (options) => {
|
|
3134
|
+
const project = options.project;
|
|
3135
|
+
const configFile = project.vite.config.configFile;
|
|
4015
3136
|
if (project.vitest.version !== version) {
|
|
4016
3137
|
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."));
|
|
4017
3138
|
}
|
|
@@ -4020,6 +3141,7 @@ async function createBrowserServer(project, configFile, prePlugins = [], postPlu
|
|
|
4020
3141
|
const logLevel = process.env.VITEST_BROWSER_DEBUG ?? "info";
|
|
4021
3142
|
const logger = createViteLogger(project.vitest.logger, logLevel, { allowClearScreen: false });
|
|
4022
3143
|
const mockerRegistry = new MockerRegistry();
|
|
3144
|
+
let cacheDir;
|
|
4023
3145
|
const vite = await createViteServer({
|
|
4024
3146
|
...project.options,
|
|
4025
3147
|
base: "/",
|
|
@@ -4043,16 +3165,35 @@ async function createBrowserServer(project, configFile, prePlugins = [], postPlu
|
|
|
4043
3165
|
},
|
|
4044
3166
|
cacheDir: project.vite.config.cacheDir,
|
|
4045
3167
|
plugins: [
|
|
4046
|
-
|
|
3168
|
+
{
|
|
3169
|
+
name: "vitest-internal:browser-cacheDir",
|
|
3170
|
+
configResolved(config) {
|
|
3171
|
+
cacheDir = config.cacheDir;
|
|
3172
|
+
}
|
|
3173
|
+
},
|
|
3174
|
+
...options.mocksPlugins({ filter(id) {
|
|
3175
|
+
if (id.includes(distRoot) || id.includes(cacheDir)) {
|
|
3176
|
+
return false;
|
|
3177
|
+
}
|
|
3178
|
+
return true;
|
|
3179
|
+
} }),
|
|
3180
|
+
options.metaEnvReplacer(),
|
|
4047
3181
|
...project.options?.plugins || [],
|
|
4048
3182
|
BrowserPlugin(server),
|
|
4049
3183
|
interceptorPlugin({ registry: mockerRegistry }),
|
|
4050
|
-
|
|
3184
|
+
options.coveragePlugin()
|
|
4051
3185
|
]
|
|
4052
3186
|
});
|
|
4053
3187
|
await vite.listen();
|
|
4054
3188
|
setupBrowserRpc(server, mockerRegistry);
|
|
4055
3189
|
return server;
|
|
3190
|
+
};
|
|
3191
|
+
function defineBrowserProvider(options) {
|
|
3192
|
+
return {
|
|
3193
|
+
...options,
|
|
3194
|
+
options: options.options || {},
|
|
3195
|
+
serverFactory: createBrowserServer
|
|
3196
|
+
};
|
|
4056
3197
|
}
|
|
4057
3198
|
|
|
4058
|
-
export {
|
|
3199
|
+
export { createBrowserServer, defineBrowserCommand, defineBrowserProvider, parseKeyDef, resolveScreenshotPath };
|