refactor: use RxJS for waitForTarget (#11029)
This commit is contained in:
parent
d63f0cfc61
commit
c8feb3e472
@ -14,9 +14,7 @@ This will look all open [browser contexts](./puppeteer.browsercontext.md).
|
|||||||
class BrowserContext {
|
class BrowserContext {
|
||||||
abstract waitForTarget(
|
abstract waitForTarget(
|
||||||
predicate: (x: Target) => boolean | Promise<boolean>,
|
predicate: (x: Target) => boolean | Promise<boolean>,
|
||||||
options?: {
|
options?: WaitForTargetOptions
|
||||||
timeout?: number;
|
|
||||||
}
|
|
||||||
): Promise<Target>;
|
): Promise<Target>;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -26,7 +24,7 @@ class BrowserContext {
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---------------------------------------------------------------------------- | ------------ |
|
| --------- | ---------------------------------------------------------------------------- | ------------ |
|
||||||
| predicate | (x: [Target](./puppeteer.target.md)) => boolean \| Promise<boolean> | |
|
| predicate | (x: [Target](./puppeteer.target.md)) => boolean \| Promise<boolean> | |
|
||||||
| options | { timeout?: number; } | _(Optional)_ |
|
| options | [WaitForTargetOptions](./puppeteer.waitfortargetoptions.md) | _(Optional)_ |
|
||||||
|
|
||||||
**Returns:**
|
**Returns:**
|
||||||
|
|
||||||
|
@ -18,15 +18,23 @@ import type {ChildProcess} from 'child_process';
|
|||||||
|
|
||||||
import type {Protocol} from 'devtools-protocol';
|
import type {Protocol} from 'devtools-protocol';
|
||||||
|
|
||||||
|
import {
|
||||||
|
firstValueFrom,
|
||||||
|
from,
|
||||||
|
merge,
|
||||||
|
raceWith,
|
||||||
|
filterAsync,
|
||||||
|
fromEvent,
|
||||||
|
type Observable,
|
||||||
|
} from '../../third_party/rxjs/rxjs.js';
|
||||||
import {EventEmitter, type EventType} from '../common/EventEmitter.js';
|
import {EventEmitter, type EventType} from '../common/EventEmitter.js';
|
||||||
import {debugError, waitWithTimeout} from '../common/util.js';
|
import {debugError} from '../common/util.js';
|
||||||
import {Deferred} from '../util/Deferred.js';
|
import {timeout} from '../common/util.js';
|
||||||
import {asyncDisposeSymbol, disposeSymbol} from '../util/disposable.js';
|
import {asyncDisposeSymbol, disposeSymbol} from '../util/disposable.js';
|
||||||
|
|
||||||
import type {BrowserContext} from './BrowserContext.js';
|
import type {BrowserContext} from './BrowserContext.js';
|
||||||
import type {Page} from './Page.js';
|
import type {Page} from './Page.js';
|
||||||
import type {Target} from './Target.js';
|
import type {Target} from './Target.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -387,31 +395,14 @@ export abstract class Browser extends EventEmitter<BrowserEvents> {
|
|||||||
predicate: (x: Target) => boolean | Promise<boolean>,
|
predicate: (x: Target) => boolean | Promise<boolean>,
|
||||||
options: WaitForTargetOptions = {}
|
options: WaitForTargetOptions = {}
|
||||||
): Promise<Target> {
|
): Promise<Target> {
|
||||||
const {timeout = 30000} = options;
|
const {timeout: ms = 30000} = options;
|
||||||
const targetDeferred = Deferred.create<Target | PromiseLike<Target>>();
|
return await firstValueFrom(
|
||||||
|
merge(
|
||||||
this.on(BrowserEvent.TargetCreated, check);
|
fromEvent(this, BrowserEvent.TargetCreated) as Observable<Target>,
|
||||||
this.on(BrowserEvent.TargetChanged, check);
|
fromEvent(this, BrowserEvent.TargetChanged) as Observable<Target>,
|
||||||
try {
|
from(this.targets())
|
||||||
this.targets().forEach(check);
|
).pipe(filterAsync(predicate), raceWith(timeout(ms)))
|
||||||
if (!timeout) {
|
);
|
||||||
return await targetDeferred.valueOrThrow();
|
|
||||||
}
|
|
||||||
return await waitWithTimeout(
|
|
||||||
targetDeferred.valueOrThrow(),
|
|
||||||
'target',
|
|
||||||
timeout
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
this.off(BrowserEvent.TargetCreated, check);
|
|
||||||
this.off(BrowserEvent.TargetChanged, check);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function check(target: Target): Promise<void> {
|
|
||||||
if ((await predicate(target)) && !targetDeferred.resolved()) {
|
|
||||||
targetDeferred.resolve(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,7 +18,7 @@ import {EventEmitter, type EventType} from '../common/EventEmitter.js';
|
|||||||
import {debugError} from '../common/util.js';
|
import {debugError} from '../common/util.js';
|
||||||
import {asyncDisposeSymbol, disposeSymbol} from '../util/disposable.js';
|
import {asyncDisposeSymbol, disposeSymbol} from '../util/disposable.js';
|
||||||
|
|
||||||
import type {Browser, Permission} from './Browser.js';
|
import type {Browser, Permission, WaitForTargetOptions} from './Browser.js';
|
||||||
import type {Page} from './Page.js';
|
import type {Page} from './Page.js';
|
||||||
import type {Target} from './Target.js';
|
import type {Target} from './Target.js';
|
||||||
|
|
||||||
@ -128,7 +128,7 @@ export abstract class BrowserContext extends EventEmitter<BrowserContextEvents>
|
|||||||
*/
|
*/
|
||||||
abstract waitForTarget(
|
abstract waitForTarget(
|
||||||
predicate: (x: Target) => boolean | Promise<boolean>,
|
predicate: (x: Target) => boolean | Promise<boolean>,
|
||||||
options?: {timeout?: number}
|
options?: WaitForTargetOptions
|
||||||
): Promise<Target>;
|
): Promise<Target>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||||
|
|
||||||
|
import type {WaitForTargetOptions} from '../api/Browser.js';
|
||||||
import {BrowserContext} from '../api/BrowserContext.js';
|
import {BrowserContext} from '../api/BrowserContext.js';
|
||||||
import type {Page} from '../api/Page.js';
|
import type {Page} from '../api/Page.js';
|
||||||
import type {Target} from '../api/Target.js';
|
import type {Target} from '../api/Target.js';
|
||||||
@ -58,7 +59,7 @@ export class BidiBrowserContext extends BrowserContext {
|
|||||||
|
|
||||||
override waitForTarget(
|
override waitForTarget(
|
||||||
predicate: (x: Target) => boolean | Promise<boolean>,
|
predicate: (x: Target) => boolean | Promise<boolean>,
|
||||||
options: {timeout?: number} = {}
|
options: WaitForTargetOptions = {}
|
||||||
): Promise<Target> {
|
): Promise<Target> {
|
||||||
return this.#browser.waitForTarget(target => {
|
return this.#browser.waitForTarget(target => {
|
||||||
return target.browserContext() === this && predicate(target);
|
return target.browserContext() === this && predicate(target);
|
||||||
|
@ -288,8 +288,9 @@ export class BidiConnection extends EventEmitter<BidiEvents> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.#closed = true;
|
this.#closed = true;
|
||||||
this.#transport.onmessage = undefined;
|
// Both may still be invoked and produce errors
|
||||||
this.#transport.onclose = undefined;
|
this.#transport.onmessage = () => {};
|
||||||
|
this.#transport.onclose = () => {};
|
||||||
|
|
||||||
this.#callbacks.clear();
|
this.#callbacks.clear();
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import {
|
|||||||
type IsPageTargetCallback,
|
type IsPageTargetCallback,
|
||||||
type Permission,
|
type Permission,
|
||||||
type TargetFilterCallback,
|
type TargetFilterCallback,
|
||||||
|
type WaitForTargetOptions,
|
||||||
} from '../api/Browser.js';
|
} from '../api/Browser.js';
|
||||||
import {BrowserContext, BrowserContextEvent} from '../api/BrowserContext.js';
|
import {BrowserContext, BrowserContextEvent} from '../api/BrowserContext.js';
|
||||||
import {CDPSessionEvent, type CDPSession} from '../api/CDPSession.js';
|
import {CDPSessionEvent, type CDPSession} from '../api/CDPSession.js';
|
||||||
@ -453,7 +454,7 @@ export class CdpBrowserContext extends BrowserContext {
|
|||||||
|
|
||||||
override waitForTarget(
|
override waitForTarget(
|
||||||
predicate: (x: Target) => boolean | Promise<boolean>,
|
predicate: (x: Target) => boolean | Promise<boolean>,
|
||||||
options: {timeout?: number} = {}
|
options: WaitForTargetOptions = {}
|
||||||
): Promise<Target> {
|
): Promise<Target> {
|
||||||
return this.#browser.waitForTarget(target => {
|
return this.#browser.waitForTarget(target => {
|
||||||
return target.browserContext() === this && predicate(target);
|
return target.browserContext() === this && predicate(target);
|
||||||
|
@ -19,6 +19,7 @@ import type {ServerResponse} from 'http';
|
|||||||
import expect from 'expect';
|
import expect from 'expect';
|
||||||
import {type Frame, TimeoutError} from 'puppeteer';
|
import {type Frame, TimeoutError} from 'puppeteer';
|
||||||
import type {HTTPRequest} from 'puppeteer-core/internal/api/HTTPRequest.js';
|
import type {HTTPRequest} from 'puppeteer-core/internal/api/HTTPRequest.js';
|
||||||
|
import type {HTTPResponse} from 'puppeteer-core/internal/api/HTTPResponse.js';
|
||||||
import {Deferred} from 'puppeteer-core/internal/util/Deferred.js';
|
import {Deferred} from 'puppeteer-core/internal/util/Deferred.js';
|
||||||
|
|
||||||
import {getTestState, setupTestBrowserHooks} from './mocha-utils.js';
|
import {getTestState, setupTestBrowserHooks} from './mocha-utils.js';
|
||||||
@ -837,18 +838,27 @@ describe('navigation', function () {
|
|||||||
server.setRoute('/one-style.html', (_req, res) => {
|
server.setRoute('/one-style.html', (_req, res) => {
|
||||||
return serverResponses.push(res);
|
return serverResponses.push(res);
|
||||||
});
|
});
|
||||||
const navigations = [];
|
const navigations: Array<Promise<HTTPResponse | null>> = [];
|
||||||
for (let i = 0; i < 3; ++i) {
|
for (let i = 0; i < 3; ++i) {
|
||||||
navigations.push(frames[i]!.goto(server.PREFIX + '/one-style.html'));
|
navigations.push(frames[i]!.goto(server.PREFIX + '/one-style.html'));
|
||||||
await server.waitForRequest('/one-style.html');
|
await server.waitForRequest('/one-style.html');
|
||||||
}
|
}
|
||||||
// Respond from server out-of-order.
|
// Respond from server out-of-order.
|
||||||
const serverResponseTexts = ['AAA', 'BBB', 'CCC'];
|
const serverResponseTexts = ['AAA', 'BBB', 'CCC'];
|
||||||
for (const i of [1, 2, 0]) {
|
try {
|
||||||
serverResponses[i]!.end(serverResponseTexts[i]);
|
for (const i of [1, 2, 0]) {
|
||||||
const response = (await navigations[i])!;
|
const response = await getResponse(i);
|
||||||
expect(response.frame()).toBe(frames[i]);
|
expect(response.frame()).toBe(frames[i]);
|
||||||
expect(await response.text()).toBe(serverResponseTexts[i]);
|
expect(await response.text()).toBe(serverResponseTexts[i]);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
await Promise.all([getResponse(0), getResponse(1), getResponse(2)]);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getResponse(index: number) {
|
||||||
|
serverResponses[index]!.end(serverResponseTexts[index]);
|
||||||
|
return (await navigations[index])!;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user