├── .github └── workflows │ ├── basic.yml │ ├── browser.yml │ ├── cloud.yml │ ├── docker.yml │ ├── env-vars.yml │ ├── k6_extension.yml │ ├── local-service.yml │ ├── macos.yml │ ├── modules.yml │ ├── run-with-glob.yml │ └── windows.yml ├── CODEOWNERS ├── README.md ├── basic └── script.js ├── browser └── script.js ├── cloud └── script.js ├── docker └── script.js ├── env-var └── script.js ├── extension └── script.js ├── local-service └── script.js ├── modules ├── script.js └── utils │ └── options.js └── run-with-glob ├── run-tests.sh ├── script.js └── script2.js /.github/workflows/basic.yml: -------------------------------------------------------------------------------- 1 | name: Basic Workflow 2 | on: [push] 3 | 4 | jobs: 5 | k6_local_test: 6 | name: k6 local test run - basic example 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v1 12 | 13 | - name: Run local k6 test 14 | uses: grafana/k6-action@v0.2.0 15 | with: 16 | filename: basic/script.js 17 | flags: --out json=results.json 18 | 19 | - name: Upload performance test results 20 | uses: actions/upload-artifact@v3 21 | with: 22 | name: k6-report 23 | path: results.json 24 | -------------------------------------------------------------------------------- /.github/workflows/browser.yml: -------------------------------------------------------------------------------- 1 | name: Browser Workflow 2 | on: [push] 3 | 4 | jobs: 5 | k6_browser_test: 6 | name: k6 browser test 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v4 12 | 13 | - name: Install k6 in Ubuntu 14 | run: | 15 | sudo gpg -k 16 | sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69 17 | echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list 18 | sudo apt-get update 19 | sudo apt-get install k6 20 | 21 | # Install Chrome (or chromium) when using ACT, as the default ACT image does not include it. 22 | # Note that running the browser in a container like Snap or Flatpak is not supported. 23 | - name: Install chrome 24 | if: ${{ env.ACT }} 25 | run: | 26 | wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add - 27 | sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list' 28 | sudo apt update && sudo apt install -y google-chrome-stable 29 | 30 | # If you plan to run ACT on Apple Silicon, be aware that Chrome has not yet released an arm64 version. In this case, you have to: 31 | # 1. Enable the option on Docker Desktop: `Use Rosetta for x86/amd64 emulation on Apple Silicon` 32 | # 2. Run ACT using the `--container-architecture linux/amd64` flag. For example: 33 | # act -W .github/workflows/browser.yml --container-architecture linux/amd64 34 | 35 | - name: Run browser test 36 | run: | 37 | export K6_BROWSER_HEADLESS=true 38 | export K6_BROWSER_ARGS='no-sandbox' 39 | if [ "$ACT" = "true" ]; then 40 | export K6_BROWSER_EXECUTABLE_PATH=/usr/bin/google-chrome 41 | fi 42 | k6 run browser/script.js 43 | env: 44 | ACT: ${{ env.ACT }} -------------------------------------------------------------------------------- /.github/workflows/cloud.yml: -------------------------------------------------------------------------------- 1 | name: Cloud Workflow 2 | on: [push] 3 | 4 | jobs: 5 | build: 6 | name: Run k6 cloud test 7 | runs-on: ubuntu-latest 8 | environment: test 9 | 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | 14 | - name: Run k6 cloud test 15 | uses: grafana/k6-action@v0.2.0 16 | with: 17 | filename: cloud/script.js 18 | cloud: true 19 | token: ${{ secrets.K6_CLOUD_API_TOKEN }} 20 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker Workflow 2 | on: [push] 3 | 4 | jobs: 5 | k6_local_test: 6 | name: k6 local test run - docker example 7 | runs-on: ubuntu-latest 8 | container: grafana/k6:latest 9 | 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v1 13 | 14 | - name: Local k6 test 15 | run: k6 run docker/script.js 16 | -------------------------------------------------------------------------------- /.github/workflows/env-vars.yml: -------------------------------------------------------------------------------- 1 | name: Docker Env Vars Workflow 2 | on: [push] 3 | 4 | jobs: 5 | k6_local_test: 6 | name: k6 local test run - env vars example 7 | runs-on: ubuntu-latest 8 | container: docker://grafana/k6:latest 9 | 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v1 13 | 14 | - name: Run k6 cloud test 15 | uses: k6io/action@v0.1 16 | with: 17 | filename: env-var/script.js 18 | env: 19 | MY_HOSTNAME: test.loadimpact.com 20 | -------------------------------------------------------------------------------- /.github/workflows/k6_extension.yml: -------------------------------------------------------------------------------- 1 | name: K6 Extension Workflow 2 | on: [push] 3 | 4 | jobs: 5 | k6_local_test: 6 | name: k6 counter extension run 7 | runs-on: ubuntu-latest 8 | container: docker://golang:1.17-alpine 9 | 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v1 13 | 14 | - name: Install xk6 15 | run: go install go.k6.io/xk6/cmd/xk6@latest 16 | 17 | - name: Build xk6-counter binary 18 | run: xk6 build --with github.com/mstoykov/xk6-counter@latest 19 | 20 | - name: Run k6 extension test 21 | run: ./k6 run extension/script.js 22 | 23 | -------------------------------------------------------------------------------- /.github/workflows/local-service.yml: -------------------------------------------------------------------------------- 1 | name: Local Service Workflow 2 | on: [push] 3 | 4 | jobs: 5 | runner-job: 6 | runs-on: ubuntu-latest 7 | 8 | services: 9 | quickpizza: 10 | image: ghcr.io/grafana/quickpizza-local:latest 11 | ports: 12 | - 3333:3333 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | 18 | - name: Run local k6 test 19 | uses: grafana/k6-action@v0.3.1 20 | with: 21 | filename: local-service/script.js 22 | env: 23 | BASE_URL: "http://localhost:3333" -------------------------------------------------------------------------------- /.github/workflows/macos.yml: -------------------------------------------------------------------------------- 1 | name: MacOS Workflow 2 | on: [push] 3 | 4 | jobs: 5 | k6_local_test: 6 | name: k6 local test on macos 7 | runs-on: macos-latest 8 | 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v1 12 | 13 | - name: Install k6 by homebrew 14 | run: brew install k6 15 | 16 | - name: Local k6 test 17 | run: k6 run basic/script.js 18 | -------------------------------------------------------------------------------- /.github/workflows/modules.yml: -------------------------------------------------------------------------------- 1 | name: Modules Workflow 2 | on: [push] 3 | 4 | jobs: 5 | k6_local_test: 6 | name: k6 local test run - using js modules example 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v1 12 | 13 | - name: Local k6 test with js modules 14 | uses: grafana/k6-action@v0.2.0 15 | with: 16 | filename: modules/script.js 17 | -------------------------------------------------------------------------------- /.github/workflows/run-with-glob.yml: -------------------------------------------------------------------------------- 1 | name: Run with Glob Workflow 2 | on: [push] 3 | 4 | jobs: 5 | run_multiple_tests: 6 | name: Run multiple tests with glob patterns 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v4 12 | 13 | - name: Install k6 in Ubuntu 14 | run: | 15 | sudo gpg -k 16 | sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69 17 | echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list 18 | sudo apt-get update 19 | sudo apt-get install k6 20 | 21 | - name: Run multiple tests 22 | run: run-with-glob/run-tests.sh -t **/run-with-glob/*.js -p '-i 1' 23 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: Windows Workflow 2 | on: [push] 3 | 4 | jobs: 5 | k6_local_test: 6 | name: k6 local test run on windows 7 | runs-on: windows-latest 8 | 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v1 12 | 13 | - name: download and extract k6 release binaries 14 | run: | 15 | curl -L https://github.com/grafana/k6/releases/download/v0.25.1/k6-v0.25.1-win64.zip -o k6.zip 16 | 7z.exe e k6.zip 17 | shell: bash 18 | 19 | - name: k6 test 20 | run: ./k6.exe run basic/script.js 21 | shell: bash 22 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @ppcano -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Automated k6 load testing with Github Actions 2 | 3 | This is an example repo for how to setup k6 with Github Actions to build load testing into an automation flow. 4 | 5 | Examples: 6 | 7 | | File | Description | 8 | | --------------------------------------------------------------------------| -------------------------------------------------------------------------------------- | 9 | | [.github/workflows/basic.yml](.github/workflows/basic.yml) | Runs on ubuntu-latest using the marketplace action | 10 | | [.github/workflows/env-vars.yml](.github/workflows/env-vars.yml) | Runs on ubuntu-latest, with env vars, using the marketplace action | 11 | | [.github/workflows/modules.yml](.github/workflows/modules.yml) | Runs on ubuntu-latest, with js modules, using the marketplace action | 12 | | [.github/workflows/cloud.yml](.github/workflows/cloud.yml) | Runs in the k6 cloud, using the marketplace action | 13 | | [.github/workflows/macos.yml](.github/workflows/macos.yml) | Runs on macOS, using k6 installed with hombrew | 14 | | [.github/workflows/windows.yml](.github/workflows/windows.yml) | Runs on windows, using the downloaded k6 release binary | 15 | | [.github/workflows/docker.yml](.github/workflows/docker.yml) | Runs on ubuntu-latest, in a docker container created from the official k6 docker image | 16 | | [.github/workflows/k6_extension.yml](.github/workflows/k6_extension.yml) | Runs on golang:1.17-alpine, an environment suitable for running k6 extensions | 17 | | [.github/workflows/local-service.yml](.github/workflows/local-service.yml) | Runs side by side with the system under test, Quickpizza | 18 | | [.github/workflows/browser.yml](.github/workflows/browser.yml) | Runs browser tests | 19 | | [.github/workflows/run-with-glob.yml](.github/workflows/run-with-glob.yml) | Runs multiple tests with glob pattern | 20 | 21 | More complex examples could be combined from the basic examples from the list above. 22 | 23 | The full guide describing how to use this repository is located [here](https://blog.loadimpact.com/load-testing-using-github-actions). 24 | -------------------------------------------------------------------------------- /basic/script.js: -------------------------------------------------------------------------------- 1 | import { check, sleep } from "k6"; 2 | import http from "k6/http"; 3 | 4 | export let options = { 5 | stages: [ 6 | { duration: "20s", target: 10 } 7 | ] 8 | }; 9 | 10 | export default function() { 11 | var r = http.get("http://test.loadimpact.com"); 12 | check(r, { 13 | "status is 200": (r) => r.status === 200 14 | }); 15 | sleep(1); 16 | } 17 | -------------------------------------------------------------------------------- /browser/script.js: -------------------------------------------------------------------------------- 1 | import { browser } from "k6/experimental/browser"; 2 | import { check } from "k6"; 3 | 4 | export const options = { 5 | scenarios: { 6 | ui: { 7 | executor: "shared-iterations", 8 | options: { 9 | browser: { 10 | type: "chromium", 11 | }, 12 | }, 13 | }, 14 | }, 15 | }; 16 | 17 | export default async function () { 18 | const page = browser.newPage(); 19 | 20 | try { 21 | await page.goto('https://test.k6.io/'); 22 | check(page, { 23 | header: 24 | page.locator("title").textContent() == 25 | "Demo website for load testing", 26 | }); 27 | 28 | } finally { 29 | page.close(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cloud/script.js: -------------------------------------------------------------------------------- 1 | import { check, sleep } from "k6"; 2 | import http from "k6/http"; 3 | 4 | export let options = { 5 | stages: [ 6 | { duration: "20s", target: 10 } 7 | ], 8 | ext: { 9 | loadimpact: { 10 | name: 'k6-example-github-actions', 11 | projectID: 3673231, 12 | }, 13 | }, 14 | }; 15 | 16 | export default function() { 17 | var r = http.get("http://test.loadimpact.com"); 18 | check(r, { 19 | "status is 200": (r) => r.status === 200 20 | }); 21 | sleep(1); 22 | } 23 | -------------------------------------------------------------------------------- /docker/script.js: -------------------------------------------------------------------------------- 1 | import { check, sleep } from "k6"; 2 | import http from "k6/http"; 3 | 4 | export let options = { 5 | stages: [ 6 | { duration: "20s", target: 10 } 7 | ] 8 | }; 9 | 10 | export default function() { 11 | var r = http.get("http://test.loadimpact.com"); 12 | check(r, { 13 | "status is 200": (r) => r.status === 200 14 | }); 15 | sleep(1); 16 | } 17 | -------------------------------------------------------------------------------- /env-var/script.js: -------------------------------------------------------------------------------- 1 | import { check, sleep } from "k6"; 2 | import http from "k6/http"; 3 | 4 | export let options = { 5 | stages: [ 6 | { duration: "20s", target: 10 } 7 | ] 8 | }; 9 | 10 | export default function() { 11 | var r = http.get(`http://${__ENV.MY_HOSTNAME}/`); 12 | check(r, { 13 | "status is 200": (r) => r.status === 200 14 | }); 15 | sleep(5); 16 | } 17 | -------------------------------------------------------------------------------- /extension/script.js: -------------------------------------------------------------------------------- 1 | import counter from "k6/x/counter"; 2 | 3 | export const options = { 4 | vus: 10, 5 | duration: '5s', 6 | }; 7 | 8 | export default function () { 9 | console.log(counter.up(), __VU, __ITER); 10 | } 11 | -------------------------------------------------------------------------------- /local-service/script.js: -------------------------------------------------------------------------------- 1 | import http from "k6/http"; 2 | import { check, sleep } from "k6"; 3 | 4 | const BASE_URL = __ENV.BASE_URL || 'http://localhost:3333'; 5 | 6 | export const options = { 7 | vus: 5, 8 | duration: '10s', 9 | }; 10 | 11 | export default function () { 12 | let restrictions = { 13 | maxCaloriesPerSlice: 500, 14 | mustBeVegetarian: false, 15 | excludedIngredients: ["pepperoni"], 16 | excludedTools: ["knife"], 17 | maxNumberOfToppings: 6, 18 | minNumberOfToppings: 2 19 | } 20 | let res = http.post(`${BASE_URL}/api/pizza`, JSON.stringify(restrictions), { 21 | headers: { 22 | 'Content-Type': 'application/json', 23 | 'X-User-ID': 23423, 24 | }, 25 | }); 26 | check(res, { "status is 200": (res) => res.status === 200 }); 27 | console.log(`${res.json().pizza.name} (${res.json().pizza.ingredients.length} ingredients)`); 28 | sleep(1); 29 | } -------------------------------------------------------------------------------- /modules/script.js: -------------------------------------------------------------------------------- 1 | import { check, sleep } from "k6"; 2 | import http from "k6/http"; 3 | 4 | export { default as options } from "./utils/options.js"; 5 | 6 | export default function() { 7 | var r = http.get("http://test.loadimpact.com"); 8 | check(r, { 9 | "status is 200": (r) => r.status === 200 10 | }); 11 | sleep(1); 12 | } 13 | -------------------------------------------------------------------------------- /modules/utils/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | stages: [ 3 | { duration: "20s", target: 10 } 4 | ] 5 | }; 6 | -------------------------------------------------------------------------------- /run-with-glob/run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | shopt -s globstar 3 | 4 | while getopts "t:f:" flag; do 5 | case $flag in 6 | t) TESTS="$OPTARG" ;; 7 | f) FLAGS="$OPTARG" ;; 8 | esac 9 | done 10 | 11 | echo "Running tests: $TESTS with flags: $FLAGS" 12 | 13 | for test in $TESTS; do 14 | k6 run $FLAGS "$test" 15 | 16 | exit_code=$? 17 | if [ $exit_code -ne 0 ]; then 18 | exit $exit_code 19 | fi 20 | done -------------------------------------------------------------------------------- /run-with-glob/script.js: -------------------------------------------------------------------------------- 1 | import { check, sleep } from "k6"; 2 | import http from "k6/http"; 3 | 4 | export let options = { 5 | stages: [ 6 | { duration: "20s", target: 10 } 7 | ] 8 | }; 9 | 10 | export default function() { 11 | var r = http.get("http://test.loadimpact.com"); 12 | check(r, { 13 | "status is 200": (r) => r.status === 200 14 | }); 15 | sleep(1); 16 | } 17 | -------------------------------------------------------------------------------- /run-with-glob/script2.js: -------------------------------------------------------------------------------- 1 | import { check, sleep } from "k6"; 2 | import http from "k6/http"; 3 | 4 | export let options = { 5 | stages: [ 6 | { duration: "20s", target: 10 } 7 | ] 8 | }; 9 | 10 | export default function() { 11 | var r = http.get("http://test.loadimpact.com"); 12 | check(r, { 13 | "status is 200": (r) => r.status === 200 14 | }); 15 | sleep(1); 16 | } 17 | --------------------------------------------------------------------------------