diff --git a/docs/api.md b/docs/api.md index f89e308aa9c52..bf64c81f3d111 100644 --- a/docs/api.md +++ b/docs/api.md @@ -983,7 +983,7 @@ the method will return an array with all the targets in all browser contexts. #### browser.waitForTarget(predicate[, options]) -- `predicate` <[function]\([Target]\):[boolean]> A function to be run for every target +- `predicate` <[function]\([Target]\):[boolean]|[Promise]> 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. @@ -1135,7 +1135,7 @@ 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 +- `predicate` <[function]\([Target]\):[boolean]|[Promise]> 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. diff --git a/experimental/puppeteer-firefox/lib/Browser.js b/experimental/puppeteer-firefox/lib/Browser.js index 5d2169fe3969d..fd234500e2c9c 100644 --- a/experimental/puppeteer-firefox/lib/Browser.js +++ b/experimental/puppeteer-firefox/lib/Browser.js @@ -114,7 +114,7 @@ class Browser extends EventEmitter { } /** - * @param {function(!Target):boolean} predicate + * @param {function(!Target):boolean|Promise} predicate * @param {{timeout?: number}=} options * @return {!Promise} */ @@ -122,9 +122,6 @@ class Browser extends EventEmitter { 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(Events.Browser.TargetCreated, check); @@ -132,7 +129,21 @@ class Browser extends EventEmitter { try { if (!timeout) return await targetPromise; - return await helper.waitWithTimeout(targetPromise, 'target', timeout); + return await helper.waitWithTimeout( + Promise.race([ + targetPromise, + (async () => { + for (const target of this.targets()) { + if (await predicate(target)) { + return target; + } + } + await targetPromise; + })(), + ]), + 'target', + timeout + ); } finally { this.removeListener(Events.Browser.TargetCreated, check); this.removeListener('targetchanged', check); @@ -141,8 +152,8 @@ class Browser extends EventEmitter { /** * @param {!Target} target */ - function check(target) { - if (predicate(target)) + async function check(target) { + if (await predicate(target)) resolve(target); } } @@ -334,7 +345,7 @@ class BrowserContext extends EventEmitter { } /** - * @param {function(Target):boolean} predicate + * @param {function(Target):boolean|Promise} predicate * @param {{timeout?: number}=} options * @return {!Promise} */ diff --git a/src/common/Browser.ts b/src/common/Browser.ts index 497615e996acd..36dde2bf79dad 100644 --- a/src/common/Browser.ts +++ b/src/common/Browser.ts @@ -539,12 +539,10 @@ export class Browser extends EventEmitter { * ``` */ async waitForTarget( - predicate: (x: Target) => boolean, + predicate: (x: Target) => boolean | Promise, options: WaitForTargetOptions = {} ): Promise { const { timeout = 30000 } = options; - const existingTarget = this.targets().find(predicate); - if (existingTarget) return existingTarget; let resolve: (value: Target | PromiseLike) => void; const targetPromise = new Promise((x) => (resolve = x)); this.on(BrowserEmittedEvents.TargetCreated, check); @@ -552,7 +550,17 @@ export class Browser extends EventEmitter { try { if (!timeout) return await targetPromise; return await helper.waitWithTimeout( - targetPromise, + Promise.race([ + targetPromise, + (async () => { + for (const target of this.targets()) { + if (await predicate(target)) { + return target; + } + } + await targetPromise; + })(), + ]), 'target', timeout ); @@ -561,8 +569,8 @@ export class Browser extends EventEmitter { this.removeListener(BrowserEmittedEvents.TargetChanged, check); } - function check(target: Target): void { - if (predicate(target)) resolve(target); + async function check(target: Target): Promise { + if (await predicate(target)) resolve(target); } } @@ -736,7 +744,7 @@ export class BrowserContext extends EventEmitter { * that matches the `predicate` function. */ waitForTarget( - predicate: (x: Target) => boolean, + predicate: (x: Target) => boolean | Promise, options: { timeout?: number } = {} ): Promise { return this._browser.waitForTarget( diff --git a/test/target.spec.ts b/test/target.spec.ts index 918510f1f0de4..d1081d65f838b 100644 --- a/test/target.spec.ts +++ b/test/target.spec.ts @@ -67,6 +67,30 @@ describe('Target', function () { ).toBe('Hello world'); expect(await originalPage.$('body')).toBeTruthy(); }); + itFailsFirefox('should be able to use async waitForTarget', async () => { + const { page, server, context } = getTestState(); + + const [otherPage] = await Promise.all([ + context + .waitForTarget((target) => + target + .page() + .then( + (page) => + page.url() === server.CROSS_PROCESS_PREFIX + '/empty.html' + ) + ) + .then((target) => target.page()), + page.evaluate( + (url: string) => window.open(url), + server.CROSS_PROCESS_PREFIX + '/empty.html' + ), + ]); + expect(otherPage.url()).toEqual( + server.CROSS_PROCESS_PREFIX + '/empty.html' + ); + expect(page).not.toEqual(otherPage); + }); itFailsFirefox( 'should report when a new page is created and closed', async () => {