From 4f5f1f6d77a11029e2614c7f668d17e1176f0392 Mon Sep 17 00:00:00 2001 From: JoelEinbinder Date: Tue, 25 Jul 2017 11:37:46 -0700 Subject: [PATCH] Move missing methods from page onto frame. (#125) Closes #113 --- docs/api.md | 23 +++++++++++- lib/FrameManager.js | 86 +++++++++++++++++++++++++++++++++++++++++++-- lib/Page.js | 34 +++--------------- 3 files changed, 109 insertions(+), 34 deletions(-) diff --git a/docs/api.md b/docs/api.md index 22a94365eb8..71a463b9f2d 100644 --- a/docs/api.md +++ b/docs/api.md @@ -79,13 +79,17 @@ * [class: Frame](#class-frame) + [frame.$(selector, pageFunction, ...args)](#frameselector-pagefunction-args) + [frame.$$(selector, pageFunction, ...args)](#frameselector-pagefunction-args) + + [frame.addScriptTag(url)](#frameaddscripttagurl) + [frame.childFrames()](#framechildframes) + [frame.click(selector[, options])](#frameclickselector-options) + [frame.evaluate(pageFunction, ...args)](#frameevaluatepagefunction-args) + + [frame.focus(selector)](#framefocusselector) + [frame.hover(selector)](#framehoverselector) + + [frame.injectFile(filePath)](#frameinjectfilefilepath) + [frame.isDetached()](#frameisdetached) + [frame.name()](#framename) + [frame.parentFrame()](#frameparentframe) + + [frame.title()](#frametitle) + [frame.url()](#frameurl) + [frame.waitFor(selectorOrTimeout[, options])](#framewaitforselectorortimeout-options) + [frame.waitForSelector(selector[, options])](#framewaitforselectorselector-options) @@ -347,7 +351,7 @@ Shortcut for [page.mainFrame().$$(selector, pageFunction, ...args)](#pageselecto - `url` <[string]> Url of a script to be added - returns: <[Promise]> Promise which resolves as the script gets added and loads. -Adds a `` tag to the page with the desired url. Alternatively, javascript could be injected to the page via `page.injectFile` method. +Adds a `` tag to the page with the desired url. Alternatively, javascript could be injected to the page via [`page.injectFile`](#pageinjectfilefilepath) method. #### page.click(selector[, options]) - `selector` <[string]> A query selector to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked. @@ -791,6 +795,12 @@ browser.newPage().then(async page => { - `...args` <...[string]> Arguments to pass to `pageFunction` - returns: <[Promise]<[Array]<[Object]>>> Promise which resolves to array of function return values. +#### frame.addScriptTag(url) +- `url` <[string]> Url of a script to be added +- returns: <[Promise]> Promise which resolves as the script gets added and loads. + +Adds a `` tag to the frame with the desired url. Alternatively, javascript could be injected to the frame via [`frame.injectFile`](#frameinjectfilefilepath) method. + #### frame.childFrames() - returns: <[Array]<[Frame]>> @@ -820,10 +830,18 @@ browser.newPage().then(async page => }); ``` +#### frame.focus(selector) +- `selector` <[string]> A query selector of element to focus. If there are multiple elements satisfying the selector, the first will be focused. +- returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully focused. Promise gets rejected if there's no element matching `selector`. + #### frame.hover(selector) - `selector` <[string]> A query selector to search for element to hover. If there are multiple elements satisfying the selector, the first will be hovered. - returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully hovered. Promise gets rejected if there's no element matching `selector`. +#### frame.injectFile(filePath) +- `filePath` <[string]> Path to the javascript file to be injected into frame. +- returns: <[Promise]> Promise which resolves when file gets successfully evaluated in frame. + #### frame.isDetached() - returns: <[boolean]> @@ -837,6 +855,9 @@ Returns frame's name as specified in the tag. #### frame.parentFrame() - returns: <[Frame]> Returns parent frame, if any. Detached frames and main frames return `null`. +#### frame.title() +- returns: <[Promise]<[string]>> Returns page's title. + #### frame.url() - returns: <[string]> diff --git a/lib/FrameManager.js b/lib/FrameManager.js index 74180e326ed..5564b8e31b5 100644 --- a/lib/FrameManager.js +++ b/lib/FrameManager.js @@ -14,6 +14,7 @@ * limitations under the License. */ +let fs = require('fs'); let EventEmitter = require('events'); let helper = require('./helper'); @@ -157,21 +158,43 @@ class FrameManager extends EventEmitter { /** * @param {!Frame} frame - * @param {string} expression - * @return {!Promise<(!Object|undefined)>} + * @return {number} */ - async _evaluateOnFrame(frame, expression) { + _contextIdForFrame(frame) { let contextId = undefined; if (frame !== this._mainFrame) { contextId = this._frameIdToExecutionContextId.get(frame._id); console.assert(contextId, 'Frame does not have default context to evaluate in!'); } + return contextId; + } + + /** + * @param {!Frame} frame + * @param {string} expression + * @return {!Promise<(!Object|undefined)>} + */ + async _evaluateOnFrame(frame, expression) { + let contextId = this._contextIdForFrame(frame); expression = `Promise.resolve(${expression})`; let { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.evaluate', { expression, contextId, returnByValue: false, awaitPromise: true }); if (exceptionDetails) throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails)); return await helper.serializeRemoteObject(this._client, remoteObject); } + + /** + * @param {!Frame} frame + * @param {string} expression + * @return {!Promise<(!Object|undefined)>} + */ + async _rawEvaluateOnFrame(frame, expression) { + let contextId = this._contextIdForFrame(frame); + let { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.evaluate', { expression, contextId, returnByValue: false }); + if (exceptionDetails) + throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails)); + return await helper.serializeRemoteObject(this._client, remoteObject); + } } /** @enum {string} */ @@ -251,6 +274,40 @@ class Frame { return this._detached; } + /** + * @param {string} filePath + * @return {!Promise} + */ + async injectFile(filePath) { + let contents = await new Promise((resolve, reject) => { + fs.readFile(filePath, 'utf8', (err, data) => { + if (err) return reject(err); + resolve(data); + }); + }); + contents += `//# sourceURL=` + filePath.replace(/\n/g,''); + return this._frameManager._rawEvaluateOnFrame(this, contents); + } + + /** + * @param {string} url + * @return {!Promise} + */ + async addScriptTag(url) { + return this.evaluate(addScriptTag, url); + + /** + * @param {string} url + */ + function addScriptTag(url) { + let script = document.createElement('script'); + script.src = url; + let promise = new Promise(x => script.onload = x); + document.head.appendChild(script); + return promise; + } + } + /** * @param {(string|number)} selectorOrTimeout * @param {!Object=} options @@ -313,6 +370,13 @@ class Frame { return this._frameManager._evaluateOnFrame(this, expression); } + /** + * @return {!Promise} + */ + async title() { + return this.evaluate(() => document.title); + } + /** * @param {string} selector * @return {!Promise} @@ -346,6 +410,22 @@ class Frame { await this.evaluate(() => new Promise(f => requestAnimationFrame(f))); } + /** + * @param {string} selector + * @return {!Promise} + */ + async focus(selector) { + let success = await this.evaluate(selector => { + let node = document.querySelector(selector); + if (!node) + return false; + node.focus(); + return true; + }, selector); + if (!success) + throw new Error('No node found for selector: ' + selector); + } + /** * @param {?Object} framePayload */ diff --git a/lib/Page.js b/lib/Page.js index e34e5183519..27e36404785 100644 --- a/lib/Page.js +++ b/lib/Page.js @@ -119,18 +119,7 @@ class Page extends EventEmitter { * @return {!Promise} */ async addScriptTag(url) { - return this.evaluate(addScriptTag, url); - - /** - * @param {string} url - */ - function addScriptTag(url) { - let script = document.createElement('script'); - script.src = url; - let promise = new Promise(x => script.onload = x); - document.head.appendChild(script); - return promise; - } + return this.mainFrame().addScriptTag(url); } /** @@ -138,14 +127,7 @@ class Page extends EventEmitter { * @return {!Promise} */ async injectFile(filePath) { - let contents = await new Promise((resolve, reject) => { - fs.readFile(filePath, 'utf8', (err, data) => { - if (err) return reject(err); - resolve(data); - }); - }); - contents += `//# sourceURL=` + filePath.replace(/\n/g,''); - return this._client.send('Runtime.evaluate', { expression: contents, returnByValue: true }); + return this.mainFrame().injectFile(filePath); } /** @@ -498,7 +480,7 @@ class Page extends EventEmitter { * @return {!Promise} */ async title() { - return this.evaluate(() => document.title); + return this.mainFrame().title(); } /** @@ -552,15 +534,7 @@ class Page extends EventEmitter { * @return {!Promise} */ async focus(selector) { - let success = await this.evaluate(selector => { - let node = document.querySelector(selector); - if (!node) - return false; - node.focus(); - return true; - }, selector); - if (!success) - throw new Error('No node found for selector: ' + selector); + return this.mainFrame().focus(selector); } /**