mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
feat(api): add element.select and element.evaluate for consistency (#4892)
This commit is contained in:
parent
135bb424ba
commit
73fd7ff822
73
docs/api.md
73
docs/api.md
@ -237,6 +237,8 @@
|
||||
- [class: JSHandle](#class-jshandle)
|
||||
* [jsHandle.asElement()](#jshandleaselement)
|
||||
* [jsHandle.dispose()](#jshandledispose)
|
||||
* [jsHandle.evaluate(pageFunction[, ...args])](#jshandleevaluatepagefunction-args)
|
||||
* [jsHandle.evaluateHandle(pageFunction[, ...args])](#jshandleevaluatehandlepagefunction-args)
|
||||
* [jsHandle.executionContext()](#jshandleexecutioncontext)
|
||||
* [jsHandle.getProperties()](#jshandlegetproperties)
|
||||
* [jsHandle.getProperty(propertyName)](#jshandlegetpropertypropertyname)
|
||||
@ -253,6 +255,8 @@
|
||||
* [elementHandle.click([options])](#elementhandleclickoptions)
|
||||
* [elementHandle.contentFrame()](#elementhandlecontentframe)
|
||||
* [elementHandle.dispose()](#elementhandledispose)
|
||||
* [elementHandle.evaluate(pageFunction[, ...args])](#elementhandleevaluatepagefunction-args)
|
||||
* [elementHandle.evaluateHandle(pageFunction[, ...args])](#elementhandleevaluatehandlepagefunction-args)
|
||||
* [elementHandle.executionContext()](#elementhandleexecutioncontext)
|
||||
* [elementHandle.focus()](#elementhandlefocus)
|
||||
* [elementHandle.getProperties()](#elementhandlegetproperties)
|
||||
@ -262,6 +266,7 @@
|
||||
* [elementHandle.jsonValue()](#elementhandlejsonvalue)
|
||||
* [elementHandle.press(key[, options])](#elementhandlepresskey-options)
|
||||
* [elementHandle.screenshot([options])](#elementhandlescreenshotoptions)
|
||||
* [elementHandle.select(...values)](#elementhandleselectvalues)
|
||||
* [elementHandle.tap()](#elementhandletap)
|
||||
* [elementHandle.toString()](#elementhandletostring)
|
||||
* [elementHandle.type(text[, options])](#elementhandletypetext-options)
|
||||
@ -3030,6 +3035,34 @@ Returns either `null` or the object handle itself, if the object handle is an in
|
||||
|
||||
The `jsHandle.dispose` method stops referencing the element handle.
|
||||
|
||||
#### jsHandle.evaluate(pageFunction[, ...args])
|
||||
- `pageFunction` <[function]\([Object]\)> Function to be evaluated in browser context
|
||||
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
|
||||
- returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction`
|
||||
|
||||
This method passes this handle as the first argument to `pageFunction`.
|
||||
|
||||
If `pageFunction` returns a [Promise], then `handle.evaluate` would wait for the promise to resolve and return its value.
|
||||
|
||||
Examples:
|
||||
```js
|
||||
const tweetHandle = await page.$('.tweet .retweets');
|
||||
expect(await tweetHandle.evaluate(node => node.innerText)).toBe('10');
|
||||
```
|
||||
|
||||
#### jsHandle.evaluateHandle(pageFunction[, ...args])
|
||||
- `pageFunction` <[function]|[string]> Function to be evaluated
|
||||
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
|
||||
- returns: <[Promise]<[JSHandle]>> Promise which resolves to the return value of `pageFunction` as in-page object (JSHandle)
|
||||
|
||||
This method passes this handle as the first argument to `pageFunction`.
|
||||
|
||||
The only difference between `jsHandle.evaluate` and `jsHandle.evaluateHandle` is that `executionContext.evaluateHandle` returns in-page object (JSHandle).
|
||||
|
||||
If the function passed to the `jsHandle.evaluateHandle` returns a [Promise], then `jsHandle.evaluateHandle` would wait for the promise to resolve and return its value.
|
||||
|
||||
See [Page.evaluateHandle](#pageevaluatehandlepagefunction-args) for more details.
|
||||
|
||||
#### jsHandle.executionContext()
|
||||
- returns: <[ExecutionContext]>
|
||||
|
||||
@ -3190,6 +3223,34 @@ If the element is detached from DOM, the method throws an error.
|
||||
|
||||
The `elementHandle.dispose` method stops referencing the element handle.
|
||||
|
||||
#### elementHandle.evaluate(pageFunction[, ...args])
|
||||
- `pageFunction` <[function]\([Object]\)> Function to be evaluated in browser context
|
||||
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
|
||||
- returns: <[Promise]<[Serializable]>> Promise which resolves to the return value of `pageFunction`
|
||||
|
||||
This method passes this handle as the first argument to `pageFunction`.
|
||||
|
||||
If `pageFunction` returns a [Promise], then `handle.evaluate` would wait for the promise to resolve and return its value.
|
||||
|
||||
Examples:
|
||||
```js
|
||||
const tweetHandle = await page.$('.tweet .retweets');
|
||||
expect(await tweetHandle.evaluate(node => node.innerText)).toBe('10');
|
||||
```
|
||||
|
||||
#### elementHandle.evaluateHandle(pageFunction[, ...args])
|
||||
- `pageFunction` <[function]|[string]> Function to be evaluated
|
||||
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
|
||||
- returns: <[Promise]<[JSHandle]>> Promise which resolves to the return value of `pageFunction` as in-page object (JSHandle)
|
||||
|
||||
This method passes this handle as the first argument to `pageFunction`.
|
||||
|
||||
The only difference between `evaluateHandle.evaluate` and `evaluateHandle.evaluateHandle` is that `executionContext.evaluateHandle` returns in-page object (JSHandle).
|
||||
|
||||
If the function passed to the `evaluateHandle.evaluateHandle` returns a [Promise], then `evaluateHandle.evaluateHandle` would wait for the promise to resolve and return its value.
|
||||
|
||||
See [Page.evaluateHandle](#pageevaluatehandlepagefunction-args) for more details.
|
||||
|
||||
#### elementHandle.executionContext()
|
||||
- returns: <[ExecutionContext]>
|
||||
|
||||
@ -3257,6 +3318,18 @@ If `key` is a single character and no modifier keys besides `Shift` are being he
|
||||
This method scrolls element into view if needed, and then uses [page.screenshot](#pagescreenshotoptions) to take a screenshot of the element.
|
||||
If the element is detached from DOM, the method throws an error.
|
||||
|
||||
#### elementHandle.select(...values)
|
||||
- `...values` <...[string]> Values of options to select. If the `<select>` has the `multiple` attribute, all values are considered, otherwise only the first one is taken into account.
|
||||
- returns: <[Promise]<[Array]<[string]>>> An array of option values that have been successfully selected.
|
||||
|
||||
Triggers a `change` and `input` event once all the provided options have been selected.
|
||||
If there's no `<select>` element matching `selector`, the method throws an error.
|
||||
|
||||
```js
|
||||
handle.select('blue'); // single selection
|
||||
handle.select('red', 'green', 'blue'); // multiple selections
|
||||
```
|
||||
|
||||
#### elementHandle.tap()
|
||||
- returns: <[Promise]> Promise which resolves when the element is successfully tapped. Promise gets rejected if the element is detached from DOM.
|
||||
|
||||
|
@ -393,24 +393,12 @@ class DOMWorld {
|
||||
* @param {!Array<string>} values
|
||||
* @return {!Promise<!Array<string>>}
|
||||
*/
|
||||
select(selector, ...values){
|
||||
for (const value of values)
|
||||
assert(helper.isString(value), 'Values must be strings. Found value "' + value + '" of type "' + (typeof value) + '"');
|
||||
return this.$eval(selector, (element, values) => {
|
||||
if (element.nodeName.toLowerCase() !== 'select')
|
||||
throw new Error('Element is not a <select> element.');
|
||||
|
||||
const options = Array.from(element.options);
|
||||
element.value = undefined;
|
||||
for (const option of options) {
|
||||
option.selected = values.includes(option.value);
|
||||
if (option.selected && !element.multiple)
|
||||
break;
|
||||
}
|
||||
element.dispatchEvent(new Event('input', { 'bubbles': true }));
|
||||
element.dispatchEvent(new Event('change', { 'bubbles': true }));
|
||||
return options.filter(option => option.selected).map(option => option.value);
|
||||
}, values);
|
||||
async select(selector, ...values) {
|
||||
const handle = await this.$(selector);
|
||||
assert(handle, 'No node found for selector: ' + selector);
|
||||
const result = await handle.select(...values);
|
||||
await handle.dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,16 +46,34 @@ class JSHandle {
|
||||
return this._context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Function|String} pageFunction
|
||||
* @param {!Array<*>} args
|
||||
* @return {!Promise<(!Object|undefined)>}
|
||||
*/
|
||||
async evaluate(pageFunction, ...args) {
|
||||
return await this.executionContext().evaluate(pageFunction, this, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Function|string} pageFunction
|
||||
* @param {!Array<*>} args
|
||||
* @return {!Promise<!Puppeteer.JSHandle>}
|
||||
*/
|
||||
async evaluateHandle(pageFunction, ...args) {
|
||||
return await this.executionContext().evaluateHandle(pageFunction, this, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} propertyName
|
||||
* @return {!Promise<?JSHandle>}
|
||||
*/
|
||||
async getProperty(propertyName) {
|
||||
const objectHandle = await this._context.evaluateHandle((object, propertyName) => {
|
||||
const objectHandle = await this.evaluateHandle((object, propertyName) => {
|
||||
const result = {__proto__: null};
|
||||
result[propertyName] = object[propertyName];
|
||||
return result;
|
||||
}, this, propertyName);
|
||||
}, propertyName);
|
||||
const properties = await objectHandle.getProperties();
|
||||
const result = properties.get(propertyName) || null;
|
||||
await objectHandle.dispose();
|
||||
@ -160,7 +178,7 @@ class ElementHandle extends JSHandle {
|
||||
}
|
||||
|
||||
async _scrollIntoViewIfNeeded() {
|
||||
const error = await this.executionContext().evaluate(async(element, pageJavascriptEnabled) => {
|
||||
const error = await this.evaluate(async(element, pageJavascriptEnabled) => {
|
||||
if (!element.isConnected)
|
||||
return 'Node is detached from document';
|
||||
if (element.nodeType !== Node.ELEMENT_NODE)
|
||||
@ -180,7 +198,7 @@ class ElementHandle extends JSHandle {
|
||||
if (visibleRatio !== 1.0)
|
||||
element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
|
||||
return false;
|
||||
}, this, this._page._javascriptEnabled);
|
||||
}, this._page._javascriptEnabled);
|
||||
if (error)
|
||||
throw new Error(error);
|
||||
}
|
||||
@ -266,6 +284,30 @@ class ElementHandle extends JSHandle {
|
||||
await this._page.mouse.click(x, y, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Array<string>} values
|
||||
* @return {!Promise<!Array<string>>}
|
||||
*/
|
||||
async select(...values) {
|
||||
for (const value of values)
|
||||
assert(helper.isString(value), 'Values must be strings. Found value "' + value + '" of type "' + (typeof value) + '"');
|
||||
return this.evaluate((element, values) => {
|
||||
if (element.nodeName.toLowerCase() !== 'select')
|
||||
throw new Error('Element is not a <select> element.');
|
||||
|
||||
const options = Array.from(element.options);
|
||||
element.value = undefined;
|
||||
for (const option of options) {
|
||||
option.selected = values.includes(option.value);
|
||||
if (option.selected && !element.multiple)
|
||||
break;
|
||||
}
|
||||
element.dispatchEvent(new Event('input', { 'bubbles': true }));
|
||||
element.dispatchEvent(new Event('change', { 'bubbles': true }));
|
||||
return options.filter(option => option.selected).map(option => option.value);
|
||||
}, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Array<string>} filePaths
|
||||
*/
|
||||
@ -282,7 +324,7 @@ class ElementHandle extends JSHandle {
|
||||
}
|
||||
|
||||
async focus() {
|
||||
await this.executionContext().evaluate(element => element.focus(), this);
|
||||
await this.evaluate(element => element.focus());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -392,9 +434,9 @@ class ElementHandle extends JSHandle {
|
||||
* @return {!Promise<?ElementHandle>}
|
||||
*/
|
||||
async $(selector) {
|
||||
const handle = await this.executionContext().evaluateHandle(
|
||||
const handle = await this.evaluateHandle(
|
||||
(element, selector) => element.querySelector(selector),
|
||||
this, selector
|
||||
selector
|
||||
);
|
||||
const element = handle.asElement();
|
||||
if (element)
|
||||
@ -408,9 +450,9 @@ class ElementHandle extends JSHandle {
|
||||
* @return {!Promise<!Array<!ElementHandle>>}
|
||||
*/
|
||||
async $$(selector) {
|
||||
const arrayHandle = await this.executionContext().evaluateHandle(
|
||||
const arrayHandle = await this.evaluateHandle(
|
||||
(element, selector) => element.querySelectorAll(selector),
|
||||
this, selector
|
||||
selector
|
||||
);
|
||||
const properties = await arrayHandle.getProperties();
|
||||
await arrayHandle.dispose();
|
||||
@ -433,7 +475,7 @@ class ElementHandle extends JSHandle {
|
||||
const elementHandle = await this.$(selector);
|
||||
if (!elementHandle)
|
||||
throw new Error(`Error: failed to find element matching selector "${selector}"`);
|
||||
const result = await this.executionContext().evaluate(pageFunction, elementHandle, ...args);
|
||||
const result = await elementHandle.evaluate(pageFunction, ...args);
|
||||
await elementHandle.dispose();
|
||||
return result;
|
||||
}
|
||||
@ -445,12 +487,12 @@ class ElementHandle extends JSHandle {
|
||||
* @return {!Promise<(!Object|undefined)>}
|
||||
*/
|
||||
async $$eval(selector, pageFunction, ...args) {
|
||||
const arrayHandle = await this.executionContext().evaluateHandle(
|
||||
const arrayHandle = await this.evaluateHandle(
|
||||
(element, selector) => Array.from(element.querySelectorAll(selector)),
|
||||
this, selector
|
||||
selector
|
||||
);
|
||||
|
||||
const result = await this.executionContext().evaluate(pageFunction, arrayHandle, ...args);
|
||||
const result = await arrayHandle.evaluate(pageFunction, ...args);
|
||||
await arrayHandle.dispose();
|
||||
return result;
|
||||
}
|
||||
@ -460,7 +502,7 @@ class ElementHandle extends JSHandle {
|
||||
* @return {!Promise<!Array<!ElementHandle>>}
|
||||
*/
|
||||
async $x(expression) {
|
||||
const arrayHandle = await this.executionContext().evaluateHandle(
|
||||
const arrayHandle = await this.evaluateHandle(
|
||||
(element, expression) => {
|
||||
const document = element.ownerDocument || element;
|
||||
const iterator = document.evaluate(expression, element, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
|
||||
@ -470,7 +512,7 @@ class ElementHandle extends JSHandle {
|
||||
array.push(item);
|
||||
return array;
|
||||
},
|
||||
this, expression
|
||||
expression
|
||||
);
|
||||
const properties = await arrayHandle.getProperties();
|
||||
await arrayHandle.dispose();
|
||||
@ -487,7 +529,7 @@ class ElementHandle extends JSHandle {
|
||||
* @returns {!Promise<boolean>}
|
||||
*/
|
||||
isIntersectingViewport() {
|
||||
return this.executionContext().evaluate(async element => {
|
||||
return this.evaluate(async element => {
|
||||
const visibleRatio = await new Promise(resolve => {
|
||||
const observer = new IntersectionObserver(entries => {
|
||||
resolve(entries[0].intersectionRatio);
|
||||
@ -496,7 +538,7 @@ class ElementHandle extends JSHandle {
|
||||
observer.observe(element);
|
||||
});
|
||||
return visibleRatio > 0;
|
||||
}, this);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user