getting property from ElementHandle

I would rather extend ElementHandle for missing methods like:

//  [email protected]
let { ElementHandle } = require( "puppeteer/lib/ExecutionContext" );
// [email protected] 
if ( ElementHandle === undefined ) {
  ElementHandle = require( "puppeteer/lib/JSHandle" ).ElementHandle;
}

/**
 * Set value on a select element
 * @param {string} value
 * @returns {Promise<Undefined>}
 */
ElementHandle.prototype.select = async function( value ) {
  await this._page.evaluateHandle( ( el, value ) => {
      const event = new Event( "change", { bubbles: true });
      event.simulated = true;
      el.querySelector( `option[value="${ value }"]` ).selected = true;
      el.dispatchEvent( event );
  }, this, value );
};

/**
 * Check if element is visible in the DOM
 * @returns {Promise<Boolean>}
 **/
ElementHandle.prototype.isVisible = async function(){
  return (await this.boundingBox() !== null);
};

/**
 * Get element attribute
 * @param {string} attr
 * @returns {Promise<String>}
 */
ElementHandle.prototype.getAttr = async function( attr ){
  const handle = await this._page.evaluateHandle( ( el, attr ) => el.getAttribute( attr ), this, attr );
  return await handle.jsonValue();
};

/**
 * Get element property
 * @param {string} prop
 * @returns {Promise<String>}
 */
ElementHandle.prototype.getProp = async function( prop ){
  const handle = await this._page.evaluateHandle( ( el, prop ) => el[ prop ], this, prop );
  return await handle.jsonValue();
};

As soon as you import this module once in you code you can play with the handles as follows:

const elh = await page.$( `#testTarget` );
console.log( await elh.isVisible() );
console.log( await elh.getAttr( "class" ) );
console.log( await elh.getProp( "innerHTML" ) );

In the accepted answer page.eval() is mentioned, however, with puppeteer such a method has never existed and I think what is really meant is in fact page.evaluate().

However, Using page.evaluate() requires you to split your operation into two parts (one for getting the element, one to select the value).

Is there any way to do this not as verbose?

In such cases, page.$eval() appears to be more appropriate as it allows you to directly pass your selector as argument, thus reducing the number of operations or variable you need to introduce:

Now in your particular case, you want to perform the $eval not just on the whole page but on an ElementHandle, which is possible since May 9, 2018 via elementHandle.$eval():

This method runs document.querySelector within the element and passes it as the first argument to pageFunction.

enter image description here

This translates to your example as follows (here using a css selector instead of xpath):

await elementHandle.$eval('/div/div/h3/a', el => el.text);

I prefer to use the eval() function so I can use less verbose code:

page.eval(() => {

    let element = document.querySelector('#mySelector')
    return element.innerText

}).then(text => {
    console.log(text)
})

You can also pass an element you previously grabbed like your ele var:

Using Promise syntax

page.eval(element => {
    return element.innerText
}, ele).then(text => {
    // Do whatever you want with text
})

Using async/await syntax

const text = await page.eval(element => element.innerText), ele) 
// Do whatever you want with text

...or write a tiny helper function.

public async GetProperty(element: ElementHandle, property: string): Promise<string> {
    return await (await element.getProperty(property)).jsonValue();
}

use:

let inner = await GetProperty(ele, 'innerHTML');