api: waitForElement accepts waitFor: attached|detached|visible|hidden (#1244)

This includes rename waitForSelector -> waitForElement and removes $wait.
This commit is contained in:
Dmitry Gozman 2020-03-05 17:45:41 -08:00 committed by GitHub
parent 9bc6dcea1d
commit 1d770af804
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 174 additions and 225 deletions

View file

@ -94,7 +94,7 @@ const iPhone = devices['iPhone 6'];
- returns: <[Object]> - returns: <[Object]>
- `TimeoutError` <[function]> A class of [TimeoutError]. - `TimeoutError` <[function]> A class of [TimeoutError].
Playwright methods might throw errors if they are unable to fulfill a request. For example, [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options) Playwright methods might throw errors if they are unable to fulfill a request. For example, [page.waitForElement(selector[, options])](#pagewaitforelementselector-options)
might fail if the selector doesn't match any nodes during the given timeframe. might fail if the selector doesn't match any nodes during the given timeframe.
For certain types of errors Playwright uses specific error classes. For certain types of errors Playwright uses specific error classes.
@ -103,7 +103,7 @@ These classes are available via [`browserType.errors`](#browsertypeerrors) or [`
An example of handling a timeout error: An example of handling a timeout error:
```js ```js
try { try {
await page.waitForSelector('.foo'); await page.waitForElement('.foo');
} catch (e) { } catch (e) {
if (e instanceof playwright.errors.TimeoutError) { if (e instanceof playwright.errors.TimeoutError) {
// Do something if this is a timeout. // Do something if this is a timeout.
@ -620,7 +620,6 @@ page.removeListener('request', logRequest);
- [page.$$(selector)](#pageselector-1) - [page.$$(selector)](#pageselector-1)
- [page.$$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args) - [page.$$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args)
- [page.$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args-1) - [page.$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args-1)
- [page.$wait(selector[, options])](#pagewaitselector-options)
- [page.accessibility](#pageaccessibility) - [page.accessibility](#pageaccessibility)
- [page.addInitScript(script[, ...args])](#pageaddinitscriptscript-args) - [page.addInitScript(script[, ...args])](#pageaddinitscriptscript-args)
- [page.addScriptTag(options)](#pageaddscripttagoptions) - [page.addScriptTag(options)](#pageaddscripttagoptions)
@ -667,13 +666,13 @@ page.removeListener('request', logRequest);
- [page.url()](#pageurl) - [page.url()](#pageurl)
- [page.viewportSize()](#pageviewportsize) - [page.viewportSize()](#pageviewportsize)
- [page.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#pagewaitforselectororfunctionortimeout-options-args) - [page.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#pagewaitforselectororfunctionortimeout-options-args)
- [page.waitForElement(selector[, options])](#pagewaitforelementselector-options)
- [page.waitForEvent(event[, optionsOrPredicate])](#pagewaitforeventevent-optionsorpredicate) - [page.waitForEvent(event[, optionsOrPredicate])](#pagewaitforeventevent-optionsorpredicate)
- [page.waitForFunction(pageFunction[, options[, ...args]])](#pagewaitforfunctionpagefunction-options-args) - [page.waitForFunction(pageFunction[, options[, ...args]])](#pagewaitforfunctionpagefunction-options-args)
- [page.waitForLoadState([options])](#pagewaitforloadstateoptions) - [page.waitForLoadState([options])](#pagewaitforloadstateoptions)
- [page.waitForNavigation([options])](#pagewaitfornavigationoptions) - [page.waitForNavigation([options])](#pagewaitfornavigationoptions)
- [page.waitForRequest(urlOrPredicate[, options])](#pagewaitforrequesturlorpredicate-options) - [page.waitForRequest(urlOrPredicate[, options])](#pagewaitforrequesturlorpredicate-options)
- [page.waitForResponse(urlOrPredicate[, options])](#pagewaitforresponseurlorpredicate-options) - [page.waitForResponse(urlOrPredicate[, options])](#pagewaitforresponseurlorpredicate-options)
- [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options)
- [page.workers()](#pageworkers) - [page.workers()](#pageworkers)
<!-- GEN:stop --> <!-- GEN:stop -->
@ -842,25 +841,6 @@ const html = await page.$eval('.main-container', e => e.outerHTML);
Shortcut for [page.mainFrame().$eval(selector, pageFunction)](#frameevalselector-pagefunction-args). Shortcut for [page.mainFrame().$eval(selector, pageFunction)](#frameevalselector-pagefunction-args).
#### page.$wait(selector[, options])
- `selector` <[string]> A selector of an element to wait for
- `options` <[Object]>
- `visibility` <"visible"|"hidden"|"any"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`any`). Defaults to `any`.
- `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
- 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.
Wait for the `selector` to appear in page. If at the moment of calling
the method the `selector` already exists, the method will return
immediately. If the selector doesn't appear after the `timeout` milliseconds of waiting, the function will throw.
This method works across navigations:
```js
const handle = await page.$wait(selector);
await handle.click();
```
This is a shortcut to [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options).
#### page.accessibility #### page.accessibility
- returns: <[Accessibility]> - returns: <[Accessibility]>
@ -1585,7 +1565,7 @@ This is a shortcut for [page.mainFrame().url()](#frameurl)
#### page.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]]) #### page.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])
- `selectorOrFunctionOrTimeout` <[string]|[number]|[function]> A [selector], predicate or timeout to wait for - `selectorOrFunctionOrTimeout` <[string]|[number]|[function]> A [selector], predicate or timeout to wait for
- `options` <[Object]> Optional waiting parameters - `options` <[Object]> Optional waiting parameters
- `visibility` <"visible"|"hidden"|"any"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`any`). Defaults to `any`. - `waitFor` <"attached"|"detached"|"visible"|"hidden"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`attached`) or not present in dom (`detached`). Defaults to `attached`.
- `polling` <[number]|"raf"|"mutation"> An interval at which the `pageFunction` is executed, defaults to `raf`. If `polling` is a number, then it is treated as an interval in milliseconds at which the function would be executed. If `polling` is a string, then it can be one of the following values: - `polling` <[number]|"raf"|"mutation"> An interval at which the `pageFunction` is executed, defaults to `raf`. If `polling` is a number, then it is treated as an interval in milliseconds at which the function would be executed. If `polling` is a string, then it can be one of the following values:
- `'raf'` - to constantly execute `pageFunction` in `requestAnimationFrame` callback. This is the tightest polling mode which is suitable to observe styling changes. - `'raf'` - to constantly execute `pageFunction` in `requestAnimationFrame` callback. This is the tightest polling mode which is suitable to observe styling changes.
- `'mutation'` - to execute `pageFunction` on every DOM mutation. - `'mutation'` - to execute `pageFunction` on every DOM mutation.
@ -1594,7 +1574,7 @@ This is a shortcut for [page.mainFrame().url()](#frameurl)
- returns: <[Promise]<[JSHandle]>> Promise which resolves to a JSHandle of the success value - returns: <[Promise]<[JSHandle]>> Promise which resolves to a JSHandle of the success value
This method behaves differently with respect to the type of the first parameter: This method behaves differently with respect to the type of the first parameter:
- if `selectorOrFunctionOrTimeout` is a `string`, then the first argument is treated as a [selector] and the method is a shortcut for [page.waitForSelector](#pagewaitforselectorselector-options) - if `selectorOrFunctionOrTimeout` is a `string`, then the first argument is treated as a [selector] and the method is a shortcut for [page.waitForElement](#pagewaitforelementselector-options)
- if `selectorOrFunctionOrTimeout` is a `function`, then the first argument is treated as a predicate to wait for and the method is a shortcut for [page.waitForFunction()](#pagewaitforfunctionpagefunction-options-args). - if `selectorOrFunctionOrTimeout` is a `function`, then the first argument is treated as a predicate to wait for and the method is a shortcut for [page.waitForFunction()](#pagewaitforfunctionpagefunction-options-args).
- if `selectorOrFunctionOrTimeout` is a `number`, then the first argument is treated as a timeout in milliseconds and the method returns a promise which resolves after the timeout - if `selectorOrFunctionOrTimeout` is a `number`, then the first argument is treated as a timeout in milliseconds and the method returns a promise which resolves after the timeout
- otherwise, an exception is thrown - otherwise, an exception is thrown
@ -1615,7 +1595,35 @@ const selector = '.foo';
await page.waitFor(selector => !!document.querySelector(selector), {}, selector); await page.waitFor(selector => !!document.querySelector(selector), {}, selector);
``` ```
Shortcut for [page.mainFrame().waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#framewaitforselectororfunctionortimeout-options-args). Shortcut for [page.mainFrame().waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#framewaitforelementorfunctionortimeout-options-args).
#### page.waitForElement(selector[, options])
- `selector` <[string]> A selector of an element to wait for
- `options` <[Object]>
- `waitFor` <"attached"|"detached"|"visible"|"hidden"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`attached`) or not present in dom (`detached`). Defaults to `attached`.
- `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
- returns: <[Promise]<?[ElementHandle]>> Promise which resolves when element specified by selector satisfies `waitFor` option. Resolves to `null` if waiting for `hidden` or `detached`.
Wait for the `selector` to satisfy `waitFor` option (either appear/disappear from dom, or become visible/hidden). If at the moment of calling the method `selector` already satisfies the condition, the method will return immediately. If the selector doesn't satisfy the condition for the `timeout` milliseconds, the function will throw.
This method works across navigations:
```js
const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'.
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
let currentURL;
page
.waitForElement('img')
.then(() => console.log('First URL with image: ' + currentURL));
for (currentURL of ['https://example.com', 'https://google.com', 'https://bbc.com']) {
await page.goto(currentURL);
}
await browser.close();
})();
```
Shortcut for [page.mainFrame().waitForElement(selector[, options])](#framewaitforelementselector-options).
#### page.waitForEvent(event[, optionsOrPredicate]) #### page.waitForEvent(event[, optionsOrPredicate])
- `event` <[string]> Event name, same one would pass into `page.on(event)`. - `event` <[string]> Event name, same one would pass into `page.on(event)`.
@ -1733,36 +1741,6 @@ const finalResponse = await page.waitForResponse(response => response.url() ===
return finalResponse.ok(); return finalResponse.ok();
``` ```
#### page.waitForSelector(selector[, options])
- `selector` <[string]> A selector of an element to wait for
- `options` <[Object]>
- `visibility` <"visible"|"hidden"|"any"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`any`). Defaults to `any`.
- `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
- 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.
Wait for the `selector` to appear in page. If at the moment of calling
the method the `selector` already exists, the method will return
immediately. If the selector doesn't appear after the `timeout` milliseconds of waiting, the function will throw.
This method works across navigations:
```js
const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'.
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
let currentURL;
page
.waitForSelector('img')
.then(() => console.log('First URL with image: ' + currentURL));
for (currentURL of ['https://example.com', 'https://google.com', 'https://bbc.com']) {
await page.goto(currentURL);
}
await browser.close();
})();
```
Shortcut for [page.mainFrame().waitForSelector(selector[, options])](#framewaitforselectorselector-options).
#### page.workers() #### page.workers()
- returns: <[Array]<[Worker]>> - returns: <[Array]<[Worker]>>
This method returns all of the dedicated [WebWorkers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) associated with the page. This method returns all of the dedicated [WebWorkers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) associated with the page.
@ -1819,7 +1797,6 @@ An example of getting text from an iframe element:
- [frame.$$(selector)](#frameselector-1) - [frame.$$(selector)](#frameselector-1)
- [frame.$$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args) - [frame.$$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args)
- [frame.$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args-1) - [frame.$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args-1)
- [frame.$wait(selector[, options])](#framewaitselector-options)
- [frame.addScriptTag(options)](#frameaddscripttagoptions) - [frame.addScriptTag(options)](#frameaddscripttagoptions)
- [frame.addStyleTag(options)](#frameaddstyletagoptions) - [frame.addStyleTag(options)](#frameaddstyletagoptions)
- [frame.check(selector, [options])](#framecheckselector-options) - [frame.check(selector, [options])](#framecheckselector-options)
@ -1845,10 +1822,10 @@ An example of getting text from an iframe element:
- [frame.uncheck(selector, [options])](#frameuncheckselector-options) - [frame.uncheck(selector, [options])](#frameuncheckselector-options)
- [frame.url()](#frameurl) - [frame.url()](#frameurl)
- [frame.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#framewaitforselectororfunctionortimeout-options-args) - [frame.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#framewaitforselectororfunctionortimeout-options-args)
- [frame.waitForElement(selector[, options])](#framewaitforelementselector-options)
- [frame.waitForFunction(pageFunction[, options[, ...args]])](#framewaitforfunctionpagefunction-options-args) - [frame.waitForFunction(pageFunction[, options[, ...args]])](#framewaitforfunctionpagefunction-options-args)
- [frame.waitForLoadState([options])](#framewaitforloadstateoptions) - [frame.waitForLoadState([options])](#framewaitforloadstateoptions)
- [frame.waitForNavigation([options])](#framewaitfornavigationoptions) - [frame.waitForNavigation([options])](#framewaitfornavigationoptions)
- [frame.waitForSelector(selector[, options])](#framewaitforselectorselector-options)
<!-- GEN:stop --> <!-- GEN:stop -->
#### frame.$(selector) #### frame.$(selector)
@ -1895,25 +1872,6 @@ const preloadHref = await frame.$eval('link[rel=preload]', el => el.href);
const html = await frame.$eval('.main-container', e => e.outerHTML); const html = await frame.$eval('.main-container', e => e.outerHTML);
``` ```
#### frame.$wait(selector[, options])
- `selector` <[string]> A selector of an element to wait for
- `options` <[Object]>
- `visibility` <"visible"|"hidden"|"any"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`any`). Defaults to `any`.
- `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
- 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.
Wait for the `selector` to appear in page. If at the moment of calling
the method the `selector` already exists, the method will return
immediately. If the selector doesn't appear after the `timeout` milliseconds of waiting, the function will throw.
This method works across navigations:
```js
const handle = await page.$wait(selector);
await handle.click();
```
This is a shortcut to [frame.waitForSelector(selector[, options])](#framewaitforselectorselector-options).
#### frame.addScriptTag(options) #### frame.addScriptTag(options)
- `options` <[Object]> - `options` <[Object]>
- `url` <[string]> URL of a script to be added. - `url` <[string]> URL of a script to be added.
@ -2243,7 +2201,7 @@ Returns frame's url.
#### frame.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]]) #### frame.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])
- `selectorOrFunctionOrTimeout` <[string]|[number]|[function]> A [selector], predicate or timeout to wait for - `selectorOrFunctionOrTimeout` <[string]|[number]|[function]> A [selector], predicate or timeout to wait for
- `options` <[Object]> Optional waiting parameters - `options` <[Object]> Optional waiting parameters
- `visibility` <"visible"|"hidden"|"any"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`any`). Defaults to `any`. - `waitFor` <"attached"|"detached"|"visible"|"hidden"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`attached`) or not present in dom (`detached`). Defaults to `attached`.
- `polling` <[number]|"raf"|"mutation"> An interval at which the `pageFunction` is executed, defaults to `raf`. If `polling` is a number, then it is treated as an interval in milliseconds at which the function would be executed. If `polling` is a string, then it can be one of the following values: - `polling` <[number]|"raf"|"mutation"> An interval at which the `pageFunction` is executed, defaults to `raf`. If `polling` is a number, then it is treated as an interval in milliseconds at which the function would be executed. If `polling` is a string, then it can be one of the following values:
- `'raf'` - to constantly execute `pageFunction` in `requestAnimationFrame` callback. This is the tightest polling mode which is suitable to observe styling changes. - `'raf'` - to constantly execute `pageFunction` in `requestAnimationFrame` callback. This is the tightest polling mode which is suitable to observe styling changes.
- `'mutation'` - to execute `pageFunction` on every DOM mutation. - `'mutation'` - to execute `pageFunction` on every DOM mutation.
@ -2252,7 +2210,7 @@ Returns frame's url.
- returns: <[Promise]<[JSHandle]>> Promise which resolves to a JSHandle of the success value - returns: <[Promise]<[JSHandle]>> Promise which resolves to a JSHandle of the success value
This method behaves differently with respect to the type of the first parameter: This method behaves differently with respect to the type of the first parameter:
- if `selectorOrFunctionOrTimeout` is a `string`, then the first argument is treated as a [selector] and the method is a shortcut for [frame.waitForSelector](#framewaitforselectorselector-options) - if `selectorOrFunctionOrTimeout` is a `string`, then the first argument is treated as a [selector] and the method is a shortcut for [frame.waitForElement](#framewaitforelementselector-options)
- if `selectorOrFunctionOrTimeout` is a `function`, then the first argument is treated as a predicate to wait for and the method is a shortcut for [frame.waitForFunction()](#framewaitforfunctionpagefunction-options-args). - if `selectorOrFunctionOrTimeout` is a `function`, then the first argument is treated as a predicate to wait for and the method is a shortcut for [frame.waitForFunction()](#framewaitforfunctionpagefunction-options-args).
- if `selectorOrFunctionOrTimeout` is a `number`, then the first argument is treated as a timeout in milliseconds and the method returns a promise which resolves after the timeout - if `selectorOrFunctionOrTimeout` is a `number`, then the first argument is treated as a timeout in milliseconds and the method returns a promise which resolves after the timeout
- otherwise, an exception is thrown - otherwise, an exception is thrown
@ -2273,6 +2231,33 @@ const selector = '.foo';
await page.waitFor(selector => !!document.querySelector(selector), {}, selector); await page.waitFor(selector => !!document.querySelector(selector), {}, selector);
``` ```
#### frame.waitForElement(selector[, options])
- `selector` <[string]> A selector of an element to wait for
- `options` <[Object]>
- `waitFor` <"attached"|"detached"|"visible"|"hidden"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`attached`) or not present in dom (`detached`). Defaults to `attached`.
- `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
- returns: <[Promise]<?[ElementHandle]>> Promise which resolves when element specified by selector satisfies `waitFor` option. Resolves to `null` if waiting for `hidden` or `detached`.
Wait for the `selector` to satisfy `waitFor` option (either appear/disappear from dom, or become visible/hidden). If at the moment of calling the method `selector` already satisfies the condition, the method will return immediately. If the selector doesn't satisfy the condition for the `timeout` milliseconds, the function will throw.
This method works across navigations:
```js
const { webkit } = require('playwright'); // Or 'chromium' or 'firefox'.
(async () => {
const browser = await webkit.launch();
const page = await browser.newPage();
let currentURL;
page.mainFrame()
.waitForElement('img')
.then(() => console.log('First URL with image: ' + currentURL));
for (currentURL of ['https://example.com', 'https://google.com', 'https://bbc.com']) {
await page.goto(currentURL);
}
await browser.close();
})();
```
#### frame.waitForFunction(pageFunction[, options[, ...args]]) #### frame.waitForFunction(pageFunction[, options[, ...args]])
- `pageFunction` <[function]|[string]> Function to be evaluated in browser context - `pageFunction` <[function]|[string]> Function to be evaluated in browser context
- `options` <[Object]> Optional waiting parameters - `options` <[Object]> Optional waiting parameters
@ -2346,36 +2331,6 @@ const [response] = await Promise.all([
**NOTE** Usage of the [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API) to change the URL is considered a navigation. **NOTE** Usage of the [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API) to change the URL is considered a navigation.
#### frame.waitForSelector(selector[, options])
- `selector` <[string]> A selector of an element to wait for
- `options` <[Object]>
- `visibility` <"visible"|"hidden"|"any"> Wait for element to become visible (`visible`), hidden (`hidden`), present in dom (`any`). Defaults to `any`.
- `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
- 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.
Wait for the `selector` to appear in page. If at the moment of calling
the method the `selector` already exists, the method will return
immediately. If the selector doesn't appear after the `timeout` milliseconds of waiting, the function will throw.
This method works across navigations:
```js
const { webkit } = require('playwright'); // Or 'chromium' or 'firefox'.
(async () => {
const browser = await webkit.launch();
const page = await browser.newPage();
let currentURL;
page.mainFrame()
.waitForSelector('img')
.then(() => console.log('First URL with image: ' + currentURL));
for (currentURL of ['https://example.com', 'https://google.com', 'https://bbc.com']) {
await page.goto(currentURL);
}
await browser.close();
})();
```
### class: ElementHandle ### class: ElementHandle
* extends: [JSHandle] * extends: [JSHandle]
@ -3322,7 +3277,7 @@ const { selectors, firefox } = require('playwright'); // Or 'chromium' or 'webk
* extends: [Error] * extends: [Error]
TimeoutError is emitted whenever certain operations are terminated due to timeout, e.g. [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options) or [browserType.launch([options])](#browsertypelaunchoptions). TimeoutError is emitted whenever certain operations are terminated due to timeout, e.g. [page.waitForElement(selector[, options])](#pagewaitforelementselector-options) or [browserType.launch([options])](#browsertypelaunchoptions).
### class: Accessibility ### class: Accessibility
@ -3550,7 +3505,7 @@ Download browser binary if it is missing.
- returns: <[Object]> - returns: <[Object]>
- `TimeoutError` <[function]> A class of [TimeoutError]. - `TimeoutError` <[function]> A class of [TimeoutError].
Playwright methods might throw errors if they are unable to fulfill a request. For example, [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options) Playwright methods might throw errors if they are unable to fulfill a request. For example, [page.waitForElement(selector[, options])](#pagewaitforelementselector-options)
might fail if the selector doesn't match any nodes during the given timeframe. might fail if the selector doesn't match any nodes during the given timeframe.
For certain types of errors Playwright uses specific error classes. For certain types of errors Playwright uses specific error classes.
@ -3560,7 +3515,7 @@ An example of handling a timeout error:
```js ```js
const { webkit } = require('playwright'); // Or 'chromium' or 'firefox'. const { webkit } = require('playwright'); // Or 'chromium' or 'firefox'.
try { try {
await page.waitForSelector('.foo'); await page.waitForElement('.foo');
} catch (e) { } catch (e) {
if (e instanceof webkit.errors.TimeoutError) { if (e instanceof webkit.errors.TimeoutError) {
// Do something if this is a timeout. // Do something if this is a timeout.

View file

@ -82,7 +82,7 @@ export class FrameExecutionContext extends js.ExecutionContext {
return super._createHandle(remoteObject); return super._createHandle(remoteObject);
} }
_injected(): Promise<js.JSHandle> { _injected(): Promise<js.JSHandle<Injected>> {
const selectors = Selectors._instance(); const selectors = Selectors._instance();
if (this._injectedPromise && selectors._generation !== this._injectedGeneration) { if (this._injectedPromise && selectors._generation !== this._injectedGeneration) {
this._injectedPromise.then(handle => handle.dispose()); this._injectedPromise.then(handle => handle.dispose());
@ -456,17 +456,22 @@ export function waitForFunctionTask(selector: string | undefined, pageFunction:
}, await context._injected(), selector, predicateBody, polling, options.timeout || 0, ...args); }, await context._injected(), selector, predicateBody, polling, options.timeout || 0, ...args);
} }
export function waitForSelectorTask(selector: string, visibility: types.Visibility, timeout: number): Task { export function waitForSelectorTask(selector: string, waitFor: 'attached' | 'detached' | 'visible' | 'hidden', timeout: number): Task {
return async (context: FrameExecutionContext) => context.evaluateHandle((injected: Injected, selector: string, visibility: types.Visibility, timeout: number) => { return async (context: FrameExecutionContext) => context.evaluateHandle((injected, selector, waitFor, timeout) => {
const polling = visibility === 'any' ? 'mutation' : 'raf'; const polling = (waitFor === 'attached' || waitFor === 'detached') ? 'mutation' : 'raf';
return injected.poll(polling, selector, timeout, (element: Element | undefined): Element | boolean => { return injected.poll(polling, selector, timeout, (element: Element | undefined): Element | boolean => {
if (!element) switch (waitFor) {
return visibility === 'hidden'; case 'attached':
if (visibility === 'any') return element || false;
return element; case 'detached':
return injected.isVisible(element) === (visibility === 'visible') ? element : false; return !element;
case 'visible':
return element && injected.isVisible(element) ? element : false;
case 'hidden':
return !element || !injected.isVisible(element);
}
}); });
}, await context._injected(), selector, visibility, timeout); }, await context._injected(), selector, waitFor, timeout);
} }
export const setFileInputFunction = async (element: HTMLInputElement, payloads: types.FilePayload[]) => { export const setFileInputFunction = async (element: HTMLInputElement, payloads: types.FilePayload[]) => {

View file

@ -589,9 +589,10 @@ export class Frame {
return handle; return handle;
} }
async waitForSelector(selector: string, options?: types.TimeoutOptions & { visibility?: types.Visibility }): Promise<dom.ElementHandle<Element> | null> { async waitForElement(selector: string, options?: types.WaitForElementOptions): Promise<dom.ElementHandle<Element> | null> {
const { timeout = this._page._timeoutSettings.timeout(), visibility = 'any' } = (options || {}); if (options && (options as any).visibility)
const handle = await this._waitForSelectorInUtilityContext(selector, visibility, timeout); throw new Error('options.visibility is not supported, did you mean options.waitFor?');
const handle = await this._waitForSelectorInUtilityContext(selector, options);
const mainContext = await this._mainContext(); const mainContext = await this._mainContext();
if (handle && handle._context !== mainContext) { if (handle && handle._context !== mainContext) {
const adopted = this._page._delegate.adoptElementHandle(handle, mainContext); const adopted = this._page._delegate.adoptElementHandle(handle, mainContext);
@ -601,10 +602,6 @@ export class Frame {
return handle; return handle;
} }
async $wait(selector: string, options?: types.TimeoutOptions & { visibility?: types.Visibility }): Promise<dom.ElementHandle<Element> | null> {
return this.waitForSelector(selector, options);
}
$eval: types.$Eval = async (selector, pageFunction, ...args) => { $eval: types.$Eval = async (selector, pageFunction, ...args) => {
const context = await this._mainContext(); const context = await this._mainContext();
const elementHandle = await context._$(selector); const elementHandle = await context._$(selector);
@ -875,9 +872,9 @@ export class Frame {
handle.dispose(); handle.dispose();
} }
async waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options: types.WaitForFunctionOptions & { visibility?: types.Visibility } = {}, ...args: any[]): Promise<js.JSHandle | null> { async waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options: types.WaitForFunctionOptions & types.WaitForElementOptions = {}, ...args: any[]): Promise<js.JSHandle | null> {
if (helper.isString(selectorOrFunctionOrTimeout)) if (helper.isString(selectorOrFunctionOrTimeout))
return this.waitForSelector(selectorOrFunctionOrTimeout, options) as any; return this.waitForElement(selectorOrFunctionOrTimeout, options) as any;
if (helper.isNumber(selectorOrFunctionOrTimeout)) if (helper.isNumber(selectorOrFunctionOrTimeout))
return new Promise(fulfill => setTimeout(fulfill, selectorOrFunctionOrTimeout)); return new Promise(fulfill => setTimeout(fulfill, selectorOrFunctionOrTimeout));
if (typeof selectorOrFunctionOrTimeout === 'function') if (typeof selectorOrFunctionOrTimeout === 'function')
@ -891,9 +888,9 @@ export class Frame {
throw new Error('waitFor option should be a boolean, got "' + (typeof waitFor) + '"'); throw new Error('waitFor option should be a boolean, got "' + (typeof waitFor) + '"');
let handle: dom.ElementHandle<Element>; let handle: dom.ElementHandle<Element>;
if (waitFor) { if (waitFor) {
const maybeHandle = await this._waitForSelectorInUtilityContext(selector, 'any', timeout); const maybeHandle = await this._waitForSelectorInUtilityContext(selector, { timeout, waitFor: 'attached' });
if (!maybeHandle) if (!maybeHandle)
throw new Error('No node found for selector: ' + selectorToString(selector, 'any')); throw new Error('No node found for selector: ' + selectorToString(selector, 'attached'));
handle = maybeHandle; handle = maybeHandle;
} else { } else {
const context = await this._context('utility'); const context = await this._context('utility');
@ -904,14 +901,12 @@ export class Frame {
return handle; return handle;
} }
private async _waitForSelectorInUtilityContext(selector: string, waitFor: types.Visibility, timeout: number): Promise<dom.ElementHandle<Element> | null> { private async _waitForSelectorInUtilityContext(selector: string, options?: types.WaitForElementOptions): Promise<dom.ElementHandle<Element> | null> {
let visibility: types.Visibility = 'any'; const { timeout = this._page._timeoutSettings.timeout(), waitFor = 'attached' } = (options || {});
if (waitFor === 'visible' || waitFor === 'hidden' || waitFor === 'any') if (!['attached', 'detached', 'visible', 'hidden'].includes(waitFor))
visibility = waitFor; throw new Error(`Unsupported waitFor option "${waitFor}"`);
else const task = dom.waitForSelectorTask(selector, waitFor, timeout);
throw new Error(`Unsupported visibility option "${waitFor}"`); const result = await this._scheduleRerunnableTask(task, 'utility', timeout, `selector "${selectorToString(selector, waitFor)}"`);
const task = dom.waitForSelectorTask(selector, visibility, timeout);
const result = await this._scheduleRerunnableTask(task, 'utility', timeout, `selector "${selectorToString(selector, visibility)}"`);
if (!result.asElement()) { if (!result.asElement()) {
result.dispose(); result.dispose();
return null; return null;
@ -1095,14 +1090,13 @@ function createTimeoutPromise(timeout: number): Disposable<Promise<TimeoutError>
}; };
} }
function selectorToString(selector: string, visibility: types.Visibility): string { function selectorToString(selector: string, waitFor: 'attached' | 'detached' | 'visible' | 'hidden'): string {
let label; let label;
switch (visibility) { switch (waitFor) {
case 'visible': label = '[visible] '; break; case 'visible': label = '[visible] '; break;
case 'hidden': label = '[hidden] '; break; case 'hidden': label = '[hidden] '; break;
case 'any': case 'attached': label = ''; break;
case undefined: case 'detached': label = '[detached]'; break;
label = ''; break;
} }
return `${label}${selector}`; return `${label}${selector}`;
} }

View file

@ -231,12 +231,8 @@ export class Page extends platform.EventEmitter {
return this.mainFrame().$(selector); return this.mainFrame().$(selector);
} }
async waitForSelector(selector: string, options?: types.TimeoutOptions & { visibility?: types.Visibility }): Promise<dom.ElementHandle<Element> | null> { async waitForElement(selector: string, options?: types.WaitForElementOptions): Promise<dom.ElementHandle<Element> | null> {
return this.mainFrame().waitForSelector(selector, options); return this.mainFrame().waitForElement(selector, options);
}
async $wait(selector: string, options?: types.TimeoutOptions & { visibility?: types.Visibility }): Promise<dom.ElementHandle<Element> | null> {
return this.mainFrame().$wait(selector, options);
} }
evaluateHandle: types.EvaluateHandle = async (pageFunction, ...args) => { evaluateHandle: types.EvaluateHandle = async (pageFunction, ...args) => {
@ -483,7 +479,7 @@ export class Page extends platform.EventEmitter {
return this.mainFrame().uncheck(selector, options); return this.mainFrame().uncheck(selector, options);
} }
async waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options?: types.WaitForFunctionOptions & { visibility?: types.Visibility }, ...args: any[]): Promise<js.JSHandle | null> { async waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options?: types.WaitForFunctionOptions & types.WaitForElementOptions, ...args: any[]): Promise<js.JSHandle | null> {
return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args); return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
} }

View file

@ -41,7 +41,7 @@ export type Quad = [ Point, Point, Point, Point ];
export type TimeoutOptions = { timeout?: number }; export type TimeoutOptions = { timeout?: number };
export type WaitForOptions = TimeoutOptions & { waitFor?: boolean }; export type WaitForOptions = TimeoutOptions & { waitFor?: boolean };
export type Visibility = 'visible' | 'hidden' | 'any'; export type WaitForElementOptions = TimeoutOptions & { waitFor?: 'attached' | 'detached' | 'visible' | 'hidden' };
export type Polling = 'raf' | 'mutation' | number; export type Polling = 'raf' | 'mutation' | number;
export type WaitForFunctionOptions = TimeoutOptions & { polling?: Polling }; export type WaitForFunctionOptions = TimeoutOptions & { polling?: Polling };

View file

@ -85,7 +85,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
document.body.appendChild(frame); document.body.appendChild(frame);
return new Promise(x => frame.onload = x); return new Promise(x => frame.onload = x);
}); });
await page.waitForSelector('iframe[src="https://google.com/"]'); await page.waitForElement('iframe[src="https://google.com/"]');
const urls = page.frames().map(frame => frame.url()).sort(); const urls = page.frames().map(frame => frame.url()).sort();
expect(urls).toEqual([ expect(urls).toEqual([
server.EMPTY_PAGE, server.EMPTY_PAGE,

View file

@ -171,15 +171,15 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
expect(error.message).toContain('Navigation failed because browser has disconnected!'); expect(error.message).toContain('Navigation failed because browser has disconnected!');
await browserServer.close(); await browserServer.close();
}); });
it('should reject waitForSelector when browser closes', async({server}) => { it('should reject waitForElement when browser closes', async({server}) => {
server.setRoute('/empty.html', () => {}); server.setRoute('/empty.html', () => {});
const browserServer = await playwright.launchServer({...defaultBrowserOptions }); const browserServer = await playwright.launchServer({...defaultBrowserOptions });
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() }); const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
const page = await remote.newPage(); const page = await remote.newPage();
const watchdog = page.waitForSelector('div', { timeout: 60000 }).catch(e => e); const watchdog = page.waitForElement('div', { timeout: 60000 }).catch(e => e);
// Make sure the previous waitForSelector has time to make it to the browser before we disconnect. // Make sure the previous waitForElement has time to make it to the browser before we disconnect.
await page.waitForSelector('body'); await page.waitForElement('body');
await remote.close(); await remote.close();
const error = await watchdog; const error = await watchdog;

View file

@ -211,17 +211,6 @@ module.exports.describe = function({testRunner, expect, selectors, FFOX, CHROMIU
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 waitFor visibility', async({page, server}) => {
await page.setContent('<section id="testAttribute">43543</section>');
expect(await page.waitForSelector('css=section', { waitFor: 'visible'})).toBeTruthy();
expect(await page.waitForSelector('css=section', { waitFor: 'any'})).toBeTruthy();
expect(await page.waitForSelector('css=section')).toBeTruthy();
await page.setContent('<section id="testAttribute" style="display: none">43543</section>');
expect(await page.waitForSelector('css=section', { waitFor: 'hidden'})).toBeTruthy();
expect(await page.waitForSelector('css=section', { waitFor: 'any'})).toBeTruthy();
expect(await page.waitForSelector('css=section')).toBeTruthy();
});
}); });
describe('Page.$$', function() { describe('Page.$$', function() {

View file

@ -207,19 +207,19 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
}); });
}); });
describe('Frame.waitForSelector', function() { describe('Frame.waitForElement', function() {
const addElement = tag => document.body.appendChild(document.createElement(tag)); const addElement = tag => document.body.appendChild(document.createElement(tag));
it('should immediately resolve promise if node exists', async({page, server}) => { it('should immediately resolve promise if node exists', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const frame = page.mainFrame(); const frame = page.mainFrame();
await frame.waitForSelector('*'); await frame.waitForElement('*');
await frame.evaluate(addElement, 'div'); await frame.evaluate(addElement, 'div');
await frame.waitForSelector('div'); await frame.waitForElement('div');
}); });
it('should work with removed MutationObserver', async({page, server}) => { it('should work with removed MutationObserver', async({page, server}) => {
await page.evaluate(() => delete window.MutationObserver); await page.evaluate(() => delete window.MutationObserver);
const [handle] = await Promise.all([ const [handle] = await Promise.all([
page.waitForSelector('.zombo'), page.waitForElement('.zombo'),
page.setContent(`<div class='zombo'>anything</div>`), page.setContent(`<div class='zombo'>anything</div>`),
]); ]);
expect(await page.evaluate(x => x.textContent, handle)).toBe('anything'); expect(await page.evaluate(x => x.textContent, handle)).toBe('anything');
@ -227,7 +227,7 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
it('should resolve promise when node is added', async({page, server}) => { it('should resolve promise when node is added', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const frame = page.mainFrame(); const frame = page.mainFrame();
const watchdog = frame.waitForSelector('div'); const watchdog = frame.waitForElement('div');
await frame.evaluate(addElement, 'br'); await frame.evaluate(addElement, 'br');
await frame.evaluate(addElement, 'div'); await frame.evaluate(addElement, 'div');
const eHandle = await watchdog; const eHandle = await watchdog;
@ -236,7 +236,7 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
}); });
it('should work when node is added through innerHTML', async({page, server}) => { it('should work when node is added through innerHTML', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const watchdog = page.waitForSelector('h3 div'); const watchdog = page.waitForElement('h3 div');
await page.evaluate(addElement, 'span'); await page.evaluate(addElement, 'span');
await page.evaluate(() => document.querySelector('span').innerHTML = '<h3><div></div></h3>'); await page.evaluate(() => document.querySelector('span').innerHTML = '<h3><div></div></h3>');
await watchdog; await watchdog;
@ -245,7 +245,7 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const otherFrame = page.frames()[1]; const otherFrame = page.frames()[1];
const watchdog = page.waitForSelector('div'); const watchdog = page.waitForElement('div');
await otherFrame.evaluate(addElement, 'div'); await otherFrame.evaluate(addElement, 'div');
await page.evaluate(addElement, 'div'); await page.evaluate(addElement, 'div');
const eHandle = await watchdog; const eHandle = await watchdog;
@ -256,17 +256,17 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
const frame1 = page.frames()[1]; const frame1 = page.frames()[1];
const frame2 = page.frames()[2]; const frame2 = page.frames()[2];
const waitForSelectorPromise = frame2.waitForSelector('div'); const waitForElementPromise = frame2.waitForElement('div');
await frame1.evaluate(addElement, 'div'); await frame1.evaluate(addElement, 'div');
await frame2.evaluate(addElement, 'div'); await frame2.evaluate(addElement, 'div');
const eHandle = await waitForSelectorPromise; const eHandle = await waitForElementPromise;
expect(await eHandle.ownerFrame()).toBe(frame2); expect(await eHandle.ownerFrame()).toBe(frame2);
}); });
it('should throw when frame is detached', async({page, server}) => { it('should throw when frame is detached', async({page, server}) => {
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1]; const frame = page.frames()[1];
let waitError = null; let waitError = null;
const waitPromise = frame.waitForSelector('.box').catch(e => waitError = e); const waitPromise = frame.waitForElement('.box').catch(e => waitError = e);
await utils.detachFrame(page, 'frame1'); await utils.detachFrame(page, 'frame1');
await waitPromise; await waitPromise;
expect(waitError).toBeTruthy(); expect(waitError).toBeTruthy();
@ -274,74 +274,74 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
}); });
it('should survive cross-process navigation', async({page, server}) => { it('should survive cross-process navigation', async({page, server}) => {
let boxFound = false; let boxFound = false;
const waitForSelector = page.waitForSelector('.box').then(() => boxFound = true); const waitForElement = page.waitForElement('.box').then(() => boxFound = true);
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
expect(boxFound).toBe(false); expect(boxFound).toBe(false);
await page.reload(); await page.reload();
expect(boxFound).toBe(false); expect(boxFound).toBe(false);
await page.goto(server.CROSS_PROCESS_PREFIX + '/grid.html'); await page.goto(server.CROSS_PROCESS_PREFIX + '/grid.html');
await waitForSelector; await waitForElement;
expect(boxFound).toBe(true); expect(boxFound).toBe(true);
}); });
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').then(() => divFound = true); const waitForElement = page.waitForElement('div', { waitFor: 'visible' }).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'));
expect(divFound).toBe(false); expect(divFound).toBe(false);
await page.evaluate(() => document.querySelector('div').style.removeProperty('visibility')); await page.evaluate(() => document.querySelector('div').style.removeProperty('visibility'));
expect(await waitForSelector).toBe(true); expect(await waitForElement).toBe(true);
expect(divFound).toBe(true); expect(divFound).toBe(true);
}); });
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', { visibility: 'visible' }).then(() => divVisible = true); const waitForElement = page.waitForElement('div#inner', { waitFor: 'visible' }).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'));
expect(divVisible).toBe(false); expect(divVisible).toBe(false);
await page.evaluate(() => document.querySelector('div').style.removeProperty('visibility')); await page.evaluate(() => document.querySelector('div').style.removeProperty('visibility'));
expect(await waitForSelector).toBe(true); expect(await waitForElement).toBe(true);
expect(divVisible).toBe(true); expect(divVisible).toBe(true);
}); });
it('hidden should wait for visibility: hidden', async({page, server}) => { it('hidden should wait for 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', { visibility: 'hidden' }).then(() => divHidden = true); const waitForElement = page.waitForElement('div', { waitFor: 'hidden' }).then(() => divHidden = true);
await page.waitForSelector('div'); // do a round trip await page.waitForElement('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'));
expect(await waitForSelector).toBe(true); expect(await waitForElement).toBe(true);
expect(divHidden).toBe(true); expect(divHidden).toBe(true);
}); });
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', { visibility: 'hidden' }).then(() => divHidden = true); const waitForElement = page.waitForElement('div', { waitFor: 'hidden' }).then(() => divHidden = true);
await page.waitForSelector('div'); // do a round trip await page.waitForElement('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'));
expect(await waitForSelector).toBe(true); expect(await waitForElement).toBe(true);
expect(divHidden).toBe(true); expect(divHidden).toBe(true);
}); });
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', { visibility: 'hidden' }).then(() => divRemoved = true); const waitForElement = page.waitForElement('div', { waitFor: 'hidden' }).then(() => divRemoved = true);
await page.waitForSelector('div'); // do a round trip await page.waitForElement('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());
expect(await waitForSelector).toBe(true); expect(await waitForElement).toBe(true);
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', { visibility: 'hidden' }); const handle = await page.waitForElement('non-existing', { waitFor: 'hidden' });
expect(handle).toBe(null); expect(handle).toBe(null);
}); });
it('should respect timeout', async({page, server}) => { it('should respect timeout', async({page, server}) => {
let error = null; let error = null;
await page.waitForSelector('div', { timeout: 10 }).catch(e => error = e); await page.waitForElement('div', { timeout: 10 }).catch(e => error = e);
expect(error).toBeTruthy(); expect(error).toBeTruthy();
expect(error.message).toContain('waiting for selector "div" failed: timeout'); expect(error.message).toContain('waiting for selector "div" failed: timeout');
expect(error).toBeInstanceOf(playwright.errors.TimeoutError); expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
@ -349,52 +349,52 @@ module.exports.describe = 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', { visibility: 'hidden', timeout: 10 }).catch(e => error = e); await page.waitForElement('div', { waitFor: 'hidden', timeout: 10 }).catch(e => error = e);
expect(error).toBeTruthy(); expect(error).toBeTruthy();
expect(error.message).toContain('waiting for selector "[hidden] div" 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}) => {
let divFound = false; let divFound = false;
const waitForSelector = page.waitForSelector('.zombo').then(() => divFound = true); const waitForElement = page.waitForElement('.zombo').then(() => divFound = true);
await page.setContent(`<div class='notZombo'></div>`); await page.setContent(`<div class='notZombo'></div>`);
expect(divFound).toBe(false); expect(divFound).toBe(false);
await page.evaluate(() => document.querySelector('div').className = 'zombo'); await page.evaluate(() => document.querySelector('div').className = 'zombo');
expect(await waitForSelector).toBe(true); expect(await waitForElement).toBe(true);
}); });
it('should return the element handle', async({page, server}) => { it('should return the element handle', async({page, server}) => {
const waitForSelector = page.waitForSelector('.zombo'); const waitForElement = page.waitForElement('.zombo');
await page.setContent(`<div class='zombo'>anything</div>`); await page.setContent(`<div class='zombo'>anything</div>`);
expect(await page.evaluate(x => x.textContent, await waitForSelector)).toBe('anything'); expect(await page.evaluate(x => x.textContent, await waitForElement)).toBe('anything');
}); });
it('should have correct stack trace for timeout', async({page, server}) => { it('should have correct stack trace for timeout', async({page, server}) => {
let error; let error;
await page.waitForSelector('.zombo', { timeout: 10 }).catch(e => error = e); await page.waitForElement('.zombo', { timeout: 10 }).catch(e => error = e);
expect(error.stack).toContain('waittask.spec.js'); expect(error.stack).toContain('waittask.spec.js');
}); });
it('should throw for unknown waitFor option', async({page, server}) => { it('should throw for unknown waitFor option', async({page, server}) => {
await page.setContent('<section>test</section>'); await page.setContent('<section>test</section>');
const error = await page.waitForSelector('section', { visibility: 'foo' }).catch(e => e); const error = await page.waitForElement('section', { waitFor: 'foo' }).catch(e => e);
expect(error.message).toContain('Unsupported visibility option'); expect(error.message).toContain('Unsupported waitFor option');
}); });
it('should throw for numeric waitFor option', async({page, server}) => { it('should throw for visibility option', async({page, server}) => {
await page.setContent('<section>test</section>'); await page.setContent('<section>test</section>');
const error = await page.waitForSelector('section', { visibility: 123 }).catch(e => e); const error = await page.waitForElement('section', { visibility: 'hidden' }).catch(e => e);
expect(error.message).toContain('Unsupported visibility option'); expect(error.message).toBe('options.visibility is not supported, did you mean options.waitFor?');
}); });
it('should throw for true waitFor option', async({page, server}) => { it('should throw for true waitFor option', async({page, server}) => {
await page.setContent('<section>test</section>'); await page.setContent('<section>test</section>');
const error = await page.waitForSelector('section', { visibility: true }).catch(e => e); const error = await page.waitForElement('section', { waitFor: true }).catch(e => e);
expect(error.message).toContain('Unsupported visibility option'); expect(error.message).toContain('Unsupported waitFor option');
}); });
it('should throw for false waitFor option', async({page, server}) => { it('should throw for false waitFor option', async({page, server}) => {
await page.setContent('<section>test</section>'); await page.setContent('<section>test</section>');
const error = await page.waitForSelector('section', { visibility: false }).catch(e => e); const error = await page.waitForElement('section', { waitFor: false }).catch(e => e);
expect(error.message).toContain('Unsupported visibility option'); expect(error.message).toContain('Unsupported waitFor option');
}); });
it('should support >> selector syntax', async({page, server}) => { it('should support >> selector syntax', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const frame = page.mainFrame(); const frame = page.mainFrame();
const watchdog = frame.waitForSelector('css=div >> css=span'); const watchdog = frame.waitForElement('css=div >> css=span');
await frame.evaluate(addElement, 'br'); await frame.evaluate(addElement, 'br');
await frame.evaluate(addElement, 'div'); await frame.evaluate(addElement, 'div');
await frame.evaluate(() => document.querySelector('div').appendChild(document.createElement('span'))); await frame.evaluate(() => document.querySelector('div').appendChild(document.createElement('span')));
@ -402,24 +402,34 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
const tagName = await eHandle.getProperty('tagName').then(e => e.jsonValue()); const tagName = await eHandle.getProperty('tagName').then(e => e.jsonValue());
expect(tagName).toBe('SPAN'); expect(tagName).toBe('SPAN');
}); });
it('$wait alias should work', async({page, server}) => { it('should wait for detached if already detached', async({page, server}) => {
await page.setContent('<section>test</section>'); await page.setContent('<section id="testAttribute">43543</section>');
const handle = await page.$wait('section'); expect(await page.waitForElement('css=div', { waitFor: 'detached'})).toBe(null);
expect(await handle.evaluate(e => e.textContent)).toBe('test'); });
it('should wait for detached', async({page, server}) => {
await page.setContent('<section id="testAttribute"><div>43543</div></section>');
let done = false;
const waitFor = page.waitForElement('css=div', { waitFor: 'detached'}).then(() => done = true);
expect(done).toBe(false);
await page.waitForElement('css=section');
expect(done).toBe(false);
await page.$eval('div', div => div.remove());
expect(await waitFor).toBe(true);
expect(done).toBe(true);
}); });
}); });
describe('Frame.waitForSelector xpath', function() { describe('Frame.waitForElement xpath', function() {
const addElement = tag => document.body.appendChild(document.createElement(tag)); const addElement = tag => document.body.appendChild(document.createElement(tag));
it('should support some fancy xpath', async({page, server}) => { it('should support some fancy xpath', async({page, server}) => {
await page.setContent(`<p>red herring</p><p>hello world </p>`); await page.setContent(`<p>red herring</p><p>hello world </p>`);
const waitForXPath = page.waitForSelector('//p[normalize-space(.)="hello world"]'); const waitForXPath = page.waitForElement('//p[normalize-space(.)="hello world"]');
expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('hello world '); expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('hello world ');
}); });
it('should respect timeout', async({page}) => { it('should respect timeout', async({page}) => {
let error = null; let error = null;
await page.waitForSelector('//div', { timeout: 10 }).catch(e => error = e); await page.waitForElement('//div', { timeout: 10 }).catch(e => error = e);
expect(error).toBeTruthy(); expect(error).toBeTruthy();
expect(error.message).toContain('waiting for selector "//div" failed: timeout'); expect(error.message).toContain('waiting for selector "//div" failed: timeout');
expect(error).toBeInstanceOf(playwright.errors.TimeoutError); expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
@ -429,7 +439,7 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
const frame1 = page.frames()[1]; const frame1 = page.frames()[1];
const frame2 = page.frames()[2]; const frame2 = page.frames()[2];
const waitForXPathPromise = frame2.waitForSelector('//div'); const waitForXPathPromise = frame2.waitForElement('//div');
await frame1.evaluate(addElement, 'div'); await frame1.evaluate(addElement, 'div');
await frame2.evaluate(addElement, 'div'); await frame2.evaluate(addElement, 'div');
const eHandle = await waitForXPathPromise; const eHandle = await waitForXPathPromise;
@ -439,20 +449,20 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1]; const frame = page.frames()[1];
let waitError = null; let waitError = null;
const waitPromise = frame.waitForSelector('//*[@class="box"]').catch(e => waitError = e); const waitPromise = frame.waitForElement('//*[@class="box"]').catch(e => waitError = e);
await utils.detachFrame(page, 'frame1'); await utils.detachFrame(page, 'frame1');
await waitPromise; await waitPromise;
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('should return the element handle', async({page, server}) => { it('should return the element handle', async({page, server}) => {
const waitForXPath = page.waitForSelector('//*[@class="zombo"]'); const waitForXPath = page.waitForElement('//*[@class="zombo"]');
await page.setContent(`<div class='zombo'>anything</div>`); await page.setContent(`<div class='zombo'>anything</div>`);
expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('anything'); expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('anything');
}); });
it('should allow you to select an element with single slash', async({page, server}) => { it('should allow you to select an element with single slash', async({page, server}) => {
await page.setContent(`<div>some text</div>`); await page.setContent(`<div>some text</div>`);
const waitForXPath = page.waitForSelector('//html/body/div'); const waitForXPath = page.waitForElement('//html/body/div');
expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('some text'); expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('some text');
}); });
}); });