feat: add support for async waitForTarget (#7885)

* feat: add support for async waitForTarget

* fix: add timeout

* fix: potential async bugs
This commit is contained in:
Tmk 2022-02-18 19:05:29 +08:00 committed by GitHub
parent 543a4d44bc
commit dbf0639822
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 60 additions and 17 deletions

View File

@ -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<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.
@ -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<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.

View File

@ -114,7 +114,7 @@ class Browser extends EventEmitter {
}
/**
* @param {function(!Target):boolean} predicate
* @param {function(!Target):boolean|Promise<boolean>} predicate
* @param {{timeout?: number}=} options
* @return {!Promise<!Target>}
*/
@ -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<boolean>} predicate
* @param {{timeout?: number}=} options
* @return {!Promise<Target>}
*/

View File

@ -539,12 +539,10 @@ export class Browser extends EventEmitter {
* ```
*/
async waitForTarget(
predicate: (x: Target) => boolean,
predicate: (x: Target) => boolean | Promise<boolean>,
options: WaitForTargetOptions = {}
): Promise<Target> {
const { timeout = 30000 } = options;
const existingTarget = this.targets().find(predicate);
if (existingTarget) return existingTarget;
let resolve: (value: Target | PromiseLike<Target>) => void;
const targetPromise = new Promise<Target>((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<Target>(
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<void> {
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<boolean>,
options: { timeout?: number } = {}
): Promise<Target> {
return this._browser.waitForTarget(

View File

@ -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 () => {