api(waitForSelector): bring it back (#1272)

This commit is contained in:
Pavel Feldman 2020-03-06 15:02:42 -08:00 committed by GitHub
parent 29f243056c
commit 3fa000f5f4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 120 additions and 121 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.waitForElement(selector[, options])](#pagewaitforelementselector-options) Playwright methods might throw errors if they are unable to fulfill a request. For example, [page.waitForSelector(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.waitForElement('.foo'); await page.waitForSelector('.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.
@ -683,13 +683,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 -->
@ -1664,7 +1664,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.waitForElement](#pagewaitforelementselector-options) - if `selectorOrFunctionOrTimeout` is a `string`, then the first argument is treated as a [selector] and the method is a shortcut for [page.waitForSelector](#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
@ -1687,34 +1687,6 @@ await page.waitFor(selector => !!document.querySelector(selector), {}, selector)
Shortcut for [page.mainFrame().waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#framewaitforelementorfunctionortimeout-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)`.
- `optionsOrPredicate` <[Function]|[Object]> Either a predicate that receives an event or an options object. - `optionsOrPredicate` <[Function]|[Object]> Either a predicate that receives an event or an options object.
@ -1833,6 +1805,34 @@ 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]>
- `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
.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])](#framewaitforelementselector-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.
@ -1915,10 +1915,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)
@ -2375,7 +2375,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.waitForElement](#framewaitforelementselector-options) - if `selectorOrFunctionOrTimeout` is a `string`, then the first argument is treated as a [selector] and the method is a shortcut for [frame.waitForSelector](#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
@ -2396,33 +2396,6 @@ 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
@ -2497,6 +2470,32 @@ 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]>
- `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()
.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]
@ -3515,7 +3514,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.waitForElement(selector[, options])](#pagewaitforelementselector-options) or [browserType.launch([options])](#browsertypelaunchoptions). TimeoutError is emitted whenever certain operations are terminated due to timeout, e.g. [page.waitForSelector(selector[, options])](#pagewaitforelementselector-options) or [browserType.launch([options])](#browsertypelaunchoptions).
### class: Accessibility ### class: Accessibility
@ -3743,7 +3742,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.waitForElement(selector[, options])](#pagewaitforelementselector-options) Playwright methods might throw errors if they are unable to fulfill a request. For example, [page.waitForSelector(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.
@ -3753,7 +3752,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.waitForElement('.foo'); await page.waitForSelector('.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

@ -580,7 +580,7 @@ export class Frame {
return handle; return handle;
} }
async waitForElement(selector: string, options?: types.WaitForElementOptions): Promise<dom.ElementHandle<Element> | null> { async waitForSelector(selector: string, options?: types.WaitForElementOptions): Promise<dom.ElementHandle<Element> | null> {
if (options && (options as any).visibility) if (options && (options as any).visibility)
throw new Error('options.visibility is not supported, did you mean options.waitFor?'); throw new Error('options.visibility is not supported, did you mean options.waitFor?');
const handle = await this._waitForSelectorInUtilityContext(selector, options); const handle = await this._waitForSelectorInUtilityContext(selector, options);
@ -870,7 +870,7 @@ export class Frame {
async waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options: types.WaitForFunctionOptions & types.WaitForElementOptions = {}, ...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.waitForElement(selectorOrFunctionOrTimeout, options) as any; return this.waitForSelector(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')

View file

@ -230,8 +230,8 @@ export class Page extends platform.EventEmitter {
return this.mainFrame().$(selector); return this.mainFrame().$(selector);
} }
async waitForElement(selector: string, options?: types.WaitForElementOptions): Promise<dom.ElementHandle<Element> | null> { async waitForSelector(selector: string, options?: types.WaitForElementOptions): Promise<dom.ElementHandle<Element> | null> {
return this.mainFrame().waitForElement(selector, options); return this.mainFrame().waitForSelector(selector, options);
} }
evaluateHandle: types.EvaluateHandle = async (pageFunction, ...args) => { evaluateHandle: types.EvaluateHandle = async (pageFunction, ...args) => {

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.waitForElement('iframe[src="https://google.com/"]'); await page.waitForSelector('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 waitForElement when browser closes', async({server}) => { it('should reject waitForSelector 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.waitForElement('div', { timeout: 60000 }).catch(e => e); const watchdog = page.waitForSelector('div', { timeout: 60000 }).catch(e => e);
// Make sure the previous waitForElement has time to make it to the browser before we disconnect. // Make sure the previous waitForSelector has time to make it to the browser before we disconnect.
await page.waitForElement('body'); await page.waitForSelector('body');
await remote.close(); await remote.close();
const error = await watchdog; const error = await watchdog;

View file

@ -207,19 +207,19 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
}); });
}); });
describe('Frame.waitForElement', function() { describe('Frame.waitForSelector', 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.waitForElement('*'); await frame.waitForSelector('*');
await frame.evaluate(addElement, 'div'); await frame.evaluate(addElement, 'div');
await frame.waitForElement('div'); await frame.waitForSelector('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.waitForElement('.zombo'), page.waitForSelector('.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.waitForElement('div'); const watchdog = frame.waitForSelector('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.waitForElement('h3 div'); const watchdog = page.waitForSelector('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.waitForElement('div'); const watchdog = page.waitForSelector('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 waitForElementPromise = frame2.waitForElement('div'); const waitForSelectorPromise = frame2.waitForSelector('div');
await frame1.evaluate(addElement, 'div'); await frame1.evaluate(addElement, 'div');
await frame2.evaluate(addElement, 'div'); await frame2.evaluate(addElement, 'div');
const eHandle = await waitForElementPromise; const eHandle = await waitForSelectorPromise;
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.waitForElement('.box').catch(e => waitError = e); const waitPromise = frame.waitForSelector('.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 waitForElement = page.waitForElement('.box').then(() => boxFound = true); const waitForSelector = page.waitForSelector('.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 waitForElement; await waitForSelector;
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 waitForElement = page.waitForElement('div', { waitFor: 'visible' }).then(() => divFound = true); const waitForSelector = page.waitForSelector('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 waitForElement).toBe(true); expect(await waitForSelector).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 waitForElement = page.waitForElement('div#inner', { waitFor: 'visible' }).then(() => divVisible = true); const waitForSelector = page.waitForSelector('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 waitForElement).toBe(true); expect(await waitForSelector).toBe(true);
expect(divVisible).toBe(true); expect(divVisible).toBe(true);
}); });
it('hidden should wait for 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 waitForElement = page.waitForElement('div', { waitFor: 'hidden' }).then(() => divHidden = true); const waitForSelector = page.waitForSelector('div', { waitFor: 'hidden' }).then(() => divHidden = true);
await page.waitForElement('div'); // do a round trip await page.waitForSelector('div'); // do a round trip
expect(divHidden).toBe(false); expect(divHidden).toBe(false);
await page.evaluate(() => document.querySelector('div').style.setProperty('visibility', 'hidden')); await page.evaluate(() => document.querySelector('div').style.setProperty('visibility', 'hidden'));
expect(await waitForElement).toBe(true); expect(await waitForSelector).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 waitForElement = page.waitForElement('div', { waitFor: 'hidden' }).then(() => divHidden = true); const waitForSelector = page.waitForSelector('div', { waitFor: 'hidden' }).then(() => divHidden = true);
await page.waitForElement('div'); // do a round trip await page.waitForSelector('div'); // do a round trip
expect(divHidden).toBe(false); expect(divHidden).toBe(false);
await page.evaluate(() => document.querySelector('div').style.setProperty('display', 'none')); await page.evaluate(() => document.querySelector('div').style.setProperty('display', 'none'));
expect(await waitForElement).toBe(true); expect(await waitForSelector).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 waitForElement = page.waitForElement('div', { waitFor: 'hidden' }).then(() => divRemoved = true); const waitForSelector = page.waitForSelector('div', { waitFor: 'hidden' }).then(() => divRemoved = true);
await page.waitForElement('div'); // do a round trip await page.waitForSelector('div'); // do a round trip
expect(divRemoved).toBe(false); expect(divRemoved).toBe(false);
await page.evaluate(() => document.querySelector('div').remove()); await page.evaluate(() => document.querySelector('div').remove());
expect(await waitForElement).toBe(true); expect(await waitForSelector).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.waitForElement('non-existing', { waitFor: 'hidden' }); const handle = await page.waitForSelector('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.waitForElement('div', { timeout: 10 }).catch(e => error = e); await page.waitForSelector('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.waitForElement('div', { waitFor: 'hidden', timeout: 10 }).catch(e => error = e); await page.waitForSelector('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 waitForElement = page.waitForElement('.zombo').then(() => divFound = true); const waitForSelector = page.waitForSelector('.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 waitForElement).toBe(true); expect(await waitForSelector).toBe(true);
}); });
it('should return the element handle', async({page, server}) => { it('should return the element handle', async({page, server}) => {
const waitForElement = page.waitForElement('.zombo'); const waitForSelector = page.waitForSelector('.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 waitForElement)).toBe('anything'); expect(await page.evaluate(x => x.textContent, await waitForSelector)).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.waitForElement('.zombo', { timeout: 10 }).catch(e => error = e); await page.waitForSelector('.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.waitForElement('section', { waitFor: 'foo' }).catch(e => e); const error = await page.waitForSelector('section', { waitFor: 'foo' }).catch(e => e);
expect(error.message).toContain('Unsupported waitFor option'); expect(error.message).toContain('Unsupported waitFor option');
}); });
it('should throw for visibility 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.waitForElement('section', { visibility: 'hidden' }).catch(e => e); const error = await page.waitForSelector('section', { visibility: 'hidden' }).catch(e => e);
expect(error.message).toBe('options.visibility is not supported, did you mean options.waitFor?'); 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.waitForElement('section', { waitFor: true }).catch(e => e); const error = await page.waitForSelector('section', { waitFor: true }).catch(e => e);
expect(error.message).toContain('Unsupported waitFor 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.waitForElement('section', { waitFor: false }).catch(e => e); const error = await page.waitForSelector('section', { waitFor: false }).catch(e => e);
expect(error.message).toContain('Unsupported waitFor 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.waitForElement('css=div >> css=span'); const watchdog = frame.waitForSelector('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')));
@ -404,14 +404,14 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
}); });
it('should wait for detached if already detached', async({page, server}) => { it('should wait for detached if already detached', async({page, server}) => {
await page.setContent('<section id="testAttribute">43543</section>'); await page.setContent('<section id="testAttribute">43543</section>');
expect(await page.waitForElement('css=div', { waitFor: 'detached'})).toBe(null); expect(await page.waitForSelector('css=div', { waitFor: 'detached'})).toBe(null);
}); });
it('should wait for detached', async({page, server}) => { it('should wait for detached', async({page, server}) => {
await page.setContent('<section id="testAttribute"><div>43543</div></section>'); await page.setContent('<section id="testAttribute"><div>43543</div></section>');
let done = false; let done = false;
const waitFor = page.waitForElement('css=div', { waitFor: 'detached'}).then(() => done = true); const waitFor = page.waitForSelector('css=div', { waitFor: 'detached'}).then(() => done = true);
expect(done).toBe(false); expect(done).toBe(false);
await page.waitForElement('css=section'); await page.waitForSelector('css=section');
expect(done).toBe(false); expect(done).toBe(false);
await page.$eval('div', div => div.remove()); await page.$eval('div', div => div.remove());
expect(await waitFor).toBe(true); expect(await waitFor).toBe(true);
@ -419,17 +419,17 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
}); });
}); });
describe('Frame.waitForElement xpath', function() { describe('Frame.waitForSelector 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.waitForElement('//p[normalize-space(.)="hello world"]'); const waitForXPath = page.waitForSelector('//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.waitForElement('//div', { timeout: 10 }).catch(e => error = e); await page.waitForSelector('//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);
@ -439,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.waitForElement('//div'); const waitForXPathPromise = frame2.waitForSelector('//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;
@ -449,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.waitForElement('//*[@class="box"]').catch(e => waitError = e); const waitPromise = frame.waitForSelector('//*[@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.waitForElement('//*[@class="zombo"]'); const waitForXPath = page.waitForSelector('//*[@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.waitForElement('//html/body/div'); const waitForXPath = page.waitForSelector('//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');
}); });
}); });