Skip to content

Commit

Permalink
DISCO-100 Added lighthouse automated test and prerenderspec (#2188)
Browse files Browse the repository at this point in the history
* Added lighthouse prerenderspec

* Added missing lighthouse target percentages

* Fixed prerenderspec lint

* Allow lighthouse prerenderspec to run against absolute urls and targets set from the env

* Added script to run lighthouse on top pages

* Save automated lighthouse reports to disk and compare later

* Download/save lighthouse reports

* Use production bucket and fix lint

* Moved automated lighthouse to Concourse

* Fixed lint

* Use CI image for lighthouse

* Remove input mapping and instead use a different name for the input and output

* Renamed previous report to most recent report and removed unused code

* Use the correct image for deno

* Added .ts extension for import from deno

* Use the entry script instead of deno

* Removed more deno

* Added network timeout for yarn install

* Ensure puppeteer is setup before loading browserutils

* Launch Chrome with --no-sandbox

* Try to set the global browser variable properly

* Attempt to circumvent puppeteer check

* Load url function's PORT from src/config instead of jest-puppeteer config

* Attempt to use process.exit() to make the script exit successfully

* Use correct dir for concourse inputs/outputs

* Remove - from the beginning of filenames

---------

Co-authored-by: staxly[bot] <35789409+staxly[bot]@users.noreply.github.com>
  • Loading branch information
Dantemss and staxly[bot] authored May 10, 2024
1 parent f5d9801 commit 25533ec
Show file tree
Hide file tree
Showing 13 changed files with 160 additions and 21 deletions.
2 changes: 1 addition & 1 deletion concourse/book-scan/script.bash
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ cloudfront_environment=$(< cloudfront-environment/version.txt)

cd rex-web

yarn
yarn install --network-timeout 60000

node script/entry.js domVisitor errorsExist --rootUrl="https://$cloudfront_environment" --queryString="validateLinks"
2 changes: 1 addition & 1 deletion concourse/create-test-plan-configs/task.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@ run:
- |
DEST=$(pwd)/test-plans
cd rex-web/concourse/create-test-plan-configs
yarn install
yarn install --network-timeout 60000
./script.js $DEST
12 changes: 12 additions & 0 deletions concourse/lighthouse/script.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -euxo pipefail

base_dir=$(pwd)

cd rex-web

yarn install --network-timeout 60000

node script/entry.js lighthouse --pages="$LIGHTHOUSE_PAGES" \
--mostRecentReportDir="$base_dir/$LIGHTHOUSE_MOST_RECENT_REPORT_DIR" \
--reportDir="$base_dir/$LIGHTHOUSE_REPORT_DIR"
26 changes: 26 additions & 0 deletions concourse/lighthouse/task.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
platform: linux

image_resource:
type: docker-image
source:
password: ((dockerhub-password))
username: ((dockerhub-username))
repository: openstax/rex-web
tag: CI-2023-08-10

inputs:
- name: rex-web
- name: most-recent-lighthouse-report
outputs:
- name: lighthouse-report

params:
AWS_ACCESS_KEY_ID: ((prod-aws-access-key-id))
AWS_SECRET_ACCESS_KEY: ((prod-aws-secret-access-key))
LIGHTHOUSE_PAGES: ((lighthouse-pages))
LIGHTHOUSE_MOST_RECENT_REPORT_DIR: most-recent-lighthouse-report
LIGHTHOUSE_REPORT_DIR: lighthouse-report

run:
path: rex-web/concourse/lighthouse/script.bash
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
"test:prerender:specs": "REACT_APP_ENV=test SERVER_MODE=built jest --testPathPattern=\"(\\.|/)prerenderspec\\.tsx?\" --config jest-puppeteer.config.json",
"test:prerender:browser": "REACT_APP_ENV=test SERVER_MODE=built jest --testPathPattern=\"(\\.|/)browserspec\\.tsx?\" --config jest-puppeteer.config.json -i",
"test:prerender:screenshots": "REACT_APP_ENV=test SERVER_MODE=built jest --testPathPattern=\"(\\.|/)screenshotspec\\.tsx?\" --config jest-puppeteer.config.json",
"test:lighthouse-manual": "REACT_APP_ENV=test lighthouse --view --config-path=./src/test/audits/index.js",
"test:lighthouse:manual": "REACT_APP_ENV=test lighthouse --view --config-path=./src/test/audits/index.js",
"analyze:bundle": "craco build && source-map-explorer 'build/static/js/*.js' -m",
"analyze:dom": "node ./script/entry.js domVisitor",
"heroku-postbuild": "npm run-script build:clean"
Expand Down
42 changes: 42 additions & 0 deletions script/lighthouse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { readFile, writeFile } from 'fs';
import puppeteer from 'puppeteer';
import asyncPool from 'tiny-async-pool';
import argv from 'yargs';
import { checkLighthouse, ScoreTargets } from '../src/test/lighthouse';

const {
pages, mostRecentReportDir, reportDir,
} = argv.string('pages').string('mostRecentReportDir').string('reportDir').argv;

async function run() {
if (!pages) { throw new Error('You must specify some --pages to test'); }

const browser = await puppeteer.launch({ args: ['--no-sandbox'] });

const pageArray: string[] = JSON.parse(pages);

await asyncPool(1, pageArray, async(pageUrl) => {
const filename = `${pageUrl.replace(/[^a-z0-9]+/gi, '-').replace(/^-+/i, '')}.json`;
const targets = mostRecentReportDir ? await new Promise<ScoreTargets | undefined>(
(resolve) => readFile(
`${mostRecentReportDir}/${filename}`,
{ encoding: 'utf8' },
(err, data) => err ? resolve(undefined) : resolve(JSON.parse(data))
)
) : undefined;

const result = await checkLighthouse(browser, pageUrl, targets);

if (reportDir) {
await new Promise<void>((resolve) => writeFile(
`${reportDir}/${filename}`,
JSON.stringify(result), () => resolve()
));
}
});
}

run().then(() => process.exit(), (err) => {
console.error(err); // tslint:disable-line:no-console
process.exit(1);
});
7 changes: 6 additions & 1 deletion src/app/content/content.browserspec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ describe('content', () => {
});

it('a11y lighthouse check', async() => {
await checkLighthouse(browser, TEST_LONG_PAGE_URL);
await checkLighthouse(browser, TEST_LONG_PAGE_URL, {
accessibility: 1,
'best-practices': 0.88,
customAccessibility: 1,
seo: 1,
});
});

it(`when clicking a toc link:
Expand Down
7 changes: 6 additions & 1 deletion src/index.browserspec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,10 @@ describe('Browser sanity tests', () => {
});

it('a11y lighthouse check', async() => {
await checkLighthouse(browser, TEST_PAGE_URL);
await checkLighthouse(browser, TEST_PAGE_URL, {
accessibility: 1,
'best-practices': 0.79,
customAccessibility: 1,
seo: 0.69,
});
});
1 change: 0 additions & 1 deletion src/index.prerenderspec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import fetch from 'node-fetch';
import { url } from './test/browserutils';

describe('Prerender sanity tests', () => {

it('has a release manifest', async() => {
const release = await fetch(url('rex/release.json'))
.then((response) => response.text());
Expand Down
23 changes: 23 additions & 0 deletions src/lighthouse.prerenderspec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/** @jest-environment puppeteer */
import asyncPool from 'tiny-async-pool';
import { checkLighthouse } from './test/browserutils';

const TEST_PAGE_WITHOUT_MATH = '/books/book-slug-1/pages/2-test-page-3';
const TEST_PAGE_WITH_LINKS_NAME = '1-introduction-to-science-and-the-realm-of-physics-physical-quantities-and-units';
const TEST_PAGE_WITH_LINKS = '/books/book-slug-1/pages/' + TEST_PAGE_WITH_LINKS_NAME;
const TEST_PAGE_WITH_FIGURE = '/books/book-slug-1/pages/test-page-for-generic-styles';
const LIGHTHOUSE_PAGES = [ TEST_PAGE_WITHOUT_MATH, TEST_PAGE_WITH_LINKS, TEST_PAGE_WITH_FIGURE ];
const LIGHTHOUSE_TARGETS = {
accessibility: 0.97,
'best-practices': 0.88,
customAccessibility: 1,
seo: 0.9,
};

describe('lighthouse', () => {
it('matches or exceeds target scores', async() => {
await asyncPool(1, LIGHTHOUSE_PAGES, async(pageUrl) => {
await checkLighthouse(browser, pageUrl, LIGHTHOUSE_TARGETS);
});
});
});
20 changes: 5 additions & 15 deletions src/test/browserutils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import lighthouse from 'lighthouse';
import puppeteer from 'puppeteer';
import * as lighthouseConfig from './audits';
import url from './url';
import { checkLighthouse, ScoreTargets } from './lighthouse';

export { checkLighthouse, url };
export type { ScoreTargets };

// jest-puppeteer will expose the `page` and `browser` globals to Jest tests.
declare global {
Expand Down Expand Up @@ -42,8 +45,6 @@ export const setTallerDesktopViewport = (target: puppeteer.Page) =>
export const setDesktopViewport = (target: puppeteer.Page) => target.setViewport({height: 874, width: desktopWidth});
export const setMobileViewport = (target: puppeteer.Page) => target.setViewport({height: 731, width: 411});

export const url = (path: string) => `http://localhost:${puppeteerConfig.server.port}/${path.replace(/^\/+/, '')}`;

const calmHooks = (target: puppeteer.Page) => target.evaluate(() => {
if (window && window.__APP_ASYNC_HOOKS) {
return window.__APP_ASYNC_HOOKS.calm();
Expand Down Expand Up @@ -114,14 +115,3 @@ export const h1Content = (target: puppeteer.Page) => target.evaluate(() => {
const h1 = document && document.querySelector('h1');
return h1 && h1.textContent;
});

export const checkLighthouse = async(target: puppeteer.Browser, urlPath: string) => {

const port = Number((new URL(target.wsEndpoint())).port);
const { lhr } = await lighthouse(url(urlPath), {port}, lighthouseConfig);

expect(lhr.categories.customAccessibility.score).toBeGreaterThanOrEqual(1);
expect(lhr.categories.accessibility.score).toBeGreaterThanOrEqual(1);
expect(lhr.categories.seo.score).toBeGreaterThanOrEqual(0.69);
expect(lhr.categories['best-practices'].score).toBeGreaterThanOrEqual(0.79);
};
32 changes: 32 additions & 0 deletions src/test/lighthouse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import lighthouse from 'lighthouse';
import { Browser } from 'puppeteer';
import * as lighthouseConfig from './audits';
import url from './url';

type Categories = Awaited<ReturnType<typeof lighthouse>>['lhr']['categories'];
export type ScoreTargets = { [key in keyof Categories]?: number };

const testedCategories: Array<keyof Categories> = [
'accessibility', 'best-practices', 'customAccessibility', 'pwa', 'seo',
];

export const checkLighthouse = async(target: Browser, urlPath: string, scoreTargets?: ScoreTargets) => {
const absoluteUrl = urlPath.startsWith('https://') || urlPath.startsWith('http://') ? urlPath : url(urlPath);
const port = Number((new URL(target.wsEndpoint())).port);
const { lhr } = await lighthouse(absoluteUrl, {port}, lighthouseConfig);

const result: ScoreTargets = {};
testedCategories.forEach((category) => {
const { score } = lhr.categories[category];
if (scoreTargets) {
const minScore = scoreTargets[category];

if (minScore && score < minScore) {
throw new Error(`${category} score of ${score} was less than the minimum of ${minScore}`);
}
}
result[category] = score;
});

return result;
};
5 changes: 5 additions & 0 deletions src/test/url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { PORT } from '../config';

const url = (path: string) => `http://localhost:${PORT}/${path.replace(/^\/+/, '')}`;

export default url;

0 comments on commit 25533ec

Please sign in to comment.