mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
feat(JSHandles): introduce JSHandles (#943)
This patch: - introduces ExecutionContext class that incapsulates javascript execution context. An examples of execution contexts are workers and frames - introduces JSHandle that holds a references to the javascript object in ExecutionContext - inherits ElementHandle from JSHandle Fixes #382.
This commit is contained in:
parent
59bcc2ee56
commit
0d0f9b7984
221
docs/api.md
221
docs/api.md
@ -44,6 +44,7 @@
|
||||
+ [page.emulate(options)](#pageemulateoptions)
|
||||
+ [page.emulateMedia(mediaType)](#pageemulatemediamediatype)
|
||||
+ [page.evaluate(pageFunction, ...args)](#pageevaluatepagefunction-args)
|
||||
+ [page.evaluateHandle(pageFunction, ...args)](#pageevaluatehandlepagefunction-args)
|
||||
+ [page.evaluateOnNewDocument(pageFunction, ...args)](#pageevaluateonnewdocumentpagefunction-args)
|
||||
+ [page.exposeFunction(name, puppeteerFunction)](#pageexposefunctionname-puppeteerfunction)
|
||||
+ [page.focus(selector)](#pagefocusselector)
|
||||
@ -112,6 +113,7 @@
|
||||
+ [frame.addStyleTag(url)](#frameaddstyletagurl)
|
||||
+ [frame.childFrames()](#framechildframes)
|
||||
+ [frame.evaluate(pageFunction, ...args)](#frameevaluatepagefunction-args)
|
||||
+ [frame.executionContext()](#frameexecutioncontext)
|
||||
+ [frame.injectFile(filePath)](#frameinjectfilefilepath)
|
||||
+ [frame.isDetached()](#frameisdetached)
|
||||
+ [frame.name()](#framename)
|
||||
@ -121,11 +123,28 @@
|
||||
+ [frame.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#framewaitforselectororfunctionortimeout-options-args)
|
||||
+ [frame.waitForFunction(pageFunction[, options[, ...args]])](#framewaitforfunctionpagefunction-options-args)
|
||||
+ [frame.waitForSelector(selector[, options])](#framewaitforselectorselector-options)
|
||||
* [class: ExecutionContext](#class-executioncontext)
|
||||
+ [executionContext.evaluate(pageFunction, ...args)](#executioncontextevaluatepagefunction-args)
|
||||
+ [executionContext.evaluateHandle(pageFunction, ...args)](#executioncontextevaluatehandlepagefunction-args)
|
||||
* [class: JSHandle](#class-jshandle)
|
||||
+ [jsHandle.asElement()](#jshandleaselement)
|
||||
+ [jsHandle.dispose()](#jshandledispose)
|
||||
+ [jsHandle.executionContext()](#jshandleexecutioncontext)
|
||||
+ [jsHandle.getProperties()](#jshandlegetproperties)
|
||||
+ [jsHandle.getProperty(propertyName)](#jshandlegetpropertypropertyname)
|
||||
+ [jsHandle.jsonValue()](#jshandlejsonvalue)
|
||||
+ [jsHandle.toString()](#jshandletostring)
|
||||
* [class: ElementHandle](#class-elementhandle)
|
||||
+ [elementHandle.asElement()](#elementhandleaselement)
|
||||
+ [elementHandle.click([options])](#elementhandleclickoptions)
|
||||
+ [elementHandle.dispose()](#elementhandledispose)
|
||||
+ [elementHandle.executionContext()](#elementhandleexecutioncontext)
|
||||
+ [elementHandle.getProperties()](#elementhandlegetproperties)
|
||||
+ [elementHandle.getProperty(propertyName)](#elementhandlegetpropertypropertyname)
|
||||
+ [elementHandle.hover()](#elementhandlehover)
|
||||
+ [elementHandle.jsonValue()](#elementhandlejsonvalue)
|
||||
+ [elementHandle.tap()](#elementhandletap)
|
||||
+ [elementHandle.toString()](#elementhandletostring)
|
||||
+ [elementHandle.uploadFile(...filePaths)](#elementhandleuploadfilefilepaths)
|
||||
* [class: Request](#class-request)
|
||||
+ [request.abort()](#requestabort)
|
||||
@ -352,7 +371,7 @@ Shortcut for [page.mainFrame().$$(selector)](#frameselector-1).
|
||||
|
||||
This method runs `document.querySelector` within the page and passes it as the first argument to `pageFunction`. If there's no element matching `selector`, the method throws an error.
|
||||
|
||||
If `pageFunction` returns a [Promise], then `page.$eval` would wait for the promise to resolve and return it's value.
|
||||
If `pageFunction` returns a [Promise], then `page.$eval` would wait for the promise to resolve and return its value.
|
||||
|
||||
Examples:
|
||||
```js
|
||||
@ -475,7 +494,7 @@ List of all available devices is available in the source code: [DeviceDescriptor
|
||||
- `...args` <...[Serializable]|[ElementHandle]> Arguments to pass to `pageFunction`
|
||||
- returns: <[Promise]<[Serializable]>> Resolves to the return value of `pageFunction`
|
||||
|
||||
If the function, passed to the `page.evaluate`, returns a [Promise], then `page.evaluate` would wait for the promise to resolve and return it's value.
|
||||
If the function, passed to the `page.evaluate`, returns a [Promise], then `page.evaluate` would wait for the promise to resolve and return its value.
|
||||
|
||||
```js
|
||||
const result = await page.evaluate(() => {
|
||||
@ -499,6 +518,35 @@ await bodyHandle.dispose();
|
||||
|
||||
Shortcut for [page.mainFrame().evaluate(pageFunction, ...args)](#frameevaluatepagefunction-args).
|
||||
|
||||
#### page.evaluateHandle(pageFunction, ...args)
|
||||
- `pageFunction` <[function]|[string]> Function to be evaluated in the page context
|
||||
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
|
||||
- returns: <[Promise]<[JSHandle]>> Resolves to the return value of `pageFunction`
|
||||
|
||||
If the function, passed to the `page.evaluateHandle`, returns a [Promise], then `page.evaluateHandle` would wait for the promise to resolve and return its value.
|
||||
|
||||
```js
|
||||
const aWindowHandle = await page.evaluateHandle(() => Promise.resolve(window));
|
||||
aWindowHandle; // Handle for the window object.
|
||||
```
|
||||
|
||||
A string can also be passed in instead of a function.
|
||||
|
||||
```js
|
||||
const aHandle = await page.evaluateHandle('document'); // Handle for the 'document'.
|
||||
```
|
||||
|
||||
[JSHandle] instances could be passed as arguments to the `page.evaluateHandle`:
|
||||
```js
|
||||
const aHandle = await page.evaluateHandle(() => document.body);
|
||||
const resultHandle = await page.evaluateHandle(body => body.innerHTML, aHandle);
|
||||
console.log(await resultHandle.jsonValue());
|
||||
await resultHandle.dispose();
|
||||
```
|
||||
|
||||
Shortcut for [page.mainFrame().executionContext().evaluateHandle(pageFunction, ...args)](#frameobjectpagefunction-args).
|
||||
|
||||
|
||||
#### page.evaluateOnNewDocument(pageFunction, ...args)
|
||||
- `pageFunction` <[function]|[string]> Function to be evaluated in browser context
|
||||
- `...args` <...[Serializable]> Arguments to pass to `pageFunction`
|
||||
@ -1171,7 +1219,7 @@ The method runs `document.querySelectorAll` within the frame. If no elements mat
|
||||
|
||||
This method runs `document.querySelector` within the frame and passes it as the first argument to `pageFunction`. If there's no element matching `selector`, the method throws an error.
|
||||
|
||||
If `pageFunction` returns a [Promise], then `frame.$eval` would wait for the promise to resolve and return it's value.
|
||||
If `pageFunction` returns a [Promise], then `frame.$eval` would wait for the promise to resolve and return its value.
|
||||
|
||||
Examples:
|
||||
```js
|
||||
@ -1200,7 +1248,7 @@ Adds a `<link rel="stylesheet">` tag to the frame with the desired url.
|
||||
- `...args` <...[Serializable]|[ElementHandle]> Arguments to pass to `pageFunction`
|
||||
- returns: <[Promise]<[Serializable]>> Promise which resolves to function return value
|
||||
|
||||
If the function, passed to the `frame.evaluate`, returns a [Promise], then `frame.evaluate` would wait for the promise to resolve and return it's value.
|
||||
If the function, passed to the `frame.evaluate`, returns a [Promise], then `frame.evaluate` would wait for the promise to resolve and return its value.
|
||||
|
||||
```js
|
||||
const result = await frame.evaluate(() => {
|
||||
@ -1222,6 +1270,9 @@ const html = await frame.evaluate(body => body.innerHTML, bodyHandle);
|
||||
await bodyHandle.dispose();
|
||||
```
|
||||
|
||||
#### frame.executionContext()
|
||||
- returns: <[ExecutionContext]> Execution context associated with this frame.
|
||||
|
||||
#### 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).
|
||||
- returns: <[Promise]> Promise which resolves when file gets successfully evaluated in frame.
|
||||
@ -1314,8 +1365,129 @@ puppeteer.launch().then(async browser => {
|
||||
});
|
||||
```
|
||||
|
||||
### class: ExecutionContext
|
||||
|
||||
The class represents a context for JavaScript execution. Examples of JavaScript contexts are:
|
||||
- each [frame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe) has a separate execution context
|
||||
- all kind of [workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) have their own contexts
|
||||
|
||||
#### executionContext.evaluate(pageFunction, ...args)
|
||||
- `pageFunction` <[function]|[string]> Function to be evaluated in browser context
|
||||
- `...args` <...[Serializable]|[ElementHandle]> Arguments to pass to `pageFunction`
|
||||
- returns: <[Promise]<[Serializable]>> Promise which resolves to function return value
|
||||
|
||||
If the function, passed to the `executionContext.evaluate`, returns a [Promise], then `executionContext.evaluate` would wait for the promise to resolve and return its value.
|
||||
|
||||
```js
|
||||
const result = await executionContext.evaluate(() => Promise.resolve(8 * 7));
|
||||
console.log(result); // prints "56"
|
||||
```
|
||||
|
||||
A string can also be passed in instead of a function.
|
||||
|
||||
```js
|
||||
console.log(await executionContext.evaluate('1 + 2')); // prints "3"
|
||||
```
|
||||
|
||||
[JSHandle] instances can be passed as arguments to the `frame.evaluate`:
|
||||
```js
|
||||
const oneHandle = await executionContext.evaluateHandle(() => 1);
|
||||
const twoHandle = await executionContext.evaluateHandle(() => 2);
|
||||
const result = await executionContext.evaluate((a, b) => a + b, oneHandle, twoHandle);
|
||||
await oneHandle.dispose();
|
||||
await twoHandle.dispose();
|
||||
console.log(result); // prints '3'.
|
||||
```
|
||||
|
||||
#### executionContext.evaluateHandle(pageFunction, ...args)
|
||||
- `pageFunction` <[function]|[string]> Function to be evaluated in the page context
|
||||
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
|
||||
- returns: <[Promise]<[JSHandle]>> Resolves to the return value of `pageFunction`
|
||||
|
||||
If the function, passed to the `executionContext.evaluateHandle`, returns a [Promise], then `executionContext.evaluteHandle` would wait for the promise to resolve and return its value.
|
||||
|
||||
```js
|
||||
const aHandle = await context.evaluateHandle(() => Promise.resolve(self));
|
||||
aHandle; // Handle for the global object.
|
||||
```
|
||||
|
||||
A string can also be passed in instead of a function.
|
||||
|
||||
```js
|
||||
const aHandle = await context.evaluateHandle('1 + 2'); // Handle for the '3' object.
|
||||
```
|
||||
|
||||
[JSHandle] instances could be passed as arguments to the `executionContext.evaluateHandle`:
|
||||
```js
|
||||
const context = page.mainFrame().executionContext();
|
||||
const aHandle = await context.evaluateHandle(() => document.body);
|
||||
const resultHandle = await context.evaluateHandle(body => body.innerHTML, aHandle);
|
||||
console.log(await resultHandle.jsonValue()); // prints body's innerHTML
|
||||
await aHandle.dispose();
|
||||
await resultHandle.dispose();
|
||||
```
|
||||
|
||||
### class: JSHandle
|
||||
|
||||
JSHandle represents an in-page javascript object. JSHandles could be created with the [page.evaluateHandle](#pageobjectpagefunction-args) method.
|
||||
|
||||
```js
|
||||
await windowHandle = await page.evaluateHandle(() => window);
|
||||
// ...
|
||||
```
|
||||
|
||||
JSHandle prevents references javascript objects from garbage collection unless the handle is [disposed](#objecthandledispose). JSHandles are auto-disposed when their origin frame gets navigated or the parent context gets destroyed.
|
||||
|
||||
JSHandle instances can be used as arguments in [`page.$eval()`](#pageevalselector-pagefunction-args), [`page.evaluate()`](#pageevaluatepagefunction-args) and [`page.evaluateHandle`](#pageobjectpagefunction-args) methods.
|
||||
|
||||
#### jsHandle.asElement()
|
||||
- returns: <[ElementHandle]>
|
||||
|
||||
Returns either `null` or the object handle itself, if the object handle is an instance of [ElementHandle].
|
||||
|
||||
#### jsHandle.dispose()
|
||||
- returns: <[Promise]> Promise which resolves when the object handle is successfully disposed.
|
||||
|
||||
The `jsHandle.dispose` method stops referencing the element handle.
|
||||
|
||||
#### jsHandle.executionContext()
|
||||
- returns: [ExecutionContext]
|
||||
|
||||
Returns execution context the handle belongs to.
|
||||
|
||||
#### jsHandle.getProperties()
|
||||
- returns: <[Promise]<[Map]<[string], [JSHandle]>>>
|
||||
|
||||
The method returns a map with property names as keys and JSHandle instances for the property values.
|
||||
|
||||
```js
|
||||
const handle = await page.evaluateHandle(() => {window, document});
|
||||
const properties = await handle.getProperties();
|
||||
const windowHandle = properties.get('window');
|
||||
const documentHandle = properties.get('document');
|
||||
await handle.dispose();
|
||||
```
|
||||
|
||||
#### jsHandle.getProperty(propertyName)
|
||||
- `propertyName` <[string]> property to get
|
||||
- returns: <[Promise]<[JSHandle]>>
|
||||
|
||||
Fetches a single property from the referenced object.
|
||||
|
||||
#### jsHandle.jsonValue()
|
||||
- returns: <[Promise]<[Object]>>
|
||||
|
||||
Returns a JSON representation of the object. The JSON is generated by running [`JSON.stringify`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) on the object in page and consequent [`JSON.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) in puppeteer.
|
||||
|
||||
> **NOTE** The method will throw if the referenced object is not stringifiable.
|
||||
|
||||
#### jsHandle.toString()
|
||||
- returns: <[string]>
|
||||
|
||||
### class: ElementHandle
|
||||
|
||||
> **NOTE** Class [ElementHandle] extends [JSHandle].
|
||||
|
||||
ElementHandle represents an in-page DOM element. ElementHandles could be created with the [page.$](#pageselector) method.
|
||||
|
||||
```js
|
||||
@ -1334,6 +1506,9 @@ ElementHandle prevents DOM element from garbage collection unless the handle is
|
||||
|
||||
ElementHandle instances can be used as arguments in [`page.$eval()`](#pageevalselector-pagefunction-args) and [`page.evaluate()`](#pageevaluatepagefunction-args) methods.
|
||||
|
||||
#### elementHandle.asElement()
|
||||
- returns: <[ElementHandle]>
|
||||
|
||||
#### elementHandle.click([options])
|
||||
- `options` <[Object]>
|
||||
- `button` <[string]> `left`, `right`, or `middle`, defaults to `left`.
|
||||
@ -1349,18 +1524,54 @@ If the element is detached from DOM, the method throws an error.
|
||||
|
||||
The `elementHandle.dispose` method stops referencing the element handle.
|
||||
|
||||
#### elementHandle.executionContext()
|
||||
- returns: [ExecutionContext]
|
||||
|
||||
#### elementHandle.getProperties()
|
||||
- returns: <[Promise]<[Map]<[string], [JSHandle]>>>
|
||||
|
||||
The method returns a map with property names as keys and JSHandle instances for the property values.
|
||||
|
||||
```js
|
||||
const listHandle = await page.evaluateHandle(() => document.body.children);
|
||||
const properties = await containerHandle.getProperties();
|
||||
const children = [];
|
||||
for (const property of properties.values()) {
|
||||
const element = property.asElement();
|
||||
if (element)
|
||||
children.push(element);
|
||||
}
|
||||
children; // holds elementHandles to all children of document.body
|
||||
```
|
||||
|
||||
#### elementHandle.getProperty(propertyName)
|
||||
- `propertyName` <[string]> property to get
|
||||
- returns: <[Promise]<[JSHandle]>>
|
||||
|
||||
Fetches a single property from the objectHandle.
|
||||
|
||||
#### 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.jsonValue()
|
||||
- returns: <[Promise]<[Object]>>
|
||||
|
||||
Returns a JSON representation of the object. The JSON is generated by running [`JSON.stringify`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) on the object in page and consequent [`JSON.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) in puppeteer.
|
||||
|
||||
> **NOTE** The method will throw if the referenced object is not stringifiable.
|
||||
|
||||
#### elementHandle.tap()
|
||||
- returns: <[Promise]> Promise which resolves when the element is successfully tapped. Promise gets rejected if the element is detached from DOM.
|
||||
|
||||
This method scrolls element into view if needed, and then uses [touchscreen.tap](#touchscreentapx-y) to tap in the center of the element.
|
||||
If the element is detached from DOM, the method throws an error.
|
||||
|
||||
#### elementHandle.toString()
|
||||
- returns: <[string]>
|
||||
|
||||
#### elementHandle.uploadFile(...filePaths)
|
||||
- `...filePaths` <...[string]> Sets the value of the file input these paths. If some of the `filePaths` are relative paths, then they are resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd).
|
||||
- returns: <[Promise]>
|
||||
@ -1479,6 +1690,8 @@ Contains the URL of the response.
|
||||
[Element]: https://developer.mozilla.org/en-US/docs/Web/API/element "Element"
|
||||
[Keyboard]: #class-keyboard "Keyboard"
|
||||
[Dialog]: #class-dialog "Dialog"
|
||||
[JSHandle]: #class-objecthandle "JSHandle"
|
||||
[ExecutionContext]: #class-executioncontext "ExecutionContext"
|
||||
[Mouse]: #class-mouse "Mouse"
|
||||
[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"
|
||||
|
@ -14,55 +14,50 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
const path = require('path');
|
||||
const {JSHandle} = require('./ExecutionContext');
|
||||
const {helper} = require('./helper');
|
||||
|
||||
class ElementHandle {
|
||||
class ElementHandle extends JSHandle {
|
||||
/**
|
||||
* @param {!Frame} frame
|
||||
* @param {!Connection} client
|
||||
* @param {!ExecutionContext} context
|
||||
* @param {!Session} client
|
||||
* @param {!Object} remoteObject
|
||||
* @param {!Mouse} mouse
|
||||
* @param {!Touchscreen} touchscreen;
|
||||
*/
|
||||
constructor(frame, client, remoteObject, mouse, touchscreen) {
|
||||
this._frame = frame;
|
||||
this._client = client;
|
||||
this._remoteObject = remoteObject;
|
||||
constructor(context, client, remoteObject, mouse, touchscreen) {
|
||||
super(context, client, remoteObject);
|
||||
this._mouse = mouse;
|
||||
this._touchscreen = touchscreen;
|
||||
this._disposed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {?string}
|
||||
* @override
|
||||
* @return {?ElementHandle}
|
||||
*/
|
||||
_remoteObjectId() {
|
||||
return this._disposed ? null : this._remoteObject.objectId;
|
||||
}
|
||||
|
||||
async dispose() {
|
||||
if (this._disposed)
|
||||
return;
|
||||
this._disposed = true;
|
||||
await helper.releaseObject(this._client, this._remoteObject);
|
||||
asElement() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Promise<{x: number, y: number}>}
|
||||
*/
|
||||
async _visibleCenter() {
|
||||
const center = await this._frame.evaluate(element => {
|
||||
const {center, error} = await this.executionContext().evaluate(element => {
|
||||
if (!element.ownerDocument.contains(element))
|
||||
return null;
|
||||
return {center: null, error: 'Node is detached from document'};
|
||||
if (element.nodeType !== HTMLElement.ELEMENT_NODE)
|
||||
return {center: null, error: 'Node is not of type HTMLElement'};
|
||||
element.scrollIntoViewIfNeeded();
|
||||
const rect = element.getBoundingClientRect();
|
||||
return {
|
||||
const center = {
|
||||
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
|
||||
};
|
||||
return {center, error: null};
|
||||
}, this);
|
||||
if (!center)
|
||||
throw new Error('No node found for selector: ' + selector);
|
||||
if (error)
|
||||
throw new Error(error);
|
||||
return center;
|
||||
}
|
||||
|
||||
|
192
lib/ExecutionContext.js
Normal file
192
lib/ExecutionContext.js
Normal file
@ -0,0 +1,192 @@
|
||||
/**
|
||||
* 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 ExecutionContext {
|
||||
/**
|
||||
* @param {!Session} client
|
||||
* @param {string} contextId
|
||||
* @param {function(*):!JSHandle} objectHandleFactory
|
||||
*/
|
||||
constructor(client, contextId, objectHandleFactory) {
|
||||
this._client = client;
|
||||
this._contextId = contextId;
|
||||
this._objectHandleFactory = objectHandleFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function()|string} pageFunction
|
||||
* @param {!Array<*>} args
|
||||
* @return {!Promise<(!Object|undefined)>}
|
||||
*/
|
||||
async evaluate(pageFunction, ...args) {
|
||||
const handle = await this.evaluateHandle(pageFunction, ...args);
|
||||
const result = await handle.jsonValue();
|
||||
await handle.dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function()|string} pageFunction
|
||||
* @param {!Array<*>} args
|
||||
* @return {!Promise<!JSHandle>}
|
||||
*/
|
||||
async evaluateHandle(pageFunction, ...args) {
|
||||
if (helper.isString(pageFunction)) {
|
||||
const contextId = this._contextId;
|
||||
const expression = pageFunction;
|
||||
const { 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 this._objectHandleFactory(remoteObject);
|
||||
}
|
||||
|
||||
const { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.callFunctionOn', {
|
||||
functionDeclaration: pageFunction.toString(),
|
||||
executionContextId: this._contextId,
|
||||
arguments: args.map(convertArgument.bind(this)),
|
||||
returnByValue: false,
|
||||
awaitPromise: true
|
||||
});
|
||||
if (exceptionDetails)
|
||||
throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails));
|
||||
return this._objectHandleFactory(remoteObject);
|
||||
|
||||
/**
|
||||
* @param {*} arg
|
||||
* @return {*}
|
||||
* @this {Frame}
|
||||
*/
|
||||
function convertArgument(arg) {
|
||||
if (Object.is(arg, -0))
|
||||
return { unserializableValue: '-0' };
|
||||
if (Object.is(arg, Infinity))
|
||||
return { unserializableValue: 'Infinity' };
|
||||
if (Object.is(arg, -Infinity))
|
||||
return { unserializableValue: '-Infinity' };
|
||||
if (Object.is(arg, NaN))
|
||||
return { unserializableValue: 'NaN' };
|
||||
const objectHandle = arg && (arg instanceof JSHandle) ? arg : null;
|
||||
if (objectHandle) {
|
||||
if (objectHandle._context !== this)
|
||||
throw new Error('JSHandles can be evaluated only in the context they were created!');
|
||||
if (objectHandle._disposed)
|
||||
throw new Error('JSHandle is disposed!');
|
||||
if (objectHandle._remoteObject.unserializableValue)
|
||||
return { unserializableValue: objectHandle._remoteObject.unserializableValue };
|
||||
if (!objectHandle._remoteObject.objectId)
|
||||
return { value: objectHandle._remoteObject.value };
|
||||
return { objectId: objectHandle._remoteObject.objectId };
|
||||
}
|
||||
return { value: arg };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class JSHandle {
|
||||
/**
|
||||
* @param {!ExecutionContext} context
|
||||
* @param {!Session} client
|
||||
* @param {!Object} remoteObject
|
||||
*/
|
||||
constructor(context, client, remoteObject) {
|
||||
this._context = context;
|
||||
this._client = client;
|
||||
this._remoteObject = remoteObject;
|
||||
this._disposed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!ExecutionContext}
|
||||
*/
|
||||
executionContext() {
|
||||
return this._context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} propertyName
|
||||
* @return {!Promise<?JSHandle>}
|
||||
*/
|
||||
async getProperty(propertyName) {
|
||||
const objectHandle = await this._context.evaluateHandle((object, propertyName) => {
|
||||
const result = {__proto__: null};
|
||||
result[propertyName] = object[propertyName];
|
||||
return result;
|
||||
}, this, propertyName);
|
||||
const properties = await objectHandle.getProperties();
|
||||
const result = properties.get(propertyName) || null;
|
||||
await objectHandle.dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Property<Map<string, !ObjectHandle>>}
|
||||
*/
|
||||
async getProperties() {
|
||||
const response = await this._client.send('Runtime.getProperties', {
|
||||
objectId: this._remoteObject.objectId,
|
||||
ownProperties: true
|
||||
});
|
||||
const result = new Map();
|
||||
for (const property of response.result) {
|
||||
if (!property.enumerable)
|
||||
continue;
|
||||
result.set(property.name, this._context._objectHandleFactory(property.value));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Promise<?Object>}
|
||||
*/
|
||||
async jsonValue() {
|
||||
if (this._remoteObject.objectId) {
|
||||
const jsonString = await this._context.evaluate(object => JSON.stringify(object), this);
|
||||
return JSON.parse(jsonString);
|
||||
}
|
||||
return helper.valueFromRemoteObject(this._remoteObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {?ElementHandle}
|
||||
*/
|
||||
asElement() {
|
||||
return null;
|
||||
}
|
||||
|
||||
async dispose() {
|
||||
if (this._disposed)
|
||||
return;
|
||||
this._disposed = true;
|
||||
await helper.releaseObject(this._client, this._remoteObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @return {string}
|
||||
*/
|
||||
toString() {
|
||||
if (this._remoteObject.objectId) {
|
||||
const type = this._remoteObject.subtype || this._remoteObject.type;
|
||||
return 'JSHandle@' + type;
|
||||
}
|
||||
return helper.valueFromRemoteObject(this._remoteObject) + '';
|
||||
}
|
||||
}
|
||||
|
||||
helper.tracePublicAPI(JSHandle);
|
||||
module.exports = {ExecutionContext, JSHandle};
|
@ -17,6 +17,7 @@
|
||||
const fs = require('fs');
|
||||
const EventEmitter = require('events');
|
||||
const {helper} = require('./helper');
|
||||
const {ExecutionContext, JSHandle} = require('./ExecutionContext');
|
||||
const ElementHandle = require('./ElementHandle');
|
||||
|
||||
class FrameManager extends EventEmitter {
|
||||
@ -33,6 +34,8 @@ class FrameManager extends EventEmitter {
|
||||
this._touchscreen = touchscreen;
|
||||
/** @type {!Map<string, !Frame>} */
|
||||
this._frames = new Map();
|
||||
/** @type {!Map<string, !ExecutionContext>} */
|
||||
this._contextIdToContext = new Map();
|
||||
|
||||
this._client.on('Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId));
|
||||
this._client.on('Page.frameNavigated', event => this._onFrameNavigated(event.frame));
|
||||
@ -112,16 +115,36 @@ class FrameManager extends EventEmitter {
|
||||
this._removeFramesRecursively(frame);
|
||||
}
|
||||
|
||||
_onExecutionContextCreated(context) {
|
||||
const frameId = context.auxData && context.auxData.isDefault ? context.auxData.frameId : null;
|
||||
_onExecutionContextCreated(contextPayload) {
|
||||
const context = new ExecutionContext(this._client, contextPayload.id, this.createJSHandle.bind(this, contextPayload.id));
|
||||
this._contextIdToContext.set(contextPayload.id, context);
|
||||
|
||||
const frameId = contextPayload.auxData && contextPayload.auxData.isDefault ? contextPayload.auxData.frameId : null;
|
||||
const frame = this._frames.get(frameId);
|
||||
if (!frame)
|
||||
return;
|
||||
frame._defaultContextId = context.id;
|
||||
frame._context = context;
|
||||
for (const waitTask of frame._waitTasks)
|
||||
waitTask.rerun();
|
||||
}
|
||||
|
||||
_onExecutionContextDestroyed(contextPayload) {
|
||||
this._contextIdToContext.delete(contextPayload.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} contextId
|
||||
* @param {*} remoteObject
|
||||
* @return {!JSHandle}
|
||||
*/
|
||||
createJSHandle(contextId, remoteObject) {
|
||||
const context = this._contextIdToContext.get(contextId);
|
||||
console.assert(context, 'INTERNAL ERROR: missing context with id = ' + contextId);
|
||||
if (remoteObject.subtype === 'node')
|
||||
return new ElementHandle(context, this._client, remoteObject, this._mouse, this._touchscreen);
|
||||
return new JSHandle(context, this._client, remoteObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Frame} frame
|
||||
*/
|
||||
@ -167,7 +190,7 @@ class Frame {
|
||||
this._parentFrame = parentFrame;
|
||||
this._url = '';
|
||||
this._id = frameId;
|
||||
this._defaultContextId = '<not-initialized>';
|
||||
this._context = null;
|
||||
/** @type {!Set<!WaitTask>} */
|
||||
this._waitTasks = new Set();
|
||||
|
||||
@ -177,14 +200,20 @@ class Frame {
|
||||
this._parentFrame._childFrames.add(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!ExecutionContext}
|
||||
*/
|
||||
executionContext() {
|
||||
return this._context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function()|string} pageFunction
|
||||
* @param {!Array<*>} args
|
||||
* @return {!Promise<(!Object|undefined)>}
|
||||
*/
|
||||
async evaluate(pageFunction, ...args) {
|
||||
const remoteObject = await this._rawEvaluate(pageFunction, ...args);
|
||||
return await helper.serializeRemoteObject(this._client, remoteObject);
|
||||
return this._context.evaluate(pageFunction, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -192,10 +221,11 @@ class Frame {
|
||||
* @return {!Promise<?ElementHandle>}
|
||||
*/
|
||||
async $(selector) {
|
||||
const remoteObject = await this._rawEvaluate(selector => document.querySelector(selector), selector);
|
||||
if (remoteObject.subtype === 'node')
|
||||
return new ElementHandle(this, this._client, remoteObject, this._mouse, this._touchscreen);
|
||||
await helper.releaseObject(this._client, remoteObject);
|
||||
const handle = await this._context.evaluateHandle(selector => document.querySelector(selector), selector);
|
||||
const element = handle.asElement();
|
||||
if (element)
|
||||
return element;
|
||||
await handle.dispose();
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -220,76 +250,18 @@ class Frame {
|
||||
* @return {!Promise<!Array<!ElementHandle>>}
|
||||
*/
|
||||
async $$(selector) {
|
||||
const remoteObject = await this._rawEvaluate(selector => Array.from(document.querySelectorAll(selector)), selector);
|
||||
const response = await this._client.send('Runtime.getProperties', {
|
||||
objectId: remoteObject.objectId,
|
||||
ownProperties: true
|
||||
});
|
||||
const properties = response.result;
|
||||
const arrayHandle = await this._context.evaluateHandle(selector => document.querySelectorAll(selector), selector);
|
||||
const properties = await arrayHandle.getProperties();
|
||||
await arrayHandle.dispose();
|
||||
const result = [];
|
||||
const releasePromises = [helper.releaseObject(this._client, remoteObject)];
|
||||
for (const property of properties) {
|
||||
if (property.enumerable && property.value.subtype === 'node')
|
||||
result.push(new ElementHandle(this, this._client, property.value, this._mouse, this._touchscreen));
|
||||
else
|
||||
releasePromises.push(helper.releaseObject(this._client, property.value));
|
||||
for (const property of properties.values()) {
|
||||
const elementHandle = property.asElement();
|
||||
if (elementHandle)
|
||||
result.push(elementHandle);
|
||||
}
|
||||
await Promise.all(releasePromises);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function()|string} pageFunction
|
||||
* @param {!Array<*>} args
|
||||
* @return {!Promise<(!Object|undefined)>}
|
||||
*/
|
||||
async _rawEvaluate(pageFunction, ...args) {
|
||||
if (helper.isString(pageFunction)) {
|
||||
const contextId = this._defaultContextId;
|
||||
const expression = pageFunction;
|
||||
const { 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 remoteObject;
|
||||
}
|
||||
|
||||
const { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.callFunctionOn', {
|
||||
functionDeclaration: pageFunction.toString(),
|
||||
executionContextId: this._defaultContextId,
|
||||
arguments: args.map(convertArgument.bind(this)),
|
||||
returnByValue: false,
|
||||
awaitPromise: true
|
||||
});
|
||||
if (exceptionDetails)
|
||||
throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails));
|
||||
return remoteObject;
|
||||
|
||||
/**
|
||||
* @param {*} arg
|
||||
* @return {*}
|
||||
* @this {Frame}
|
||||
*/
|
||||
function convertArgument(arg) {
|
||||
if (Object.is(arg, -0))
|
||||
return { unserializableValue: '-0' };
|
||||
if (Object.is(arg, Infinity))
|
||||
return { unserializableValue: 'Infinity' };
|
||||
if (Object.is(arg, -Infinity))
|
||||
return { unserializableValue: '-Infinity' };
|
||||
if (Object.is(arg, NaN))
|
||||
return { unserializableValue: 'NaN' };
|
||||
if (arg instanceof ElementHandle) {
|
||||
if (arg._frame !== this)
|
||||
throw new Error('ElementHandles passed as arguments should belong to the frame that does evaluation');
|
||||
const objectId = arg._remoteObjectId();
|
||||
if (!objectId)
|
||||
throw new Error('ElementHandle is disposed!');
|
||||
return { objectId };
|
||||
}
|
||||
return { value: arg };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
|
10
lib/Page.js
10
lib/Page.js
@ -166,6 +166,16 @@ class Page extends EventEmitter {
|
||||
return this.mainFrame().$(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} selector
|
||||
* @param {function()|string} pageFunction
|
||||
* @param {!Array<*>} args
|
||||
* @return {!Promise<!JSHandle>}
|
||||
*/
|
||||
async evaluateHandle(pageFunction, ...args) {
|
||||
return this.mainFrame().executionContext().evaluateHandle(pageFunction, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} selector
|
||||
* @param {function()|string} pageFunction
|
||||
|
@ -64,7 +64,8 @@ class Helper {
|
||||
* @param {!Object} remoteObject
|
||||
* @return {!Promise<!Object>}
|
||||
*/
|
||||
static async serializeRemoteObject(client, remoteObject) {
|
||||
static valueFromRemoteObject(remoteObject) {
|
||||
console.assert(!remoteObject.objectId, 'Cannot extract value when objectId is given');
|
||||
if (remoteObject.unserializableValue) {
|
||||
switch (remoteObject.unserializableValue) {
|
||||
case '-0':
|
||||
@ -79,8 +80,17 @@ class Helper {
|
||||
throw new Error('Unsupported unserializable value: ' + remoteObject.unserializableValue);
|
||||
}
|
||||
}
|
||||
return remoteObject.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Session} client
|
||||
* @param {!Object} remoteObject
|
||||
* @return {!Promise<!Object>}
|
||||
*/
|
||||
static async serializeRemoteObject(client, remoteObject) {
|
||||
if (!remoteObject.objectId)
|
||||
return remoteObject.value;
|
||||
return Helper.valueFromRemoteObject(remoteObject);
|
||||
if (remoteObject.subtype === 'promise')
|
||||
return remoteObject.description;
|
||||
try {
|
||||
|
162
test/test.js
162
test/test.js
@ -265,9 +265,10 @@ describe('Page', function() {
|
||||
const result = await page.evaluate((a, b) => Object.is(a, undefined) && Object.is(b, 'foo'), undefined, 'foo');
|
||||
expect(result).toBe(true);
|
||||
}));
|
||||
it('should not fail for window object', SX(async function() {
|
||||
const result = await page.evaluate(() => window);
|
||||
expect(result).toBe('Window');
|
||||
it('should fail for window object', SX(async function() {
|
||||
let error = null;
|
||||
await page.evaluate(() => window).catch(e => error = e);
|
||||
expect(error.message).toContain('Converting circular structure to JSON');
|
||||
}));
|
||||
it('should accept a string', SX(async function() {
|
||||
const result = await page.evaluate('1 + 2');
|
||||
@ -294,7 +295,7 @@ describe('Page', function() {
|
||||
await element.dispose();
|
||||
let error = null;
|
||||
await page.evaluate(e => e.textContent, element).catch(e => error = e);
|
||||
expect(error.message).toContain('ElementHandle is disposed');
|
||||
expect(error.message).toContain('JSHandle is disposed');
|
||||
}));
|
||||
it('should throw if elementHandles are from other frames', SX(async function() {
|
||||
const FrameUtils = require('./frame-utils');
|
||||
@ -303,7 +304,117 @@ describe('Page', function() {
|
||||
let error = null;
|
||||
await page.evaluate(body => body.innerHTML, bodyHandle).catch(e => error = e);
|
||||
expect(error).toBeTruthy();
|
||||
expect(error.message).toContain('ElementHandles passed as arguments should belong');
|
||||
expect(error.message).toContain('JSHandles can be evaluated only in the context they were created');
|
||||
}));
|
||||
it('should accept object handle as an argument', SX(async function() {
|
||||
const navigatorHandle = await page.evaluateHandle(() => navigator);
|
||||
const text = await page.evaluate(e => e.userAgent, navigatorHandle);
|
||||
expect(text).toContain('Mozilla');
|
||||
}));
|
||||
it('should accept object handle to primitive types', SX(async function() {
|
||||
const aHandle = await page.evaluateHandle(() => 5);
|
||||
const isFive = await page.evaluate(e => Object.is(e, 5), aHandle);
|
||||
expect(isFive).toBeTruthy();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Page.evaluateHandle', function() {
|
||||
it('should work', SX(async function() {
|
||||
const windowHandle = await page.evaluateHandle(() => window);
|
||||
expect(windowHandle).toBeTruthy();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('JSHandle.getProperty', function() {
|
||||
it('should work', SX(async function() {
|
||||
const aHandle = await page.evaluateHandle(() => ({
|
||||
one: 1,
|
||||
two: 2,
|
||||
three: 3
|
||||
}));
|
||||
const twoHandle = await aHandle.getProperty('two');
|
||||
expect(await twoHandle.jsonValue()).toEqual(2);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('JSHandle.jsonValue', function() {
|
||||
it('should work', SX(async function() {
|
||||
const aHandle = await page.evaluateHandle(() => ({foo: 'bar'}));
|
||||
const json = await aHandle.jsonValue();
|
||||
expect(json).toEqual({foo: 'bar'});
|
||||
}));
|
||||
it('should work with dates', SX(async function() {
|
||||
const dateHandle = await page.evaluateHandle(() => new Date('2017-09-26T00:00:00.000Z'));
|
||||
const json = await dateHandle.jsonValue();
|
||||
expect(json).toBe('2017-09-26T00:00:00.000Z');
|
||||
}));
|
||||
it('should throw for circular objects', SX(async function() {
|
||||
const windowHandle = await page.evaluateHandle('window');
|
||||
let error = null;
|
||||
await windowHandle.jsonValue().catch(e => error = e);
|
||||
expect(error.message).toContain('Converting circular structure to JSON');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('JSHandle.getProperties', function() {
|
||||
it('should work', SX(async function() {
|
||||
const aHandle = await page.evaluateHandle(() => ({
|
||||
foo: 'bar'
|
||||
}));
|
||||
const properties = await aHandle.getProperties();
|
||||
const foo = properties.get('foo');
|
||||
expect(foo).toBeTruthy();
|
||||
expect(await foo.jsonValue()).toBe('bar');
|
||||
}));
|
||||
it('should return even non-own properties', SX(async function() {
|
||||
const aHandle = await page.evaluateHandle(() => {
|
||||
class A {
|
||||
constructor() {
|
||||
this.a = '1';
|
||||
}
|
||||
}
|
||||
class B extends A {
|
||||
constructor() {
|
||||
super();
|
||||
this.b = '2';
|
||||
}
|
||||
}
|
||||
return new B();
|
||||
});
|
||||
const properties = await aHandle.getProperties();
|
||||
expect(await properties.get('a').jsonValue()).toBe('1');
|
||||
expect(await properties.get('b').jsonValue()).toBe('2');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('JSHandle.asElement', function() {
|
||||
it('should work', SX(async function() {
|
||||
const aHandle = await page.evaluateHandle(() => document.body);
|
||||
const element = aHandle.asElement();
|
||||
expect(element).toBeTruthy();
|
||||
}));
|
||||
it('should return null for non-elements', SX(async function() {
|
||||
const aHandle = await page.evaluateHandle(() => 2);
|
||||
const element = aHandle.asElement();
|
||||
expect(element).toBeFalsy();
|
||||
}));
|
||||
it('should return ElementHandle for TextNodes', SX(async function() {
|
||||
await page.setContent('<div>ee!</div>');
|
||||
const aHandle = await page.evaluateHandle(() => document.querySelector('div').firstChild);
|
||||
const element = aHandle.asElement();
|
||||
expect(element).toBeTruthy();
|
||||
expect(await page.evaluate(e => e.nodeType === HTMLElement.TEXT_NODE, element));
|
||||
}));
|
||||
});
|
||||
|
||||
describe('JSHandle.toString', function() {
|
||||
it('should work for primitives', SX(async function() {
|
||||
const aHandle = await page.evaluateHandle(() => 2);
|
||||
expect(aHandle.toString()).toBe('2');
|
||||
}));
|
||||
it('should work for complicated objects', SX(async function() {
|
||||
const aHandle = await page.evaluateHandle(() => window);
|
||||
expect(aHandle.toString()).toBe('JSHandle@object');
|
||||
}));
|
||||
});
|
||||
|
||||
@ -322,6 +433,30 @@ describe('Page', function() {
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Frame.context', function() {
|
||||
const FrameUtils = require('./frame-utils');
|
||||
it('should work', SX(async function() {
|
||||
await page.goto(EMPTY_PAGE);
|
||||
await FrameUtils.attachFrame(page, 'frame1', EMPTY_PAGE);
|
||||
expect(page.frames().length).toBe(2);
|
||||
const [frame1, frame2] = page.frames();
|
||||
expect(frame1.executionContext()).toBeTruthy();
|
||||
expect(frame2.executionContext()).toBeTruthy();
|
||||
expect(frame1.executionContext() !== frame2.executionContext()).toBeTruthy();
|
||||
|
||||
await Promise.all([
|
||||
frame1.executionContext().evaluate(() => window.a = 1),
|
||||
frame2.executionContext().evaluate(() => window.a = 2)
|
||||
]);
|
||||
const [a1, a2] = await Promise.all([
|
||||
frame1.executionContext().evaluate(() => window.a),
|
||||
frame2.executionContext().evaluate(() => window.a)
|
||||
]);
|
||||
expect(a1).toBe(1);
|
||||
expect(a2).toBe(2);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Frame.evaluate', function() {
|
||||
const FrameUtils = require('./frame-utils');
|
||||
it('should have different execution contexts', SX(async function() {
|
||||
@ -1241,9 +1376,24 @@ describe('Page', function() {
|
||||
it('should work', SX(async function() {
|
||||
await page.goto(PREFIX + '/input/button.html');
|
||||
const button = await page.$('button');
|
||||
await button.click('button');
|
||||
await button.click();
|
||||
expect(await page.evaluate(() => result)).toBe('Clicked');
|
||||
}));
|
||||
it('should work for TextNodes', SX(async function() {
|
||||
await page.goto(PREFIX + '/input/button.html');
|
||||
const buttonTextNode = await page.evaluateHandle(() => document.querySelector('button').firstChild);
|
||||
let error = null;
|
||||
await buttonTextNode.click().catch(err => error = err);
|
||||
expect(error.message).toBe('Node is not of type HTMLElement');
|
||||
}));
|
||||
it('should throw for detached nodes', SX(async function() {
|
||||
await page.goto(PREFIX + '/input/button.html');
|
||||
const button = await page.$('button');
|
||||
await page.evaluate(button => button.remove(), button);
|
||||
let error = null;
|
||||
await button.click().catch(err => error = err);
|
||||
expect(error.message).toBe('Node is detached from document');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('ElementHandle.hover', function() {
|
||||
|
@ -59,8 +59,8 @@ class MDOutline {
|
||||
let actualText = element.firstChild.textContent;
|
||||
let angleIndex = actualText.indexOf('<');
|
||||
let spaceIndex = actualText.indexOf(' ');
|
||||
angleIndex = angleIndex === -1 ? angleText.length : angleIndex;
|
||||
spaceIndex = spaceIndex === -1 ? spaceIndex.length : spaceIndex + 1;
|
||||
angleIndex = angleIndex === -1 ? actualText.length : angleIndex;
|
||||
spaceIndex = spaceIndex === -1 ? actualText.length : spaceIndex + 1;
|
||||
actualText = actualText.substring(0, Math.min(angleIndex, spaceIndex));
|
||||
if (actualText !== expectedText)
|
||||
errors.push(`${member.name} has mistyped 'return' type declaration: expected exactly '${expectedText}', found '${actualText}'.`);
|
||||
|
Loading…
Reference in New Issue
Block a user