Implement basic element handles (#248)
This patch implements basic element handles which a backed with remote objects. Fixes #111
This commit is contained in:
parent
a424f5613a
commit
af89e893e7
102
docs/api.md
102
docs/api.md
@ -26,6 +26,7 @@
|
|||||||
+ [event: 'requestfailed'](#event-requestfailed)
|
+ [event: 'requestfailed'](#event-requestfailed)
|
||||||
+ [event: 'requestfinished'](#event-requestfinished)
|
+ [event: 'requestfinished'](#event-requestfinished)
|
||||||
+ [event: 'response'](#event-response)
|
+ [event: 'response'](#event-response)
|
||||||
|
+ [page.$(selector)](#pageselector)
|
||||||
+ [page.addBinding(name, puppeteerFunction)](#pageaddbindingname-puppeteerfunction)
|
+ [page.addBinding(name, puppeteerFunction)](#pageaddbindingname-puppeteerfunction)
|
||||||
+ [page.addScriptTag(url)](#pageaddscripttagurl)
|
+ [page.addScriptTag(url)](#pageaddscripttagurl)
|
||||||
+ [page.click(selector[, options])](#pageclickselector-options)
|
+ [page.click(selector[, options])](#pageclickselector-options)
|
||||||
@ -82,12 +83,10 @@
|
|||||||
+ [dialog.message()](#dialogmessage)
|
+ [dialog.message()](#dialogmessage)
|
||||||
+ [dialog.type](#dialogtype)
|
+ [dialog.type](#dialogtype)
|
||||||
* [class: Frame](#class-frame)
|
* [class: Frame](#class-frame)
|
||||||
|
+ [frame.$(selector)](#frameselector)
|
||||||
+ [frame.addScriptTag(url)](#frameaddscripttagurl)
|
+ [frame.addScriptTag(url)](#frameaddscripttagurl)
|
||||||
+ [frame.childFrames()](#framechildframes)
|
+ [frame.childFrames()](#framechildframes)
|
||||||
+ [frame.click(selector[, options])](#frameclickselector-options)
|
|
||||||
+ [frame.evaluate(pageFunction, ...args)](#frameevaluatepagefunction-args)
|
+ [frame.evaluate(pageFunction, ...args)](#frameevaluatepagefunction-args)
|
||||||
+ [frame.focus(selector)](#framefocusselector)
|
|
||||||
+ [frame.hover(selector)](#framehoverselector)
|
|
||||||
+ [frame.injectFile(filePath)](#frameinjectfilefilepath)
|
+ [frame.injectFile(filePath)](#frameinjectfilefilepath)
|
||||||
+ [frame.isDetached()](#frameisdetached)
|
+ [frame.isDetached()](#frameisdetached)
|
||||||
+ [frame.name()](#framename)
|
+ [frame.name()](#framename)
|
||||||
@ -98,6 +97,11 @@
|
|||||||
+ [frame.waitFor(selectorOrFunctionOrTimeout[, options])](#framewaitforselectororfunctionortimeout-options)
|
+ [frame.waitFor(selectorOrFunctionOrTimeout[, options])](#framewaitforselectororfunctionortimeout-options)
|
||||||
+ [frame.waitForFunction(pageFunction[, options, ...args])](#framewaitforfunctionpagefunction-options-args)
|
+ [frame.waitForFunction(pageFunction[, options, ...args])](#framewaitforfunctionpagefunction-options-args)
|
||||||
+ [frame.waitForSelector(selector[, options])](#framewaitforselectorselector-options)
|
+ [frame.waitForSelector(selector[, options])](#framewaitforselectorselector-options)
|
||||||
|
* [class: ElementHandle](#class-elementhandle)
|
||||||
|
+ [elementHandle.click([options])](#elementhandleclickoptions)
|
||||||
|
+ [elementHandle.evaluate(pageFunction, ...args)](#elementhandleevaluatepagefunction-args)
|
||||||
|
+ [elementHandle.hover()](#elementhandlehover)
|
||||||
|
+ [elementHandle.release()](#elementhandlerelease)
|
||||||
* [class: Request](#class-request)
|
* [class: Request](#class-request)
|
||||||
+ [request.abort()](#requestabort)
|
+ [request.abort()](#requestabort)
|
||||||
+ [request.continue([overrides])](#requestcontinueoverrides)
|
+ [request.continue([overrides])](#requestcontinueoverrides)
|
||||||
@ -283,6 +287,13 @@ Emitted when a request is successfully finished.
|
|||||||
|
|
||||||
Emitted when a [response] is received.
|
Emitted when a [response] is received.
|
||||||
|
|
||||||
|
#### page.$(selector)
|
||||||
|
- `selector` <[string]> Selector to query page for
|
||||||
|
- returns: <[Promise]<[ElementHandle]>> Promise which resolves to ElementHandle pointing to the page element.
|
||||||
|
|
||||||
|
The method queries page for the selector. If there's no such element on the page, the method will resolve to `null`.
|
||||||
|
|
||||||
|
Shortcut for [page.mainFrame().$(selector)](#frameselector).
|
||||||
|
|
||||||
#### page.addBinding(name, puppeteerFunction)
|
#### page.addBinding(name, puppeteerFunction)
|
||||||
- `name` <[string]> Name of the binding on window object
|
- `name` <[string]> Name of the binding on window object
|
||||||
@ -345,7 +356,6 @@ puppeteer.launch().then(async browser => {
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
#### page.addScriptTag(url)
|
#### page.addScriptTag(url)
|
||||||
- `url` <[string]> Url of a script to be added
|
- `url` <[string]> Url of a script to be added
|
||||||
- returns: <[Promise]> Promise which resolves as the script gets added and loads.
|
- returns: <[Promise]> Promise which resolves as the script gets added and loads.
|
||||||
@ -932,6 +942,12 @@ puppeteer.launch().then(async browser => {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### frame.$(selector)
|
||||||
|
- `selector` <[string]> Selector to query page for
|
||||||
|
- returns: <[Promise]<[ElementHandle]>> Promise which resolves to ElementHandle pointing to the page element.
|
||||||
|
|
||||||
|
The method queries page for the selector. If there's no such element on the page, the method will resolve to `null`.
|
||||||
|
|
||||||
|
|
||||||
#### frame.addScriptTag(url)
|
#### frame.addScriptTag(url)
|
||||||
- `url` <[string]> Url of a script to be added
|
- `url` <[string]> Url of a script to be added
|
||||||
@ -942,14 +958,6 @@ Adds a `<script>` tag to the frame with the desired url. Alternatively, JavaScri
|
|||||||
#### frame.childFrames()
|
#### frame.childFrames()
|
||||||
- returns: <[Array]<[Frame]>>
|
- returns: <[Array]<[Frame]>>
|
||||||
|
|
||||||
#### frame.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.
|
|
||||||
- `options` <[Object]>
|
|
||||||
- `button` <[string]> `left`, `right`, or `middle`, defaults to `left`.
|
|
||||||
- `clickCount` <[number]> defaults to 1
|
|
||||||
- `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. Promise gets rejected if there's no element matching `selector`.
|
|
||||||
|
|
||||||
#### frame.evaluate(pageFunction, ...args)
|
#### frame.evaluate(pageFunction, ...args)
|
||||||
- `pageFunction` <[function]|[string]> Function to be evaluated in browser context
|
- `pageFunction` <[function]|[string]> Function to be evaluated in browser context
|
||||||
- `...args` <...[string]> Arguments to pass to `pageFunction`
|
- `...args` <...[string]> Arguments to pass to `pageFunction`
|
||||||
@ -976,15 +984,6 @@ A string can also be passed in instead of a function.
|
|||||||
console.log(await page.evaluate('1 + 2')); // prints "3"
|
console.log(await page.evaluate('1 + 2')); // prints "3"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
#### 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)
|
#### frame.injectFile(filePath)
|
||||||
- `filePath` <[string]> Path to the JavaScript file to be injected into frame. If `filePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd).
|
- `filePath` <[string]> Path to the JavaScript file to be injected into frame. If `filePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd).
|
||||||
- returns: <[Promise]> Promise which resolves when file gets successfully evaluated in frame.
|
- returns: <[Promise]> Promise which resolves when file gets successfully evaluated in frame.
|
||||||
@ -1079,6 +1078,66 @@ puppeteer.launch().then(async browser => {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### class: ElementHandle
|
||||||
|
|
||||||
|
ElementHandle represents an in-page DOM element. ElementHandles could be created with the [page.$](#pageselector) method.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const puppeteer = require('puppeteer');
|
||||||
|
puppeteer.launch().then(async browser => {
|
||||||
|
let page = await browser.newPage();
|
||||||
|
await page.goto('https://google.com');
|
||||||
|
let inputElement = await page.$('input[type=submit]');
|
||||||
|
await inputElement.click();
|
||||||
|
...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
ElementHandle prevents DOM element from garbage collection unless the handle is [released](#elementhandlerelease). ElementHandles are auto-released when their origin frame gets navigated.
|
||||||
|
|
||||||
|
#### elementHandle.click([options])
|
||||||
|
- `options` <[Object]>
|
||||||
|
- `button` <[string]> `left`, `right`, or `middle`, defaults to `left`.
|
||||||
|
- `clickCount` <[number]> defaults to 1
|
||||||
|
- `delay` <[number]> Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0.
|
||||||
|
- returns: <[Promise]> Promise which resolves when the element is successfully clicked. Promise gets rejected if the element is detached from DOM.
|
||||||
|
|
||||||
|
This method scrolls element into view if needed, and then uses [page.mouse](#pagemouse) to click in the center of the element.
|
||||||
|
If the element is detached from DOM, the method throws an error.
|
||||||
|
|
||||||
|
#### elementHandle.evaluate(pageFunction, ...args)
|
||||||
|
- `pageFunction` <[function]> Function to be evaluated in browser context
|
||||||
|
- `...args` <...[string]> Arguments to pass to `pageFunction`
|
||||||
|
- returns: <[Promise]<[Object]>> Promise which resolves to function return value
|
||||||
|
|
||||||
|
If the function, passed to the `elementHandle.evaluate`, returns a [Promise], then `elementHandle.evaluate` would wait for the promise to resolve and return it's value.
|
||||||
|
The function will be passed in the element ifself as a first argument.
|
||||||
|
|
||||||
|
#### elementHandle.hover()
|
||||||
|
- returns: <[Promise]> Promise which resolves when the element is successfully hovered.
|
||||||
|
|
||||||
|
This method scrolls element into view if needed, and then uses [page.mouse](#pagemouse) to hover over the center of the element.
|
||||||
|
If the element is detached from DOM, the method throws an error.
|
||||||
|
|
||||||
|
|
||||||
|
#### elementHandle.release()
|
||||||
|
- returns: <[Promise]> Promise which resolves when the element handle is successfully released.
|
||||||
|
|
||||||
|
The `elementHandle.release` method stops referencing the element handle.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const {Browser} = require('puppeteer');
|
||||||
|
const browser = new Browser();
|
||||||
|
browser.newPage().then(async page =>
|
||||||
|
await page.setContent('<div>hello</div>');
|
||||||
|
let element = await page.$('div');
|
||||||
|
let text = element.evaluate((e, suffix) => e.textContent + ' ' + suffix, 'world!');
|
||||||
|
console.log(text); // "hello world!"
|
||||||
|
browser.close();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### class: Request
|
### class: Request
|
||||||
|
|
||||||
Whenever the page sends a request, the following events are emitted by puppeteer's page:
|
Whenever the page sends a request, the following events are emitted by puppeteer's page:
|
||||||
@ -1189,3 +1248,4 @@ Contains the URL of the response.
|
|||||||
[Map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map "Map"
|
[Map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map "Map"
|
||||||
[selector]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors "selector"
|
[selector]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors "selector"
|
||||||
[Tracing]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-tracing "Tracing"
|
[Tracing]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-tracing "Tracing"
|
||||||
|
[ElementHandle]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-element "ElementHandle"
|
||||||
|
@ -166,6 +166,8 @@ class Session extends EventEmitter {
|
|||||||
* @return {!Promise<?Object>}
|
* @return {!Promise<?Object>}
|
||||||
*/
|
*/
|
||||||
send(method, params = {}) {
|
send(method, params = {}) {
|
||||||
|
if (!this._connection)
|
||||||
|
return Promise.reject(new Error(`Protocol error (${method}): Session closed. Most likely the page has been closed.`));
|
||||||
let id = ++this._lastId;
|
let id = ++this._lastId;
|
||||||
let message = JSON.stringify({id, method, params});
|
let message = JSON.stringify({id, method, params});
|
||||||
debugSession('SEND ► ' + message);
|
debugSession('SEND ► ' + message);
|
||||||
@ -212,6 +214,7 @@ class Session extends EventEmitter {
|
|||||||
for (let callback of this._callbacks.values())
|
for (let callback of this._callbacks.values())
|
||||||
callback.reject(new Error(`Protocol error (${callback.method}): Target closed.`));
|
callback.reject(new Error(`Protocol error (${callback.method}): Target closed.`));
|
||||||
this._callbacks.clear();
|
this._callbacks.clear();
|
||||||
|
this._connection = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
98
lib/ElementHandle.js
Normal file
98
lib/ElementHandle.js
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2017 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
const helper = require('./helper');
|
||||||
|
|
||||||
|
class ElementHandle {
|
||||||
|
/**
|
||||||
|
* @param {!Connection} client
|
||||||
|
* @param {!Object} remoteObject
|
||||||
|
* @param {!Mouse} mouse
|
||||||
|
*/
|
||||||
|
constructor(client, remoteObject, mouse) {
|
||||||
|
this._client = client;
|
||||||
|
this._remoteObject = remoteObject;
|
||||||
|
this._mouse = mouse;
|
||||||
|
this._released = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {!Promise}
|
||||||
|
*/
|
||||||
|
async release() {
|
||||||
|
if (this._released)
|
||||||
|
return;
|
||||||
|
this._released = true;
|
||||||
|
await helper.releaseObject(this._client, this._remoteObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {function()} pageFunction
|
||||||
|
* @param {!Array<*>} args
|
||||||
|
* @return {!Promise<(!Object|undefined)>}
|
||||||
|
*/
|
||||||
|
async evaluate(pageFunction, ...args) {
|
||||||
|
console.assert(!this._released, 'ElementHandle is released!');
|
||||||
|
console.assert(typeof pageFunction === 'function', 'First argument to ElementHandle.evaluate must be a function!');
|
||||||
|
|
||||||
|
let stringifiedArgs = ['this'];
|
||||||
|
stringifiedArgs.push(...args.map(x => JSON.stringify(x)));
|
||||||
|
let functionDeclaration = `function() { return (${pageFunction})(${stringifiedArgs.join(',')}) }`;
|
||||||
|
const objectId = this._remoteObject.objectId;
|
||||||
|
let { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.callFunctionOn', { objectId, functionDeclaration, returnByValue: false});
|
||||||
|
if (exceptionDetails)
|
||||||
|
throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails));
|
||||||
|
return await helper.serializeRemoteObject(this._client, remoteObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {!Promise<{x: number, y: number}>}
|
||||||
|
*/
|
||||||
|
async _visibleCenter() {
|
||||||
|
let center = await this.evaluate(element => {
|
||||||
|
if (!element.ownerDocument.contains(element))
|
||||||
|
return null;
|
||||||
|
element.scrollIntoViewIfNeeded();
|
||||||
|
let rect = element.getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
x: (Math.max(rect.left, 0) + Math.min(rect.right, window.innerWidth)) / 2,
|
||||||
|
y: (Math.max(rect.top, 0) + Math.min(rect.bottom, window.innerHeight)) / 2
|
||||||
|
};
|
||||||
|
});
|
||||||
|
if (!center)
|
||||||
|
throw new Error('No node found for selector: ' + selector);
|
||||||
|
return center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {!Promise}
|
||||||
|
*/
|
||||||
|
async hover() {
|
||||||
|
let {x, y} = await this._visibleCenter();
|
||||||
|
await this._mouse.move(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {!Object=} options
|
||||||
|
* @return {!Promise}
|
||||||
|
*/
|
||||||
|
async click(options) {
|
||||||
|
let {x, y} = await this._visibleCenter();
|
||||||
|
await this._mouse.click(x, y, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ElementHandle;
|
||||||
|
helper.tracePublicAPI(ElementHandle);
|
@ -18,6 +18,7 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const EventEmitter = require('events');
|
const EventEmitter = require('events');
|
||||||
const helper = require('./helper');
|
const helper = require('./helper');
|
||||||
|
const ElementHandle = require('./ElementHandle');
|
||||||
|
|
||||||
class FrameManager extends EventEmitter {
|
class FrameManager extends EventEmitter {
|
||||||
/**
|
/**
|
||||||
@ -171,12 +172,34 @@ class Frame {
|
|||||||
* @return {!Promise<(!Object|undefined)>}
|
* @return {!Promise<(!Object|undefined)>}
|
||||||
*/
|
*/
|
||||||
async evaluate(pageFunction, ...args) {
|
async evaluate(pageFunction, ...args) {
|
||||||
|
let remoteObject = await this._rawEvaluate(pageFunction, ...args);
|
||||||
|
return await helper.serializeRemoteObject(this._client, remoteObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} selector
|
||||||
|
* @return {!Promise<?ElementHandle>}
|
||||||
|
*/
|
||||||
|
async $(selector) {
|
||||||
|
let remoteObject = await this._rawEvaluate(selector => document.querySelector(selector), selector);
|
||||||
|
if (remoteObject.subtype === 'node')
|
||||||
|
return new ElementHandle(this._client, remoteObject, this._mouse);
|
||||||
|
helper.releaseObject(this._client, remoteObject);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {function()|string} pageFunction
|
||||||
|
* @param {!Array<*>} args
|
||||||
|
* @return {!Promise<(!Object|undefined)>}
|
||||||
|
*/
|
||||||
|
async _rawEvaluate(pageFunction, ...args) {
|
||||||
let expression = helper.evaluationString(pageFunction, ...args);
|
let expression = helper.evaluationString(pageFunction, ...args);
|
||||||
const contextId = this._defaultContextId;
|
const contextId = this._defaultContextId;
|
||||||
let { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.evaluate', { expression, contextId, returnByValue: false});
|
let { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.evaluate', { expression, contextId, returnByValue: false});
|
||||||
if (exceptionDetails)
|
if (exceptionDetails)
|
||||||
throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails));
|
throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails));
|
||||||
return await helper.serializeRemoteObject(this._client, remoteObject);
|
return remoteObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -324,62 +347,6 @@ class Frame {
|
|||||||
return this.evaluate(() => document.title);
|
return this.evaluate(() => document.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} selector
|
|
||||||
* @return {!Promise<{x: number, y: number}>}
|
|
||||||
*/
|
|
||||||
async _centerOfElement(selector) {
|
|
||||||
let center = await this.evaluate(selector => {
|
|
||||||
let element = document.querySelector(selector);
|
|
||||||
if (!element)
|
|
||||||
return null;
|
|
||||||
element.scrollIntoViewIfNeeded();
|
|
||||||
let rect = element.getBoundingClientRect();
|
|
||||||
return {
|
|
||||||
x: (Math.max(rect.left, 0) + Math.min(rect.right, window.innerWidth)) / 2,
|
|
||||||
y: (Math.max(rect.top, 0) + Math.min(rect.bottom, window.innerHeight)) / 2
|
|
||||||
};
|
|
||||||
}, selector);
|
|
||||||
if (!center)
|
|
||||||
throw new Error('No node found for selector: ' + selector);
|
|
||||||
return center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} selector
|
|
||||||
* @return {!Promise}
|
|
||||||
*/
|
|
||||||
async hover(selector) {
|
|
||||||
let {x, y} = await this._centerOfElement(selector);
|
|
||||||
await this._mouse.move(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} selector
|
|
||||||
* @param {!Object=} options
|
|
||||||
* @return {!Promise}
|
|
||||||
*/
|
|
||||||
async click(selector, options) {
|
|
||||||
let {x, y} = await this._centerOfElement(selector);
|
|
||||||
await this._mouse.click(x, y, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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
|
* @param {!Object} framePayload
|
||||||
*/
|
*/
|
||||||
|
23
lib/Page.js
23
lib/Page.js
@ -138,6 +138,14 @@ class Page extends EventEmitter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} selector
|
||||||
|
* @return {!Promise<?ElementHandle>}
|
||||||
|
*/
|
||||||
|
async $(selector) {
|
||||||
|
return this.mainFrame().$(selector);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} url
|
* @param {string} url
|
||||||
* @return {!Promise}
|
* @return {!Promise}
|
||||||
@ -555,7 +563,10 @@ class Page extends EventEmitter {
|
|||||||
* @return {!Promise}
|
* @return {!Promise}
|
||||||
*/
|
*/
|
||||||
async click(selector, options) {
|
async click(selector, options) {
|
||||||
await this.mainFrame().click(selector, options);
|
let handle = await this.$(selector);
|
||||||
|
console.assert(handle, 'No node found for selector: ' + selector);
|
||||||
|
await handle.click(options);
|
||||||
|
await handle.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -563,7 +574,10 @@ class Page extends EventEmitter {
|
|||||||
* @param {!Promise}
|
* @param {!Promise}
|
||||||
*/
|
*/
|
||||||
async hover(selector) {
|
async hover(selector) {
|
||||||
await this.mainFrame().hover(selector);
|
let handle = await this.$(selector);
|
||||||
|
console.assert(handle, 'No node found for selector: ' + selector);
|
||||||
|
await handle.hover();
|
||||||
|
await handle.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -571,7 +585,10 @@ class Page extends EventEmitter {
|
|||||||
* @return {!Promise}
|
* @return {!Promise}
|
||||||
*/
|
*/
|
||||||
async focus(selector) {
|
async focus(selector) {
|
||||||
return this.mainFrame().focus(selector);
|
let handle = await this.$(selector);
|
||||||
|
console.assert(handle, 'No node found for selector: ' + selector);
|
||||||
|
await handle.evaluate(element => element.focus());
|
||||||
|
await handle.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
72
test/test.js
72
test/test.js
@ -1059,6 +1059,78 @@ describe('Page', function() {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Page.$', function() {
|
||||||
|
it('should query existing element', SX(async function() {
|
||||||
|
await page.setContent('<section>test</section>');
|
||||||
|
let element = await page.$('section');
|
||||||
|
expect(element).toBeTruthy();
|
||||||
|
}));
|
||||||
|
it('should return null for non-existing element', SX(async function() {
|
||||||
|
let element = await page.$('non-existing-element');
|
||||||
|
expect(element).toBe(null);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ElementHandle.evaluate', function() {
|
||||||
|
it('should work', SX(async function() {
|
||||||
|
await page.setContent('<section>42</section>');
|
||||||
|
let element = await page.$('section');
|
||||||
|
let text = await element.evaluate(e => e.textContent);
|
||||||
|
expect(text).toBe('42');
|
||||||
|
}));
|
||||||
|
it('should await promise if any', SX(async function() {
|
||||||
|
await page.setContent('<section>39</section>');
|
||||||
|
let element = await page.$('section');
|
||||||
|
let text = await element.evaluate(e => Promise.resolve(e.textContent));
|
||||||
|
expect(text).toBe('39');
|
||||||
|
}));
|
||||||
|
it('should throw if underlying page got closed', SX(async function() {
|
||||||
|
let otherPage = await browser.newPage();
|
||||||
|
await otherPage.setContent('<section>88</section>');
|
||||||
|
let element = await otherPage.$('section');
|
||||||
|
expect(element).toBeTruthy();
|
||||||
|
await otherPage.close();
|
||||||
|
let error = null;
|
||||||
|
try {
|
||||||
|
await element.evaluate(e => e.textContent);
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
expect(error.message).toContain('Session closed');
|
||||||
|
}));
|
||||||
|
it('should throw if underlying element was released', SX(async function() {
|
||||||
|
await page.setContent('<section>39</section>');
|
||||||
|
let element = await page.$('section');
|
||||||
|
expect(element).toBeTruthy();
|
||||||
|
await element.release();
|
||||||
|
let error = null;
|
||||||
|
try {
|
||||||
|
await element.evaluate(e => e.textContent);
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
expect(error.message).toContain('ElementHandle is released');
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ElementHandle.click', function() {
|
||||||
|
it('should work', SX(async function() {
|
||||||
|
await page.goto(PREFIX + '/input/button.html');
|
||||||
|
let button = await page.$('button');
|
||||||
|
await button.click('button');
|
||||||
|
expect(await page.evaluate(() => result)).toBe('Clicked');
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ElementHandle.hover', function() {
|
||||||
|
it('should work', SX(async function() {
|
||||||
|
await page.goto(PREFIX + '/input/scrollable.html');
|
||||||
|
let button = await page.$('#button-6');
|
||||||
|
await button.hover();
|
||||||
|
expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6');
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
describe('input', function() {
|
describe('input', function() {
|
||||||
it('should click the button', SX(async function() {
|
it('should click the button', SX(async function() {
|
||||||
await page.goto(PREFIX + '/input/button.html');
|
await page.goto(PREFIX + '/input/button.html');
|
||||||
|
@ -38,6 +38,7 @@ const EXCLUDE_METHODS = new Set([
|
|||||||
'Body.constructor',
|
'Body.constructor',
|
||||||
'Browser.constructor',
|
'Browser.constructor',
|
||||||
'Dialog.constructor',
|
'Dialog.constructor',
|
||||||
|
'ElementHandle.constructor',
|
||||||
'Frame.constructor',
|
'Frame.constructor',
|
||||||
'Headers.constructor',
|
'Headers.constructor',
|
||||||
'Headers.fromPayload',
|
'Headers.fromPayload',
|
||||||
|
Loading…
Reference in New Issue
Block a user