diff --git a/docs/api.md b/docs/api.md index 2f24f78ea2e22..226ed5880e3bd 100644 --- a/docs/api.md +++ b/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) @@ -3034,6 +3039,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]> @@ -3194,6 +3227,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]> @@ -3261,6 +3322,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 `` 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. diff --git a/lib/DOMWorld.js b/lib/DOMWorld.js index 1668fad003483..c5852cacd9faf 100644 --- a/lib/DOMWorld.js +++ b/lib/DOMWorld.js @@ -389,28 +389,16 @@ class DOMWorld { } /** - * @param {string} selector - * @param {!Array} values - * @return {!Promise>} - */ - 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 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} 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} */ 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>} */ 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>} */ 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} */ 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); + }); } }