diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml index 36221e9c909..98e4f6a9396 100644 --- a/.github/workflows/lighthouse.yml +++ b/.github/workflows/lighthouse.yml @@ -1,41 +1,25 @@ name: DCR Run Lighthouse CI on: - push: - branches: - - main - paths-ignore: - - "apps-rendering/**" - pull_request: - # If/when we compare results to `main`, we should also run on 'reopened' - types: [opened, synchronize] - + push: + paths-ignore: + - "apps-rendering/**" jobs: - lhci: - name: DCR Lighthouse - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 - - name: Install Node - uses: guardian/actions-setup-node@main - # Make sure we install dependencies in the root directory - - uses: bahmutov/npm-install@v1 - - run: make build - working-directory: dotcom-rendering - - name: Install and run Lighthouse CI - working-directory: dotcom-rendering - env: - LHCI_GITHUB_TOKEN: ${{ secrets.LHCI_GITHUB_TOKEN }} - run: | - npm install -g puppeteer-core@2.1.0 @lhci/cli@0.8.2 - lhci autorun - - - name: Setup deno - uses: denolib/setup-deno@v2 - with: - deno-version: v1.21.0 - - - name: Surface Lighthouse Results - run: deno run --no-check --allow-net=api.github.com --allow-env="GITHUB_TOKEN","GITHUB_EVENT_PATH" --allow-read scripts/deno/surface-lighthouse-results.ts - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + lhci: + name: DCR Lighthouse + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Install Node + uses: guardian/actions-setup-node@main + # Make sure we install dependencies in the root directory + - uses: bahmutov/npm-install@v1 + - run: make build + working-directory: dotcom-rendering + - name: Install and run Lighthouse CI + working-directory: dotcom-rendering + env: + LHCI_GITHUB_TOKEN: ${{ secrets.LHCI_GITHUB_TOKEN }} + run: | + npm install -g puppeteer-core@2.1.0 @lhci/cli@0.8.2 + lhci autorun diff --git a/.gitignore b/.gitignore index 148043eb299..fee1fdb74da 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,3 @@ coverage # Architecture Diagram webArchitecture.svg - -# lighthouse results from local build -.lighthouseci/ \ No newline at end of file diff --git a/dotcom-rendering/lighthouserc.js b/dotcom-rendering/lighthouserc.js index 2fabc452261..ef0b3ff17ae 100644 --- a/dotcom-rendering/lighthouserc.js +++ b/dotcom-rendering/lighthouserc.js @@ -10,14 +10,13 @@ module.exports = { puppeteerScript: './scripts/lighthouse/puppeteer-script.js', settings: { onlyCategories: "accessibility,best-practices,performance,seo", - disableStorageReset: true, + disableStorageReset: true } }, upload: { target: 'temporary-public-storage', }, assert: { - includePassedAssertions: true, assertions: { "first-contentful-paint": ["warn", {"maxNumericValue": 1500}], "largest-contentful-paint": ["warn", {"maxNumericValue": 3000}], diff --git a/scripts/deno/surface-lighthouse-results.ts b/scripts/deno/surface-lighthouse-results.ts deleted file mode 100644 index ebfdc609247..00000000000 --- a/scripts/deno/surface-lighthouse-results.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { Octokit } from "https://cdn.skypack.dev/octokit"; -import type { RestEndpointMethodTypes } from "https://cdn.skypack.dev/@octokit/plugin-rest-endpoint-methods?dts"; -import type { EventPayloadMap } from "https://cdn.skypack.dev/@octokit/webhooks-types?dts"; -import "https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/v0.8.2/types/assert.d.ts"; - -/* -- Setup -- */ - -type OctokitWithRest = { - rest: { - issues: { - [Method in keyof RestEndpointMethodTypes["issues"]]: ( - arg: RestEndpointMethodTypes["issues"][Method]["parameters"] - ) => Promise; - }; - }; -}; - -/** Github token for Authentication */ -const token = Deno.env.get("GITHUB_TOKEN"); -if (!token) throw new Error("Missing GITHUB_TOKEN"); - -/** Path for workflow event */ -const path = Deno.env.get("GITHUB_EVENT_PATH"); -if (!path) throw new Error("Missing GITHUB_EVENT_PATH"); - -/** - * https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads - */ -const payload: EventPayloadMap["push" | "pull_request"] = JSON.parse( - Deno.readTextFileSync(path) -); - -const isPullRequestEvent = ( - payload: EventPayloadMap[keyof EventPayloadMap] -): payload is EventPayloadMap["pull_request"] => - //@ts-expect-error -- We’re actually checking the type - typeof payload?.pull_request?.number === "number"; - -/** - * One of two values depending on the workflow trigger event - * - * - on a "pull_request" event: the current Issue / PR number - * on which to add or update the Lighthouse report - * - * - on a "push" event: the original issue to track the latest report: - * https://github.com/guardian/dotcom-rendering/issues/4584, which allows - * to track the state Lighthouse CI Reports on `main` over time. - */ -const issue_number = isPullRequestEvent(payload) - ? // If PullRequestEvent - payload.pull_request.number - : // If PushEvent - 4584; - -console.log(`Using issue #${issue_number}`); - -/** The Lighthouse results directory */ -const dir = "dotcom-rendering/.lighthouseci"; - -const links: Record = JSON.parse( - Deno.readTextFileSync(`${dir}/links.json`) -); - -/** https://github.com/GoogleChrome/lighthouse-ci/blob/5963dcce0e88b8d3aedaba56a93ec4b93cf073a1/packages/utils/src/assertions.js#L15-L30 */ -interface AssertionResult { - url: string; - name: - | keyof Omit - | "auditRan"; - operator: string; - expected: number; - actual: number; - values: number[]; - passed: boolean; - level?: LHCI.AssertCommand.AssertionFailureLevel; - auditId?: string; - auditProperty?: string; - auditTitle?: string; - auditDocumentationLink?: string; - message?: string; -} -const results: AssertionResult[] = JSON.parse( - Deno.readTextFileSync(`${dir}/assertion-results.json`) -); - -/* -- Definitions -- */ - -/** The string to search for when looking for a comment */ -const REPORT_TITLE = "⚡️ Lighthouse report"; -const GIHUB_PARAMS = { - owner: "guardian", - repo: "dotcom-rendering", - issue_number, -} as const; - -// @ts-expect-error -- Octokit’s own types are not as good as ours -const octokit = new Octokit({ auth: token }) as OctokitWithRest; - -/* -- Methods -- */ - -/** If large number, round to 0 decimal, if small, round to 6 decimal points */ -const formatNumber = (expected: number, actual: number): string => - expected > 100 ? Math.ceil(actual).toString() : actual.toFixed(6); - -const getStatus = ( - passed: boolean, - level: AssertionResult["level"] -): "✅" | "⚠️" | "❌" => { - if (passed) return "✅"; - - switch (level) { - case "off": - case "warn": - return "⚠️"; - case "error": - default: - return "❌"; - } -}; - -const generateAuditTable = ( - auditUrl: string, - results: AssertionResult[] -): string => { - const reportUrl = links[auditUrl]; - - const resultsTemplateString = results.map( - ({ auditTitle, auditProperty, passed, expected, actual, level }) => - `| ${auditTitle ?? auditProperty ?? "Unknown Test"} | ${getStatus( - passed, - level - )} | ${expected} | ${formatNumber(expected, actual)} |` - ); - - const [endpoint, testUrlClean] = auditUrl.split("?url="); - - const table = [ - `### [Report for ${endpoint.split("/").slice(-1)}](${reportUrl})`, - `> tested url \`${testUrlClean}\``, - "", - "| Category | Status | Expected | Actual |", - "| --- | --- | --- | --- |", - ...resultsTemplateString, - "", - ].join("\n"); - - return table; -}; - -const createLighthouseResultsMd = (): string => { - const auditCount = results.length; - const failedAuditCount = results.filter((result) => !result.passed).length; - const auditUrls = [...new Set(results.map((result) => result.url))]; - - return [ - `## ${REPORT_TITLE} for the changes in this PR`, - `Lighthouse tested ${auditUrls.length} URLs `, - failedAuditCount > 0 - ? `⚠️ Budget exceeded for ${failedAuditCount} of ${auditCount} audits.` - : "All audits passed", - ...auditUrls.map((url) => - generateAuditTable( - url, - results.filter((result) => result.url === url) - ) - ), - ].join("\n\n"); -}; - -const getCommentID = async (): Promise => { - const { data: comments } = await octokit.rest.issues.listComments({ - ...GIHUB_PARAMS, - }); - - const comment = comments.find((comment) => - comment.body?.includes(REPORT_TITLE) - ); - - return comment?.id ?? null; -}; - -try { - const body = createLighthouseResultsMd(); - const comment_id = await getCommentID(); - - const { data } = comment_id - ? await octokit.rest.issues.updateComment({ - ...GIHUB_PARAMS, - comment_id, - body, - }) - : await octokit.rest.issues.createComment({ - ...GIHUB_PARAMS, - body, - }); - - console.log( - `Successfully ${ - comment_id ? "updated" : "created" - } Lighthouse report comment` - ); - console.log("See:", data.html_url); -} catch (error) { - if (error instanceof Error) throw error; - - console.error("there was an error:", error.message); - Deno.exit(1); -}