diff --git a/cli/README.md b/cli/README.md index 30dda09..4cc04dd 100644 --- a/cli/README.md +++ b/cli/README.md @@ -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 diff --git a/cli/commands/benchmark-web-vitals.mjs b/cli/commands/benchmark-web-vitals.mjs index 27cf3da..8a5a1a9 100644 --- a/cli/commands/benchmark-web-vitals.mjs +++ b/cli/commands/benchmark-web-vitals.mjs @@ -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 */ /** @@ -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 ', + description: + 'Enable a specific device by name, for example "Moto G4" or "iPad"', + }, { argname: '-w, --window-viewport ', 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', }, ]; @@ -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. */ @@ -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 ) { @@ -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 ) ) { @@ -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: . - // TODO: Consider deviceScaleFactor. params.windowViewport = { width: 412, height: 823, @@ -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 ); @@ -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 );