1

In typescript, I have noticed that when I take a complex object and put it in an array, when I attempt to access that object from the array it loses its type and instead simply becomes of type object.

For example

let myArray = return await this.browser.evaluate((sel: string) => Array.from(document.querySelectorAll(sel)), selector)

document.querySelectorAll(sel) returns a NodeList<Element> which is ArrayLike. Array.from should convert the NodeList into an array of elements, but once the array is formed all of the array elements lose their Element type

I have a function that will only accept parameters of type Element, but when I try to pass in myArray[0] as a parameter to said function, I get the error: Error: Unsupported target type: object

I have tried so many different things to try and get the array to maintain its object type that it would be difficult to explain each and every one of them. I am wondering how can I create an array of Elements and have them continue to be Elements when accessed later instead of generic objects

Here is a little more context in the testing I've done

I am going to this page: https://www.w3schools.com/html/html_tables.asp and the selector I am passing into evaluate is table[id="customers"] tbody tr This should match with the 6 rows that appear in the table.

let test = await this.browser.evaluate((sel: string) => 
Array.from(document.querySelectorAll(sel)), selector)
console.log('testy1: ', test)
console.log('testy2: ', test[0])
console.log('testy3: ', typeof(test[0]))

When I run the above code this is the output I get in the console log:

testy1:  [ {}, {}, {}, {}, {}, {}, {} ]
testy2:  {}
testy3:  object

It seems to be matching grabbing the elements from the page because it is correctly returning 6 elements. but maybe the issue is that the objects returned are empty? I am not sure.

I think my problem may be related to this question: puppeteer page.evaluate querySelectorAll return empty objects

but the solution to that question doesn't work for me because href isn't a property of object type Element

switch201
  • 587
  • 1
  • 4
  • 16
  • `Array.from` returns a `Element[]`. But, `this.browser.evaluate` is returning object array of some other type – adiga May 17 '19 at 14:49
  • @adiga do you happen to know how I could write that line of code such that myArray also becomes `Element[]`? – switch201 May 17 '19 at 15:08
  • @adiga According to the puppeteer documentation page.evaluate should resolve to the return value of what ever page function I am passing in, which in this case is `Array.from(....)` https://pptr.dev/#?product=Puppeteer&version=v1.11.0&show=api-pageevaluatepagefunction-args\ – switch201 May 17 '19 at 15:26
  • how about ... `((await this.browser.evaluate((sel: string) => Array.from(document.querySelectorAll(sel)), selector)) as Element[])` ? – Jacob Goh May 17 '19 at 15:47
  • @ Jacob Goh I have tried that too and it doesn't seem to work either. let me give you guys an edit with more info really quick – switch201 May 17 '19 at 15:59
  • @Sébastien Deprez please see my edit above. – switch201 May 17 '19 at 16:06

2 Answers2

1

The problem here is that the function you are passing to page.evaluate is run inside the browser context (inside the browser page). To send the results from the browser context to the Node.js environment, the results are serialized.

See the return type in the docs for page.evaluate:

returns: Promise<Serializable> Promise which resolves to the return value of pageFunction

The Serializable here means that your data will be passed to the Node.js environment via JSON.stringify and there automatically parsed for you. This process will however remove any non-serializable properties of objects. This is the reason why you end up with many empty objects.

Get element handles in puppeteer

To get an handle of an element on the page, you need to use the page.$, which creates an object (in your Node.js environment) that is linked to an element inside the browser context. These kind of handles can also be passed to page.evaluate calls. To query for multiple element, you can use the function page.$$.

Code sample

Here is an example, which first queries an element and then passes the element handle to an evaluate function to read an attribute.

const elementHandle = await page.$('a');
const result = await page.evaluate(el => el.href, elementHandle);

Usage of TypeScript

The problem regarding TypeScript is, that TypeScript is not able to predict the type correctly in this scenario. For the TypeScript compiler this looks like a normal function call while in reality, the function is send to the client to be executed. Therefore, you have to cast the type yourself in this case as otherwise Typescript will just assume any as argument type:

const elementHandle = await page.$('a');
const result = await page.evaluate((el: { href: string }) => el.href, elementHandle);
Thomas Dondorf
  • 23,416
  • 6
  • 84
  • 105
  • Thank you very much for your answer. It makes sense to me now, but when I try to implement your example, I get the error `Property 'href' does not exist on type 'Element'.` – switch201 May 17 '19 at 16:43
  • This is how I implemented your example where `selector` is a selector string: `const elementHandle = await this.browser.$(selector)` `let test = await this.browser.evaluate((el: { href: any; }) => el.href, elementHandle)` – switch201 May 17 '19 at 16:45
  • @switch201 Is `browser` the page handle? Do you maybe still need to create a page by calling `newPage`? And does the page contain a link? – Thomas Dondorf May 17 '19 at 16:50
  • yes browser is the page handle. I already have a page object that is calling this function which passed in a Selector object. I have seen other references to Element.href on other stack overflow questions, but when I looked in the docks that didn't appear to be an accessible property. – switch201 May 17 '19 at 17:55
  • And are you getting an error or what does the code produce? – Thomas Dondorf May 17 '19 at 18:01
  • I am getting gettin this error: `Property 'href' does not exist on type 'Element'` – switch201 May 17 '19 at 18:14
  • After you kindly explained how `evaluate` works I have been able to better refine my google searches as I work towards a solutions. I am going to try using `page.$$` as this should return an array of `ElementHandles`, and I think from there I might be able to interact with the elements. – switch201 May 17 '19 at 18:16
  • I updated my answer with a TypeScript example. You might also just want to use a `page.evaluate` function and use `document.querySelector` inside. See this [question](https://stackoverflow.com/q/55664420/5627599) for more information :) – Thomas Dondorf May 17 '19 at 18:30
  • 1
    ahh I see now, I firstly want to say I appreciate all the work you've put into helping me I will accept your answer as correct, but this ins't really what I was looking for. Maybe a better way to phrase my question would have been this: "I want to start with a selector string that matches to multiple elements on a given page. How do I take the string and end up with an `Element[]` containing each element that matched with the given string.' I am starting to think it's not possible with puppeteer sadly. – switch201 May 17 '19 at 18:39
  • @switch201 Oh, I see. If that is your goal `page.$$` is what you are looking for. No worries, happy to help :) Actually added the function to the answer for future visitors ;) – Thomas Dondorf May 17 '19 at 18:50
0

i'm facing the same problem with types definition, and Element is not exported from any Lib.

To get apropriate intelisense with el methods, i changed my code with below example:

From:

...  
await page.waitForTimeout(3000)
await page.waitForSelector('button[type=submit]') 
await page.$eval('button[type=submit]', el => el.click())
await page.waitForNavigation({waitUntil: "load"})
await page.goto(url)
...

To this:

...  
await page.waitForTimeout(3000) 
await page.waitForSelector('button[type=submit]')
await page.$('button[type=submit]').then(el => {
  el.click()
})
await page.waitForNavigation({waitUntil: "load"})
await page.goto(url)
...

puppeteer element intelisense example

PS: I found the issue in definition types on DefinedType Github https://github.com/DefinitelyTyped/DefinitelyTyped/issues/24419

Heron Eto
  • 61
  • 2