Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ci(docs-infra): increase wait for SW on localhost to avoid CI flakes #29953

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 2 additions & 8 deletions .circleci/config.yml
Expand Up @@ -249,10 +249,7 @@ jobs:
- run: yarn --cwd aio lint
# Run PWA-score tests
# (Run before unit and e2e tests, which destroy the `dist/` directory.)
# Temporarily lowering the min required PWA score to avoid flakes on CI.
# TODO(gkalpak): Re-enable once https://github.com/angular/angular/issues/29910 is resolved.
# - run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
- run: yarn --cwd aio test-pwa-score-localhost 70
- run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
# Check the bundle sizes.
# (Run before unit and e2e tests, which destroy the `dist/` directory.)
- run: yarn --cwd aio payload-size
Expand Down Expand Up @@ -287,10 +284,7 @@ jobs:
- run: yarn --cwd aio build-local --progress=false
# Run PWA-score tests
# (Run before unit and e2e tests, which destroy the `dist/` directory.)
# Temporarily lowering the min required PWA score to avoid flakes on CI.
# TODO(gkalpak): Re-enable once https://github.com/angular/angular/issues/29910 is resolved.
# - run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
- run: yarn --cwd aio test-pwa-score-localhost 70
- run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
# Run unit tests
- run: yarn --cwd aio test --progress=false --watch=false
# Run e2e tests
Expand Down
102 changes: 60 additions & 42 deletions aio/scripts/test-pwa-score.js
Expand Up @@ -2,7 +2,9 @@

