Secure your code as it's written. Use Snyk Code to scan source code in minutes - no build needed - and fix issues immediately.
.version(() => require('./package.json').version)
.alias('v', 'version')
.showHelpOnFail(false, 'Specify --help for available options')
.boolean(['reset', 'view', 'headless'])
.default('output', 'html')
.default('output-path', './public/results.html')
.default('log-level', 'info')
.argv;
flags.chromePath = process.env.CHROME_PATH || null;
const url = yargs.argv._[0];
const runner = new LighthouseRunner(url, flags);//, PERF_CONFIG);
const lights = new HueLights(BRIDGE_IP, USERNAME);
Log.setLevel(flags.logLevel);
/**
* Creates new "Lighthouse" user on the Hue bridge if needed.
*/
function createHueUserIfNeeded() {
const setHostNamePromise = BRIDGE_IP ? Promise.resolve(BRIDGE_IP) :
lights.setHostnameOfBridge();
return setHostNamePromise
.then(hostname => lights.config())
.then(config => {
// Username is registered with the Hue.
if ('linkbutton' in config) {
console.log(`${Log.purple}Hue:${Log.reset} Re-using known user`);
return lights.username;
}
const lhFlags = {...LIGHTHOUSE_FLAGS, onlyCategories: Object.keys(minScores).sort()};
const lhConfig = {
extends: 'lighthouse:default',
// Since the Angular ServiceWorker waits for the app to stabilize before registering,
// wait a few seconds after load to allow Lighthouse to reliably detect it.
passes: [{passName: 'defaultPass', pauseAfterLoadMs: WAIT_FOR_SW_DELAY}],
};
console.log(`Running web-app audits for '${url}'...`);
console.log(` Audit categories: ${lhFlags.onlyCategories.join(', ')}`);
// If testing on HTTP, skip HTTPS-specific tests.
// (Note: Browsers special-case localhost and run ServiceWorker even on HTTP.)
if (isOnHttp) skipHttpsAudits(lhConfig);
logger.setLevel(lhFlags.logLevel);
try {
console.log('');
const startTime = Date.now();
const results = await launchChromeAndRunLighthouse(url, lhFlags, lhConfig);
const success = await processResults(results, minScores, logFile);
console.log(`\n(Completed in ${((Date.now() - startTime) / 1000).toFixed(1)}s.)\n`);
if (!success) {
throw new Error('One or more scores are too low.');
}
} catch (err) {
onError(err);
}
}
.then(config => {
// Username is registered with the Hue.
if ('linkbutton' in config) {
console.log(`${Log.purple}Hue:${Log.reset} Re-using known user`);
return lights.username;
}
console.log(`${Log.purple}Hue:${Log.reset} Creating new user on bridge.`);
return lights.createUser(APP_DESCRIPTION);
});
}
async computeOptimizedImages(driver, imageRecords) {
this._encodingStartAt = Date.now();
/** @type {LH.Artifacts['OptimizedImages']} */
const results = [];
for (const record of imageRecords) {
try {
const stats = await this.calculateImageStats(driver, record);
/** @type {LH.Artifacts.OptimizedImage} */
const image = {failed: false, ...stats, ...record};
results.push(image);
} catch (err) {
log.warn('optimized-images', err.message);
// Track this with Sentry since these errors aren't surfaced anywhere else, but we don't
// want to tank the entire run due to a single image.
Sentry.captureException(err, {
tags: {gatherer: 'OptimizedImages'},
extra: {imageUrl: URL.elideDataURI(record.url)},
level: 'warning',
});
/** @type {LH.Artifacts.OptimizedImageError} */
const imageError = {failed: true, errMsg: err.message, ...record};
results.push(imageError);
}
}
return results;
'service_workers',
'cache_storage',
].join(',');
// `Storage.clearDataForOrigin` is one of our PROTOCOL_TIMEOUT culprits and this command is also
// run in the context of PAGE_HUNG to cleanup. We'll keep the timeout low and just warn if it fails.
this.setNextProtocolTimeout(5000);
try {
await this.sendCommand('Storage.clearDataForOrigin', {
origin: origin,
storageTypes: typesToClear,
});
} catch (err) {
if (/** @type {LH.LighthouseError} */(err).code === 'PROTOCOL_TIMEOUT') {
log.warn('Driver', 'clearDataForOrigin timed out');
} else {
throw err;
}
}
}
id: `lh:audit:${audit.meta.id}`,
};
log.time(status);
let auditResult;
try {
// Return an early error if an artifact required for the audit is missing or an error.
for (const artifactName of audit.meta.requiredArtifacts) {
const noArtifact = artifacts[artifactName] === undefined;
// If trace required, check that DEFAULT_PASS trace exists.
// TODO: need pass-specific check of networkRecords and traces.
const noTrace = artifactName === 'traces' && !artifacts.traces[Audit.DEFAULT_PASS];
if (noArtifact || noTrace) {
log.warn('Runner',
`${artifactName} gatherer, required by audit ${audit.meta.id}, did not run.`);
throw new Error(`Required ${artifactName} gatherer did not run.`);
}
// If artifact was an error, output error result on behalf of audit.
if (artifacts[artifactName] instanceof Error) {
/** @type {Error} */
// @ts-ignore An artifact *could* be an Error, but caught here, so ignore elsewhere.
const artifactError = artifacts[artifactName];
Sentry.captureException(artifactError, {
tags: {gatherer: artifactName},
level: 'error',
});
log.warn('Runner', `${artifactName} gatherer, required by audit ${audit.meta.id},` +
delete this.chrome;
this.destroyTmp().then(resolve);
});
log.log('ChromeLauncher', `Killing Chrome instance ${this.chrome.pid}`);
try {
if (isWindows) {
// While pipe is the default, stderr also gets printed to process.stderr
// if you don't explicitly set `stdio`
execSync(`taskkill /pid ${this.chrome.pid} /T /F`, {stdio: 'pipe'});
} else {
process.kill(-this.chrome.pid);
}
} catch (err) {
const message = `Chrome could not be killed ${err.message}`;
log.warn('ChromeLauncher', message);
reject(new Error(message));
}
} else {
// fail silently as we did not start chrome
resolve();
}
});
}
static async beforePass(passContext, gathererResults) {
const bpStatus = {msg: `Running beforePass methods`, id: `lh:gather:beforePass`};
log.time(bpStatus, 'verbose');
const blockedUrls = (passContext.passConfig.blockedUrlPatterns || [])
.concat(passContext.settings.blockedUrlPatterns || []);
// Set request blocking before any network activity
// No "clearing" is done at the end of the pass since blockUrlPatterns([]) will unset all if
// neccessary at the beginning of the next pass.
await passContext.driver.blockUrlPatterns(blockedUrls);
await passContext.driver.setExtraHTTPHeaders(passContext.settings.extraHeaders);
for (const gathererDefn of passContext.passConfig.gatherers) {
const gatherer = gathererDefn.instance;
// Abuse the passContext to pass through gatherer options
passContext.options = gathererDefn.options || {};
const status = {
msg: `Retrieving setup: ${gatherer.name}`,
id: `lh:gather:beforePass:${gatherer.name}`,
async getBrowserVersion() {
const status = {msg: 'Getting browser version', id: 'lh:gather:getVersion'};
log.time(status, 'verbose');
const version = await this.sendCommand('Browser.getVersion');
const match = version.product.match(/\/(\d+)/); // eg 'Chrome/71.0.3577.0'
const milestone = match ? parseInt(match[1]) : 0;
log.timeEnd(status);
return Object.assign(version, {milestone});
}
async cleanBrowserCaches() {
const status = {msg: 'Cleaning browser cache', id: 'lh:driver:cleanBrowserCaches'};
log.time(status);
// Wipe entire disk cache
await this.sendCommand('Network.clearBrowserCache');
// Toggle 'Disable Cache' to evict the memory cache
await this.sendCommand('Network.setCacheDisabled', {cacheDisabled: true});
await this.sendCommand('Network.setCacheDisabled', {cacheDisabled: false});
log.timeEnd(status);
}