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]>
- `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.
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:
```js
try {
await page.waitForSelector('.foo');
await page.waitForElement('.foo');
} catch (e) {
if (e instanceof playwright.errors.TimeoutError) {
// Do something if this is a timeout.
@ -620,7 +620,6 @@ page.removeListener('request', logRequest);
- [page.$$(selector)](#pageselector-1)
- [page.$$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args)
- [page.$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args-1)
- [page.$wait(selector[, options])](#pagewaitselector-options)
- [page.accessibility](#pageaccessibility)
- [page.addInitScript(script[, ...args])](#pageaddinitscriptscript-args)
- [page.addScriptTag(options)](#pageaddscripttagoptions)
@ -667,13 +666,13 @@ page.removeListener('request', logRequest);
- [page.url()](#pageurl)
- [page.viewportSize()](#pageviewportsize)
- [page.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#pagewaitforselectororfunctionortimeout-options-args)
- [page.waitForElement(selector[, options])](#pagewaitforelementselector-options)
- [page.waitForEvent(event[, optionsOrPredicate])](#pagewaitforeventevent-optionsorpredicate)
- [page.waitForFunction(pageFunction[, options[, ...args]])](#pagewaitforfunctionpagefunction-options-args)
- [page.waitForLoadState([options])](#pagewaitforloadstateoptions)
- [page.waitForNavigation([options])](#pagewaitfornavigationoptions)
- [page.waitForRequest(urlOrPredicate[, options])](#pagewaitforrequesturlorpredicate-options)
- [page.waitForResponse(urlOrPredicate[, options])](#pagewaitforresponseurlorpredicate-options)
- [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options)
- [page.workers()](#pageworkers)
<!-- 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).
#### 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
- returns: <[Accessibility]>
@ -1585,7 +1565,7 @@ This is a shortcut for [page.mainFrame().url()](#frameurl)
#### page.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])
- `selectorOrFunctionOrTimeout` <[string]|[number]|[function]> A [selector], predicate or timeout to wait for
- `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:
- `'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.
@ -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
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 `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
@ -1615,7 +1595,35 @@ const selector = '.foo';
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])
- `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();
```
#### 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()
- 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.
@ -1819,7 +1797,6 @@ An example of getting text from an iframe element:
- [frame.$$(selector)](#frameselector-1)
- [frame.$$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args)
- [frame.$eval(selector, pageFunction[, ...args])](#frameevalselector-pagefunction-args-1)
- [frame.$wait(selector[, options])](#framewaitselector-options)
- [frame.addScriptTag(options)](#frameaddscripttagoptions)
- [frame.addStyleTag(options)](#frameaddstyletagoptions)
- [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.url()](#frameurl)
- [frame.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#framewaitforselectororfunctionortimeout-options-args)
- [frame.waitForElement(selector[, options])](#framewaitforelementselector-options)
- [frame.waitForFunction(pageFunction[, options[, ...args]])](#framewaitforfunctionpagefunction-options-args)
- [frame.waitForLoadState([options])](#framewaitforloadstateoptions)
- [frame.waitForNavigation([options])](#framewaitfornavigationoptions)
- [frame.waitForSelector(selector[, options])](#framewaitforselectorselector-options)
<!-- GEN:stop -->
#### 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);
```
#### 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)
- `options` <[Object]>
- `url` <[string]> URL of a script to be added.
@ -2243,7 +2201,7 @@ Returns frame's url.
#### frame.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])
- `selectorOrFunctionOrTimeout` <[string]|[number]|[function]> A [selector], predicate or timeout to wait for
- `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:
- `'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.
@ -2252,7 +2210,7 @@ Returns frame's url.
- 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:
- 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 `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
@ -2273,6 +2231,33 @@ const selector = '.foo';
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]])
- `pageFunction` <[function]|[string]> Function to be evaluated in browser context
- `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.
#### 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
* extends: [JSHandle]
@ -3322,7 +3277,7 @@ const { selectors, firefox } = require('playwright'); // Or 'chromium' or 'webk
* 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
@ -3550,7 +3505,7 @@ Download browser binary if it is missing.
- returns: <[Object]>
- `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.
For certain types of errors Playwright uses specific error classes.
@ -3560,7 +3515,7 @@ An example of handling a timeout error:
```js
const { webkit } = require('playwright'); // Or 'chromium' or 'firefox'.
try {
await page.waitForSelector('.foo');
await page.waitForElement('.foo');
} catch (e) {
if (e instanceof webkit.errors.TimeoutError) {
// Do something if this is a timeout.

View File

@ -82,7 +82,7 @@ export class FrameExecutionContext extends js.ExecutionContext {
return super._createHandle(remoteObject);
}
_injected(): Promise<js.JSHandle> {
_injected(): Promise<js.JSHandle<Injected>> {
const selectors = Selectors._instance();
if (this._injectedPromise && selectors._generation !== this._injectedGeneration) {
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);
}
export function waitForSelectorTask(selector: string, visibility: types.Visibility, timeout: number): Task {
return async (context: FrameExecutionContext) => context.evaluateHandle((injected: Injected, selector: string, visibility: types.Visibility, timeout: number) => {
const polling = visibility === 'any' ? 'mutation' : 'raf';
export function waitForSelectorTask(selector: string, waitFor: 'attached' | 'detached' | 'visible' | 'hidden', timeout: number): Task {
return async (context: FrameExecutionContext) => context.evaluateHandle((injected, selector, waitFor, timeout) => {
const polling = (waitFor === 'attached' || waitFor === 'detached') ? 'mutation' : 'raf';
return injected.poll(polling, selector, timeout, (element: Element | undefined): Element | boolean => {
if (!element)
return visibility === 'hidden';
if (visibility === 'any')
return element;
return injected.isVisible(element) === (visibility === 'visible') ? element : false;
switch (waitFor) {
case 'attached':
return element || false;
case 'detached':
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[]) => {

View File

@ -589,9 +589,10 @@ export class Frame {
return handle;
}
async waitForSelector(selector: string, options?: types.TimeoutOptions & { visibility?: types.Visibility }): Promise<dom.ElementHandle<Element> | null> {
const { timeout = this._page._timeoutSettings.timeout(), visibility = 'any' } = (options || {});
const handle = await this._waitForSelectorInUtilityContext(selector, visibility, timeout);
async waitForElement(selector: string, options?: types.WaitForElementOptions): Promise<dom.ElementHandle<Element> | null> {
if (options && (options as any).visibility)
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();
if (handle && handle._context !== mainContext) {
const adopted = this._page._delegate.adoptElementHandle(handle, mainContext);
@ -601,10 +602,6 @@ export class Frame {
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) => {
const context = await this._mainContext();
const elementHandle = await context._$(selector);
@ -875,9 +872,9 @@ export class Frame {
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))
return this.waitForSelector(selectorOrFunctionOrTimeout, options) as any;
return this.waitForElement(selectorOrFunctionOrTimeout, options) as any;
if (helper.isNumber(selectorOrFunctionOrTimeout))
return new Promise(fulfill => setTimeout(fulfill, selectorOrFunctionOrTimeout));
if (typeof selectorOrFunctionOrTimeout === 'function')
@ -891,9 +888,9 @@ export class Frame {
throw new Error('waitFor option should be a boolean, got "' + (typeof waitFor) + '"');
let handle: dom.ElementHandle<Element>;
if (waitFor) {
const maybeHandle = await this._waitForSelectorInUtilityContext(selector, 'any', timeout);
const maybeHandle = await this._waitForSelectorInUtilityContext(selector, { timeout, waitFor: 'attached' });
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;
} else {
const context = await this._context('utility');
@ -904,14 +901,12 @@ export class Frame {
return handle;
}
private async _waitForSelectorInUtilityContext(selector: string, waitFor: types.Visibility, timeout: number): Promise<dom.ElementHandle<Element> | null> {
let visibility: types.Visibility = 'any';
if (waitFor === 'visible' || waitFor === 'hidden' || waitFor === 'any')
visibility = waitFor;
else
throw new Error(`Unsupported visibility option "${waitFor}"`);
const task = dom.waitForSelectorTask(selector, visibility, timeout);
const result = await this._scheduleRerunnableTask(task, 'utility', timeout, `selector "${selectorToString(selector, visibility)}"`);
private async _waitForSelectorInUtilityContext(selector: string, options?: types.WaitForElementOptions): Promise<dom.ElementHandle<Element> | null> {
const { timeout = this._page._timeoutSettings.timeout(), waitFor = 'attached' } = (options || {});
if (!['attached', 'detached', 'visible', 'hidden'].includes(waitFor))
throw new Error(`Unsupported waitFor option "${waitFor}"`);
const task = dom.waitForSelectorTask(selector, waitFor, timeout);
const result = await this._scheduleRerunnableTask(task, 'utility', timeout, `selector "${selectorToString(selector, waitFor)}"`);
if (!result.asElement()) {
result.dispose();
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;
switch (visibility) {
switch (waitFor) {
case 'visible': label = '[visible] '; break;
case 'hidden': label = '[hidden] '; break;
case 'any':
case undefined:
label = ''; break;
case 'attached': label = ''; break;
case 'detached': label = '[detached]'; break;
}
return `${label}${selector}`;
}

View File

@ -231,12 +231,8 @@ export class Page extends platform.EventEmitter {
return this.mainFrame().$(selector);
}
async waitForSelector(selector: string, options?: types.TimeoutOptions & { visibility?: types.Visibility }): Promise<dom.ElementHandle<Element> | null> {
return this.mainFrame().waitForSelector(selector, options);
}
async $wait(selector: string, options?: types.TimeoutOptions & { visibility?: types.Visibility }): Promise<dom.ElementHandle<Element> | null> {
return this.mainFrame().$wait(selector, options);
async waitForElement(selector: string, options?: types.WaitForElementOptions): Promise<dom.ElementHandle<Element> | null> {
return this.mainFrame().waitForElement(selector, options);
}
evaluateHandle: types.EvaluateHandle = async (pageFunction, ...args) => {
@ -483,7 +479,7 @@ export class Page extends platform.EventEmitter {
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);
}

View File

@ -41,7 +41,7 @@ export type Quad = [ Point, Point, Point, Point ];
export type TimeoutOptions = { timeout?: number };
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 WaitForFunctionOptions = TimeoutOptions & { polling?: Polling };

View File

@ -85,7 +85,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
document.body.appendChild(frame);
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();
expect(urls).toEqual([
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!');
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', () => {});
const browserServer = await playwright.launchServer({...defaultBrowserOptions });
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
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.
await page.waitForSelector('body');
// Make sure the previous waitForElement has time to make it to the browser before we disconnect.
await page.waitForElement('body');
await remote.close();
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');
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() {

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));
it('should immediately resolve promise if node exists', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const frame = page.mainFrame();
await frame.waitForSelector('*');
await frame.waitForElement('*');
await frame.evaluate(addElement, 'div');
await frame.waitForSelector('div');
await frame.waitForElement('div');
});
it('should work with removed MutationObserver', async({page, server}) => {
await page.evaluate(() => delete window.MutationObserver);
const [handle] = await Promise.all([
page.waitForSelector('.zombo'),
page.waitForElement('.zombo'),
page.setContent(`<div class='zombo'>anything</div>`),
]);
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}) => {
await page.goto(server.EMPTY_PAGE);
const frame = page.mainFrame();
const watchdog = frame.waitForSelector('div');
const watchdog = frame.waitForElement('div');
await frame.evaluate(addElement, 'br');
await frame.evaluate(addElement, 'div');
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}) => {
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(() => document.querySelector('span').innerHTML = '<h3><div></div></h3>');
await watchdog;
@ -245,7 +245,7 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const otherFrame = page.frames()[1];
const watchdog = page.waitForSelector('div');
const watchdog = page.waitForElement('div');
await otherFrame.evaluate(addElement, 'div');
await page.evaluate(addElement, 'div');
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);
const frame1 = page.frames()[1];
const frame2 = page.frames()[2];
const waitForSelectorPromise = frame2.waitForSelector('div');
const waitForElementPromise = frame2.waitForElement('div');
await frame1.evaluate(addElement, 'div');
await frame2.evaluate(addElement, 'div');
const eHandle = await waitForSelectorPromise;
const eHandle = await waitForElementPromise;
expect(await eHandle.ownerFrame()).toBe(frame2);
});
it('should throw when frame is detached', async({page, server}) => {
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.frames()[1];
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 waitPromise;
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}) => {
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);
expect(boxFound).toBe(false);
await page.reload();
expect(boxFound).toBe(false);
await page.goto(server.CROSS_PROCESS_PREFIX + '/grid.html');
await waitForSelector;
await waitForElement;
expect(boxFound).toBe(true);
});
it('should wait for visible', async({page, server}) => {
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>`);
expect(divFound).toBe(false);
await page.evaluate(() => document.querySelector('div').style.removeProperty('display'));
expect(divFound).toBe(false);
await page.evaluate(() => document.querySelector('div').style.removeProperty('visibility'));
expect(await waitForSelector).toBe(true);
expect(await waitForElement).toBe(true);
expect(divFound).toBe(true);
});
it('should wait for visible recursively', async({page, server}) => {
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>`);
expect(divVisible).toBe(false);
await page.evaluate(() => document.querySelector('div').style.removeProperty('display'));
expect(divVisible).toBe(false);
await page.evaluate(() => document.querySelector('div').style.removeProperty('visibility'));
expect(await waitForSelector).toBe(true);
expect(await waitForElement).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;
await page.setContent(`<div style='display: block;'></div>`);
const waitForSelector = page.waitForSelector('div', { visibility: 'hidden' }).then(() => divHidden = true);
await page.waitForSelector('div'); // do a round trip
const waitForElement = page.waitForElement('div', { waitFor: 'hidden' }).then(() => divHidden = true);
await page.waitForElement('div'); // do a round trip
expect(divHidden).toBe(false);
await page.evaluate(() => document.querySelector('div').style.setProperty('visibility', 'hidden'));
expect(await waitForSelector).toBe(true);
expect(await waitForElement).toBe(true);
expect(divHidden).toBe(true);
});
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', { visibility: 'hidden' }).then(() => divHidden = true);
await page.waitForSelector('div'); // do a round trip
const waitForElement = page.waitForElement('div', { waitFor: 'hidden' }).then(() => divHidden = true);
await page.waitForElement('div'); // do a round trip
expect(divHidden).toBe(false);
await page.evaluate(() => document.querySelector('div').style.setProperty('display', 'none'));
expect(await waitForSelector).toBe(true);
expect(await waitForElement).toBe(true);
expect(divHidden).toBe(true);
});
it('hidden should wait for removal', async({page, server}) => {
await page.setContent(`<div></div>`);
let divRemoved = false;
const waitForSelector = page.waitForSelector('div', { visibility: 'hidden' }).then(() => divRemoved = true);
await page.waitForSelector('div'); // do a round trip
const waitForElement = page.waitForElement('div', { waitFor: 'hidden' }).then(() => divRemoved = true);
await page.waitForElement('div'); // do a round trip
expect(divRemoved).toBe(false);
await page.evaluate(() => document.querySelector('div').remove());
expect(await waitForSelector).toBe(true);
expect(await waitForElement).toBe(true);
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', { visibility: 'hidden' });
const handle = await page.waitForElement('non-existing', { waitFor: 'hidden' });
expect(handle).toBe(null);
});
it('should respect timeout', async({page, server}) => {
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.message).toContain('waiting for selector "div" failed: timeout');
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}) => {
await page.setContent(`<div></div>`);
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.message).toContain('waiting for selector "[hidden] div" failed: timeout');
});
it('should respond to node attribute mutation', async({page, server}) => {
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>`);
expect(divFound).toBe(false);
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}) => {
const waitForSelector = page.waitForSelector('.zombo');
const waitForElement = page.waitForElement('.zombo');
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}) => {
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');
});
it('should throw for unknown waitFor option', async({page, server}) => {
await page.setContent('<section>test</section>');
const error = await page.waitForSelector('section', { visibility: 'foo' }).catch(e => e);
expect(error.message).toContain('Unsupported visibility option');
const error = await page.waitForElement('section', { waitFor: 'foo' }).catch(e => e);
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>');
const error = await page.waitForSelector('section', { visibility: 123 }).catch(e => e);
expect(error.message).toContain('Unsupported visibility option');
const error = await page.waitForElement('section', { visibility: 'hidden' }).catch(e => e);
expect(error.message).toBe('options.visibility is not supported, did you mean options.waitFor?');
});
it('should throw for true waitFor option', async({page, server}) => {
await page.setContent('<section>test</section>');
const error = await page.waitForSelector('section', { visibility: true }).catch(e => e);
expect(error.message).toContain('Unsupported visibility option');
const error = await page.waitForElement('section', { waitFor: true }).catch(e => e);
expect(error.message).toContain('Unsupported waitFor option');
});
it('should throw for false waitFor option', async({page, server}) => {
await page.setContent('<section>test</section>');
const error = await page.waitForSelector('section', { visibility: false }).catch(e => e);
expect(error.message).toContain('Unsupported visibility option');
const error = await page.waitForElement('section', { waitFor: false }).catch(e => e);
expect(error.message).toContain('Unsupported waitFor option');
});
it('should support >> selector syntax', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
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, 'div');
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());
expect(tagName).toBe('SPAN');
});
it('$wait alias should work', async({page, server}) => {
await page.setContent('<section>test</section>');
const handle = await page.$wait('section');
expect(await handle.evaluate(e => e.textContent)).toBe('test');
it('should wait for detached if already detached', async({page, server}) => {
await page.setContent('<section id="testAttribute">43543</section>');
expect(await page.waitForElement('css=div', { waitFor: 'detached'})).toBe(null);
});
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));
it('should support some fancy xpath', async({page, server}) => {
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 ');
});
it('should respect timeout', async({page}) => {
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.message).toContain('waiting for selector "//div" failed: timeout');
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);
const frame1 = page.frames()[1];
const frame2 = page.frames()[2];
const waitForXPathPromise = frame2.waitForSelector('//div');
const waitForXPathPromise = frame2.waitForElement('//div');
await frame1.evaluate(addElement, 'div');
await frame2.evaluate(addElement, 'div');
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);
const frame = page.frames()[1];
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 waitPromise;
expect(waitError).toBeTruthy();
expect(waitError.message).toContain('waitForFunction failed: frame got detached.');
});
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>`);
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}) => {
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');
});
});