feat: browser.waitForTarget (#3356)
This adds `browser.waitForTarget` and `browserContext.waitForTarget`. It also fixes a flaky test that was incorrectly expecting targets to appear instantly.
This commit is contained in:
parent
07febb637c
commit
6ac66c3547
30
docs/api.md
30
docs/api.md
@ -52,6 +52,7 @@
|
|||||||
* [browser.targets()](#browsertargets)
|
* [browser.targets()](#browsertargets)
|
||||||
* [browser.userAgent()](#browseruseragent)
|
* [browser.userAgent()](#browseruseragent)
|
||||||
* [browser.version()](#browserversion)
|
* [browser.version()](#browserversion)
|
||||||
|
* [browser.waitForTarget(predicate[, options])](#browserwaitfortargetpredicate-options)
|
||||||
* [browser.wsEndpoint()](#browserwsendpoint)
|
* [browser.wsEndpoint()](#browserwsendpoint)
|
||||||
- [class: BrowserContext](#class-browsercontext)
|
- [class: BrowserContext](#class-browsercontext)
|
||||||
* [event: 'targetchanged'](#event-targetchanged-1)
|
* [event: 'targetchanged'](#event-targetchanged-1)
|
||||||
@ -65,6 +66,7 @@
|
|||||||
* [browserContext.overridePermissions(origin, permissions)](#browsercontextoverridepermissionsorigin-permissions)
|
* [browserContext.overridePermissions(origin, permissions)](#browsercontextoverridepermissionsorigin-permissions)
|
||||||
* [browserContext.pages()](#browsercontextpages)
|
* [browserContext.pages()](#browsercontextpages)
|
||||||
* [browserContext.targets()](#browsercontexttargets)
|
* [browserContext.targets()](#browsercontexttargets)
|
||||||
|
* [browserContext.waitForTarget(predicate[, options])](#browsercontextwaitfortargetpredicate-options)
|
||||||
- [class: Page](#class-page)
|
- [class: Page](#class-page)
|
||||||
* [event: 'close'](#event-close)
|
* [event: 'close'](#event-close)
|
||||||
* [event: 'console'](#event-console)
|
* [event: 'console'](#event-console)
|
||||||
@ -699,6 +701,20 @@ the method will return an array with all the targets in all browser contexts.
|
|||||||
|
|
||||||
> **NOTE** the format of browser.version() might change with future releases of Chromium.
|
> **NOTE** the format of browser.version() might change with future releases of Chromium.
|
||||||
|
|
||||||
|
#### browser.waitForTarget(predicate[, options])
|
||||||
|
- `predicate` <[function]\([Target]\):[boolean]> A function to be run for every target
|
||||||
|
- `options` <[Object]>
|
||||||
|
- `timeout` <[number]> Maximum wait time in milliseconds. Pass `0` to disable the timeout. Defaults to 30 seconds.
|
||||||
|
- returns: <[Promise]<[Target]>> Promise which resolves to the first target found that matches the `predicate` function.
|
||||||
|
|
||||||
|
This searches for a target in all browser contexts.
|
||||||
|
|
||||||
|
An example of finding a target for a page opened via `window.open`:
|
||||||
|
```js
|
||||||
|
await page.evaluate(() => window.open('https://www.example.com/'));
|
||||||
|
const newWindowTarget = await browser.waitForTarget(target => target.url() === 'https://www.example.com/');
|
||||||
|
```
|
||||||
|
|
||||||
#### browser.wsEndpoint()
|
#### browser.wsEndpoint()
|
||||||
- returns: <[string]> Browser websocket url.
|
- returns: <[string]> Browser websocket url.
|
||||||
|
|
||||||
@ -823,6 +839,20 @@ An array of all pages inside the browser context.
|
|||||||
|
|
||||||
An array of all active targets inside the browser context.
|
An array of all active targets inside the browser context.
|
||||||
|
|
||||||
|
#### browserContext.waitForTarget(predicate[, options])
|
||||||
|
- `predicate` <[function]\([Target]\):[boolean]> A function to be run for every target
|
||||||
|
- `options` <[Object]>
|
||||||
|
- `timeout` <[number]> Maximum wait time in milliseconds. Pass `0` to disable the timeout. Defaults to 30 seconds.
|
||||||
|
- returns: <[Promise]<[Target]>> Promise which resolves to the first target found that matches the `predicate` function.
|
||||||
|
|
||||||
|
This searches for a target in this specific browser context.
|
||||||
|
|
||||||
|
An example of finding a target for a page opened via `window.open`:
|
||||||
|
```js
|
||||||
|
await page.evaluate(() => window.open('https://www.example.com/'));
|
||||||
|
const newWindowTarget = await browserContext.waitForTarget(target => target.url() === 'https://www.example.com/');
|
||||||
|
```
|
||||||
|
|
||||||
### class: Page
|
### class: Page
|
||||||
|
|
||||||
* extends: [`EventEmitter`](https://nodejs.org/api/events.html#events_class_eventemitter)
|
* extends: [`EventEmitter`](https://nodejs.org/api/events.html#events_class_eventemitter)
|
||||||
|
@ -192,6 +192,39 @@ class Browser extends EventEmitter {
|
|||||||
return this.targets().find(target => target.type() === 'browser');
|
return this.targets().find(target => target.type() === 'browser');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {function(!Target):boolean} predicate
|
||||||
|
* @param {{timeout?: number}=} options
|
||||||
|
*/
|
||||||
|
async waitForTarget(predicate, options = {}) {
|
||||||
|
const {
|
||||||
|
timeout = 30000
|
||||||
|
} = options;
|
||||||
|
const existingTarget = this.targets().find(predicate);
|
||||||
|
if (existingTarget)
|
||||||
|
return existingTarget;
|
||||||
|
let resolve;
|
||||||
|
const targetPromise = new Promise(x => resolve = x);
|
||||||
|
this.on(Browser.Events.TargetCreated, check);
|
||||||
|
this.on(Browser.Events.TargetChanged, check);
|
||||||
|
try {
|
||||||
|
if (!timeout)
|
||||||
|
return await targetPromise;
|
||||||
|
return await helper.waitWithTimeout(targetPromise, 'target', timeout);
|
||||||
|
} finally {
|
||||||
|
this.removeListener(Browser.Events.TargetCreated, check);
|
||||||
|
this.removeListener(Browser.Events.TargetChanged, check);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {!Target} target
|
||||||
|
*/
|
||||||
|
function check(target) {
|
||||||
|
if (predicate(target))
|
||||||
|
resolve(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {!Promise<!Array<!Puppeteer.Page>>}
|
* @return {!Promise<!Array<!Puppeteer.Page>>}
|
||||||
*/
|
*/
|
||||||
@ -262,6 +295,14 @@ class BrowserContext extends EventEmitter {
|
|||||||
return this._browser.targets().filter(target => target.browserContext() === this);
|
return this._browser.targets().filter(target => target.browserContext() === this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {function(!Target):boolean} predicate
|
||||||
|
* @param {{timeout?: number}=} options
|
||||||
|
*/
|
||||||
|
waitForTarget(predicate, options) {
|
||||||
|
return this._browser.waitForTarget(target => target.browserContext() === this && predicate(target), options);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {!Promise<!Array<!Puppeteer.Page>>}
|
* @return {!Promise<!Array<!Puppeteer.Page>>}
|
||||||
*/
|
*/
|
||||||
|
@ -248,6 +248,25 @@ class Helper {
|
|||||||
}
|
}
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {!Promise<T>} promise
|
||||||
|
* @param {string} taskName
|
||||||
|
* @param {number} timeout
|
||||||
|
* @return {!Promise<T>}
|
||||||
|
*/
|
||||||
|
static async waitWithTimeout(promise, taskName, timeout) {
|
||||||
|
let reject;
|
||||||
|
const timeoutError = new TimeoutError(`waiting for ${taskName} failed: timeout ${timeout}ms exceeded`);
|
||||||
|
const timeoutPromise = new Promise((resolve, x) => reject = x);
|
||||||
|
const timeoutTimer = setTimeout(() => reject(timeoutError), timeout);
|
||||||
|
try {
|
||||||
|
return await Promise.race([promise, timeoutPromise]);
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timeoutTimer);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
const puppeteer = utils.requireRoot('index');
|
const puppeteer = utils.requireRoot('index');
|
||||||
|
const {TimeoutError} = utils.requireRoot('Errors');
|
||||||
|
|
||||||
module.exports.addTests = function({testRunner, expect}) {
|
module.exports.addTests = function({testRunner, expect}) {
|
||||||
const {describe, xdescribe, fdescribe} = testRunner;
|
const {describe, xdescribe, fdescribe} = testRunner;
|
||||||
@ -79,6 +80,24 @@ module.exports.addTests = function({testRunner, expect}) {
|
|||||||
]);
|
]);
|
||||||
await context.close();
|
await context.close();
|
||||||
});
|
});
|
||||||
|
it('should wait for a target', async function({browser, server}) {
|
||||||
|
const context = await browser.createIncognitoBrowserContext();
|
||||||
|
let resolved = false;
|
||||||
|
const targetPromise = context.waitForTarget(target => target.url() === server.EMPTY_PAGE);
|
||||||
|
targetPromise.then(() => resolved = true);
|
||||||
|
const page = await context.newPage();
|
||||||
|
expect(resolved).toBe(false);
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
const target = await targetPromise;
|
||||||
|
expect(await target.page()).toBe(page);
|
||||||
|
await context.close();
|
||||||
|
});
|
||||||
|
it('should timeout waiting for a non-existent target', async function({browser, server}) {
|
||||||
|
const context = await browser.createIncognitoBrowserContext();
|
||||||
|
const error = await context.waitForTarget(target => target.url() === server.EMPTY_PAGE, {timeout: 1}).catch(e => e);
|
||||||
|
expect(error).toBeInstanceOf(TimeoutError);
|
||||||
|
await context.close();
|
||||||
|
});
|
||||||
it('should isolate localStorage and cookies', async function({browser, server}) {
|
it('should isolate localStorage and cookies', async function({browser, server}) {
|
||||||
// Create two incognito contexts.
|
// Create two incognito contexts.
|
||||||
const context1 = await browser.createIncognitoBrowserContext();
|
const context1 = await browser.createIncognitoBrowserContext();
|
||||||
|
@ -121,7 +121,7 @@ module.exports.addTests = function({testRunner, expect}) {
|
|||||||
server.waitForRequest('/one-style.css')
|
server.waitForRequest('/one-style.css')
|
||||||
]);
|
]);
|
||||||
// Connect to the opened page.
|
// Connect to the opened page.
|
||||||
const target = context.targets().find(target => target.url().includes('one-style.html'));
|
const target = await context.waitForTarget(target => target.url().includes('one-style.html'));
|
||||||
const newPage = await target.page();
|
const newPage = await target.page();
|
||||||
// Issue a redirect.
|
// Issue a redirect.
|
||||||
serverResponse.writeHead(302, { location: '/injectedstyle.css' });
|
serverResponse.writeHead(302, { location: '/injectedstyle.css' });
|
||||||
|
Loading…
Reference in New Issue
Block a user