k6, the open-source load testing tool, can be integrated into GitHub Actions to automate performance testing directly within your CI/CD pipeline.

Here’s a look at k6 running a basic load test against a local Nginx server within a GitHub Actions workflow:

name: k6 Performance Test

on: [push]

jobs:
  k6-test:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v3

    - name: Set up k6
      uses: k6io/action@v1.1.0
      with:
        # Path to the k6 test script
        script: tests/my_test.js
        # Optional: Override the default GitHub token

        # token: ${{ secrets.MY_K6_TOKEN }}

        # Optional: Report thresholds to GitHub Checks API
        # checks: true

    - name: Run k6 test
      run: k6 run tests/my_test.js --out json=report.json

    - name: Upload test results
      uses: actions/upload-artifact@v3
      with:
        name: k6-test-results
        path: report.json

This workflow checks out your code, sets up k6, runs a script located at tests/my_test.js, and then uploads the generated report.json as an artifact.

The core problem k6 aims to solve in a CI/CD context is ensuring that code changes don’t introduce performance regressions. Traditionally, performance testing was a separate, often manual, phase. Integrating k6 into GitHub Actions allows for continuous performance validation, catching issues earlier when they are cheaper and easier to fix.

Internally, k6 executes JavaScript test scripts that define the load pattern (Virtual Users, duration, ramp-up) and the checks or assertions to be made against the target system. These scripts are executed by the k6 engine, which simulates HTTP requests and collects metrics like response time, error rate, and throughput. The results can then be outputted in various formats, including JSON, which is useful for programmatic analysis or reporting.

The primary levers you control are within your k6 test script (tests/my_test.js in the example). This JavaScript file dictates the user behavior you want to simulate. You define the target URL, the HTTP methods and payloads, and crucially, the performance thresholds you expect.

Here’s a snippet of what tests/my_test.js might look like:

import http from 'k6/http';
import { sleep } from 'k6';
import { check } from 'k6';

export const options = {
  stages: [
    { duration: '30s', target: 20 }, // Ramp up to 20 users over 30 seconds
    { duration: '1m', target: 20 },  // Stay at 20 users for 1 minute
    { duration: '10s', target: 0 },  // Ramp down to 0 users over 10 seconds
  ],
  thresholds: {
    'http_req_failed': 'rate<0.01', // http errors should be less than 1%
    'http_req_duration': 'p(95)<500', // 95% of requests should be below 500ms
  },
};

export default function () {
  const res = http.get('http://localhost:8080'); // Replace with your target URL
  check(res, { 'status was 200': (r) => r.status == 200 });
  sleep(1);
}

In this script, options.stages defines the load profile: gradually increasing to 20 virtual users over 30 seconds, holding that load for a minute, and then gradually decreasing. options.thresholds sets the performance targets: no more than 1% of requests should fail, and 95% of requests must complete within 500 milliseconds. The default function defines the actual user actions, in this case, a simple GET request.

When k6 runs within GitHub Actions, it executes this script against your application. If any of the defined thresholds are not met, k6 will exit with a non-zero status code, causing the GitHub Actions job to fail. This failure immediately signals a performance regression in your pipeline.

The k6io/action@v1.1.0 GitHub Action is a convenient wrapper. It handles downloading the k6 binary and allows you to specify your test script. However, for more complex scenarios or finer control, you can directly use a standard run step to execute the k6 run command, as shown in the initial example. This gives you access to all of k6’s command-line options, including various output formats (--out json=report.json, --out html=report.html, --out csv=report.csv) and configuration flags.

A common point of confusion is how to interpret the results when the action itself doesn’t fail the job. The k6io/action can report thresholds to the GitHub Checks API if checks: true is set, which visually indicates pass/fail directly in your pull request. Alternatively, by using --out json=report.json and then a subsequent step to parse that JSON and conditionally fail the job (e.g., using jq and exit), you can implement custom failure logic.

The next logical step after automating basic performance tests is to integrate with external monitoring or reporting services.

Want structured learning?

Take the full K6 course →