Automated website performance testing with GitHub Actions, k6 and Cloudflare Workers

Automated website performance testing with GitHub Actions, k6 and Cloudflare Workers

Speed is a key feature of all the best developer tools so the Console website needs to be fast. How we use k6 inside GitHub Actions to run performance tests on every commit.

Speed is a key feature of all the best developer tools, so the Console website needs to be fast and our CI/CD pipeline includes performance tests for every push.

Some pages are simple, but our developer tool reviews and list of all the current developer beta programs have a lot of entries. Despite this, both of these pages take no more than 1.5 seconds to load, and both score 100/100 on Google PageSpeed Insights.

This post is about how we automate our website performance testing.

100/100 in Google Pagespeed for Beta Console

Static sites are fast

We achieve most of our high performance by having a static HTML website. There is no backend, no database and no dynamic loading data from APIs. The site is built once and static HTML deployed.

We use Hugo as our static site framework. The site is split into the main theme, templates for specific pages, JSON for the Tools, Betas and Newsletter pages, and then markdown for the rest of the content. At build-time, we have a script which takes the content from our Google Sheets backend and reads it into the Hugo templates. This content doesn't change often, so it doesn't need a complex database backend.

This approach means the HTML itself is very small. For Tools it is only 56KB and Betas is 36KB. There are other assets like fonts, icons, images and CSS, but the browser can render the main content very quickly whilst the assets download in parallel. The largest components are the tool favicons we display for each vendor.

100ms to request, download and render the developer tools reviews page.

Distributed on Cloudflare

Cloudflare has one of the largest networks and we take advantage of that to put the site assets as close to our visitors as possible through the Cloudflare Workers Sites product. Once Hugo has built the HTML, we upload it to Cloudflare and it is deployed around the world.

Since the origin is within Cloudflare's network, it is able to serve the site from cache for most requests. We invalidate the cache only when a new deployment is uploaded, but even then it is simply loading the assets from Cloudflare's KV store. We do not run our own servers so there is no external origin.

Cloudflare cache status for console.dev
Cloudflare Workers requests for console.dev

Performance testing using k6

Every commit that is pushed to our GitHub repo triggers a GitHub Action to run performance tests. This builds the Hugo site and then deploys it to a special test domain that runs as a separate Cloudflare Worker.

We define several test plans which execute using the open source load testing tool, k6. The test plans are written in JS and issue a group of requests against specific pages to test whether they load within our performance SLAs, that there are no errors, and the page content is what we expect.

For example, the tools.js test will fail if p95 response time is greater than 1000ms and if more than 1% of requests trigger an error. The tests are run through the k6-action on GitHub Actions.

export const options = {
    duration: "5s",
    vus: 5,
    thresholds: {
        http_req_duration: ["p(95)<1000"],
        errors: ["rate<0.01"], // <1% errors
    },
};

Since the test site is not always in use, the results represent a worst case with no cached content from a cold start. Even so, our performance SLAs are strict. The homepage must load in less than 500ms, the tools page in 1000ms and betas in less than 1500ms. These are different because of the type of content and length for each page, but were chosen based on testing the page in multiple scenarios.

Test/staging Cloudflare Worker.

Securing tests using Cloudflare Access

The test site is secured by Cloudflare Access so that only authorized users are able to log in and view it. We also use this site as a staging environment to manually deploy experiments and draft pages that we want to test outside of our local development environment. Placing it behind an authenticated login gives us a safe area to play around and break things before it goes live. It also helps ensure we avoid Google thinking it is duplicate content.

Cloudflare Access supports service tokens so that automated systems can authenticate. We store the token as a secret on GitHub Actions and then the appropriate headers are sent by k6 when it issues the requests.

export default function () {
    // Use service tokens to access the test URL behind Cloudflare
    // https://developers.cloudflare.com/cloudflare-one/identity/service-auth/service-tokens
    const res = http.get("https://test.console.dev/betas/", {
        headers: {
            "CF-Access-Client-Id": `${__ENV.CF_CLIENT_ID}`,
            "CF-Access-Client-Secret": `${__ENV.CF_CLIENT_SECRET}`
        }
    });

No regressions

We're always working on improving the site and it deploys automatically from the main branch every day. Including performance testing as part of our build pipeline on every commit allows us to ensure that we don't accidentally introduce regressions. Too many websites take too long to load. Console is not one of them.

Discover the best tools for developers

Console Newsletter - A free weekly email digest of the best tools and beta releases for developers. Every Thursday. See the latest email.