Skip to content

Commit

Permalink
feat: add page.emulateNetworkConditions (#6759)
Browse files Browse the repository at this point in the history
  • Loading branch information
jschfflr committed Jan 21, 2021
1 parent ebd087a commit 5ea76e9
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 9 deletions.
45 changes: 45 additions & 0 deletions docs/api.md
Expand Up @@ -42,6 +42,7 @@
* [puppeteer.errors](#puppeteererrors)
* [puppeteer.executablePath()](#puppeteerexecutablepath)
* [puppeteer.launch([options])](#puppeteerlaunchoptions)
* [puppeteer.networkConditions](#puppeteernetworkconditions)
* [puppeteer.product](#puppeteerproduct)
* [puppeteer.registerCustomQueryHandler(name, queryHandler)](#puppeteerregistercustomqueryhandlername-queryhandler)
* [puppeteer.unregisterCustomQueryHandler(name)](#puppeteerunregistercustomqueryhandlername)
Expand Down Expand Up @@ -128,6 +129,7 @@
* [page.emulateIdleState(overrides)](#pageemulateidlestateoverrides)
* [page.emulateMediaFeatures(features)](#pageemulatemediafeaturesfeatures)
* [page.emulateMediaType(type)](#pageemulatemediatypetype)
* [page.emulateNetworkConditions(networkConditions)](#pageemulatenetworkconditionsnetworkconditions)
* [page.emulateTimezone(timezoneId)](#pageemulatetimezonetimezoneid)
* [page.emulateVisionDeficiency(type)](#pageemulatevisiondeficiencytype)
* [page.evaluate(pageFunction[, ...args])](#pageevaluatepagefunction-args)
Expand Down Expand Up @@ -610,6 +612,26 @@ const browser = await puppeteer.launch({
>
> See [`this article`](https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/) for a description of the differences between Chromium and Chrome. [`This article`](https://chromium.googlesource.com/chromium/src/+/lkgr/docs/chromium_browser_vs_google_chrome.md) describes some differences for Linux users.
#### puppeteer.networkConditions
- returns: <[Object]>

Returns a list of network conditions to be used with [`page.emulateNetworkConditions(networkConditions)`](#pageemulatenetworkconditionsnetworkconditions). Actual list of
conditions can be found in [`src/common/NetworkConditions.ts`](https://github.com/puppeteer/puppeteer/blob/main/src/common/NetworkConditions.ts).

```js
const puppeteer = require('puppeteer');
const slow3G = puppeteer.networkConditions['Slow 3G'];

(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.emulateNetworkConditions(slow3G);
await page.goto('https://www.google.com');
// other actions...
await browser.close();
})();
```

#### puppeteer.product
- returns: <[string]> returns the name of the browser that is under automation (`"chrome"` or `"firefox"`)

Expand Down Expand Up @@ -1441,6 +1463,29 @@ await page.evaluate(() => matchMedia('print').matches);
// → false
```

#### page.emulateNetworkConditions(networkConditions)
- `networkConditions` <?[Object]> Passing `null` disables network condition emulation.
- `download` <[number]> Download speed (bytes/s), `-1` to disable
- `upload` <[number]> Upload speed (bytes/s), `-1` to disable
- `latency` <[number]> Latency (ms), `0` to disable
- returns: <[Promise]>

> **NOTE** This does not affect WebSockets and WebRTC PeerConnections (see https://crbug.com/563644)
```js
const puppeteer = require('puppeteer');
const slow3G = puppeteer.networkConditions['Slow 3G'];

(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.emulateNetworkConditions(slow3G);
await page.goto('https://www.google.com');
// other actions...
await browser.close();
})();
```

#### page.emulateTimezone(timezoneId)
- `timezoneId` <?[string]> Changes the timezone of the page. See [ICU’s `metaZones.txt`](https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1) for a list of supported timezone IDs. Passing `null` disables timezone emulation.
- returns: <[Promise]>
Expand Down
32 changes: 32 additions & 0 deletions src/common/NetworkConditions.ts
@@ -0,0 +1,32 @@
/**
* Copyright 2021 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { NetworkConditions } from './NetworkManager.js';

export type PredefinedNetworkConditions = { [name: string]: NetworkConditions };

export const networkConditions: PredefinedNetworkConditions = {
'Slow 3G': {
download: ((500 * 1000) / 8) * 0.8,
upload: ((500 * 1000) / 8) * 0.8,
latency: 400 * 5,
},
'Fast 3G': {
download: ((1.6 * 1000 * 1000) / 8) * 0.9,
upload: ((750 * 1000) / 8) * 0.9,
latency: 150 * 3.75,
},
};
55 changes: 47 additions & 8 deletions src/common/NetworkManager.ts
Expand Up @@ -30,6 +30,22 @@ export interface Credentials {
password: string;
}

/**
* @public
*/
export interface NetworkConditions {
// Download speed (bytes/s)
download: number;
// Upload speed (bytes/s)
upload: number;
// Latency (ms)
latency: number;
}

export interface InternalNetworkConditions extends NetworkConditions {
offline: boolean;
}

/**
* We use symbols to prevent any external parties listening to these events.
* They are internal to Puppeteer.
Expand All @@ -56,13 +72,18 @@ export class NetworkManager extends EventEmitter {
Protocol.Network.RequestWillBeSentEvent
>();
_extraHTTPHeaders: Record<string, string> = {};
_offline = false;
_credentials?: Credentials = null;
_attemptedAuthentications = new Set<string>();
_userRequestInterceptionEnabled = false;
_protocolRequestInterceptionEnabled = false;
_userCacheDisabled = false;
_requestIdToInterceptionId = new Map<string, string>();
_emulatedNetworkConditions: InternalNetworkConditions = {
offline: false,
upload: -1,
download: -1,
latency: 0,
};

constructor(
client: CDPSession,
Expand Down Expand Up @@ -130,14 +151,32 @@ export class NetworkManager extends EventEmitter {
}

async setOfflineMode(value: boolean): Promise<void> {
if (this._offline === value) return;
this._offline = value;
this._emulatedNetworkConditions.offline = value;
await this._updateNetworkConditions();
}

async emulateNetworkConditions(
networkConditions: NetworkConditions | null
): Promise<void> {
this._emulatedNetworkConditions.upload = networkConditions
? networkConditions.upload
: -1;
this._emulatedNetworkConditions.download = networkConditions
? networkConditions.download
: -1;
this._emulatedNetworkConditions.latency = networkConditions
? networkConditions.latency
: 0;

await this._updateNetworkConditions();
}

async _updateNetworkConditions(): Promise<void> {
await this._client.send('Network.emulateNetworkConditions', {
offline: this._offline,
// values of 0 remove any active throttling. crbug.com/456324#c9
latency: 0,
downloadThroughput: -1,
uploadThroughput: -1,
offline: this._emulatedNetworkConditions.offline,
latency: this._emulatedNetworkConditions.latency,
uploadThroughput: this._emulatedNetworkConditions.upload,
downloadThroughput: this._emulatedNetworkConditions.download,
});
}

Expand Down
14 changes: 13 additions & 1 deletion src/common/Page.ts
Expand Up @@ -37,7 +37,11 @@ import { Browser, BrowserContext } from './Browser.js';
import { Target } from './Target.js';
import { createJSHandle, JSHandle, ElementHandle } from './JSHandle.js';
import { Viewport } from './PuppeteerViewport.js';
import { Credentials, NetworkManagerEmittedEvents } from './NetworkManager.js';
import {
Credentials,
NetworkConditions,
NetworkManagerEmittedEvents,
} from './NetworkManager.js';
import { HTTPRequest } from './HTTPRequest.js';
import { HTTPResponse } from './HTTPResponse.js';
import { Accessibility } from './Accessibility.js';
Expand Down Expand Up @@ -693,6 +697,14 @@ export class Page extends EventEmitter {
return this._frameManager.networkManager().setOfflineMode(enabled);
}

emulateNetworkConditions(
networkConditions: NetworkConditions | null
): Promise<void> {
return this._frameManager
.networkManager()
.emulateNetworkConditions(networkConditions);
}

/**
* @param timeout - Maximum navigation time in milliseconds.
*/
Expand Down
29 changes: 29 additions & 0 deletions src/common/Puppeteer.ts
Expand Up @@ -26,6 +26,10 @@ import {
} from './QueryHandler.js';
import { Product } from './Product.js';
import { connectToBrowser, BrowserOptions } from './BrowserConnector.js';
import {
PredefinedNetworkConditions,
networkConditions,
} from './NetworkConditions.js';

/**
* Settings that are common to the Puppeteer class, regardless of enviroment.
Expand Down Expand Up @@ -125,6 +129,31 @@ export class Puppeteer {
return puppeteerErrors;
}

/**
* @remarks
* Returns a list of network conditions to be used with `page.emulateNetworkConditions(networkConditions)`. Actual list of predefined conditions can be found in {@link https://github.com/puppeteer/puppeteer/blob/main/src/common/NetworkConditions.ts | src/common/NetworkConditions.ts}.
*
* @example
*
* ```js
* const puppeteer = require('puppeteer');
* const slow3G = puppeteer.networkConditions['Slow 3G'];
*
* (async () => {
* const browser = await puppeteer.launch();
* const page = await browser.newPage();
* await page.emulateNetworkConditions(slow3G);
* await page.goto('https://www.google.com');
* // other actions...
* await browser.close();
* })();
* ```
*
*/
get networkConditions(): PredefinedNetworkConditions {
return networkConditions;
}

/**
* Registers a {@link CustomQueryHandler | custom query handler}. After
* registration, the handler can be used everywhere where a selector is
Expand Down
22 changes: 22 additions & 0 deletions test/page.spec.ts
Expand Up @@ -388,6 +388,28 @@ describe('Page', function () {
});
});

describeFailsFirefox('Page.emulateNetworkConditions', function () {
it('should change navigator.connection.effectiveType', async () => {
const { page, puppeteer } = getTestState();

const slow3G = puppeteer.networkConditions['Slow 3G'];
const fast3G = puppeteer.networkConditions['Fast 3G'];

expect(
await page.evaluate('window.navigator.connection.effectiveType')
).toBe('4g');
await page.emulateNetworkConditions(fast3G);
expect(
await page.evaluate('window.navigator.connection.effectiveType')
).toBe('3g');
await page.emulateNetworkConditions(slow3G);
expect(
await page.evaluate('window.navigator.connection.effectiveType')
).toBe('2g');
await page.emulateNetworkConditions(null);
});
});

describe('ExecutionContext.queryObjects', function () {
itFailsFirefox('should work', async () => {
const { page } = getTestState();
Expand Down
7 changes: 7 additions & 0 deletions utils/doclint/check_public_api/index.js
Expand Up @@ -576,6 +576,13 @@ function compareDocumentations(actual, expected) {
expectedName: 'Viewport',
},
],
[
'Method Page.emulateNetworkConditions() networkConditions',
{
actualName: 'Object',
expectedName: 'NetworkConditions',
},
],
[
'Method Page.setViewport() options.viewport',
{
Expand Down

0 comments on commit 5ea76e9

Please sign in to comment.