Skip to content
This repository has been archived by the owner on Feb 8, 2023. It is now read-only.

Commit

Permalink
Check site performance using Lighthouse (#884)
Browse files Browse the repository at this point in the history
* Add lighthouse runner

* Run lighthouse as a Github action

* Install node modules for lighthouse runner in CI

* Fix invalid Circle CI config

* Try to fix invalid Circle CI config again

* Remove excess backslashes from Circle CI config

* Ignore lighthouse runner in CI

* Workaround github actions changing docker workdir

* Use workspaces in CI to store node_modules

This should hopefully be faster than checking out from cache all the time

* Fix error in lighthouse runner

* Update lighthouse action to leave a PR comment

* Make lighthouse error fail the github action

* Make it easier to run lighthouse action locally

* Add console log in getPullRequestIdsForRef

* Change lighthouse action to accept commit refs

Previously it expected branch refs and errorred on commit refs, but
github provides commit refs

* Remove console log in lighthouse action
  • Loading branch information
Sam Rowe authored Feb 17, 2020
1 parent 88ff626 commit e71c952
Show file tree
Hide file tree
Showing 10 changed files with 1,778 additions and 18 deletions.
42 changes: 24 additions & 18 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ jobs:

steps:
- checkout

- restore_cache:
keys:
- node_modules-v7-{{ checksum "yarn.lock" }}
Expand All @@ -19,14 +20,19 @@ jobs:
paths:
- ./node_modules

- persist_to_workspace:
root: .
paths:
- node_modules/

validate_formatting:
docker:
- image: redbadger/website-honestly-deploy:16
steps:
- checkout
- restore_cache:
keys:
- node_modules-v7-{{ checksum "yarn.lock" }}

- attach_workspace:
at: .

- run:
name: Validate formatting
Expand All @@ -37,9 +43,9 @@ jobs:
- image: redbadger/website-honestly-deploy:16
steps:
- checkout
- restore_cache:
keys:
- node_modules-v7-{{ checksum "yarn.lock" }}

- attach_workspace:
at: .

- run:
name: Lint code
Expand All @@ -50,9 +56,9 @@ jobs:
- image: redbadger/website-honestly-deploy:16
steps:
- checkout
- restore_cache:
keys:
- node_modules-v7-{{ checksum "yarn.lock" }}

- attach_workspace:
at: .

- run:
name: Validate types
Expand All @@ -63,9 +69,9 @@ jobs:
- image: redbadger/website-honestly-deploy:16
steps:
- checkout
- restore_cache:
keys:
- node_modules-v7-{{ checksum "yarn.lock" }}

- attach_workspace:
at: .

- run:
name: Notify Code Climate of build
Expand All @@ -90,9 +96,9 @@ jobs:
- image: redbadger/website-honestly-deploy:16
steps:
- checkout
- restore_cache:
keys:
- node_modules-v7-{{ checksum "yarn.lock" }}

- attach_workspace:
at: .

- run:
name: Deploy branch
Expand All @@ -103,9 +109,9 @@ jobs:
- image: redbadger/website-honestly-deploy:16
steps:
- checkout
- restore_cache:
keys:
- node_modules-v7-{{ checksum "yarn.lock" }}

- attach_workspace:
at: .

- run:
name: Deploy to staging
Expand Down
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ coverage
dist
flow-typed/npm
website-next
**/node_modules/**
lighthouse/
10 changes: 10 additions & 0 deletions .github/main.workflow
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
workflow "Check website performance" {
on = "deployment"
resolves = ["Run Lighthouse"]
}

action "Run Lighthouse" {
uses = "./lighthouse"

secrets = ["GITHUB_TOKEN"]
}
2 changes: 2 additions & 0 deletions lighthouse/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
.node-version
5 changes: 5 additions & 0 deletions lighthouse/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"settings": {
"import/resolver": "node"
}
}
1 change: 1 addition & 0 deletions lighthouse/.node-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v12.0.0
47 changes: 47 additions & 0 deletions lighthouse/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
FROM node:12.2.0-stretch-slim


LABEL "com.github.actions.name"="Lighthouse"
LABEL "com.github.actions.description"="Reports on the performance of the website"
LABEL "com.github.actions.icon"="activity"
LABEL "com.github.actions.color"="red"


# Install Google Chrome & dumb-init (https://github.com/Yelp/dumb-init)
RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
&& apt-get update \
&& apt-get -y upgrade \
&& apt-get install -y google-chrome-stable dumb-init --no-install-recommends \
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf /src/*.deb


# Install dependencies
ENV NODE_ENV=production
COPY package.json yarn.lock /
RUN yarn install --frozen-lockfile --production


# Add a chrome user and setup home dir
# RUN groupadd --system chrome && \
# useradd --system --create-home --gid chrome --groups audio,video chrome && \
# mkdir --parents /home/chrome/reports && \
# chown --recursive chrome:chrome /home/chrome

# USER chrome


COPY runner.js /

#VOLUME /home/chrome/reports
#WORKDIR /home/chrome/reports

# Disable Lighthouse error reporting to prevent prompt.
# ENV CI=true


EXPOSE 8080


ENTRYPOINT ["dumb-init", "--", "node", "/runner.js"]
12 changes: 12 additions & 0 deletions lighthouse/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "lighthouse-runner",
"version": "1.0.0",
"main": "index.js",
"license": "UNLICENSED",
"private": true,
"dependencies": {
"chrome-launcher": "^0.10.7",
"lighthouse": "^5.0.0",
"node-fetch": "^2.6.0"
}
}
165 changes: 165 additions & 0 deletions lighthouse/runner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
const lighthouse = require('lighthouse');
const chromeLauncher = require('chrome-launcher');
const fs = require('fs');
const fetch = require('node-fetch');

const repoFullName = process.env.GITHUB_REPOSITORY;
const githubEventPath = process.env.GITHUB_EVENT_PATH;
const githubAuthToken = process.env.GITHUB_TOKEN;

/* eslint-disable no-use-before-define */

function runLighthouse({ targetUrl }) {
const chromeFlags = [
'--headless',
'--no-sandbox', // chrome sandboxing requires docker container to have the
// `SYS_ADMIN` capability added which is not supported by GitHub actions
];

return chromeLauncher.launch({ chromeFlags }).then(chrome => {
const opts = {
port: chrome.port,
output: 'html',
};

const config = null;

return lighthouse(targetUrl, opts, config).then(results => {
// use results.lhr for the JS-consumable output
// https://github.com/GoogleChrome/lighthouse/blob/master/types/lhr.d.ts
// use results.report for the HTML/JSON/CSV output as a string
// use results.artifacts for the trace/screenshots/other specific case you need (rarer)
return chrome.kill().then(() => results);
});
});
}

function readArgumentsFromEnvironment() {
if (!githubEventPath) {
// eslint-disable-next-line no-console
console.warn(`GITHUB_EVENT_PATH environment variable is not set, running locally.`);
return {
targetUrl: 'https://www-staging.red-badger.com/658e7ac/',
ref: '7865779',
repo: {
owner: 'redbadger',
name: 'website-honestly',
},
};
}

const { deployment } = JSON.parse(fs.readFileSync(githubEventPath));

const [owner, name] = repoFullName.split('/');

return {
targetUrl: deployment.url,
ref: deployment.ref,
repo: {
owner,
name,
},
};
}

function run() {
const { targetUrl, repo, ref } = readArgumentsFromEnvironment();

runLighthouse({ targetUrl }).then(results => reportResults({ results, repo, ref }));
}

async function reportResults({ results, repo, ref }) {
const scores = Object.entries(results.lhr.categories)
.map(([categoryName, { score }]) => `| ${categoryName} | ${score * 100} |`)
.join('\n');

const body = `
⚡️ Lighthouse scores for latest changes:
| Category | Score |
| -------- | ----- |
${scores}
`;

const pullRequestIds = await getPullRequestIdsForRef({ repo, ref });

await Promise.all(pullRequestIds.map(issueId => createComment({ body, issueId })));
}

async function createComment({ body, issueId }) {
const query = `
mutation AddLighthouseComment($IssueId: ID!, $Body: String!) {
addComment(input: {subjectId: $IssueId, body: $Body}) {
clientMutationId
}
}
`;

const variables = {
IssueId: issueId,
Body: body,
};

await gitHubGraphqlRequest({ query, variables });
}

async function getPullRequestIdsForRef({ repo: { owner, name }, ref }) {
const query = `
query PullRequestIdsForRef($RepoOwner: String!, $RepoName: String!, $Ref: String!) {
repository(owner: $RepoOwner, name: $RepoName) {
object(expression: $Ref) {
...on Commit {
associatedPullRequests(first: 10) {
nodes {
id
}
}
}
}
}
}
`;

const variables = {
RepoOwner: owner,
RepoName: name,
Ref: ref,
};

const {
repository: {
object: { associatedPullRequests },
},
} = await gitHubGraphqlRequest({ query, variables });

if (!associatedPullRequests) return [];

return associatedPullRequests.nodes.map(({ id }) => id);
}

async function gitHubGraphqlRequest({ query, variables }) {
const response = await fetch(`https://api.github.com/graphql`, {
method: 'POST',
headers: {
Authorization: `bearer ${githubAuthToken}`,
},
body: JSON.stringify({ query, variables }),
});

if (!response.ok) {
throw new Error(`Bad response from GitHub api: ${response.statusText}`);
}

const { data } = await response.json();

return data;
}

run();

process.on('unhandledRejection', error => {
// eslint-disable-next-line no-console
console.error(error);
process.exit(1);
});
Loading

0 comments on commit e71c952

Please sign in to comment.