mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat(selectors): support optional "visible" property in all selectors (#129)
This commit is contained in:
parent
e358b47f76
commit
fc5898892b
125
docs/api.md
125
docs/api.md
@ -310,6 +310,9 @@
|
||||
* [coverage.stopCSSCoverage()](#coveragestopcsscoverage)
|
||||
* [coverage.stopJSCoverage()](#coveragestopjscoverage)
|
||||
- [class: TimeoutError](#class-timeouterror)
|
||||
- [class: Selector](#class-selector)
|
||||
* [selector.selector](#selectorselector)
|
||||
* [selector.visible](#selectorvisible)
|
||||
<!-- GEN:stop -->
|
||||
|
||||
### Overview
|
||||
@ -1049,7 +1052,7 @@ Emitted when a request finishes successfully.
|
||||
Emitted when a [response] is received.
|
||||
|
||||
#### page.$(selector)
|
||||
- `selector` <[string]> A [selector] to query page for
|
||||
- `selector` <[string]|[Selector]> A [selector] to query page for
|
||||
- returns: <[Promise]<?[ElementHandle]>>
|
||||
|
||||
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).
|
||||
|
||||
#### page.$$(selector)
|
||||
- `selector` <[string]> A [selector] to query page for
|
||||
- `selector` <[string]|[Selector]> A [selector] to query page for
|
||||
- returns: <[Promise]<[Array]<[ElementHandle]>>>
|
||||
|
||||
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).
|
||||
|
||||
#### 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
|
||||
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `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])
|
||||
- `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
|
||||
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `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.
|
||||
|
||||
#### 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]>
|
||||
- `button` <"left"|"right"|"middle"> Defaults to `left`.
|
||||
- `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]>
|
||||
|
||||
#### 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]>
|
||||
- `button` <"left"|"right"|"middle"> Defaults to `left`.
|
||||
- `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)
|
||||
- `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.
|
||||
- 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)
|
||||
|
||||
#### 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`.
|
||||
|
||||
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)
|
||||
|
||||
#### 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]>
|
||||
- `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]>
|
||||
@ -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.
|
||||
|
||||
#### 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.
|
||||
- `value` <[string]> Matches by `option.value`.
|
||||
- `label` <[string]> Matches by `option.label`.
|
||||
@ -1714,7 +1717,7 @@ await page.goto('https://example.com');
|
||||
Shortcut for [page.mainFrame().title()](#frametitle).
|
||||
|
||||
#### 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]>
|
||||
- `button` <"left"|"right"|"middle"> Defaults to `left`.
|
||||
- `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).
|
||||
|
||||
#### 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.
|
||||
- `options` <[Object]>
|
||||
- `delay` <[number]> Time to wait between key presses in milliseconds. Defaults to 0.
|
||||
@ -1907,10 +1910,8 @@ return finalResponse.ok();
|
||||
```
|
||||
|
||||
#### 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
|
||||
- `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.
|
||||
- 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])
|
||||
- `xpath` <[string]> A [xpath] of an element to wait for
|
||||
- `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.
|
||||
- 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)
|
||||
- `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.
|
||||
|
||||
The method queries frame for the selector. If there's no such element within the frame, the method will resolve to `null`.
|
||||
|
||||
#### 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.
|
||||
|
||||
The method runs `document.querySelectorAll` within the frame. If no elements match the selector, the return value resolves to `[]`.
|
||||
|
||||
#### 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
|
||||
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `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])
|
||||
- `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
|
||||
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `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]>>
|
||||
|
||||
#### 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]>
|
||||
- `button` <"left"|"right"|"middle"> Defaults to `left`.
|
||||
- `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.
|
||||
|
||||
#### 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]>
|
||||
- `button` <"left"|"right"|"middle"> Defaults to `left`.
|
||||
- `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.
|
||||
|
||||
#### 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.
|
||||
- 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.
|
||||
|
||||
#### 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`.
|
||||
|
||||
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])
|
||||
- `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]>
|
||||
- `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]>
|
||||
@ -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`.
|
||||
|
||||
#### 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.
|
||||
- `value` <[string]> Matches by `option.value`.
|
||||
- `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.
|
||||
|
||||
#### 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]>
|
||||
- `button` <"left"|"right"|"middle"> Defaults to `left`.
|
||||
- `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.
|
||||
|
||||
#### 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.
|
||||
- `options` <[Object]>
|
||||
- `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])
|
||||
- `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
|
||||
- `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.
|
||||
- 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])
|
||||
- `xpath` <[string]> A [xpath] of an element to wait for
|
||||
- `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.
|
||||
- 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.$(selector)
|
||||
- `selector` <[string]> A [selector] to query element for
|
||||
- `selector` <[string]|[Selector]> A [selector] to query element for
|
||||
- returns: <[Promise]<?[ElementHandle]>>
|
||||
|
||||
The method runs `element.querySelector` within the page. If no element matches the selector, the return value resolves to `null`.
|
||||
|
||||
#### elementHandle.$$(selector)
|
||||
- `selector` <[string]> A [selector] to query element for
|
||||
- `selector` <[string]|[Selector]> A [selector] to query element for
|
||||
- returns: <[Promise]<[Array]<[ElementHandle]>>>
|
||||
|
||||
The method runs `element.querySelectorAll` within the page. If no elements match the selector, the return value resolves to `[]`.
|
||||
|
||||
#### 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
|
||||
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `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])
|
||||
- `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
|
||||
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `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).
|
||||
|
||||
### 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"
|
||||
@ -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"
|
||||
[Request]: #class-request "Request"
|
||||
[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"
|
||||
[Target]: #class-target "Target"
|
||||
[TimeoutError]: #class-timeouterror "TimeoutError"
|
||||
|
||||
@ -222,7 +222,7 @@ export class Page extends EventEmitter {
|
||||
this._timeoutSettings.setDefaultTimeout(timeout);
|
||||
}
|
||||
|
||||
async $(selector: string): Promise<dom.ElementHandle | null> {
|
||||
async $(selector: string | types.Selector): Promise<dom.ElementHandle | null> {
|
||||
return this.mainFrame().$(selector);
|
||||
}
|
||||
|
||||
@ -239,7 +239,7 @@ export class Page extends EventEmitter {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -535,35 +535,35 @@ export class Page extends EventEmitter {
|
||||
return this._mouse;
|
||||
}
|
||||
|
||||
click(selector: string, options?: ClickOptions) {
|
||||
click(selector: string | types.Selector, options?: ClickOptions) {
|
||||
return this.mainFrame().click(selector, options);
|
||||
}
|
||||
|
||||
dblclick(selector: string, options?: MultiClickOptions) {
|
||||
dblclick(selector: string | types.Selector, options?: MultiClickOptions) {
|
||||
return this.mainFrame().dblclick(selector, options);
|
||||
}
|
||||
|
||||
tripleclick(selector: string, options?: MultiClickOptions) {
|
||||
tripleclick(selector: string | types.Selector, options?: MultiClickOptions) {
|
||||
return this.mainFrame().tripleclick(selector, options);
|
||||
}
|
||||
|
||||
fill(selector: string, value: string) {
|
||||
fill(selector: string | types.Selector, value: string) {
|
||||
return this.mainFrame().fill(selector, value);
|
||||
}
|
||||
|
||||
focus(selector: string) {
|
||||
focus(selector: string | types.Selector) {
|
||||
return this.mainFrame().focus(selector);
|
||||
}
|
||||
|
||||
hover(selector: string, options?: PointerActionOptions) {
|
||||
hover(selector: string | types.Selector, options?: PointerActionOptions) {
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -571,15 +571,15 @@ export class Page extends EventEmitter {
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
150
src/dom.ts
150
src/dom.ts
@ -10,6 +10,7 @@ import * as cssSelectorEngineSource from './generated/cssSelectorEngineSource';
|
||||
import * as xpathSelectorEngineSource from './generated/xpathSelectorEngineSource';
|
||||
import { assert, helper } from './helper';
|
||||
import Injected from './injected/injected';
|
||||
import { SelectorRoot } from './injected/selectorEngine';
|
||||
|
||||
export interface DOMWorldDelegate {
|
||||
keyboard: input.Keyboard;
|
||||
@ -25,10 +26,8 @@ export interface DOMWorldDelegate {
|
||||
adoptElementHandle(handle: ElementHandle, to: DOMWorld): Promise<ElementHandle>;
|
||||
}
|
||||
|
||||
type SelectorRoot = Element | ShadowRoot | Document;
|
||||
|
||||
type ResolvedSelector = { root?: ElementHandle, selector: string, disposeRoot?: boolean };
|
||||
type Selector = string | { root?: ElementHandle, selector: string };
|
||||
export type ScopedSelector = types.Selector & { scope?: ElementHandle };
|
||||
type ResolvedSelector = { scope?: ElementHandle, selector: string, visible?: boolean, disposeScope?: boolean };
|
||||
|
||||
export class DOMWorld {
|
||||
readonly context: js.ExecutionContext;
|
||||
@ -65,37 +64,47 @@ export class DOMWorld {
|
||||
return this.delegate.adoptElementHandle(handle, this);
|
||||
}
|
||||
|
||||
private async _resolveSelector(selector: Selector): Promise<ResolvedSelector> {
|
||||
async resolveSelector(selector: string | ScopedSelector): Promise<ResolvedSelector> {
|
||||
if (helper.isString(selector))
|
||||
return { selector: normalizeSelector(selector) };
|
||||
if (selector.root && selector.root.executionContext() !== this.context) {
|
||||
const root = await this.adoptElementHandle(selector.root);
|
||||
return { root, selector: normalizeSelector(selector.selector), disposeRoot: true };
|
||||
if (selector.scope && selector.scope.executionContext() !== this.context) {
|
||||
const scope = await this.adoptElementHandle(selector.scope);
|
||||
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> {
|
||||
const resolved = await this._resolveSelector(selector);
|
||||
async $(selector: string | ScopedSelector): Promise<ElementHandle | null> {
|
||||
const resolved = await this.resolveSelector(selector);
|
||||
const handle = await this.context.evaluateHandle(
|
||||
(injected: Injected, selector: string, root: SelectorRoot | undefined) => injected.querySelector(selector, root || document),
|
||||
await this.injected(), resolved.selector, resolved.root
|
||||
(injected: Injected, selector: string, scope: SelectorRoot | undefined, visible: boolean | undefined) => {
|
||||
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)
|
||||
await resolved.root.dispose();
|
||||
if (resolved.disposeScope)
|
||||
await resolved.scope.dispose();
|
||||
if (!handle.asElement())
|
||||
await handle.dispose();
|
||||
return handle.asElement();
|
||||
}
|
||||
|
||||
async $$(selector: Selector): Promise<ElementHandle[]> {
|
||||
const resolved = await this._resolveSelector(selector);
|
||||
async $$(selector: string | ScopedSelector): Promise<ElementHandle[]> {
|
||||
const resolved = await this.resolveSelector(selector);
|
||||
const arrayHandle = await this.context.evaluateHandle(
|
||||
(injected: Injected, selector: string, root: SelectorRoot | undefined) => injected.querySelectorAll(selector, root || document),
|
||||
await this.injected(), resolved.selector, resolved.root
|
||||
(injected: Injected, selector: string, scope: SelectorRoot | undefined, visible: boolean | undefined) => {
|
||||
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)
|
||||
await resolved.root.dispose();
|
||||
if (resolved.disposeScope)
|
||||
await resolved.scope.dispose();
|
||||
const properties = await arrayHandle.getProperties();
|
||||
await arrayHandle.dispose();
|
||||
const result = [];
|
||||
@ -109,20 +118,25 @@ export class DOMWorld {
|
||||
return result;
|
||||
}
|
||||
|
||||
$eval: types.$Eval<Selector> = async (selector, pageFunction, ...args) => {
|
||||
$eval: types.$Eval<string | ScopedSelector> = async (selector, pageFunction, ...args) => {
|
||||
const elementHandle = await this.$(selector);
|
||||
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);
|
||||
await elementHandle.dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
$$eval: types.$$Eval<Selector> = async (selector, pageFunction, ...args) => {
|
||||
const resolved = await this._resolveSelector(selector);
|
||||
$$eval: types.$$Eval<string | ScopedSelector> = async (selector, pageFunction, ...args) => {
|
||||
const resolved = await this.resolveSelector(selector);
|
||||
const arrayHandle = await this.context.evaluateHandle(
|
||||
(injected: Injected, selector: string, root: SelectorRoot | undefined) => injected.querySelectorAll(selector, root || document),
|
||||
await this.injected(), resolved.selector, resolved.root
|
||||
(injected: Injected, selector: string, scope: SelectorRoot | undefined, visible: boolean | undefined) => {
|
||||
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);
|
||||
await arrayHandle.dispose();
|
||||
@ -223,6 +237,7 @@ export class ElementHandle extends js.JSHandle {
|
||||
if (error)
|
||||
throw new Error(error);
|
||||
await this.focus();
|
||||
// TODO: we should check that focus() succeeded.
|
||||
await this._world.delegate.keyboard.sendCharacters(value);
|
||||
}
|
||||
|
||||
@ -254,24 +269,31 @@ export class ElementHandle extends js.JSHandle {
|
||||
return this._world.delegate.screenshot(this, options);
|
||||
}
|
||||
|
||||
$(selector: string): Promise<ElementHandle | null> {
|
||||
return this._world.$({ root: this, selector });
|
||||
private _scopedSelector(selector: string | types.Selector): string | ScopedSelector {
|
||||
selector = types.clearSelector(selector);
|
||||
if (helper.isString(selector))
|
||||
selector = { selector };
|
||||
return { scope: this, selector: selector.selector, visible: selector.visible };
|
||||
}
|
||||
|
||||
$$(selector: string): Promise<ElementHandle[]> {
|
||||
return this._world.$$({ root: this, selector });
|
||||
$(selector: string | types.Selector): Promise<ElementHandle | null> {
|
||||
return this._world.$(this._scopedSelector(selector));
|
||||
}
|
||||
|
||||
$eval: types.$Eval = (selector, pageFunction, ...args) => {
|
||||
return this._world.$eval({ root: this, selector }, pageFunction, ...args as any);
|
||||
$$(selector: string | types.Selector): Promise<ElementHandle[]> {
|
||||
return this._world.$$(this._scopedSelector(selector));
|
||||
}
|
||||
|
||||
$$eval: types.$$Eval = (selector, pageFunction, ...args) => {
|
||||
return this._world.$$eval({ root: this, 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);
|
||||
}
|
||||
|
||||
$$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[]> {
|
||||
return this._world.$$({ root: this, selector: 'xpath=' + expression });
|
||||
return this._world.$$({ scope: this, selector: 'xpath=' + expression });
|
||||
}
|
||||
|
||||
isIntersectingViewport(): Promise<boolean> {
|
||||
@ -300,18 +322,9 @@ function normalizeSelector(selector: string): string {
|
||||
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 Polling = 'raf' | 'mutation' | number;
|
||||
export type WaitForFunctionOptions = { polling?: Polling, timeout?: number };
|
||||
|
||||
export function waitForFunctionTask(pageFunction: Function | string, options: WaitForFunctionOptions, ...args: any[]) {
|
||||
export function waitForFunctionTask(pageFunction: Function | string, options: types.WaitForFunctionOptions, ...args: any[]) {
|
||||
const { polling = 'raf' } = options;
|
||||
if (helper.isString(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);
|
||||
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);
|
||||
if (polling === 'raf')
|
||||
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);
|
||||
}
|
||||
|
||||
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 {
|
||||
const { visible: waitForVisible = false, hidden: waitForHidden = false } = options;
|
||||
selector = normalizeSelector(selector);
|
||||
|
||||
return async (domWorld: DOMWorld) => domWorld.context.evaluateHandle((injected: Injected, selector: string, waitForVisible: boolean, waitForHidden: boolean, timeout: number) => {
|
||||
if (waitForVisible || waitForHidden)
|
||||
return injected.pollRaf(predicate, timeout);
|
||||
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);
|
||||
function predicate(): Element | boolean {
|
||||
const element = injected.querySelector(selector, scope || document);
|
||||
if (!element)
|
||||
return visible === false;
|
||||
if (visible === undefined)
|
||||
return element;
|
||||
return injected.isVisible(element) === visible ? element : false;
|
||||
}
|
||||
}
|
||||
}, await domWorld.injected(), selector, waitForVisible, waitForHidden, options.timeout);
|
||||
}, await domWorld.injected(), resolved.selector, resolved.scope, resolved.visible, timeout);
|
||||
};
|
||||
}
|
||||
|
||||
@ -118,7 +118,7 @@ export class Page extends EventEmitter {
|
||||
}
|
||||
|
||||
async emulateMedia(options: {
|
||||
type?: ""|"screen"|"print",
|
||||
type?: ''|'screen'|'print',
|
||||
colorScheme?: 'dark' | 'light' | 'no-preference' }) {
|
||||
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);
|
||||
@ -457,35 +457,35 @@ export class Page extends EventEmitter {
|
||||
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);
|
||||
}
|
||||
|
||||
dblclick(selector: string, options?: input.MultiClickOptions) {
|
||||
dblclick(selector: string | types.Selector, options?: input.MultiClickOptions) {
|
||||
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);
|
||||
}
|
||||
|
||||
fill(selector: string, value: string) {
|
||||
fill(selector: string | types.Selector, value: string) {
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
focus(selector: string) {
|
||||
focus(selector: string | types.Selector) {
|
||||
return this._frameManager.mainFrame().focus(selector);
|
||||
}
|
||||
|
||||
hover(selector: string) {
|
||||
hover(selector: string | types.Selector) {
|
||||
return this._frameManager.mainFrame().hover(selector);
|
||||
}
|
||||
|
||||
@ -493,15 +493,15 @@ export class Page extends EventEmitter {
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -509,11 +509,11 @@ export class Page extends EventEmitter {
|
||||
return this._frameManager.mainFrame().title();
|
||||
}
|
||||
|
||||
$(selector: string): Promise<dom.ElementHandle | null> {
|
||||
$(selector: string | types.Selector): Promise<dom.ElementHandle | null> {
|
||||
return this._frameManager.mainFrame().$(selector);
|
||||
}
|
||||
|
||||
$$(selector: string): Promise<Array<dom.ElementHandle>> {
|
||||
$$(selector: string | types.Selector): Promise<Array<dom.ElementHandle>> {
|
||||
return this._frameManager.mainFrame().$$(selector);
|
||||
}
|
||||
|
||||
|
||||
@ -122,9 +122,9 @@ export class Frame {
|
||||
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();
|
||||
return domWorld.$(selector);
|
||||
return domWorld.$(types.clearSelector(selector));
|
||||
}
|
||||
|
||||
async $x(expression: string): Promise<dom.ElementHandle[]> {
|
||||
@ -142,9 +142,9 @@ export class Frame {
|
||||
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();
|
||||
return domWorld.$$(selector);
|
||||
return domWorld.$$(types.clearSelector(selector));
|
||||
}
|
||||
|
||||
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 handle = await domWorld.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
const handle = await domWorld.$(types.clearSelector(selector));
|
||||
assert(handle, 'No node found for selector: ' + types.selectorToString(selector));
|
||||
await handle.click(options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async dblclick(selector: string, options?: MultiClickOptions) {
|
||||
async dblclick(selector: string | types.Selector, options?: MultiClickOptions) {
|
||||
const domWorld = await this._utilityDOMWorld();
|
||||
const handle = await domWorld.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
const handle = await domWorld.$(types.clearSelector(selector));
|
||||
assert(handle, 'No node found for selector: ' + types.selectorToString(selector));
|
||||
await handle.dblclick(options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async tripleclick(selector: string, options?: MultiClickOptions) {
|
||||
async tripleclick(selector: string | types.Selector, options?: MultiClickOptions) {
|
||||
const domWorld = await this._utilityDOMWorld();
|
||||
const handle = await domWorld.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
const handle = await domWorld.$(types.clearSelector(selector));
|
||||
assert(handle, 'No node found for selector: ' + types.selectorToString(selector));
|
||||
await handle.tripleclick(options);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async fill(selector: string, value: string) {
|
||||
async fill(selector: string | types.Selector, value: string) {
|
||||
const domWorld = await this._utilityDOMWorld();
|
||||
const handle = await domWorld.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
const handle = await domWorld.$(types.clearSelector(selector));
|
||||
assert(handle, 'No node found for selector: ' + types.selectorToString(selector));
|
||||
await handle.fill(value);
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async focus(selector: string) {
|
||||
async focus(selector: string | types.Selector) {
|
||||
const domWorld = await this._utilityDOMWorld();
|
||||
const handle = await domWorld.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
const handle = await domWorld.$(types.clearSelector(selector));
|
||||
assert(handle, 'No node found for selector: ' + types.selectorToString(selector));
|
||||
await handle.focus();
|
||||
await handle.dispose();
|
||||
}
|
||||
|
||||
async hover(selector: string, options?: PointerActionOptions) {
|
||||
async hover(selector: string | types.Selector, options?: PointerActionOptions) {
|
||||
const domWorld = await this._utilityDOMWorld();
|
||||
const handle = await domWorld.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
const handle = await domWorld.$(types.clearSelector(selector));
|
||||
assert(handle, 'No node found for selector: ' + types.selectorToString(selector));
|
||||
await handle.hover(options);
|
||||
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 handle = await domWorld.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
const handle = await domWorld.$(types.clearSelector(selector));
|
||||
assert(handle, 'No node found for selector: ' + types.selectorToString(selector));
|
||||
const toDispose: Promise<dom.ElementHandle>[] = [];
|
||||
const adoptedValues = await Promise.all(values.map(async value => {
|
||||
if (value instanceof dom.ElementHandle && value.executionContext() !== domWorld.context) {
|
||||
@ -367,10 +367,10 @@ export class Frame {
|
||||
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 handle = await domWorld.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
const handle = await domWorld.$(types.clearSelector(selector));
|
||||
assert(handle, 'No node found for selector: ' + types.selectorToString(selector));
|
||||
await handle.type(text, options);
|
||||
await handle.dispose();
|
||||
}
|
||||
@ -385,10 +385,10 @@ export class Frame {
|
||||
return Promise.reject(new Error('Unsupported target type: ' + (typeof selectorOrFunctionOrTimeout)));
|
||||
}
|
||||
|
||||
async waitForSelector(selector: string, options: dom.WaitForSelectorOptions = {}): Promise<dom.ElementHandle | null> {
|
||||
const task = dom.waitForSelectorTask(selector, { timeout: this._timeoutSettings.timeout(), ...options });
|
||||
const title = `selector "${selector}"${options.hidden ? ' to be hidden' : ''}`;
|
||||
const handle = await this._scheduleRerunnableTask(task, 'utility', options.timeout, title);
|
||||
async waitForSelector(selector: string | types.Selector, options: types.TimeoutOptions = {}): Promise<dom.ElementHandle | null> {
|
||||
const { timeout = this._timeoutSettings.timeout() } = options;
|
||||
const task = dom.waitForSelectorTask(types.clearSelector(selector), timeout);
|
||||
const handle = await this._scheduleRerunnableTask(task, 'utility', timeout, `selector "${types.selectorToString(selector)}"`);
|
||||
if (!handle.asElement()) {
|
||||
await handle.dispose();
|
||||
return null;
|
||||
@ -401,11 +401,11 @@ export class Frame {
|
||||
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);
|
||||
}
|
||||
|
||||
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 };
|
||||
const task = dom.waitForFunctionTask(pageFunction, options, ...args);
|
||||
return this._scheduleRerunnableTask(task, 'main', options.timeout);
|
||||
|
||||
@ -82,6 +82,16 @@ class Injected {
|
||||
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> {
|
||||
let timedOut = false;
|
||||
if (timeout)
|
||||
|
||||
25
src/types.ts
25
src/types.ts
@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import * as js from './javascript';
|
||||
import { helper } from './helper';
|
||||
|
||||
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>);
|
||||
@ -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 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> = <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 | 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 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 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 };
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@ export class Launcher {
|
||||
stdio = ['ignore', 'ignore', 'ignore', 'pipe', 'pipe'];
|
||||
webkitArguments.push('--inspector-pipe');
|
||||
if (options.headless !== false)
|
||||
webkitArguments.push('--headless');
|
||||
webkitArguments.push('--headless');
|
||||
const webkitProcess = childProcess.spawn(
|
||||
webkitExecutable,
|
||||
webkitArguments,
|
||||
|
||||
@ -206,7 +206,7 @@ export class Page extends EventEmitter {
|
||||
this._timeoutSettings.setDefaultTimeout(timeout);
|
||||
}
|
||||
|
||||
async $(selector: string): Promise<dom.ElementHandle | null> {
|
||||
async $(selector: string | types.Selector): Promise<dom.ElementHandle | null> {
|
||||
return this.mainFrame().$(selector);
|
||||
}
|
||||
|
||||
@ -223,7 +223,7 @@ export class Page extends EventEmitter {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -466,35 +466,35 @@ export class Page extends EventEmitter {
|
||||
return this._mouse;
|
||||
}
|
||||
|
||||
click(selector: string, options?: ClickOptions) {
|
||||
click(selector: string | types.Selector, options?: ClickOptions) {
|
||||
return this.mainFrame().click(selector, options);
|
||||
}
|
||||
|
||||
dblclick(selector: string, options?: MultiClickOptions) {
|
||||
dblclick(selector: string | types.Selector, options?: MultiClickOptions) {
|
||||
return this.mainFrame().dblclick(selector, options);
|
||||
}
|
||||
|
||||
tripleclick(selector: string, options?: MultiClickOptions) {
|
||||
tripleclick(selector: string | types.Selector, options?: MultiClickOptions) {
|
||||
return this.mainFrame().tripleclick(selector, options);
|
||||
}
|
||||
|
||||
hover(selector: string) {
|
||||
hover(selector: string | types.Selector) {
|
||||
return this.mainFrame().hover(selector);
|
||||
}
|
||||
|
||||
fill(selector: string, value: string) {
|
||||
fill(selector: string | types.Selector, value: string) {
|
||||
return this.mainFrame().fill(selector, value);
|
||||
}
|
||||
|
||||
focus(selector: string) {
|
||||
focus(selector: string | types.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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -502,15 +502,15 @@ export class Page extends EventEmitter {
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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}) => {
|
||||
await page.goto(server.PREFIX + '/wrappedlink.html');
|
||||
await page.click('a');
|
||||
|
||||
@ -1119,6 +1119,27 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF
|
||||
await page.fill('textarea', 123).catch(e => error = e);
|
||||
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
|
||||
|
||||
@ -35,6 +35,19 @@ module.exports.addTests = function({testRunner, expect, product, FFOX, CHROME, W
|
||||
const idAttribute = await page.$eval('section', e => e.id);
|
||||
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}) => {
|
||||
await page.setContent('<section>hello</section>');
|
||||
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);
|
||||
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() {
|
||||
@ -139,6 +158,17 @@ module.exports.addTests = function({testRunner, expect, product, FFOX, CHROME, W
|
||||
const element = await page.$('css=section >> css=div');
|
||||
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() {
|
||||
@ -154,6 +184,12 @@ module.exports.addTests = function({testRunner, expect, product, FFOX, CHROME, W
|
||||
const elements = await page.$$('div');
|
||||
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() {
|
||||
@ -192,6 +228,22 @@ module.exports.addTests = function({testRunner, expect, product, FFOX, CHROME, W
|
||||
const second = await html.$('.third');
|
||||
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() {
|
||||
it('should work', async({page, server}) => {
|
||||
@ -214,7 +266,7 @@ module.exports.addTests = function({testRunner, expect, product, FFOX, CHROME, W
|
||||
await page.setContent(htmlContent);
|
||||
const elementHandle = await page.$('#myId');
|
||||
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() {
|
||||
|
||||
@ -289,7 +289,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
|
||||
});
|
||||
it('should wait for visible', async({page, server}) => {
|
||||
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>`);
|
||||
expect(divFound).toBe(false);
|
||||
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}) => {
|
||||
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>`);
|
||||
expect(divVisible).toBe(false);
|
||||
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}) => {
|
||||
let divHidden = false;
|
||||
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
|
||||
expect(divHidden).toBe(false);
|
||||
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}) => {
|
||||
let divHidden = false;
|
||||
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
|
||||
expect(divHidden).toBe(false);
|
||||
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}) => {
|
||||
await page.setContent(`<div></div>`);
|
||||
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
|
||||
expect(divRemoved).toBe(false);
|
||||
await page.evaluate(() => document.querySelector('div').remove());
|
||||
@ -340,7 +340,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
|
||||
expect(divRemoved).toBe(true);
|
||||
});
|
||||
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);
|
||||
});
|
||||
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}) => {
|
||||
await page.setContent(`<div></div>`);
|
||||
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.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}) => {
|
||||
@ -426,16 +426,6 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO
|
||||
expect(waitError).toBeTruthy();
|
||||
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}) => {
|
||||
const waitForXPath = page.waitForXPath('//*[@class="zombo"]');
|
||||
await page.setContent(`<div class='zombo'>anything</div>`);
|
||||
|
||||
@ -154,6 +154,15 @@ function checkSources(sources) {
|
||||
typeName = 'Object';
|
||||
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)) {
|
||||
let properties = undefined;
|
||||
if (!circular.includes(typeName))
|
||||
|
||||
@ -114,8 +114,10 @@ function checkSorting(doc) {
|
||||
function filterJSDocumentation(jsSources, jsDocumentation) {
|
||||
const apijs = jsSources.find(source => source.name() === 'api.ts');
|
||||
let includedClasses = null;
|
||||
if (apijs)
|
||||
if (apijs) {
|
||||
includedClasses = new Set(Object.keys(require(path.join(apijs.filePath(), '..', '..', 'lib', 'api.js')).Chromium));
|
||||
includedClasses.add('Selector');
|
||||
}
|
||||
// Filter private classes and methods.
|
||||
const classes = [];
|
||||
for (const cls of jsDocumentation.classesArray) {
|
||||
|
||||
@ -3,6 +3,7 @@ const path = require('path');
|
||||
const fs = require('fs');
|
||||
const StreamZip = require('node-stream-zip');
|
||||
const vm = require('vm');
|
||||
const os = require('os');
|
||||
|
||||
async function generateChromeProtocol(revision) {
|
||||
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');
|
||||
if (revision.local && fs.existsSync(outputPath))
|
||||
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
|
||||
await new Promise(x => zip.on('ready', x));
|
||||
const data = zip.entryDataSync(zip.entry('chrome/juggler/content/protocol/Protocol.js'))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user