Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(puppeteer-core): Infer element type from complex selector #9253

Merged
merged 15 commits into from Nov 23, 2022

Conversation

gomain
Copy link
Contributor

@gomain gomain commented Nov 11, 2022

What kind of change does this PR introduce?

Better type inference.

Did you add tests for your changes?

Not yet. Yes.

If relevant, did you update the documentation?

Not yet.

Summary

Currently methods that return an element handle, i.e. .$, .waitForSelector attempt to infer the node element type from the selector string. However, this only works when the selector is an exact match of the element tag, i.e. a selector "a" would be inferred as HTMLAnchorElement . And not when the selector is complex, i.e. selectors "a#some-id", div > a, a:nth-child(2) would all fallback on Element.

This is due to simply looking up the the selector in HTMLElementTagNameMap and SVGElementTagNameMap without any attempt to parse the selector string.

This PR is an attempt to do so.

Does this PR introduce a breaking change?

This could break existing incorrect assertions using the as keyword.

Other information

This PR introduces a dependency on the type-fest package.

This PR is far from complete (no tests, no docs). Put out early for feedback and discussion.

@google-cla
Copy link

google-cla bot commented Nov 11, 2022

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@OrKoN OrKoN requested a review from jrandolf November 11, 2022 11:08
@gomain gomain force-pushed the infer-element-type-from-complex-selector branch from 06d83c6 to 15a49bf Compare November 11, 2022 12:36
@gomain gomain changed the title Infer element type from complex selector feat(puppeteer-core): Infer element type from complex selector Nov 11, 2022
@gomain gomain force-pushed the infer-element-type-from-complex-selector branch 4 times, most recently from 6a3b7ca to d446359 Compare November 13, 2022 16:32
@OrKoN
Copy link
Collaborator

OrKoN commented Nov 14, 2022

@jrandolf could you please review?

@jrandolf
Copy link
Contributor

Hey Gomain, thanks for the PR. As of now, we do not plan to add this feature. We would like TypeScript to first resolve this problem on their side if they ever do: microsoft/TypeScript#29037 Resolving it ourselves may lead to incompatibility in resolution.

@jrandolf jrandolf closed this Nov 14, 2022
@OrKoN
Copy link
Collaborator

OrKoN commented Nov 14, 2022

@jrandolf can we have it as an intermediate solution?

@OrKoN OrKoN reopened this Nov 14, 2022
@jrandolf
Copy link
Contributor

@gomain Could you add test cases for these types in the test-d folder?

@jrandolf jrandolf force-pushed the infer-element-type-from-complex-selector branch from d446359 to c89aa6b Compare November 15, 2022 10:12
@gomain gomain force-pushed the infer-element-type-from-complex-selector branch from c89aa6b to dd1a176 Compare November 15, 2022 15:14
@gomain
Copy link
Contributor Author

gomain commented Nov 16, 2022

@gomain Could you add test cases for these types in the test-d folder?

@jrandolf please see latest commit. I could not assert, for e.g., NodeFor<'a'> resolves to HTMLAnchorElement and not Element with tsd.

@OrKoN
Copy link
Collaborator

OrKoN commented Nov 16, 2022

@gomain I think tests can be added along existing tests https://github.com/puppeteer/puppeteer/blob/main/test-d/ElementHandle.test-d.ts#L8 E.g., we don't need to use NodeFor directly but can you the public API and check the types.

@gomain
Copy link
Contributor Author

gomain commented Nov 17, 2022

There are a few concerns I think we should discuss.

  1. NodeFor is public, and therefore deserves to be tested. I'm not too sure as to why it is public though. Suggesting it is not used directly, and therefore not need testing, suggests it should be private.
  2. Types influenced by NodeFor spans across a large number of api. Picking one or two for the purpose of testing that node type is derived from given selector is at best weak. Covering each and every instance where some selector depicts some type would be superfluous. Wouldn't we rather test NodeFor in isolation and take for granted that types built on top of it are correct, given the on top building are trivially simple and/or tested.
  3. Implementing tests along existing tests would suffer the limitation of tsd

    The current type testing utility tsd is insufficient in asserting a
    type is strictly of a super type and not one of its sub types.

    declare const node: Element;
    declare const a: HTMLAnchorElement;
    
    expectType<Element>(node); // pass
    expectType<Element>(a); // also pass
  4. Unless types are the primary artifact of a library or that .d.ts files were hand written, it does not make sense to test .d.ts files, i.e. after compilation. Once types are generated by a project, it is the description of that project's api. Testing that the type of the api meets some expected type is questioning the spec, not the implementation. This does not dismiss the need for type testing. For the sake of organization, we would want to separate tests - including type tests, from the projects that implement them. For this I propose:
    1. Have a dedicated project puppeteer-test-types for type testing. This project does not emit any build.
    2. This project uses expect-type for type testing. (Or other compile-time type testing utility)
    3. This project is the last but to puppeteer-test in the dependency build chain. If puppeteer-test-types fails to build (i.e. typecheck), build fails.
    4. Drop use of tsd. Partly because of the above limitation. But also to put type testing in the realm of building.