/**
* Usage:
* node scripts/test-pwa-score <url> <min-score> [<log-file>]
* ```sh
* node scripts/test-pwa-score <url> <min-score> [<log-file>]
* ```
*
* Fails if the score is below `<min-score>`.
* If `<log-file>` is defined, the full results will be logged there.
Expand All @@ -14,12 +16,12 @@
const chromeLauncher = require('chrome-launcher');
const lighthouse = require('lighthouse');
const printer = require('lighthouse/lighthouse-cli/printer');
const config = require('lighthouse/lighthouse-core/config/default-config.js');
const logger = require('lighthouse-logger');

// Constants
const CHROME_LAUNCH_OPTS = {};
const LIGHTHOUSE_FLAGS = {logLevel: 'info'};
const LONG_WAIT_FOR_SW_DELAY = 5000;
const SKIPPED_HTTPS_AUDITS = ['redirects-http'];
const VIEWER_URL = 'https://googlechrome.github.io/lighthouse/viewer/';

Expand All @@ -32,22 +34,31 @@ if (process.env.CI) {
_main(process.argv.slice(2));

// Functions - Definitions
function _main(args) {
async function _main(args) {
const {url, minScore, logFile} = parseInput(args);
const isOnHttp = /^http:/.test(url);
const isOnLocalhost = /\/\/localhost\b/.test(url);
const config = {extends: 'lighthouse:default'};

console.log(`Running PWA audit for '${url}'...`);

if (isOnHttp) {
skipHttpsAudits(config);
}
// If testing on HTTP, skip HTTPS-specific tests.
// (Note: Browsers special-case localhost and run ServiceWorker even on HTTP.)
if (isOnHttp) skipHttpsAudits(config);

// If testing on localhost, where the server has less optimizations (e.g. no file compression),
// wait longer for the ServiceWorker to be registered, so Lighthouse can reliably detect it.
if (isOnLocalhost) waitLongerForSw(config);

logger.setLevel(LIGHTHOUSE_FLAGS.logLevel);

launchChromeAndRunLighthouse(url, LIGHTHOUSE_FLAGS, config).
then(results => processResults(results, logFile)).
then(score => evaluateScore(minScore, score)).
catch(onError);
try {
const results = await launchChromeAndRunLighthouse(url, LIGHTHOUSE_FLAGS, config);
const score = await processResults(results, logFile);
evaluateScore(minScore, score);
} catch (err) {
onError(err);
}
}

function evaluateScore(expectedScore, actualScore) {
Expand All @@ -60,13 +71,15 @@ function evaluateScore(expectedScore, actualScore) {
}
}

function launchChromeAndRunLighthouse(url, flags, config) {
return chromeLauncher.launch(CHROME_LAUNCH_OPTS).then(chrome => {
flags.port = chrome.port;
return lighthouse(url, flags, config).
then(results => chrome.kill().then(() => results)).
catch(err => chrome.kill().then(() => { throw err; }, () => { throw err; }));
});
async function launchChromeAndRunLighthouse(url, flags, config) {
const chrome = await chromeLauncher.launch(CHROME_LAUNCH_OPTS);
flags.port = chrome.port;

try {
return await lighthouse(url, flags, config);
} finally {
await chrome.kill();
}
}

function onError(err) {
Expand All @@ -88,36 +101,41 @@ function parseInput(args) {
return {url, minScore, logFile};
}

function processResults(results, logFile) {
async function processResults(results, logFile) {
const lhVersion = results.lhr.lighthouseVersion;
const categories = results.lhr.categories;
const report = results.report;

return Promise.resolve().
then(() => {
if (logFile) {
console.log(`Saving results in '${logFile}'...`);
console.log(`(LightHouse viewer: ${VIEWER_URL})`);

return printer.write(report, printer.OutputMode.json, logFile);
}
}).
then(() => {
const categoryData = Object.keys(categories).map(name => categories[name]);
const maxTitleLen = Math.max(...categoryData.map(({title}) => title.length));

console.log('\nLighthouse version:', results.lhr.lighthouseVersion);

console.log('\nAudit scores:');
categoryData.forEach(({title, score}) => {
const paddedTitle = `${title}:`.padEnd(maxTitleLen + 1);
const paddedScore = (score * 100).toFixed(0).padStart(3);
console.log(` - ${paddedTitle} ${paddedScore} / 100`);
});
}).
then(() => categories.pwa.score * 100);
if (logFile) {
console.log(`\nSaving results in '${logFile}'...`);
console.log(`(LightHouse viewer: ${VIEWER_URL})`);

await printer.write(report, printer.OutputMode.json, logFile);
}

const categoryData = Object.keys(categories).map(name => categories[name]);
const maxTitleLen = Math.max(...categoryData.map(({title}) => title.length));

console.log(`\nLighthouse version: ${lhVersion}`);

console.log('\nAudit scores:');
categoryData.forEach(({title, score}) => {
const paddedTitle = `${title}:`.padEnd(maxTitleLen + 1);
const paddedScore = (score * 100).toFixed(0).padStart(3);
console.log(` - ${paddedTitle} ${paddedScore} / 100`);
});

return categories.pwa.score * 100;
}

function skipHttpsAudits(config) {
console.info(`Skipping HTTPS-related audits (${SKIPPED_HTTPS_AUDITS.join(', ')})...`);
config.settings.skipAudits = SKIPPED_HTTPS_AUDITS;
const settings = config.settings || (config.settings = {});
settings.skipAudits = SKIPPED_HTTPS_AUDITS;
}

function waitLongerForSw(config) {
console.info(`Will wait longer for ServiceWorker (${LONG_WAIT_FOR_SW_DELAY}ms)...`);
const passes = config.passes || (config.passes = []);
passes.push({passName: 'defaultPass', pauseAfterLoadMs: LONG_WAIT_FOR_SW_DELAY});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What other implications are there here? I'd expect local scores to be artificially inflated because in these 5s the app loads completely.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICT, there are no other implications. (The score is 100% anyway as long as the SW is detected in time and the failing tests when it is not detected are boolean (does it have a SW? does it work offline? etc).)

Other categories (e.g. performance) don't seem to be inflated due to this setting (e.g. performance is still ~45% on localhost). This makes sense, because pauseAfterLoadMs does not affect performance-specific metrics (such as first-meaningful-paint).

}