@quitecode/chromium-automaton 0.1.0-beta.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 ADDED
@@ -0,0 +1,118 @@
1
+ # Chromium Automaton
2
+
3
+ [![ci](https://github.com/quitecode9-lab/chromium-automation/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/quitecode9-lab/chromium-automation/actions/workflows/ci.yml)
4
+
5
+ Chromium Automaton is a lightweight, Chromium-only browser automation library built directly on the Chrome DevTools Protocol (CDP). It offers a Playwright-style API without bundling any runner, fixtures, or reporting.
6
+
7
+ ## What it is
8
+ - A small CDP client with a focused API for Chromium automation
9
+ - A CLI for downloading and caching Chromium snapshots
10
+ - Runner-agnostic assertions you can plug into any test setup
11
+
12
+ ## What it is not
13
+ - Not a framework, runner, or test harness
14
+ - Not multi-browser (Chromium only)
15
+ - Not a report generator (no built-in reporting)
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ npm install @quitecode/chromium-automaton
21
+ ```
22
+
23
+ ## Download Chromium
24
+
25
+ ```bash
26
+ npx chromium-automaton download
27
+ npx chromium-automaton download --latest
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ```ts
33
+ import { chromium, expect } from "@quitecode/chromium-automaton";
34
+
35
+ const browser = await chromium.launch({ headless: true });
36
+ const page = await browser.newPage();
37
+
38
+ await page.goto("https://example.com", { waitUntil: "load" });
39
+ await page.click("h1");
40
+ const screenshotBase64 = await page.screenshotBase64();
41
+
42
+ await expect(page).element("h1").toHaveText(/Example Domain/);
43
+
44
+ await browser.close();
45
+ ```
46
+
47
+ ## Documentation Site
48
+
49
+ Docs are built with VitePress and published to GitHub Pages:
50
+ https://quitecode9-lab.github.io/chromium-automation/
51
+
52
+ ## Architecture
53
+
54
+ ```mermaid
55
+ graph LR
56
+ CLI[CLI: chromium-automaton download] --> Downloader[Downloader]
57
+ Downloader --> Cache[Chromium Cache]
58
+ User[User Code] --> API[chromium.launch]
59
+ API --> Manager[ChromiumManager]
60
+ Manager --> Chromium[Chromium Process]
61
+ Manager --> Conn[CDP Connection]
62
+ Conn --> Browser[Browser]
63
+ Browser --> Page[Page]
64
+ Page --> Frame[Frame]
65
+ Page --> Locator[Locator]
66
+ Page --> Expect[expect]
67
+ ```
68
+
69
+ ## Locators
70
+
71
+ ```ts
72
+ const locator = page.locator("#login", { pierceShadowDom: true });
73
+ await locator.click();
74
+ await locator.type("admin");
75
+ ```
76
+
77
+ ## Frames
78
+
79
+ ```ts
80
+ const frame = page.frame({ urlIncludes: "embedded" });
81
+ if (frame) {
82
+ await frame.click("button.submit");
83
+ }
84
+ ```
85
+
86
+ ## Shadow DOM
87
+
88
+ ```ts
89
+ await page.click("button.action", { pierceShadowDom: true });
90
+ const text = await page.evaluate(() => document.title);
91
+ ```
92
+
93
+ ## Assertions
94
+
95
+ ```ts
96
+ await expect(page).element(".ready").toExist();
97
+ await expect(page).element(".hidden").not.toBeVisible();
98
+ await expect(page).element("h1").toHaveText("Example Domain");
99
+ ```
100
+
101
+ ## Environment configuration
102
+ - `CHROMIUM_AUTOMATON_CACHE_DIR`: override cache root
103
+ - `CHROMIUM_AUTOMATON_REVISION`: override pinned revision
104
+ - `CHROMIUM_AUTOMATON_EXECUTABLE_PATH`: bypass download and use this executable
105
+ - `CHROMIUM_AUTOMATON_LOG_LEVEL`: error, warn, info, debug, trace
106
+
107
+ Default cache root:
108
+ - Linux and macOS: `~/.cache/chromium-automaton`
109
+ - Windows: `%LOCALAPPDATA%\chromium-automaton`
110
+
111
+ ## Limitations
112
+ - Chromium only (no Chrome, Firefox, WebKit)
113
+ - XPath selectors do not pierce shadow DOM
114
+ - `evaluate(string)` is unsafe if the string includes untrusted input
115
+ - Only http/https are allowed in `goto` unless `allowFileUrl` is true
116
+
117
+ ## ESM
118
+ This package is ESM-only. Use `import` syntax in Node 18+.
@@ -0,0 +1 @@
1
+ export { E as ExpectSelectorOptions, e as expect } from '../expect-CBZIoH1D.js';
@@ -0,0 +1,7 @@
1
+ import {
2
+ expect
3
+ } from "../chunk-ADOXZ36A.js";
4
+ export {
5
+ expect
6
+ };
7
+ //# sourceMappingURL=expect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,292 @@
1
+ // src/assert/AssertionError.ts
2
+ var AssertionError = class extends Error {
3
+ selector;
4
+ timeoutMs;
5
+ lastState;
6
+ constructor(message, options = {}) {
7
+ super(message);
8
+ this.name = "AssertionError";
9
+ this.selector = options.selector;
10
+ this.timeoutMs = options.timeoutMs;
11
+ this.lastState = options.lastState;
12
+ }
13
+ };
14
+
15
+ // src/core/Waiter.ts
16
+ async function waitFor(predicate, options = {}) {
17
+ const timeoutMs = options.timeoutMs ?? 3e4;
18
+ const intervalMs = options.intervalMs ?? 100;
19
+ const start = Date.now();
20
+ let lastError;
21
+ while (Date.now() - start < timeoutMs) {
22
+ try {
23
+ const result = await predicate();
24
+ if (result) {
25
+ return result;
26
+ }
27
+ } catch (err) {
28
+ lastError = err;
29
+ }
30
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
31
+ }
32
+ const description = options.description ? ` (${options.description})` : "";
33
+ const error = new Error(`Timeout after ${timeoutMs}ms${description}`);
34
+ error.cause = lastError;
35
+ throw error;
36
+ }
37
+
38
+ // src/assert/expect.ts
39
+ var ElementExpectation = class _ElementExpectation {
40
+ frame;
41
+ selector;
42
+ options;
43
+ negate;
44
+ events;
45
+ constructor(frame, selector, options, negate, events) {
46
+ this.frame = frame;
47
+ this.selector = selector;
48
+ this.options = options;
49
+ this.negate = negate;
50
+ this.events = events;
51
+ }
52
+ get not() {
53
+ return new _ElementExpectation(this.frame, this.selector, this.options, !this.negate, this.events);
54
+ }
55
+ async toExist() {
56
+ return this.assert(async () => {
57
+ const exists = await this.frame.exists(this.selector, this.options);
58
+ return this.negate ? !exists : exists;
59
+ }, this.negate ? "Expected element not to exist" : "Expected element to exist");
60
+ }
61
+ async toBeVisible() {
62
+ return this.assert(async () => {
63
+ const visible = await this.frame.isVisible(this.selector, this.options);
64
+ return this.negate ? !visible : visible;
65
+ }, this.negate ? "Expected element not to be visible" : "Expected element to be visible");
66
+ }
67
+ async toBeHidden() {
68
+ return this.assert(async () => {
69
+ const visible = await this.frame.isVisible(this.selector, this.options);
70
+ return this.negate ? visible : !visible;
71
+ }, this.negate ? "Expected element not to be hidden" : "Expected element to be hidden");
72
+ }
73
+ async toBeEnabled() {
74
+ return this.assert(async () => {
75
+ const enabled = await this.frame.isEnabled(this.selector, this.options);
76
+ if (enabled == null) {
77
+ return this.negate ? true : false;
78
+ }
79
+ return this.negate ? !enabled : enabled;
80
+ }, this.negate ? "Expected element not to be enabled" : "Expected element to be enabled");
81
+ }
82
+ async toBeDisabled() {
83
+ return this.assert(async () => {
84
+ const enabled = await this.frame.isEnabled(this.selector, this.options);
85
+ if (enabled == null) {
86
+ return this.negate ? true : false;
87
+ }
88
+ const disabled = !enabled;
89
+ return this.negate ? !disabled : disabled;
90
+ }, this.negate ? "Expected element not to be disabled" : "Expected element to be disabled");
91
+ }
92
+ async toBeChecked() {
93
+ return this.assert(async () => {
94
+ const checked = await this.frame.isChecked(this.selector, this.options);
95
+ if (checked == null) {
96
+ return this.negate ? true : false;
97
+ }
98
+ return this.negate ? !checked : checked;
99
+ }, this.negate ? "Expected element not to be checked" : "Expected element to be checked");
100
+ }
101
+ async toBeUnchecked() {
102
+ return this.assert(async () => {
103
+ const checked = await this.frame.isChecked(this.selector, this.options);
104
+ if (checked == null) {
105
+ return this.negate ? true : false;
106
+ }
107
+ const unchecked = !checked;
108
+ return this.negate ? !unchecked : unchecked;
109
+ }, this.negate ? "Expected element not to be unchecked" : "Expected element to be unchecked");
110
+ }
111
+ async toHaveText(textOrRegex) {
112
+ const expected = textOrRegex;
113
+ return this.assert(async () => {
114
+ const text = await this.frame.text(this.selector, this.options);
115
+ if (text == null) {
116
+ return this.negate ? true : false;
117
+ }
118
+ const matches = expected instanceof RegExp ? new RegExp(expected.source, expected.flags.replace("g", "")).test(text) : text.includes(expected);
119
+ return this.negate ? !matches : matches;
120
+ }, this.negate ? "Expected element text not to match" : "Expected element text to match", { expected });
121
+ }
122
+ async toHaveExactText(textOrRegex) {
123
+ const expected = textOrRegex;
124
+ return this.assert(async () => {
125
+ const text = await this.frame.text(this.selector, this.options);
126
+ if (text == null) {
127
+ return this.negate ? true : false;
128
+ }
129
+ const matches = expected instanceof RegExp ? new RegExp(expected.source, expected.flags.replace("g", "")).test(text) : text === expected;
130
+ return this.negate ? !matches : matches;
131
+ }, this.negate ? "Expected element text not to match exactly" : "Expected element text to match exactly", { expected });
132
+ }
133
+ async toContainText(textOrRegex) {
134
+ const expected = textOrRegex;
135
+ return this.assert(async () => {
136
+ const text = await this.frame.text(this.selector, this.options);
137
+ if (text == null) {
138
+ return this.negate ? true : false;
139
+ }
140
+ const matches = expected instanceof RegExp ? new RegExp(expected.source, expected.flags.replace("g", "")).test(text) : text.includes(expected);
141
+ return this.negate ? !matches : matches;
142
+ }, this.negate ? "Expected element text not to contain" : "Expected element text to contain", { expected });
143
+ }
144
+ async toHaveValue(valueOrRegex) {
145
+ const expected = valueOrRegex;
146
+ return this.assert(async () => {
147
+ const value = await this.frame.value(this.selector, this.options);
148
+ if (value == null) {
149
+ return this.negate ? true : false;
150
+ }
151
+ const matches = expected instanceof RegExp ? new RegExp(expected.source, expected.flags.replace("g", "")).test(value) : value === expected;
152
+ return this.negate ? !matches : matches;
153
+ }, this.negate ? "Expected element value not to match" : "Expected element value to match", { expected });
154
+ }
155
+ async toHaveAttribute(name, valueOrRegex) {
156
+ const expected = valueOrRegex;
157
+ return this.assert(async () => {
158
+ const value = await this.frame.attribute(this.selector, name, this.options);
159
+ if (expected === void 0) {
160
+ const exists = value != null;
161
+ return this.negate ? !exists : exists;
162
+ }
163
+ if (value == null) {
164
+ return this.negate ? true : false;
165
+ }
166
+ const matches = expected instanceof RegExp ? new RegExp(expected.source, expected.flags.replace("g", "")).test(value) : value === expected;
167
+ return this.negate ? !matches : matches;
168
+ }, this.negate ? "Expected element attribute not to match" : "Expected element attribute to match", { expected, name });
169
+ }
170
+ async toHaveId(idOrRegex) {
171
+ return this.toHaveAttribute("id", idOrRegex);
172
+ }
173
+ async toHaveName(nameOrRegex) {
174
+ return this.toHaveAttribute("name", nameOrRegex);
175
+ }
176
+ async toHaveCount(expected) {
177
+ return this.assert(async () => {
178
+ const count = await this.frame.count(this.selector, this.options);
179
+ const matches = count === expected;
180
+ return this.negate ? !matches : matches;
181
+ }, this.negate ? "Expected element count not to match" : "Expected element count to match", { expected });
182
+ }
183
+ async toHaveClass(nameOrRegex) {
184
+ const expected = nameOrRegex;
185
+ return this.assert(async () => {
186
+ const classes = await this.frame.classes(this.selector, this.options);
187
+ if (classes == null) {
188
+ return this.negate ? true : false;
189
+ }
190
+ const matches = expected instanceof RegExp ? classes.some((value) => new RegExp(expected.source, expected.flags.replace("g", "")).test(value)) : classes.includes(expected);
191
+ return this.negate ? !matches : matches;
192
+ }, this.negate ? "Expected element class not to match" : "Expected element class to match", { expected });
193
+ }
194
+ async toHaveClasses(expected) {
195
+ return this.assert(async () => {
196
+ const classes = await this.frame.classes(this.selector, this.options);
197
+ if (classes == null) {
198
+ return this.negate ? true : false;
199
+ }
200
+ const matches = expected.every((value) => classes.includes(value));
201
+ return this.negate ? !matches : matches;
202
+ }, this.negate ? "Expected element classes not to match" : "Expected element classes to match", { expected });
203
+ }
204
+ async toHaveCss(property, valueOrRegex) {
205
+ const expected = valueOrRegex;
206
+ return this.assert(async () => {
207
+ const value = await this.frame.css(this.selector, property, this.options);
208
+ if (value == null) {
209
+ return this.negate ? true : false;
210
+ }
211
+ const actual = value.trim();
212
+ const matches = expected instanceof RegExp ? new RegExp(expected.source, expected.flags.replace("g", "")).test(actual) : actual === expected;
213
+ return this.negate ? !matches : matches;
214
+ }, this.negate ? "Expected element css not to match" : "Expected element css to match", { expected, property });
215
+ }
216
+ async toHaveFocus() {
217
+ return this.assert(async () => {
218
+ const focused = await this.frame.hasFocus(this.selector, this.options);
219
+ if (focused == null) {
220
+ return this.negate ? true : false;
221
+ }
222
+ return this.negate ? !focused : focused;
223
+ }, this.negate ? "Expected element not to have focus" : "Expected element to have focus");
224
+ }
225
+ async toBeInViewport(options = {}) {
226
+ return this.assert(async () => {
227
+ const inViewport = await this.frame.isInViewport(this.selector, this.options, Boolean(options.fully));
228
+ if (inViewport == null) {
229
+ return this.negate ? true : false;
230
+ }
231
+ return this.negate ? !inViewport : inViewport;
232
+ }, this.negate ? "Expected element not to be in viewport" : "Expected element to be in viewport");
233
+ }
234
+ async toBeEditable() {
235
+ return this.assert(async () => {
236
+ const editable = await this.frame.isEditable(this.selector, this.options);
237
+ if (editable == null) {
238
+ return this.negate ? true : false;
239
+ }
240
+ return this.negate ? !editable : editable;
241
+ }, this.negate ? "Expected element not to be editable" : "Expected element to be editable");
242
+ }
243
+ async assert(predicate, message, details = {}) {
244
+ const timeoutMs = this.options.timeoutMs ?? 3e4;
245
+ const start = Date.now();
246
+ this.events.emit("assertion:start", { name: message, selector: this.selector, frameId: this.frame.id });
247
+ let lastState;
248
+ try {
249
+ await waitFor(async () => {
250
+ const result = await predicate();
251
+ lastState = result;
252
+ return result;
253
+ }, { timeoutMs, description: message });
254
+ } catch {
255
+ const duration2 = Date.now() - start;
256
+ this.events.emit("assertion:end", { name: message, selector: this.selector, frameId: this.frame.id, durationMs: duration2 });
257
+ throw new AssertionError(message, { selector: this.selector, timeoutMs, lastState: { lastState, ...details } });
258
+ }
259
+ const duration = Date.now() - start;
260
+ this.events.emit("assertion:end", { name: message, selector: this.selector, frameId: this.frame.id, durationMs: duration });
261
+ }
262
+ };
263
+ var ExpectFrame = class {
264
+ frame;
265
+ events;
266
+ constructor(frame, events) {
267
+ this.frame = frame;
268
+ this.events = events;
269
+ }
270
+ element(selector, options = {}) {
271
+ return new ElementExpectation(this.frame, selector, options, false, this.events);
272
+ }
273
+ };
274
+ function expect(page) {
275
+ return {
276
+ element: (selector, options = {}) => new ElementExpectation(page.mainFrame(), selector, options, false, page.getEvents()),
277
+ frame: (options) => {
278
+ const frame = page.frame(options);
279
+ if (!frame) {
280
+ throw new AssertionError("Frame not found", { selector: JSON.stringify(options) });
281
+ }
282
+ return new ExpectFrame(frame, page.getEvents());
283
+ }
284
+ };
285
+ }
286
+
287
+ export {
288
+ waitFor,
289
+ AssertionError,
290
+ expect
291
+ };
292
+ //# sourceMappingURL=chunk-ADOXZ36A.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/assert/AssertionError.ts","../src/core/Waiter.ts","../src/assert/expect.ts"],"sourcesContent":["export class AssertionError extends Error {\n selector?: string;\n timeoutMs?: number;\n lastState?: unknown;\n\n constructor(message: string, options: { selector?: string; timeoutMs?: number; lastState?: unknown } = {}) {\n super(message);\n this.name = \"AssertionError\";\n this.selector = options.selector;\n this.timeoutMs = options.timeoutMs;\n this.lastState = options.lastState;\n }\n}\n","export type WaitForOptions = {\n timeoutMs?: number;\n intervalMs?: number;\n description?: string;\n};\n\nexport async function waitFor<T>(predicate: () => Promise<T> | T, options: WaitForOptions = {}): Promise<NonNullable<T>> {\n const timeoutMs = options.timeoutMs ?? 30_000;\n const intervalMs = options.intervalMs ?? 100;\n const start = Date.now();\n\n let lastError: unknown;\n while (Date.now() - start < timeoutMs) {\n try {\n const result = await predicate();\n if (result) {\n return result as NonNullable<T>;\n }\n } catch (err) {\n lastError = err;\n }\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n\n const description = options.description ? ` (${options.description})` : \"\";\n const error = new Error(`Timeout after ${timeoutMs}ms${description}`);\n (error as Error & { cause?: unknown }).cause = lastError;\n throw error;\n}\n","import { AssertionError } from \"./AssertionError.js\";\nimport { waitFor } from \"../core/Waiter.js\";\nimport { Page } from \"../core/Page.js\";\nimport { Frame } from \"../core/Frame.js\";\nimport { AutomationEvents } from \"../core/Events.js\";\n\nexport type ExpectSelectorOptions = {\n timeoutMs?: number;\n pierceShadowDom?: boolean;\n};\n\nclass ElementExpectation {\n private frame: Frame;\n private selector: string;\n private options: ExpectSelectorOptions;\n private negate: boolean;\n private events: AutomationEvents;\n\n constructor(frame: Frame, selector: string, options: ExpectSelectorOptions, negate: boolean, events: AutomationEvents) {\n this.frame = frame;\n this.selector = selector;\n this.options = options;\n this.negate = negate;\n this.events = events;\n }\n\n get not() {\n return new ElementExpectation(this.frame, this.selector, this.options, !this.negate, this.events);\n }\n\n async toExist() {\n return this.assert(async () => {\n const exists = await this.frame.exists(this.selector, this.options);\n return this.negate ? !exists : exists;\n }, this.negate ? \"Expected element not to exist\" : \"Expected element to exist\");\n }\n\n async toBeVisible() {\n return this.assert(async () => {\n const visible = await this.frame.isVisible(this.selector, this.options);\n return this.negate ? !visible : visible;\n }, this.negate ? \"Expected element not to be visible\" : \"Expected element to be visible\");\n }\n\n async toBeHidden() {\n return this.assert(async () => {\n const visible = await this.frame.isVisible(this.selector, this.options);\n return this.negate ? visible : !visible;\n }, this.negate ? \"Expected element not to be hidden\" : \"Expected element to be hidden\");\n }\n\n async toBeEnabled() {\n return this.assert(async () => {\n const enabled = await this.frame.isEnabled(this.selector, this.options);\n if (enabled == null) {\n return this.negate ? true : false;\n }\n return this.negate ? !enabled : enabled;\n }, this.negate ? \"Expected element not to be enabled\" : \"Expected element to be enabled\");\n }\n\n async toBeDisabled() {\n return this.assert(async () => {\n const enabled = await this.frame.isEnabled(this.selector, this.options);\n if (enabled == null) {\n return this.negate ? true : false;\n }\n const disabled = !enabled;\n return this.negate ? !disabled : disabled;\n }, this.negate ? \"Expected element not to be disabled\" : \"Expected element to be disabled\");\n }\n\n async toBeChecked() {\n return this.assert(async () => {\n const checked = await this.frame.isChecked(this.selector, this.options);\n if (checked == null) {\n return this.negate ? true : false;\n }\n return this.negate ? !checked : checked;\n }, this.negate ? \"Expected element not to be checked\" : \"Expected element to be checked\");\n }\n\n async toBeUnchecked() {\n return this.assert(async () => {\n const checked = await this.frame.isChecked(this.selector, this.options);\n if (checked == null) {\n return this.negate ? true : false;\n }\n const unchecked = !checked;\n return this.negate ? !unchecked : unchecked;\n }, this.negate ? \"Expected element not to be unchecked\" : \"Expected element to be unchecked\");\n }\n\n async toHaveText(textOrRegex: string | RegExp) {\n const expected = textOrRegex;\n return this.assert(async () => {\n const text = await this.frame.text(this.selector, this.options);\n if (text == null) {\n return this.negate ? true : false;\n }\n const matches = expected instanceof RegExp\n ? new RegExp(expected.source, expected.flags.replace(\"g\", \"\")).test(text)\n : text.includes(expected);\n return this.negate ? !matches : matches;\n }, this.negate ? \"Expected element text not to match\" : \"Expected element text to match\", { expected });\n }\n\n async toHaveExactText(textOrRegex: string | RegExp) {\n const expected = textOrRegex;\n return this.assert(async () => {\n const text = await this.frame.text(this.selector, this.options);\n if (text == null) {\n return this.negate ? true : false;\n }\n const matches = expected instanceof RegExp\n ? new RegExp(expected.source, expected.flags.replace(\"g\", \"\")).test(text)\n : text === expected;\n return this.negate ? !matches : matches;\n }, this.negate ? \"Expected element text not to match exactly\" : \"Expected element text to match exactly\", { expected });\n }\n\n async toContainText(textOrRegex: string | RegExp) {\n const expected = textOrRegex;\n return this.assert(async () => {\n const text = await this.frame.text(this.selector, this.options);\n if (text == null) {\n return this.negate ? true : false;\n }\n const matches = expected instanceof RegExp\n ? new RegExp(expected.source, expected.flags.replace(\"g\", \"\")).test(text)\n : text.includes(expected);\n return this.negate ? !matches : matches;\n }, this.negate ? \"Expected element text not to contain\" : \"Expected element text to contain\", { expected });\n }\n\n async toHaveValue(valueOrRegex: string | RegExp) {\n const expected = valueOrRegex;\n return this.assert(async () => {\n const value = await this.frame.value(this.selector, this.options);\n if (value == null) {\n return this.negate ? true : false;\n }\n const matches = expected instanceof RegExp\n ? new RegExp(expected.source, expected.flags.replace(\"g\", \"\")).test(value)\n : value === expected;\n return this.negate ? !matches : matches;\n }, this.negate ? \"Expected element value not to match\" : \"Expected element value to match\", { expected });\n }\n\n async toHaveAttribute(name: string, valueOrRegex?: string | RegExp) {\n const expected = valueOrRegex;\n return this.assert(async () => {\n const value = await this.frame.attribute(this.selector, name, this.options);\n if (expected === undefined) {\n const exists = value != null;\n return this.negate ? !exists : exists;\n }\n if (value == null) {\n return this.negate ? true : false;\n }\n const matches = expected instanceof RegExp\n ? new RegExp(expected.source, expected.flags.replace(\"g\", \"\")).test(value)\n : value === expected;\n return this.negate ? !matches : matches;\n }, this.negate ? \"Expected element attribute not to match\" : \"Expected element attribute to match\", { expected, name });\n }\n\n async toHaveId(idOrRegex: string | RegExp) {\n return this.toHaveAttribute(\"id\", idOrRegex);\n }\n\n async toHaveName(nameOrRegex: string | RegExp) {\n return this.toHaveAttribute(\"name\", nameOrRegex);\n }\n\n async toHaveCount(expected: number) {\n return this.assert(async () => {\n const count = await this.frame.count(this.selector, this.options);\n const matches = count === expected;\n return this.negate ? !matches : matches;\n }, this.negate ? \"Expected element count not to match\" : \"Expected element count to match\", { expected });\n }\n\n async toHaveClass(nameOrRegex: string | RegExp) {\n const expected = nameOrRegex;\n return this.assert(async () => {\n const classes = await this.frame.classes(this.selector, this.options);\n if (classes == null) {\n return this.negate ? true : false;\n }\n const matches = expected instanceof RegExp\n ? classes.some((value) => new RegExp(expected.source, expected.flags.replace(\"g\", \"\")).test(value))\n : classes.includes(expected);\n return this.negate ? !matches : matches;\n }, this.negate ? \"Expected element class not to match\" : \"Expected element class to match\", { expected });\n }\n\n async toHaveClasses(expected: string[]) {\n return this.assert(async () => {\n const classes = await this.frame.classes(this.selector, this.options);\n if (classes == null) {\n return this.negate ? true : false;\n }\n const matches = expected.every((value) => classes.includes(value));\n return this.negate ? !matches : matches;\n }, this.negate ? \"Expected element classes not to match\" : \"Expected element classes to match\", { expected });\n }\n\n async toHaveCss(property: string, valueOrRegex: string | RegExp) {\n const expected = valueOrRegex;\n return this.assert(async () => {\n const value = await this.frame.css(this.selector, property, this.options);\n if (value == null) {\n return this.negate ? true : false;\n }\n const actual = value.trim();\n const matches = expected instanceof RegExp\n ? new RegExp(expected.source, expected.flags.replace(\"g\", \"\")).test(actual)\n : actual === expected;\n return this.negate ? !matches : matches;\n }, this.negate ? \"Expected element css not to match\" : \"Expected element css to match\", { expected, property });\n }\n\n async toHaveFocus() {\n return this.assert(async () => {\n const focused = await this.frame.hasFocus(this.selector, this.options);\n if (focused == null) {\n return this.negate ? true : false;\n }\n return this.negate ? !focused : focused;\n }, this.negate ? \"Expected element not to have focus\" : \"Expected element to have focus\");\n }\n\n async toBeInViewport(options: { fully?: boolean } = {}) {\n return this.assert(async () => {\n const inViewport = await this.frame.isInViewport(this.selector, this.options, Boolean(options.fully));\n if (inViewport == null) {\n return this.negate ? true : false;\n }\n return this.negate ? !inViewport : inViewport;\n }, this.negate ? \"Expected element not to be in viewport\" : \"Expected element to be in viewport\");\n }\n\n async toBeEditable() {\n return this.assert(async () => {\n const editable = await this.frame.isEditable(this.selector, this.options);\n if (editable == null) {\n return this.negate ? true : false;\n }\n return this.negate ? !editable : editable;\n }, this.negate ? \"Expected element not to be editable\" : \"Expected element to be editable\");\n }\n\n private async assert(predicate: () => Promise<boolean>, message: string, details: Record<string, unknown> = {}) {\n const timeoutMs = this.options.timeoutMs ?? 30_000;\n const start = Date.now();\n this.events.emit(\"assertion:start\", { name: message, selector: this.selector, frameId: this.frame.id });\n\n let lastState: unknown;\n try {\n await waitFor(async () => {\n const result = await predicate();\n lastState = result;\n return result;\n }, { timeoutMs, description: message });\n } catch {\n const duration = Date.now() - start;\n this.events.emit(\"assertion:end\", { name: message, selector: this.selector, frameId: this.frame.id, durationMs: duration });\n throw new AssertionError(message, { selector: this.selector, timeoutMs, lastState: { lastState, ...details } });\n }\n\n const duration = Date.now() - start;\n this.events.emit(\"assertion:end\", { name: message, selector: this.selector, frameId: this.frame.id, durationMs: duration });\n }\n}\n\nclass ExpectFrame {\n private frame: Frame;\n private events: AutomationEvents;\n\n constructor(frame: Frame, events: AutomationEvents) {\n this.frame = frame;\n this.events = events;\n }\n\n element(selector: string, options: ExpectSelectorOptions = {}) {\n return new ElementExpectation(this.frame, selector, options, false, this.events);\n }\n}\n\nexport function expect(page: Page) {\n return {\n element: (selector: string, options: ExpectSelectorOptions = {}) => new ElementExpectation(page.mainFrame(), selector, options, false, page.getEvents()),\n frame: (options: { name?: string; urlIncludes?: string; predicate?: (frame: Frame) => boolean }) => {\n const frame = page.frame(options);\n if (!frame) {\n throw new AssertionError(\"Frame not found\", { selector: JSON.stringify(options) });\n }\n return new ExpectFrame(frame, page.getEvents());\n }\n };\n}\n"],"mappings":"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","names":["duration"]}