@OrKoN
Copy link
Collaborator

OrKoN commented Nov 17, 2022

@gomain that kind of refactoring is not a priority for us right now. As for this change, I think it'd be sufficient if we add tests like expectType<ElementHandle<HTMLAnchorElement> | null>(await handle.$('.complex > .selector a')); (I don't think tsd limitation affects that as we are testing for a subtype).

@gomain
Copy link
Contributor Author

gomain commented Nov 17, 2022

@OrKoN Any comments on points 1. & 2. ?

@OrKoN
Copy link
Collaborator

OrKoN commented Nov 17, 2022

@gomain 1) private types are stripped from the resulting .d.ts file so NodeFor needs to be marked public to be available in return types. If we can test it with dts, let's. Otherwise, method-level test-d tests suffice. 2) test-d serves as a e2e test suite to prevent regressions so at least capturing the expectations for some of the methods is useful.

@OrKoN
Copy link
Collaborator

OrKoN commented Nov 17, 2022

@gomain what was the problem with using tsd?

import {NodeFor} from 'puppeteer';
import {expectType} from 'tsd';

type TestFn = <Selector extends string>(selector: Selector) => NodeFor<Selector>;

const test = (() => {}) as TestFn;

expectType<HTMLAnchorElement>(test('.cls a'));

Seems to work fine for me? Also, using Element instead of a more specific type throws an error: Parameter type Element is declared too wide for argument type HTMLAnchorElement.

@OrKoN
Copy link
Collaborator

OrKoN commented Nov 17, 2022

Sorry if I am misunderstanding the issue but it seems to work?:

import {NodeFor} from 'puppeteer';
import {expectType, expectNotType} from 'tsd';

type TestFn = <Selector extends string>(selector: Selector) => NodeFor<Selector>;

const test = (() => {}) as TestFn;

expectType<HTMLAnchorElement>(test('.cls a'));
expectNotType<Element>(test('.cls a'));
expectType<Element>(test('#el'));

remove dependency on `type-fest`
Breakdown nested composition into intermediate steps. This is
preferred because typescript does not typecheck possibility of
intermediate nesting evaluating to non-constrainted types.
@gomain
Copy link
Contributor Author

gomain commented Nov 18, 2022

@OrKoN My apologies, false alarm on tsd behaviour. I must have been doing it wrong and jumped to that conclusion too soon.

@gomain gomain force-pushed the infer-element-type-from-complex-selector branch from 3ced5c7 to 70bf827 Compare November 18, 2022 09:30
@OrKoN
Copy link
Collaborator

OrKoN commented Nov 18, 2022

Many thanks! @gomain

@OrKoN OrKoN enabled auto-merge (squash) November 23, 2022 09:06
@OrKoN OrKoN disabled auto-merge November 23, 2022 09:59
@OrKoN OrKoN merged commit bef1061 into puppeteer:main Nov 23, 2022
OrKoN pushed a commit that referenced this pull request Nov 23, 2022
🤖 I have created a release *beep* *boop*
---


<details><summary>ng-schematics: 0.1.0</summary>

## 0.1.0 (2022-11-23)


### Features

* **ng-schematics:** Release @puppeteer/ng-schematics
([#9244](#9244))
([be33929](be33929))
</details>

<details><summary>puppeteer: 19.3.0</summary>

##
[19.3.0](puppeteer-v19.2.2...puppeteer-v19.3.0)
(2022-11-23)


### Miscellaneous Chores

* **puppeteer:** Synchronize puppeteer versions


### Dependencies

* The following workspace dependencies were updated
  * dependencies
    * puppeteer-core bumped from 19.2.2 to 19.3.0
</details>

<details><summary>puppeteer-core: 19.3.0</summary>

##
[19.3.0](puppeteer-core-v19.2.2...puppeteer-core-v19.3.0)
(2022-11-23)


### Features

* **puppeteer-core:** Infer element type from complex selector
([#9253](#9253))
([bef1061](bef1061))
* **puppeteer-core:** update Chrome launcher flags
([#9239](#9239))
([ae87bfc](ae87bfc))


### Bug Fixes

* remove boundary conditions for visibility
([#9249](#9249))
([e003513](e003513))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants