diff --git a/docs/api.md b/docs/api.md index 01ab77efe2346..6d304272cee6e 100644 --- a/docs/api.md +++ b/docs/api.md @@ -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) @@ -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) @@ -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"`) @@ -1441,6 +1463,29 @@ await page.evaluate(() => matchMedia('print').matches); // → false ``` +#### page.emulateNetworkConditions(networkConditions) +- `networkConditions` 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` 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]> diff --git a/src/common/NetworkConditions.ts b/src/common/NetworkConditions.ts new file mode 100644 index 0000000000000..c1a89f280bd01 --- /dev/null +++ b/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, + }, +}; diff --git a/src/common/NetworkManager.ts b/src/common/NetworkManager.ts index 52b0aee0bfcc3..251f87f4dea4e 100644 --- a/src/common/NetworkManager.ts +++ b/src/common/NetworkManager.ts @@ -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. @@ -56,13 +72,18 @@ export class NetworkManager extends EventEmitter { Protocol.Network.RequestWillBeSentEvent >(); _extraHTTPHeaders: Record = {}; - _offline = false; _credentials?: Credentials = null; _attemptedAuthentications = new Set(); _userRequestInterceptionEnabled = false; _protocolRequestInterceptionEnabled = false; _userCacheDisabled = false; _requestIdToInterceptionId = new Map(); + _emulatedNetworkConditions: InternalNetworkConditions = { + offline: false, + upload: -1, + download: -1, + latency: 0, + }; constructor( client: CDPSession, @@ -130,14 +151,32 @@ export class NetworkManager extends EventEmitter { } async setOfflineMode(value: boolean): Promise { - if (this._offline === value) return; - this._offline = value; + this._emulatedNetworkConditions.offline = value; + await this._updateNetworkConditions(); + } + + async emulateNetworkConditions( + networkConditions: NetworkConditions | null + ): Promise { + 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 { 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, }); } diff --git a/src/common/Page.ts b/src/common/Page.ts index 4403998b889a5..9df29693c1abd 100644 --- a/src/common/Page.ts +++ b/src/common/Page.ts @@ -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'; @@ -693,6 +697,14 @@ export class Page extends EventEmitter { return this._frameManager.networkManager().setOfflineMode(enabled); } + emulateNetworkConditions( + networkConditions: NetworkConditions | null + ): Promise { + return this._frameManager + .networkManager() + .emulateNetworkConditions(networkConditions); + } + /** * @param timeout - Maximum navigation time in milliseconds. */ diff --git a/src/common/Puppeteer.ts b/src/common/Puppeteer.ts index 0dc40d33b5ef8..6062c18624a5d 100644 --- a/src/common/Puppeteer.ts +++ b/src/common/Puppeteer.ts @@ -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. @@ -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 diff --git a/test/page.spec.ts b/test/page.spec.ts index ccac65ea660ff..18a653e693ca7 100644 --- a/test/page.spec.ts +++ b/test/page.spec.ts @@ -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(); diff --git a/utils/doclint/check_public_api/index.js b/utils/doclint/check_public_api/index.js index 84d5ca0f2a9e1..13c62028e5319 100644 --- a/utils/doclint/check_public_api/index.js +++ b/utils/doclint/check_public_api/index.js @@ -576,6 +576,13 @@ function compareDocumentations(actual, expected) { expectedName: 'Viewport', }, ], + [ + 'Method Page.emulateNetworkConditions() networkConditions', + { + actualName: 'Object', + expectedName: 'NetworkConditions', + }, + ], [ 'Method Page.setViewport() options.viewport', {