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

fix: use an xpath query handler #8730

Merged
merged 1 commit into from Aug 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
114 changes: 57 additions & 57 deletions docs/api/index.md

Large diffs are not rendered by default.

10 changes: 4 additions & 6 deletions docs/api/puppeteer.customqueryhandler.md
Expand Up @@ -4,8 +4,6 @@ sidebar_label: CustomQueryHandler

# CustomQueryHandler interface

Contains two functions `queryOne` and `queryAll` that can be [registered](./puppeteer.registercustomqueryhandler.md) as alternative querying strategies. The functions `queryOne` and `queryAll` are executed in the page context. `queryOne` should take an `Element` and a selector string as argument and return a single `Element` or `null` if no element is found. `queryAll` takes the same arguments but should instead return a `NodeListOf<Element>` or `Array<Element>` with all the elements that match the given query selector.

**Signature:**

```typescript
Expand All @@ -14,7 +12,7 @@ export interface CustomQueryHandler

## Properties

| Property | Modifiers | Type | Description |
| ------------------------------------------------------- | --------- | ---------------------------------------------------- | ----------------- |
| [queryAll?](./puppeteer.customqueryhandler.queryall.md) | | (element: Node, selector: string) =&gt; Node\[\] | <i>(Optional)</i> |
| [queryOne?](./puppeteer.customqueryhandler.queryone.md) | | (element: Node, selector: string) =&gt; Node \| null | <i>(Optional)</i> |
| Property | Modifiers | Type | Description |
| ------------------------------------------------------- | --------- | ------------------------------------------------- | ----------------- |
| [queryAll?](./puppeteer.customqueryhandler.queryall.md) | | (node: Node, selector: string) =&gt; Node\[\] | <i>(Optional)</i> |
| [queryOne?](./puppeteer.customqueryhandler.queryone.md) | | (node: Node, selector: string) =&gt; Node \| null | <i>(Optional)</i> |
2 changes: 1 addition & 1 deletion docs/api/puppeteer.customqueryhandler.queryall.md
Expand Up @@ -8,6 +8,6 @@ sidebar_label: CustomQueryHandler.queryAll

```typescript
interface CustomQueryHandler {
queryAll?: (element: Node, selector: string) => Node[];
queryAll?: (node: Node, selector: string) => Node[];
}
```
2 changes: 1 addition & 1 deletion docs/api/puppeteer.customqueryhandler.queryone.md
Expand Up @@ -8,6 +8,6 @@ sidebar_label: CustomQueryHandler.queryOne

```typescript
interface CustomQueryHandler {
queryOne?: (element: Node, selector: string) => Node | null;
queryOne?: (node: Node, selector: string) => Node | null;
}
```
6 changes: 5 additions & 1 deletion docs/api/puppeteer.elementhandle._x.md
Expand Up @@ -4,7 +4,11 @@ sidebar_label: ElementHandle.$x

# ElementHandle.$x() method

The method evaluates the XPath expression relative to the elementHandle. If there are no such elements, the method will resolve to an empty array.
> Warning: This API is now obsolete.
>
> Use [ElementHandle.$$()](./puppeteer.elementhandle.__.md) with the `xpath` prefix.
>
> The method evaluates the XPath expression relative to the elementHandle. If there are no such elements, the method will resolve to an empty array.

**Signature:**

Expand Down
80 changes: 29 additions & 51 deletions docs/api/puppeteer.elementhandle.md

Large diffs are not rendered by default.

54 changes: 29 additions & 25 deletions docs/api/puppeteer.elementhandle.waitforxpath.md
Expand Up @@ -4,31 +4,35 @@ sidebar_label: ElementHandle.waitForXPath

# ElementHandle.waitForXPath() method

Wait for the `xpath` within the element. If at the moment of calling the method the `xpath` already exists, the method will return immediately. If the `xpath` doesn't appear after the `timeout` milliseconds of waiting, the function will throw.

If `xpath` starts with `//` instead of `.//`, the dot will be appended automatically.

This method works across navigation

