very-happy-dom 0.2.0 → 0.3.0

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 (57) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +220 -4
  3. package/dist/aria/roles.d.ts +13 -0
  4. package/dist/aria/state.d.ts +33 -0
  5. package/dist/aria/visibility.d.ts +24 -1
  6. package/dist/browser/BrowserContext.d.ts +12 -0
  7. package/dist/browser/BrowserFrame.d.ts +2 -0
  8. package/dist/browser/BrowserPage.d.ts +55 -4
  9. package/dist/browser/Dialog.d.ts +36 -0
  10. package/dist/browser/Locator.d.ts +33 -3
  11. package/dist/browser/keys.d.ts +48 -0
  12. package/dist/{chunk-76fz7rx8.js → chunk-1rvpej1f.js} +2 -2
  13. package/dist/{chunk-httdr8re.js → chunk-3j0xdgak.js} +2 -2
  14. package/dist/chunk-6sd65akb.js +2 -0
  15. package/dist/{chunk-h2qtm7yn.js → chunk-7vtf83p2.js} +2 -2
  16. package/dist/chunk-8bm80b93.js +2 -0
  17. package/dist/{chunk-j2wys65b.js → chunk-8r26c3ah.js} +2 -2
  18. package/dist/chunk-bd2npvc0.js +2 -0
  19. package/dist/chunk-c2ccy7s1.js +2 -0
  20. package/dist/chunk-cqy1f504.js +3 -0
  21. package/dist/{chunk-c195vfyf.js → chunk-fet56t0p.js} +2 -2
  22. package/dist/chunk-ftv7ktch.js +2 -0
  23. package/dist/{chunk-7yd4gnqy.js → chunk-gbbkvps6.js} +2 -2
  24. package/dist/chunk-gx33dcgq.js +2 -0
  25. package/dist/chunk-ha2jjx02.js +3 -0
  26. package/dist/chunk-jfqdhrwq.js +8 -0
  27. package/dist/chunk-k63rmfca.js +2 -0
  28. package/dist/chunk-k8rqbg4p.js +11 -0
  29. package/dist/chunk-n9azxffj.js +2 -0
  30. package/dist/chunk-sjmj8mb1.js +3 -0
  31. package/dist/{chunk-jk62hdff.js → chunk-t1v0nj69.js} +2 -2
  32. package/dist/chunk-x3xbed4x.js +2 -0
  33. package/dist/css/CSSOM.d.ts +0 -3
  34. package/dist/css/cascade.d.ts +2 -13
  35. package/dist/css/media.d.ts +38 -0
  36. package/dist/index.d.ts +10 -1
  37. package/dist/index.js +15 -15
  38. package/dist/jsdom/index.js +1 -1
  39. package/dist/matchers.d.ts +60 -3
  40. package/dist/matchers.js +2 -2
  41. package/dist/network/RequestInterceptor.d.ts +27 -0
  42. package/dist/register.js +1 -1
  43. package/dist/window/Window.d.ts +8 -0
  44. package/package.json +1 -1
  45. package/dist/chunk-182syxjh.js +0 -2
  46. package/dist/chunk-3d9kwyx0.js +0 -3
  47. package/dist/chunk-7c8vh5me.js +0 -11
  48. package/dist/chunk-a7z9k802.js +0 -2
  49. package/dist/chunk-ew1knb9c.js +0 -2
  50. package/dist/chunk-gkeyaw9g.js +0 -2
  51. package/dist/chunk-ptess7d1.js +0 -3
  52. package/dist/chunk-qkxcr7jt.js +0 -2
  53. package/dist/chunk-rr2grqs5.js +0 -2
  54. package/dist/chunk-t7xttfys.js +0 -2
  55. package/dist/chunk-vxcq2fte.js +0 -10
  56. package/dist/chunk-wktrnbx6.js +0 -2
  57. package/dist/chunk-yqxxa2ez.js +0 -2
