portadom 1.0.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +83 -213
  2. package/dist/{cjs/dom → dom}/dom.d.ts +10 -9
  3. package/dist/{cjs/dom → dom}/domUtils.d.ts +11 -10
  4. package/dist/dom/types.d.ts +360 -0
  5. package/dist/index.d.ts +6 -0
  6. package/dist/index.js +1216 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/{cjs/page → page}/page.d.ts +3 -3
  9. package/dist/{cjs/page → page}/pageUtils.d.ts +2 -2
  10. package/dist/{cjs/page → page}/types.d.ts +1 -1
  11. package/dist/utils/async.d.ts +8 -0
  12. package/dist/utils/error.d.ts +1 -0
  13. package/dist/{cjs/utils → utils}/types.d.ts +2 -0
  14. package/package.json +42 -37
  15. package/dist/cjs/dom/dom.js +0 -809
  16. package/dist/cjs/dom/dom.js.map +0 -1
  17. package/dist/cjs/dom/domUtils.js +0 -126
  18. package/dist/cjs/dom/domUtils.js.map +0 -1
  19. package/dist/cjs/dom/types.d.ts +0 -419
  20. package/dist/cjs/dom/types.js +0 -248
  21. package/dist/cjs/dom/types.js.map +0 -1
  22. package/dist/cjs/index.d.ts +0 -6
  23. package/dist/cjs/index.js +0 -22
  24. package/dist/cjs/index.js.map +0 -1
  25. package/dist/cjs/page/page.js +0 -105
  26. package/dist/cjs/page/page.js.map +0 -1
  27. package/dist/cjs/page/pageUtils.js +0 -116
  28. package/dist/cjs/page/pageUtils.js.map +0 -1
  29. package/dist/cjs/page/types.js +0 -3
  30. package/dist/cjs/page/types.js.map +0 -1
  31. package/dist/cjs/utils/async.d.ts +0 -23
  32. package/dist/cjs/utils/async.js +0 -99
  33. package/dist/cjs/utils/async.js.map +0 -1
  34. package/dist/cjs/utils/error.d.ts +0 -1
  35. package/dist/cjs/utils/error.js +0 -10
  36. package/dist/cjs/utils/error.js.map +0 -1
  37. package/dist/cjs/utils/format.js +0 -19
  38. package/dist/cjs/utils/format.js.map +0 -1
  39. package/dist/cjs/utils/types.js +0 -3
  40. package/dist/cjs/utils/types.js.map +0 -1
  41. package/dist/cjs/utils/url.js +0 -21
  42. package/dist/cjs/utils/url.js.map +0 -1
  43. /package/dist/{cjs/utils → utils}/format.d.ts +0 -0
  44. /package/dist/{cjs/utils → utils}/url.d.ts +0 -0
package/README.md CHANGED
@@ -1,264 +1,134 @@
1
- # Portadom
1
+ # portadom
2
2
 
3
- *Single DOM manipulation interface across Browser API, JSDOM, Cheerio, Playwright.*
3
+ **One DOM interface. Any engine.**
4
4
 
5
- If you write web scrapers, you will know that you have multiple ways of parsing and manipulating the HTML / DOM:
6
- - Download the HTML and feed into JSDOM or Cheerio.
7
- - Through browser automation like Playwright, Puppeteer, or Selenium.
8
- - Or right from inside the DevTools console, if you need to test something out.
5
+ Write scraping and extraction logic once. Run it on Cheerio, Playwright, or the Browser API. Swap engines when requirements change -- no rewrite needed.
9
6
 
10
- When I'm writing scrapers, my approach is usually:
11
- 1. Define the transformations in DevTools with vanilla JS.
12
- 2. Check if the HTML data can be extracted statically, just from the HTML (no JS).
13
- 3. If static HTML is enough, then migrate vanilla JS to JSDOM or Cheerio.
14
- 4. If I need JS runtime, migrate the vanilla JS to Playwright or other browser automation tool.
15
-
16
- Migrating from one to another can be prone to errors, and you may miss some features.
17
-
18
- Portadom takes care of this. Here's how you can move the same DOM manipulation logic from Cheerio to Playwright:
19
-
20
- Before:
21
-
22
- ```js
23
- import { load as loadCheerio } from 'cheerio';
24
- import { cheerioPortadom } from 'portadom';
25
-
26
- // Loading step changes
27
- const html = `<div>
28
- <a href="#">Click Me!</a>
29
- </div>`;
30
- const $ = loadCheerio(html);
31
- const dom = cheerioPortadom($.root(), url);
32
-
33
- // DOM manipulation remains the same
34
- const btn = dom.findOne('a');
35
- const btnText = await btn.text();
36
- // btnText == "Click Me!"
7
+ ```bash
8
+ npm install portadom
37
9
  ```
38
10
 
39
- After:
40
-
41
- ```js
42
- import { playwrightLocatorPortadom } from 'portadom';
11
+ ## Quick start
43
12
 