```ts
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
let currentURL;
page
.waitForXPath('//img')
.then(() => console.log('First URL with image: ' + currentURL));
for (currentURL of [
'https://example.com',
'https://google.com',
'https://bbc.com',
]) {
await page.goto(currentURL);
}
await browser.close();
})();
```
> Warning: This API is now obsolete.
>
> Use [ElementHandle.waitForSelector()](./puppeteer.elementhandle.waitforselector.md) with the `xpath` prefix.
>
> Wait for the `xpath` within the element. If at the moment of calling the method the `xpath` already exists, the method will return immediately. If the `xpath` doesn't appear after the `timeout` milliseconds of waiting, the function will throw.
>
> If `xpath` starts with `//` instead of `.//`, the dot will be appended automatically.
>
> This method works across navigation
>
> ```ts
> const puppeteer = require('puppeteer');
> (async () => {
> const browser = await puppeteer.launch();
> const page = await browser.newPage();
> let currentURL;
> page
> .waitForXPath('//img')
> .then(() => console.log('First URL with image: ' + currentURL));
> for (currentURL of [
> 'https://example.com',
> 'https://google.com',
> 'https://bbc.com',
> ]) {
> await page.goto(currentURL);
> }
> await browser.close();
> })();
> ```

**Signature:**

Expand Down
14 changes: 8 additions & 6 deletions docs/api/puppeteer.frame.waitforxpath.md
Expand Up @@ -4,6 +4,14 @@ sidebar_label: Frame.waitForXPath

# Frame.waitForXPath() method

> Warning: This API is now obsolete.
>
> Use [Frame.waitForSelector()](./puppeteer.frame.waitforselector.md) with the `xpath` prefix.
>
> Wait for the `xpath` to appear in page. If at the moment of calling the method the `xpath` already exists, the method will return immediately. If the xpath doesn't appear after the `timeout` milliseconds of waiting, the function will throw.
>
> For a code example, see the example for [Frame.waitForSelector()](./puppeteer.frame.waitforselector.md). That function behaves identically other than taking a CSS selector rather than an XPath.

**Signature:**

```typescript
Expand All @@ -25,9 +33,3 @@ class Frame {
**Returns:**

Promise&lt;[ElementHandle](./puppeteer.elementhandle.md)&lt;Node&gt; \| null&gt;

## Remarks

Wait for the `xpath` to appear in page. If at the moment of calling the method the `xpath` already exists, the method will return immediately. If the xpath doesn't appear after the `timeout` milliseconds of waiting, the function will throw.

For a code example, see the example for [Frame.waitForSelector()](./puppeteer.frame.waitforselector.md). That function behaves identically other than taking a CSS selector rather than an XPath.
2 changes: 1 addition & 1 deletion src/common/AriaQueryHandler.ts
Expand Up @@ -114,7 +114,7 @@ const waitFor = async (
return (await domWorld._waitForSelectorInPage(
(_: Element, selector: string) => {
return (
globalThis as any as unknown as {
globalThis as unknown as {
ariaQuerySelector(selector: string): void;
}
).ariaQuerySelector(selector);
Expand Down
46 changes: 0 additions & 46 deletions src/common/DOMWorld.ts
Expand Up @@ -717,52 +717,6 @@ export class DOMWorld {
return elementHandle;
}

async waitForXPath(
xpath: string,
options: WaitForSelectorOptions
): Promise<ElementHandle<Node> | null> {
const {
visible: waitForVisible = false,
hidden: waitForHidden = false,
timeout = this.#timeoutSettings.timeout(),
} = options;
const polling = waitForVisible || waitForHidden ? 'raf' : 'mutation';
const title = `XPath \`${xpath}\`${waitForHidden ? ' to be hidden' : ''}`;
function predicate(
root: Element | Document,
xpath: string,
waitForVisible: boolean,
waitForHidden: boolean
): Node | null | boolean {
const node = document.evaluate(
xpath,
root,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
).singleNodeValue;
return checkWaitForOptions(node, waitForVisible, waitForHidden);
}
const waitTaskOptions: WaitTaskOptions = {
domWorld: this,
predicateBody: makePredicateString(predicate),
predicateAcceptsContextElement: true,
title,
polling,
timeout,
args: [xpath, waitForVisible, waitForHidden],
root: options.root,
};
const waitTask = new WaitTask(waitTaskOptions);
const jsHandle = await waitTask.promise;
const elementHandle = jsHandle.asElement();
if (!elementHandle) {
await jsHandle.dispose();
return null;
}
return elementHandle;
}

