Prog blog

How to audit your website using free tools

How to audit your website using free tools

While creating this blog, I thought it would be good to have a very high result (at least 90/100) in PageSpeed Insights. PageSpeed Insight is based on the current version of Lighthouse, an open source web audit tool from Google. In the latest versions of Google Chrome we can see in the chrome dev tools a tab called Audits which is a graphical overlay on Lighthouse. In addition to performance testing, we also have options to check the results for categories such as Accessibility, Best practices, SEO and Progressive web apps.

Audits in Chrome Dev Tools

Audits in Chrome Dev Tools after generate report

Maybe checking one page is trivially simple, but with more pages it becomes a problematic and time consuming task. Luckily, Lighthouse provides a programmable API that allows us to automate the whole process by writing a few lines of code. Below I'll give you an example of how I use this possibility on my blog to list pages with problems after updating on my blog domain.

At the beginning we create a simple typescript configuration for our programs.

bin/tsconfig.json

{
  "compilerOptions": {
    "target": "es5",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
    "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
    "strict": true,                           /* Enable all strict type-checking options. */
    "esModuleInterop": true,                  /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    "forceConsistentCasingInFileNames": true  /* Disallow inconsistently-cased references to the same file. */
  }
}

The first function will check the score for the page audit to see if some score is below 90 points out of 100 possible.

const inBadCondition = <T extends { results: { categories: { [key:string]: { score: number }}} }>(urlAudit: T) =>
  Object.keys(urlAudit.results.categories).some(
    key => urlAudit.results.categories[key].score < 0.90
  );

It will run the lighthouse for each address and return the result of its audit.

const auditUrls = async (urls: string[], opts: any, config = null) => {
  const urlResults: UrlAudit[] = [];
  for (const url of urls) {
    const results = (await lighthouse(url, opts, config)).lhr;
    urlResults.push({
      url,
      results,
    });
  }
  return urlResults;
};

You'll also need a function that runs chrome, tests all addresses and returns only those in bad condition.

const launchChromeAndRunLighthouse = async (urls: string[], opts: any, config = null): Promise<UrlAudit[]> => {
  const chrome = await chromeLauncher.launch({ chromeFlags: opts.chromeFlags });
  opts.port = chrome.port;
  const urlAudits = await auditUrls(urls, opts, config)
  const weakUrls = urlAudits.filter(inBadCondition);
  await chrome.kill();
  return weakUrls;
}

This makes launching our program trivially easy. For my needs I'm downloading from ROUTES table all subpages of my blog and domain address from environments. Additionally in options will run the flag --show-paint-rects for a visual look at the site behavior during the audit. When finished, the program will return in stdout any url that is in bad condition and at the end will write Done. to make sure that the program has finished.

const opts = {
  chromeFlags: ['--show-paint-rects'],
};

const getUrls = () => ROUTES.map(route => `${environment.baseUrl}${route}`);

launchChromeAndRunLighthouse(getUrls(), opts).then(weakUrlsList => {
  weakUrlsList.forEach((badUrl: UrlAudit) => {
    console.log(badUrl.url);
    console.log(badUrl.results.categories);
  });
  console.log('Done.');
});

The program is launched with the command.

ts-node --project bin/tsconfig.json bin/lighthouse

Complete code.

bin/lighthouse.ts

import * as chromeLauncher from 'chrome-launcher';

import { ROUTES } from '../routes.static';
import { environment } from '../src/environments/environment.prod';

const lighthouse = require('lighthouse');

interface UrlAudit {
  url: string;
  results: any;
}

const inBadCondition = <T extends { results: { categories: { [key:string]: { score: number }}} }>(urlAudit: T) =>
  Object.keys(urlAudit.results.categories).some(
    key => urlAudit.results.categories[key].score < 0.90
  );

const auditUrls = async (urls: string[], opts: any, config = null) => {
  const urlResults: UrlAudit[] = [];
  for (const url of urls) {
    const results = (await lighthouse(url, opts, config)).lhr;
    urlResults.push({
      url,
      results,
    });
  }
  return urlResults;
};

const launchChromeAndRunLighthouse = async (urls: string[], opts: any, config = null): Promise<UrlAudit[]> => {
  const chrome = await chromeLauncher.launch({ chromeFlags: opts.chromeFlags });
  opts.port = chrome.port;
  const urlAudits = await auditUrls(urls, opts, config)
  const weakUrls = urlAudits.filter(inBadCondition);
  await chrome.kill();
  return weakUrls;
}

const opts = {
  chromeFlags: ['--show-paint-rects'],
};

const getUrls = () => ROUTES.map(route => `${environment.baseUrl}${route}`);

launchChromeAndRunLighthouse(getUrls(), opts).then(weakUrlsList => {
  weakUrlsList.forEach((badUrl: UrlAudit) => {
    console.log(badUrl.url);
    console.log(badUrl.results.categories);
  });
  console.log('Done.');
});

Example result for the audit https://developers.google.com/web/tools/lighthouse

npm run lighthouse

> ng-blog@0.0.0 lighthouse /Users/pawel/workspace/ng-blog
> ts-node --project bin/tsconfig.json bin/lighthouse

https://developers.google.com/web/tools/lighthouse
{
  performance: {
    title: 'Performance',
    auditRefs: [
      [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object],
      [Object]
    ],
    id: 'performance',
    score: 0.36
  },
  accessibility: {
    title: 'Accessibility',
    description: 'These checks highlight opportunities to [improve the accessibility of your web app](https://developers.google.com/web/fundamentals/accessibility). Only a subset of accessibility issues can be automatically detected so manual testing is also encouraged.',
    manualDescription: 'These items address areas which an automated testing tool cannot cover. Learn more in our guide on [conducting an accessibility review](https://developers.google.com/web/fundamentals/accessibility/how-to-review).',
    auditRefs: [
      [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object],
      [Object], [Object], [Object], [Object],
      [Object], [Object]
    ],
    id: 'accessibility',
    score: 0.98
  },
  'best-practices': {
    title: 'Best Practices',
    auditRefs: [
      [Object], [Object],
      [Object], [Object],
      [Object], [Object],
      [Object], [Object],
      [Object], [Object],
      [Object], [Object],
      [Object], [Object],
      [Object]
    ],
    id: 'best-practices',
    score: 0.86
  },
  seo: {
    title: 'SEO',
    description: 'These checks ensure that your page is optimized for search engine results ranking. There are additional factors Lighthouse does not check that may affect your search ranking. [Learn more](https://support.google.com/webmasters/answer/35769).',
    manualDescription: 'Run these additional validators on your site to check additional SEO best practices.',
    auditRefs: [
      [Object], [Object],
      [Object], [Object],
      [Object], [Object],
      [Object], [Object],
      [Object], [Object],
      [Object], [Object],
      [Object], [Object]
    ],
    id: 'seo',
    score: 0.96
  },
  pwa: {
    title: 'Progressive Web App',
    description: 'These checks validate the aspects of a Progressive Web App. [Learn more](https://developers.google.com/web/progressive-web-apps/checklist).',
    manualDescription: "These checks are required by the baseline [PWA Checklist](https://developers.google.com/web/progressive-web-apps/checklist) but are not automatically checked by Lighthouse. They do not affect your score but it's important that you verify them manually.",
    auditRefs: [
      [Object], [Object],
      [Object], [Object],
      [Object], [Object],
      [Object], [Object],
      [Object], [Object],
      [Object], [Object],
      [Object], [Object],
      [Object], [Object]
    ],
    id: 'pwa',
    score: 0.74
  }
}
Done.

Thanks to such a simple program, we are able to test countless numbers of pages for free.