44
- // Loading step changes
45
- const page = await somehowLoadPage();
46
- const bodyLoc = page.locator('body');
47
- const dom = playwrightLocatorPortadom(bodyLoc, page);
13
+ Wrap any DOM engine in a `Portadom` instance, then use the same API everywhere:
48
14
 
49
- // DOM manipulation remains the same
50
- const btn = dom.findOne('a');
51
- const btnText = await btn.text();
52
- // btnText == "Click Me!"
53
- ```
15
+ ```typescript
16
+ import * as cheerio from 'cheerio';
17
+ import { cheerioPortadom } from 'portadom';
54
18
 
55
- ## Installation
19
+ // 1. Create a Portadom instance from a Cheerio root
20
+ const $ = cheerio.load('<h1>Hello</h1><a href="/about">About</a>');
21
+ const dom = cheerioPortadom($.root(), 'https://example.com');
56
22
 
57
- ```sh
58
- npm install portadom
23
+ // 2. Use the engine-agnostic API
24
+ await dom.findOne('h1').text(); // "Hello"
25
+ await dom.findOne('a').href(); // "https://example.com/about"
26
+ await dom.findMany('li').map((el) => el.text()); // ["Item 1", "Item 2", ...]
59
27
  ```
60
28
 
61
- ## How to use
62
-
63
- ### Minimal example
29
+ Switch engines by changing only the setup -- the extraction logic stays identical:
64
30
 
65
- ```js
66
- const html = `<div>
67
- <a href="#">Click Me!</a>
68
- </div>`;
69
- const $ = loadCheerio(html);
70
- const dom = cheerioPortadom($.root(), url);
71
-
72
- const btn = dom.findOne('a');
73
- const btnText = await btn.text();
74
- // btnText == "Click Me!"
31
+ ```typescript
32
+ import { playwrightLocatorPortadom } from 'portadom';
75
33
 