waitForFunction(
pageFunction: Function | string,
options: {polling?: string | number; timeout?: number} = {},
Expand Down
54 changes: 10 additions & 44 deletions src/common/ElementHandle.ts
Expand Up @@ -140,6 +140,8 @@ export class ElementHandle<
}

/**
* @deprecated Use {@link ElementHandle.waitForSelector} with the `xpath` prefix.
*
* Wait for the `xpath` within the element. If at the moment of calling the
* method the `xpath` already exists, the method will return immediately. If
* the `xpath` doesn't appear after the `timeout` milliseconds of waiting, the
Expand Down Expand Up @@ -197,27 +199,10 @@ export class ElementHandle<
timeout?: number;
} = {}
): Promise<ElementHandle<Node> | null> {
const frame = this._context.frame();
assert(frame);
const secondaryContext = await frame._secondaryWorld.executionContext();
const adoptedRoot = await secondaryContext._adoptElementHandle(this);
xpath = xpath.startsWith('//') ? '.' + xpath : xpath;
if (!xpath.startsWith('.//')) {
await adoptedRoot.dispose();
throw new Error('Unsupported xpath expression: ' + xpath);
}
const handle = await frame._secondaryWorld.waitForXPath(xpath, {
...options,
root: adoptedRoot,
});
await adoptedRoot.dispose();
if (!handle) {
return null;
if (xpath.startsWith('//')) {
xpath = `.${xpath}`;
}
const mainExecutionContext = await frame._mainWorld.executionContext();
const result = await mainExecutionContext._adoptElementHandle(handle);
await handle.dispose();
return result;
return this.waitForSelector(`xpath/${xpath}`, options);
}

override asElement(): ElementHandle<ElementType> | null {
Expand Down Expand Up @@ -964,36 +949,17 @@ export class ElementHandle<
}

/**
* @deprecated Use {@link ElementHandle.$$} with the `xpath` prefix.
*
* The method evaluates the XPath expression relative to the elementHandle.
* If there are no such elements, the method will resolve to an empty array.
* @param expression - Expression to {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/evaluate | evaluate}
*/
async $x(expression: string): Promise<Array<ElementHandle<Node>>> {
const arrayHandle = await this.evaluateHandle((element, expression) => {
const doc = element.ownerDocument || document;
const iterator = doc.evaluate(
expression,
element,
null,
XPathResult.ORDERED_NODE_ITERATOR_TYPE
);
const array = [];
let item;
while ((item = iterator.iterateNext())) {
array.push(item);
}
return array;
}, expression);
const properties = await arrayHandle.getProperties();
await arrayHandle.dispose();
const result = [];
for (const property of properties.values()) {
const elementHandle = property.asElement();
if (elementHandle) {
result.push(elementHandle);
}
if (expression.startsWith('//')) {
expression = `.${expression}`;
}
return result;
return this.$$(`xpath/${expression}`);
}

/**
Expand Down
13 changes: 5 additions & 8 deletions src/common/FrameManager.ts
Expand Up @@ -1374,7 +1374,8 @@ export class Frame {
}

/**
* @remarks
* @deprecated Use {@link Frame.waitForSelector} with the `xpath` prefix.
*
* Wait for the `xpath` to appear in page. If at the moment of calling the
* method the `xpath` already exists, the method will return immediately. If
* the xpath doesn't appear after the `timeout` milliseconds of waiting, the
Expand All @@ -1392,14 +1393,10 @@ export class Frame {
xpath: string,
options: WaitForSelectorOptions = {}
): Promise<ElementHandle<Node> | null> {
const handle = await this._secondaryWorld.waitForXPath(xpath, options);
if (!handle) {
return null;
if (xpath.startsWith('//')) {
xpath = `.${xpath}`;
}
const mainExecutionContext = await this._mainWorld.executionContext();
const result = await mainExecutionContext._adoptElementHandle(handle);
await handle.dispose();
return result;
return this.waitForSelector(`xpath/${xpath}`, options);
}

/**
Expand Down