@molecule/app-e2e-fixtures-default 1.0.0 → 1.0.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/dist/page.js ADDED
@@ -0,0 +1,1052 @@
1
+ /**
2
+ * A Playwright-shaped `Page` built on an {@link E2ETransport} — the driver
3
+ * half of the runtime in `runtime.ts`. Bonds that can only run code inside a
4
+ * page (the live preview) get the whole documented subset from this file.
5
+ *
6
+ * @module
7
+ */
8
+ import { E2EStrictModeError, E2ETimeoutError, E2EUnsupportedError } from '@molecule/app-e2e';
9
+ import { installE2ERuntime } from './runtime.js';
10
+ /** Brand on locators created here (the `expect` wrapper routes on it). */
11
+ export const E2E_LOCATOR = Symbol.for('molecule.e2e.locator');
12
+ /** Brand on pages created here. */
13
+ export const E2E_PAGE = Symbol.for('molecule.e2e.page');
14
+ const RUNTIME_SOURCE = String(installE2ERuntime);
15
+ const CALL_SOURCE = '(r) => { const g = globalThis; if (!g.__molE2E) return { __needInstall: true }; return g.__molE2E.call(r) }';
16
+ const toMatch = (value, opts) => value instanceof RegExp
17
+ ? { re: { source: value.source, flags: value.flags } }
18
+ : { s: value, exact: opts?.exact, ignoreCase: opts?.ignoreCase };
19
+ /** Full-text match (expect's toHaveText): a string must equal the whole text; a RegExp tests it. */
20
+ const toFull = (value, opts) => value instanceof RegExp
21
+ ? { re: { source: value.source, flags: value.flags } }
22
+ : { s: value, exact: true, ignoreCase: opts?.ignoreCase };
23
+ const fnSource = (fn) => {
24
+ if (typeof fn === 'function')
25
+ return fn.toString();
26
+ const s = String(fn);
27
+ return /^\s*(async\s+)?(function\b|\([^)]*\)\s*=>|[\w$]+\s*=>)/.test(s) ? s : `() => (${s})`;
28
+ };
29
+ const describeSteps = (steps) => steps
30
+ .map((s) => {
31
+ const t = (m) => m ? (m.re ? `/${m.re.source}/${m.re.flags}` : JSON.stringify(m.s)) : '';
32
+ switch (s.k) {
33
+ case 'css':
34
+ case 'raw':
35
+ return `locator(${JSON.stringify(s.sel)})`;
36
+ case 'text':
37
+ return `getByText(${t(s.m)})`;
38
+ case 'xpath':
39
+ return `locator(${JSON.stringify('xpath=' + s.expr)})`;
40
+ case 'role':
41
+ return `getByRole(${JSON.stringify(s.role)}${s.name !== undefined ? `, { name: ${t(s.name)} }` : ''})`;
42
+ case 'label':
43
+ return `getByLabel(${t(s.m)})`;
44
+ case 'placeholder':
45
+ return `getByPlaceholder(${t(s.m)})`;
46
+ case 'testid':
47
+ return `getByTestId(${t(s.m)})`;
48
+ case 'title':
49
+ return `getByTitle(${t(s.m)})`;
50
+ case 'alt':
51
+ return `getByAltText(${t(s.m)})`;
52
+ case 'nth':
53
+ return s.i === 0 ? 'first()' : s.i === -1 ? 'last()' : `nth(${s.i})`;
54
+ case 'visible':
55
+ return 'filter({ visible: true })';
56
+ case 'filter':
57
+ return `filter(${JSON.stringify({ hasText: s.hasText?.s ?? s.hasText?.re?.source, hasNotText: s.hasNotText?.s })})`;
58
+ default:
59
+ return s.k;
60
+ }
61
+ })
62
+ .join('.');
63
+ const UNSUPPORTED_LOCATOR = {
64
+ screenshot: 'Screenshots need pixels; assert layout with boundingBox() and evaluate(el => getComputedStyle(el)...) instead, or run the spec with the playwright bond on your machine.',
65
+ elementHandle: 'Element handles are not available; keep using the Locator (click, evaluate, boundingBox, textContent all work).',
66
+ elementHandles: 'Element handles are not available; use locator.all(), locator.evaluateAll() or locator.count().',
67
+ setInputFiles: "File inputs cannot be filled through the preview; set the input's files in page.evaluate() with a DataTransfer, or use the playwright bond.",
68
+ dragTo: 'Drag and drop is not synthesised; dispatch the dragstart/dragover/drop events with locator.dispatchEvent() or use the playwright bond.',
69
+ frameLocator: 'Iframes inside the preview are not driven; only the top document is.',
70
+ contentFrame: 'Iframes inside the preview are not driven; only the top document is.',
71
+ selectText: 'Select text with locator.evaluate(el => { const r = document.createRange(); r.selectNodeContents(el); getSelection().addRange(r) }).',
72
+ and: 'Combine conditions with locator.filter({ has, hasText }) instead of and().',
73
+ or: 'Use two locators, or a CSS selector list ("a, b"), instead of or().',
74
+ ariaSnapshot: 'Use getByRole()/toHaveRole()/toHaveAccessibleName() instead of aria snapshots.',
75
+ };
76
+ const UNSUPPORTED_PAGE = {
77
+ screenshot: 'Screenshots need pixels; assert layout with locator.boundingBox() and page.evaluate(() => getComputedStyle(...)) instead, or run the spec with the playwright bond on your machine.',
78
+ pdf: 'PDF rendering needs a real browser; use the playwright bond.',
79
+ route: 'Network interception needs a real browser. Read responses with page.request.get(url) or page.evaluate(() => fetch(url)) instead.',
80
+ unroute: 'Network interception needs a real browser.',
81
+ unrouteAll: 'Network interception needs a real browser.',
82
+ routeFromHAR: 'Network interception needs a real browser.',
83
+ waitForRequest: 'Requests are not observable through the preview; call page.request.get(url) or page.evaluate(() => fetch(url)) and assert on the response.',
84
+ waitForResponse: 'Responses are not observable through the preview; call page.request.get(url) or page.evaluate(() => fetch(url)) and assert on the response.',
85
+ waitForEvent: 'Only console, pageerror, dialog and close events exist here; use page.on(...) for those, or poll with page.waitForFunction().',
86
+ setContent: 'The preview shows the running app; navigate with page.goto() instead of replacing the document.',
87
+ emulateMedia: 'Media emulation needs a real browser; test the phone layout with page.setViewportSize({ width: 390, height: 844 }), and read matchMedia() results with page.evaluate().',
88
+ exposeFunction: 'Bindings need a real browser; pass data with page.evaluate(fn, arg) and return values from it.',
89
+ exposeBinding: 'Bindings need a real browser; pass data with page.evaluate(fn, arg) and return values from it.',
90
+ addInitScript: 'Init scripts need a real browser; run setup with page.evaluate() after page.goto().',
91
+ setExtraHTTPHeaders: 'Request headers cannot be set for the previewed page; pass headers to page.request.get(url, { headers }).',
92
+ $: 'Element handles are not available; use page.locator(selector) (it has click, textContent, evaluate, boundingBox...).',
93
+ $$: 'Element handles are not available; use page.locator(selector).all() or locator.evaluateAll().',
94
+ frame: 'Iframes inside the preview are not driven; only the top document is.',
95
+ frameLocator: 'Iframes inside the preview are not driven; only the top document is.',
96
+ accessibility: 'Use getByRole(), toHaveRole() and toHaveAccessibleName() instead.',
97
+ coverage: 'Coverage needs a real browser.',
98
+ clock: 'Clock control needs a real browser; stub Date/setTimeout inside page.evaluate() if a test needs it.',
99
+ requestGC: 'Not available through the preview.',
100
+ setChecked: 'Use page.locator(selector).setChecked(value).',
101
+ };
102
+ const CONTEXT_UNSUPPORTED = {
103
+ newPage: 'One page per test with the preview bond; reuse `page`.',
104
+ cookies: 'Read cookies with page.evaluate(() => document.cookie).',
105
+ addCookies: 'Set cookies with page.evaluate(() => { document.cookie = "..." }).',
106
+ clearCookies: "Clear cookies with page.evaluate() (expire each one) or from the app's own sign-out.",
107
+ storageState: 'Read storage with page.evaluate(() => ({ ...localStorage })).',
108
+ route: 'Network interception needs a real browser.',
109
+ unroute: 'Network interception needs a real browser.',
110
+ addInitScript: 'Init scripts need a real browser; run setup with page.evaluate() after page.goto().',
111
+ exposeFunction: 'Bindings need a real browser.',
112
+ exposeBinding: 'Bindings need a real browser.',
113
+ setExtraHTTPHeaders: 'Request headers cannot be set for the previewed page.',
114
+ setGeolocation: 'Geolocation emulation needs a real browser.',
115
+ setOffline: 'Offline emulation needs a real browser.',
116
+ waitForEvent: 'Only page-level console/pageerror/dialog/close events exist here.',
117
+ };
118
+ const defaultAlternative = 'It needs a real browser; the same spec runs unchanged with @molecule/app-e2e-playwright on your machine or in CI. Inside the preview, page.evaluate() can usually do the same job.';
119
+ /** The response object `page.request.*` returns over the preview: a `fetch` run inside the page, read back as text. */
120
+ class ResponseImpl {
121
+ r;
122
+ constructor(r) {
123
+ this.r = r;
124
+ }
125
+ /** Whether the status is 2xx. */
126
+ ok() {
127
+ return this.r.ok;
128
+ }
129
+ /** HTTP status code. */
130
+ status() {
131
+ return this.r.status;
132
+ }
133
+ /** HTTP status text. */
134
+ statusText() {
135
+ return this.r.statusText;
136
+ }
137
+ /** Playwright's `url()`: the last known URL (or, on a response, its final URL). */
138
+ url() {
139
+ return this.r.url;
140
+ }
141
+ /** Response headers, lower-cased. */
142
+ headers() {
143
+ return Object.fromEntries(this.r.headers.map(([k, v]) => [k.toLowerCase(), v]));
144
+ }
145
+ /** Response headers as `{ name, value }` pairs. */
146
+ headersArray() {
147
+ return this.r.headers.map(([name, value]) => ({ name, value }));
148
+ }
149
+ /** The body as text (for a response) or the message text (for a console message). */
150
+ async text() {
151
+ return this.r.text;
152
+ }
153
+ /** The body parsed as JSON. */
154
+ async json() {
155
+ return JSON.parse(this.r.text);
156
+ }
157
+ /** The body as a Buffer (UTF-8 text; binary bodies are not preserved). */
158
+ async body() {
159
+ return Buffer.from(this.r.text, 'utf8');
160
+ }
161
+ /** Nothing to release; kept for parity with Playwright. */
162
+ async dispose() {
163
+ /* nothing to release */
164
+ }
165
+ }
166
+ /** A Playwright-shaped Locator: a lazy chain of steps resolved inside the page on every action. */
167
+ class LocatorImpl {
168
+ owner;
169
+ steps;
170
+ [E2E_LOCATOR] = true;
171
+ constructor(owner, steps) {
172
+ this.owner = owner;
173
+ this.steps = steps;
174
+ }
175
+ /** A new locator with more steps (locators are immutable). */
176
+ extend(step) {
177
+ return new LocatorImpl(this.owner, this.steps.concat(step));
178
+ }
179
+ /** Steps for a selector string (parsed in the page) or another locator. */
180
+ stepsOf(sel) {
181
+ // A string is a Playwright selector (CSS, `text=`, `xpath=`, `>>`...); the page parses it.
182
+ return typeof sel === 'string' ? [{ k: 'raw', sel }] : sel.steps;
183
+ }
184
+ /** The filter steps a `filter()` / `locator()` options object adds. */
185
+ filterStep(opts) {
186
+ const out = [];
187
+ if (opts &&
188
+ (opts.hasText !== undefined || opts.hasNotText !== undefined || opts.has || opts.hasNot)) {
189
+ out.push({
190
+ k: 'filter',
191
+ hasText: opts.hasText !== undefined ? toMatch(opts.hasText) : undefined,
192
+ hasNotText: opts.hasNotText !== undefined ? toMatch(opts.hasNotText) : undefined,
193
+ has: opts.has ? opts.has.steps : undefined,
194
+ hasNot: opts.hasNot ? opts.hasNot.steps : undefined,
195
+ });
196
+ }
197
+ if (opts?.visible)
198
+ out.push({ k: 'visible' });
199
+ return out;
200
+ }
201
+ // ---- building ----
202
+ /** Playwright's `locator()`: narrow to descendants matching a selector, with optional `hasText` / `has` filters. */
203
+ locator(sel, opts) {
204
+ return this.extend(this.stepsOf(sel).concat(this.filterStep(opts)));
205
+ }
206
+ /** Playwright's `getByRole()`: by ARIA role, with accessible-name, heading-level and state options. */
207
+ getByRole(role, opts = {}) {
208
+ return this.extend({
209
+ k: 'role',
210
+ role,
211
+ name: opts.name !== undefined
212
+ ? toMatch(opts.name, { exact: opts.exact })
213
+ : undefined,
214
+ level: opts.level,
215
+ checked: opts.checked,
216
+ pressed: opts.pressed,
217
+ expanded: opts.expanded,
218
+ selected: opts.selected,
219
+ disabled: opts.disabled,
220
+ includeHidden: opts.includeHidden,
221
+ });
222
+ }
223
+ /** Playwright's `getByText()`: the smallest elements whose text matches. */
224
+ getByText(text, opts) {
225
+ return this.extend({ k: 'text', m: toMatch(text, { exact: opts?.exact }) });
226
+ }
227
+ /** Playwright's `getByLabel()`: form controls by their label text (also `aria-label`). */
228
+ getByLabel(text, opts) {
229
+ return this.extend({ k: 'label', m: toMatch(text, { exact: opts?.exact }) });
230
+ }
231
+ /** Playwright's `getByPlaceholder()`. */
232
+ getByPlaceholder(text, opts) {
233
+ return this.extend({ k: 'placeholder', m: toMatch(text, { exact: opts?.exact }) });
234
+ }
235
+ /** Playwright's `getByTitle()`. */
236
+ getByTitle(text, opts) {
237
+ return this.extend({ k: 'title', m: toMatch(text, { exact: opts?.exact }) });
238
+ }
239
+ /** Playwright's `getByAltText()`. */
240
+ getByAltText(text, opts) {
241
+ return this.extend({ k: 'alt', m: toMatch(text, { exact: opts?.exact }) });
242
+ }
243
+ /** Playwright's `getByTestId()` on the configured test-id attribute (default `data-testid`). */
244
+ getByTestId(id) {
245
+ return this.extend({ k: 'testid', attr: this.owner.testIdAttribute, m: toFull(id) });
246
+ }
247
+ /** Playwright's `locator.filter()`: keep matches by text, by a descendant locator, or by visibility. */
248
+ filter(opts = {}) {
249
+ return this.extend(this.filterStep(opts));
250
+ }
251
+ /** The first match. */
252
+ first() {
253
+ return this.extend({ k: 'nth', i: 0 });
254
+ }
255
+ /** The last match. */
256
+ last() {
257
+ return this.extend({ k: 'nth', i: -1 });
258
+ }
259
+ /** The i-th match (a negative index counts from the end). */
260
+ nth(i) {
261
+ return this.extend({ k: 'nth', i });
262
+ }
263
+ /** The page this belongs to (null for a console message over the preview). */
264
+ page() {
265
+ return this.owner.asPage();
266
+ }
267
+ /** The locator chain, Playwright-style, for error messages. */
268
+ toString() {
269
+ return describeSteps(this.steps);
270
+ }
271
+ /** The locator chain as text. */
272
+ describe() {
273
+ return describeSteps(this.steps);
274
+ }
275
+ // ---- runtime calls ----
276
+ /** Run one runtime call for this locator. */
277
+ async call(fn, args, timeout) {
278
+ return this.owner.rt(fn, args, timeout);
279
+ }
280
+ /** Turn a runtime reply into a value, throwing strict-mode, timeout or plain errors. */
281
+ unwrap(res, what) {
282
+ if (res.strict)
283
+ throw new E2EStrictModeError(this.describe(), Number(res.count));
284
+ if (res.ok === false) {
285
+ const message = `${what}: ${String(res.error ?? 'failed')}`;
286
+ throw res.timeout
287
+ ? new E2ETimeoutError(`${message} (timeout ${this.owner.defaultTimeout}ms)`)
288
+ : new Error(message);
289
+ }
290
+ return res.value;
291
+ }
292
+ /** Run a single-target action with auto-wait and actionability checks. */
293
+ async act(action, opts = {}) {
294
+ const timeout = opts.timeout ?? this.owner.defaultTimeout;
295
+ const res = await this.call('act', [this.steps, action, { ...opts, timeout: undefined }], timeout);
296
+ this.unwrap(res, `locator.${action}()`);
297
+ }
298
+ /** Read one value from the single strict target. */
299
+ async read(what, args = [], timeout) {
300
+ return this.unwrap(await this.call('read', [this.steps, what, args], timeout ?? this.owner.defaultTimeout), `locator.${what}()`);
301
+ }
302
+ // ---- actions ----
303
+ /** Playwright's `click()`: waits until the element is visible, enabled and not covered, then clicks its centre (or `position`). */
304
+ click(opts) {
305
+ return this.act('click', opts);
306
+ }
307
+ /** Playwright's `dblclick()`. */
308
+ dblclick(opts) {
309
+ return this.act('dblclick', opts);
310
+ }
311
+ /** Playwright's `hover()`: moves the pointer over the element. */
312
+ hover(opts) {
313
+ return this.act('hover', opts);
314
+ }
315
+ /** Playwright's `tap()`: touch events followed by a click. */
316
+ tap(opts) {
317
+ return this.act('tap', opts);
318
+ }
319
+ /** Playwright's `fill()`: focuses, replaces the value through the native setter and fires `input` / `change`. */
320
+ fill(value, opts) {
321
+ return this.act('fill', { ...opts, value });
322
+ }
323
+ /** Playwright's `clear()`: fills with an empty string. */
324
+ clear(opts) {
325
+ return this.act('clear', opts);
326
+ }
327
+ /** Playwright's `type()`: presses each character in turn (or, on a console message, its level). */
328
+ type(text, opts) {
329
+ return this.act('type', { ...opts, text });
330
+ }
331
+ /** Playwright's `pressSequentially()`: presses each character in turn. */
332
+ pressSequentially(text, opts) {
333
+ return this.act('type', { ...opts, text });
334
+ }
335
+ /** Playwright's `press()`: a key or chord (`Enter`, `Control+a`); Enter submits a form. */
336
+ press(key, opts) {
337
+ return this.act('press', { ...opts, key });
338
+ }
339
+ /** Playwright's `check()`: clicks until checked (or, on the page, throws on a failed runtime reply). */
340
+ check(opts) {
341
+ return this.act('check', opts);
342
+ }
343
+ /** Playwright's `uncheck()`: clicks until unchecked. */
344
+ uncheck(opts) {
345
+ return this.act('uncheck', opts);
346
+ }
347
+ /** Playwright's `setChecked()`. */
348
+ setChecked(checked, opts) {
349
+ return this.act('setChecked', { ...opts, checked });
350
+ }
351
+ /** Playwright's `selectOption()`: by value, label or index; returns the selected values. */
352
+ async selectOption(values, opts) {
353
+ const timeout = opts?.timeout ?? this.owner.defaultTimeout;
354
+ const res = await this.call('act', [this.steps, 'selectOption', { values }], timeout);
355
+ this.unwrap(res, 'locator.selectOption()');
356
+ return res.values ?? [];
357
+ }
358
+ /** Playwright's `focus()`. */
359
+ focus(opts) {
360
+ return this.act('focus', opts);
361
+ }
362
+ /** Playwright's `blur()`. */
363
+ blur(opts) {
364
+ return this.act('blur', opts);
365
+ }
366
+ /** Playwright's `dispatchEvent()`: a synthetic event of the given type. */
367
+ dispatchEvent(type, init, opts) {
368
+ return this.act('dispatchEvent', { ...opts, type, init });
369
+ }
370
+ /** Playwright's `scrollIntoViewIfNeeded()`. */
371
+ scrollIntoViewIfNeeded(opts) {
372
+ return this.act('scrollIntoViewIfNeeded', opts);
373
+ }
374
+ /** No-op over the preview: nothing to paint. */
375
+ async highlight() {
376
+ /* nothing to paint through the preview */
377
+ }
378
+ // ---- reads ----
379
+ /** Playwright's `count()`: how many elements match right now. */
380
+ async count() {
381
+ return (await this.call('readAll', [this.steps, 'count'])).value;
382
+ }
383
+ /** Playwright's `all()`: one locator per current match. */
384
+ async all() {
385
+ const n = await this.count();
386
+ return Array.from({ length: n }, (_, i) => this.nth(i));
387
+ }
388
+ /** Playwright's `allTextContents()`. */
389
+ async allTextContents() {
390
+ return (await this.call('readAll', [this.steps, 'allTextContents'])).value;
391
+ }
392
+ /** Playwright's `allInnerTexts()`. */
393
+ async allInnerTexts() {
394
+ return (await this.call('readAll', [this.steps, 'allInnerTexts'])).value;
395
+ }
396
+ /** Playwright's `textContent()` (strict: exactly one match). */
397
+ textContent(opts) {
398
+ return this.read('textContent', [], opts?.timeout);
399
+ }
400
+ /** Playwright's `innerText()`. */
401
+ innerText(opts) {
402
+ return this.read('innerText', [], opts?.timeout);
403
+ }
404
+ /** Playwright's `innerHTML()`. */
405
+ innerHTML(opts) {
406
+ return this.read('innerHTML', [], opts?.timeout);
407
+ }
408
+ /** Playwright's `inputValue()` for inputs, textareas and selects. */
409
+ inputValue(opts) {
410
+ return this.read('inputValue', [], opts?.timeout);
411
+ }
412
+ /** Playwright's `getAttribute()`. */
413
+ getAttribute(name, opts) {
414
+ return this.read('getAttribute', [name], opts?.timeout);
415
+ }
416
+ /** Playwright's `isVisible()`: false when nothing matches; no waiting. */
417
+ isVisible() {
418
+ return this.read('isVisible');
419
+ }
420
+ /** Playwright's `isHidden()`: true when nothing matches; no waiting. */
421
+ isHidden() {
422
+ return this.read('isHidden');
423
+ }
424
+ /** Playwright's `isEnabled()`. */
425
+ isEnabled(opts) {
426
+ return this.read('isEnabled', [], opts?.timeout);
427
+ }
428
+ /** Playwright's `isDisabled()`. */
429
+ isDisabled(opts) {
430
+ return this.read('isDisabled', [], opts?.timeout);
431
+ }
432
+ /** Playwright's `isEditable()`. */
433
+ isEditable(opts) {
434
+ return this.read('isEditable', [], opts?.timeout);
435
+ }
436
+ /** Playwright's `isChecked()` for checkboxes, radios and switches. */
437
+ isChecked(opts) {
438
+ return this.read('isChecked', [], opts?.timeout);
439
+ }
440
+ /** Playwright's `boundingBox()`: the element's rect, or null when it is not visible. */
441
+ boundingBox(opts) {
442
+ return this.read('boundingBox', [], opts?.timeout);
443
+ }
444
+ /** Playwright's `evaluate()`: runs the function inside the page and returns JSON. */
445
+ async evaluate(fn, arg, opts) {
446
+ return this.unwrap(await this.call('evalOn', [this.steps, fnSource(fn), arg], opts?.timeout ?? this.owner.defaultTimeout), 'locator.evaluate()');
447
+ }
448
+ /** Playwright's `locator.evaluateAll()`: runs `fn(elements, arg)` inside the page. */
449
+ async evaluateAll(fn, arg) {
450
+ return this.unwrap(await this.call('evalAll', [this.steps, fnSource(fn), arg]), 'locator.evaluateAll()');
451
+ }
452
+ /** Playwright's `waitFor()`: until attached, detached, visible or hidden. */
453
+ async waitFor(opts = {}) {
454
+ const timeout = opts.timeout ?? this.owner.defaultTimeout;
455
+ const res = await this.call('waitFor', [this.steps, opts.state ?? 'visible'], timeout);
456
+ this.unwrap(res, 'locator.waitFor()');
457
+ }
458
+ /** One probe for the expect matchers; the wrapper polls it. */
459
+ async probe(matcher, args) {
460
+ return this.call('probe', [this.steps, matcher, args]);
461
+ }
462
+ }
463
+ /** The console message `page.on('console')` listeners receive. */
464
+ class ConsoleMessageImpl {
465
+ p;
466
+ constructor(p) {
467
+ this.p = p;
468
+ }
469
+ /** Playwright's `type()`: presses each character in turn (or, on a console message, its level). */
470
+ type() {
471
+ return this.p.type;
472
+ }
473
+ /** The body as text (for a response) or the message text (for a console message). */
474
+ text() {
475
+ return this.p.text;
476
+ }
477
+ /** Where it was logged, when known. */
478
+ location() {
479
+ return {
480
+ url: this.p.url ?? '',
481
+ lineNumber: this.p.lineNumber ?? 0,
482
+ columnNumber: this.p.columnNumber ?? 0,
483
+ };
484
+ }
485
+ /** Not available over the preview: always empty. */
486
+ args() {
487
+ return [];
488
+ }
489
+ /** The page this belongs to (null for a console message over the preview). */
490
+ page() {
491
+ return null;
492
+ }
493
+ }
494
+ /** A Playwright-shaped Page over an `E2ETransport`; `createEvaluatePage` wraps it in a Proxy. */
495
+ class PageImpl {
496
+ transport;
497
+ options;
498
+ [E2E_PAGE] = true;
499
+ defaultTimeout;
500
+ navigationTimeout;
501
+ testIdAttribute;
502
+ viewport;
503
+ closed = false;
504
+ proxy;
505
+ listeners = new Map();
506
+ warned = new Set();
507
+ unsubscribe = [];
508
+ mouse;
509
+ keyboard;
510
+ touchscreen;
511
+ request;
512
+ constructor(transport, options) {
513
+ this.transport = transport;
514
+ this.options = options;
515
+ this.defaultTimeout = options.timeout ?? 10_000;
516
+ this.navigationTimeout = options.navigationTimeout ?? 15_000;
517
+ this.testIdAttribute =
518
+ options.testIdAttribute ?? 'data-testid';
519
+ this.viewport = options.viewport ?? null;
520
+ this.unsubscribe.push(transport.on('console', (payload) => this.emit('console', new ConsoleMessageImpl(payload))), transport.on('pageerror', (payload) => {
521
+ const p = payload;
522
+ const err = new Error(p.message);
523
+ if (p.stack)
524
+ err.stack = p.stack;
525
+ this.emit('pageerror', err);
526
+ }), transport.on('close', () => {
527
+ this.closed = true;
528
+ this.emit('close', this.proxy);
529
+ }));
530
+ const mouseCall = (action, x, y, opts = {}) => this.rt('mouse', [action, x, y, opts])
531
+ .then((r) => this.check(r, `mouse.${action}()`))
532
+ .then(() => undefined);
533
+ this.mouse = {
534
+ click: (x, y, opts) => mouseCall('click', x, y, opts),
535
+ dblclick: (x, y, opts) => mouseCall('dblclick', x, y, opts),
536
+ move: (x, y) => mouseCall('move', x, y),
537
+ down: () => mouseCall('down', 0, 0),
538
+ up: () => mouseCall('up', 0, 0),
539
+ wheel: (dx, dy) => mouseCall('wheel', dx, dy),
540
+ };
541
+ const keyCall = (action, arg) => this.rt('keyboard', [action, arg])
542
+ .then((r) => this.check(r, `keyboard.${action}()`))
543
+ .then(() => undefined);
544
+ this.keyboard = {
545
+ press: (key) => keyCall('press', key),
546
+ type: (text) => keyCall('type', text),
547
+ insertText: (text) => keyCall('insertText', text),
548
+ down: (key) => keyCall('down', key),
549
+ up: (key) => keyCall('up', key),
550
+ };
551
+ this.touchscreen = { tap: (x, y) => mouseCall('click', x, y) };
552
+ const fetchCall = async (method, url, opts = {}) => {
553
+ const res = await this.rt('fetch', [url, { ...opts, method }], opts.timeout);
554
+ this.check(res, `request.${method.toLowerCase()}()`);
555
+ return new ResponseImpl(res.value);
556
+ };
557
+ this.request = {
558
+ get: (url, opts) => fetchCall('GET', url, opts),
559
+ post: (url, opts) => fetchCall('POST', url, opts),
560
+ put: (url, opts) => fetchCall('PUT', url, opts),
561
+ patch: (url, opts) => fetchCall('PATCH', url, opts),
562
+ delete: (url, opts) => fetchCall('DELETE', url, opts),
563
+ head: (url, opts) => fetchCall('HEAD', url, opts),
564
+ fetch: (url, opts) => fetchCall(String(opts?.method ?? 'GET'), url, opts),
565
+ dispose: async () => undefined,
566
+ storageState: () => {
567
+ throw new E2EUnsupportedError('request.storageState()', 'Read storage with page.evaluate(() => ({ ...localStorage })).', this.bondName);
568
+ },
569
+ };
570
+ }
571
+ /** The bond's name, for error messages. */
572
+ get bondName() {
573
+ return this.options.bondName ?? 'this e2e bond';
574
+ }
575
+ /** The branded Page proxy. */
576
+ asPage() {
577
+ return this.proxy;
578
+ }
579
+ /** Called once by `createEvaluatePage` with the proxy that wraps this instance. */
580
+ attachProxy(proxy) {
581
+ this.proxy = proxy;
582
+ }
583
+ /** Fire the listeners of one event. */
584
+ emit(event, payload) {
585
+ for (const fn of this.listeners.get(event) ?? []) {
586
+ try {
587
+ fn(payload);
588
+ }
589
+ catch (error) {
590
+ console.error(`[app-e2e] listener for '${event}' threw`, error);
591
+ }
592
+ }
593
+ }
594
+ /** Playwright's `check()`: clicks until checked (or, on the page, throws on a failed runtime reply). */
595
+ check(res, what) {
596
+ if (res.ok === false) {
597
+ const message = `${what}: ${String(res.error ?? 'failed')}`;
598
+ throw res.timeout ? new E2ETimeoutError(message) : new Error(message);
599
+ }
600
+ return res;
601
+ }
602
+ /** Run a runtime call inside the page, installing the runtime on a fresh document. */
603
+ async rt(fn, args, timeout) {
604
+ if (this.closed)
605
+ throw new Error('Target page, context or browser has been closed');
606
+ const budget = (timeout ?? this.defaultTimeout) + 2_000;
607
+ let res = (await this.transport.evaluate(CALL_SOURCE, { fn, args, timeout: timeout ?? this.defaultTimeout }, { timeout: budget }));
608
+ if (res && res.__needInstall) {
609
+ await this.transport.evaluate(RUNTIME_SOURCE, undefined, { timeout: 5_000 });
610
+ res = (await this.transport.evaluate(CALL_SOURCE, { fn, args, timeout: timeout ?? this.defaultTimeout }, { timeout: budget }));
611
+ }
612
+ return (res ?? {});
613
+ }
614
+ // ---- navigation ----
615
+ /** Resolve a spec URL against `baseURL` when it is relative. */
616
+ resolveUrl(url) {
617
+ if (/^[a-z]+:/i.test(url))
618
+ return url;
619
+ const base = this.options.baseURL;
620
+ if (base)
621
+ return new URL(url, base.endsWith('/') ? base : base + '/').href;
622
+ return url;
623
+ }
624
+ /** Playwright's `page.goto()`: navigate and wait for the new document to load. */
625
+ async goto(url, opts = {}) {
626
+ await this.transport.navigate('goto', this.resolveUrl(url), {
627
+ timeout: opts.timeout ?? this.navigationTimeout,
628
+ });
629
+ this.emit('load', this.proxy);
630
+ return null;
631
+ }
632
+ /** Playwright's `page.reload()`. */
633
+ async reload(opts = {}) {
634
+ await this.transport.navigate('reload', undefined, {
635
+ timeout: opts.timeout ?? this.navigationTimeout,
636
+ });
637
+ this.emit('load', this.proxy);
638
+ return null;
639
+ }
640
+ /** Playwright's `page.goBack()`. */
641
+ async goBack(opts = {}) {
642
+ await this.transport.navigate('back', undefined, {
643
+ timeout: opts.timeout ?? this.navigationTimeout,
644
+ });
645
+ return null;
646
+ }
647
+ /** Playwright's `page.goForward()`. */
648
+ async goForward(opts = {}) {
649
+ await this.transport.navigate('forward', undefined, {
650
+ timeout: opts.timeout ?? this.navigationTimeout,
651
+ });
652
+ return null;
653
+ }
654
+ /** Playwright's `url()`: the last known URL (or, on a response, its final URL). */
655
+ url() {
656
+ return this.transport.url();
657
+ }
658
+ /** Playwright's `page.title()`. */
659
+ async title() {
660
+ return String((await this.rt('info', [])).title ?? '');
661
+ }
662
+ /** Playwright's `page.content()`: the document's HTML. */
663
+ async content() {
664
+ return String(this.check(await this.rt('eval', ['() => "<!DOCTYPE html>" + document.documentElement.outerHTML']), 'page.content()').value);
665
+ }
666
+ /** Playwright's `waitForLoadState()`: until the document is complete (plus a quiet moment for `networkidle`). */
667
+ async waitForLoadState(state = 'load', opts = {}) {
668
+ const deadline = Date.now() + (opts.timeout ?? this.navigationTimeout);
669
+ for (;;) {
670
+ const info = await this.rt('info', []);
671
+ if (info.readyState === 'complete' ||
672
+ (state === 'domcontentloaded' && info.readyState !== 'loading'))
673
+ break;
674
+ if (Date.now() > deadline)
675
+ throw new E2ETimeoutError(`page.waitForLoadState(${state}): document still ${String(info.readyState)}`);
676
+ await new Promise((r) => setTimeout(r, 100));
677
+ }
678
+ if (state === 'networkidle')
679
+ await new Promise((r) => setTimeout(r, 500));
680
+ }
681
+ /** Playwright's `waitForURL()`: a string (full URL or path), RegExp or predicate. */
682
+ async waitForURL(url, opts = {}) {
683
+ const deadline = Date.now() + (opts.timeout ?? this.navigationTimeout);
684
+ const matches = (current) => {
685
+ if (typeof url === 'function')
686
+ return url(new URL(current));
687
+ if (url instanceof RegExp)
688
+ return url.test(current);
689
+ const want = this.resolveUrl(url);
690
+ if (/^[a-z]+:/i.test(want))
691
+ return current === want || current.replace(/\/$/, '') === want.replace(/\/$/, '');
692
+ const c = new URL(current);
693
+ return (c.pathname + c.search + c.hash === want ||
694
+ c.pathname === want ||
695
+ c.pathname.replace(/\/$/, '') === want.replace(/\/$/, ''));
696
+ };
697
+ for (;;) {
698
+ const info = await this.rt('info', []);
699
+ if (matches(String(info.url)))
700
+ return;
701
+ if (Date.now() > deadline)
702
+ throw new E2ETimeoutError(`page.waitForURL(${String(url)}): still at ${String(info.url)}`);
703
+ await new Promise((r) => setTimeout(r, 100));
704
+ }
705
+ }
706
+ /** Playwright's `waitForTimeout()`. */
707
+ async waitForTimeout(ms) {
708
+ await new Promise((r) => setTimeout(r, ms));
709
+ }
710
+ /** Playwright's `waitForFunction()`: polls the function inside the page until it returns a truthy value. */
711
+ async waitForFunction(fn, arg, opts = {}) {
712
+ const deadline = Date.now() + (opts.timeout ?? this.defaultTimeout);
713
+ const interval = typeof opts.polling === 'number' ? opts.polling : 100;
714
+ for (;;) {
715
+ const res = this.check(await this.rt('eval', [fnSource(fn), arg]), 'page.waitForFunction()');
716
+ if (res.value)
717
+ return res.value;
718
+ if (Date.now() > deadline)
719
+ throw new E2ETimeoutError('page.waitForFunction(): the function never returned a truthy value');
720
+ await new Promise((r) => setTimeout(r, interval));
721
+ }
722
+ }
723
+ /** Playwright's `waitForSelector()`; returns the locator (element handles are not available). */
724
+ async waitForSelector(selector, opts = {}) {
725
+ const loc = this.locator(selector);
726
+ await loc.waitFor({ state: opts.state ?? 'visible', timeout: opts.timeout });
727
+ return opts.state === 'detached' || opts.state === 'hidden' ? null : loc;
728
+ }
729
+ // ---- evaluation ----
730
+ /** Playwright's `evaluate()`: runs the function inside the page and returns JSON. */
731
+ async evaluate(fn, arg) {
732
+ return this.check(await this.rt('eval', [fnSource(fn), arg]), 'page.evaluate()').value;
733
+ }
734
+ /** Playwright's `$eval()`. */
735
+ $eval(selector, fn, arg) {
736
+ return this.locator(selector).evaluate(fn, arg);
737
+ }
738
+ /** Playwright's `$$eval()`. */
739
+ $$eval(selector, fn, arg) {
740
+ return this.locator(selector).evaluateAll(fn, arg);
741
+ }
742
+ /** Playwright's `addStyleTag()` (inline content). */
743
+ async addStyleTag(opts) {
744
+ this.check(await this.rt('addTag', ['style', opts]), 'page.addStyleTag()');
745
+ return null;
746
+ }
747
+ /** Playwright's `addScriptTag()`. */
748
+ async addScriptTag(opts) {
749
+ this.check(await this.rt('addTag', ['script', opts]), 'page.addScriptTag()');
750
+ return null;
751
+ }
752
+ // ---- locators ----
753
+ /** Playwright's `locator()`: narrow to descendants matching a selector, with optional `hasText` / `has` filters. */
754
+ locator(sel, opts) {
755
+ return new LocatorImpl(this, []).locator(sel, opts);
756
+ }
757
+ /** Playwright's `getByRole()`: by ARIA role, with accessible-name, heading-level and state options. */
758
+ getByRole(role, opts) {
759
+ return new LocatorImpl(this, []).getByRole(role, opts);
760
+ }
761
+ /** Playwright's `getByText()`: the smallest elements whose text matches. */
762
+ getByText(text, opts) {
763
+ return new LocatorImpl(this, []).getByText(text, opts);
764
+ }
765
+ /** Playwright's `getByLabel()`: form controls by their label text (also `aria-label`). */
766
+ getByLabel(text, opts) {
767
+ return new LocatorImpl(this, []).getByLabel(text, opts);
768
+ }
769
+ /** Playwright's `getByPlaceholder()`. */
770
+ getByPlaceholder(text, opts) {
771
+ return new LocatorImpl(this, []).getByPlaceholder(text, opts);
772
+ }
773
+ /** Playwright's `getByTitle()`. */
774
+ getByTitle(text, opts) {
775
+ return new LocatorImpl(this, []).getByTitle(text, opts);
776
+ }
777
+ /** Playwright's `getByAltText()`. */
778
+ getByAltText(text, opts) {
779
+ return new LocatorImpl(this, []).getByAltText(text, opts);
780
+ }
781
+ /** Playwright's `getByTestId()` on the configured test-id attribute (default `data-testid`). */
782
+ getByTestId(id) {
783
+ return new LocatorImpl(this, []).getByTestId(id);
784
+ }
785
+ // ---- viewport ----
786
+ /** Playwright's `setViewportSize()`: asks the host to resize the frame; throws when it did not. */
787
+ async setViewportSize(size) {
788
+ const got = await this.transport.viewport(size.width, size.height);
789
+ if (Math.abs(got.width - size.width) > 2) {
790
+ throw new E2EUnsupportedError(`page.setViewportSize(${size.width}x${size.height})`, `The preview host did not resize the frame (it is ${got.width}x${got.height}). Open the project's preview inside the molecule.dev IDE, which honours viewport requests; a preview opened in a plain browser tab keeps the tab's size.`, this.bondName);
791
+ }
792
+ this.viewport = got;
793
+ }
794
+ /** Apply the configured viewport without failing the test when the host cannot resize (used once at connect). */
795
+ async applyInitialViewport() {
796
+ if (!this.viewport)
797
+ return;
798
+ try {
799
+ const got = await this.transport.viewport(this.viewport.width, this.viewport.height);
800
+ if (Math.abs(got.width - this.viewport.width) > 2)
801
+ this.warnOnce('viewport', `[app-e2e] the preview host kept its own size (${got.width}x${got.height}); the configured viewport ${this.viewport.width}x${this.viewport.height} was not applied. Open the preview inside the molecule.dev IDE to test other widths.`);
802
+ this.viewport = got;
803
+ }
804
+ catch (error) {
805
+ this.warnOnce('viewport', `[app-e2e] could not apply the configured viewport: ${String(error)}`);
806
+ }
807
+ }
808
+ /** Playwright's `viewportSize()`. */
809
+ viewportSize() {
810
+ return this.viewport;
811
+ }
812
+ /** Log a warning once per key. */
813
+ warnOnce(key, message) {
814
+ if (this.warned.has(key))
815
+ return;
816
+ this.warned.add(key);
817
+ console.warn(message);
818
+ }
819
+ // ---- events ----
820
+ /** Playwright's `page.on()`: console, pageerror, dialog, close and load fire over the preview; network events never do (warned once). */
821
+ on(event, fn) {
822
+ if ([
823
+ 'request',
824
+ 'response',
825
+ 'requestfinished',
826
+ 'requestfailed',
827
+ 'download',
828
+ 'popup',
829
+ 'websocket',
830
+ 'worker',
831
+ 'frameattached',
832
+ 'framedetached',
833
+ 'framenavigated',
834
+ 'filechooser',
835
+ 'crash',
836
+ 'domcontentloaded',
837
+ ].includes(event)) {
838
+ this.warnOnce('event:' + event, `[app-e2e] page.on('${event}') never fires with ${this.bondName}; network and browser events need a real browser. Use page.request or page.evaluate(() => fetch(...)) to observe responses.`);
839
+ }
840
+ if (!this.listeners.has(event))
841
+ this.listeners.set(event, new Set());
842
+ this.listeners.get(event).add(fn);
843
+ return this.proxy;
844
+ }
845
+ /** Alias of `on()`. */
846
+ addListener(event, fn) {
847
+ return this.on(event, fn);
848
+ }
849
+ /** Playwright's `page.once()`. */
850
+ once(event, fn) {
851
+ const wrapped = (payload) => {
852
+ this.off(event, wrapped);
853
+ fn(payload);
854
+ };
855
+ return this.on(event, wrapped);
856
+ }
857
+ /** Playwright's `page.off()`. */
858
+ off(event, fn) {
859
+ this.listeners.get(event)?.delete(fn);
860
+ return this.proxy;
861
+ }
862
+ /** Alias of `off()`. */
863
+ removeListener(event, fn) {
864
+ return this.off(event, fn);
865
+ }
866
+ /** Playwright's `removeAllListeners()`. */
867
+ removeAllListeners(event) {
868
+ if (event)
869
+ this.listeners.delete(event);
870
+ else
871
+ this.listeners.clear();
872
+ return this.proxy;
873
+ }
874
+ /** Called by a transport that forwards dialogs (the preview client auto-accepts them). */
875
+ emitDialog(dialog) {
876
+ this.emit('dialog', dialog);
877
+ }
878
+ // ---- lifecycle ----
879
+ /** Playwright's `page.close()`: releases the transport. */
880
+ async close() {
881
+ if (this.closed)
882
+ return;
883
+ this.closed = true;
884
+ for (const un of this.unsubscribe)
885
+ un();
886
+ await this.transport.close();
887
+ this.emit('close', this.proxy);
888
+ }
889
+ /** Playwright's `isClosed()`. */
890
+ isClosed() {
891
+ return this.closed;
892
+ }
893
+ /** No-op over the preview: the page is already in front. */
894
+ async bringToFront() {
895
+ /* the preview is already the front page */
896
+ }
897
+ /** Playwright's `setDefaultTimeout()`. */
898
+ setDefaultTimeout(ms) {
899
+ this.defaultTimeout = ms;
900
+ }
901
+ /** Playwright's `setDefaultNavigationTimeout()`. */
902
+ setDefaultNavigationTimeout(ms) {
903
+ this.navigationTimeout = ms;
904
+ }
905
+ /** No-op: there is no inspector to pause in. */
906
+ async pause() {
907
+ /* no inspector to pause in */
908
+ }
909
+ /** The page itself, standing in for the main frame. */
910
+ mainFrame() {
911
+ return this.proxy;
912
+ }
913
+ /** The main frame only. */
914
+ frames() {
915
+ return [this.mainFrame()];
916
+ }
917
+ /** Always empty over the preview. */
918
+ workers() {
919
+ return [];
920
+ }
921
+ /** Always null over the preview. */
922
+ opener() {
923
+ return null;
924
+ }
925
+ /** Always null: no recording over the preview. */
926
+ video() {
927
+ return null;
928
+ }
929
+ /** A minimal BrowserContext: pages, timeouts and no-op tracing; the rest throws naming the alternative. */
930
+ context() {
931
+ const page = this.proxy;
932
+ const ctx = {
933
+ pages: () => [page],
934
+ close: () => this.close(),
935
+ setDefaultTimeout: (ms) => this.setDefaultTimeout(ms),
936
+ setDefaultNavigationTimeout: (ms) => this.setDefaultNavigationTimeout(ms),
937
+ grantPermissions: async () => undefined,
938
+ clearPermissions: async () => undefined,
939
+ browser: () => null,
940
+ on: () => ctx,
941
+ once: () => ctx,
942
+ off: () => ctx,
943
+ addListener: () => ctx,
944
+ removeListener: () => ctx,
945
+ tracing: {
946
+ start: async () => undefined,
947
+ stop: async () => undefined,
948
+ startChunk: async () => undefined,
949
+ stopChunk: async () => undefined,
950
+ group: async () => undefined,
951
+ groupEnd: async () => undefined,
952
+ },
953
+ request: this.request,
954
+ serviceWorkers: () => [],
955
+ backgroundPages: () => [],
956
+ };
957
+ const bond = this.bondName;
958
+ return new Proxy(ctx, {
959
+ get(target, prop) {
960
+ if (typeof prop === 'symbol' || prop in target)
961
+ return target[prop];
962
+ if (prop === 'then')
963
+ return undefined;
964
+ if (prop in CONTEXT_UNSUPPORTED) {
965
+ return () => {
966
+ throw new E2EUnsupportedError(`context.${prop}()`, CONTEXT_UNSUPPORTED[prop], bond);
967
+ };
968
+ }
969
+ return undefined;
970
+ },
971
+ });
972
+ }
973
+ }
974
+ const wrapLocator = (impl, bond) => new Proxy(impl, {
975
+ get(target, prop, receiver) {
976
+ if (typeof prop === 'symbol' || prop in target) {
977
+ const value = Reflect.get(target, prop, receiver);
978
+ if (typeof value === 'function' &&
979
+ typeof prop === 'string' &&
980
+ !['constructor', 'toString', 'describe', 'page'].includes(prop)) {
981
+ return (...args) => {
982
+ const result = value.apply(target, args);
983
+ // Chainable builders return LocatorImpl synchronously; wrap them so the branded proxy travels.
984
+ if (result instanceof LocatorImpl)
985
+ return wrapLocator(result, bond);
986
+ if (result instanceof Promise)
987
+ return result.then((v) => Array.isArray(v) && v.every((x) => x instanceof LocatorImpl)
988
+ ? v.map((x) => wrapLocator(x, bond))
989
+ : v);
990
+ return result;
991
+ };
992
+ }
993
+ return value;
994
+ }
995
+ if (prop === 'then')
996
+ return undefined;
997
+ if (prop in UNSUPPORTED_LOCATOR) {
998
+ return () => {
999
+ throw new E2EUnsupportedError(`locator.${prop}()`, UNSUPPORTED_LOCATOR[prop], bond);
1000
+ };
1001
+ }
1002
+ return undefined;
1003
+ },
1004
+ });
1005
+ /**
1006
+ * Build a Playwright-shaped `Page` over a transport. Bonds call this from
1007
+ * `connect()`; the returned object carries the documented subset and throws an
1008
+ * {@link E2EUnsupportedError} naming the alternative for the rest.
1009
+ */
1010
+ export const createEvaluatePage = async (transport, options = {}) => {
1011
+ const impl = new PageImpl(transport, options);
1012
+ const bond = impl.bondName;
1013
+ const proxy = new Proxy(impl, {
1014
+ get(target, prop, receiver) {
1015
+ if (typeof prop === 'symbol' || prop in target) {
1016
+ const value = Reflect.get(target, prop, receiver);
1017
+ if (typeof value === 'function' &&
1018
+ typeof prop === 'string' &&
1019
+ !['constructor', 'asPage', 'attachProxy', 'rt'].includes(prop)) {
1020
+ return (...args) => {
1021
+ const result = value.apply(target, args);
1022
+ if (result instanceof LocatorImpl)
1023
+ return wrapLocator(result, bond);
1024
+ if (result instanceof Promise)
1025
+ return result.then((v) => (v instanceof LocatorImpl ? wrapLocator(v, bond) : v));
1026
+ return result;
1027
+ };
1028
+ }
1029
+ return value;
1030
+ }
1031
+ if (prop === 'then')
1032
+ return undefined;
1033
+ if (prop in UNSUPPORTED_PAGE) {
1034
+ return () => {
1035
+ throw new E2EUnsupportedError(`page.${prop}()`, UNSUPPORTED_PAGE[prop], bond);
1036
+ };
1037
+ }
1038
+ return () => {
1039
+ throw new E2EUnsupportedError(`page.${String(prop)}()`, defaultAlternative, bond);
1040
+ };
1041
+ },
1042
+ });
1043
+ impl.attachProxy(proxy);
1044
+ await impl.applyInitialViewport();
1045
+ return proxy;
1046
+ };
1047
+ /** Whether a value is a locator built by {@link createEvaluatePage}. */
1048
+ export const isE2ELocator = (value) => !!value && typeof value === 'object' && value[E2E_LOCATOR] === true;
1049
+ /** Whether a value is a page built by {@link createEvaluatePage}. */
1050
+ export const isE2EPage = (value) => !!value && typeof value === 'object' && value[E2E_PAGE] === true;
1051
+ export { toMatch as textMatch, toFull as textMatchFull };
1052
+ //# sourceMappingURL=page.js.map