76
- const btnProp = await btn.href();
77
- // btnProp == "https://example.com#"
78
- ```
34
+ // 1. Create a Portadom instance from a Playwright locator
35
+ const dom = playwrightLocatorPortadom(page.locator('body'), page);
79
36
 
80
- ### Full example
81
-
82
- ```js
83
- const $ = loadCheerio(html);
84
- const dom = cheerioPortadom($.root(), url);
85
- // ...
86
- const rootEl = dom.root();
87
- const url = await dom.url();
88
-
89
- // Find and extract data
90
- const entries = await rootEl.findMany('.list-row:not(.native-agent):not(.reach-list)')
91
- .mapAsyncSerial(async (el) => {
92
- const employerName = await el.findOne('.employer').text();
93
- const employerUrl = await el.findOne('.offer-company-logo-link').href();
94
- const employerLogoUrl = await el.findOne('.offer-company-logo-link img').src();
95
-
96
- const offerUrlEl = el.findOne('h2 a');
97
- const offerUrl = await offerUrlEl.href();
98
- const offerName = await offerUrlEl.text();
99
- const offerId = offerUrl?.match(/O\d{2,}/)?.[0] ?? null;
100
-
101
- const location = await el.findOne('.job-location').text();
102
-
103
- const salaryText = await el.findOne('.label-group > a[data-dimension7="Salary label"]').text();
104
-
105
- const labels = await el.findMany('.label-group > a:not([data-dimension7="Salary label"])')
106
- .mapAsyncSerial((el) => el.text())
107
- .then((arr) => arr.filter(Boolean) as string[]);
108
-
109
- const footerInfoEl = el.findOne('.list-footer .info');
110
- const lastChangeRelativeTimeEl = footerInfoEl.findOne('strong');
111
- const lastChangeRelativeTime = await lastChangeRelativeTimeEl.text();
112
- // Remove the element so it's easier to get the text content
113
- await lastChangeRelativeTimeEl.remove();
114
- const lastChangeTypeText = await footerInfoEl.textAsLower();
115
- const lastChangeType = lastChangeTypeText === 'pridané' ? 'added' : 'modified';
116
-
117
- return {
118
- listingUrl: url,
119
- employerName,
120
- employerUrl,
121
- employerLogoUrl,
122
- offerName,
123
- offerUrl,
124
- offerId,
125
- location,
126
- labels,
127
- lastChangeRelativeTime,
128
- lastChangeType,
129
- };
130
- });
37
+ // 2. Exact same API -- no changes needed
38
+ await dom.findOne('h1').text();
39
+ await dom.findOne('a').href();
131
40
  ```
132
41
 
133
- ### Loading
42
+ ## Why portadom?
134
43
 
135
- Here is how you can load DOM in different environments:
44
+ ### Write once. Run anywhere.
136
45
 
137
- #### Browser
46
+ Cheerio, Playwright, and the Browser API have incompatible DOM APIs. Portadom wraps them behind a single `Portadom` interface. Your selectors, traversals, and data extraction stay the same regardless of engine.
138
47
 
139
- When working with browser [Document](https://developer.mozilla.org/en-US/docs/Web/API/Document), the `node` is an [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element).
48
+ ### Chain without awaits.
140
49
 
141
- ```js
142
- import { browserPortadom } from 'portadom';
50
+ Traversal methods return `PortadomPromise` -- chain `findOne`, `closest`, `parent`, and more without intermediate `await`s. The promise resolves when you extract a value.
143
51
 
144
- const dom = browserPortadom(document.body);
145
- const btnNode = await dom.findOne('a').node;
52
+ ```typescript
53
+ // Without portadom
54
+ const root = await page.locator('#root').elementHandle();
55
+ const heading = root ? await root.$('h1') : null;
56
+ const text = heading ? await heading.textContent() : null;
146
57
 
147
- // Or
148
- const startNode = document.querySelector('...');
149
- const dom = browserPortadom(startNode);
150
- const btnNode = await dom.findOne('a').node;
58
+ // With portadom
59
+ const text = await dom.findOne('#root').findOne('h1').text();
151
60
  ```
152
61
 
153
- #### Cheerio
62
+ ### Collections that feel like arrays.
154
63
 
