browsertime
Version:
Get performance metrics from your web page using Browsertime.
267 lines (230 loc) • 8.92 kB
JavaScript
import { platform } from 'node:os';
import {
ServiceBuilder,
Options,
Channel
} from 'selenium-webdriver/firefox.js';
import intel from 'intel';
import { pac, manual } from 'selenium-webdriver/proxy.js';
import pick from 'lodash.pick';
import isEmpty from 'lodash.isempty';
import get from 'lodash.get';
import { defaultFirefoxPreferences } from '../settings/firefoxPreferences.js';
import { disableSafeBrowsingPreferences } from '../settings/disableSafeBrowsingPreferences.js';
import { disableTrackingProtectionPreferences } from '../settings/disableTrackingProtectionPreferences.js';
import { toArray } from '../../support/util.js';
import { Android, isAndroidConfigured } from '../../android/index.js';
const log = intel.getLogger('browsertime.firefox');
export async function configureBuilder(builder, baseDir, options) {
// Here we configure everything that we need to start Firefox
const firefoxConfig = options.firefox || {};
let geckodriverPath = get(firefoxConfig, 'geckodriverPath');
if (!geckodriverPath) {
const geckodriver = await import('@sitespeed.io/geckodriver');
geckodriverPath = geckodriver.default.binPath();
}
const serviceBuilder = new ServiceBuilder(geckodriverPath);
if (options.verbose >= 2) {
// This echoes the output from geckodriver to the console.
serviceBuilder.setStdio('inherit');
// TODO: serviceBuilder.loggingTo(`${baseDir}/geckodriver.log`);
if (options.verbose >= 3) {
serviceBuilder.enableVerboseLogging(true);
}
}
// https://firefox-source-docs.mozilla.org/testing/geckodriver/Flags.html
let geckodriverArguments = get(firefoxConfig, 'geckodriverArgs');
if (geckodriverArguments) {
if (!Array.isArray(geckodriverArguments))
geckodriverArguments = [geckodriverArguments];
for (let argument of geckodriverArguments) {
serviceBuilder.addArguments(argument);
}
}
builder.setFirefoxService(serviceBuilder);
const ffOptions = new Options();
const profileTemplate = get(firefoxConfig, 'profileTemplate');
if (profileTemplate) {
ffOptions.setProfile(get(firefoxConfig, 'profileTemplate'));
}
if (options.userAgent) {
ffOptions.setPreference('general.useragent.override', options.userAgent);
}
if (firefoxConfig.collectMozLog) {
process.env.MOZ_LOG = firefoxConfig.setMozLog;
process.env.MOZ_LOG_FILE = `${baseDir}/moz_log.txt`;
}
// Output the window recorder image frames to a base directory.
// This pref expects a trailing slash.
if (firefoxConfig.windowRecorder) {
if (isAndroidConfigured(options)) {
const android = new Android(options);
const pathToRecording = android.getFullPathOnSdCard(
'browsertime-firefox-windowrecording'
);
ffOptions.setPreference(
'layers.windowrecording.path',
`${pathToRecording}/`
);
} else {
ffOptions.setPreference('layers.windowrecording.path', `${baseDir}/`);
}
}
if (firefoxConfig.noDefaultPrefs) {
log.info('Skip setting default preferences for Firefox');
} else {
for (const pref of Object.keys(defaultFirefoxPreferences)) {
ffOptions.setPreference(pref, defaultFirefoxPreferences[pref]);
}
}
if (firefoxConfig.disableSafeBrowsing) {
for (const pref of Object.keys(disableSafeBrowsingPreferences)) {
ffOptions.setPreference(pref, disableSafeBrowsingPreferences[pref]);
}
}
if (firefoxConfig.disableTrackingProtection) {
for (const pref of Object.keys(disableTrackingProtectionPreferences)) {
ffOptions.setPreference(pref, disableTrackingProtectionPreferences[pref]);
}
}
if (options.debug) {
ffOptions.addArguments('--devtools');
// Mozilla use detach to make sure you can inspect the browser
ffOptions.setPreference('detach', true);
}
if (options.extension) {
const extensions = Array.isArray(options.extension)
? options.extension
: [options.extension];
for (const extension of extensions) {
ffOptions.addExtensions(extension);
}
}
ffOptions.setPreference('devtools.chrome.enabled', true);
const userPrefs = toArray(firefoxConfig.preference);
log.debug('Set Firefox preference %j', userPrefs);
for (const pref of userPrefs) {
const name = pref.slice(0, Math.max(0, pref.indexOf(':')));
let value = pref.slice(pref.indexOf(':') + 1);
if (value === 'false') {
ffOptions.setPreference(name, false);
} else if (value === 'true') {
ffOptions.setPreference(name, true);
// eslint-disable-next-line unicorn/prefer-number-properties
} else if (isNaN(value)) {
ffOptions.setPreference(name, value);
} else {
ffOptions.setPreference(name, Number(value));
}
}
let firefoxTypes = [
get(firefoxConfig, 'binaryPath') ?? undefined,
get(firefoxConfig, 'nightly') ? Channel.NIGHTLY : undefined,
get(firefoxConfig, 'beta') ? Channel.BETA : undefined,
get(firefoxConfig, 'developer') ? Channel.AURORA : undefined
];
firefoxTypes = firefoxTypes.filter(function (n) {
return n !== undefined;
});
// Do not set binary when using Android
// https://bugzilla.mozilla.org/show_bug.cgi?id=1751196
if (!isAndroidConfigured(options)) {
// Fix for https://github.com/sitespeedio/browsertime/issues/2041
if (platform() === 'darwin') {
if (
!firefoxConfig.binaryPath &&
!firefoxConfig.nightly &&
!firefoxConfig.beta &&
!firefoxConfig.developer
) {
ffOptions.setBinary('/Applications/Firefox.app/Contents/MacOS/firefox');
} else if (firefoxConfig.nightly) {
ffOptions.setBinary(
'/Applications/Firefox Nightly.app/Contents/MacOS/firefox'
);
} else if (firefoxConfig.beta) {
ffOptions.setBinary(
'/Applications/Firefox Nightly.app/Contents/MacOS/firefox'
);
} else if (firefoxConfig.developer) {
ffOptions.setBinary(
'/Applications/FirefoxDeveloperEdition.app/Contents/MacOS/firefox'
);
} else if (firefoxConfig.binaryPath) {
ffOptions.setBinary(firefoxConfig.binaryPath);
}
} else {
// See https://github.com/sitespeedio/browsertime/issues/2041
ffOptions.setBinary(
firefoxTypes.length > 0 ? firefoxTypes[0] : Channel.RELEASE
);
}
}
ffOptions.addArguments('-no-remote');
ffOptions.enableBidi();
if (firefoxConfig.args) {
ffOptions.addArguments(firefoxConfig.args);
}
const environments = toArray(firefoxConfig.env);
ffOptions.firefoxOptions_().env = ffOptions.firefoxOptions_().env || {};
for (const environment of environments) {
const name = environment.slice(0, Math.max(0, environment.indexOf('=')));
const value = environment.slice(environment.indexOf('=') + 1);
if (isAndroidConfigured(options)) {
ffOptions.firefoxOptions_().env[name] = value;
} else {
process.env[name] = value;
}
}
if (options.headless) {
ffOptions.addArguments('-headless');
}
if (firefoxConfig.acceptInsecureCerts) {
builder.getCapabilities().set('acceptInsecureCerts', true);
}
builder
.getCapabilities()
.set('pageLoadStrategy', get(options, 'pageLoadStrategy', 'normal'));
if (isAndroidConfigured(options)) {
// Monkey patch to avoid changing `selenium-webdriver` before the
// corresponding `geckodriver` functionality has been replaced:
// see this [Firefox bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1525126)
// currently under review.
const android = firefoxConfig.android;
if (android && android.package) {
ffOptions.firefoxOptions_().androidPackage = android.package;
if (android && android.activity) {
ffOptions.firefoxOptions_().androidActivity = android.activity;
}
} else {
ffOptions.firefoxOptions_().androidPackage = 'org.mozilla.firefox';
ffOptions.firefoxOptions_().androidActivity =
'org.mozilla.fenix.IntentReceiverActivity';
}
if (android && android.deviceSerial) {
ffOptions.firefoxOptions_().androidDeviceSerial = android.deviceSerial;
}
ffOptions.firefoxOptions_().androidIntentArguments = [
'-a',
'android.intent.action.VIEW',
'-d',
'about:blank'
];
if (android && android.intentArgument) {
ffOptions.firefoxOptions_().androidIntentArguments = (
ffOptions.firefoxOptions_().androidIntentArguments || []
)
// eslint-disable-next-line unicorn/prefer-spread
.concat(...toArray(android.intentArgument));
}
}
const proxyPacSettings = pick(options.proxy, ['pac']);
if (!isEmpty(proxyPacSettings)) {
builder.setProxy(pac(proxyPacSettings));
}
const proxySettings = pick(options.proxy, ['ftp', 'http', 'https', 'bypass']);
if (!isEmpty(proxySettings)) {
builder.setProxy(manual(proxySettings));
}
builder.setFirefoxOptions(ffOptions);
}