chore: add support for waitForNetworkIdle (#10261)
This commit is contained in:
parent
2741b76d30
commit
b03acac30f
2
.github/workflows/changed-packages.yml
vendored
2
.github/workflows/changed-packages.yml
vendored
@ -24,7 +24,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: Check if branch is out of date
|
||||
if: ${{ inputs.check-mergeable-state }}
|
||||
if: ${{ inputs.check-mergeable-state && github.base_ref == 'main' }}
|
||||
run: |
|
||||
git fetch origin main --depth 1 &&
|
||||
git merge-base --is-ancestor origin/main @;
|
||||
|
@ -26,12 +26,17 @@ import type {Coverage} from '../common/Coverage.js';
|
||||
import {Device} from '../common/Device.js';
|
||||
import {DeviceRequestPrompt} from '../common/DeviceRequestPrompt.js';
|
||||
import type {Dialog} from '../common/Dialog.js';
|
||||
import {TargetCloseError} from '../common/Errors.js';
|
||||
import {EventEmitter, Handler} from '../common/EventEmitter.js';
|
||||
import type {FileChooser} from '../common/FileChooser.js';
|
||||
import type {Keyboard, Mouse, Touchscreen} from '../common/Input.js';
|
||||
import type {WaitForSelectorOptions} from '../common/IsolatedWorld.js';
|
||||
import type {PuppeteerLifeCycleEvent} from '../common/LifecycleWatcher.js';
|
||||
import type {Credentials, NetworkConditions} from '../common/NetworkManager.js';
|
||||
import {
|
||||
Credentials,
|
||||
NetworkConditions,
|
||||
NetworkManagerEmittedEvents,
|
||||
} from '../common/NetworkManager.js';
|
||||
import {
|
||||
LowerCasePaperFormat,
|
||||
paperFormats,
|
||||
@ -47,9 +52,16 @@ import type {
|
||||
HandleFor,
|
||||
NodeFor,
|
||||
} from '../common/types.js';
|
||||
import {importFSPromises, isNumber, isString} from '../common/util.js';
|
||||
import {
|
||||
importFSPromises,
|
||||
isNumber,
|
||||
isString,
|
||||
waitForEvent,
|
||||
} from '../common/util.js';
|
||||
import type {WebWorker} from '../common/WebWorker.js';
|
||||
import {assert} from '../util/assert.js';
|
||||
import {Deferred} from '../util/Deferred.js';
|
||||
import {createDeferred} from '../util/Deferred.js';
|
||||
|
||||
import type {Browser} from './Browser.js';
|
||||
import type {BrowserContext} from './BrowserContext.js';
|
||||
@ -1615,6 +1627,72 @@ export class Page extends EventEmitter {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected async _waitForNetworkIdle(
|
||||
networkManager: EventEmitter & {
|
||||
inFlightRequestsCount: () => number;
|
||||
},
|
||||
idleTime: number,
|
||||
timeout: number,
|
||||
closedDeferred: Deferred<TargetCloseError>
|
||||
): Promise<void> {
|
||||
const idleDeferred = createDeferred<void>();
|
||||
const abortDeferred = createDeferred<Error>();
|
||||
|
||||
let idleTimer: NodeJS.Timeout;
|
||||
const cleanup = () => {
|
||||
idleTimer && clearTimeout(idleTimer);
|
||||
abortDeferred.reject(new Error('abort'));
|
||||
};
|
||||
|
||||
const evaluate = () => {
|
||||
idleTimer && clearTimeout(idleTimer);
|
||||
if (networkManager.inFlightRequestsCount() === 0) {
|
||||
idleTimer = setTimeout(idleDeferred.resolve, idleTime);
|
||||
}
|
||||
};
|
||||
|
||||
evaluate();
|
||||
|
||||
const eventHandler = () => {
|
||||
evaluate();
|
||||
return false;
|
||||
};
|
||||
|
||||
const listenToEvent = (event: symbol) => {
|
||||
return waitForEvent(
|
||||
networkManager,
|
||||
event,
|
||||
eventHandler,
|
||||
timeout,
|
||||
abortDeferred.valueOrThrow()
|
||||
);
|
||||
};
|
||||
|
||||
const eventPromises = [
|
||||
listenToEvent(NetworkManagerEmittedEvents.Request),
|
||||
listenToEvent(NetworkManagerEmittedEvents.Response),
|
||||
listenToEvent(NetworkManagerEmittedEvents.RequestFailed),
|
||||
];
|
||||
|
||||
await Promise.race([
|
||||
idleDeferred.valueOrThrow(),
|
||||
...eventPromises,
|
||||
closedDeferred.valueOrThrow(),
|
||||
]).then(
|
||||
r => {
|
||||
cleanup();
|
||||
return r;
|
||||
},
|
||||
error => {
|
||||
cleanup();
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param urlOrPredicate - A URL or predicate to wait for.
|
||||
* @param options - Optional waiting parameters
|
||||
|
@ -149,10 +149,14 @@ export class NetworkEventManager {
|
||||
return this.queuedRedirectInfo(fetchRequestId).shift();
|
||||
}
|
||||
|
||||
numRequestsInProgress(): number {
|
||||
return [...this.#httpRequestsMap].filter(([, request]) => {
|
||||
return !request.response();
|
||||
}).length;
|
||||
inFlightRequestsCount(): number {
|
||||
let inProgressRequestCounter = 0;
|
||||
for (const [, request] of this.#httpRequestsMap) {
|
||||
if (!request.response()) {
|
||||
inProgressRequestCounter++;
|
||||
}
|
||||
}
|
||||
return inProgressRequestCounter;
|
||||
}
|
||||
|
||||
storeRequestWillBeSent(
|
||||
|
@ -181,8 +181,8 @@ export class NetworkManager extends EventEmitter {
|
||||
return Object.assign({}, this.#extraHTTPHeaders);
|
||||
}
|
||||
|
||||
numRequestsInProgress(): number {
|
||||
return this.#networkEventManager.numRequestsInProgress();
|
||||
inFlightRequestsCount(): number {
|
||||
return this.#networkEventManager.inFlightRequestsCount();
|
||||
}
|
||||
|
||||
async setOfflineMode(value: boolean): Promise<void> {
|
||||
|
@ -153,7 +153,7 @@ export class CDPPage extends Page {
|
||||
#screenshotTaskQueue: TaskQueue;
|
||||
#workers = new Map<string, WebWorker>();
|
||||
#fileChooserDeferreds = new Set<Deferred<FileChooser>>();
|
||||
#sessionCloseDeferred = createDeferred<Error>();
|
||||
#sessionCloseDeferred = createDeferred<TargetCloseError>();
|
||||
#serviceWorkerBypassed = false;
|
||||
#userDragInterceptionEnabled = false;
|
||||
|
||||
@ -1000,64 +1000,11 @@ export class CDPPage extends Page {
|
||||
): Promise<void> {
|
||||
const {idleTime = 500, timeout = this.#timeoutSettings.timeout()} = options;
|
||||
|
||||
const networkManager = this.#frameManager.networkManager;
|
||||
|
||||
const idleDeferred = createDeferred<void>();
|
||||
|
||||
let abortRejectCallback: (error: Error) => void;
|
||||
const abortPromise = new Promise<Error>((_, reject) => {
|
||||
abortRejectCallback = reject;
|
||||
});
|
||||
|
||||
let idleTimer: NodeJS.Timeout;
|
||||
const cleanup = () => {
|
||||
idleTimer && clearTimeout(idleTimer);
|
||||
abortRejectCallback(new Error('abort'));
|
||||
};
|
||||
|
||||
const evaluate = () => {
|
||||
idleTimer && clearTimeout(idleTimer);
|
||||
if (networkManager.numRequestsInProgress() === 0) {
|
||||
idleTimer = setTimeout(idleDeferred.resolve, idleTime);
|
||||
}
|
||||
};
|
||||
|
||||
evaluate();
|
||||
|
||||
const eventHandler = () => {
|
||||
evaluate();
|
||||
return false;
|
||||
};
|
||||
|
||||
const listenToEvent = (event: symbol) => {
|
||||
return waitForEvent(
|
||||
networkManager,
|
||||
event,
|
||||
eventHandler,
|
||||
await this._waitForNetworkIdle(
|
||||
this.#frameManager.networkManager,
|
||||
idleTime,
|
||||
timeout,
|
||||
abortPromise
|
||||
);
|
||||
};
|
||||
|
||||
const eventPromises = [
|
||||
listenToEvent(NetworkManagerEmittedEvents.Request),
|
||||
listenToEvent(NetworkManagerEmittedEvents.Response),
|
||||
listenToEvent(NetworkManagerEmittedEvents.RequestFailed),
|
||||
];
|
||||
|
||||
await Promise.race([
|
||||
idleDeferred.valueOrThrow(),
|
||||
...eventPromises,
|
||||
this.#sessionCloseDeferred.valueOrThrow(),
|
||||
]).then(
|
||||
r => {
|
||||
cleanup();
|
||||
return r;
|
||||
},
|
||||
error => {
|
||||
cleanup();
|
||||
throw error;
|
||||
}
|
||||
this.#sessionCloseDeferred
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,7 @@ export class NetworkManager extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
#onFetchError(event: any) {
|
||||
#onFetchError(event: Bidi.Network.FetchErrorParams) {
|
||||
const request = this.#requestMap.get(event.request.request);
|
||||
if (!request) {
|
||||
return;
|
||||
@ -101,6 +101,17 @@ export class NetworkManager extends EventEmitter {
|
||||
return this.#navigationMap.get(navigationId ?? '') ?? null;
|
||||
}
|
||||
|
||||
inFlightRequestsCount(): number {
|
||||
let inFlightRequestCounter = 0;
|
||||
for (const [, request] of this.#requestMap) {
|
||||
if (!request.response() || request._failureText) {
|
||||
inFlightRequestCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
return inFlightRequestCounter;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.removeAllListeners();
|
||||
this.#requestMap.clear();
|
||||
|
@ -497,6 +497,19 @@ export class Page extends PageBase {
|
||||
);
|
||||
}
|
||||
|
||||
override async waitForNetworkIdle(
|
||||
options: {idleTime?: number; timeout?: number} = {}
|
||||
): Promise<void> {
|
||||
const {idleTime = 500, timeout = this.#timeoutSettings.timeout()} = options;
|
||||
|
||||
await this._waitForNetworkIdle(
|
||||
this.#networkManager,
|
||||
idleTime,
|
||||
timeout,
|
||||
this.#closedDeferred
|
||||
);
|
||||
}
|
||||
|
||||
override title(): Promise<string> {
|
||||
return this.mainFrame().title();
|
||||
}
|
||||
|
@ -167,6 +167,12 @@
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[page.spec] Page Page.waitForNetworkIdle *",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[page.spec] Page Page.waitForRequest *",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
@ -587,6 +593,12 @@
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[page.spec] Page Page.waitForNetworkIdle should work with aborted requests",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[proxy.spec] *",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
|
Loading…
Reference in New Issue
Block a user