From 660b65780f51d86ad615b7871bdf125b505624e6 Mon Sep 17 00:00:00 2001 From: Alix Axel Date: Mon, 5 Feb 2018 23:58:03 +0100 Subject: [PATCH] feat(Frame): add click(), focus(), hover(), tap() and type() (#1970) This patch adds frame shortcuts to drive input: - `Frame.click()` - `Frame.focus()` - `Frame.hover()` - `Frame.tap()` - `Frame.type()` --- docs/api.md | 72 +++++++++++++++++++++++++++++++++++++++++++++ lib/FrameManager.js | 53 +++++++++++++++++++++++++++++++++ lib/Page.js | 47 ++++++++++------------------- 3 files changed, 141 insertions(+), 31 deletions(-) diff --git a/docs/api.md b/docs/api.md index 370ec3b8028..68ed9c405df 100644 --- a/docs/api.md +++ b/docs/api.md @@ -135,16 +135,21 @@ * [frame.addScriptTag(options)](#frameaddscripttagoptions) * [frame.addStyleTag(options)](#frameaddstyletagoptions) * [frame.childFrames()](#framechildframes) + * [frame.click(selector[, options])](#frameclickselector-options) * [frame.content()](#framecontent) * [frame.evaluate(pageFunction, ...args)](#frameevaluatepagefunction-args) * [frame.evaluateHandle(pageFunction, ...args)](#frameevaluatehandlepagefunction-args) * [frame.executionContext()](#frameexecutioncontext) + * [frame.focus(selector)](#framefocusselector) + * [frame.hover(selector)](#framehoverselector) * [frame.isDetached()](#frameisdetached) * [frame.name()](#framename) * [frame.parentFrame()](#frameparentframe) * [frame.select(selector, ...values)](#frameselectselector-values) * [frame.setContent(html)](#framesetcontenthtml) + * [frame.tap(selector)](#frametapselector) * [frame.title()](#frametitle) + * [frame.type(selector, text[, options])](#frametypeselector-text-options) * [frame.url()](#frameurl) * [frame.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#framewaitforselectororfunctionortimeout-options-args) * [frame.waitForFunction(pageFunction[, options[, ...args]])](#framewaitforfunctionpagefunction-options-args) @@ -621,6 +626,8 @@ const [response] = await Promise.all([ ]); ``` +Shortcut for [page.mainFrame().click(selector[, options])](#frameclickselector-options). + #### page.close() - returns: <[Promise]> @@ -856,6 +863,8 @@ puppeteer.launch().then(async browser => { This method fetches an element with `selector` and focuses it. If there's no element matching `selector`, the method throws an error. +Shortcut for [page.mainFrame().focus(selector)](#framefocusselector). + #### page.frames() - returns: <[Array]<[Frame]>> An array of all frames attached to the page. @@ -913,6 +922,8 @@ The `page.goto` will throw an error if: This method fetches an element with `selector`, scrolls it into view if needed, and then uses [page.mouse](#pagemouse) to hover over the center of the element. If there's no element matching `selector`, the method throws an error. +Shortcut for [page.mainFrame().hover(selector)](#framehoverselector). + #### page.keyboard - returns: <[Keyboard]> @@ -1162,6 +1173,8 @@ In the case of multiple pages in a single browser, each page can have its own vi This method fetches an element with `selector`, scrolls it into view if needed, and then uses [page.touchscreen](#pagetouchscreen) to tap in the center of the element. If there's no element matching `selector`, the method throws an error. +Shortcut for [page.mainFrame().tap(selector)](#frametapselector). + #### page.target() - returns: <[Target]> a target this page was created from. @@ -1192,6 +1205,8 @@ page.type('#mytextarea', 'Hello'); // Types instantly page.type('#mytextarea', 'World', {delay: 100}); // Types slower, like a user ``` +Shortcut for [page.mainFrame().type(selector, text[, options])](#frametypeselector-text-options). + #### page.url() - returns: <[string]> @@ -1625,6 +1640,26 @@ Adds a `` tag into the page with the desired url or a `> +#### frame.click(selector[, options]) +- `selector` <[string]> A [selector] to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked. +- `options` <[Object]> + - `button` <[string]> `left`, `right`, or `middle`, defaults to `left`. + - `clickCount` <[number]> defaults to 1. See [UIEvent.detail]. + - `delay` <[number]> Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0. +- returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully clicked. The Promise will be rejected if there is no element matching `selector`. + +This method fetches an element with `selector`, scrolls it into view if needed, and then uses [page.mouse](#pagemouse) to click in the center of the element. +If there's no element matching `selector`, the method throws an error. + +Bare in mind that if `click()` triggers a navigation event and there's a separate `page.waitForNavigation()` promise to be resolved, you may end up with a race condition that yields unexpected results. The correct pattern for click and wait for navigation is the following: + +```javascript +const [response] = await Promise.all([ + page.waitForNavigation(waitOptions), + frame.click(selector, clickOptions), +]); +``` + #### frame.content() - returns: <[Promise]<[String]>> @@ -1689,6 +1724,20 @@ await resultHandle.dispose(); #### frame.executionContext() - returns: <[Promise]<[ExecutionContext]>> Execution context associated with this frame. +#### frame.focus(selector) +- `selector` <[string]> A [selector] of an 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. The promise will be rejected if there is no element matching `selector`. + +This method fetches an element with `selector` and focuses it. +If there's no element matching `selector`, the method throws an error. + +#### frame.hover(selector) +- `selector` <[string]> A [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`. + +This method fetches an element with `selector`, scrolls it into view if needed, and then uses [page.mouse](#pagemouse) to hover over the center of the element. +If there's no element matching `selector`, the method throws an error. + #### frame.isDetached() - returns: <[boolean]> @@ -1723,9 +1772,32 @@ frame.select('select#colors', 'red', 'green', 'blue'); // multiple selections - `html` <[string]> HTML markup to assign to the page. - returns: <[Promise]> +#### frame.tap(selector) +- `selector` <[string]> A [selector] to search for element to tap. If there are multiple elements satisfying the selector, the first will be tapped. +- returns: <[Promise]> + +This method fetches an element with `selector`, scrolls it into view if needed, and then uses [page.touchscreen](#pagetouchscreen) to tap in the center of the element. +If there's no element matching `selector`, the method throws an error. + #### frame.title() - returns: <[Promise]<[string]>> Returns page's title. +#### frame.type(selector, text[, options]) +- `selector` <[string]> A [selector] of an element to type into. If there are multiple elements satisfying the selector, the first will be used. +- `text` <[string]> A text to type into a focused element. +- `options` <[Object]> + - `delay` <[number]> Time to wait between key presses in milliseconds. Defaults to 0. +- returns: <[Promise]> + +Sends a `keydown`, `keypress`/`input`, and `keyup` event for each character in the text. + +To press a special key, like `Control` or `ArrowDown`, use [`keyboard.press`](#keyboardpresskey-options). + +```js +frame.type('#mytextarea', 'Hello'); // Types instantly +frame.type('#mytextarea', 'World', {delay: 100}); // Types slower, like a user +``` + #### frame.url() - returns: <[string]> diff --git a/lib/FrameManager.js b/lib/FrameManager.js index d428943095e..d700e0b0766 100644 --- a/lib/FrameManager.js +++ b/lib/FrameManager.js @@ -547,6 +547,37 @@ class Frame { } } + /** + * @param {string} selector + * @param {!Object=} options + */ + async click(selector, options = {}) { + const handle = await this.$(selector); + console.assert(handle, 'No node found for selector: ' + selector); + await handle.click(options); + await handle.dispose(); + } + + /** + * @param {string} selector + */ + async focus(selector) { + const handle = await this.$(selector); + console.assert(handle, 'No node found for selector: ' + selector); + await handle.focus(); + await handle.dispose(); + } + + /** + * @param {string} selector + */ + async hover(selector) { + const handle = await this.$(selector); + console.assert(handle, 'No node found for selector: ' + selector); + await handle.hover(); + await handle.dispose(); + } + /** * @param {string} selector * @param {!Array} values @@ -572,6 +603,28 @@ class Frame { }, values); } + /** + * @param {string} selector + */ + async tap(selector) { + const handle = await this.$(selector); + console.assert(handle, 'No node found for selector: ' + selector); + await handle.tap(); + await handle.dispose(); + } + + /** + * @param {string} selector + * @param {string} text + * @param {{delay: (number|undefined)}=} options + */ + async type(selector, text, options) { + const handle = await this.$(selector); + console.assert(handle, 'No node found for selector: ' + selector); + await handle.type(text, options); + await handle.dispose(); + } + /** * @param {(string|number|Function)} selectorOrFunctionOrTimeout * @param {!Object=} options diff --git a/lib/Page.js b/lib/Page.js index 2387b7aec1d..415dec75439 100644 --- a/lib/Page.js +++ b/lib/Page.js @@ -144,16 +144,6 @@ class Page extends EventEmitter { return this._coverage; } - /** - * @param {string} selector - */ - async tap(selector) { - const handle = await this.$(selector); - console.assert(handle, 'No node found for selector: ' + selector); - await handle.tap(); - await handle.dispose(); - } - /** * @return {!Tracing} */ @@ -814,31 +804,22 @@ class Page extends EventEmitter { * @param {string} selector * @param {!Object=} options */ - async click(selector, options = {}) { - const handle = await this.$(selector); - console.assert(handle, 'No node found for selector: ' + selector); - await handle.click(options); - await handle.dispose(); + click(selector, options = {}) { + return this.mainFrame().click(selector, options); } /** * @param {string} selector */ - async hover(selector) { - const handle = await this.$(selector); - console.assert(handle, 'No node found for selector: ' + selector); - await handle.hover(); - await handle.dispose(); + focus(selector) { + return this.mainFrame().focus(selector); } /** * @param {string} selector */ - async focus(selector) { - const handle = await this.$(selector); - console.assert(handle, 'No node found for selector: ' + selector); - await handle.focus(); - await handle.dispose(); + hover(selector) { + return this.mainFrame().hover(selector); } /** @@ -846,20 +827,24 @@ class Page extends EventEmitter { * @param {!Array} values * @return {!Promise>} */ - async select(selector, ...values) { + select(selector, ...values) { return this.mainFrame().select(selector, ...values); } + /** + * @param {string} selector + */ + tap(selector) { + return this.mainFrame().tap(selector); + } + /** * @param {string} selector * @param {string} text * @param {{delay: (number|undefined)}=} options */ - async type(selector, text, options) { - const handle = await this.$(selector); - console.assert(handle, 'No node found for selector: ' + selector); - await handle.type(text, options); - await handle.dispose(); + type(selector, text, options) { + return this.mainFrame().type(selector, text, options); } /**