@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.
Files changed (31) hide show
  1. package/context.d.ts +50 -1
  2. package/dist/client/.vite/manifest.json +15 -6
  3. package/dist/client/__vitest__/assets/index-Cd6On2Pm.js +136 -0
  4. package/dist/client/__vitest__/assets/index-Dw9P28Qt.css +1 -0
  5. package/dist/client/__vitest__/index.html +2 -2
  6. package/dist/client/__vitest_browser__/{orchestrator-DM4mHHP0.js → orchestrator-BfoS0x4w.js} +55 -31
  7. package/dist/client/__vitest_browser__/rrweb-snapshot-xhvrgOHx.js +5476 -0
  8. package/dist/client/__vitest_browser__/{utils-DmkAiRYk.js → tester-BJtW9QqZ.js} +2674 -930
  9. package/dist/client/__vitest_browser__/utils-Nd8hqrhP.js +189 -0
  10. package/dist/client/orchestrator.html +2 -2
  11. package/dist/client/tester/locators.d.ts +69 -0
  12. package/dist/client/tester/tester.html +2 -2
  13. package/dist/client/tester/trace.d.ts +54 -0
  14. package/dist/client.js +1 -0
  15. package/dist/context.js +155 -15
  16. package/dist/expect-element.js +30 -30
  17. package/dist/index.d.ts +6 -22
  18. package/dist/index.js +29 -4551
  19. package/dist/locators-CesZ2RSY.js +5 -0
  20. package/dist/locators.d.ts +9 -1
  21. package/dist/locators.js +1 -1
  22. package/dist/shared/screenshotMatcher/types.d.ts +3 -3
  23. package/dist/state.js +1 -0
  24. package/dist/vendor-types.d.ts +131 -0
  25. package/dist/vendor-types.ts +1 -0
  26. package/package.json +15 -7
  27. package/dist/client/__vitest__/assets/index-BPQdrqGZ.js +0 -94
  28. package/dist/client/__vitest__/assets/index-Da0hb3oU.css +0 -1
  29. package/dist/client/__vitest__/bg.png +0 -0
  30. package/dist/client/__vitest_browser__/tester-Bf18VQr2.js +0 -2288
  31. 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-DM4mHHP0.js"></script>
30
- <link rel="modulepreload" crossorigin href="/__vitest_browser__/utils-DmkAiRYk.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-Bf18VQr2.js"></script>
9
- <link rel="modulepreload" crossorigin href="/__vitest_browser__/utils-DmkAiRYk.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>
@@ -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 convertToSelector(elementOrLocator, options) {
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
- return convertElementToCssSelector(elementOrLocator);
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.selector;
268
+ return elementOrLocator.serialize();
166
269
  }
167
270
  const element = await elementOrLocator.findElement(options);
168
- return convertElementToCssSelector(element);
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 selector = await convertToSelector(element, options);
382
+ const serializedElement = await serializeElement(element, options);
275
383
  const { unreleased } = await triggerCommand("__vitest_type", [
276
- selector,
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 ? 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)) : []]);
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) => triggerCommand("__vitest_markTrace", [{
497
- name,
498
- stack: bodyOrOptions?.stack ?? error?.stack
499
- }], error));
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 };