feat: add waitForXPath to ElementHandle (#8329)

This commit is contained in:
Alex Rudenko 2022-05-10 12:45:20 +02:00 committed by GitHub
parent 1e82d98027
commit 7eaadafe19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 145 additions and 0 deletions

View File

@ -342,6 +342,7 @@
* [elementHandle.type(text[, options])](#elementhandletypetext-options)
* [elementHandle.uploadFile(...filePaths)](#elementhandleuploadfilefilepaths)
* [elementHandle.waitForSelector(selector[, options])](#elementhandlewaitforselectorselector-options)
* [elementHandle.waitForXPath(xpath[, options])](#elementhandlewaitforxpathxpath-options)
- [class: HTTPRequest](#class-httprequest)
* [httpRequest.abort([errorCode], [priority])](#httprequestaborterrorcode-priority)
* [httpRequest.abortErrorReason()](#httprequestaborterrorreason)
@ -4902,6 +4903,44 @@ Wait for an element matching `selector` to appear within the `elementHandle`s
This method does not work across navigations or if the element is detached from DOM.
#### elementHandle.waitForXPath(xpath[, options])
- `xpath` <[string]> A [xpath] of an element to wait for
- `options` <[Object]> Optional waiting parameters
- `visible` <[boolean]> wait for element to be present in DOM and to be visible, i.e. to not have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`.
- `hidden` <[boolean]> wait for element to not be found in the DOM or to be hidden, i.e. have `display: none` or `visibility: hidden` CSS properties. Defaults to `false`.
- `timeout` <[number]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method.
- returns: <[Promise]<?[ElementHandle]>> Promise which resolves when element specified by xpath string is added to DOM. Resolves to `null` if waiting for `hidden: true` and xpath is not found in DOM.
Wait for the `xpath` to appear 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 navigations:
```js
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();
})();
```
### class: HTTPRequest
Whenever the page sends a request, such as for a network resource, the following events are emitted by Puppeteer's page:

View File

@ -416,6 +416,84 @@ export class ElementHandle<
return result;
}
/**
* 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
* ```js
* 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();
* })();
* ```
* @param xpath - A
* {@link https://developer.mozilla.org/en-US/docs/Web/XPath | xpath} of an
* element to wait for
* @param options - Optional waiting parameters
* @returns Promise which resolves when element specified by xpath string is
* added to DOM. Resolves to `null` if waiting for `hidden: true` and xpath is
* not found in DOM.
* @remarks
* The optional Argument `options` have properties:
*
* - `visible`: A boolean to wait for element to be present in DOM and to be
* visible, i.e. to not have `display: none` or `visibility: hidden` CSS
* properties. Defaults to `false`.
*
* - `hidden`: A boolean wait for element to not be found in the DOM or to be
* hidden, i.e. have `display: none` or `visibility: hidden` CSS properties.
* Defaults to `false`.
*
* - `timeout`: A number which is maximum time to wait for in milliseconds.
* Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default
* value can be changed by using the {@link Page.setDefaultTimeout} method.
*/
async waitForXPath(
xpath: string,
options: {
visible?: boolean;
hidden?: boolean;
timeout?: number;
} = {}
): Promise<ElementHandle | null> {
const frame = this._context.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;
const mainExecutionContext = await frame._mainWorld.executionContext();
const result = await mainExecutionContext._adoptElementHandle(handle);
await handle.dispose();
return result;
}
asElement(): ElementHandle<ElementType> | null {
return this;
}

View File

@ -280,6 +280,34 @@ describe('ElementHandle specs', function () {
});
});
describe('Element.waitForXPath', () => {
it('should wait correctly with waitForXPath on an element', async () => {
const { page } = getTestState();
// Set the page content after the waitFor has been started.
await page.setContent(
`<div id=el1>
el1
<div id=el2>
el2
</div>
</div>
<div id=el3>
el3
</div>`
);
const el2 = await page.waitForSelector('#el1');
expect(
await (await el2.waitForXPath('//div')).evaluate((el) => el.id)
).toStrictEqual('el2');
expect(
await (await el2.waitForXPath('.//div')).evaluate((el) => el.id)
).toStrictEqual('el2');
});
});
describe('ElementHandle.hover', function () {
it('should work', async () => {
const { page, server } = getTestState();