browsertime
Version:
Get performance metrics from your web page using Browsertime.
286 lines (255 loc) • 9.97 kB
JavaScript
import { readFileSync } from 'node:fs';
import { getLogger } from '@sitespeed.io/log';
import { chromeDesktopOptions as defaultChromeOptions } from '../settings/chromeDesktopOptions.js';
import { chromeAndroidOptions as defaultAndroidChromeOptions } from '../settings/chromeAndroidOptions.js';
import { getViewPort } from '../../support/getViewPort.js';
import { toArray } from '../../support/util.js';
import { isAndroidConfigured } from '../../android/index.js';
const log = getLogger('browsertime.chrome');
const CHROME_AMD_EDGE_INTERNAL_PHONE_HOME = [
'MAP cache.pack.google.com 127.0.0.1',
'MAP clients1.google.com 127.0.0.1',
'MAP update.googleapis.com 127.0.0.1',
'MAP content-autofill.googleapis.com 127.0.0.1',
'MAP redirector.gvt1.com 127.0.0.1',
'MAP laptop-updates.brave.com 127.0.0.1',
'MAP offlinepages-pa.googleapis.com 127.0.0.1',
'MAP edge.microsoft.com 127.0.0.1',
'MAP optimizationguide-pa.googleapis.com 127.0.0.1'
];
const CHROME_FEATURES_THAT_WE_ENABLE = ['SoftNavigationHeuristics'];
const CHROME_FEATURES_THAT_WE_DISABLES = [
'AutofillServerCommunication',
'CalculateNativeWinOcclusion',
'HeavyAdPrivacyMitigations',
'InterestFeedContentSuggestions',
'MediaRouter',
'OfflinePagesPrefetching',
'OptimizationHints',
'SidePanelPinning',
'Translate',
'msAutofillEdgeCoupons',
'msShoppingTrigger',
'msEdgeShoppingUI',
'msEntityExtraction',
'msEntityExtractionProactive',
'msWebAssist'
];
export function setupChromiumOptions(
seleniumOptions,
browserOptions,
options,
baseDir
) {
// Record every argument / preference / capability we apply to the browser
// so it can be surfaced in the result JSON for debugging — issue #1622.
// Stored on `options` because both Chrome and Edge call this and both end
// up needing it later in the engine for `result.info.browser`.
const recorded = {
args: [],
preferences: {},
mobileEmulation: undefined,
binaryPath: undefined,
extensions: 0
};
options.recordedBrowserSettings = recorded;
const addArgs = arg => {
seleniumOptions.addArguments(arg);
if (Array.isArray(arg)) {
recorded.args.push(...arg);
} else {
recorded.args.push(arg);
}
};
const setPrefs = prefs => {
seleniumOptions.setUserPreferences(prefs);
Object.assign(recorded.preferences, prefs);
};
// Fixing save password popup, only on Desktop
if (!isAndroidConfigured(options)) {
setPrefs({
'profile.password_manager_enable': false,
'profile.default_content_setting_values.notifications': 2,
credentials_enable_service: false
});
}
if (options.headless) {
addArgs('--headless=new');
}
// If we run in Docker we need to always use no-sandbox
if (options.docker) {
addArgs('--no-sandbox');
addArgs('--disable-setuid-sandbox');
}
if (options.xvfb && (options.xvfb === true || options.xvfb === 'true')) {
addArgs('--disable-gpu');
}
addArgs('--disable-features=' + CHROME_FEATURES_THAT_WE_DISABLES.join(','));
addArgs('--enable-features=' + CHROME_FEATURES_THAT_WE_ENABLE.join(','));
const viewPort = getViewPort(options);
// If viewport is defined (only on desktop) then set start args
if (viewPort) {
addArgs('--window-position=0,0');
addArgs('--window-size=' + viewPort.replace('x', ','));
// Desktop Chrome enforces a minimum window width (~500px) regardless of
// --window-size, so a request like --viewPort 360x640 silently becomes
// 500x640. The video and filmstrips are still cropped to the requested
// size, which is why they look chopped. Mobile emulation is the supported
// way to test smaller viewports — it tells the renderer to lay out at
// the requested size while the OS window can stay desktop-sized.
// See https://github.com/sitespeedio/browsertime/issues/1222
const usingEmulation =
browserOptions.mobileEmulation &&
(browserOptions.mobileEmulation.deviceName ||
(browserOptions.mobileEmulation.width &&
browserOptions.mobileEmulation.height));
if (!usingEmulation) {
const [w] = viewPort.split('x').map(Number);
if (w && w < 500) {
log.warn(
'Requested viewport %s — desktop Chrome enforces a minimum window width of about 500px and will silently use a wider window. The video, filmstrip and screenshots will be cropped because they are sized to the requested viewport. To test smaller viewports, use Chrome mobile emulation: --chrome.mobileEmulation.deviceName "iPhone X" (or set --chrome.mobileEmulation.width and --chrome.mobileEmulation.height).',
viewPort
);
}
}
}
// If we are recording responses and we also block on domain
if (
browserOptions.blockDomainsExcept &&
browserOptions.webPageReplayHostResolver &&
browserOptions.webPageReplayRecord
) {
const firstPartyDomains = toArray(browserOptions.blockDomainsExcept);
let excludes = '';
for (let domain of firstPartyDomains) {
excludes += ` MAP ${domain}:80 127.0.0.1:${browserOptions.webPageReplayHTTPPort},`;
excludes += ` MAP ${domain}:443 127.0.0.1:${browserOptions.webPageReplayHTTPSPort},`;
}
excludes += ' EXCLUDE localhost';
addArgs('--host-resolver-rules=' + excludes);
} // If we are replaying with WebPageReplay
else if (browserOptions.webPageReplayHostResolver) {
addArgs(
`--host-resolver-rules= "MAP *:80 127.0.0.1:${browserOptions.webPageReplayHTTPPort}, MAP *:443 127.0.0.1:${browserOptions.webPageReplayHTTPSPort}, EXCLUDE localhost"`
);
} // If we do not use WebPageReplay but wanna block on domain
else if (browserOptions.blockDomainsExcept) {
let excludes = '';
let excludesDomains = toArray(browserOptions.blockDomainsExcept);
for (let domain of excludesDomains) {
excludes += 'MAP * 127.0.0.1, EXCLUDE ' + domain + ',';
}
addArgs('--host-resolver-rules=' + excludes);
} else {
// Make sure we only set this if we do not have any other host resolver rules
const chromeCommandLineArguments = toArray(browserOptions.args);
const argumentsWithHostResolverRules = chromeCommandLineArguments.filter(
argument => argument.includes('host-resolver-rules')
);
if (argumentsWithHostResolverRules.length === 0) {
addArgs(
`--host-resolver-rules="${CHROME_AMD_EDGE_INTERNAL_PHONE_HOME.join(
','
)}"`
);
}
}
if (options.extension) {
const extensions = Array.isArray(options.extension)
? options.extension
: [options.extension];
for (const extension of extensions) {
seleniumOptions.addExtensions(
readFileSync(extension, { encoding: 'base64' })
);
recorded.extensions += 1;
}
}
if (options.debug) {
addArgs('--auto-open-devtools-for-tabs');
}
const perfLogConfig = { enableNetwork: true, enablePage: true };
seleniumOptions.setPerfLoggingPrefs(perfLogConfig);
if (options.userAgent) {
addArgs('--user-agent=' + options.userAgent);
}
if (browserOptions.ignoreCertificateErrors) {
addArgs('--ignore-certificate-errors');
}
if (browserOptions.collectNetLog) {
const dir = isAndroidConfigured(browserOptions)
? '/data/local/tmp'
: baseDir;
addArgs(`--log-net-log=${dir}/chromeNetlog.json`);
const level = browserOptions.netLogCaptureMode || 'IncludeSensitive';
addArgs(`--net-log-capture-mode=${level}`);
}
if (browserOptions.android) {
addArgs(defaultAndroidChromeOptions);
addArgs('--remote-debugging-port=' + options.devToolsPort);
} else {
if (browserOptions.noDefaultOptions) {
log.info('Skip setting default options for Chrome');
} else {
addArgs(defaultChromeOptions);
addArgs('--remote-debugging-port=' + options.devToolsPort);
}
}
if (browserOptions.enableVideoAutoplay) {
addArgs('--autoplay-policy=no-user-gesture-required');
}
// It's a new splash screen introduced in Chrome 98
// for new profiles
// disable it with ChromeWhatsNewUI
if (browserOptions.args) {
const chromeCommandLineArguments = toArray(browserOptions.args);
for (const argument of chromeCommandLineArguments) {
if (
argument.includes('disable-features') &&
!argument.includes('ChromeWhatsNewUI')
) {
addArgs(`${argument},ChromeWhatsNewUI`);
log.debug('Set Chrome args %j', `${argument},ChromeWhatsNewUI`);
} else if (
argument.includes('enable-features') &&
!argument.includes('SoftNavigationHeuristics')
) {
addArgs(`${argument},SoftNavigationHeuristics`);
log.debug('Set Chrome args %j', `${argument},SoftNavigationHeuristics`);
} else {
addArgs(argument);
log.debug('Set Chrome args %j', argument);
}
}
} else {
addArgs('--disable-features=ChromeWhatsNewUI');
}
if (browserOptions.binaryPath) {
seleniumOptions.setChromeBinaryPath(browserOptions.binaryPath);
recorded.binaryPath = browserOptions.binaryPath;
}
if (browserOptions.mobileEmulation) {
seleniumOptions.setMobileEmulation(browserOptions.mobileEmulation);
recorded.mobileEmulation = browserOptions.mobileEmulation;
}
// See https://bugs.chromium.org/p/chromium/issues/detail?id=818483
// Coming again in Chrome 76
seleniumOptions.excludeSwitches('enable-automation');
const android = browserOptions.android;
if (android) {
if (android.package) {
seleniumOptions.androidPackage(android.package);
if (android.activity) {
seleniumOptions.androidActivity(android.activity);
if (android.process) {
seleniumOptions.androidProcess(android.process);
}
}
} else {
seleniumOptions.androidChrome();
}
seleniumOptions.androidDeviceSerial(android.deviceSerial);
}
log.debug('Setting the following Selenium options: %j', seleniumOptions);
return seleniumOptions;
}