This PR greatly improves the types within Puppeteer:
- **Almost everything** is auto-deduced.
- Parameters don't need to be specified in the function. They are deduced from the spread.
- Return types don't need to be specified. They are deduced from the function. (More on this below)
- Selections based on tag names correctly deduce element type, similar to TypeScript's mechanism for `getElementByTagName`.
- [**BREAKING CHANGE**] We've removed the ability to declare return types in type arguments for the following reasons:
1. Setting them will indubitably break auto-deduction.
2. You can just use `as ...` in TypeScript to coerce the correct type (given it makes sense).
- [**BREAKING CHANGE**] `waitFor` is officially gone.
To migrate to these changes, there are only four things you may need to change:
- If you set a return type using the `ReturnType` type parameter, remove it and use `as ...` and `HandleFor` (if necessary).
⛔ `evaluate<ReturnType>(a: number, b: number) => {...}, a, b)`
✅ `(await evaluate(a, b) => {...}, a, b)) as ReturnType`
⛔ `evaluateHandle<ReturnType>(a: number, b: number) => {...}, a, b)`
✅ `(await evaluateHandle(a, b) => {...}, a, b)) as HandleFor<ReturnType>`
- If you set any type parameters in the *parameters* of an evaluation function, remove them.
⛔ `evaluate(a: number, b: number) => {...}, a, b)`
✅ `evaluate(a, b) => {...}, a, b)`
- If you set any type parameters in the method's declaration, remove them.
⛔ `evaluate<(a: number, b: number) => void>((a, b) => {...}, a, b)`
✅ `evaluate(a, b) => {...}, a, b)`
If an iframe has a border, it has to be added to the offsets
too. We can work around it by using the content box coordinates
for the offsets. That should also prevent discrepancies if the
iframe has a padding set.
The doc for boundingBox says that it should return the boundingBox
relative to the main frame, therefore, this fix would make the
actual implementation correspond to the documentation. boxModel
documentation does not have this note but I think it'd make sense
to have it match the behaviour of the boundingBox API.
So it appears that all bindings are added to the secondary world and all
evaluations are also running there. ElementHandle.evaluate is returning
handles from the main world though. Therefore, we need to be careful
and adopt handles to the right context before doing waitForSelector
So it appears that all bindings are added to the secondary world and all
evaluations are also running there. ElementHandle.evaluate is returning
handles from the main world though. Therefore, we need to be careful
and adopt handles to the right context before doing waitForSelector.
Until now, the click would be always sent to the middle
point of the target element. With this change, one can define
offsets relative to the border box of the elements and click
different areas of an element.
This commit adds drag-and-drop support, leveraging new additions to the CDP Input domain (Input.setInterceptDrags, Input.dispatchDragEvent, and Input.dragIntercepted).
* fix: modified comment for method product, platform and newPage
* fix: added comment for browsercontext, StartCSSCoverage, StartJSCoverage
* fix: corrected comments for JSONValue, asElement, evaluateHandle
* fix: corrected comments for JSONValue, asElement, evaluateHandle
* fix: added comments for some of the method
* fix: added proper comments
Co-authored-by: Jack Franklin <jacktfranklin@chromium.org>
This commit updates the JSHandle class to take a generic representing
the underlying object that it's wrapping. We can then define
`ElementHandle` as a class that extends `JSHandle<Element>` and begin
to get better type inference.
Prior to this commit the following code would have `d` set to `any`:
```
const div: page.$<HTMLDivElement>('div')
const text = await div.evaluate(d => d.innerText)
```
You could work around this in two ways:
```
const text = await div.evaluate<(d: HTMLDivElement) => string>(d => d.innerText)
const text = await div.evaluate((d: HTMLDivElement) => d.innerText)
```
But both of these have two issues:
1. Requires the user to type extra information.
2. There's no type checking: in the code above I could type `d` as
`number` and TS would be happy.
With the change here to `evaluate` the user can now type the original
code:
```
const div: page.$<HTMLDivElement>('div')
const text = await div.evaluate(d => d.innerText)
```
And TypeScript will know that `d` is an `HTMLDivElement`.
This change brings us inline with the approach that @types/puppeteer
takes. If we land this and it works, we can do the same with
`evaluateHandle` to hopefully make a similar improvement there.
BREAKING: because this changes the types, which were previously `any`,
this is technically a breaking change as users using TS could start
getting errors after this change is released.
We're seeing odd failures with Prettier on some CI branches; my hunch is that they are installing different versions of the package and therefore getting formatting conflicts. This PR updates them all and pins them to specific versions - something we should probably consider generally, or remove our `package-lock.json` from the gitignore.
* fix: make `$` and `$$` selectors generic
This means, much like TS's in built `querySelector` type, you can now do:
```ts
const listItems = page.$$<HTMLLIElement>('ul li');
```
And/or:
```ts
const h2 = page.$<HTMLHeadingElement>('h2');
```
And the return value will be of type `ElementHandle<T>|null`, where `T`
is the type you provided. By default `T` is an `Element`, so you don't
have to provide this if you don't care as a consumer about the exact
type you get back.
* chore: fix test assertions
During the migration to TS we changed `jsonValue` so it returned
`<Record<string, unknown>>`. This is only true if all the JSON values it
returns are objects; but it could return an array, a string, a number,
etc. Therefore we make the type generic, setting the default to
`unknown`, so the user has control over the type.
* chore: run unit tests on node 10.15
We saw in https://github.com/puppeteer/puppeteer/issues/6548 that the
`fs.promises` module was experimental in Node <10.17 and as such we
introduced issues for users on 10.15.
Until we can drop Node v10 (it's EOL is 30-04-20201
https://github.com/nodejs/Release#release-schedule) we should run our
tests on an old Node 10 to avoid regressing in this area.
* chore: helper for importing fs safely
In `src/common` we now use `fs.promises.X` which we can dynamically
`import`. In a browser environment this code will never run because it's
gated on `isNode` (in a future PR we will add tree-shaking to the bundle
step such that this code is eliminated). By using `import`, we ensure
TypeScript still can track types and give good type information.
In `src/node` we continue to use `util.promisify` but that's not a
concern as that code explicitly is never run in the browser.
This commit changes the internal representation of query handlers to contain Puppeteer-level code instead of page functions.
The interface `CustomQueryHandler` is introduced for user-defined query handlers. When a `CustomQueryHandler` is registered using `registerCustomQueryHandler` a corresponding Puppeteer-level handler is created through `makeQueryHandler` by wrapping the page functions as appropriate.
The internal query handlers (defined by the interface `QueryHandler`) contain two new functions: `waitFor` and `queryAllArray`.
- `waitFor` allows page-based handlers to make use of the `WaitTask`-backed implementation in `DOMWorld`, whereas purely Puppeteer-based handlers can define an alternative approach instead.
- `queryAllArray` is similar to `queryAll` but with a slightly different interface; it returns a `JSHandle` to an array with the results as opposed to an array of `ElementHandle`. It is used by `$$eval`.
After this change, we can introduce built-in query handlers that are not executed in the page context (#6307).
* chore: enforce file extensions on imports
To make our output agnostic it should include file extensions in the
output, as per the ESM spec. It's a bit odd for Node packages but makes
it easier to publish a browser build.
* chore: Use devtools-protocol package
Rather than maintain our own protocol we can instead use the devtools-protocol package and pin it to the version of Chromium that Puppeteer is shipping with.
The only changes are naming changes between the bespoke protocol that Puppeteer created and the devtools-protocol one.
This pulls in the types (based on the DefinitelyTyped repo) for
`page.$eval` (and the `$eval` method on other classes). The `$eval`
method is quite hard to type due to the way we wrap and unwrap
ElementHandles that are passed to / returned from the `pageFunction`
that users provide.
Longer term we can improve the types by providing type overloads as
DefinitelyTyped does but I've deferred that for now (see the `TODO` in
the code for more details).