155
- In [Cheerio](https://cheerio.js.org/), the `node` is the Cheerio Element wrapper. [See DOM traversal with Cheerio](https://cheerio.js.org/docs/basics/traversing).
64
+ `findMany()` returns `PortadomArrayPromise` -- the full `Array` API (`map`, `filter`, `find`, `slice`, `reduce`, ...) plus async helpers like `mapAsyncSerial` and `filterAsyncParallel`.
156
65
 
157
- ```js
158
- import { cheerioPortadom } from 'portadom';
159
- import { load as loadCheerio } from 'cheerio';
160
-
161
- const $ = loadCheerio(html);
162
- const dom = cheerioPortadom($.root(), url);
163
- const btnNode = await dom.findOne('a').node;
164
-
165
- // Or
166
- const startNode = $('a');
167
- const dom = cheerioPortadom(startNode, url);
168
- const btnNode = await dom.findOne('a').node;
169
-
170
- // Set `null` if you don't have an URL for the HTML
171
- const dom = cheerioPortadom($.root(), null);
66
+ ```typescript
67
+ const prices = await dom
68
+ .findMany('.product')
69
+ .filterAsyncParallel(async (el) => (await el.attr('in-stock')) === 'true')
70
+ .mapAsyncSerial(async (el) => await el.findOne('.price').textAsNumber({ mode: 'float' }));
172
71
  ```
173
72
 
174
- #### Playwright (using Locators)
73
+ ### URLs resolved automatically.
175
74
 
176
- In [Playwright](https://cheerio.js.org/), you can either work with the [Locators](https://playwright.dev/docs/api/class-locator) or the [ElementHandles](https://playwright.dev/docs/api/class-elementhandle).
75
+ `href()` and `src()` resolve relative paths to absolute URLs using the page's base URL. No manual `new URL()` calls.
177
76
 
178
- When using Locators, the `node` is a Locator instance.
77
+ <details>
78
+ <summary>What portadom replaces (click to expand)</summary>
179
79
 
180
- ```js
181
- import { playwrightLocatorPortadom } from 'portadom';
80
+ **Cheerio -- extracting an href:**
182
81
 
183
- const page = await somehowLoadPage();
184
- const bodyLoc = page.locator('body');
185
- const dom = playwrightLocatorPortadom(bodyLoc, page);
186
- const btnNode = await dom.findOne('a').node;
82
+ ```typescript
83
+ const rawHref = $('a').attr('href'); // "/about"
84
+ const absHref = rawHref?.startsWith('/') ? new URL(rawHref, 'https://example.com').href : rawHref;
187
85
  ```
188
86
 
189
- #### Playwright (using Handles)
190
-
191
- When using ElementHandles, the `node` is an ElementHandle instance.
192
-
193
- NOTE: You can pass Locator to `playwrightHandlePortadom`, but this will be converted to JSHandle internally.
194
-
195
- ```js
196
- import { playwrightHandlePortadom } from 'portadom';
197
-
198
- const page = await somehowLoadPage();
87
+ **Portadom:**
199
88
 
200
- // Use `evaluateHandle` with page-side logic to query the target element
201
- const handle = await page.evaluateHandle(, () => document.body);
202
- const handle = await page.evaluateHandle(, () => document.querySelector('.myClass'));
203
-
204
- // Or use other helpers such as `getByText`
205
- const handle = await page.getByText('hello');
206
-
207
- // Or use locators
208
- const handle = page.locator('body');
209
-
210
- const dom = playwrightHandlePortadom(bodyLoc, page);
211
- const btnNode = await dom.findOne('a').node;
89
+ ```typescript
90
+ await dom.findOne('a').href(); // "https://example.com/about"
212
91
  ```
213
92
 
214
- ### Chaining
215
-
216
- For cross-compatibility, each method on a Portadom instance returns
217
- a Promise.
93
+ </details>
218
94
 
219
- But this then leads to `then` / `await` hell when you need to call multiple methods in a row:
95
+ ## Supported engines
220
96
 
221
- ```js
222
- const employerName = (await (await el.findOne('.employer'))?.text()) ?? null;
223
- ```
224
-
225
- To get around that, the results are wrapped in chainable instance. This applies to each method that returns a Portadom instance, or an array of Portadom instances.
97
+ | Factory function | Engine | Element type |
98
+ | ------------------------------------------ | --------------------- | -------------------------- |
99
+ | `cheerioPortadom(el, url)` | Cheerio | `Cheerio<AnyNode>` |
100
+ | `playwrightLocatorPortadom(locator, page)` | Playwright (locators) | `Locator` |
101
+ | `playwrightHandlePortadom(handle, page)` | Playwright (handles) | `Locator \| ElementHandle` |
102
+ | `browserPortadom(element)` | Browser API | `Element` |
226
103
 
227
- So instead, we can call:
104
+ Install only the engine(s) you need:
228
105
 
229
- ```js
230
- const employerName = await el.findOne('.employer').text();
106
+ ```bash
107
+ npm install cheerio # static HTML
108
+ npm install playwright # browser automation
231
109
  ```
232
110
 
233
- You don't have to chain the commands. Instead, you can access the associated promise under `promise` property. For example this:
111
+ ## Page-level operations (experimental)
234
112
 
235
- ```js
236
- const mapPromises = await dom.findOne('ul')
237
- .parent()
238
- .findMany('li[data-id]')
239
- .map((li) => li.attr('data-id'));
240
- const attrs = await Promise.all(mapResult);
241
- ```
113
+ `Portapage` provides page-level helpers like infinite scroll handling:
242
114
 
243
- Is the same as:
115
+ ```typescript
116
+ import { playwrightPortapage } from 'portadom';
244
117
 
245
- ```js
246
- const ul = await dom.findOne('ul').promise;
247
- const parent = await ul?.parent().promise;
248
- const idEls = await parent?.findMany('li[data-id]').promise;
249
- const mapPromises = idEls?.map((li) => li.attr('data-id')) ?? [];
250
- const attrs = await Promise.all(mapPromises);
118
+ const portapage = await playwrightPortapage(page);
119
+ await portapage.infiniteScroll('#feed', async (newItems, ctx, stop) => {
120
+ // Process each batch of new DOM elements as they load
121
+ if (shouldStop) stop();
122
+ });
251
123
  ```
252
124
 
253
- ## Reference
125
+ ## Documentation
254
126
 
255
- See the [full documentation here](./docs/typedoc/modules.md).
256
- - [Portadom](./docs/typedoc/interfaces/Portadom.md)
257
- - [Portapage](./docs/typedoc/interfaces/Portapage.md)
127
+ | Document | Description |
128
+ | ------------------------------------------ | -------------------------------- |
129
+ | [API reference](./docs/typedoc/globals.md) | Full typedoc-generated API docs. |
130
+ | [Changelog](./CHANGELOG.md) | Release history. |
258
131
 
259
- ## Real life exampes
132
+ ## License
260
133
 
261
- - [Profesia.sk Scraper](https://github.com/JuroOravec/apify-actor-profesia-sk)
262
- - [Example 1](https://github.com/JuroOravec/apify-actor-profesia-sk/blob/3793915632bd81dc257d36699808635c8bc3f87e/src/pageActions/jobListing.ts#L128)
263
- - [Example 2](https://github.com/JuroOravec/apify-actor-profesia-sk/blob/3793915632bd81dc257d36699808635c8bc3f87e/src/pageActions/jobDetail.ts#L75)
264
- - [SKCRIS Scraper](https://github.com/JuroOravec/apify-actor-skcris/blob/9ce92f9bd55ffcde91f22744e49ba97b6b4f0e44/src/pageActions/detail.ts#L510)
134
+ MIT
@@ -1,19 +1,20 @@
1
- import type { AnyNode, Cheerio } from 'cheerio';
1
+ import type { AnyNode } from 'domhandler';
2
+ import type { Cheerio } from 'cheerio';
2
3
  import type { ElementHandle, Locator, Page } from 'playwright';
3
- import { type Portadom } from './types';
4
+ import { type Portadom } from './types.js';
4
5
  /** Implementation of Portadom in browser (using Browser API) */
5
- export type BrowserPortadom<T extends Element = Element> = Portadom<T, Element>;
6
+ export type BrowserPortadom<El extends Element = Element> = Portadom<El>;
6
7
  /** Implementation of Portadom in browser (using Browser API) */
7
- export declare const browserPortadom: <El extends Element>(node: El) => BrowserPortadom<El>;
8
+ export declare const browserPortadom: <El extends Element = Element>(node: El) => BrowserPortadom<El>;
8
9
  /** Implementation of Portadom in Cheerio */
9
10
  export type CheerioPortadom<El extends Cheerio<AnyNode> = Cheerio<AnyNode>> = Portadom<El, Cheerio<AnyNode>>;
10
11
  /** Implementation of Portadom in Cheerio */
11
- export declare const cheerioPortadom: <El extends Cheerio<AnyNode>>(cheerioNode: El, srcUrl: string | null) => CheerioPortadom<El>;
12
+ export declare const cheerioPortadom: <El extends Cheerio<AnyNode> = Cheerio<AnyNode>>(cheerioNode: El, srcUrl: string | null) => CheerioPortadom<El>;
12
13
  /** Implementation of Portadom in Playwright using Handles */
13
- export type PlaywrightHandlePortadom<El extends Locator | ElementHandle<Node> = Locator | ElementHandle<Node>> = Portadom<El, Locator | ElementHandle<Node>>;
14
+ export type PlaywrightHandlePortadom<El extends Locator | ElementHandle = Locator | ElementHandle> = Portadom<El, Locator | ElementHandle>;
14
15
  /** Implementation of Portadom in Playwright using Handles */
15
- export declare const playwrightHandlePortadom: <El extends ElementHandle<Node> | Locator>(node: El, page: Page) => PlaywrightHandlePortadom<El>;
16
+ export declare const playwrightHandlePortadom: <El extends Locator | ElementHandle = Locator | ElementHandle>(node: El, page: Page) => PlaywrightHandlePortadom<El>;
16
17
  /** Implementation of Portadom in Playwright using Locators */
17
- export type PlaywrightLocatorPortadom<El extends Locator = Locator> = Portadom<El, Locator>;
18
+ export type PlaywrightLocatorPortadom<El extends Locator = Locator> = Portadom<El>;
18
19
  /** Implementation of Portadom in Playwright using Locators */
19
- export declare const playwrightLocatorPortadom: <El extends Locator>(node: El, page: Page) => PlaywrightLocatorPortadom<El>;
20
+ export declare const playwrightLocatorPortadom: <El extends Locator = Locator>(node: El, page: Page) => PlaywrightLocatorPortadom<El>;
@@ -1,6 +1,7 @@
1
- import type { AnyNode, Cheerio } from 'cheerio';
1
+ import type { AnyNode } from 'domhandler';
2
+ import type { Cheerio } from 'cheerio';
2
3
  import type { ElementHandle, JSHandle, Locator } from 'playwright';
3
- import type { MaybeArray, MaybePromise } from '../utils/types';
4
+ import type { MaybeArray, MaybePromise } from '../utils/types.js';
4
5
  /**
5
6
  * Given a Cheerio selection, split it into an array of Cheerio selections,
6
7
  * where each has only one element.
@@ -9,7 +10,7 @@ import type { MaybeArray, MaybePromise } from '../utils/types';
9
10
  *
10
11
  * To `[Cheerio[el], Cheerio[el], Cheerio[el], Cheerio[el]]`
11
12
  */
12
- export declare const splitCheerioSelection: (cheerioSel: Cheerio<AnyNode>) => Cheerio<AnyNode>[];
13
+ export declare const splitCheerioSelection: <T extends AnyNode>(cheerioSel: Cheerio<T>) => Cheerio<T>[];
13
14
  /**
14
15
  * Given a Playwright JSHandle that points to an array of Elements, split it into an array of
15
16
  * ElementHandles, where each has only one element.
@@ -18,12 +19,12 @@ export declare const splitCheerioSelection: (cheerioSel: Cheerio<AnyNode>) => Ch
18
19
  *
19
20
  * To `ElHandle(el), ElHandle(el), ElHandle(el)`
20
21
  */
21
- export declare const splitPlaywrightSelection: <T>(handle: JSHandle<T[]>) => Promise<import("playwright-core/types/structs").SmartHandle<T>[]>;
22
+ export declare const splitPlaywrightSelection: <T>(handle: JSHandle<T[]>) => Promise<JSHandle<T>[]>;
22
23
  /** Any instance that is a Playwright Handle. */
23
- export type AnyHandle<T = any> = JSHandle<T> | ElementHandle<T>;
24
+ export type AnyHandle = JSHandle | ElementHandle;
24
25
  /** Any instance that is a Playwright Handle, or can be converted to one. */
25
- export type HandleLike<T = any> = Locator | JSHandle<T> | ElementHandle<T>;
26
- export declare const handleIsLocator: <T>(h: HandleLike<T>) => h is Locator;
26
+ export type HandleLike = Locator | JSHandle | ElementHandle;
27
+ export declare const handleIsLocator: (h: HandleLike) => h is Locator;
27
28
  /**
28
29
  * Join several Locators and Handles in a single JSHandle.
29
30
  *
@@ -31,12 +32,12 @@ export declare const handleIsLocator: <T>(h: HandleLike<T>) => h is Locator;
31
32
  *
32
33
  * To override how Locators are resolved, supply own `locatorResolver` function.
33
34
  */
34
- export declare const mergeHandles: <T = any>(handles: HandleLike<T>[], options?: {
35
+ export declare const mergeHandles: (handles: HandleLike[], options?: {
35
36
  /**
36
37
  * Configure how to process {@link Locator}s into {@link JSHandle}s.
37
38
  *
38
39
  * By default, Locator resolves to the first DOM Element it matches,
39
40
  * using {@link Locator.elementHandle}.
40
41
  */
41
- locatorResolver?: ((loc: Locator) => MaybePromise<MaybeArray<AnyHandle<T>>>) | undefined;
42
- } | undefined) => Promise<JSHandle<T[]>>;
42
+ locatorResolver?: (loc: Locator) => MaybePromise<MaybeArray<AnyHandle>>;
43
+ }) => Promise<JSHandle<Element[]>>;