@vitest/browser 5.0.0-beta.1 → 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.
@@ -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-pTEf6o0n.js"></script>
30
- <link rel="modulepreload" crossorigin href="/__vitest_browser__/utils-BYUpz6v6.js">
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-CIKiUsoz.js"></script>
9
- <link rel="modulepreload" crossorigin href="/__vitest_browser__/utils-BYUpz6v6.js">
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>
@@ -1,4 +1,5 @@
1
1
  import type { Task } from "@vitest/runner";
2
+ import type { SerializedLocator } from "./locators.js";
2
3
  export interface BrowserTraceData {
3
4
  retry: number;
4
5
  repeats: number;
@@ -20,7 +21,7 @@ export interface BrowserTraceEntry {
20
21
  line: number;
21
22
  column: number;
22
23
  };
23
- selector?: string;
24
+ element?: SerializedLocator;
24
25
  snapshot: TraceSnapshot;
25
26
  }
26
27
  interface TraceSnapshot {
package/dist/context.js CHANGED
@@ -74,19 +74,9 @@ function getTraceStateKey(testId, repeats, retry) {
74
74
  }
75
75
  // TODO: should we avoid accumulating? send and immediately clear each entry to save memory?
76
76
  function recordBrowserTraceEntry(task, options) {
77
- // TODO: trace-view currently receives selectors after locator/action resolution,
78
- // so provider-specific lowered selectors can leak into snapshot lookup. Preserve
79
- // the original locator selector, or record the target node id before lowering.
80
- // for example, this causes shadow dom selectors to fail with `>>>` marker.
81
- // For now, remove trivial `html >` prefix generated by convertElementToCssSelector.
82
- // this is also necessary to `engine.querySelector + document.documentElement`
83
- // to find an element on webdriverio
84
- if (options.selector?.startsWith("html >")) {
85
- options.selector = options.selector.slice(6);
86
- }
87
77
  const attemptInfo = getBrowserState().browserTraceAttempts.get(task.id);
88
78
  const relativeStartTime = (options.startTime ?? now()) - attemptInfo.startTime;
89
- const snapshot = takeSnapshot(options.selector);
79
+ const snapshot = takeSnapshot(options.element);
90
80
  const entry = {
91
81
  ...options,
92
82
  startTime: relativeStartTime,
@@ -110,7 +100,7 @@ function recordBrowserTraceEntry(task, options) {
110
100
  // selector engine inside the snapshot iframe at view time via injected script.
111
101
  // Our approach resolves at collection time (same moment as snapshot) — simpler but
112
102
  // requires Mirror plumbing. nodeId-based lookup also works across shadow DOM, unlike querySelector.
113
- function takeSnapshot(selector) {
103
+ function takeSnapshot(serializedLocator) {
114
104
  const { snapshot, createMirror } = getBrowserState().browserTraceDomSnapshot;
115
105
  const traceView = getBrowserState().config.browser.traceView;
116
106
  const engine = getBrowserState().selectorEngine;
@@ -137,9 +127,9 @@ function takeSnapshot(selector) {
137
127
  const ids = Array.from(elements, (el) => mirror.getId(el)).filter((id) => id !== -1);
138
128
  result.pseudoClassIds[className] = ids;
139
129
  }
140
- if (selector) {
130
+ if (serializedLocator) {
141
131
  try {
142
- const el = engine.querySelector(engine.parseSelector(selector), document.documentElement, false);
132
+ const el = engine.querySelector(engine.parseSelector(serializedLocator._pwSelector ?? serializedLocator.selector), document.documentElement, false);
143
133
  if (!el) {
144
134
  result.selectorResolution = "missing";
145
135
  } else {
@@ -262,19 +252,28 @@ function processTimeoutOptions(options_) {
262
252
  }
263
253
  const provider$1 = getBrowserState().provider;
264
254
  const kElementLocator = Symbol.for("$$vitest:locator-resolved");
265
- async function convertToSelector(elementOrLocator, options) {
255
+ async function serializeElement(elementOrLocator, options) {
266
256
  if (!elementOrLocator) {
267
257
  throw new Error("Expected element or locator to be defined.");
268
258
  }
269
259
  if (elementOrLocator instanceof Element) {
270
- return convertElementToCssSelector(elementOrLocator);
260
+ const selector = convertElementToCssSelector(elementOrLocator);
261
+ return {
262
+ selector,
263
+ locator: __INTERNAL._asLocator("javascript", selector)
264
+ };
271
265
  }
272
266
  if (isLocator(elementOrLocator)) {
273
267
  if (provider$1 === "playwright" || kElementLocator in elementOrLocator) {
274
- return elementOrLocator.selector;
268
+ return elementOrLocator.serialize();
275
269
  }
276
270
  const element = await elementOrLocator.findElement(options);
277
- return convertElementToCssSelector(element);
271
+ const selector = convertElementToCssSelector(element);
272
+ const locator = __INTERNAL._asLocator("javascript", selector);
273
+ return {
274
+ selector,
275
+ locator
276
+ };
278
277
  }
279
278
  throw new Error("Expected element or locator to be an instance of Element or Locator.");
280
279
  }
@@ -380,9 +379,9 @@ function createUserEvent(__tl_user_event_base__, options) {
380
379
  },
381
380
  type(element, text, options) {
382
381
  return ensureAwaited(async (error) => {
383
- const selector = await convertToSelector(element, options);
382
+ const serializedElement = await serializeElement(element, options);
384
383
  const { unreleased } = await triggerCommand("__vitest_type", [
385
- selector,
384
+ serializedElement,
386
385
  text,
387
386
  {
388
387
  ...options,
@@ -566,7 +565,7 @@ const page = {
566
565
  screenshotIds[repeatCount] ??= {};
567
566
  screenshotIds[repeatCount][taskName] = number + 1;
568
567
  const name = options.path || `${taskName.replace(/[^a-z0-9]/gi, "-")}-${number}.png`;
569
- const [element, ...mask] = await Promise.all([options.element ? convertToSelector(options.element, options) : undefined, ..."mask" in options ? options.mask.map((el) => convertToSelector(el, options)) : []]);
568
+ const [element, ...mask] = await Promise.all([options.element ? serializeElement(options.element, options) : undefined, ..."mask" in options ? options.mask.map((el) => serializeElement(el, options)) : []]);
570
569
  const normalizedOptions = "mask" in options ? {
571
570
  ...options,
572
571
  mask
@@ -754,7 +753,7 @@ function prettyDOM(dom, maxLength = Number(defaultOptions?.maxLength ?? import.m
754
753
  return dom.outerHTML.length > maxLength ? `${pretty.slice(0, maxLength)}...` : pretty;
755
754
  }
756
755
  function getElementError(selector, container) {
757
- 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)}`);
758
757
  error.name = "VitestBrowserElementError";
759
758
  return error;
760
759
  }