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:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
- name: Check if branch is out of date
|
- name: Check if branch is out of date
|
||||||
if: ${{ inputs.check-mergeable-state }}
|
if: ${{ inputs.check-mergeable-state && github.base_ref == 'main' }}
|
||||||
run: |
|
run: |
|
||||||
git fetch origin main --depth 1 &&
|
git fetch origin main --depth 1 &&
|
||||||
git merge-base --is-ancestor origin/main @;
|
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 {Device} from '../common/Device.js';
|
||||||
import {DeviceRequestPrompt} from '../common/DeviceRequestPrompt.js';
|
import {DeviceRequestPrompt} from '../common/DeviceRequestPrompt.js';
|
||||||
import type {Dialog} from '../common/Dialog.js';
|
import type {Dialog} from '../common/Dialog.js';
|
||||||
|
import {TargetCloseError} from '../common/Errors.js';
|
||||||
import {EventEmitter, Handler} from '../common/EventEmitter.js';
|
import {EventEmitter, Handler} from '../common/EventEmitter.js';
|
||||||
import type {FileChooser} from '../common/FileChooser.js';
|
import type {FileChooser} from '../common/FileChooser.js';
|
||||||
import type {Keyboard, Mouse, Touchscreen} from '../common/Input.js';
|
import type {Keyboard, Mouse, Touchscreen} from '../common/Input.js';
|
||||||
import type {WaitForSelectorOptions} from '../common/IsolatedWorld.js';
|
import type {WaitForSelectorOptions} from '../common/IsolatedWorld.js';
|
||||||
import type {PuppeteerLifeCycleEvent} from '../common/LifecycleWatcher.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 {
|
import {
|
||||||
LowerCasePaperFormat,
|
LowerCasePaperFormat,
|
||||||
paperFormats,
|
paperFormats,
|
||||||
@ -47,9 +52,16 @@ import type {
|
|||||||
HandleFor,
|
HandleFor,
|
||||||
NodeFor,
|
NodeFor,
|
||||||
} from '../common/types.js';
|
} 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 type {WebWorker} from '../common/WebWorker.js';
|
||||||
import {assert} from '../util/assert.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 {Browser} from './Browser.js';
|
||||||
import type {BrowserContext} from './BrowserContext.js';
|
import type {BrowserContext} from './BrowserContext.js';
|
||||||
@ -1615,6 +1627,72 @@ export class Page extends EventEmitter {
|
|||||||
throw new Error('Not implemented');
|
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 urlOrPredicate - A URL or predicate to wait for.
|
||||||
* @param options - Optional waiting parameters
|
* @param options - Optional waiting parameters
|
||||||
|
@ -149,10 +149,14 @@ export class NetworkEventManager {
|
|||||||
return this.queuedRedirectInfo(fetchRequestId).shift();
|
return this.queuedRedirectInfo(fetchRequestId).shift();
|
||||||
}
|
}
|
||||||
|
|
||||||
numRequestsInProgress(): number {
|
inFlightRequestsCount(): number {
|
||||||
return [...this.#httpRequestsMap].filter(([, request]) => {
|
let inProgressRequestCounter = 0;
|
||||||
return !request.response();
|
for (const [, request] of this.#httpRequestsMap) {
|
||||||
}).length;
|
if (!request.response()) {
|
||||||
|
inProgressRequestCounter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return inProgressRequestCounter;
|
||||||
}
|
}
|
||||||
|
|
||||||
storeRequestWillBeSent(
|
storeRequestWillBeSent(
|
||||||
|
@ -181,8 +181,8 @@ export class NetworkManager extends EventEmitter {
|
|||||||
return Object.assign({}, this.#extraHTTPHeaders);
|
return Object.assign({}, this.#extraHTTPHeaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
numRequestsInProgress(): number {
|
inFlightRequestsCount(): number {
|
||||||
return this.#networkEventManager.numRequestsInProgress();
|
return this.#networkEventManager.inFlightRequestsCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
async setOfflineMode(value: boolean): Promise<void> {
|
async setOfflineMode(value: boolean): Promise<void> {
|
||||||
|
@ -153,7 +153,7 @@ export class CDPPage extends Page {
|
|||||||
#screenshotTaskQueue: TaskQueue;
|
#screenshotTaskQueue: TaskQueue;
|
||||||
#workers = new Map<string, WebWorker>();
|
#workers = new Map<string, WebWorker>();
|
||||||
#fileChooserDeferreds = new Set<Deferred<FileChooser>>();
|
#fileChooserDeferreds = new Set<Deferred<FileChooser>>();
|
||||||
#sessionCloseDeferred = createDeferred<Error>();
|
#sessionCloseDeferred = createDeferred<TargetCloseError>();
|
||||||
#serviceWorkerBypassed = false;
|
#serviceWorkerBypassed = false;
|
||||||
#userDragInterceptionEnabled = false;
|
#userDragInterceptionEnabled = false;
|
||||||
|
|
||||||
@ -1000,64 +1000,11 @@ export class CDPPage extends Page {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const {idleTime = 500, timeout = this.#timeoutSettings.timeout()} = options;
|
const {idleTime = 500, timeout = this.#timeoutSettings.timeout()} = options;
|
||||||
|
|
||||||
const networkManager = this.#frameManager.networkManager;
|
await this._waitForNetworkIdle(
|
||||||
|
this.#frameManager.networkManager,
|
||||||
const idleDeferred = createDeferred<void>();
|
idleTime,
|
||||||
|
|
||||||
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,
|
|
||||||
timeout,
|
timeout,
|
||||||
abortPromise
|
this.#sessionCloseDeferred
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
const request = this.#requestMap.get(event.request.request);
|
||||||
if (!request) {
|
if (!request) {
|
||||||
return;
|
return;
|
||||||
@ -101,6 +101,17 @@ export class NetworkManager extends EventEmitter {
|
|||||||
return this.#navigationMap.get(navigationId ?? '') ?? null;
|
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 {
|
dispose(): void {
|
||||||
this.removeAllListeners();
|
this.removeAllListeners();
|
||||||
this.#requestMap.clear();
|
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> {
|
override title(): Promise<string> {
|
||||||
return this.mainFrame().title();
|
return this.mainFrame().title();
|
||||||
}
|
}
|
||||||
|
@ -167,6 +167,12 @@
|
|||||||
"parameters": ["webDriverBiDi"],
|
"parameters": ["webDriverBiDi"],
|
||||||
"expectations": ["PASS"]
|
"expectations": ["PASS"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[page.spec] Page Page.waitForNetworkIdle *",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["PASS"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[page.spec] Page Page.waitForRequest *",
|
"testIdPattern": "[page.spec] Page Page.waitForRequest *",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
@ -587,6 +593,12 @@
|
|||||||
"parameters": ["webDriverBiDi"],
|
"parameters": ["webDriverBiDi"],
|
||||||
"expectations": ["FAIL"]
|
"expectations": ["FAIL"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[page.spec] Page Page.waitForNetworkIdle should work with aborted requests",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["FAIL"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[proxy.spec] *",
|
"testIdPattern": "[proxy.spec] *",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
Loading…
Reference in New Issue
Block a user