@vitest/browser 4.1.5 → 5.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/context.d.ts +50 -1
- package/dist/client/.vite/manifest.json +15 -6
- package/dist/client/__vitest__/assets/index-Cd6On2Pm.js +136 -0
- package/dist/client/__vitest__/assets/index-Dw9P28Qt.css +1 -0
- package/dist/client/__vitest__/index.html +2 -2
- package/dist/client/__vitest_browser__/{orchestrator-DM4mHHP0.js → orchestrator-BfoS0x4w.js} +55 -31
- package/dist/client/__vitest_browser__/rrweb-snapshot-xhvrgOHx.js +5476 -0
- package/dist/client/__vitest_browser__/{utils-DmkAiRYk.js → tester-BJtW9QqZ.js} +2674 -930
- package/dist/client/__vitest_browser__/utils-Nd8hqrhP.js +189 -0
- package/dist/client/orchestrator.html +2 -2
- package/dist/client/tester/locators.d.ts +69 -0
- package/dist/client/tester/tester.html +2 -2
- package/dist/client/tester/trace.d.ts +54 -0
- package/dist/client.js +1 -0
- package/dist/context.js +155 -15
- package/dist/expect-element.js +30 -30
- package/dist/index.d.ts +6 -22
- package/dist/index.js +29 -4551
- package/dist/locators-CesZ2RSY.js +5 -0
- package/dist/locators.d.ts +9 -1
- package/dist/locators.js +1 -1
- package/dist/shared/screenshotMatcher/types.d.ts +3 -3
- package/dist/state.js +1 -0
- package/dist/vendor-types.d.ts +131 -0
- package/dist/vendor-types.ts +1 -0
- package/package.json +15 -7
- package/dist/client/__vitest__/assets/index-BPQdrqGZ.js +0 -94
- package/dist/client/__vitest__/assets/index-Da0hb3oU.css +0 -1
- package/dist/client/__vitest__/bg.png +0 -0
- package/dist/client/__vitest_browser__/tester-Bf18VQr2.js +0 -2288
- package/dist/index-D8jtZoIM.js +0 -5
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
(function polyfill() {
|
|
2
|
+
const relList = document.createElement("link").relList;
|
|
3
|
+
if (relList && relList.supports && relList.supports("modulepreload")) return;
|
|
4
|
+
for (const link of document.querySelectorAll('link[rel="modulepreload"]')) processPreload(link);
|
|
5
|
+
new MutationObserver((mutations) => {
|
|
6
|
+
for (const mutation of mutations) {
|
|
7
|
+
if (mutation.type !== "childList") continue;
|
|
8
|
+
for (const node of mutation.addedNodes) if (node.tagName === "LINK" && node.rel === "modulepreload") processPreload(node);
|
|
9
|
+
}
|
|
10
|
+
}).observe(document, {
|
|
11
|
+
childList: true,
|
|
12
|
+
subtree: true
|
|
13
|
+
});
|
|
14
|
+
function getFetchOpts(link) {
|
|
15
|
+
const fetchOpts = {};
|
|
16
|
+
if (link.integrity) fetchOpts.integrity = link.integrity;
|
|
17
|
+
if (link.referrerPolicy) fetchOpts.referrerPolicy = link.referrerPolicy;
|
|
18
|
+
if (link.crossOrigin === "use-credentials") fetchOpts.credentials = "include";
|
|
19
|
+
else if (link.crossOrigin === "anonymous") fetchOpts.credentials = "omit";
|
|
20
|
+
else fetchOpts.credentials = "same-origin";
|
|
21
|
+
return fetchOpts;
|
|
22
|
+
}
|
|
23
|
+
function processPreload(link) {
|
|
24
|
+
if (link.ep) return;
|
|
25
|
+
link.ep = true;
|
|
26
|
+
const fetchOpts = getFetchOpts(link);
|
|
27
|
+
fetch(link.href, fetchOpts);
|
|
28
|
+
}
|
|
29
|
+
})();
|
|
30
|
+
const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
|
|
31
|
+
function normalizeWindowsPath(input = "") {
|
|
32
|
+
if (!input) {
|
|
33
|
+
return input;
|
|
34
|
+
}
|
|
35
|
+
return input.replace(/\\/g, "/").replace(_DRIVE_LETTER_START_RE, (r) => r.toUpperCase());
|
|
36
|
+
}
|
|
37
|
+
const _IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[A-Za-z]:[/\\]/;
|
|
38
|
+
const _ROOT_FOLDER_RE = /^\/([A-Za-z]:)?$/;
|
|
39
|
+
function cwd() {
|
|
40
|
+
if (typeof process !== "undefined" && typeof process.cwd === "function") {
|
|
41
|
+
return process.cwd().replace(/\\/g, "/");
|
|
42
|
+
}
|
|
43
|
+
return "/";
|
|
44
|
+
}
|
|
45
|
+
const resolve = function(...arguments_) {
|
|
46
|
+
arguments_ = arguments_.map((argument) => normalizeWindowsPath(argument));
|
|
47
|
+
let resolvedPath = "";
|
|
48
|
+
let resolvedAbsolute = false;
|
|
49
|
+
for (let index = arguments_.length - 1; index >= -1 && !resolvedAbsolute; index--) {
|
|
50
|
+
const path = index >= 0 ? arguments_[index] : cwd();
|
|
51
|
+
if (!path || path.length === 0) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
resolvedPath = `${path}/${resolvedPath}`;
|
|
55
|
+
resolvedAbsolute = isAbsolute(path);
|
|
56
|
+
}
|
|
57
|
+
resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute);
|
|
58
|
+
if (resolvedAbsolute && !isAbsolute(resolvedPath)) {
|
|
59
|
+
return `/${resolvedPath}`;
|
|
60
|
+
}
|
|
61
|
+
return resolvedPath.length > 0 ? resolvedPath : ".";
|
|
62
|
+
};
|
|
63
|
+
function normalizeString(path, allowAboveRoot) {
|
|
64
|
+
let res = "";
|
|
65
|
+
let lastSegmentLength = 0;
|
|
66
|
+
let lastSlash = -1;
|
|
67
|
+
let dots = 0;
|
|
68
|
+
let char = null;
|
|
69
|
+
for (let index = 0; index <= path.length; ++index) {
|
|
70
|
+
if (index < path.length) {
|
|
71
|
+
char = path[index];
|
|
72
|
+
} else if (char === "/") {
|
|
73
|
+
break;
|
|
74
|
+
} else {
|
|
75
|
+
char = "/";
|
|
76
|
+
}
|
|
77
|
+
if (char === "/") {
|
|
78
|
+
if (lastSlash === index - 1 || dots === 1) ;
|
|
79
|
+
else if (dots === 2) {
|
|
80
|
+
if (res.length < 2 || lastSegmentLength !== 2 || res[res.length - 1] !== "." || res[res.length - 2] !== ".") {
|
|
81
|
+
if (res.length > 2) {
|
|
82
|
+
const lastSlashIndex = res.lastIndexOf("/");
|
|
83
|
+
if (lastSlashIndex === -1) {
|
|
84
|
+
res = "";
|
|
85
|
+
lastSegmentLength = 0;
|
|
86
|
+
} else {
|
|
87
|
+
res = res.slice(0, lastSlashIndex);
|
|
88
|
+
lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
|
|
89
|
+
}
|
|
90
|
+
lastSlash = index;
|
|
91
|
+
dots = 0;
|
|
92
|
+
continue;
|
|
93
|
+
} else if (res.length > 0) {
|
|
94
|
+
res = "";
|
|
95
|
+
lastSegmentLength = 0;
|
|
96
|
+
lastSlash = index;
|
|
97
|
+
dots = 0;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (allowAboveRoot) {
|
|
102
|
+
res += res.length > 0 ? "/.." : "..";
|
|
103
|
+
lastSegmentLength = 2;
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
if (res.length > 0) {
|
|
107
|
+
res += `/${path.slice(lastSlash + 1, index)}`;
|
|
108
|
+
} else {
|
|
109
|
+
res = path.slice(lastSlash + 1, index);
|
|
110
|
+
}
|
|
111
|
+
lastSegmentLength = index - lastSlash - 1;
|
|
112
|
+
}
|
|
113
|
+
lastSlash = index;
|
|
114
|
+
dots = 0;
|
|
115
|
+
} else if (char === "." && dots !== -1) {
|
|
116
|
+
++dots;
|
|
117
|
+
} else {
|
|
118
|
+
dots = -1;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return res;
|
|
122
|
+
}
|
|
123
|
+
const isAbsolute = function(p) {
|
|
124
|
+
return _IS_ABSOLUTE_RE.test(p);
|
|
125
|
+
};
|
|
126
|
+
const relative = function(from, to) {
|
|
127
|
+
const _from = resolve(from).replace(_ROOT_FOLDER_RE, "$1").split("/");
|
|
128
|
+
const _to = resolve(to).replace(_ROOT_FOLDER_RE, "$1").split("/");
|
|
129
|
+
if (_to[0][1] === ":" && _from[0][1] === ":" && _from[0] !== _to[0]) {
|
|
130
|
+
return _to.join("/");
|
|
131
|
+
}
|
|
132
|
+
const _fromCopy = [..._from];
|
|
133
|
+
for (const segment of _fromCopy) {
|
|
134
|
+
if (_to[0] !== segment) {
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
_from.shift();
|
|
138
|
+
_to.shift();
|
|
139
|
+
}
|
|
140
|
+
return [..._from.map(() => ".."), ..._to].join("/");
|
|
141
|
+
};
|
|
142
|
+
async function importId(id) {
|
|
143
|
+
const name = `/@id/${id}`.replace(/\\/g, "/");
|
|
144
|
+
return (/* @__PURE__ */ getBrowserState()).wrapModule(() => import(
|
|
145
|
+
/* @vite-ignore */
|
|
146
|
+
name
|
|
147
|
+
));
|
|
148
|
+
}
|
|
149
|
+
async function importFs(id) {
|
|
150
|
+
const name = `/@fs/${id}`.replace(/\\/g, "/");
|
|
151
|
+
return (/* @__PURE__ */ getBrowserState()).wrapModule(() => import(
|
|
152
|
+
/* @vite-ignore */
|
|
153
|
+
name
|
|
154
|
+
));
|
|
155
|
+
}
|
|
156
|
+
const moduleRunner = {
|
|
157
|
+
isBrowser: true,
|
|
158
|
+
import: (id) => {
|
|
159
|
+
if (id[0] === "/" || id[1] === ":") {
|
|
160
|
+
return importFs(id);
|
|
161
|
+
}
|
|
162
|
+
return importId(id);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
const now = globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
|
|
166
|
+
function getConfig() {
|
|
167
|
+
return (/* @__PURE__ */ getBrowserState()).config;
|
|
168
|
+
}
|
|
169
|
+
// @__NO_SIDE_EFFECTS__
|
|
170
|
+
function getBrowserState() {
|
|
171
|
+
return window.__vitest_browser_runner__;
|
|
172
|
+
}
|
|
173
|
+
// @__NO_SIDE_EFFECTS__
|
|
174
|
+
function getWorkerState() {
|
|
175
|
+
const state = window.__vitest_worker__;
|
|
176
|
+
if (!state) {
|
|
177
|
+
throw new Error("Worker state is not found. This is an issue with Vitest. Please, open an issue.");
|
|
178
|
+
}
|
|
179
|
+
return state;
|
|
180
|
+
}
|
|
181
|
+
export {
|
|
182
|
+
getConfig as a,
|
|
183
|
+
resolve as b,
|
|
184
|
+
getWorkerState as c,
|
|
185
|
+
getBrowserState as g,
|
|
186
|
+
moduleRunner as m,
|
|
187
|
+
now as n,
|
|
188
|
+
relative as r
|
|
189
|
+
};
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
{__VITEST_INJECTOR__}
|
|
27
27
|
{__VITEST_ERROR_CATCHER__}
|
|
28
28
|
{__VITEST_SCRIPTS__}
|
|
29
|
-
<script type="module" crossorigin src="/__vitest_browser__/orchestrator-
|
|
30
|
-
<link rel="modulepreload" crossorigin href="/__vitest_browser__/utils-
|
|
29
|
+
<script type="module" crossorigin src="/__vitest_browser__/orchestrator-BfoS0x4w.js"></script>
|
|
30
|
+
<link rel="modulepreload" crossorigin href="/__vitest_browser__/utils-Nd8hqrhP.js">
|
|
31
31
|
</head>
|
|
32
32
|
<body>
|
|
33
33
|
<div id="vitest-tester"></div>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { LocatorByRoleOptions, LocatorOptions, LocatorScreenshotOptions, MarkOptions, SelectorOptions, UserEventClearOptions, UserEventClickOptions, UserEventDragAndDropOptions, UserEventFillOptions, UserEventHoverOptions, UserEventSelectOptions, UserEventUploadOptions, UserEventWheelOptions } from "vitest/browser";
|
|
2
|
+
import { Ivya } from "ivya";
|
|
3
|
+
export { ensureAwaited } from "../utils.js";
|
|
4
|
+
export { convertElementToCssSelector, getIframeScale, processTimeoutOptions } from "./tester-utils.js";
|
|
5
|
+
export { getByAltTextSelector, getByLabelSelector, getByPlaceholderSelector, getByRoleSelector, getByTestIdSelector, getByTextSelector, getByTitleSelector } from "ivya";
|
|
6
|
+
export declare const selectorEngine: Ivya;
|
|
7
|
+
export declare abstract class Locator {
|
|
8
|
+
abstract selector: string;
|
|
9
|
+
private _parsedSelector;
|
|
10
|
+
protected _container?: Element | undefined;
|
|
11
|
+
protected _pwSelector?: string | undefined;
|
|
12
|
+
protected _pwLocator?: string | undefined;
|
|
13
|
+
protected _errorSource?: Error;
|
|
14
|
+
constructor();
|
|
15
|
+
click(options?: UserEventClickOptions): Promise<void>;
|
|
16
|
+
dblClick(options?: UserEventClickOptions): Promise<void>;
|
|
17
|
+
tripleClick(options?: UserEventClickOptions): Promise<void>;
|
|
18
|
+
wheel(options: UserEventWheelOptions): Promise<void>;
|
|
19
|
+
clear(options?: UserEventClearOptions): Promise<void>;
|
|
20
|
+
hover(options?: UserEventHoverOptions): Promise<void>;
|
|
21
|
+
unhover(options?: UserEventHoverOptions): Promise<void>;
|
|
22
|
+
fill(text: string, options?: UserEventFillOptions): Promise<void>;
|
|
23
|
+
upload(files: string | string[] | File | File[], options?: UserEventUploadOptions): Promise<void>;
|
|
24
|
+
dropTo(target: Locator, options?: UserEventDragAndDropOptions): Promise<void>;
|
|
25
|
+
selectOptions(value: HTMLElement | HTMLElement[] | Locator | Locator[] | string | string[], options?: UserEventSelectOptions): Promise<void>;
|
|
26
|
+
screenshot(options: Omit<LocatorScreenshotOptions, "base64"> & {
|
|
27
|
+
base64: true;
|
|
28
|
+
}): Promise<{
|
|
29
|
+
path: string;
|
|
30
|
+
base64: string;
|
|
31
|
+
}>;
|
|
32
|
+
screenshot(options?: LocatorScreenshotOptions): Promise<string>;
|
|
33
|
+
mark(name: string, options?: MarkOptions): Promise<void>;
|
|
34
|
+
protected abstract locator(selector: string): Locator;
|
|
35
|
+
protected abstract elementLocator(element: Element): Locator;
|
|
36
|
+
getByRole(role: string, options?: LocatorByRoleOptions): Locator;
|
|
37
|
+
getByAltText(text: string | RegExp, options?: LocatorOptions): Locator;
|
|
38
|
+
getByLabelText(text: string | RegExp, options?: LocatorOptions): Locator;
|
|
39
|
+
getByPlaceholder(text: string | RegExp, options?: LocatorOptions): Locator;
|
|
40
|
+
getByTestId(testId: string | RegExp): Locator;
|
|
41
|
+
getByText(text: string | RegExp, options?: LocatorOptions): Locator;
|
|
42
|
+
getByTitle(title: string | RegExp, options?: LocatorOptions): Locator;
|
|
43
|
+
filter(filter: LocatorOptions): Locator;
|
|
44
|
+
and(locator: Locator): Locator;
|
|
45
|
+
or(locator: Locator): Locator;
|
|
46
|
+
query(): HTMLElement | SVGElement | null;
|
|
47
|
+
element(): HTMLElement | SVGElement;
|
|
48
|
+
elements(): (HTMLElement | SVGElement)[];
|
|
49
|
+
get length(): number;
|
|
50
|
+
all(): Locator[];
|
|
51
|
+
nth(index: number): Locator;
|
|
52
|
+
first(): Locator;
|
|
53
|
+
last(): Locator;
|
|
54
|
+
toString(): string;
|
|
55
|
+
serialize(): SerializedLocator;
|
|
56
|
+
asLocator(): string;
|
|
57
|
+
toJSON(): SerializedLocator;
|
|
58
|
+
findElement(options_?: SelectorOptions): Promise<HTMLElement | SVGElement>;
|
|
59
|
+
protected triggerCommand<T>(command: string, ...args: any[]): Promise<T>;
|
|
60
|
+
}
|
|
61
|
+
export declare function triggerCommandWithTrace<T>(options: {
|
|
62
|
+
name: string;
|
|
63
|
+
arguments: unknown[];
|
|
64
|
+
errorSource?: Error | undefined;
|
|
65
|
+
}): Promise<T>;
|
|
66
|
+
export interface SerializedLocator {
|
|
67
|
+
selector: string;
|
|
68
|
+
locator: string;
|
|
69
|
+
}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<link rel="icon" href="{__VITEST_FAVICON__}" type="image/svg+xml">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>Vitest Browser Tester</title>
|
|
8
|
-
<script type="module" crossorigin src="/__vitest_browser__/tester-
|
|
9
|
-
<link rel="modulepreload" crossorigin href="/__vitest_browser__/utils-
|
|
8
|
+
<script type="module" crossorigin src="/__vitest_browser__/tester-BJtW9QqZ.js"></script>
|
|
9
|
+
<link rel="modulepreload" crossorigin href="/__vitest_browser__/utils-Nd8hqrhP.js">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
</body>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Task } from "@vitest/runner";
|
|
2
|
+
import type { SerializedLocator } from "./locators.js";
|
|
3
|
+
export interface BrowserTraceData {
|
|
4
|
+
retry: number;
|
|
5
|
+
repeats: number;
|
|
6
|
+
recordCanvas: boolean;
|
|
7
|
+
entries: BrowserTraceEntry[];
|
|
8
|
+
}
|
|
9
|
+
export type BrowserTraceEntryKind = "action" | "expect" | "mark" | "lifecycle";
|
|
10
|
+
export type BrowserTraceEntryStatus = "pass" | "fail";
|
|
11
|
+
export type BrowserTraceSelectorResolution = "matched" | "missing" | "error";
|
|
12
|
+
export interface BrowserTraceEntry {
|
|
13
|
+
name: string;
|
|
14
|
+
kind: BrowserTraceEntryKind;
|
|
15
|
+
status?: BrowserTraceEntryStatus;
|
|
16
|
+
startTime: number;
|
|
17
|
+
duration?: number;
|
|
18
|
+
stack?: string;
|
|
19
|
+
location?: {
|
|
20
|
+
file: string;
|
|
21
|
+
line: number;
|
|
22
|
+
column: number;
|
|
23
|
+
};
|
|
24
|
+
element?: SerializedLocator;
|
|
25
|
+
snapshot: TraceSnapshot;
|
|
26
|
+
}
|
|
27
|
+
interface TraceSnapshot {
|
|
28
|
+
serialized: unknown;
|
|
29
|
+
viewport: {
|
|
30
|
+
width: number;
|
|
31
|
+
height: number;
|
|
32
|
+
};
|
|
33
|
+
scroll: {
|
|
34
|
+
x: number;
|
|
35
|
+
y: number;
|
|
36
|
+
};
|
|
37
|
+
selectorId?: number;
|
|
38
|
+
selectorResolution?: BrowserTraceSelectorResolution;
|
|
39
|
+
selectorError?: string;
|
|
40
|
+
pseudoClassIds: Record<PseudoClassName, number[]>;
|
|
41
|
+
}
|
|
42
|
+
declare const PSEUDO_CLASS_NAMES: readonly [":hover", ":active", ":focus", ":focus-visible", ":focus-within"];
|
|
43
|
+
type PseudoClassName = (typeof PSEUDO_CLASS_NAMES)[number];
|
|
44
|
+
export type BrowserTraceState = Record<string, BrowserTraceData>;
|
|
45
|
+
export interface BrowserTraceAttempt {
|
|
46
|
+
retry: number;
|
|
47
|
+
repeats: number;
|
|
48
|
+
startTime: number;
|
|
49
|
+
}
|
|
50
|
+
export declare function recordBrowserTraceEntry(task: Task, options: Omit<BrowserTraceEntry, "snapshot" | "startTime"> & {
|
|
51
|
+
startTime?: number;
|
|
52
|
+
}): void;
|
|
53
|
+
export declare function getBrowserTrace(testId: string, repeats: number, retry: number): BrowserTraceData | undefined;
|
|
54
|
+
export {};
|
package/dist/client.js
CHANGED
|
@@ -317,6 +317,7 @@ const stringify = (value, replacer, space) => {
|
|
|
317
317
|
}
|
|
318
318
|
};
|
|
319
319
|
|
|
320
|
+
globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
|
|
320
321
|
/* @__NO_SIDE_EFFECTS__ */
|
|
321
322
|
function getBrowserState() {
|
|
322
323
|
// @ts-expect-error not typed global
|
package/dist/context.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { vi } from 'vitest';
|
|
2
2
|
import { __INTERNAL, stringify } from 'vitest/internal/browser';
|
|
3
3
|
|
|
4
|
+
const now = globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
|
|
4
5
|
function ensureAwaited(promise) {
|
|
5
6
|
const test = getWorkerState().current;
|
|
6
7
|
if (!test || test.type !== "test") {
|
|
@@ -49,6 +50,105 @@ function getWorkerState() {
|
|
|
49
50
|
return state;
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
// rrweb-snapshot rewrites pseudo-class selectors in serialized styles so replay can
|
|
54
|
+
// reproduce snapshot-time states. For example:
|
|
55
|
+
// some-selector:hover { ... }
|
|
56
|
+
// becomes:
|
|
57
|
+
// some-selector:hover, some-selector.\:hover { ... }
|
|
58
|
+
// Vitest side integration then adds matching pseudo-state classes in the replay DOM.
|
|
59
|
+
// rrweb-snapshot only handles `:hover` upstream, so we patch it locally for the
|
|
60
|
+
// other user-action pseudo-classes as well.
|
|
61
|
+
// https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/Pseudo-classes#user_action_pseudo-classes
|
|
62
|
+
const PSEUDO_CLASS_NAMES = [
|
|
63
|
+
":hover",
|
|
64
|
+
":active",
|
|
65
|
+
":focus",
|
|
66
|
+
":focus-visible",
|
|
67
|
+
":focus-within"
|
|
68
|
+
];
|
|
69
|
+
function getBrowserTraceState() {
|
|
70
|
+
return getBrowserState().browserTraceState ??= {};
|
|
71
|
+
}
|
|
72
|
+
function getTraceStateKey(testId, repeats, retry) {
|
|
73
|
+
return `${testId}:${repeats}:${retry}`;
|
|
74
|
+
}
|
|
75
|
+
// TODO: should we avoid accumulating? send and immediately clear each entry to save memory?
|
|
76
|
+
function recordBrowserTraceEntry(task, options) {
|
|
77
|
+
const attemptInfo = getBrowserState().browserTraceAttempts.get(task.id);
|
|
78
|
+
const relativeStartTime = (options.startTime ?? now()) - attemptInfo.startTime;
|
|
79
|
+
const snapshot = takeSnapshot(options.element);
|
|
80
|
+
const entry = {
|
|
81
|
+
...options,
|
|
82
|
+
startTime: relativeStartTime,
|
|
83
|
+
snapshot
|
|
84
|
+
};
|
|
85
|
+
const { retry, repeats } = attemptInfo;
|
|
86
|
+
const { recordCanvas } = getBrowserState().config.browser.traceView;
|
|
87
|
+
const state = getBrowserTraceState();
|
|
88
|
+
const traceKey = getTraceStateKey(task.id, repeats, retry);
|
|
89
|
+
state[traceKey] ??= {
|
|
90
|
+
retry,
|
|
91
|
+
repeats,
|
|
92
|
+
recordCanvas,
|
|
93
|
+
entries: []
|
|
94
|
+
};
|
|
95
|
+
state[traceKey].entries.push(entry);
|
|
96
|
+
}
|
|
97
|
+
// Resolve ivya selector to a DOM element and take a snapshot with rrweb Mirror
|
|
98
|
+
// so we can store the nodeId for provider-agnostic element highlighting in the viewer.
|
|
99
|
+
// Note: Playwright's alternative approach is to store the raw selector and run the
|
|
100
|
+
// selector engine inside the snapshot iframe at view time via injected script.
|
|
101
|
+
// Our approach resolves at collection time (same moment as snapshot) — simpler but
|
|
102
|
+
// requires Mirror plumbing. nodeId-based lookup also works across shadow DOM, unlike querySelector.
|
|
103
|
+
function takeSnapshot(serializedLocator) {
|
|
104
|
+
const { snapshot, createMirror } = getBrowserState().browserTraceDomSnapshot;
|
|
105
|
+
const traceView = getBrowserState().config.browser.traceView;
|
|
106
|
+
const engine = getBrowserState().selectorEngine;
|
|
107
|
+
const mirror = createMirror();
|
|
108
|
+
const serialized = snapshot(document, {
|
|
109
|
+
mirror,
|
|
110
|
+
inlineImages: traceView.inlineImages,
|
|
111
|
+
recordCanvas: traceView.recordCanvas
|
|
112
|
+
});
|
|
113
|
+
const result = {
|
|
114
|
+
serialized,
|
|
115
|
+
viewport: {
|
|
116
|
+
width: window.innerWidth,
|
|
117
|
+
height: window.innerHeight
|
|
118
|
+
},
|
|
119
|
+
scroll: {
|
|
120
|
+
x: window.scrollX,
|
|
121
|
+
y: window.scrollY
|
|
122
|
+
},
|
|
123
|
+
pseudoClassIds: {}
|
|
124
|
+
};
|
|
125
|
+
for (const className of PSEUDO_CLASS_NAMES) {
|
|
126
|
+
const elements = document.querySelectorAll(className);
|
|
127
|
+
const ids = Array.from(elements, (el) => mirror.getId(el)).filter((id) => id !== -1);
|
|
128
|
+
result.pseudoClassIds[className] = ids;
|
|
129
|
+
}
|
|
130
|
+
if (serializedLocator) {
|
|
131
|
+
try {
|
|
132
|
+
const el = engine.querySelector(engine.parseSelector(serializedLocator._pwSelector ?? serializedLocator.selector), document.documentElement, false);
|
|
133
|
+
if (!el) {
|
|
134
|
+
result.selectorResolution = "missing";
|
|
135
|
+
} else {
|
|
136
|
+
const id = mirror.getId(el);
|
|
137
|
+
if (id !== -1) {
|
|
138
|
+
result.selectorId = id;
|
|
139
|
+
result.selectorResolution = "matched";
|
|
140
|
+
} else {
|
|
141
|
+
result.selectorResolution = "missing";
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
} catch (error) {
|
|
145
|
+
result.selectorResolution = "error";
|
|
146
|
+
result.selectorError = error instanceof Error ? error.message : String(error);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
|
|
52
152
|
/* @__NO_SIDE_EFFECTS__ */
|
|
53
153
|
function convertElementToCssSelector(element) {
|
|
54
154
|
if (!element || !(element instanceof Element)) {
|
|
@@ -121,7 +221,6 @@ function getParent(el) {
|
|
|
121
221
|
}
|
|
122
222
|
return parent;
|
|
123
223
|
}
|
|
124
|
-
const now = globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
|
|
125
224
|
function processTimeoutOptions(options_) {
|
|
126
225
|
if (options_ && options_.timeout != null) {
|
|
127
226
|
return options_;
|
|
@@ -153,19 +252,28 @@ function processTimeoutOptions(options_) {
|
|
|
153
252
|
}
|
|
154
253
|
const provider$1 = getBrowserState().provider;
|
|
155
254
|
const kElementLocator = Symbol.for("$$vitest:locator-resolved");
|
|
156
|
-
async function
|
|
255
|
+
async function serializeElement(elementOrLocator, options) {
|
|
157
256
|
if (!elementOrLocator) {
|
|
158
257
|
throw new Error("Expected element or locator to be defined.");
|
|
159
258
|
}
|
|
160
259
|
if (elementOrLocator instanceof Element) {
|
|
161
|
-
|
|
260
|
+
const selector = convertElementToCssSelector(elementOrLocator);
|
|
261
|
+
return {
|
|
262
|
+
selector,
|
|
263
|
+
locator: __INTERNAL._asLocator("javascript", selector)
|
|
264
|
+
};
|
|
162
265
|
}
|
|
163
266
|
if (isLocator(elementOrLocator)) {
|
|
164
267
|
if (provider$1 === "playwright" || kElementLocator in elementOrLocator) {
|
|
165
|
-
return elementOrLocator.
|
|
268
|
+
return elementOrLocator.serialize();
|
|
166
269
|
}
|
|
167
270
|
const element = await elementOrLocator.findElement(options);
|
|
168
|
-
|
|
271
|
+
const selector = convertElementToCssSelector(element);
|
|
272
|
+
const locator = __INTERNAL._asLocator("javascript", selector);
|
|
273
|
+
return {
|
|
274
|
+
selector,
|
|
275
|
+
locator
|
|
276
|
+
};
|
|
169
277
|
}
|
|
170
278
|
throw new Error("Expected element or locator to be an instance of Element or Locator.");
|
|
171
279
|
}
|
|
@@ -271,9 +379,9 @@ function createUserEvent(__tl_user_event_base__, options) {
|
|
|
271
379
|
},
|
|
272
380
|
type(element, text, options) {
|
|
273
381
|
return ensureAwaited(async (error) => {
|
|
274
|
-
const
|
|
382
|
+
const serializedElement = await serializeElement(element, options);
|
|
275
383
|
const { unreleased } = await triggerCommand("__vitest_type", [
|
|
276
|
-
|
|
384
|
+
serializedElement,
|
|
277
385
|
text,
|
|
278
386
|
{
|
|
279
387
|
...options,
|
|
@@ -457,7 +565,7 @@ const page = {
|
|
|
457
565
|
screenshotIds[repeatCount] ??= {};
|
|
458
566
|
screenshotIds[repeatCount][taskName] = number + 1;
|
|
459
567
|
const name = options.path || `${taskName.replace(/[^a-z0-9]/gi, "-")}-${number}.png`;
|
|
460
|
-
const [element, ...mask] = await Promise.all([options.element ?
|
|
568
|
+
const [element, ...mask] = await Promise.all([options.element ? serializeElement(options.element, options) : undefined, ..."mask" in options ? options.mask.map((el) => serializeElement(el, options)) : []]);
|
|
461
569
|
const normalizedOptions = "mask" in options ? {
|
|
462
570
|
...options,
|
|
463
571
|
mask
|
|
@@ -473,8 +581,11 @@ const page = {
|
|
|
473
581
|
mark(name, bodyOrOptions, options) {
|
|
474
582
|
const currentTest = getWorkerState().current;
|
|
475
583
|
const hasActiveTrace = !!currentTest && getBrowserState().activeTraceTaskIds.has(currentTest.id);
|
|
584
|
+
const hasActiveTraceView = !!currentTest && getBrowserState().browserTraceAttempts.has(currentTest.id);
|
|
476
585
|
if (typeof bodyOrOptions === "function") {
|
|
477
586
|
return ensureAwaited(async (error) => {
|
|
587
|
+
let status = "pass";
|
|
588
|
+
const startTime = now();
|
|
478
589
|
if (hasActiveTrace) {
|
|
479
590
|
await triggerCommand("__vitest_groupTraceStart", [{
|
|
480
591
|
name,
|
|
@@ -483,20 +594,46 @@ const page = {
|
|
|
483
594
|
}
|
|
484
595
|
try {
|
|
485
596
|
return await bodyOrOptions();
|
|
597
|
+
} catch (err) {
|
|
598
|
+
status = "fail";
|
|
599
|
+
throw err;
|
|
486
600
|
} finally {
|
|
601
|
+
if (hasActiveTraceView) {
|
|
602
|
+
// TODO: support nested trace
|
|
603
|
+
recordBrowserTraceEntry(currentTest, {
|
|
604
|
+
name,
|
|
605
|
+
kind: "mark",
|
|
606
|
+
status,
|
|
607
|
+
startTime,
|
|
608
|
+
duration: now() - startTime,
|
|
609
|
+
stack: options?.stack ?? error?.stack
|
|
610
|
+
});
|
|
611
|
+
}
|
|
487
612
|
if (hasActiveTrace) {
|
|
488
613
|
await triggerCommand("__vitest_groupTraceEnd", [], error);
|
|
489
614
|
}
|
|
490
615
|
}
|
|
491
616
|
});
|
|
492
617
|
}
|
|
493
|
-
if (!hasActiveTrace) {
|
|
618
|
+
if (!hasActiveTrace && !hasActiveTraceView) {
|
|
494
619
|
return Promise.resolve();
|
|
495
620
|
}
|
|
496
|
-
return ensureAwaited((error) =>
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
621
|
+
return ensureAwaited((error) => {
|
|
622
|
+
if (hasActiveTraceView) {
|
|
623
|
+
recordBrowserTraceEntry(currentTest, {
|
|
624
|
+
name,
|
|
625
|
+
kind: "mark",
|
|
626
|
+
stack: bodyOrOptions?.stack ?? error?.stack
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
if (!hasActiveTrace) {
|
|
630
|
+
return Promise.resolve();
|
|
631
|
+
}
|
|
632
|
+
return triggerCommand("__vitest_markTrace", [{
|
|
633
|
+
name,
|
|
634
|
+
stack: bodyOrOptions?.stack ?? error?.stack
|
|
635
|
+
}], error);
|
|
636
|
+
});
|
|
500
637
|
},
|
|
501
638
|
getByRole() {
|
|
502
639
|
throw new Error(`Method "getByRole" is not supported by the "${provider}" provider.`);
|
|
@@ -616,7 +753,7 @@ function prettyDOM(dom, maxLength = Number(defaultOptions?.maxLength ?? import.m
|
|
|
616
753
|
return dom.outerHTML.length > maxLength ? `${pretty.slice(0, maxLength)}...` : pretty;
|
|
617
754
|
}
|
|
618
755
|
function getElementError(selector, container) {
|
|
619
|
-
const error = new Error(`Cannot find element with locator: ${__INTERNAL._asLocator("javascript", selector)}\n\n${prettyDOM(container)}`);
|
|
756
|
+
const error = new Error(`Cannot find element with locator: ${typeof selector === "string" ? __INTERNAL._asLocator("javascript", selector) : selector.asLocator()}\n\n${prettyDOM(container)}`);
|
|
620
757
|
error.name = "VitestBrowserElementError";
|
|
621
758
|
return error;
|
|
622
759
|
}
|
|
@@ -628,7 +765,10 @@ const utils = {
|
|
|
628
765
|
prettyDOM,
|
|
629
766
|
debug,
|
|
630
767
|
getElementLocatorSelectors,
|
|
631
|
-
configurePrettyDOM
|
|
768
|
+
configurePrettyDOM,
|
|
769
|
+
get aria() {
|
|
770
|
+
return getBrowserState().aria;
|
|
771
|
+
}
|
|
632
772
|
};
|
|
633
773
|
|
|
634
774
|
export { cdp, createUserEvent, locators, page, utils };
|