feat(api): implement Page.waitForNetworkIdle()
(#5140)
which will wait for there to be no network requests in progress during the `idleTime` before resolving.
This commit is contained in:
parent
b5020dc041
commit
3c6029c702
12
docs/api.md
12
docs/api.md
@ -197,6 +197,7 @@
|
||||
* [page.waitForFileChooser([options])](#pagewaitforfilechooseroptions)
|
||||
* [page.waitForFunction(pageFunction[, options[, ...args]])](#pagewaitforfunctionpagefunction-options-args)
|
||||
* [page.waitForNavigation([options])](#pagewaitfornavigationoptions)
|
||||
* [page.waitForNetworkIdle([options])](#pagewaitfornetworkidleoptions)
|
||||
* [page.waitForRequest(urlOrPredicate[, options])](#pagewaitforrequesturlorpredicate-options)
|
||||
* [page.waitForResponse(urlOrPredicate[, options])](#pagewaitforresponseurlorpredicate-options)
|
||||
* [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options)
|
||||
@ -2846,6 +2847,17 @@ const [response] = await Promise.all([
|
||||
|
||||
Shortcut for [page.mainFrame().waitForNavigation(options)](#framewaitfornavigationoptions).
|
||||
|
||||
#### page.waitForNetworkIdle([options])
|
||||
- `options` <[Object]> Optional waiting parameters
|
||||
- `timeout` <[number]> Maximum wait time in milliseconds, defaults to 30 seconds, pass `0` to disable the timeout. The default value can be changed by using the [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) method.
|
||||
- `idleTime` <[number]> How long to wait for no network requests in milliseconds, defaults to 500 milliseconds.
|
||||
- returns: <[Promise]<void>> Promise which resolves when network is idle.
|
||||
|
||||
```js
|
||||
page.evaluate(() => fetch('some-url'));
|
||||
page.waitForNetworkIdle(); // The promise resolves after fetch above finishes
|
||||
```
|
||||
|
||||
#### page.waitForRequest(urlOrPredicate[, options])
|
||||
|
||||
- `urlOrPredicate` <[string]|[Function]> A URL or predicate to wait for.
|
||||
|
@ -188,6 +188,12 @@ export class NetworkManager extends EventEmitter {
|
||||
return Object.assign({}, this._extraHTTPHeaders);
|
||||
}
|
||||
|
||||
numRequestsInProgress(): number {
|
||||
return [...this._requestIdToRequest].filter(([, request]) => {
|
||||
return !request.response();
|
||||
}).length;
|
||||
}
|
||||
|
||||
async setOfflineMode(value: boolean): Promise<void> {
|
||||
this._emulatedNetworkConditions.offline = value;
|
||||
await this._updateNetworkConditions();
|
||||
|
@ -1894,6 +1894,79 @@ export class Page extends EventEmitter {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param options - Optional waiting parameters
|
||||
* @returns Promise which resolves when network is idle
|
||||
*/
|
||||
async waitForNetworkIdle(
|
||||
options: { idleTime?: number; timeout?: number } = {}
|
||||
): Promise<void> {
|
||||
const { idleTime = 500, timeout = this._timeoutSettings.timeout() } =
|
||||
options;
|
||||
|
||||
const networkManager = this._frameManager.networkManager();
|
||||
|
||||
let idleResolveCallback;
|
||||
const idlePromise = new Promise((resolve) => {
|
||||
idleResolveCallback = resolve;
|
||||
});
|
||||
|
||||
let abortRejectCallback;
|
||||
const abortPromise = new Promise<Error>((_, reject) => {
|
||||
abortRejectCallback = reject;
|
||||
});
|
||||
|
||||
let idleTimer;
|
||||
const onIdle = () => idleResolveCallback();
|
||||
|
||||
const cleanup = () => {
|
||||
idleTimer && clearTimeout(idleTimer);
|
||||
abortRejectCallback(new Error('abort'));
|
||||
};
|
||||
|
||||
const evaluate = () => {
|
||||
idleTimer && clearTimeout(idleTimer);
|
||||
if (networkManager.numRequestsInProgress() === 0)
|
||||
idleTimer = setTimeout(onIdle, idleTime);
|
||||
};
|
||||
|
||||
evaluate();
|
||||
|
||||
const eventHandler = () => {
|
||||
evaluate();
|
||||
return false;
|
||||
};
|
||||
|
||||
const listenToEvent = (event) =>
|
||||
helper.waitForEvent(
|
||||
networkManager,
|
||||
event,
|
||||
eventHandler,
|
||||
timeout,
|
||||
abortPromise
|
||||
);
|
||||
|
||||
const eventPromises = [
|
||||
listenToEvent(NetworkManagerEmittedEvents.Request),
|
||||
listenToEvent(NetworkManagerEmittedEvents.Response),
|
||||
];
|
||||
|
||||
await Promise.race([
|
||||
idlePromise,
|
||||
...eventPromises,
|
||||
this._sessionClosePromise(),
|
||||
]).then(
|
||||
(r) => {
|
||||
cleanup();
|
||||
return r;
|
||||
},
|
||||
(error) => {
|
||||
cleanup();
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method navigate to the previous page in history.
|
||||
* @param options - Navigation parameters
|
||||
|
@ -825,6 +825,79 @@ describe('Page', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Page.waitForNetworkIdle', function () {
|
||||
it('should work', async () => {
|
||||
const { page, server } = getTestState();
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
let res;
|
||||
const [t1, t2] = await Promise.all([
|
||||
page.waitForNetworkIdle().then((r) => {
|
||||
res = r;
|
||||
return Date.now();
|
||||
}),
|
||||
page
|
||||
.evaluate(() =>
|
||||
(async () => {
|
||||
await Promise.all([
|
||||
fetch('/digits/1.png'),
|
||||
fetch('/digits/2.png'),
|
||||
]);
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
await fetch('/digits/3.png');
|
||||
await new Promise((resolve) => setTimeout(resolve, 400));
|
||||
await fetch('/digits/4.png');
|
||||
})()
|
||||
)
|
||||
.then(() => Date.now()),
|
||||
]);
|
||||
expect(res).toBe(undefined);
|
||||
expect(t1).toBeGreaterThan(t2);
|
||||
expect(t1 - t2).toBeGreaterThanOrEqual(400);
|
||||
});
|
||||
it('should respect timeout', async () => {
|
||||
const { page, puppeteer } = getTestState();
|
||||
let error = null;
|
||||
await page
|
||||
.waitForNetworkIdle({ timeout: 1 })
|
||||
.catch((error_) => (error = error_));
|
||||
expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
|
||||
});
|
||||
it('should respect idleTime', async () => {
|
||||
const { page, server } = getTestState();
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
const [t1, t2] = await Promise.all([
|
||||
page.waitForNetworkIdle({ idleTime: 10 }).then(() => Date.now()),
|
||||
page
|
||||
.evaluate(() =>
|
||||
(async () => {
|
||||
await Promise.all([
|
||||
fetch('/digits/1.png'),
|
||||
fetch('/digits/2.png'),
|
||||
]);
|
||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||
})()
|
||||
)
|
||||
.then(() => Date.now()),
|
||||
]);
|
||||
expect(t2).toBeGreaterThan(t1);
|
||||
});
|
||||
it('should work with no timeout', async () => {
|
||||
const { page, server } = getTestState();
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
const [result] = await Promise.all([
|
||||
page.waitForNetworkIdle({ timeout: 0 }),
|
||||
page.evaluate(() =>
|
||||
setTimeout(() => {
|
||||
fetch('/digits/1.png');
|
||||
fetch('/digits/2.png');
|
||||
fetch('/digits/3.png');
|
||||
}, 50)
|
||||
),
|
||||
]);
|
||||
expect(result).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describeFailsFirefox('Page.exposeFunction', function () {
|
||||
it('should work', async () => {
|
||||
const { page } = getTestState();
|
||||
|
Loading…
Reference in New Issue
Block a user