fix: add a way to run page.$$ without the isolation (#12539)

This commit is contained in:
Alex Rudenko 2024-06-11 11:37:44 +02:00 committed by GitHub
parent 97451731da
commit 03e10a7559
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 181 additions and 12 deletions

View File

@ -1172,6 +1172,13 @@ Valid options to configure PDF generation via [Page.pdf()](./puppeteer.page.pdf.
</td></tr>
<tr><td>
<span id="queryoptions">[QueryOptions](./puppeteer.queryoptions.md)</span>
</td><td>
</td></tr>
<tr><td>
<span id="remoteaddress">[RemoteAddress](./puppeteer.remoteaddress.md)</span>
</td><td>

View File

@ -11,7 +11,8 @@ Queries the current element for all elements matching the given selector.
```typescript
class ElementHandle {
$$<Selector extends string>(
selector: Selector
selector: Selector,
options?: QueryOptions
): Promise<Array<ElementHandle<NodeFor<Selector>>>>;
}
```
@ -43,6 +44,19 @@ Selector
The selector to query for.
</td></tr>
<tr><td>
options
</td><td>
[QueryOptions](./puppeteer.queryoptions.md)
</td><td>
_(Optional)_
</td></tr>
</tbody></table>
**Returns:**

View File

@ -105,7 +105,7 @@ Queries the current element for an element matching the given selector.
</td></tr>
<tr><td>
<span id="__">[$$(selector)](./puppeteer.elementhandle.__.md)</span>
<span id="__">[$$(selector, options)](./puppeteer.elementhandle.__.md)</span>
</td><td>

View File

@ -11,7 +11,8 @@ Queries the frame for all elements matching the given selector.
```typescript
class Frame {
$$<Selector extends string>(
selector: Selector
selector: Selector,
options?: QueryOptions
): Promise<Array<ElementHandle<NodeFor<Selector>>>>;
}
```
@ -43,6 +44,19 @@ Selector
[selector](https://pptr.dev/guides/page-interactions#query-selectors) to query page for. [CSS selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) can be passed as-is and a [Puppeteer-specific seletor syntax](https://pptr.dev/guides/page-interactions#p-selectors) allows quering by [text](https://pptr.dev/guides/page-interactions#text-selectors--p-text), [a11y role and name](https://pptr.dev/guides/page-interactions#aria-selectors--p-aria), and [xpath](https://pptr.dev/guides/page-interactions#xpath-selectors--p-xpath) and [combining these queries across shadow roots](https://pptr.dev/guides/page-interactions#-and--combinators). Alternatively, you can specify a selector type using a prefix [prefix](https://pptr.dev/guides/page-interactions#built-in-selectors).
</td></tr>
<tr><td>
options
</td><td>
[QueryOptions](./puppeteer.queryoptions.md)
</td><td>
_(Optional)_
</td></tr>
</tbody></table>
**Returns:**

View File

@ -125,7 +125,7 @@ Queries the frame for an element matching the given selector.
</td></tr>
<tr><td>
<span id="__">[$$(selector)](./puppeteer.frame.__.md)</span>
<span id="__">[$$(selector, options)](./puppeteer.frame.__.md)</span>
</td><td>

View File

@ -11,7 +11,8 @@ Finds elements on the page that match the selector. If no elements match the sel
```typescript
class Page {
$$<Selector extends string>(
selector: Selector
selector: Selector,
options?: QueryOptions
): Promise<Array<ElementHandle<NodeFor<Selector>>>>;
}
```
@ -43,6 +44,19 @@ Selector
[selector](https://pptr.dev/guides/page-interactions#query-selectors) to query page for. [CSS selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) can be passed as-is and a [Puppeteer-specific seletor syntax](https://pptr.dev/guides/page-interactions#p-selectors) allows quering by [text](https://pptr.dev/guides/page-interactions#text-selectors--p-text), [a11y role and name](https://pptr.dev/guides/page-interactions#aria-selectors--p-aria), and [xpath](https://pptr.dev/guides/page-interactions#xpath-selectors--p-xpath) and [combining these queries across shadow roots](https://pptr.dev/guides/page-interactions#-and--combinators). Alternatively, you can specify a selector type using a prefix [prefix](https://pptr.dev/guides/page-interactions#built-in-selectors).
</td></tr>
<tr><td>
options
</td><td>
[QueryOptions](./puppeteer.queryoptions.md)
</td><td>
_(Optional)_
</td></tr>
</tbody></table>
**Returns:**

View File

@ -250,7 +250,7 @@ Shortcut for [Page.mainFrame().$(selector)](./puppeteer.frame._.md).
</td></tr>
<tr><td>
<span id="__">[$$(selector)](./puppeteer.page.__.md)</span>
<span id="__">[$$(selector, options)](./puppeteer.page.__.md)</span>
</td><td>

View File

@ -0,0 +1,55 @@
---
sidebar_label: QueryOptions
---
# QueryOptions interface
#### Signature:
```typescript
export interface QueryOptions
```
## Properties
<table><thead><tr><th>
Property
</th><th>
Modifiers
</th><th>
Type
</th><th>
Description
</th><th>
Default
</th></tr></thead>
<tbody><tr><td>
<span id="isolate">isolate</span>
</td><td>
</td><td>
boolean
</td><td>
Whether to run the query in isolation. When returning many elements from [Page.$$()](./puppeteer.page.__.md) or similar methods, it might be useful to turn off the isolation to improve performance. By default, the querying code will be executed in a separate sandbox realm.
</td><td>
`true`
</td></tr>
</tbody></table>

View File

@ -31,7 +31,11 @@ import type {
MouseClickOptions,
} from './Input.js';
import {JSHandle} from './JSHandle.js';
import type {ScreenshotOptions, WaitForSelectorOptions} from './Page.js';
import type {
QueryOptions,
ScreenshotOptions,
WaitForSelectorOptions,
} from './Page.js';
/**
* @public
@ -371,8 +375,34 @@ export abstract class ElementHandle<
* elements matching the given selector.
*/
@throwIfDisposed()
@ElementHandle.bindIsolatedHandle
async $$<Selector extends string>(
selector: Selector,
options?: QueryOptions
): Promise<Array<ElementHandle<NodeFor<Selector>>>> {
if (options?.isolate === false) {
return await this.#$$impl(selector);
}
return await this.#$$(selector);
}
/**
* Isolates {@link ElementHandle.$$} if needed.
*
* @internal
*/
@ElementHandle.bindIsolatedHandle
async #$$<Selector extends string>(
selector: Selector
): Promise<Array<ElementHandle<NodeFor<Selector>>>> {
return await this.#$$impl(selector);
}
/**
* Implementation for {@link ElementHandle.$$}.
*
* @internal
*/
async #$$impl<Selector extends string>(
selector: Selector
): Promise<Array<ElementHandle<NodeFor<Selector>>>> {
const {updatedSelector, QueryHandler} =

View File

@ -10,6 +10,7 @@ import type {ClickOptions, ElementHandle} from '../api/ElementHandle.js';
import type {HTTPResponse} from '../api/HTTPResponse.js';
import type {
Page,
QueryOptions,
WaitForSelectorOptions,
WaitTimeoutOptions,
} from '../api/Page.js';
@ -564,11 +565,12 @@ export abstract class Frame extends EventEmitter<FrameEvents> {
*/
@throwIfDetached
async $$<Selector extends string>(
selector: Selector
selector: Selector,
options?: QueryOptions
): Promise<Array<ElementHandle<NodeFor<Selector>>>> {
// eslint-disable-next-line rulesdir/use-using -- This is cached.
const document = await this.#document();
return await document.$$(selector);
return await document.$$(selector, options);
}
/**

View File

@ -346,6 +346,21 @@ export interface ScreencastOptions {
ffmpegPath?: string;
}
/**
* @public
*/
export interface QueryOptions {
/**
* Whether to run the query in isolation. When returning many elements
* from {@link Page.$$} or similar methods, it might be useful to turn
* off the isolation to improve performance. By default, the querying
* code will be executed in a separate sandbox realm.
*
* @defaultValue `true`
*/
isolate: boolean;
}
/**
* All the events that a page instance may emit.
*
@ -1081,9 +1096,10 @@ export abstract class Page extends EventEmitter<PageEvents> {
* Shortcut for {@link Frame.$$ | Page.mainFrame().$$(selector) }.
*/
async $$<Selector extends string>(
selector: Selector
selector: Selector,
options?: QueryOptions
): Promise<Array<ElementHandle<NodeFor<Selector>>>> {
return await this.mainFrame().$$(selector);
return await this.mainFrame().$$(selector, options);
}
/**

View File

@ -167,6 +167,23 @@ describe('querySelector', function () {
});
expect(await Promise.all(promises)).toEqual(['A', 'B']);
});
it('should query existing elements without isolation', async () => {
const {page} = await getTestState();
await page.setContent('<div>A</div><br/><div>B</div>');
const elements = await page.$$('div', {
isolate: false,
});
expect(elements).toHaveLength(2);
const promises = elements.map(element => {
return page.evaluate(e => {
return e.textContent;
}, element);
});
expect(await Promise.all(promises)).toEqual(['A', 'B']);
});
it('should return empty array if nothing is found', async () => {
const {page, server} = await getTestState();