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

feat: browser.waitForTarget #3356

Merged
merged 3 commits into from
Oct 9, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
14 changes: 14 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
* [browser.targets()](#browsertargets)
* [browser.userAgent()](#browseruseragent)
* [browser.version()](#browserversion)
* [browser.waitForTarget(predicate[, options])](#browserwaitfortargetpredicate-options)
* [browser.wsEndpoint()](#browserwsendpoint)
- [class: BrowserContext](#class-browsercontext)
* [event: 'targetchanged'](#event-targetchanged-1)
Expand All @@ -65,6 +66,7 @@
* [browserContext.overridePermissions(origin, permissions)](#browsercontextoverridepermissionsorigin-permissions)
* [browserContext.pages()](#browsercontextpages)
* [browserContext.targets()](#browsercontexttargets)
* [browserContext.waitForTarget(predicate[, options])](#browsercontextwaitfortargetpredicate-options)
- [class: Page](#class-page)
* [event: 'close'](#event-close)
* [event: 'console'](#event-console)
Expand Down Expand Up @@ -699,6 +701,12 @@ 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.

#### browser.waitForTarget(predicate[, options])
- `predicate` <[function]\([Target]\):[boolean]> A function to be run for every target
- `options` <[Object]>
- `timeout` <[number]> <[number]> Maximum wait time in milliseconds. Pass `0` to disable the timeout. Defaults to 30 seconds.
JoelEinbinder marked this conversation as resolved.
Show resolved Hide resolved
- returns: <[Promise]<[Target]>> Promise which resolves to the first target found that matches the `predicate` function.

JoelEinbinder marked this conversation as resolved.
Show resolved Hide resolved
#### browser.wsEndpoint()
- returns: <[string]> Browser websocket url.

Expand Down Expand Up @@ -823,6 +831,12 @@ An array of all pages 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]> <[number]> Maximum wait time in milliseconds. Pass `0` to disable the timeout. Defaults to 30 seconds.
JoelEinbinder marked this conversation as resolved.
Show resolved Hide resolved
- returns: <[Promise]<[Target]>> Promise which resolves to the first target found that matches the `predicate` function.
JoelEinbinder marked this conversation as resolved.
Show resolved Hide resolved

### class: Page

* extends: [`EventEmitter`](https://nodejs.org/api/events.html#events_class_eventemitter)
Expand Down
38 changes: 38 additions & 0 deletions lib/Browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,36 @@ class Browser extends EventEmitter {
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);
let target;
try {
target = await (timeout ? helper.waitWithTimeout(targetPromise, 'target', timeout) : targetPromise);
JoelEinbinder marked this conversation as resolved.
Show resolved Hide resolved
} finally {
this.removeListener(Browser.Events.TargetCreated, check);
this.removeListener(Browser.Events.TargetChanged, check);
}
return target;

function check(target) {
JoelEinbinder marked this conversation as resolved.
Show resolved Hide resolved
if (predicate(target))
resolve(target);
}
}

/**
* @return {!Promise<!Array<!Puppeteer.Page>>}
*/
Expand Down Expand Up @@ -262,6 +292,14 @@ class BrowserContext extends EventEmitter {
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>>}
*/
Expand Down
22 changes: 22 additions & 0 deletions lib/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,28 @@ class Helper {
}
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 timeoutInterval = setTimeout(() => reject(timeoutError), timeout);
/** @type {T} */
let value;
try {
value = await Promise.race([promise, timeoutPromise]);
JoelEinbinder marked this conversation as resolved.
Show resolved Hide resolved
} finally {
clearInterval(timeoutInterval);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clearTimeout

}
return value;
}
}

/**
Expand Down
20 changes: 20 additions & 0 deletions test/browsercontext.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

const utils = require('./utils');
const puppeteer = utils.requireRoot('index');
const {TimeoutError} = utils.requireRoot('Errors');

module.exports.addTests = function({testRunner, expect}) {
const {describe, xdescribe, fdescribe} = testRunner;
Expand Down Expand Up @@ -79,6 +80,25 @@ module.exports.addTests = function({testRunner, expect}) {
]);
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 page.close();
JoelEinbinder marked this conversation as resolved.
Show resolved Hide resolved
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}) {
// Create two incognito contexts.
const context1 = await browser.createIncognitoBrowserContext();
Expand Down
2 changes: 1 addition & 1 deletion test/target.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ module.exports.addTests = function({testRunner, expect}) {
server.waitForRequest('/one-style.css')
]);
// 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();
// Issue a redirect.
serverResponse.writeHead(302, { location: '/injectedstyle.css' });
Expand Down