Skip to content

Commit

Permalink
fix(page): teach waitForSelector to return null (#3846)
Browse files Browse the repository at this point in the history
`page.waitForSelector` should return `null` if waiting for `hidden:
true` and there's no matching node in DOM.

Before this patch, `page.waitForSelector` would return some JSHandle
pointing to boolean value.
  • Loading branch information
aslushnikov committed Jan 28, 2019
1 parent 7446550 commit 2061dd4
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 15 deletions.
8 changes: 4 additions & 4 deletions docs/api.md
Expand Up @@ -1926,7 +1926,7 @@ return finalResponse.ok();
- `visible` <[boolean]> wait for element to be present in DOM and to be visible, i.e. to not have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`.
- `hidden` <[boolean]> wait for element to not be found in the DOM or to be hidden, i.e. have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`.
- `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout.
- returns: <[Promise]<[ElementHandle]>> Promise which resolves when element specified by selector string is added to DOM.
- returns: <[Promise]<?[ElementHandle]>> Promise which resolves when element specified by selector string is added to DOM. Resolves to `null` if waiting for `hidden: true` and selector is not found in DOM.

Wait for the `selector` to appear in page. If at the moment of calling
the method the `selector` already exists, the method will return
Expand Down Expand Up @@ -1955,7 +1955,7 @@ Shortcut for [page.mainFrame().waitForSelector(selector[, options])](#framewaitf
- `visible` <[boolean]> wait for element to be present in DOM and to be visible, i.e. to not have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`.
- `hidden` <[boolean]> wait for element to not be found in the DOM or to be hidden, i.e. have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`.
- `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout.
- returns: <[Promise]<[ElementHandle]>> Promise which resolves when element specified by xpath string is added to DOM.
- returns: <[Promise]<?[ElementHandle]>> Promise which resolves when element specified by xpath string is added to DOM. Resolves to `null` if waiting for `hidden: true` and xpath is not found in DOM.

Wait for the `xpath` to appear in page. If at the moment of calling
the method the `xpath` already exists, the method will return
Expand Down Expand Up @@ -2725,7 +2725,7 @@ const [response] = await Promise.all([
- `visible` <[boolean]> wait for element to be present in DOM and to be visible, i.e. to not have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`.
- `hidden` <[boolean]> wait for element to not be found in the DOM or to be hidden, i.e. have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`.
- `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout.
- returns: <[Promise]<[ElementHandle]>> Promise which resolves when element specified by selector string is added to DOM.
- returns: <[Promise]<?[ElementHandle]>> Promise which resolves when element specified by selector string is added to DOM. Resolves to `null` if waiting for `hidden: true` and selector is not found in DOM.

Wait for the `selector` to appear in page. If at the moment of calling
the method the `selector` already exists, the method will return
Expand Down Expand Up @@ -2753,7 +2753,7 @@ puppeteer.launch().then(async browser => {
- `visible` <[boolean]> wait for element to be present in DOM and to be visible, i.e. to not have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`.
- `hidden` <[boolean]> wait for element to not be found in the DOM or to be hidden, i.e. have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`.
- `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout.
- returns: <[Promise]<[ElementHandle]>> Promise which resolves when element specified by xpath string is added to DOM.
- returns: <[Promise]<?[ElementHandle]>> Promise which resolves when element specified by xpath string is added to DOM. Resolves to `null` if waiting for `hidden: true` and xpath is not found in DOM.

Wait for the `xpath` to appear in page. If at the moment of calling
the method the `xpath` already exists, the method will return
Expand Down
16 changes: 11 additions & 5 deletions lib/DOMWorld.js
Expand Up @@ -429,7 +429,7 @@ class DOMWorld {
/**
* @param {string} selector
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
* @return {!Promise<!Puppeteer.ElementHandle>}
* @return {!Promise<?Puppeteer.ElementHandle>}
*/
waitForSelector(selector, options) {
return this._waitForSelectorOrXPath(selector, false, options);
Expand All @@ -438,7 +438,7 @@ class DOMWorld {
/**
* @param {string} xpath
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
* @return {!Promise<!Puppeteer.ElementHandle>}
* @return {!Promise<?Puppeteer.ElementHandle>}
*/
waitForXPath(xpath, options) {
return this._waitForSelectorOrXPath(xpath, true, options);
Expand Down Expand Up @@ -468,17 +468,23 @@ class DOMWorld {
* @param {string} selectorOrXPath
* @param {boolean} isXPath
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
* @return {!Promise<!Puppeteer.ElementHandle>}
* @return {!Promise<?Puppeteer.ElementHandle>}
*/
_waitForSelectorOrXPath(selectorOrXPath, isXPath, options = {}) {
async _waitForSelectorOrXPath(selectorOrXPath, isXPath, options = {}) {
const {
visible: waitForVisible = false,
hidden: waitForHidden = false,
timeout = 30000,
} = options;
const polling = waitForVisible || waitForHidden ? 'raf' : 'mutation';
const title = `${isXPath ? 'XPath' : 'selector'} "${selectorOrXPath}"${waitForHidden ? ' to be hidden' : ''}`;
return new WaitTask(this, predicate, title, polling, timeout, selectorOrXPath, isXPath, waitForVisible, waitForHidden).promise;
const waitTask = new WaitTask(this, predicate, title, polling, timeout, selectorOrXPath, isXPath, waitForVisible, waitForHidden);
const handle = await waitTask.promise;
if (!handle.asElement()) {
await handle.dispose();
return null;
}
return handle.asElement();

/**
* @param {string} selectorOrXPath
Expand Down
6 changes: 3 additions & 3 deletions lib/FrameManager.js
Expand Up @@ -585,7 +585,7 @@ class Frame {
* @param {(string|number|Function)} selectorOrFunctionOrTimeout
* @param {!Object=} options
* @param {!Array<*>} args
* @return {!Promise<!Puppeteer.JSHandle>}
* @return {!Promise<?Puppeteer.JSHandle>}
*/
waitFor(selectorOrFunctionOrTimeout, options = {}, ...args) {
const xPathPattern = '//';
Expand All @@ -606,7 +606,7 @@ class Frame {
/**
* @param {string} selector
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
* @return {!Promise<!Puppeteer.ElementHandle>}
* @return {!Promise<?Puppeteer.ElementHandle>}
*/
waitForSelector(selector, options) {
return this._mainWorld.waitForSelector(selector, options);
Expand All @@ -615,7 +615,7 @@ class Frame {
/**
* @param {string} xpath
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
* @return {!Promise<!Puppeteer.ElementHandle>}
* @return {!Promise<?Puppeteer.ElementHandle>}
*/
waitForXPath(xpath, options) {
return this._mainWorld.waitForXPath(xpath, options);
Expand Down
4 changes: 2 additions & 2 deletions lib/Page.js
Expand Up @@ -1049,7 +1049,7 @@ class Page extends EventEmitter {
/**
* @param {string} selector
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
* @return {!Promise<!Puppeteer.ElementHandle>}
* @return {!Promise<?Puppeteer.ElementHandle>}
*/
waitForSelector(selector, options = {}) {
return this.mainFrame().waitForSelector(selector, options);
Expand All @@ -1058,7 +1058,7 @@ class Page extends EventEmitter {
/**
* @param {string} xpath
* @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
* @return {!Promise<!Puppeteer.ElementHandle>}
* @return {!Promise<?Puppeteer.ElementHandle>}
*/
waitForXPath(xpath, options = {}) {
return this.mainFrame().waitForXPath(xpath, options);
Expand Down
13 changes: 12 additions & 1 deletion test/waittask.spec.js
Expand Up @@ -17,6 +17,13 @@
const utils = require('./utils');
const {TimeoutError} = utils.requireRoot('Errors');

let asyncawait = true;
try {
new Function('async function foo() {await 1}');
} catch (e) {
asyncawait = false;
}

module.exports.addTests = function({testRunner, expect, product}) {
const {describe, xdescribe, fdescribe} = testRunner;
const {it, fit, xit} = testRunner;
Expand Down Expand Up @@ -322,6 +329,10 @@ module.exports.addTests = function({testRunner, expect, product}) {
expect(await waitForSelector).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', { hidden: true });
expect(handle).toBe(null);
});
it('should respect timeout', async({page, server}) => {
let error = null;
await page.waitForSelector('div', {timeout: 10}).catch(e => error = e);
Expand Down Expand Up @@ -350,7 +361,7 @@ module.exports.addTests = function({testRunner, expect, product}) {
await page.setContent(`<div class='zombo'>anything</div>`);
expect(await page.evaluate(x => x.textContent, await waitForSelector)).toBe('anything');
});
it('should have correct stack trace for timeout', async({page, server}) => {
(asyncawait ? it : xit)('should have correct stack trace for timeout', async({page, server}) => {
let error;
await page.waitForSelector('.zombo', {timeout: 10}).catch(e => error = e);
expect(error.stack).toContain('waittask.spec.js');
Expand Down

0 comments on commit 2061dd4

Please sign in to comment.