diff --git a/src/chromium/DOMWorld.ts b/src/chromium/DOMWorld.ts index a01276903e..f071390ef7 100644 --- a/src/chromium/DOMWorld.ts +++ b/src/chromium/DOMWorld.ts @@ -24,7 +24,7 @@ import { helper } from '../helper'; import { ElementHandle, JSHandle } from './JSHandle'; import { LifecycleWatcher } from './LifecycleWatcher'; import { TimeoutSettings } from '../TimeoutSettings'; -import { WaitTask, WaitTaskParams } from '../waitTask'; +import { WaitTask, WaitTaskParams, waitForSelectorOrXPath } from '../waitTask'; const readFileAsync = helper.promisify(fs.readFile); @@ -284,12 +284,24 @@ export class DOMWorld { } } - waitForSelector(selector: string, options: { visible?: boolean; hidden?: boolean; timeout?: number; } | undefined): Promise { - return this._waitForSelectorOrXPath(selector, false, options); + async waitForSelector(selector: string, options: { visible?: boolean; hidden?: boolean; timeout?: number; } | undefined): Promise { + const params = waitForSelectorOrXPath(selector, false /* isXPath */, { timeout: this._timeoutSettings.timeout(), ...options }); + const handle = await this._scheduleWaitTask(params); + if (!handle.asElement()) { + await handle.dispose(); + return null; + } + return handle.asElement(); } - waitForXPath(xpath: string, options: { visible?: boolean; hidden?: boolean; timeout?: number; } | undefined): Promise { - return this._waitForSelectorOrXPath(xpath, true, options); + async waitForXPath(xpath: string, options: { visible?: boolean, hidden?: boolean, timeout?: number } = {}): Promise { + const params = waitForSelectorOrXPath(xpath, true /* isXPath */, { timeout: this._timeoutSettings.timeout(), ...options }); + const handle = await this._scheduleWaitTask(params); + if (!handle.asElement()) { + await handle.dispose(); + return null; + } + return handle.asElement(); } waitForFunction(pageFunction: Function | string, options: { polling?: string | number; timeout?: number; } = {}, ...args): Promise { @@ -318,51 +330,5 @@ export class DOMWorld { async title(): Promise { return this.evaluate(() => document.title); } - - async _waitForSelectorOrXPath( - selectorOrXPath: string, - isXPath: boolean, - options: { visible?: boolean; hidden?: boolean; timeout?: number; } = {}): Promise { - const { - visible: waitForVisible = false, - hidden: waitForHidden = false, - timeout = this._timeoutSettings.timeout(), - } = options; - const polling = waitForVisible || waitForHidden ? 'raf' : 'mutation'; - const title = `${isXPath ? 'XPath' : 'selector'} "${selectorOrXPath}"${waitForHidden ? ' to be hidden' : ''}`; - const params: WaitTaskParams = { - predicateBody: predicate, - title, - polling, - timeout, - args: [selectorOrXPath, isXPath, waitForVisible, waitForHidden] - }; - const handle = await this._scheduleWaitTask(params); - if (!handle.asElement()) { - await handle.dispose(); - return null; - } - return handle.asElement(); - - function predicate(selectorOrXPath: string, isXPath: boolean, waitForVisible: boolean, waitForHidden: boolean): (Node | boolean) | null { - const node = isXPath - ? document.evaluate(selectorOrXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue - : document.querySelector(selectorOrXPath); - if (!node) - return waitForHidden; - if (!waitForVisible && !waitForHidden) - return node; - const element = (node.nodeType === Node.TEXT_NODE ? node.parentElement : node) as Element; - const style = window.getComputedStyle(element); - const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox(); - const success = (waitForVisible === isVisible || waitForHidden === !isVisible); - return success ? node : null; - - function hasVisibleBoundingBox(): boolean { - const rect = element.getBoundingClientRect(); - return !!(rect.top || rect.bottom || rect.width || rect.height); - } - } - } } diff --git a/src/firefox/DOMWorld.ts b/src/firefox/DOMWorld.ts index 0418ee7f6f..fdd120e295 100644 --- a/src/firefox/DOMWorld.ts +++ b/src/firefox/DOMWorld.ts @@ -20,7 +20,7 @@ import * as util from 'util'; import * as types from '../types'; import {ElementHandle, JSHandle} from './JSHandle'; import { ExecutionContext } from './ExecutionContext'; -import { WaitTaskParams, WaitTask } from '../waitTask'; +import { WaitTaskParams, WaitTask, waitForSelectorOrXPath } from '../waitTask'; const readFileAsync = util.promisify(fs.readFile); @@ -238,12 +238,24 @@ export class DOMWorld { } } - waitForSelector(selector: string, options: { timeout?: number; visible?: boolean; hidden?: boolean; } | undefined): Promise { - return this._waitForSelectorOrXPath(selector, false, options); + async waitForSelector(selector: string, options: { visible?: boolean; hidden?: boolean; timeout?: number; } | undefined): Promise { + const params = waitForSelectorOrXPath(selector, false /* isXPath */, { timeout: this._timeoutSettings.timeout(), ...options }); + const handle = await this._scheduleWaitTask(params); + if (!handle.asElement()) { + await handle.dispose(); + return null; + } + return handle.asElement(); } - waitForXPath(xpath: string, options: { timeout?: number; visible?: boolean; hidden?: boolean; } | undefined): Promise { - return this._waitForSelectorOrXPath(xpath, true, options); + async waitForXPath(xpath: string, options: { visible?: boolean, hidden?: boolean, timeout?: number } = {}): Promise { + const params = waitForSelectorOrXPath(xpath, true /* isXPath */, { timeout: this._timeoutSettings.timeout(), ...options }); + const handle = await this._scheduleWaitTask(params); + if (!handle.asElement()) { + await handle.dispose(); + return null; + } + return handle.asElement(); } waitForFunction(pageFunction: Function | string, options: { polling?: string | number; timeout?: number; } | undefined = {}, ...args): Promise { @@ -265,50 +277,6 @@ export class DOMWorld { return this.evaluate(() => document.title); } - async _waitForSelectorOrXPath(selectorOrXPath: string, isXPath: boolean, options: { timeout?: number; visible?: boolean; hidden?: boolean; } | undefined = {}): Promise { - const { - visible: waitForVisible = false, - hidden: waitForHidden = false, - timeout = this._timeoutSettings.timeout(), - } = options; - const polling = waitForVisible || waitForHidden ? 'raf' : 'mutation'; - const title = `${isXPath ? 'XPath' : 'selector'} "${selectorOrXPath}"${waitForHidden ? ' to be hidden' : ''}`; - const params: WaitTaskParams = { - predicateBody: predicate, - title, - polling, - timeout, - args: [selectorOrXPath, isXPath, waitForVisible, waitForHidden] - }; - const handle = await this._scheduleWaitTask(params); - if (!handle.asElement()) { - await handle.dispose(); - return null; - } - return handle.asElement(); - - function predicate(selectorOrXPath: string, isXPath: boolean, waitForVisible: boolean, waitForHidden: boolean): (Node | boolean) | null { - const node = isXPath - ? document.evaluate(selectorOrXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue - : document.querySelector(selectorOrXPath); - if (!node) - return waitForHidden; - if (!waitForVisible && !waitForHidden) - return node; - const element: Element = node.nodeType === Node.TEXT_NODE ? node.parentElement : node as Element; - - const style = window.getComputedStyle(element); - const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox(); - const success = (waitForVisible === isVisible || waitForHidden === !isVisible); - return success ? node : null; - - function hasVisibleBoundingBox(): boolean { - const rect = element.getBoundingClientRect(); - return !!(rect.top || rect.bottom || rect.width || rect.height); - } - } - } - private _scheduleWaitTask(params: WaitTaskParams): Promise { const task = new WaitTask(params, () => this._waitTasks.delete(task)); this._waitTasks.add(task); diff --git a/src/waitTask.ts b/src/waitTask.ts index 6064cd4c56..dc40687bc5 100644 --- a/src/waitTask.ts +++ b/src/waitTask.ts @@ -104,6 +104,43 @@ export class WaitTask { } } +export function waitForSelectorOrXPath( + selectorOrXPath: string, + isXPath: boolean, + options: { visible?: boolean, hidden?: boolean, timeout: number }): WaitTaskParams { + const { visible: waitForVisible = false, hidden: waitForHidden = false, timeout } = options; + const polling = waitForVisible || waitForHidden ? 'raf' : 'mutation'; + const title = `${isXPath ? 'XPath' : 'selector'} "${selectorOrXPath}"${waitForHidden ? ' to be hidden' : ''}`; + const params: WaitTaskParams = { + predicateBody: predicate, + title, + polling, + timeout, + args: [selectorOrXPath, isXPath, waitForVisible, waitForHidden] + }; + return params; + + function predicate(selectorOrXPath: string, isXPath: boolean, waitForVisible: boolean, waitForHidden: boolean): (Node | boolean) | null { + const node = isXPath + ? document.evaluate(selectorOrXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue + : document.querySelector(selectorOrXPath); + if (!node) + return waitForHidden; + if (!waitForVisible && !waitForHidden) + return node; + const element = (node.nodeType === Node.TEXT_NODE ? node.parentElement : node) as Element; + const style = window.getComputedStyle(element); + const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox(); + const success = (waitForVisible === isVisible || waitForHidden === !isVisible); + return success ? node : null; + + function hasVisibleBoundingBox(): boolean { + const rect = element.getBoundingClientRect(); + return !!(rect.top || rect.bottom || rect.width || rect.height); + } + } +} + async function waitForPredicatePageFunction(predicateBody: string, polling: string | number, timeout: number, ...args): Promise { const predicate = new Function('...args', predicateBody); let timedOut = false; diff --git a/src/webkit/FrameManager.ts b/src/webkit/FrameManager.ts index 2781ae866f..9691d253af 100644 --- a/src/webkit/FrameManager.ts +++ b/src/webkit/FrameManager.ts @@ -29,7 +29,7 @@ import { NetworkManager, NetworkManagerEvents, Request, Response } from './Netwo import { Page } from './Page'; import { Protocol } from './protocol'; import { MultiClickOptions, ClickOptions, SelectOption } from '../input'; -import { WaitTask, WaitTaskParams } from '../waitTask'; +import { WaitTask, WaitTaskParams, waitForSelectorOrXPath } from '../waitTask'; const readFileAsync = helper.promisify(fs.readFile); @@ -291,12 +291,24 @@ export class Frame { return watchDog.waitForNavigation(); } - waitForSelector(selector: string, options: { visible?: boolean; hidden?: boolean; timeout?: number; } | undefined): Promise { - return this._waitForSelectorOrXPath(selector, false, options); + async waitForSelector(selector: string, options: { visible?: boolean; hidden?: boolean; timeout?: number; } | undefined): Promise { + const params = waitForSelectorOrXPath(selector, false /* isXPath */, { timeout: this._frameManager._timeoutSettings.timeout(), ...options }); + const handle = await this._scheduleWaitTask(params); + if (!handle.asElement()) { + await handle.dispose(); + return null; + } + return handle.asElement(); } - waitForXPath(xpath: string, options: { visible?: boolean; hidden?: boolean; timeout?: number; } | undefined): Promise { - return this._waitForSelectorOrXPath(xpath, true, options); + async waitForXPath(xpath: string, options: { visible?: boolean, hidden?: boolean, timeout?: number } = {}): Promise { + const params = waitForSelectorOrXPath(xpath, true /* isXPath */, { timeout: this._frameManager._timeoutSettings.timeout(), ...options }); + const handle = await this._scheduleWaitTask(params); + if (!handle.asElement()) { + await handle.dispose(); + return null; + } + return handle.asElement(); } waitForFunction(pageFunction: Function | string, options: { polling?: string | number; timeout?: number; } | undefined = {}, ...args): Promise { @@ -609,54 +621,6 @@ export class Frame { return this.evaluate(() => document.title); } - async _waitForSelectorOrXPath(selectorOrXPath: string, isXPath: boolean, options: { visible?: boolean; hidden?: boolean; timeout?: number; } | undefined = {}): Promise { - const { - visible: waitForVisible = false, - hidden: waitForHidden = false, - timeout = this._frameManager._timeoutSettings.timeout(), - } = options; - const polling = waitForVisible || waitForHidden ? 'raf' : 'mutation'; - const title = `${isXPath ? 'XPath' : 'selector'} "${selectorOrXPath}"${waitForHidden ? ' to be hidden' : ''}`; - const params: WaitTaskParams = { - predicateBody: predicate, - title, - polling, - timeout, - args: [selectorOrXPath, isXPath, waitForVisible, waitForHidden] - }; - const handle = await this._scheduleWaitTask(params); - if (!handle.asElement()) { - await handle.dispose(); - return null; - } - return handle.asElement(); - - /** - */ - function predicate(selectorOrXPath: string, isXPath: boolean, waitForVisible: boolean, waitForHidden: boolean): (Node | boolean) | null { - const node = isXPath - ? document.evaluate(selectorOrXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue - : document.querySelector(selectorOrXPath); - if (!node) - return waitForHidden; - if (!waitForVisible && !waitForHidden) - return node; - const element: Element = (node.nodeType === Node.TEXT_NODE ? node.parentElement : node) as Element; - - const style = window.getComputedStyle(element); - const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox(); - const success = (waitForVisible === isVisible || waitForHidden === !isVisible); - return success ? node : null; - - /** - */ - function hasVisibleBoundingBox(): boolean { - const rect = element.getBoundingClientRect(); - return !!(rect.top || rect.bottom || rect.width || rect.height); - } - } - } - _navigated(framePayload: Protocol.Page.Frame) { this._name = framePayload.name; // TODO(lushnikov): remove this once requestInterception has loaderId exposed.