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.waitForFileChooser([options])](#pagewaitforfilechooseroptions)
|
||||||
* [page.waitForFunction(pageFunction[, options[, ...args]])](#pagewaitforfunctionpagefunction-options-args)
|
* [page.waitForFunction(pageFunction[, options[, ...args]])](#pagewaitforfunctionpagefunction-options-args)
|
||||||
* [page.waitForNavigation([options])](#pagewaitfornavigationoptions)
|
* [page.waitForNavigation([options])](#pagewaitfornavigationoptions)
|
||||||
|
* [page.waitForNetworkIdle([options])](#pagewaitfornetworkidleoptions)
|
||||||
* [page.waitForRequest(urlOrPredicate[, options])](#pagewaitforrequesturlorpredicate-options)
|
* [page.waitForRequest(urlOrPredicate[, options])](#pagewaitforrequesturlorpredicate-options)
|
||||||
* [page.waitForResponse(urlOrPredicate[, options])](#pagewaitforresponseurlorpredicate-options)
|
* [page.waitForResponse(urlOrPredicate[, options])](#pagewaitforresponseurlorpredicate-options)
|
||||||
* [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options)
|
* [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options)
|
||||||
@ -2846,6 +2847,17 @@ const [response] = await Promise.all([
|
|||||||
|
|
||||||
Shortcut for [page.mainFrame().waitForNavigation(options)](#framewaitfornavigationoptions).
|
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])
|
#### page.waitForRequest(urlOrPredicate[, options])
|
||||||
|
|
||||||
- `urlOrPredicate` <[string]|[Function]> A URL or predicate to wait for.
|
- `urlOrPredicate` <[string]|[Function]> A URL or predicate to wait for.
|
||||||
|
@ -188,6 +188,12 @@ export class NetworkManager extends EventEmitter {
|
|||||||
return Object.assign({}, this._extraHTTPHeaders);
|
return Object.assign({}, this._extraHTTPHeaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
numRequestsInProgress(): number {
|
||||||
|
return [...this._requestIdToRequest].filter(([, request]) => {
|
||||||
|
return !request.response();
|
||||||
|
}).length;
|
||||||
|
}
|
||||||
|
|
||||||
async setOfflineMode(value: boolean): Promise<void> {
|
async setOfflineMode(value: boolean): Promise<void> {
|
||||||
this._emulatedNetworkConditions.offline = value;
|
this._emulatedNetworkConditions.offline = value;
|
||||||
await this._updateNetworkConditions();
|
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.
|
* This method navigate to the previous page in history.
|
||||||
* @param options - Navigation parameters
|
* @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 () {
|
describeFailsFirefox('Page.exposeFunction', function () {
|
||||||
it('should work', async () => {
|
it('should work', async () => {
|
||||||
const { page } = getTestState();
|
const { page } = getTestState();
|
||||||
|
Loading…
Reference in New Issue
Block a user