feat(selectors): support optional "visible" property in all selectors (#129)

This commit is contained in:
Dmitry Gozman 2019-12-04 13:11:10 -08:00 committed by GitHub
parent e358b47f76
commit fc5898892b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 397 additions and 209 deletions

View file

@ -310,6 +310,9 @@
* [coverage.stopCSSCoverage()](#coveragestopcsscoverage) * [coverage.stopCSSCoverage()](#coveragestopcsscoverage)
* [coverage.stopJSCoverage()](#coveragestopjscoverage) * [coverage.stopJSCoverage()](#coveragestopjscoverage)
- [class: TimeoutError](#class-timeouterror) - [class: TimeoutError](#class-timeouterror)
- [class: Selector](#class-selector)
* [selector.selector](#selectorselector)
* [selector.visible](#selectorvisible)
<!-- GEN:stop --> <!-- GEN:stop -->
### Overview ### Overview
@ -1049,7 +1052,7 @@ Emitted when a request finishes successfully.
Emitted when a [response] is received. Emitted when a [response] is received.
#### page.$(selector) #### page.$(selector)
- `selector` <[string]> A [selector] to query page for - `selector` <[string]|[Selector]> A [selector] to query page for
- returns: <[Promise]<?[ElementHandle]>> - returns: <[Promise]<?[ElementHandle]>>
The method runs `document.querySelector` within the page. If no element matches the selector, the return value resolves to `null`. The method runs `document.querySelector` within the page. If no element matches the selector, the return value resolves to `null`.
@ -1057,7 +1060,7 @@ The method runs `document.querySelector` within the page. If no element matches
Shortcut for [page.mainFrame().$(selector)](#frameselector). Shortcut for [page.mainFrame().$(selector)](#frameselector).
#### page.$$(selector) #### page.$$(selector)
- `selector` <[string]> A [selector] to query page for - `selector` <[string]|[Selector]> A [selector] to query page for
- returns: <[Promise]<[Array]<[ElementHandle]>>> - returns: <[Promise]<[Array]<[ElementHandle]>>>
The method runs `document.querySelectorAll` within the page. If no elements match the selector, the return value resolves to `[]`. The method runs `document.querySelectorAll` within the page. If no elements match the selector, the return value resolves to `[]`.
@ -1065,7 +1068,7 @@ The method runs `document.querySelectorAll` within the page. If no elements matc
Shortcut for [page.mainFrame().$$(selector)](#frameselector-1). Shortcut for [page.mainFrame().$$(selector)](#frameselector-1).
#### page.$$eval(selector, pageFunction[, ...args]) #### page.$$eval(selector, pageFunction[, ...args])
- `selector` <[string]> A [selector] to query page for - `selector` <[string]|[Selector]> A [selector] to query page for
- `pageFunction` <[function]\([Array]<[Element]>\)> Function to be evaluated in browser context - `pageFunction` <[function]\([Array]<[Element]>\)> Function to be evaluated in browser context
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction` - `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
- returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction` - returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction`
@ -1080,7 +1083,7 @@ const divsCounts = await page.$$eval('div', divs => divs.length);
``` ```
#### page.$eval(selector, pageFunction[, ...args]) #### page.$eval(selector, pageFunction[, ...args])
- `selector` <[string]> A [selector] to query page for - `selector` <[string]|[Selector]> A [selector] to query page for
- `pageFunction` <[function]\([Element]\)> Function to be evaluated in browser context - `pageFunction` <[function]\([Element]\)> Function to be evaluated in browser context
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction` - `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
- returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction` - returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction`
@ -1145,7 +1148,7 @@ Get the browser the page belongs to.
Get the browser context that the page belongs to. Get the browser context that the page belongs to.
#### page.click(selector[, options]) #### page.click(selector[, options])
- `selector` <[string]> A [selector] to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked. - `selector` <[string]|[Selector]> A [selector] to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked.
- `options` <[Object]> - `options` <[Object]>
- `button` <"left"|"right"|"middle"> Defaults to `left`. - `button` <"left"|"right"|"middle"> Defaults to `left`.
- `clickCount` <[number]> defaults to 1. See [UIEvent.detail]. - `clickCount` <[number]> defaults to 1. See [UIEvent.detail].
@ -1192,7 +1195,7 @@ Gets the full HTML contents of the page, including the doctype.
- returns: <[Coverage]> - returns: <[Coverage]>
#### page.dblclick(selector[, options]) #### page.dblclick(selector[, options])
- `selector` <[string]> A [selector] to search for element to double click. If there are multiple elements satisfying the selector, the first will be double clicked. - `selector` <[string]|[Selector]> A [selector] to search for element to double click. If there are multiple elements satisfying the selector, the first will be double clicked.
- `options` <[Object]> - `options` <[Object]>
- `button` <"left"|"right"|"middle"> Defaults to `left`. - `button` <"left"|"right"|"middle"> Defaults to `left`.
- `delay` <[number]> Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0. - `delay` <[number]> Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0.
@ -1431,7 +1434,7 @@ const fs = require('fs');
``` ```
#### page.fill(selector, value) #### page.fill(selector, value)
- `selector` <[string]> A [selector] to query page for. - `selector` <[string]|[Selector]> A [selector] to query page for.
- `value` <[string]> Value to fill for the `<input>`, `<textarea>` or `[contenteditable]` element. - `value` <[string]> Value to fill for the `<input>`, `<textarea>` or `[contenteditable]` element.
- returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully filled. The promise will be rejected if there is no element matching `selector`. - returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully filled. The promise will be rejected if there is no element matching `selector`.
@ -1441,7 +1444,7 @@ If there's no text `<input>`, `<textarea>` or `[contenteditable]` element matchi
Shortcut for [page.mainFrame().fill()](#framefillselector-value) Shortcut for [page.mainFrame().fill()](#framefillselector-value)
#### page.focus(selector) #### page.focus(selector)
- `selector` <[string]> A [selector] of an element to focus. If there are multiple elements satisfying the selector, the first will be focused. - `selector` <[string]|[Selector]> A [selector] of an element to focus. If there are multiple elements satisfying the selector, the first will be focused.
- returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully focused. The promise will be rejected if there is no element matching `selector`. - returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully focused. The promise will be rejected if there is no element matching `selector`.
This method fetches an element with `selector` and focuses it. This method fetches an element with `selector` and focuses it.
@ -1506,7 +1509,7 @@ Navigate to the next page in history.
Shortcut for [page.mainFrame().goto(url, options)](#framegotourl-options) Shortcut for [page.mainFrame().goto(url, options)](#framegotourl-options)
#### page.hover(selector[, options]) #### page.hover(selector[, options])
- `selector` <[string]> A [selector] to search for element to hover. If there are multiple elements satisfying the selector, the first will be hovered. - `selector` <[string]|[Selector]> A [selector] to search for element to hover. If there are multiple elements satisfying the selector, the first will be hovered.
- `options` <[Object]> - `options` <[Object]>
- `relativePoint` <[Object]> A point to hover relative to the top-left corner of element padding box. If not specified, hovers over some visible point of the element. - `relativePoint` <[Object]> A point to hover relative to the top-left corner of element padding box. If not specified, hovers over some visible point of the element.
- x <[number]> - x <[number]>
@ -1575,7 +1578,7 @@ Page is guaranteed to have a main frame which persists during navigations.
> **NOTE** Screenshots take at least 1/6 second on OS X. See https://crbug.com/741689 for discussion. > **NOTE** Screenshots take at least 1/6 second on OS X. See https://crbug.com/741689 for discussion.
#### page.select(selector, ...values) #### page.select(selector, ...values)
- `selector` <[string]> A [selector] to query page for. - `selector` <[string]|[Selector]> A [selector] to query page for.
- `...values` <...[string]|[ElementHandle]|[Object]> Options to select. If the `<select>` has the `multiple` attribute, all matching options are selected, otherwise only the first option matching one of the passed options is selected. String values are equivalent to `{value:'string'}`. Option is considered matching if all specified properties match. - `...values` <...[string]|[ElementHandle]|[Object]> Options to select. If the `<select>` has the `multiple` attribute, all matching options are selected, otherwise only the first option matching one of the passed options is selected. String values are equivalent to `{value:'string'}`. Option is considered matching if all specified properties match.
- `value` <[string]> Matches by `option.value`. - `value` <[string]> Matches by `option.value`.
- `label` <[string]> Matches by `option.label`. - `label` <[string]> Matches by `option.label`.
@ -1714,7 +1717,7 @@ await page.goto('https://example.com');
Shortcut for [page.mainFrame().title()](#frametitle). Shortcut for [page.mainFrame().title()](#frametitle).
#### page.tripleclick(selector[, options]) #### page.tripleclick(selector[, options])
- `selector` <[string]> A [selector] to search for element to triple click. If there are multiple elements satisfying the selector, the first will be triple clicked. - `selector` <[string]|[Selector]> A [selector] to search for element to triple click. If there are multiple elements satisfying the selector, the first will be triple clicked.
- `options` <[Object]> - `options` <[Object]>
- `button` <"left"|"right"|"middle"> Defaults to `left`. - `button` <"left"|"right"|"middle"> Defaults to `left`.
- `delay` <[number]> Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0. - `delay` <[number]> Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0.
@ -1734,7 +1737,7 @@ Bear in mind that if the first or second click of the `tripleclick()` triggers a
Shortcut for [page.mainFrame().tripleclick(selector[, options])](#frametripleclickselector-options). Shortcut for [page.mainFrame().tripleclick(selector[, options])](#frametripleclickselector-options).
#### page.type(selector, text[, options]) #### page.type(selector, text[, options])
- `selector` <[string]> A [selector] of an element to type into. If there are multiple elements satisfying the selector, the first will be used. - `selector` <[string]|[Selector]> A [selector] of an element to type into. If there are multiple elements satisfying the selector, the first will be used.
- `text` <[string]> A text to type into a focused element. - `text` <[string]> A text to type into a focused element.
- `options` <[Object]> - `options` <[Object]>
- `delay` <[number]> Time to wait between key presses in milliseconds. Defaults to 0. - `delay` <[number]> Time to wait between key presses in milliseconds. Defaults to 0.
@ -1907,10 +1910,8 @@ return finalResponse.ok();
``` ```
#### page.waitForSelector(selector[, options]) #### page.waitForSelector(selector[, options])
- `selector` <[string]> A [selector] of an element to wait for - `selector` <[string]|[Selector]> A [selector] of an element to wait for
- `options` <[Object]> Optional waiting parameters - `options` <[Object]> Optional waiting parameters
- `visible` <[boolean]> wait for element to be present in DOM and to be visible, i.e. to not have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`.
- `hidden` <[boolean]> wait for element to not be found in the DOM or to be hidden, i.e. have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`.
- `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method. - `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method.
- returns: <[Promise]<?[ElementHandle]>> Promise which resolves when element specified by selector string is added to DOM. Resolves to `null` if waiting for `hidden: true` and selector is not found in DOM. - returns: <[Promise]<?[ElementHandle]>> Promise which resolves when element specified by selector string is added to DOM. Resolves to `null` if waiting for `hidden: true` and selector is not found in DOM.
@ -1940,8 +1941,6 @@ Shortcut for [page.mainFrame().waitForSelector(selector[, options])](#framewaitf
#### page.waitForXPath(xpath[, options]) #### page.waitForXPath(xpath[, options])
- `xpath` <[string]> A [xpath] of an element to wait for - `xpath` <[string]> A [xpath] of an element to wait for
- `options` <[Object]> Optional waiting parameters - `options` <[Object]> Optional waiting parameters
- `visible` <[boolean]> wait for element to be present in DOM and to be visible, i.e. to not have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`.
- `hidden` <[boolean]> wait for element to not be found in the DOM or to be hidden, i.e. have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`.
- `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method. - `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method.
- returns: <[Promise]<?[ElementHandle]>> Promise which resolves when element specified by xpath string is added to DOM. Resolves to `null` if waiting for `hidden: true` and xpath is not found in DOM. - returns: <[Promise]<?[ElementHandle]>> Promise which resolves when element specified by xpath string is added to DOM. Resolves to `null` if waiting for `hidden: true` and xpath is not found in DOM.
@ -2503,19 +2502,19 @@ An example of getting text from an iframe element:
``` ```
#### frame.$(selector) #### frame.$(selector)
- `selector` <[string]> A [selector] to query frame for - `selector` <[string]|[Selector]> A [selector] to query frame for
- returns: <[Promise]<?[ElementHandle]>> Promise which resolves to ElementHandle pointing to the frame element. - returns: <[Promise]<?[ElementHandle]>> Promise which resolves to ElementHandle pointing to the frame element.
The method queries frame for the selector. If there's no such element within the frame, the method will resolve to `null`. The method queries frame for the selector. If there's no such element within the frame, the method will resolve to `null`.
#### frame.$$(selector) #### frame.$$(selector)
- `selector` <[string]> A [selector] to query frame for - `selector` <[string]|[Selector]> A [selector] to query frame for
- returns: <[Promise]<[Array]<[ElementHandle]>>> Promise which resolves to ElementHandles pointing to the frame elements. - returns: <[Promise]<[Array]<[ElementHandle]>>> Promise which resolves to ElementHandles pointing to the frame elements.
The method runs `document.querySelectorAll` within the frame. If no elements match the selector, the return value resolves to `[]`. The method runs `document.querySelectorAll` within the frame. If no elements match the selector, the return value resolves to `[]`.
#### frame.$$eval(selector, pageFunction[, ...args]) #### frame.$$eval(selector, pageFunction[, ...args])
- `selector` <[string]> A [selector] to query frame for - `selector` <[string]|[Selector]> A [selector] to query frame for
- `pageFunction` <[function]\([Array]<[Element]>\)> Function to be evaluated in browser context - `pageFunction` <[function]\([Array]<[Element]>\)> Function to be evaluated in browser context
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction` - `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
- returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction` - returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction`
@ -2530,7 +2529,7 @@ const divsCounts = await frame.$$eval('div', divs => divs.length);
``` ```
#### frame.$eval(selector, pageFunction[, ...args]) #### frame.$eval(selector, pageFunction[, ...args])
- `selector` <[string]> A [selector] to query frame for - `selector` <[string]|[Selector]> A [selector] to query frame for
- `pageFunction` <[function]\([Element]\)> Function to be evaluated in browser context - `pageFunction` <[function]\([Element]\)> Function to be evaluated in browser context
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction` - `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
- returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction` - returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction`
@ -2575,7 +2574,7 @@ Adds a `<link rel="stylesheet">` tag into the page with the desired url or a `<s
- returns: <[Array]<[Frame]>> - returns: <[Array]<[Frame]>>
#### frame.click(selector[, options]) #### frame.click(selector[, options])
- `selector` <[string]> A [selector] to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked. - `selector` <[string]|[Selector]> A [selector] to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked.
- `options` <[Object]> - `options` <[Object]>
- `button` <"left"|"right"|"middle"> Defaults to `left`. - `button` <"left"|"right"|"middle"> Defaults to `left`.
- `clickCount` <[number]> defaults to 1. See [UIEvent.detail]. - `clickCount` <[number]> defaults to 1. See [UIEvent.detail].
@ -2604,7 +2603,7 @@ const [response] = await Promise.all([
Gets the full HTML contents of the frame, including the doctype. Gets the full HTML contents of the frame, including the doctype.
#### frame.dblclick(selector[, options]) #### frame.dblclick(selector[, options])
- `selector` <[string]> A [selector] to search for element to double click. If there are multiple elements satisfying the selector, the first will be double clicked. - `selector` <[string]|[Selector]> A [selector] to search for element to double click. If there are multiple elements satisfying the selector, the first will be double clicked.
- `options` <[Object]> - `options` <[Object]>
- `button` <"left"|"right"|"middle"> Defaults to `left`. - `button` <"left"|"right"|"middle"> Defaults to `left`.
- `delay` <[number]> Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0. - `delay` <[number]> Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0.
@ -2685,7 +2684,7 @@ await resultHandle.dispose();
Returns promise that resolves to the frame's default execution context. Returns promise that resolves to the frame's default execution context.
#### frame.fill(selector, value) #### frame.fill(selector, value)
- `selector` <[string]> A [selector] to query page for. - `selector` <[string]|[Selector]> A [selector] to query page for.
- `value` <[string]> Value to fill for the `<input>`, `<textarea>` or `[contenteditable]` element. - `value` <[string]> Value to fill for the `<input>`, `<textarea>` or `[contenteditable]` element.
- returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully filled. The promise will be rejected if there is no element matching `selector`. - returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully filled. The promise will be rejected if there is no element matching `selector`.
@ -2693,7 +2692,7 @@ This method focuses the element and triggers an `input` event after filling.
If there's no text `<input>`, `<textarea>` or `[contenteditable]` element matching `selector`, the method throws an error. If there's no text `<input>`, `<textarea>` or `[contenteditable]` element matching `selector`, the method throws an error.
#### frame.focus(selector) #### frame.focus(selector)
- `selector` <[string]> A [selector] of an element to focus. If there are multiple elements satisfying the selector, the first will be focused. - `selector` <[string]|[Selector]> A [selector] of an element to focus. If there are multiple elements satisfying the selector, the first will be focused.
- returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully focused. The promise will be rejected if there is no element matching `selector`. - returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully focused. The promise will be rejected if there is no element matching `selector`.
This method fetches an element with `selector` and focuses it. This method fetches an element with `selector` and focuses it.
@ -2726,7 +2725,7 @@ If there's no element matching `selector`, the method throws an error.
#### frame.hover(selector[, options]) #### frame.hover(selector[, options])
- `selector` <[string]> A [selector] to search for element to hover. If there are multiple elements satisfying the selector, the first will be hovered. - `selector` <[string]|[Selector]> A [selector] to search for element to hover. If there are multiple elements satisfying the selector, the first will be hovered.
- `options` <[Object]> - `options` <[Object]>
- `relativePoint` <[Object]> A point to hover relative to the top-left corner of element padding box. If not specified, hovers over some visible point of the element. - `relativePoint` <[Object]> A point to hover relative to the top-left corner of element padding box. If not specified, hovers over some visible point of the element.
- x <[number]> - x <[number]>
@ -2755,7 +2754,7 @@ If the name is empty, returns the id attribute instead.
- returns: <?[Frame]> Parent frame, if any. Detached frames and main frames return `null`. - returns: <?[Frame]> Parent frame, if any. Detached frames and main frames return `null`.
#### frame.select(selector, ...values) #### frame.select(selector, ...values)
- `selector` <[string]> A [selector] to query frame for. - `selector` <[string]|[Selector]> A [selector] to query frame for.
- `...values` <...[string]|[ElementHandle]|[Object]> Options to select. If the `<select>` has the `multiple` attribute, all matching options are selected, otherwise only the first option matching one of the passed options is selected. String values are equivalent to `{value:'string'}`. Option is considered matching if all specified properties match. - `...values` <...[string]|[ElementHandle]|[Object]> Options to select. If the `<select>` has the `multiple` attribute, all matching options are selected, otherwise only the first option matching one of the passed options is selected. String values are equivalent to `{value:'string'}`. Option is considered matching if all specified properties match.
- `value` <[string]> Matches by `option.value`. - `value` <[string]> Matches by `option.value`.
- `label` <[string]> Matches by `option.label`. - `label` <[string]> Matches by `option.label`.
@ -2794,7 +2793,7 @@ frame.select('select#colors', { value: 'blue' }, { index: 2 }, 'red');
- returns: <[Promise]<[string]>> The page's title. - returns: <[Promise]<[string]>> The page's title.
#### frame.tripleclick(selector[, options]) #### frame.tripleclick(selector[, options])
- `selector` <[string]> A [selector] to search for element to triple click. If there are multiple elements satisfying the selector, the first will be triple clicked. - `selector` <[string]|[Selector]> A [selector] to search for element to triple click. If there are multiple elements satisfying the selector, the first will be triple clicked.
- `options` <[Object]> - `options` <[Object]>
- `button` <"left"|"right"|"middle"> Defaults to `left`. - `button` <"left"|"right"|"middle"> Defaults to `left`.
- `delay` <[number]> Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0. - `delay` <[number]> Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0.
@ -2812,7 +2811,7 @@ Bear in mind that if the first or second click of the `tripleclick()` triggers a
> **NOTE** `frame.tripleclick()` dispatches three `click` events and a single `dblclick` event. > **NOTE** `frame.tripleclick()` dispatches three `click` events and a single `dblclick` event.
#### frame.type(selector, text[, options]) #### frame.type(selector, text[, options])
- `selector` <[string]> A [selector] of an element to type into. If there are multiple elements satisfying the selector, the first will be used. - `selector` <[string]|[Selector]> A [selector] of an element to type into. If there are multiple elements satisfying the selector, the first will be used.
- `text` <[string]> A text to type into a focused element. - `text` <[string]> A text to type into a focused element.
- `options` <[Object]> - `options` <[Object]>
- `delay` <[number]> Time to wait between key presses in milliseconds. Defaults to 0. - `delay` <[number]> Time to wait between key presses in milliseconds. Defaults to 0.
@ -2916,10 +2915,8 @@ const [response] = await Promise.all([
#### frame.waitForSelector(selector[, options]) #### frame.waitForSelector(selector[, options])
- `selector` <[string]> A [selector] of an element to wait for - `selector` <[string]|[Selector]> A [selector] of an element to wait for
- `options` <[Object]> Optional waiting parameters - `options` <[Object]> Optional waiting parameters
- `visible` <[boolean]> wait for element to be present in DOM and to be visible, i.e. to not have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`.
- `hidden` <[boolean]> wait for element to not be found in the DOM or to be hidden, i.e. have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`.
- `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method. - `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method.
- returns: <[Promise]<?[ElementHandle]>> Promise which resolves when element specified by selector string is added to DOM. Resolves to `null` if waiting for `hidden: true` and selector is not found in DOM. - returns: <[Promise]<?[ElementHandle]>> Promise which resolves when element specified by selector string is added to DOM. Resolves to `null` if waiting for `hidden: true` and selector is not found in DOM.
@ -2948,8 +2945,6 @@ const playwright = require('playwright');
#### frame.waitForXPath(xpath[, options]) #### frame.waitForXPath(xpath[, options])
- `xpath` <[string]> A [xpath] of an element to wait for - `xpath` <[string]> A [xpath] of an element to wait for
- `options` <[Object]> Optional waiting parameters - `options` <[Object]> Optional waiting parameters
- `visible` <[boolean]> wait for element to be present in DOM and to be visible, i.e. to not have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`.
- `hidden` <[boolean]> wait for element to not be found in the DOM or to be hidden, i.e. have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`.
- `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method. - `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method.
- returns: <[Promise]<?[ElementHandle]>> Promise which resolves when element specified by xpath string is added to DOM. Resolves to `null` if waiting for `hidden: true` and xpath is not found in DOM. - returns: <[Promise]<?[ElementHandle]>> Promise which resolves when element specified by xpath string is added to DOM. Resolves to `null` if waiting for `hidden: true` and xpath is not found in DOM.
@ -3281,19 +3276,19 @@ ElementHandle prevents DOM element from garbage collection unless the handle is
ElementHandle instances can be used as arguments in [`page.$eval()`](#pageevalselector-pagefunction-args) and [`page.evaluate()`](#pageevaluatepagefunction-args) methods. ElementHandle instances can be used as arguments in [`page.$eval()`](#pageevalselector-pagefunction-args) and [`page.evaluate()`](#pageevaluatepagefunction-args) methods.
#### elementHandle.$(selector) #### elementHandle.$(selector)
- `selector` <[string]> A [selector] to query element for - `selector` <[string]|[Selector]> A [selector] to query element for
- returns: <[Promise]<?[ElementHandle]>> - returns: <[Promise]<?[ElementHandle]>>
The method runs `element.querySelector` within the page. If no element matches the selector, the return value resolves to `null`. The method runs `element.querySelector` within the page. If no element matches the selector, the return value resolves to `null`.
#### elementHandle.$$(selector) #### elementHandle.$$(selector)
- `selector` <[string]> A [selector] to query element for - `selector` <[string]|[Selector]> A [selector] to query element for
- returns: <[Promise]<[Array]<[ElementHandle]>>> - returns: <[Promise]<[Array]<[ElementHandle]>>>
The method runs `element.querySelectorAll` within the page. If no elements match the selector, the return value resolves to `[]`. The method runs `element.querySelectorAll` within the page. If no elements match the selector, the return value resolves to `[]`.
#### elementHandle.$$eval(selector, pageFunction[, ...args]) #### elementHandle.$$eval(selector, pageFunction[, ...args])
- `selector` <[string]> A [selector] to query page for - `selector` <[string]|[Selector]> A [selector] to query page for
- `pageFunction` <[function]\([Array]<[Element]>\)> Function to be evaluated in browser context - `pageFunction` <[function]\([Array]<[Element]>\)> Function to be evaluated in browser context
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction` - `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
- returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction` - returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction`
@ -3315,7 +3310,7 @@ expect(await feedHandle.$$eval('.tweet', nodes => nodes.map(n => n.innerText))).
``` ```
#### elementHandle.$eval(selector, pageFunction[, ...args]) #### elementHandle.$eval(selector, pageFunction[, ...args])
- `selector` <[string]> A [selector] to query page for - `selector` <[string]|[Selector]> A [selector] to query page for
- `pageFunction` <[function]\([Element]\)> Function to be evaluated in browser context - `pageFunction` <[function]\([Element]\)> Function to be evaluated in browser context
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction` - `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
- returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction` - returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction`
@ -3843,6 +3838,59 @@ reported.
TimeoutError is emitted whenever certain operations are terminated due to timeout, e.g. [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options) or [playwright.launch([options])](#playwrightlaunchoptions). TimeoutError is emitted whenever certain operations are terminated due to timeout, e.g. [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options) or [playwright.launch([options])](#playwrightlaunchoptions).
### class: Selector
Selector describes an element in the page. It can be used to obtain `ElementHandle` (see [page.$()](#pageselector) for example) or shortcut element operations to avoid intermediate handle (see [page.click()](#pageclickselector-options) for example).
All methods accepting selector also accept a string shorthand which is equivalent to `{selector: 'string'}`.
#### selector.selector
- returns: <[string]> Selector in the following format: `engine=body [>> engine=body]*`. Here `engine` is one of the supported selector engines (currently, either `css` or `xpath`), and `body` is a selector body in the format of the particular engine. When multiple `engine=body` clauses are present (separated by `>>`), next one is queried relative to the previous one's result.
For convenience, selectors in the wrong format are heuristically converted to the right format:
- selector starting with `//` is assumed to be `xpath=selector`;
- otherwise selector is assumed to be `css=selector`.
```js
// queries 'div' css selector
const handle = await page.$('css=div');
// queries '//html/body/div' xpath selector
const handle = await page.$('xpath=//html/body/div');
// queries 'span' css selector inside the result of '//html/body/div' xpath selector
const handle = await page.$('xpath=//html/body/div >> css=span');
// converted to 'css=div'
const handle = await page.$('div');
// converted to 'xpath=//html/body/div'
const handle = await page.$('//html/body/div');
// queries 'span' css selector inside the div handle
const handle = await divHandle.$('css=span');
```
#### selector.visible
- returns: <[boolean]> Optional visibility to check for. If `true`, only visible elements match. If `false`, only non-visible elements match. If `undefined`, all elements match.
Note that elements are first queried by `selector`, and only after that are checked for visiblity. In particular, [page.$()](#pageselector) will not skip to the first visible element, but instead return `null` if the first matching element is not visible.
Element is defined visible if it does not have `visibility: hidden` CSS property and it's bounding box is not empty.
```js
// queries 'div', and only returns it when visible
const handle = await page.$({selector: 'css=div', visible: true});
// queries 'div', and only returns it when non-visible
const handle = await page.$({selector: 'css=div', visible: false});
// queries 'div', and returns it no matter the visibility
const handle = await page.$({selector: 'css=div'});
// returns all visible 'div' elements
const handles = await page.$$({selector: 'css=div', visible: true});
```
[AXNode]: #accessibilitysnapshotoptions "AXNode" [AXNode]: #accessibilitysnapshotoptions "AXNode"
@ -3876,6 +3924,7 @@ TimeoutError is emitted whenever certain operations are terminated due to timeou
[Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise "Promise" [Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise "Promise"
[Request]: #class-request "Request" [Request]: #class-request "Request"
[Response]: #class-response "Response" [Response]: #class-response "Response"
[Selector]: #class-selector "Selector"
[Serializable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Description "Serializable" [Serializable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Description "Serializable"
[Target]: #class-target "Target" [Target]: #class-target "Target"
[TimeoutError]: #class-timeouterror "TimeoutError" [TimeoutError]: #class-timeouterror "TimeoutError"

View file

@ -222,7 +222,7 @@ export class Page extends EventEmitter {
this._timeoutSettings.setDefaultTimeout(timeout); this._timeoutSettings.setDefaultTimeout(timeout);
} }
async $(selector: string): Promise<dom.ElementHandle | null> { async $(selector: string | types.Selector): Promise<dom.ElementHandle | null> {
return this.mainFrame().$(selector); return this.mainFrame().$(selector);
} }
@ -239,7 +239,7 @@ export class Page extends EventEmitter {
return this.mainFrame().$$eval(selector, pageFunction, ...args as any); return this.mainFrame().$$eval(selector, pageFunction, ...args as any);
} }
async $$(selector: string): Promise<dom.ElementHandle[]> { async $$(selector: string | types.Selector): Promise<dom.ElementHandle[]> {
return this.mainFrame().$$(selector); return this.mainFrame().$$(selector);
} }
@ -535,35 +535,35 @@ export class Page extends EventEmitter {
return this._mouse; return this._mouse;
} }
click(selector: string, options?: ClickOptions) { click(selector: string | types.Selector, options?: ClickOptions) {
return this.mainFrame().click(selector, options); return this.mainFrame().click(selector, options);
} }
dblclick(selector: string, options?: MultiClickOptions) { dblclick(selector: string | types.Selector, options?: MultiClickOptions) {
return this.mainFrame().dblclick(selector, options); return this.mainFrame().dblclick(selector, options);
} }
tripleclick(selector: string, options?: MultiClickOptions) { tripleclick(selector: string | types.Selector, options?: MultiClickOptions) {
return this.mainFrame().tripleclick(selector, options); return this.mainFrame().tripleclick(selector, options);
} }
fill(selector: string, value: string) { fill(selector: string | types.Selector, value: string) {
return this.mainFrame().fill(selector, value); return this.mainFrame().fill(selector, value);
} }
focus(selector: string) { focus(selector: string | types.Selector) {
return this.mainFrame().focus(selector); return this.mainFrame().focus(selector);
} }
hover(selector: string, options?: PointerActionOptions) { hover(selector: string | types.Selector, options?: PointerActionOptions) {
return this.mainFrame().hover(selector, options); return this.mainFrame().hover(selector, options);
} }
select(selector: string, ...values: (string | dom.ElementHandle | SelectOption)[]): Promise<string[]> { select(selector: string | types.Selector, ...values: (string | dom.ElementHandle | SelectOption)[]): Promise<string[]> {
return this.mainFrame().select(selector, ...values); return this.mainFrame().select(selector, ...values);
} }
type(selector: string, text: string, options: { delay: (number | undefined); } | undefined) { type(selector: string | types.Selector, text: string, options: { delay: (number | undefined); } | undefined) {
return this.mainFrame().type(selector, text, options); return this.mainFrame().type(selector, text, options);
} }
@ -571,15 +571,15 @@ export class Page extends EventEmitter {
return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args); return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
} }
waitForSelector(selector: string, options: { visible?: boolean; hidden?: boolean; timeout?: number; } = {}): Promise<dom.ElementHandle | null> { waitForSelector(selector: string | types.Selector, options: types.TimeoutOptions = {}): Promise<dom.ElementHandle | null> {
return this.mainFrame().waitForSelector(selector, options); return this.mainFrame().waitForSelector(selector, options);
} }
waitForXPath(xpath: string, options: { visible?: boolean; hidden?: boolean; timeout?: number; } = {}): Promise<dom.ElementHandle | null> { waitForXPath(xpath: string, options: types.TimeoutOptions = {}): Promise<dom.ElementHandle | null> {
return this.mainFrame().waitForXPath(xpath, options); return this.mainFrame().waitForXPath(xpath, options);
} }
waitForFunction(pageFunction: Function | string, options: dom.WaitForFunctionOptions, ...args: any[]): Promise<js.JSHandle> { waitForFunction(pageFunction: Function | string, options: types.WaitForFunctionOptions, ...args: any[]): Promise<js.JSHandle> {
return this.mainFrame().waitForFunction(pageFunction, options, ...args); return this.mainFrame().waitForFunction(pageFunction, options, ...args);
} }
} }

View file

@ -10,6 +10,7 @@ import * as cssSelectorEngineSource from './generated/cssSelectorEngineSource';
import * as xpathSelectorEngineSource from './generated/xpathSelectorEngineSource'; import * as xpathSelectorEngineSource from './generated/xpathSelectorEngineSource';
import { assert, helper } from './helper'; import { assert, helper } from './helper';
import Injected from './injected/injected'; import Injected from './injected/injected';
import { SelectorRoot } from './injected/selectorEngine';
export interface DOMWorldDelegate { export interface DOMWorldDelegate {
keyboard: input.Keyboard; keyboard: input.Keyboard;
@ -25,10 +26,8 @@ export interface DOMWorldDelegate {
adoptElementHandle(handle: ElementHandle, to: DOMWorld): Promise<ElementHandle>; adoptElementHandle(handle: ElementHandle, to: DOMWorld): Promise<ElementHandle>;
} }
type SelectorRoot = Element | ShadowRoot | Document; export type ScopedSelector = types.Selector & { scope?: ElementHandle };
type ResolvedSelector = { scope?: ElementHandle, selector: string, visible?: boolean, disposeScope?: boolean };
type ResolvedSelector = { root?: ElementHandle, selector: string, disposeRoot?: boolean };
type Selector = string | { root?: ElementHandle, selector: string };
export class DOMWorld { export class DOMWorld {
readonly context: js.ExecutionContext; readonly context: js.ExecutionContext;
@ -65,37 +64,47 @@ export class DOMWorld {
return this.delegate.adoptElementHandle(handle, this); return this.delegate.adoptElementHandle(handle, this);
} }
private async _resolveSelector(selector: Selector): Promise<ResolvedSelector> { async resolveSelector(selector: string | ScopedSelector): Promise<ResolvedSelector> {
if (helper.isString(selector)) if (helper.isString(selector))
return { selector: normalizeSelector(selector) }; return { selector: normalizeSelector(selector) };
if (selector.root && selector.root.executionContext() !== this.context) { if (selector.scope && selector.scope.executionContext() !== this.context) {
const root = await this.adoptElementHandle(selector.root); const scope = await this.adoptElementHandle(selector.scope);
return { root, selector: normalizeSelector(selector.selector), disposeRoot: true }; return { scope, selector: normalizeSelector(selector.selector), disposeScope: true, visible: selector.visible };
} }
return { root: selector.root, selector: normalizeSelector(selector.selector) }; return { scope: selector.scope, selector: normalizeSelector(selector.selector), visible: selector.visible };
} }
async $(selector: Selector): Promise<ElementHandle | null> { async $(selector: string | ScopedSelector): Promise<ElementHandle | null> {
const resolved = await this._resolveSelector(selector); const resolved = await this.resolveSelector(selector);
const handle = await this.context.evaluateHandle( const handle = await this.context.evaluateHandle(
(injected: Injected, selector: string, root: SelectorRoot | undefined) => injected.querySelector(selector, root || document), (injected: Injected, selector: string, scope: SelectorRoot | undefined, visible: boolean | undefined) => {
await this.injected(), resolved.selector, resolved.root const element = injected.querySelector(selector, scope || document);
if (visible === undefined || !element)
return element;
return injected.isVisible(element) === visible ? element : undefined;
},
await this.injected(), resolved.selector, resolved.scope, resolved.visible
); );
if (resolved.disposeRoot) if (resolved.disposeScope)
await resolved.root.dispose(); await resolved.scope.dispose();
if (!handle.asElement()) if (!handle.asElement())
await handle.dispose(); await handle.dispose();
return handle.asElement(); return handle.asElement();
} }
async $$(selector: Selector): Promise<ElementHandle[]> { async $$(selector: string | ScopedSelector): Promise<ElementHandle[]> {
const resolved = await this._resolveSelector(selector); const resolved = await this.resolveSelector(selector);
const arrayHandle = await this.context.evaluateHandle( const arrayHandle = await this.context.evaluateHandle(
(injected: Injected, selector: string, root: SelectorRoot | undefined) => injected.querySelectorAll(selector, root || document), (injected: Injected, selector: string, scope: SelectorRoot | undefined, visible: boolean | undefined) => {
await this.injected(), resolved.selector, resolved.root const elements = injected.querySelectorAll(selector, scope || document);
if (visible !== undefined)
return elements.filter(element => injected.isVisible(element) === visible);
return elements;
},
await this.injected(), resolved.selector, resolved.scope, resolved.visible
); );
if (resolved.disposeRoot) if (resolved.disposeScope)
await resolved.root.dispose(); await resolved.scope.dispose();
const properties = await arrayHandle.getProperties(); const properties = await arrayHandle.getProperties();
await arrayHandle.dispose(); await arrayHandle.dispose();
const result = []; const result = [];
@ -109,20 +118,25 @@ export class DOMWorld {
return result; return result;
} }
$eval: types.$Eval<Selector> = async (selector, pageFunction, ...args) => { $eval: types.$Eval<string | ScopedSelector> = async (selector, pageFunction, ...args) => {
const elementHandle = await this.$(selector); const elementHandle = await this.$(selector);
if (!elementHandle) if (!elementHandle)
throw new Error(`Error: failed to find element matching selector "${selectorToString(selector)}"`); throw new Error(`Error: failed to find element matching selector "${types.selectorToString(selector)}"`);
const result = await elementHandle.evaluate(pageFunction, ...args as any); const result = await elementHandle.evaluate(pageFunction, ...args as any);
await elementHandle.dispose(); await elementHandle.dispose();
return result; return result;
} }
$$eval: types.$$Eval<Selector> = async (selector, pageFunction, ...args) => { $$eval: types.$$Eval<string | ScopedSelector> = async (selector, pageFunction, ...args) => {
const resolved = await this._resolveSelector(selector); const resolved = await this.resolveSelector(selector);
const arrayHandle = await this.context.evaluateHandle( const arrayHandle = await this.context.evaluateHandle(
(injected: Injected, selector: string, root: SelectorRoot | undefined) => injected.querySelectorAll(selector, root || document), (injected: Injected, selector: string, scope: SelectorRoot | undefined, visible: boolean | undefined) => {
await this.injected(), resolved.selector, resolved.root const elements = injected.querySelectorAll(selector, scope || document);
if (visible !== undefined)
return elements.filter(element => injected.isVisible(element) === visible);
return elements;
},
await this.injected(), resolved.selector, resolved.scope, resolved.visible
); );
const result = await arrayHandle.evaluate(pageFunction, ...args as any); const result = await arrayHandle.evaluate(pageFunction, ...args as any);
await arrayHandle.dispose(); await arrayHandle.dispose();
@ -223,6 +237,7 @@ export class ElementHandle extends js.JSHandle {
if (error) if (error)
throw new Error(error); throw new Error(error);
await this.focus(); await this.focus();
// TODO: we should check that focus() succeeded.
await this._world.delegate.keyboard.sendCharacters(value); await this._world.delegate.keyboard.sendCharacters(value);
} }
@ -254,24 +269,31 @@ export class ElementHandle extends js.JSHandle {
return this._world.delegate.screenshot(this, options); return this._world.delegate.screenshot(this, options);
} }
$(selector: string): Promise<ElementHandle | null> { private _scopedSelector(selector: string | types.Selector): string | ScopedSelector {
return this._world.$({ root: this, selector }); selector = types.clearSelector(selector);
if (helper.isString(selector))
selector = { selector };
return { scope: this, selector: selector.selector, visible: selector.visible };
} }
$$(selector: string): Promise<ElementHandle[]> { $(selector: string | types.Selector): Promise<ElementHandle | null> {
return this._world.$$({ root: this, selector }); return this._world.$(this._scopedSelector(selector));
} }
$eval: types.$Eval = (selector, pageFunction, ...args) => { $$(selector: string | types.Selector): Promise<ElementHandle[]> {
return this._world.$eval({ root: this, selector }, pageFunction, ...args as any); return this._world.$$(this._scopedSelector(selector));
} }
$$eval: types.$$Eval = (selector, pageFunction, ...args) => { $eval: types.$Eval<string | types.Selector> = (selector, pageFunction, ...args) => {
return this._world.$$eval({ root: this, selector }, pageFunction, ...args as any); return this._world.$eval(this._scopedSelector(selector), pageFunction, ...args as any);
}
$$eval: types.$$Eval<string | types.Selector> = (selector, pageFunction, ...args) => {
return this._world.$$eval(this._scopedSelector(selector), pageFunction, ...args as any);
} }
$x(expression: string): Promise<ElementHandle[]> { $x(expression: string): Promise<ElementHandle[]> {
return this._world.$$({ root: this, selector: 'xpath=' + expression }); return this._world.$$({ scope: this, selector: 'xpath=' + expression });
} }
isIntersectingViewport(): Promise<boolean> { isIntersectingViewport(): Promise<boolean> {
@ -300,18 +322,9 @@ function normalizeSelector(selector: string): string {
return 'css=' + selector; return 'css=' + selector;
} }
function selectorToString(selector: Selector): string {
if (typeof selector === 'string')
return selector;
return `:scope >> ${selector.selector}`;
}
export type Task = (domWorld: DOMWorld) => Promise<js.JSHandle>; export type Task = (domWorld: DOMWorld) => Promise<js.JSHandle>;
export type Polling = 'raf' | 'mutation' | number; export function waitForFunctionTask(pageFunction: Function | string, options: types.WaitForFunctionOptions, ...args: any[]) {
export type WaitForFunctionOptions = { polling?: Polling, timeout?: number };
export function waitForFunctionTask(pageFunction: Function | string, options: WaitForFunctionOptions, ...args: any[]) {
const { polling = 'raf' } = options; const { polling = 'raf' } = options;
if (helper.isString(polling)) if (helper.isString(polling))
assert(polling === 'raf' || polling === 'mutation', 'Unknown polling option: ' + polling); assert(polling === 'raf' || polling === 'mutation', 'Unknown polling option: ' + polling);
@ -321,7 +334,7 @@ export function waitForFunctionTask(pageFunction: Function | string, options: Wa
throw new Error('Unknown polling options: ' + polling); throw new Error('Unknown polling options: ' + polling);
const predicateBody = helper.isString(pageFunction) ? 'return (' + pageFunction + ')' : 'return (' + pageFunction + ')(...args)'; const predicateBody = helper.isString(pageFunction) ? 'return (' + pageFunction + ')' : 'return (' + pageFunction + ')(...args)';
return async (domWorld: DOMWorld) => domWorld.context.evaluateHandle((injected: Injected, predicateBody: string, polling: Polling, timeout: number, ...args) => { return async (domWorld: DOMWorld) => domWorld.context.evaluateHandle((injected: Injected, predicateBody: string, polling: types.Polling, timeout: number, ...args) => {
const predicate = new Function('...args', predicateBody); const predicate = new Function('...args', predicateBody);
if (polling === 'raf') if (polling === 'raf')
return injected.pollRaf(predicate, timeout, ...args); return injected.pollRaf(predicate, timeout, ...args);
@ -331,32 +344,23 @@ export function waitForFunctionTask(pageFunction: Function | string, options: Wa
}, await domWorld.injected(), predicateBody, polling, options.timeout, ...args); }, await domWorld.injected(), predicateBody, polling, options.timeout, ...args);
} }
export type WaitForSelectorOptions = { visible?: boolean, hidden?: boolean, timeout?: number }; export function waitForSelectorTask(selector: string | ScopedSelector, timeout: number): Task {
return async (domWorld: DOMWorld) => {
// TODO: we should not be able to adopt selector scope from a different document - handle this case.
const resolved = await domWorld.resolveSelector(selector);
return domWorld.context.evaluateHandle((injected: Injected, selector: string, scope: SelectorRoot | undefined, visible: boolean | undefined, timeout: number) => {
if (visible !== undefined)
return injected.pollRaf(predicate, timeout);
return injected.pollMutation(predicate, timeout);
export function waitForSelectorTask(selector: string, options: WaitForSelectorOptions): Task { function predicate(): Element | boolean {
const { visible: waitForVisible = false, hidden: waitForHidden = false } = options; const element = injected.querySelector(selector, scope || document);
selector = normalizeSelector(selector); if (!element)
return visible === false;
return async (domWorld: DOMWorld) => domWorld.context.evaluateHandle((injected: Injected, selector: string, waitForVisible: boolean, waitForHidden: boolean, timeout: number) => { if (visible === undefined)
if (waitForVisible || waitForHidden) return element;
return injected.pollRaf(predicate, timeout); return injected.isVisible(element) === visible ? element : false;
return injected.pollMutation(predicate, timeout);
function predicate(): Element | boolean {
const element = injected.querySelector(selector, document);
if (!element)
return waitForHidden;
if (!waitForVisible && !waitForHidden)
return element;
const style = window.getComputedStyle(element);
const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox();
const success = (waitForVisible === isVisible || waitForHidden === !isVisible);
return success ? element : false;
function hasVisibleBoundingBox(): boolean {
const rect = element.getBoundingClientRect();
return !!(rect.top || rect.bottom || rect.width || rect.height);
} }
} }, await domWorld.injected(), resolved.selector, resolved.scope, resolved.visible, timeout);
}, await domWorld.injected(), selector, waitForVisible, waitForHidden, options.timeout); };
} }

View file

@ -118,7 +118,7 @@ export class Page extends EventEmitter {
} }
async emulateMedia(options: { async emulateMedia(options: {
type?: ""|"screen"|"print", type?: ''|'screen'|'print',
colorScheme?: 'dark' | 'light' | 'no-preference' }) { colorScheme?: 'dark' | 'light' | 'no-preference' }) {
assert(!options.type || input.mediaTypes.has(options.type), 'Unsupported media type: ' + options.type); assert(!options.type || input.mediaTypes.has(options.type), 'Unsupported media type: ' + options.type);
assert(!options.colorScheme || input.mediaColorSchemes.has(options.colorScheme), 'Unsupported color scheme: ' + options.colorScheme); assert(!options.colorScheme || input.mediaColorSchemes.has(options.colorScheme), 'Unsupported color scheme: ' + options.colorScheme);
@ -457,35 +457,35 @@ export class Page extends EventEmitter {
return this.mainFrame().addStyleTag(options); return this.mainFrame().addStyleTag(options);
} }
click(selector: string, options?: input.ClickOptions) { click(selector: string | types.Selector, options?: input.ClickOptions) {
return this.mainFrame().click(selector, options); return this.mainFrame().click(selector, options);
} }
dblclick(selector: string, options?: input.MultiClickOptions) { dblclick(selector: string | types.Selector, options?: input.MultiClickOptions) {
return this.mainFrame().dblclick(selector, options); return this.mainFrame().dblclick(selector, options);
} }
tripleclick(selector: string, options?: input.MultiClickOptions) { tripleclick(selector: string | types.Selector, options?: input.MultiClickOptions) {
return this.mainFrame().tripleclick(selector, options); return this.mainFrame().tripleclick(selector, options);
} }
fill(selector: string, value: string) { fill(selector: string | types.Selector, value: string) {
return this.mainFrame().fill(selector, value); return this.mainFrame().fill(selector, value);
} }
select(selector: string, ...values: Array<string>): Promise<Array<string>> { select(selector: string | types.Selector, ...values: Array<string>): Promise<Array<string>> {
return this._frameManager.mainFrame().select(selector, ...values); return this._frameManager.mainFrame().select(selector, ...values);
} }
type(selector: string, text: string, options: { delay: (number | undefined); } | undefined) { type(selector: string | types.Selector, text: string, options: { delay: (number | undefined); } | undefined) {
return this._frameManager.mainFrame().type(selector, text, options); return this._frameManager.mainFrame().type(selector, text, options);
} }
focus(selector: string) { focus(selector: string | types.Selector) {
return this._frameManager.mainFrame().focus(selector); return this._frameManager.mainFrame().focus(selector);
} }
hover(selector: string) { hover(selector: string | types.Selector) {
return this._frameManager.mainFrame().hover(selector); return this._frameManager.mainFrame().hover(selector);
} }
@ -493,15 +493,15 @@ export class Page extends EventEmitter {
return this._frameManager.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args); return this._frameManager.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
} }
waitForFunction(pageFunction: Function | string, options: dom.WaitForFunctionOptions, ...args): Promise<js.JSHandle> { waitForFunction(pageFunction: Function | string, options: types.WaitForFunctionOptions, ...args): Promise<js.JSHandle> {
return this._frameManager.mainFrame().waitForFunction(pageFunction, options, ...args); return this._frameManager.mainFrame().waitForFunction(pageFunction, options, ...args);
} }
waitForSelector(selector: string, options: { timeout?: number; visible?: boolean; hidden?: boolean; } | undefined = {}): Promise<dom.ElementHandle> { waitForSelector(selector: string | types.Selector, options?: types.TimeoutOptions): Promise<dom.ElementHandle> {
return this._frameManager.mainFrame().waitForSelector(selector, options); return this._frameManager.mainFrame().waitForSelector(selector, options);
} }
waitForXPath(xpath: string, options: { timeout?: number; visible?: boolean; hidden?: boolean; } | undefined = {}): Promise<dom.ElementHandle> { waitForXPath(xpath: string, options?: types.TimeoutOptions): Promise<dom.ElementHandle> {
return this._frameManager.mainFrame().waitForXPath(xpath, options); return this._frameManager.mainFrame().waitForXPath(xpath, options);
} }
@ -509,11 +509,11 @@ export class Page extends EventEmitter {
return this._frameManager.mainFrame().title(); return this._frameManager.mainFrame().title();
} }
$(selector: string): Promise<dom.ElementHandle | null> { $(selector: string | types.Selector): Promise<dom.ElementHandle | null> {
return this._frameManager.mainFrame().$(selector); return this._frameManager.mainFrame().$(selector);
} }
$$(selector: string): Promise<Array<dom.ElementHandle>> { $$(selector: string | types.Selector): Promise<Array<dom.ElementHandle>> {
return this._frameManager.mainFrame().$$(selector); return this._frameManager.mainFrame().$$(selector);
} }

View file

@ -122,9 +122,9 @@ export class Frame {
return context.evaluate(pageFunction, ...args as any); return context.evaluate(pageFunction, ...args as any);
} }
async $(selector: string): Promise<dom.ElementHandle | null> { async $(selector: string | types.Selector): Promise<dom.ElementHandle | null> {
const domWorld = await this._mainDOMWorld(); const domWorld = await this._mainDOMWorld();
return domWorld.$(selector); return domWorld.$(types.clearSelector(selector));
} }
async $x(expression: string): Promise<dom.ElementHandle[]> { async $x(expression: string): Promise<dom.ElementHandle[]> {
@ -142,9 +142,9 @@ export class Frame {
return domWorld.$$eval(selector, pageFunction, ...args as any); return domWorld.$$eval(selector, pageFunction, ...args as any);
} }
async $$(selector: string): Promise<dom.ElementHandle[]> { async $$(selector: string | types.Selector): Promise<dom.ElementHandle[]> {
const domWorld = await this._mainDOMWorld(); const domWorld = await this._mainDOMWorld();
return domWorld.$$(selector); return domWorld.$$(types.clearSelector(selector));
} }
async content(): Promise<string> { async content(): Promise<string> {
@ -300,58 +300,58 @@ export class Frame {
} }
} }
async click(selector: string, options?: ClickOptions) { async click(selector: string | types.Selector, options?: ClickOptions) {
const domWorld = await this._utilityDOMWorld(); const domWorld = await this._utilityDOMWorld();
const handle = await domWorld.$(selector); const handle = await domWorld.$(types.clearSelector(selector));
assert(handle, 'No node found for selector: ' + selector); assert(handle, 'No node found for selector: ' + types.selectorToString(selector));
await handle.click(options); await handle.click(options);
await handle.dispose(); await handle.dispose();
} }
async dblclick(selector: string, options?: MultiClickOptions) { async dblclick(selector: string | types.Selector, options?: MultiClickOptions) {
const domWorld = await this._utilityDOMWorld(); const domWorld = await this._utilityDOMWorld();
const handle = await domWorld.$(selector); const handle = await domWorld.$(types.clearSelector(selector));
assert(handle, 'No node found for selector: ' + selector); assert(handle, 'No node found for selector: ' + types.selectorToString(selector));
await handle.dblclick(options); await handle.dblclick(options);
await handle.dispose(); await handle.dispose();
} }
async tripleclick(selector: string, options?: MultiClickOptions) { async tripleclick(selector: string | types.Selector, options?: MultiClickOptions) {
const domWorld = await this._utilityDOMWorld(); const domWorld = await this._utilityDOMWorld();
const handle = await domWorld.$(selector); const handle = await domWorld.$(types.clearSelector(selector));
assert(handle, 'No node found for selector: ' + selector); assert(handle, 'No node found for selector: ' + types.selectorToString(selector));
await handle.tripleclick(options); await handle.tripleclick(options);
await handle.dispose(); await handle.dispose();
} }
async fill(selector: string, value: string) { async fill(selector: string | types.Selector, value: string) {
const domWorld = await this._utilityDOMWorld(); const domWorld = await this._utilityDOMWorld();
const handle = await domWorld.$(selector); const handle = await domWorld.$(types.clearSelector(selector));
assert(handle, 'No node found for selector: ' + selector); assert(handle, 'No node found for selector: ' + types.selectorToString(selector));
await handle.fill(value); await handle.fill(value);
await handle.dispose(); await handle.dispose();
} }
async focus(selector: string) { async focus(selector: string | types.Selector) {
const domWorld = await this._utilityDOMWorld(); const domWorld = await this._utilityDOMWorld();
const handle = await domWorld.$(selector); const handle = await domWorld.$(types.clearSelector(selector));
assert(handle, 'No node found for selector: ' + selector); assert(handle, 'No node found for selector: ' + types.selectorToString(selector));
await handle.focus(); await handle.focus();
await handle.dispose(); await handle.dispose();
} }
async hover(selector: string, options?: PointerActionOptions) { async hover(selector: string | types.Selector, options?: PointerActionOptions) {
const domWorld = await this._utilityDOMWorld(); const domWorld = await this._utilityDOMWorld();
const handle = await domWorld.$(selector); const handle = await domWorld.$(types.clearSelector(selector));
assert(handle, 'No node found for selector: ' + selector); assert(handle, 'No node found for selector: ' + types.selectorToString(selector));
await handle.hover(options); await handle.hover(options);
await handle.dispose(); await handle.dispose();
} }
async select(selector: string, ...values: (string | dom.ElementHandle | SelectOption)[]): Promise<string[]> { async select(selector: string | types.Selector, ...values: (string | dom.ElementHandle | SelectOption)[]): Promise<string[]> {
const domWorld = await this._utilityDOMWorld(); const domWorld = await this._utilityDOMWorld();
const handle = await domWorld.$(selector); const handle = await domWorld.$(types.clearSelector(selector));
assert(handle, 'No node found for selector: ' + selector); assert(handle, 'No node found for selector: ' + types.selectorToString(selector));
const toDispose: Promise<dom.ElementHandle>[] = []; const toDispose: Promise<dom.ElementHandle>[] = [];
const adoptedValues = await Promise.all(values.map(async value => { const adoptedValues = await Promise.all(values.map(async value => {
if (value instanceof dom.ElementHandle && value.executionContext() !== domWorld.context) { if (value instanceof dom.ElementHandle && value.executionContext() !== domWorld.context) {
@ -367,10 +367,10 @@ export class Frame {
return result; return result;
} }
async type(selector: string, text: string, options: { delay: (number | undefined); } | undefined) { async type(selector: string | types.Selector, text: string, options: { delay: (number | undefined); } | undefined) {
const domWorld = await this._utilityDOMWorld(); const domWorld = await this._utilityDOMWorld();
const handle = await domWorld.$(selector); const handle = await domWorld.$(types.clearSelector(selector));
assert(handle, 'No node found for selector: ' + selector); assert(handle, 'No node found for selector: ' + types.selectorToString(selector));
await handle.type(text, options); await handle.type(text, options);
await handle.dispose(); await handle.dispose();
} }
@ -385,10 +385,10 @@ export class Frame {
return Promise.reject(new Error('Unsupported target type: ' + (typeof selectorOrFunctionOrTimeout))); return Promise.reject(new Error('Unsupported target type: ' + (typeof selectorOrFunctionOrTimeout)));
} }
async waitForSelector(selector: string, options: dom.WaitForSelectorOptions = {}): Promise<dom.ElementHandle | null> { async waitForSelector(selector: string | types.Selector, options: types.TimeoutOptions = {}): Promise<dom.ElementHandle | null> {
const task = dom.waitForSelectorTask(selector, { timeout: this._timeoutSettings.timeout(), ...options }); const { timeout = this._timeoutSettings.timeout() } = options;
const title = `selector "${selector}"${options.hidden ? ' to be hidden' : ''}`; const task = dom.waitForSelectorTask(types.clearSelector(selector), timeout);
const handle = await this._scheduleRerunnableTask(task, 'utility', options.timeout, title); const handle = await this._scheduleRerunnableTask(task, 'utility', timeout, `selector "${types.selectorToString(selector)}"`);
if (!handle.asElement()) { if (!handle.asElement()) {
await handle.dispose(); await handle.dispose();
return null; return null;
@ -401,11 +401,11 @@ export class Frame {
return adopted; return adopted;
} }
async waitForXPath(xpath: string, options: dom.WaitForSelectorOptions = {}): Promise<dom.ElementHandle | null> { async waitForXPath(xpath: string, options: types.TimeoutOptions = {}): Promise<dom.ElementHandle | null> {
return this.waitForSelector('xpath=' + xpath, options); return this.waitForSelector('xpath=' + xpath, options);
} }
waitForFunction(pageFunction: Function | string, options: dom.WaitForFunctionOptions = {}, ...args: any[]): Promise<js.JSHandle> { waitForFunction(pageFunction: Function | string, options: types.WaitForFunctionOptions = {}, ...args: any[]): Promise<js.JSHandle> {
options = { timeout: this._timeoutSettings.timeout(), ...options }; options = { timeout: this._timeoutSettings.timeout(), ...options };
const task = dom.waitForFunctionTask(pageFunction, options, ...args); const task = dom.waitForFunctionTask(pageFunction, options, ...args);
return this._scheduleRerunnableTask(task, 'main', options.timeout); return this._scheduleRerunnableTask(task, 'main', options.timeout);

View file

@ -82,6 +82,16 @@ class Injected {
return result; return result;
} }
isVisible(element: Element): boolean {
if (!element.ownerDocument || !element.ownerDocument.defaultView)
return true;
const style = element.ownerDocument.defaultView.getComputedStyle(element);
if (!style || style.visibility === 'hidden')
return false;
const rect = element.getBoundingClientRect();
return !!(rect.top || rect.bottom || rect.width || rect.height);
}
pollMutation(predicate: Function, timeout: number, ...args: any[]): Promise<any> { pollMutation(predicate: Function, timeout: number, ...args: any[]): Promise<any> {
let timedOut = false; let timedOut = false;
if (timeout) if (timeout)

View file

@ -2,6 +2,7 @@
// Licensed under the MIT license. // Licensed under the MIT license.
import * as js from './javascript'; import * as js from './javascript';
import { helper } from './helper';
type Boxed<Args extends any[]> = { [Index in keyof Args]: Args[Index] | js.JSHandle }; type Boxed<Args extends any[]> = { [Index in keyof Args]: Args[Index] | js.JSHandle };
type PageFunction<Args extends any[], R = any> = string | ((...args: Args) => R | Promise<R>); type PageFunction<Args extends any[], R = any> = string | ((...args: Args) => R | Promise<R>);
@ -9,10 +10,30 @@ type PageFunctionOn<On, Args extends any[], R = any> = string | ((on: On, ...arg
export type Evaluate = <Args extends any[], R>(pageFunction: PageFunction<Args, R>, ...args: Boxed<Args>) => Promise<R>; export type Evaluate = <Args extends any[], R>(pageFunction: PageFunction<Args, R>, ...args: Boxed<Args>) => Promise<R>;
export type EvaluateHandle = <Args extends any[]>(pageFunction: PageFunction<Args>, ...args: Boxed<Args>) => Promise<js.JSHandle>; export type EvaluateHandle = <Args extends any[]>(pageFunction: PageFunction<Args>, ...args: Boxed<Args>) => Promise<js.JSHandle>;
export type $Eval<S = string> = <Args extends any[], R>(selector: S, pageFunction: PageFunctionOn<Element, Args, R>, ...args: Boxed<Args>) => Promise<R>; export type $Eval<S = string | Selector> = <Args extends any[], R>(selector: S, pageFunction: PageFunctionOn<Element, Args, R>, ...args: Boxed<Args>) => Promise<R>;
export type $$Eval<S = string> = <Args extends any[], R>(selector: S, pageFunction: PageFunctionOn<Element[], Args, R>, ...args: Boxed<Args>) => Promise<R>; export type $$Eval<S = string | Selector> = <Args extends any[], R>(selector: S, pageFunction: PageFunctionOn<Element[], Args, R>, ...args: Boxed<Args>) => Promise<R>;
export type EvaluateOn = <Args extends any[], R>(pageFunction: PageFunctionOn<any, Args, R>, ...args: Boxed<Args>) => Promise<R>; export type EvaluateOn = <Args extends any[], R>(pageFunction: PageFunctionOn<any, Args, R>, ...args: Boxed<Args>) => Promise<R>;
export type EvaluateHandleOn = <Args extends any[]>(pageFunction: PageFunctionOn<any, Args>, ...args: Boxed<Args>) => Promise<js.JSHandle>; export type EvaluateHandleOn = <Args extends any[]>(pageFunction: PageFunctionOn<any, Args>, ...args: Boxed<Args>) => Promise<js.JSHandle>;
export type Rect = { x: number, y: number, width: number, height: number }; export type Rect = { x: number, y: number, width: number, height: number };
export type Point = { x: number, y: number }; export type Point = { x: number, y: number };
export type TimeoutOptions = { timeout?: number };
export type Selector = { selector: string, visible?: boolean };
export type Polling = 'raf' | 'mutation' | number;
export type WaitForFunctionOptions = TimeoutOptions & { polling?: Polling };
export function selectorToString(selector: string | Selector): string {
if (typeof selector === 'string')
return selector;
return `${selector.visible ? '[visible] ' : selector.visible === false ? '[hidden] ' : ''}${selector.selector}`;
}
// Ensures that we don't use accidental properties in selector, e.g. scope.
export function clearSelector(selector: string | Selector): string | Selector {
if (helper.isString(selector))
return selector;
return { selector: selector.selector, visible: selector.visible };
}

View file

@ -78,7 +78,7 @@ export class Launcher {
stdio = ['ignore', 'ignore', 'ignore', 'pipe', 'pipe']; stdio = ['ignore', 'ignore', 'ignore', 'pipe', 'pipe'];
webkitArguments.push('--inspector-pipe'); webkitArguments.push('--inspector-pipe');
if (options.headless !== false) if (options.headless !== false)
webkitArguments.push('--headless'); webkitArguments.push('--headless');
const webkitProcess = childProcess.spawn( const webkitProcess = childProcess.spawn(
webkitExecutable, webkitExecutable,
webkitArguments, webkitArguments,

View file

@ -206,7 +206,7 @@ export class Page extends EventEmitter {
this._timeoutSettings.setDefaultTimeout(timeout); this._timeoutSettings.setDefaultTimeout(timeout);
} }
async $(selector: string): Promise<dom.ElementHandle | null> { async $(selector: string | types.Selector): Promise<dom.ElementHandle | null> {
return this.mainFrame().$(selector); return this.mainFrame().$(selector);
} }
@ -223,7 +223,7 @@ export class Page extends EventEmitter {
return this.mainFrame().$$eval(selector, pageFunction, ...args as any); return this.mainFrame().$$eval(selector, pageFunction, ...args as any);
} }
async $$(selector: string): Promise<dom.ElementHandle[]> { async $$(selector: string | types.Selector): Promise<dom.ElementHandle[]> {
return this.mainFrame().$$(selector); return this.mainFrame().$$(selector);
} }
@ -466,35 +466,35 @@ export class Page extends EventEmitter {
return this._mouse; return this._mouse;
} }
click(selector: string, options?: ClickOptions) { click(selector: string | types.Selector, options?: ClickOptions) {
return this.mainFrame().click(selector, options); return this.mainFrame().click(selector, options);
} }
dblclick(selector: string, options?: MultiClickOptions) { dblclick(selector: string | types.Selector, options?: MultiClickOptions) {
return this.mainFrame().dblclick(selector, options); return this.mainFrame().dblclick(selector, options);
} }
tripleclick(selector: string, options?: MultiClickOptions) { tripleclick(selector: string | types.Selector, options?: MultiClickOptions) {
return this.mainFrame().tripleclick(selector, options); return this.mainFrame().tripleclick(selector, options);
} }
hover(selector: string) { hover(selector: string | types.Selector) {
return this.mainFrame().hover(selector); return this.mainFrame().hover(selector);
} }
fill(selector: string, value: string) { fill(selector: string | types.Selector, value: string) {
return this.mainFrame().fill(selector, value); return this.mainFrame().fill(selector, value);
} }
focus(selector: string) { focus(selector: string | types.Selector) {
return this.mainFrame().focus(selector); return this.mainFrame().focus(selector);
} }
select(selector: string, ...values: string[]): Promise<string[]> { select(selector: string | types.Selector, ...values: string[]): Promise<string[]> {
return this.mainFrame().select(selector, ...values); return this.mainFrame().select(selector, ...values);
} }
type(selector: string, text: string, options: { delay: (number | undefined); } | undefined) { type(selector: string | types.Selector, text: string, options: { delay: (number | undefined); } | undefined) {
return this.mainFrame().type(selector, text, options); return this.mainFrame().type(selector, text, options);
} }
@ -502,15 +502,15 @@ export class Page extends EventEmitter {
return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args); return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
} }
waitForSelector(selector: string, options: { visible?: boolean; hidden?: boolean; timeout?: number; } = {}): Promise<dom.ElementHandle | null> { waitForSelector(selector: string | types.Selector, options?: types.TimeoutOptions): Promise<dom.ElementHandle | null> {
return this.mainFrame().waitForSelector(selector, options); return this.mainFrame().waitForSelector(selector, options);
} }
waitForXPath(xpath: string, options: { visible?: boolean; hidden?: boolean; timeout?: number; } = {}): Promise<dom.ElementHandle | null> { waitForXPath(xpath: string, options?: types.TimeoutOptions): Promise<dom.ElementHandle | null> {
return this.mainFrame().waitForXPath(xpath, options); return this.mainFrame().waitForXPath(xpath, options);
} }
waitForFunction(pageFunction: Function | string, options: dom.WaitForFunctionOptions, ...args: any[]): Promise<js.JSHandle> { waitForFunction(pageFunction: Function | string, options: types.WaitForFunctionOptions, ...args: any[]): Promise<js.JSHandle> {
return this.mainFrame().waitForFunction(pageFunction, options, ...args); return this.mainFrame().waitForFunction(pageFunction, options, ...args);
} }
} }

View file

@ -126,6 +126,32 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
]); ]);
}); });
it('should respect selector visibilty', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html');
await page.click({selector: 'button', visible: true});
expect(await page.evaluate(() => result)).toBe('Clicked');
let error = null;
await page.goto(server.PREFIX + '/input/button.html');
await page.click({selector: 'button', visible: false}).catch(e => error = e);
expect(error.message).toBe('No node found for selector: [hidden] button');
expect(await page.evaluate(() => result)).toBe('Was not clicked');
error = null;
await page.goto(server.PREFIX + '/input/button.html');
await page.$eval('button', b => b.style.display = 'none');
await page.click({selector: 'button', visible: true}).catch(e => error = e);
expect(error.message).toBe('No node found for selector: [visible] button');
expect(await page.evaluate(() => result)).toBe('Was not clicked');
error = null;
await page.goto(server.PREFIX + '/input/button.html');
await page.$eval('button', b => b.style.display = 'none');
await page.click({selector: 'button', visible: false}).catch(e => error = e);
expect(error.message).toBe('Node is either not visible or not an HTMLElement');
expect(await page.evaluate(() => result)).toBe('Was not clicked');
});
it('should click wrapped links', async({page, server}) => { it('should click wrapped links', async({page, server}) => {
await page.goto(server.PREFIX + '/wrappedlink.html'); await page.goto(server.PREFIX + '/wrappedlink.html');
await page.click('a'); await page.click('a');

View file

@ -1119,6 +1119,27 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF
await page.fill('textarea', 123).catch(e => error = e); await page.fill('textarea', 123).catch(e => error = e);
expect(error.message).toContain('Value must be string.'); expect(error.message).toContain('Value must be string.');
}); });
it('should respect selector visibilty', async({page, server}) => {
await page.goto(server.PREFIX + '/input/textarea.html');
await page.fill({selector: 'input', visible: true}, 'some value');
expect(await page.evaluate(() => result)).toBe('some value');
let error = null;
await page.goto(server.PREFIX + '/input/textarea.html');
await page.fill({selector: 'input', visible: false}, 'some value').catch(e => error = e);
expect(error.message).toBe('No node found for selector: [hidden] input');
error = null;
await page.goto(server.PREFIX + '/input/textarea.html');
await page.$eval('input', i => i.style.display = 'none');
await page.fill({selector: 'input', visible: true}, 'some value').catch(e => error = e);
expect(error.message).toBe('No node found for selector: [visible] input');
await page.goto(server.PREFIX + '/input/textarea.html');
await page.$eval('input', i => i.style.display = 'none');
await page.fill({selector: 'input', visible: false}, 'some value');
expect(await page.evaluate(() => result)).toBe('');
});
}); });
// FIXME: WebKit shouldn't send targetDestroyed on PSON so that we could // FIXME: WebKit shouldn't send targetDestroyed on PSON so that we could

View file

@ -35,6 +35,19 @@ module.exports.addTests = function({testRunner, expect, product, FFOX, CHROME, W
const idAttribute = await page.$eval('section', e => e.id); const idAttribute = await page.$eval('section', e => e.id);
expect(idAttribute).toBe('testAttribute'); expect(idAttribute).toBe('testAttribute');
}); });
it('should respect visibility', async({page, server}) => {
let error = null;
await page.setContent('<section id="testAttribute" style="display: none">43543</section>');
await page.$eval({selector: 'css=section', visible: true}, e => e.id).catch(e => error = e);
expect(error.message).toContain('failed to find element matching selector "[visible] css=section"');
expect(await page.$eval({selector: 'css=section', visible: false}, e => e.id)).toBe('testAttribute');
error = null;
await page.setContent('<section id="testAttribute">43543</section>');
await page.$eval({selector: 'css=section', visible: false}, e => e.id).catch(e => error = e);
expect(error.message).toContain('failed to find element matching selector "[hidden] css=section"');
expect(await page.$eval({selector: 'css=section', visible: true}, e => e.id)).toBe('testAttribute');
});
it('should accept arguments', async({page, server}) => { it('should accept arguments', async({page, server}) => {
await page.setContent('<section>hello</section>'); await page.setContent('<section>hello</section>');
const text = await page.$eval('section', (e, suffix) => e.textContent + suffix, ' world!'); const text = await page.$eval('section', (e, suffix) => e.textContent + suffix, ' world!');
@ -107,6 +120,12 @@ module.exports.addTests = function({testRunner, expect, product, FFOX, CHROME, W
const spansCount = await page.$$eval('css=div >> css=div >> css=span', spans => spans.length); const spansCount = await page.$$eval('css=div >> css=div >> css=span', spans => spans.length);
expect(spansCount).toBe(2); expect(spansCount).toBe(2);
}); });
it('should respect visibility', async({page, server}) => {
await page.setContent('<section style="display: none">1</section><section style="display: none">2</section><section>3</section>');
expect(await page.$$eval({selector: 'css=section', visible: true}, x => x.length)).toBe(1);
expect(await page.$$eval({selector: 'css=section', visible: false}, x => x.length)).toBe(2);
expect(await page.$$eval({selector: 'css=section'}, x => x.length)).toBe(3);
});
}); });
describe('Page.$', function() { describe('Page.$', function() {
@ -139,6 +158,17 @@ module.exports.addTests = function({testRunner, expect, product, FFOX, CHROME, W
const element = await page.$('css=section >> css=div'); const element = await page.$('css=section >> css=div');
expect(element).toBeTruthy(); expect(element).toBeTruthy();
}); });
it('should respect visibility', async({page, server}) => {
await page.setContent('<section id="testAttribute">43543</section>');
expect(await page.$({selector: 'css=section', visible: true})).toBeTruthy();
expect(await page.$({selector: 'css=section', visible: false})).not.toBeTruthy();
expect(await page.$({selector: 'css=section'})).toBeTruthy();
await page.setContent('<section id="testAttribute" style="display: none">43543</section>');
expect(await page.$({selector: 'css=section', visible: true})).not.toBeTruthy();
expect(await page.$({selector: 'css=section', visible: false})).toBeTruthy();
expect(await page.$({selector: 'css=section'})).toBeTruthy();
});
}); });
describe('Page.$$', function() { describe('Page.$$', function() {
@ -154,6 +184,12 @@ module.exports.addTests = function({testRunner, expect, product, FFOX, CHROME, W
const elements = await page.$$('div'); const elements = await page.$$('div');
expect(elements.length).toBe(0); expect(elements.length).toBe(0);
}); });
it('should respect visibility', async({page, server}) => {
await page.setContent('<section style="display: none">1</section><section style="display: none">2</section><section>3</section>');
expect((await page.$$({selector: 'css=section', visible: true})).length).toBe(1);
expect((await page.$$({selector: 'css=section', visible: false})).length).toBe(2);
expect((await page.$$({selector: 'css=section'})).length).toBe(3);
});
}); });
describe('Path.$x', function() { describe('Path.$x', function() {
@ -192,6 +228,22 @@ module.exports.addTests = function({testRunner, expect, product, FFOX, CHROME, W
const second = await html.$('.third'); const second = await html.$('.third');
expect(second).toBe(null); expect(second).toBe(null);
}); });
it('should respect visibility', async({page, server}) => {
await page.goto(server.PREFIX + '/playground.html');
await page.setContent('<html><body><div class="second"><div class="inner" style="display:none">A</div></div></body></html>');
const second = await page.$('html .second');
let inner = await second.$({selector: '.inner', visible: true});
expect(inner).not.toBeTruthy();
inner = await second.$({selector: '.inner', visible: false});
expect(await inner.evaluate(e => e.textContent)).toBe('A');
await inner.evaluate(e => e.style.display = 'block');
inner = await second.$({selector: '.inner', visible: true});
expect(await inner.evaluate(e => e.textContent)).toBe('A');
});
}); });
describe('ElementHandle.$eval', function() { describe('ElementHandle.$eval', function() {
it('should work', async({page, server}) => { it('should work', async({page, server}) => {
@ -214,7 +266,7 @@ module.exports.addTests = function({testRunner, expect, product, FFOX, CHROME, W
await page.setContent(htmlContent); await page.setContent(htmlContent);
const elementHandle = await page.$('#myId'); const elementHandle = await page.$('#myId');
const errorMessage = await elementHandle.$eval('.a', node => node.innerText).catch(error => error.message); const errorMessage = await elementHandle.$eval('.a', node => node.innerText).catch(error => error.message);
expect(errorMessage).toBe(`Error: failed to find element matching selector ":scope >> .a"`); expect(errorMessage).toBe(`Error: failed to find element matching selector ".a"`);
}); });
}); });
describe('ElementHandle.$$eval', function() { describe('ElementHandle.$$eval', function() {

View file

@ -289,7 +289,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
}); });
it('should wait for visible', async({page, server}) => { it('should wait for visible', async({page, server}) => {
let divFound = false; let divFound = false;
const waitForSelector = page.waitForSelector('div', {visible: true}).then(() => divFound = true); const waitForSelector = page.waitForSelector({selector: 'div', visible: true}).then(() => divFound = true);
await page.setContent(`<div style='display: none; visibility: hidden;'>1</div>`); await page.setContent(`<div style='display: none; visibility: hidden;'>1</div>`);
expect(divFound).toBe(false); expect(divFound).toBe(false);
await page.evaluate(() => document.querySelector('div').style.removeProperty('display')); await page.evaluate(() => document.querySelector('div').style.removeProperty('display'));
@ -300,7 +300,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
}); });
it('should wait for visible recursively', async({page, server}) => { it('should wait for visible recursively', async({page, server}) => {
let divVisible = false; let divVisible = false;
const waitForSelector = page.waitForSelector('div#inner', {visible: true}).then(() => divVisible = true); const waitForSelector = page.waitForSelector({selector: 'div#inner', visible: true}).then(() => divVisible = true);
await page.setContent(`<div style='display: none; visibility: hidden;'><div id="inner">hi</div></div>`); await page.setContent(`<div style='display: none; visibility: hidden;'><div id="inner">hi</div></div>`);
expect(divVisible).toBe(false); expect(divVisible).toBe(false);
await page.evaluate(() => document.querySelector('div').style.removeProperty('display')); await page.evaluate(() => document.querySelector('div').style.removeProperty('display'));
@ -312,7 +312,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
it('hidden should wait for visibility: hidden', async({page, server}) => { it('hidden should wait for visibility: hidden', async({page, server}) => {
let divHidden = false; let divHidden = false;
await page.setContent(`<div style='display: block;'></div>`); await page.setContent(`<div style='display: block;'></div>`);
const waitForSelector = page.waitForSelector('div', {hidden: true}).then(() => divHidden = true); const waitForSelector = page.waitForSelector({selector: 'div', visible: false}).then(() => divHidden = true);
await page.waitForSelector('div'); // do a round trip await page.waitForSelector('div'); // do a round trip
expect(divHidden).toBe(false); expect(divHidden).toBe(false);
await page.evaluate(() => document.querySelector('div').style.setProperty('visibility', 'hidden')); await page.evaluate(() => document.querySelector('div').style.setProperty('visibility', 'hidden'));
@ -322,7 +322,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
it('hidden should wait for display: none', async({page, server}) => { it('hidden should wait for display: none', async({page, server}) => {
let divHidden = false; let divHidden = false;
await page.setContent(`<div style='display: block;'></div>`); await page.setContent(`<div style='display: block;'></div>`);
const waitForSelector = page.waitForSelector('div', {hidden: true}).then(() => divHidden = true); const waitForSelector = page.waitForSelector({selector: 'div', visible: false}).then(() => divHidden = true);
await page.waitForSelector('div'); // do a round trip await page.waitForSelector('div'); // do a round trip
expect(divHidden).toBe(false); expect(divHidden).toBe(false);
await page.evaluate(() => document.querySelector('div').style.setProperty('display', 'none')); await page.evaluate(() => document.querySelector('div').style.setProperty('display', 'none'));
@ -332,7 +332,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
it('hidden should wait for removal', async({page, server}) => { it('hidden should wait for removal', async({page, server}) => {
await page.setContent(`<div></div>`); await page.setContent(`<div></div>`);
let divRemoved = false; let divRemoved = false;
const waitForSelector = page.waitForSelector('div', {hidden: true}).then(() => divRemoved = true); const waitForSelector = page.waitForSelector({selector: 'div', visible: false}).then(() => divRemoved = true);
await page.waitForSelector('div'); // do a round trip await page.waitForSelector('div'); // do a round trip
expect(divRemoved).toBe(false); expect(divRemoved).toBe(false);
await page.evaluate(() => document.querySelector('div').remove()); await page.evaluate(() => document.querySelector('div').remove());
@ -340,7 +340,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
expect(divRemoved).toBe(true); expect(divRemoved).toBe(true);
}); });
it('should return null if waiting to hide non-existing element', async({page, server}) => { it('should return null if waiting to hide non-existing element', async({page, server}) => {
const handle = await page.waitForSelector('non-existing', { hidden: true }); const handle = await page.waitForSelector({selector: 'non-existing', visible: false });
expect(handle).toBe(null); expect(handle).toBe(null);
}); });
it('should respect timeout', async({page, server}) => { it('should respect timeout', async({page, server}) => {
@ -353,9 +353,9 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
it('should have an error message specifically for awaiting an element to be hidden', async({page, server}) => { it('should have an error message specifically for awaiting an element to be hidden', async({page, server}) => {
await page.setContent(`<div></div>`); await page.setContent(`<div></div>`);
let error = null; let error = null;
await page.waitForSelector('div', {hidden: true, timeout: 10}).catch(e => error = e); await page.waitForSelector({selector: 'div', visible: false}, {timeout: 10}).catch(e => error = e);
expect(error).toBeTruthy(); expect(error).toBeTruthy();
expect(error.message).toContain('waiting for selector "div" to be hidden failed: timeout'); expect(error.message).toContain('waiting for selector "[hidden] div" failed: timeout');
}); });
it('should respond to node attribute mutation', async({page, server}) => { it('should respond to node attribute mutation', async({page, server}) => {
@ -426,16 +426,6 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
expect(waitError).toBeTruthy(); expect(waitError).toBeTruthy();
expect(waitError.message).toContain('waitForFunction failed: frame got detached.'); expect(waitError.message).toContain('waitForFunction failed: frame got detached.');
}); });
it('hidden should wait for display: none', async({page, server}) => {
let divHidden = false;
await page.setContent(`<div style='display: block;'></div>`);
const waitForXPath = page.waitForXPath('//div', {hidden: true}).then(() => divHidden = true);
await page.waitForXPath('//div'); // do a round trip
expect(divHidden).toBe(false);
await page.evaluate(() => document.querySelector('div').style.setProperty('display', 'none'));
expect(await waitForXPath).toBe(true);
expect(divHidden).toBe(true);
});
it('should return the element handle', async({page, server}) => { it('should return the element handle', async({page, server}) => {
const waitForXPath = page.waitForXPath('//*[@class="zombo"]'); const waitForXPath = page.waitForXPath('//*[@class="zombo"]');
await page.setContent(`<div class='zombo'>anything</div>`); await page.setContent(`<div class='zombo'>anything</div>`);

View file

@ -154,6 +154,15 @@ function checkSources(sources) {
typeName = 'Object'; typeName = 'Object';
const nextCircular = [typeName].concat(circular); const nextCircular = [typeName].concat(circular);
if (typeName === 'Selector') {
if (!excludeClasses.has(typeName)) {
const properties = type.getProperties().map(property => serializeSymbol(property, nextCircular));
classes.push(new Documentation.Class(typeName, properties));
excludeClasses.add(typeName);
}
return new Documentation.Type(typeName, []);
}
if (isRegularObject(type)) { if (isRegularObject(type)) {
let properties = undefined; let properties = undefined;
if (!circular.includes(typeName)) if (!circular.includes(typeName))

View file

@ -114,8 +114,10 @@ function checkSorting(doc) {
function filterJSDocumentation(jsSources, jsDocumentation) { function filterJSDocumentation(jsSources, jsDocumentation) {
const apijs = jsSources.find(source => source.name() === 'api.ts'); const apijs = jsSources.find(source => source.name() === 'api.ts');
let includedClasses = null; let includedClasses = null;
if (apijs) if (apijs) {
includedClasses = new Set(Object.keys(require(path.join(apijs.filePath(), '..', '..', 'lib', 'api.js')).Chromium)); includedClasses = new Set(Object.keys(require(path.join(apijs.filePath(), '..', '..', 'lib', 'api.js')).Chromium));
includedClasses.add('Selector');
}
// Filter private classes and methods. // Filter private classes and methods.
const classes = []; const classes = [];
for (const cls of jsDocumentation.classesArray) { for (const cls of jsDocumentation.classesArray) {

View file

@ -3,6 +3,7 @@ const path = require('path');
const fs = require('fs'); const fs = require('fs');
const StreamZip = require('node-stream-zip'); const StreamZip = require('node-stream-zip');
const vm = require('vm'); const vm = require('vm');
const os = require('os');
async function generateChromeProtocol(revision) { async function generateChromeProtocol(revision) {
const outputPath = path.join(__dirname, '..', '..', 'src', 'chromium', 'protocol.d.ts'); const outputPath = path.join(__dirname, '..', '..', 'src', 'chromium', 'protocol.d.ts');
@ -120,7 +121,10 @@ async function generateFirefoxProtocol(revision) {
const outputPath = path.join(__dirname, '..', '..', 'src', 'firefox', 'protocol.d.ts'); const outputPath = path.join(__dirname, '..', '..', 'src', 'firefox', 'protocol.d.ts');
if (revision.local && fs.existsSync(outputPath)) if (revision.local && fs.existsSync(outputPath))
return; return;
const zip = new StreamZip({file: path.join(revision.executablePath, '..', 'omni.ja'), storeEntries: true}); const omnija = os.platform() === 'darwin' ?
path.join(revision.executablePath, '..', '..', 'Resources', 'omni.ja') :
path.join(revision.executablePath, '..', 'omni.ja');
const zip = new StreamZip({file: omnija, storeEntries: true});
// @ts-ignore // @ts-ignore
await new Promise(x => zip.on('ready', x)); await new Promise(x => zip.on('ready', x));
const data = zip.entryDataSync(zip.entry('chrome/juggler/content/protocol/Protocol.js')) const data = zip.entryDataSync(zip.entry('chrome/juggler/content/protocol/Protocol.js'))