2021-03-04
Artillery is a Node package that can be used to create simulated load tests. You can define steps and APIs to send requests to, as well as create randomized data to simulate users. I wanted a way to automate running load tests, as well as creating reports that can be made with Artillery — for this, I turned to GitHub Actions.
This GitHub action will run an Artillery load test, generate an HTML report, convert it to PDF, and commit it back to the GitHub repo the action is running on.
I used the official Node version 14 image and update/install some packages to set up Chrome in advance of Puppeteer.
Artillery is installed globally so that the artillery
command works in the entrypoint.sh
script and install Puppeteer locally for the checked out Git repo that this Action is used on.
# Node Alpine container image
FROM node:14.14.0
RUN apt-get update \
&& apt-get install -y wget gnupg ca-certificates jq \
&& 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 \
# We install Chrome to get all the OS level dependencies, but Chrome itself
# is not actually used as it's packaged in the node puppeteer library.
# Alternatively, we could could include the entire dep list ourselves
# (https://github.com/puppeteer/puppeteer/blob/master/docs/troubleshooting.md#chrome-headless-doesnt-launch-on-unix)
# but that seems too easy to get out of date.
&& apt-get install -y google-chrome-stable \
&& rm -rf /var/lib/apt/lists/* \
&& wget --quiet https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh -O /usr/sbin/wait-for-it.sh \
&& chmod +x /usr/sbin/wait-for-it.sh
# Install artillery globally
RUN npm i -g artillery --allow-root --unsafe-perm=true
RUN npm i puppeteer
# Copy bash script to root of container image
COPY entrypoint.sh /entrypoint.sh
COPY generate-pdf.js /generate-pdf.js
RUN chmod +x /entrypoint.sh
# Execute Bash script on container start up
ENTRYPOINT ["/entrypoint.sh"]
The GitHub Action YAML file defines two input arguments:
name: 'Automated Artillery Action'
description: 'Automate Artillery Load Tests'
branding:
icon: crosshair
color: red
inputs:
artillery_path:
description: 'Path of Artillery YAML File'
required: true
output_path:
description: 'Path to push reports to'
required: false
runs:
using: 'docker'
image: 'Dockerfile'
args:
- ${{ inputs.artillery_path }}
- ${{ inputs.output_path }}
generating report and committing back to the repo This script is used as the entry point for the Docker container and will execute once the container has spun up.
#!/usr/bin/env bash
set -e
echo "Running Load Test"
# $1 is the path of the Load Test
# $2 is the output path for reports
artillery run --output report.json $1
OUTPUT_PDF=$(date +"%y-%m-%d-%H-%M-%S").pdf
OUTPUT_PATH=$OUTPUT_PDF
PUSH_PATH=$2
if [[ ! -z $PUSH_PATH ]]; then
if [[ ${PUSH_PATH:0:1} == "/" ]]; then
PUSH_PATH=${PUSH_PATH:1}
fi
OUTPUT_PATH="$PUSH_PATH/$OUTPUT_PDF"
fi
artillery report --output report.html report.json
node /generate-pdf.js
mv report.pdf $OUTPUT_PDF
STATUSCODE=$(curl --silent --output resp.json --write-out "%{http_code}" -X GET -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/repos/${GITHUB_REPOSITORY}/contents/$DIR)
if [ $((STATUSCODE/100)) -ne 2 ]; then
echo "Github's API returned $STATUSCODE"
cat resp.json
exit 22;
fi
SHA=""
for i in $(jq -c '.[]' resp.json);
do
NAME=$(echo $i | jq -r .name)
if [ "$NAME" = "$OUTPUT_PDF" ]; then
SHA=$(echo $i | jq -r .sha)
break
fi
done
echo '{
"message": "'"update $OUTPUT_PDF"'",
"committer": {
"name": "Automated Artillery Action",
"email": "automated-artillery-action@github.com"
},
"content": "'"$(base64 -w 0 $OUTPUT_PDF)"'",
"sha": "'$SHA'"
}' > payload.json
STATUSCODE=$(curl --silent --output /dev/stderr --write-out "%{http_code}" \
-i -X PUT -H "Authorization: token $GITHUB_TOKEN" -d @payload.json \
https://api.github.com/repos/${GITHUB_REPOSITORY}/contents/${OUTPUT_PATH})
if [ $((STATUSCODE/100)) -ne 2 ]; then
echo "Github's API returned $STATUSCODE"
exit 22;
fi
This script is executed by the entrypoint.sh
script above and uses the Puppeteer NPM package to launch a headless Chrome process, open the Artillery HTML report and convert it to PDF. This PDF file is saved to the filesystem.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox']});
const page = await browser.newPage();
await page.goto('file:///github/workspace/report.html', {waitUntil: 'networkidle2'});
await page.pdf({path: 'report.pdf', format: 'A4'});
await browser.close();
})();
Create a GitHub access token on your account that has permissions to read and write to repos, this will be used by the action to commit the report to the repo. Create a secret on the repo called TOKEN
and set the value to the GitHub access token you just created.
name: Auto Artillery
on: [workflow_dispatch]
jobs:
artillery-job:
runs-on: ubuntu-latest
name: Run Load Test
steps:
- uses: actions/checkout@v1
- name: Artillery
uses: SenorGrande/automated-artillery-action@v1.0.0
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
with:
artillery_path: 'index.yml'
output_path: 'reports'
note: on: [workflow_dispatch]
means the action can be run manually.
You can then view these reports within GitHub by browsing through the repo and clicking on the file!
Thanks for reading! Choice 🤙