Skip to content

Commit

Permalink
Merge pull request #166 from GoogleChromeLabs/add/mobile-emulation
Browse files Browse the repository at this point in the history
Add device emulation to `benchmark-web-vitals`
  • Loading branch information
westonruter authored Dec 16, 2024
2 parents 7b991a5 + e46e83f commit f0ef75e
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 24 deletions.
3 changes: 2 additions & 1 deletion cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ Loads the provided URLs in a headless browser several times to measure median We
* `--show-percentiles` (`-p`): Whether to show more granular percentiles instead of only the median.
* `--throttle-cpu` (`-t`): Enable CPU throttling to emulate slow CPUs.
* `--network-conditions` (`-c`): Enable emulation of network conditions (may be either "Slow 3G" or "Fast 3G").
* `--window-viewport` (`-w`): Specify the viewport window size, like "mobile" (an alias for "412x823") or "desktop" (an alias for "1350x940"). Defaults to "960x700".
* `--emulate-device` (`-e`): Emulate a specific device, like "Moto G4" or "iPad". See list of [known devices](https://pptr.dev/api/puppeteer.knowndevices).
* `--window-viewport` (`-w`): Specify the viewport window size, like "mobile" (an alias for "412x823") or "desktop" (an alias for "1350x940"). Defaults to "960x700" if no device is being emulated.

#### Examples

Expand Down
77 changes: 54 additions & 23 deletions cli/commands/benchmark-web-vitals.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,21 @@
/**
* External dependencies
*/
import puppeteer, { Browser, PredefinedNetworkConditions } from 'puppeteer';
import puppeteer, {
Browser,
PredefinedNetworkConditions,
KnownDevices,
} from 'puppeteer';
import round from 'lodash-es/round.js';

/* eslint-disable jsdoc/valid-types */
/** @typedef {import("puppeteer").NetworkConditions} NetworkConditions */
/** @typedef {keyof typeof import("puppeteer").networkConditions} NetworkConditionName */
/** @typedef {keyof typeof PredefinedNetworkConditions} NetworkConditionName */
/** @typedef {import("puppeteer").Device} Device */
/** @typedef {keyof typeof KnownDevices} KnownDeviceName */

/* eslint-enable jsdoc/valid-types */
// TODO: deviceScaleFactor, isMobile, isLandscape, hasTouch.
/** @typedef {{width: number, height: number}} ViewportDimensions */

/**
Expand Down Expand Up @@ -97,10 +105,15 @@ export const options = [
description:
'Enable emulation of network conditions (may be either "Slow 3G" or "Fast 3G")',
},
{
argname: '-e, --emulate-device <device>',
description:
'Enable a specific device by name, for example "Moto G4" or "iPad"',
},
{
argname: '-w, --window-viewport <dimensions>',
description:
'Open page with the supplied viewport dimensions such as "mobile" (an alias for "412x823") or "desktop" (an alias for "1350x940"), defaults to "960x700"',
'Open page with the supplied viewport dimensions such as "mobile" (an alias for "412x823") or "desktop" (an alias for "1350x940"), defaults to "960x700" if no specific device is being emulated',
},
];

Expand All @@ -115,6 +128,7 @@ export const options = [
* @property {boolean} showVariance - See above.
* @property {?number} cpuThrottleFactor - See above.
* @property {?NetworkConditions} networkConditions - See above.
* @property {?Device} emulateDevice - See above.
* @property {?ViewportDimensions} windowViewport - See above.
*/

Expand All @@ -129,17 +143,18 @@ export const options = [
*/

/**
* @param {Object} opt
* @param {?string} opt.url
* @param {string|number} opt.number
* @param {?string} opt.file
* @param {?string[]} opt.metrics
* @param {string} opt.output
* @param {boolean} opt.showPercentiles
* @param {boolean} opt.showVariance
* @param {?string} opt.throttleCpu
* @param {?NetworkConditionName} opt.networkConditions
* @param {?string} opt.windowViewport
* @param {Object} opt
* @param {?string} opt.url
* @param {string|number} opt.number
* @param {?string} opt.file
* @param {?string[]} opt.metrics
* @param {string} opt.output
* @param {boolean} opt.showPercentiles
* @param {boolean} opt.showVariance
* @param {?string} opt.throttleCpu
* @param {?string} opt.networkConditions
* @param {?string} opt.emulateDevice
* @param {?string} opt.windowViewport
* @return {Params} Parameters.
*/
function getParamsFromOptions( opt ) {
Expand All @@ -160,7 +175,10 @@ function getParamsFromOptions( opt ) {
showVariance: Boolean( opt.showVariance ),
cpuThrottleFactor: null,
networkConditions: null,
windowViewport: { width: 960, height: 700 }, // Viewport similar to @wordpress/e2e-test-utils 'large' configuration.
emulateDevice: null,
windowViewport: ! opt.emulateDevice
? { width: 960, height: 700 }
: null, // Viewport similar to @wordpress/e2e-test-utils 'large' configuration.
};

if ( isNaN( params.amount ) ) {
Expand Down Expand Up @@ -200,10 +218,18 @@ function getParamsFromOptions( opt ) {
PredefinedNetworkConditions[ opt.networkConditions ];
}

if ( opt.emulateDevice ) {
if ( ! ( opt.emulateDevice in KnownDevices ) ) {
throw new Error(
`Unrecognized device to emulate: ${ opt.emulateDevice }`
);
}
params.emulateDevice = KnownDevices[ opt.emulateDevice ];
}

if ( opt.windowViewport ) {
if ( 'mobile' === opt.windowViewport ) {
// This corresponds to the mobile viewport tested in Lighthouse: <https://github.com/GoogleChrome/lighthouse/blob/b64b3534542c9dcaabb33d40b84ed7c93eefbd7d/core/config/constants.js#L14-L22>.
// TODO: Consider deviceScaleFactor.
params.windowViewport = {
width: 412,
height: 823,
Expand Down Expand Up @@ -338,7 +364,7 @@ export async function handler( opt ) {
const params = getParamsFromOptions( opt );
const results = [];

const browser = await puppeteer.launch( { headless: 'new' } );
const browser = await puppeteer.launch( { headless: true } );

const metricsDefinition = getMetricsDefinition( params.metrics );

Expand Down Expand Up @@ -449,12 +475,17 @@ async function benchmarkURL(
await page.emulateNetworkConditions( params.networkConditions );
}

await page.setViewport( params.windowViewport );
await page
.mainFrame()
.waitForFunction(
`window.innerWidth === ${ params.windowViewport.width } && window.innerHeight === ${ params.windowViewport.height }`
);
if ( params.emulateDevice ) {
await page.emulate( params.emulateDevice );
}
if ( params.windowViewport ) {
await page.setViewport( {
...( params.emulateDevice
? params.emulateDevice.viewport
: {} ),
...params.windowViewport,
} );
}

// Load the page.
const urlObj = new URL( url );
Expand Down

0 comments on commit f0ef75e

Please sign in to comment.