package/CHANGELOG.md CHANGED
@@ -1,3 +1,30 @@
1
+ [Compare changes](https://github.com/stacksjs/very-happy-dom/compare/v0.2.0...HEAD)
2
+
3
+ ## 🚀 Features
4
+
5
+ - **matchers**: add the aria, CSS and property matchers ([7e203bd](https://github.com/stacksjs/very-happy-dom/commit/7e203bd)) _(by glennmichael123 <gtorregosa@gmail.com>)_ ([#1618](https://github.com/stacksjs/very-happy-dom/issues/1618), [#1608](https://github.com/stacksjs/very-happy-dom/issues/1608), [#1602](https://github.com/stacksjs/very-happy-dom/issues/1602))
6
+ - **page**: add dialog events for alert, confirm and prompt ([5de9417](https://github.com/stacksjs/very-happy-dom/commit/5de9417)) _(by glennmichael123 <gtorregosa@gmail.com>)_ ([#1612](https://github.com/stacksjs/very-happy-dom/issues/1612), [#1602](https://github.com/stacksjs/very-happy-dom/issues/1602))
7
+ - **locator**: add filter({has}), or() and and() ([c10931d](https://github.com/stacksjs/very-happy-dom/commit/c10931d)) _(by glennmichael123 <gtorregosa@gmail.com>)_ ([#1619](https://github.com/stacksjs/very-happy-dom/issues/1619))
8
+ - **locator**: add boundingBox and scrollIntoViewIfNeeded ([9f9055b](https://github.com/stacksjs/very-happy-dom/commit/9f9055b)) _(by glennmichael123 <gtorregosa@gmail.com>)_ ([#1609](https://github.com/stacksjs/very-happy-dom/issues/1609), [#1600](https://github.com/stacksjs/very-happy-dom/issues/1600), [#1617](https://github.com/stacksjs/very-happy-dom/issues/1617))
9
+ - **page**: add setContent, and fix the document parse it exposed ([a5e2bd4](https://github.com/stacksjs/very-happy-dom/commit/a5e2bd4)) _(by glennmichael123 <gtorregosa@gmail.com>)_ ([#1610](https://github.com/stacksjs/very-happy-dom/issues/1610), [#1595](https://github.com/stacksjs/very-happy-dom/issues/1595))
10
+ - **locator**: add the form-control actions ([d1068b0](https://github.com/stacksjs/very-happy-dom/commit/d1068b0)) _(by glennmichael123 <gtorregosa@gmail.com>)_ ([#1608](https://github.com/stacksjs/very-happy-dom/issues/1608), [#1615](https://github.com/stacksjs/very-happy-dom/issues/1615), [#1616](https://github.com/stacksjs/very-happy-dom/issues/1616))
11
+ - **locator**: add evaluate, evaluateAll and the all*Text helpers ([3549710](https://github.com/stacksjs/very-happy-dom/commit/3549710)) _(by glennmichael123 <gtorregosa@gmail.com>)_ ([#1607](https://github.com/stacksjs/very-happy-dom/issues/1607))
12
+ - **page**: add the waitFor* family for page events ([4039cb7](https://github.com/stacksjs/very-happy-dom/commit/4039cb7)) _(by glennmichael123 <gtorregosa@gmail.com>)_ ([#1606](https://github.com/stacksjs/very-happy-dom/issues/1606))
13
+
14
+ ## 🐛 Bug Fixes
15
+
16
+ - **dom**: collapse whitespace in innerText, and resolve its visibility ([6589fe0](https://github.com/stacksjs/very-happy-dom/commit/6589fe0)) _(by glennmichael123 <gtorregosa@gmail.com>)_ ([#1614](https://github.com/stacksjs/very-happy-dom/issues/1614), [#1600](https://github.com/stacksjs/very-happy-dom/issues/1600), [#1607](https://github.com/stacksjs/very-happy-dom/issues/1607))
17
+ - **css**: apply @media rules, and add emulateMedia ([f67bf7a](https://github.com/stacksjs/very-happy-dom/commit/f67bf7a)) _(by glennmichael123 <gtorregosa@gmail.com>)_ ([#1611](https://github.com/stacksjs/very-happy-dom/issues/1611), [#1600](https://github.com/stacksjs/very-happy-dom/issues/1600))
18
+ - **page**: make waitForSelector throw on timeout ([32c5f0e](https://github.com/stacksjs/very-happy-dom/commit/32c5f0e)) _(by glennmichael123 <gtorregosa@gmail.com>)_ ([#1605](https://github.com/stacksjs/very-happy-dom/issues/1605))
19
+ - **keyboard**: parse modifier combinations and populate code ([ada3489](https://github.com/stacksjs/very-happy-dom/commit/ada3489)) _(by glennmichael123 <gtorregosa@gmail.com>)_ ([#1615](https://github.com/stacksjs/very-happy-dom/issues/1615))
20
+ - **aria**: treat a declared zero size as hidden ([480899c](https://github.com/stacksjs/very-happy-dom/commit/480899c)) _(by glennmichael123 <gtorregosa@gmail.com>)_ ([#1617](https://github.com/stacksjs/very-happy-dom/issues/1617), [#1604](https://github.com/stacksjs/very-happy-dom/issues/1604), [#1601](https://github.com/stacksjs/very-happy-dom/issues/1601))
21
+ - **aria**: read checked state through aria-checked ([524698f](https://github.com/stacksjs/very-happy-dom/commit/524698f)) _(by glennmichael123 <gtorregosa@gmail.com>)_ ([#1616](https://github.com/stacksjs/very-happy-dom/issues/1616))
22
+ - **routing**: stop setRequestInterception(false) from killing routes ([7537569](https://github.com/stacksjs/very-happy-dom/commit/7537569)) _(by glennmichael123 <gtorregosa@gmail.com>)_ ([#1613](https://github.com/stacksjs/very-happy-dom/issues/1613))
23
+
24
+ ## Contributors
25
+
26
+ - _glennmichael123 <gtorregosa@gmail.com>_
27
+
1
28
  [Compare changes](https://github.com/stacksjs/very-happy-dom/compare/v0.1.12...HEAD)
2
29
 
3
30
  ## 🚀 Features
package/README.md CHANGED
@@ -146,11 +146,37 @@ The one-shot rewrite — `expect(await page.getByRole('alert').textContent()).to
146
146
  drains is a race. `.not` waits for the opposite to become true rather than
147
147
  inverting a single sample.
148
148
 
149
+ Checked state is read through ARIA, so a design system's
150
+ `<div role="checkbox" aria-checked>` answers truthfully — `isChecked()`,
151
+ `getByRole({ checked })`, `check()`/`uncheck()` and `toBeChecked()` all go through
152
+ one predicate. `locator.checkedState()` returns `'mixed'` for a tri-state control,
153
+ and `toBeChecked({ indeterminate: true })` asserts it. `toBeChecked()` refuses an
154
+ element with no checked state rather than answering `false`, so
155
+ `.not.toBeChecked()` cannot pass against a plain `<div>`.
156
+
149
157
  Locators: `toBeAttached`, `toBeVisible`, `toBeHidden`, `toHaveCount`,
150
- `toHaveText`, `toContainText`, `toHaveValue`, `toHaveAttribute`, `toHaveClass`,
151
- `toBeEnabled`, `toBeDisabled`, `toBeChecked`, `toBeEditable`, `toBeFocused`,
152
- `toBeEmpty`. Pages: `toHaveURL`, `toHaveTitle`. Each takes an optional
153
- `{ timeout }`, defaulting to `page.setDefaultTimeout()`.
158
+ `toHaveText`, `toContainText`, `toHaveValue`, `toHaveValues`, `toHaveAttribute`,
159
+ `toHaveClass`, `toHaveId`, `toHaveCSS`, `toHaveJSProperty`, `toHaveRole`,
160
+ `toHaveAccessibleName`, `toHaveAccessibleDescription`, `toBeEnabled`,
161
+ `toBeDisabled`, `toBeChecked`, `toBeEditable`, `toBeFocused`, `toBeEmpty`.
162
+ Pages: `toHaveURL`, `toHaveTitle`. Each takes an optional `{ timeout }`,
163
+ defaulting to `page.setDefaultTimeout()`.
164
+
165
+ `toHaveRole` and `toHaveAccessibleName` assert what `getByRole` queries on, which
166
+ is how a failing role query becomes diagnosable. `toHaveCSS` compares values **as
167
+ the engine reports them**, so `'red'` will not match a computed `'rgb(255, 0, 0)'`
168
+ — use a RegExp. `toHaveJSProperty` takes a path, so `validity.valueMissing` works.
169
+
170
+ `toBeInViewport` is deliberately absent: with no layout pass a bounding box has no
171
+ position, so it could only ever answer "yes".
172
+
173
+ An element is hidden by `display: none`, `visibility: hidden`, the `hidden`
174
+ attribute, or a **declared** zero size — `height: 0`, `max-height: 0` and the
175
+ like, which is how a closed accordion or drawer is built when the author wants a
176
+ transition. Declared is the operative word: with no layout pass an element nobody
177
+ sized also measures `0 x 0`, so a zero box means *unknown* rather than
178
+ *collapsed*, and only a declaration is trusted. That is narrower than
179
+ Playwright's non-empty-bounding-box rule, deliberately.
154
180
 
155
181
  Locator actions auto-wait on the same terms — present, rendered and not
156
182
  disabled — and `locator.waitFor({ state })` covers `attached`, `detached`,
@@ -158,6 +184,196 @@ disabled — and `locator.waitFor({ state })` covers `attached`, `detached`,
158
184
  stable (not mid-animation) and receives-events (not covered by another element).
159
185
  Both need a layout pass.
160
186
 
187
+ ### Rendered Text
188
+
189
+ `innerText` returns text as rendered: hidden subtrees left out, whitespace
190
+ collapsed, block elements separated by newlines, `<br>` honoured. Visibility is
191
+ resolved through the cascade, so a `display: none` from a stylesheet is excluded —
192
+ not only an inline one. `white-space: pre`, `pre-wrap` and `break-spaces` keep
193
+ their text verbatim, `pre-line` keeps newlines, and a `<pre>` element counts as
194
+ `pre` with no rule needed.
195
+
196
+ `textContent` is unchanged: raw, untrimmed, uncollapsed.
197
+
198
+ ```typescript
199
+ document.body.innerHTML = '<p> Save changes </p>'
200
+ document.querySelector('p').textContent // ' Save changes '
201
+ document.querySelector('p').innerText // 'Save changes'
202
+ ```
203
+
204
+ ### Dialogs
205
+
206
+ ```typescript
207
+ page.on('dialog', dialog => dialog.accept()) // confirm() returns true
208
+ page.on('dialog', dialog => dialog.accept('Ridge')) // prompt() returns 'Ridge'
209
+ page.on('dialog', dialog => dialog.dismiss()) // false / null
210
+ ```
211
+
212
+ Without a handler every dialog is dismissed, which is Playwright's default and
213
+ what the old hardcoded `false`/`null` did — so nothing changes unless you opt in.
214
+ `dialog` carries `type`, `message` and `defaultValue`, so a test can assert *which*
215
+ dialog appeared, including an `alert()`.
216
+
217
+ The decision must be made **synchronously**: `confirm()` is a synchronous DOM API,
218
+ so a handler that awaits before deciding answers after the page has already acted
219
+ on the default. That throws rather than letting the wrong answer through.
220
+ `dialog.accept()` records the decision immediately, so `async dialog =>
221
+ await dialog.accept()` is fine — it is awaiting *before* deciding that is not.
222
+
223
+ ### Combining Locators
224
+
225
+ ```typescript
226
+ // Pick a row by what is inside it, then act within that row
227
+ const row = page.getByRole('row').filter({ has: page.getByText('Ridge Loop') })
228
+ await row.getByRole('button', { name: 'Delete' }).click()
229
+
230
+ // Two possible outcomes, without racing them
231
+ await expect(page.getByRole('alert').or(page.getByText('Saved'))).toBeVisible()
232
+
233
+ // Only the elements both match
234
+ await page.getByRole('button').and(page.locator('.primary')).click()
235
+ ```
236
+
237
+ `filter({ has })` and `{ hasNot }` query the inner locator **from each candidate**,
238
+ not from the page — so a match elsewhere does not qualify a row. `hasText` is not
239
+ a substitute: it compares the candidate's whole text, so a neighbouring column
240
+ containing the string matches too, and it cannot ask about a role.
241
+
242
+ `filter()` throws on an unrecognised key. Ignoring one is how `filter({ has })`
243
+ used to look like it worked while matching every candidate. `or()` returns
244
+ document order with duplicates removed.
245
+
246
+ ### Media Queries
247
+
248
+ ```typescript
249
+ await page.emulateMedia({ colorScheme: 'dark' })
250
+ await page.emulateMedia({ media: 'print' })
251
+ await page.emulateMedia({ reducedMotion: 'reduce', forcedColors: 'active' })
252
+ await page.emulateMedia({ colorScheme: null }) // back to the browser's setting
253
+ ```
254
+
255
+ `@media` blocks are parsed into real `CSSMediaRule`s and applied when their
256
+ condition holds, so a dark-mode or responsive stylesheet takes effect in
257
+ `getComputedStyle`, in `getBoundingClientRect` and in the locator queries that
258
+ read visibility. `matchMedia` and the cascade evaluate the query with the same
259
+ code, so they cannot disagree about the same document.
260
+
261
+ Understood features: `prefers-color-scheme`, `prefers-reduced-motion`,
262
+ `forced-colors`, `orientation`, and the `width`/`height` family with `px`, `em`
263
+ and `rem`. Media types (`screen`, `print`, `all`), `and`, comma lists, `not` and
264
+ `only` all work. Any other feature — `(hover: hover)`, for instance — makes its
265
+ query false, as an unrecognised feature does in a browser.
266
+
267
+ ### Geometry
268
+
269
+ ```typescript
270
+ const box = await page.locator('.card').boundingBox() // { x, y, width, height } | null
271
+ ```
272
+
273
+ Width and height are real — the rect resolves through the cascade, so a size from
274
+ a stylesheet is reported. **`x` and `y` are always `0`**: there is no layout pass,
275
+ so position is not computed, and overlap, ordering and is-this-above-the-fold
276
+ cannot be asked here. `null` is returned for an element that is not rendered.
277
+ `scrollIntoViewIfNeeded()` resolves and fires a `scroll` event, which is as much
278
+ as is meaningful without layout.
279
+
280
+ ### Setting Page Content
281
+
282
+ ```typescript
283
+ await page.setContent('<button>Save</button>')
284
+ await expect(page.getByRole('button')).toBeVisible()
285
+ ```
286
+
287
+ A bare fragment becomes the body; a full `<!DOCTYPE html><html><head>…` string
288
+ lands intact. It replaces the whole document rather than filling the body, fires
289
+ `domcontentloaded` and `load` so an assertion straight afterwards needs no wait,
290
+ and leaves the URL alone — setting content is not navigating.
291
+
292
+ ### Form Controls
293
+
294
+ ```typescript
295
+ await page.locator('#area').selectOption('north') // by value
296
+ await page.locator('#area').selectOption({ label: 'North' }) // by option text
297
+ await page.locator('#photos').setInputFiles({ name: 'trail.gpx', buffer: gpx })
298
+ await page.locator('#photos').setInputFiles('./fixtures/trail.gpx') // read from disk
299
+ await page.locator('#search').press('Control+Enter')
300
+ await page.locator('#search').clear()
301
+ await page.locator('#row').dblclick()
302
+ await page.locator('#terms').setChecked(true)
303
+ ```
304
+
305
+ `selectOption` fires `input` then `change`, which is the point: assigning
306
+ `select.value` by hand fires neither, so a component listening for it never
307
+ updates and the test passes against a DOM the app never saw. `setInputFiles`
308
+ builds the `FileList` that assigning `input.files` requires. Passing several
309
+ values to a non-`multiple` control is refused rather than silently truncated.
310
+ `press` focuses the element first — `page.keyboard` only reaches whatever already
311
+ has focus.
312
+
313
+ ### Keyboard
314
+
315
+ ```typescript
316
+ await page.keyboard.press('Control+Enter') // key 'Enter', ctrlKey true
317
+ await page.keyboard.press('Shift+a') // types 'A'
318
+ await page.keyboard.down('Shift')
319
+ await page.keyboard.press('Tab') // shiftKey true
320
+ await page.keyboard.up('Shift')
321
+ ```
322
+
323
+ `Control`/`Ctrl`, `Shift`, `Alt`/`Option` and `Meta`/`Cmd`/`Command` are
324
+ recognised, several at a time; modifiers go down in order and come up in reverse.
325
+ `event.code` is populated (`KeyA`, `Digit1`, `ArrowDown`, `ShiftLeft`). A key held
326
+ with Control, Alt or Meta does not insert text — `Control+A` selects rather than
327
+ typing an "a" — while Shift produces the capital. Only a known modifier name is
328
+ consumed, so `press('Enter')` and even a nonsense `press('a+b')` are passed
329
+ through as literal keys.
330
+
331
+ ### Running Code Against a Locator
332
+
333
+ ```typescript
334
+ const ids = await page.locator('tbody tr').evaluateAll(els => els.map(el => el.dataset.id))
335
+ const label = await page.getByRole('button').evaluate(el => el.dataset.state)
336
+
337
+ await page.locator('li').allTextContents() // raw textContent, per match
338
+ await page.locator('li').allInnerTexts() // rendered text: hidden subtrees left out
339
+ ```
340
+
341
+ `evaluate()` is strict and waits for the element to be attached; `evaluateAll()`
342
+ is neither, because many matches is its expected case and none is a legitimate
343
+ `[]`. The element is the real node, so writing through it changes the document.
344
+
345
+ Callbacks are recompiled against the frame's window and **cannot see their
346
+ closure**, the same as in Playwright — pass anything they need as the second
347
+ argument. `page.$eval` / `page.$$eval` exist for ports, but take the first match
348
+ rather than refusing an ambiguous selector.
349
+
350
+ ### Waiting on Page Events
351
+
352
+ ```typescript
353
+ const [response] = await Promise.all([
354
+ page.waitForResponse('**/api/trails'),
355
+ page.getByRole('button', { name: 'Search' }).click(),
356
+ ])
357
+
358
+ expect((await response.json()).trails).toHaveLength(3)
359
+ ```
360
+
361
+ Subscribe *before* the action, as above. An event that has already fired is not
362
+ replayed — the same as Playwright, and it matters more here because `goto()`
363
+ emits synchronously and returns, so there is no window at all.
364
+
365
+ - `page.waitForEvent(event, predicateOrOptions)` — the primitive, for any of the
366
+ six page events.
367
+ - `page.waitForResponse(urlOrPredicate)` / `waitForRequest(...)` — a glob or
368
+ RegExp matches the URL through the same matcher `route()` uses; a function
369
+ receives the event itself, so it can read `status` too. Responses are reported
370
+ for navigations *and* for `fetch` calls made by page code. Requests need
371
+ interception on, which `page.route()` does as a side effect.
372
+ - `page.waitForLoadState('load' | 'domcontentloaded')` — returns at once if the
373
+ state has already been reached. `networkidle` throws rather than pretending:
374
+ nothing here tracks in-flight requests.
375
+ - `page.waitForURL(urlOrPredicate)` — polled, so a `pushState` counts.
376
+
161
377
  ### Browser Context
162
378
 
163
379
  ```typescript
@@ -11,6 +11,19 @@ export declare function computeRole(element: ElementLike): string | null;
11
11
  * that allow it, then `title`, and `placeholder` as a last resort for fields.
12
12
  */
13
13
  export declare function accessibleName(element: ElementLike): string;
14
+ /**
15
+ * The description a screen reader would announce after the name.
16
+ *
17
+ * `aria-describedby` first, resolved through the document the same way
18
+ * `accessibleName` resolves `aria-labelledby`; then `aria-description`, then
19
+ * `title`. An element with no description answers with an empty string rather than
20
+ * null, matching `accessibleName`.
21
+ *
22
+ * `title` is last because it is also a name source: an element whose only text is
23
+ * a `title` uses it as its name, and using it as the description too would have
24
+ * the same string answer both questions.
25
+ */
26
+ export declare function accessibleDescription(element: ElementLike): string;
14
27
  /** Heading depth, or null when the element is not a heading. */
15
28
  export declare function headingLevel(element: ElementLike): number | null;
16
29
  /**
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The element's checked state, or `undefined` when it is not checkable at all.
3
+ *
4
+ * The native property wins where there is one: it is the live state, and an
5
+ * author who writes `aria-checked` on a real `<input type=checkbox>` is
6
+ * duplicating something the browser already tracks.
7
+ */
8
+ export declare function checkedState(element: ElementLike): CheckedState | undefined;
9
+ /**
10
+ * Checked as a plain boolean, which is what `isChecked()` reports.
11
+ *
12
+ * `mixed` is neither checked nor unchecked, and a boolean cannot say so, so it
13
+ * answers `false` here. `toBeChecked({ checked: 'mixed' })` is where the third
14
+ * state can be asked about without lying.
15
+ */
16
+ export declare function isChecked(element: ElementLike): boolean;
17
+ /**
18
+ * The element's selected state, or `undefined` when it is not selectable.
19
+ *
20
+ * `<option selected>` keeps a live property, the same as a native checkbox;
21
+ * everything else goes through `aria-selected`.
22
+ */
23
+ export declare function selectedState(element: ElementLike): boolean | undefined;
24
+ /** Selected as a plain boolean. */
25
+ export declare function isSelected(element: ElementLike): boolean;
26
+ declare interface ElementLike {
27
+ tagName?: string
28
+ checked?: boolean
29
+ selected?: boolean
30
+ getAttribute?: (name: string) => string | null
31
+ }
32
+ /** `mixed` is a real third state, for a tri-state checkbox. */
33
+ export type CheckedState = boolean | 'mixed';
@@ -1,8 +1,18 @@
1
1
  /**
2
2
  * Whether the element is painted: nothing in its ancestor chain hides it with
3
- * `display: none`, `visibility: hidden|collapse` or the `hidden` attribute.
3
+ * `display: none`, `visibility: hidden|collapse`, the `hidden` attribute, or a
4
+ * declared zero size.
4
5
  */
5
6
  export declare function isRendered(element: ElementLike): boolean;
7
+ /**
8
+ * Whether the element's own style hides it, ignoring its ancestors.
9
+ *
10
+ * @internal Shared with `innerText`, which walks the tree itself and so has
11
+ * already dealt with ancestors by not descending into a hidden one. Calling the
12
+ * ancestor-walking form per node would make that walk quadratic, and having two
13
+ * definitions of "hidden" is the thing #1601 set out to avoid.
14
+ */
15
+ export declare function hidesItself(element: ElementLike, style?: any): boolean;
6
16
  /**
7
17
  * Whether the element reaches the accessibility tree: painted, and not marked
8
18
  * `aria-hidden` on itself or an ancestor.
@@ -23,6 +33,19 @@ export declare function isExposedToAria(element: ElementLike): boolean;
23
33
  * Both walk the ancestor chain, because hiding a container hides everything
24
34
  * inside it, and both resolve through `getComputedStyle()` so a rule from a
25
35
  * stylesheet counts, not only an inline style.
36
+ *
37
+ * Collapsing an element to nothing hides it too (#1617). `height: 0` is how a
38
+ * closed accordion, disclosure or drawer is built when the author wants a
39
+ * transition, since `display: none` cannot be animated — and that markup was
40
+ * fully reachable, so a role query matched a button inside a shut drawer and,
41
+ * since #1604, clicking it succeeded.
42
+ *
43
+ * This is narrower than Playwright's rule, deliberately. Playwright asks whether
44
+ * the bounding box is non-empty; there is no layout pass here, so an element
45
+ * nobody sized reports `0 × 0` — a plain `<button>Save</button>` with no CSS has
46
+ * an empty box. Zero therefore means *unknown*, not *collapsed*, and only a
47
+ * declared zero can be trusted to mean the latter. A reader who knows
48
+ * Playwright's definition should not assume the box is being consulted.
26
49
  */
27
50
  declare interface ElementLike {
28
51
  nodeType?: number
@@ -19,6 +19,12 @@ export declare class BrowserContext {
19
19
  grantPermissions(permissions: string[], _options?: { origin?: string }): Promise<void>;
20
20
  clearPermissions(): Promise<void>;
21
21
  setGeolocation(geolocation: { latitude: number, longitude: number, accuracy?: number } | null): Promise<void>;
22
+ emulateMedia(options?: {
23
+ media?: 'screen' | 'print' | null
24
+ colorScheme?: 'light' | 'dark' | null
25
+ reducedMotion?: 'reduce' | 'no-preference' | null
26
+ forcedColors?: 'active' | 'none' | null
27
+ }): Promise<void>;
22
28
  setOffline(offline: boolean): Promise<void>;
23
29
  setExtraHTTPHeaders(headers: Record<string, string>): Promise<void>;
24
30
  addInitScript(script: string | ((...args: any[]) => any)): Promise<void>;
@@ -27,6 +33,12 @@ export declare class BrowserContext {
27
33
  permissions: string[] | null
28
34
  geolocation: { latitude: number, longitude: number, accuracy?: number } | null
29
35
  offline: boolean
36
+ media: {
37
+ type?: 'screen' | 'print'
38
+ colorScheme?: 'light' | 'dark'
39
+ reducedMotion?: 'reduce' | 'no-preference'
40
+ forcedColors?: 'active' | 'none'
41
+ }
30
42
  };
31
43
  _extraHTTPHeaders(): Record<string, string>;
32
44
  _initScriptSources(): Array<string | ((...args: any[]) => any)>;
@@ -23,6 +23,8 @@ export declare class BrowserFrame {
23
23
  waitForNavigation(): Promise<void>;
24
24
  abort(): Promise<void>;
25
25
  evaluate(code: string | ((...args: any[]) => any), arg?: any): any;
26
+ _evaluateWith(code: string | ((...args: any[]) => any), args: any[]): any;
27
+ setContent(html: string): void;
26
28
  goto(url: string, options?: { referer?: string }): Promise<Response | null>;
27
29
  reload(): Promise<Response | null>;
28
30
  goBack(): Promise<Response | null>;
@@ -1,16 +1,39 @@
1
1
  import { BrowserFrame } from './BrowserFrame';
2
2
  import { Buffer } from 'node:buffer';
3
+ import { isChecked } from '../aria/state';
3
4
  import { Locator } from './Locator';
4
5
  import type { BrowserContext } from './BrowserContext';
5
- import type { GetByRoleOptions, GetByTextOptions } from './Locator';
6
+ import type { DialogOutcome, DialogType } from './Dialog';
7
+ import type { GetByRoleOptions, GetByTextOptions, WaitForState } from './Locator';
6
8
  import type { RouteHandler, RoutePattern } from '../network/routing';
7
9
  export declare interface IBrowserPageViewport {
8
10
  width: number
9
11
  height: number
10
12
  }
11
- export type PageEventType = 'console' | 'request' | 'response' | 'error' | 'load' | 'domcontentloaded';
13
+ export declare interface WaitForEventOptions {
14
+ predicate?: (event: any) => boolean
15
+ timeout?: number
16
+ }
17
+ export type PageEventType = 'console' | 'request' | 'response' | 'error' | 'load' | 'domcontentloaded' | 'dialog';
12
18
  // eslint-disable-next-line pickier/no-unused-vars
13
19
  export type PageEventHandler = (event: any) => void;
20
+ /**
21
+ * Which event to settle on.
22
+ *
23
+ * A string is a Playwright glob and a RegExp is used as written, both against
24
+ * the URL. A function receives the event itself — the `Response` or the request
25
+ * — rather than its URL, so it can look at the status too. That is Playwright's
26
+ * split, and it is why this cannot simply be a `RoutePattern`, whose function
27
+ * form takes a URL.
28
+ */
29
+ // eslint-disable-next-line pickier/no-unused-vars
30
+ export type EventTarget<T = any> = string | RegExp | ((event: T) => boolean);
31
+ /** The load states this page can actually report. */
32
+ export type LoadState = 'load' | 'domcontentloaded';
33
+ /** How a caller can name an `<option>`: by value, label, index, or bare value. */
34
+ export type SelectOptionValue = string | { value?: string, label?: string, index?: number }
35
+ /** A file to attach: a path on disk, or bytes with a name. */
36
+ export type InputFile = string | { name: string, mimeType?: string, buffer: Uint8Array | ArrayBuffer | string }
14
37
  /**
15
38
  * BrowserPage represents a browser page (tab or popup window)
16
39
  * Compatible with Happy DOM's BrowserPage API
@@ -31,6 +54,7 @@ export declare class BrowserPage {
31
54
  waitUntilComplete(): Promise<void>;
32
55
  waitForNavigation(): Promise<void>;
33
56
  abort(): Promise<void>;
57
+ _evaluateWith(code: string | ((...args: any[]) => any), args: any[]): any;
34
58
  evaluate(code: string | ((...args: any[]) => any), arg?: any): any;
35
59
  setViewport(viewport: IBrowserPageViewport): void;
36
60
  goto(url: string, options?: { referer?: string }): Promise<Response | null>;
@@ -41,21 +65,45 @@ export declare class BrowserPage {
41
65
  _applyEmulation(): void;
42
66
  _runInitScripts(): void;
43
67
  setDefaultTimeout(timeout: number): void;
68
+ setContent(html: string): Promise<void>;
69
+ emulateMedia(options?: {
70
+ media?: 'screen' | 'print' | null
71
+ colorScheme?: 'light' | 'dark' | null
72
+ reducedMotion?: 'reduce' | 'no-preference' | null
73
+ forcedColors?: 'active' | 'none' | null
74
+ }): Promise<void>;
44
75
  _defaultTimeoutMs(): number;
45
- waitForSelector(selector: string, options?: { timeout?: number, visible?: boolean }): Promise<any | null>;
76
+ waitForSelector(selector: string, options?: { timeout?: number, state?: WaitForState, visible?: boolean }): Promise<any | null>;
46
77
  waitForFunction(options?: { timeout?: number, polling?: number | 'raf' }): Promise<any>;
47
78
  waitForTimeout(ms: number): Promise<void>;
79
+ waitForEvent(event: PageEventType, optionsOrPredicate?: WaitForEventOptions | ((event: any) => boolean)): Promise<any>;
80
+ waitForResponse(urlOrPredicate: EventTarget, options?: { timeout?: number }): Promise<any>;
81
+ waitForRequest(urlOrPredicate: EventTarget, options?: { timeout?: number }): Promise<any>;
82
+ waitForLoadState(state?: LoadState, options?: { timeout?: number }): Promise<void>;
83
+ waitForURL(urlOrPredicate: EventTarget<string>, options?: { timeout?: number }): Promise<void>;
48
84
  click(selector: string, options?: { delay?: number, button?: 'left' | 'right' | 'middle' }): Promise<void>;
49
85
  type(selector: string, text: string, options?: { delay?: number }): Promise<void>;
50
86
  _typeIntoElement(element: any, text: string, options?: { delay?: number }): Promise<void>;
51
87
  fill(selector: string, value: string): Promise<void>;
88
+ _selectOptions(element: any, wanted: SelectOptionValue[]): Promise<string[]>;
89
+ _setInputFiles(element: any, files: InputFile[]): Promise<void>;
90
+ _dblclickElement(element: any): Promise<void>;
52
91
  _fillElement(element: any, value: string): Promise<void>;
92
+ selectOption(selector: string, values: SelectOptionValue | SelectOptionValue[] | null): Promise<string[]>;
93
+ setInputFiles(selector: string, files: InputFile | InputFile[] | null): Promise<void>;
94
+ press(selector: string, key: string, options?: { delay?: number }): Promise<void>;
95
+ clear(selector: string): Promise<void>;
96
+ dblclick(selector: string): Promise<void>;
97
+ check(selector: string): Promise<void>;
98
+ uncheck(selector: string): Promise<void>;
53
99
  focus(selector: string): Promise<void>;
54
100
  blur(selector: string): Promise<void>;
55
101
  hover(selector: string): Promise<void>;
56
102
  _hoverElement(element: any): Promise<void>;
57
103
  get keyboard(): {
58
104
  press: (key: string, options?: { delay?: number }) => Promise<void>
105
+ down: (key: string) => Promise<void>
106
+ up: (key: string) => Promise<void>
59
107
  type: (text: string, options?: { delay?: number }) => Promise<void>
60
108
  };
61
109
  get mouse(): {
@@ -72,6 +120,8 @@ export declare class BrowserPage {
72
120
  getByAltText(text: string | RegExp, options?: GetByTextOptions): Locator;
73
121
  getByTitle(text: string | RegExp, options?: GetByTextOptions): Locator;
74
122
  getByTestId(testId: string | RegExp): Locator;
123
+ $eval(selector: string, fn: string | ((element: any, arg?: any) => any), arg?: any): Promise<any>;
124
+ $$eval(selector: string, fn: string | ((elements: any[], arg?: any) => any), arg?: any): Promise<any>;
75
125
  title(): Promise<string>;
76
126
  textContent(selector: string): Promise<string | null>;
77
127
  innerText(selector: string): Promise<string>;
@@ -87,6 +137,7 @@ export declare class BrowserPage {
87
137
  on(event: PageEventType, handler: PageEventHandler): void;
88
138
  off(event: PageEventType, handler: PageEventHandler): void;
89
139
  emit(event: PageEventType, data: any): void;
140
+ _requestDialog(type: DialogType, message: string, defaultValue?: string): DialogOutcome;
90
141
  _emitConsole(type: string, ...args: any[]): void;
91
142
  _emitRequest(request: any): void;
92
143
  _emitResponse(response: any): void;
@@ -94,7 +145,7 @@ export declare class BrowserPage {
94
145
  dragAndDrop(source: string, target: string, options?: { delay?: number }): Promise<void>;
95
146
  route(url: RoutePattern, handler: RouteHandler): Promise<void>;
96
147
  unroute(url?: RoutePattern, handler?: RouteHandler): Promise<void>;
97
- _ensureRouting(): void;
148
+ _syncRouting(): void;
98
149
  setRequestInterception(enabled: boolean): Promise<void>;
99
150
  screenshot(options?: {
100
151
  type?: 'png' | 'jpeg' | 'webp'
@@ -0,0 +1,36 @@
1
+ /** What a dialog was answered with. */
2
+ export declare interface DialogOutcome {
3
+ accepted: boolean
4
+ text: string | null
5
+ }
6
+ /**
7
+ * A dialog the page opened, and the decision a handler makes about it (#1612).
8
+ *
9
+ * `confirm()` returned a hardcoded `false`, so a confirm-gated action could only
10
+ * ever be cancelled:
11
+ *
12
+ * deleteButton.addEventListener('click', () => {
13
+ * if (!confirm('Delete this trail?')) return
14
+ * removeTrail() // unreachable, always
15
+ * })
16
+ *
17
+ * The test clicked the button, nothing happened, and the assertion failed with no
18
+ * hint that a dialog was involved. The "delete" half of every destructive flow
19
+ * was untestable, and destructive flows are the ones most worth a test.
20
+ *
21
+ * `alert()` was the benign one — a no-op is close enough to auto-dismissing —
22
+ * except that there was no way to assert an alert had happened at all.
23
+ */
24
+ /** `beforeunload` is deliberately absent: nothing here fires it. */
25
+ export type DialogType = 'alert' | 'confirm' | 'prompt';
26
+ export declare class Dialog {
27
+ readonly type: DialogType;
28
+ readonly message: string;
29
+ readonly or an empty string. */
30
+ defaultValue?: string;
31
+ constructor(type: DialogType, message: string);
32
+ accept(promptText?: string): Promise<void>;
33
+ dismiss(): Promise<void>;
34
+ get _wasSettled(): boolean;
35
+ _outcome(): DialogOutcome;
36
+ }
@@ -1,4 +1,6 @@
1
- import { accessibleName } from '../aria/roles';
1
+ import { accessibleDescription, accessibleName } from '../aria/roles';
2
+ import { checkedState, isChecked } from '../aria/state';
3
+ import type { InputFile, SelectOptionValue } from './BrowserPage';
2
4
  /** @internal Collapse whitespace, the way Playwright compares rendered text. */
3
5
  export declare function normalize(value: string | null | undefined): string;
4
6
  /**
@@ -43,6 +45,8 @@ export declare interface GetByTextOptions {
43
45
  export declare interface FilterOptions {
44
46
  hasText?: string | RegExp
45
47
  hasNotText?: string | RegExp
48
+ has?: Locator
49
+ hasNot?: Locator
46
50
  }
47
51
  export declare interface WaitForOptions {
48
52
  state?: WaitForState
@@ -52,8 +56,16 @@ export declare interface WaitForOptions {
52
56
  export declare interface ActionOptions {
53
57
  timeout?: number
54
58
  }
55
- /** Returns the current matches, re-evaluated on each call. */
56
- declare type Resolver = () => any[];
59
+ /**
60
+ * Returns the current matches, re-evaluated on each call.
61
+ *
62
+ * The optional root is what makes `filter({ has })` work. Playwright queries an
63
+ * inner locator *starting from the outer match*, not from the document, so a
64
+ * locator has to be answerable against an arbitrary element rather than only
65
+ * against the page. Every derived locator threads it through to its parent.
66
+ */
67
+ // eslint-disable-next-line pickier/no-unused-vars
68
+ declare type Resolver = (root?: any) => any[];
57
69
  /**
58
70
  * What a wait is waiting for.
59
71
  *
@@ -70,6 +82,9 @@ export declare class Locator {
70
82
  elementHandles(): Promise<any[]>;
71
83
  locator(selector: string): Locator;
72
84
  filter(options: FilterOptions): Locator;
85
+ or(other: Locator): Locator;
86
+ and(other: Locator): Locator;
87
+ get page(): any;
73
88
  nth(index: number): Locator;
74
89
  first(): Locator;
75
90
  last(): Locator;
@@ -87,11 +102,16 @@ export declare class Locator {
87
102
  innerHTML(): Promise<string>;
88
103
  inputValue(): Promise<string>;
89
104
  getAttribute(name: string): Promise<string | null>;
105
+ allTextContents(): Promise<string[]>;
106
+ allInnerTexts(): Promise<string[]>;
90
107
  ariaRole(): Promise<string | null>;
91
108
  accessibleName(): Promise<string>;
109
+ accessibleDescription(): Promise<string>;
110
+ selectedValues(): Promise<string[]>;
92
111
  isVisible(): Promise<boolean>;
93
112
  isHidden(): Promise<boolean>;
94
113
  isChecked(): Promise<boolean>;
114
+ checkedState(): Promise<boolean | 'mixed' | undefined>;
95
115
  isEnabled(): Promise<boolean>;
96
116
  isDisabled(): Promise<boolean>;
97
117
  isEditable(): Promise<boolean>;
@@ -102,5 +122,15 @@ export declare class Locator {
102
122
  fill(value: string, options?: ActionOptions): Promise<void>;
103
123
  type(text: string, options?: { delay?: number, timeout?: number }): Promise<void>;
104
124
  check(options?: ActionOptions): Promise<void>;
125
+ selectOption(values: SelectOptionValue | SelectOptionValue[] | null, options?: ActionOptions): Promise<string[]>;
126
+ setInputFiles(files: InputFile | InputFile[] | null, options?: ActionOptions): Promise<void>;
127
+ press(key: string, options?: ActionOptions & { delay?: number }): Promise<void>;
128
+ clear(options?: ActionOptions): Promise<void>;
129
+ dblclick(options?: ActionOptions): Promise<void>;
130
+ setChecked(checked: boolean, options?: ActionOptions): Promise<void>;
105
131
  uncheck(options?: ActionOptions): Promise<void>;
132
+ boundingBox(options?: ActionOptions): Promise<{ x: number, y: number, width: number, height: number } | null>;
133
+ scrollIntoViewIfNeeded(options?: ActionOptions): Promise<void>;
134
+ evaluate(fn: string | ((element: any, arg?: any) => any), arg?: any, options?: ActionOptions): Promise<any>;
135
+ evaluateAll(fn: string | ((elements: any[], arg?: any) => any), arg?: any): Promise<any>;
106
136
  }