Getting children of an element in Puppeteer: element.children.length works but element.children returns undefined

Viewed 131

I have this code snippet:



    const historicalDataTable = await findElementByClass(
      "table",
      elementClass,
      page
    ); // This is a custom function I wrote. Works as expected.


    const tableBody = await historicalDataTable.$eval(
      "tbody",
      (el) => el.children.length
    );

    console.log(tableBody);

This works as expected and returns the correct amount of children. However when I do

 const tableBody = await historicalDataTable.$eval(
      "tbody",
      (el) => el.children
    );

And remove the length, it returns undefined. What is going on here?

1 Answers

el.children (Element#children) will yield an HTMLCollection which is not serializable and can't be marshalled from the page's execution context into yours, so evaluate returns undefined instead.

Now, this isn't fully obvious when looking at the elementHandle.$eval docs as the only indication is that the return value is <Promise<Serializable>>, but it becomes clear from the executionContext.evaluate docs:

[...]

If the function passed to the executionContext.evaluate returns a non-Serializable value, then executionContext.evaluate resolves to undefined. DevTools Protocol also supports transferring some additional values that are not serializable by JSON: -0, NaN, Infinity, -Infinity, and bigint literals.

(Emphasis mine.)

el.children.length (HTMLCollection#length) on the other hand is a simple number which is serializable.

You have to do whatever you want to do with those elements inside of your pageFunction and return only some serializable value.

Alternatively, you could also use elementHandle.evaluateHandle to return a JSHandle to the HTMLCollection and use that handle later in another call to an evaluate-type function. (Note that that would be the only thing you can do with it though. You couldn't access .length for example from your own execution context, only from another pageFunction1.)


1: This is not entirely true, since you could for example use jsHandle.getProperty to get another JSHandle for the length, followed by jsHandle.jsonValue to get the value as number - but both of these operations are asynchronous and probably it's a lot more efficient to write your code in such a way that you can handle all the necessary operations inside the page's execution context in the first place, without too many context switches.

Related