Develop GitHub actions faster with ‘act’, an easy example

Adrian Angel Sanz Melchor
4 min readMay 11, 2023
Photo by Fotis Fotopoulos on Unsplash

The problem with actions

Sometime ago I started tinkering around GitHub actions at work, for those who don’t know about them, they are reusable pieces of code that can be integrated into your pipelines. Is a pretty useful feature that allows you to create and maintain reusable actions (like set-up python, execute Ansible etc). However I came across an issue, which was testing the actions. I was loosing so much time trying to figure out why my action worked locally but when executed on a workflow it would fail, or debugging logic via pipeline runs. I was missing having a local environment that matches the GitHub runners set-up, this is where ‘act’ comes in.

Straight to the point

I know I know, you just want the code right? The project can be found here: https://github.com/nektos/act I’m not covering the install method as is the easy part.

However, I will cover creating a fresh action, and testing it locally, you will get the hang of it once you have tested a couple of actions, let’s start with this:

  • An empty repository (already on GH)
  • You are going to create an ‘action.yaml’ file
  • Also create a ‘.github/workflows/test.yml’ in there.
  • Last but not least, we are going to use inputs! So create a file named ‘events.json’

Ending up in (.git folder excluded):

├── action.yml
├── events.json
└── .github
└── workflows
└── test.yml

That will allow us to have the basic structure of a single action that can be tested with act.

Don’t forget to add the .github folder on a .gitignore file if you don’t want your workflow to get executed on GH. Also any file needed for act other than the workflow cannot be on .gitignore when testing act because those file will not be there on runtime

Let’s start with the action.yaml file

name: 'Print a name simple action'
description: 'Prints a name'
inputs:
NAME:
description: 'The name to print'
required: false
default: 'N/A'
runs:
using: "composite"
steps:
- name: Print the name
run: |
echo 'Hello ${{ inputs.NAME }}!'

shell: bash

This is a really simple action, that will get a name as an input and print it. This action is easy to use, but when you have an action that uses JS or a complex Bash script, you will appreciate testing locally with act.

Now let’s build the test.yml

name: Print name
on:
workflow_dispatch:
inputs:
NAME:
description: 'Name to print'
required: true
type: string

jobs:
print-name:
name: Print name
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Print name
uses: ./
with:
NAME: ${{ inputs.NAME }}

Okay, two important things here

  1. Note the ‘./’ on ‘uses’, this is how you let act know where to look up for the action.
  2. The checkout is important, otherwise your action will be empty on run-time, this seem obvious, but I lost quite some time because of this, duh.

Also note that I’m using workflow_dispatch instead of push.

Last let’s get events.json

{
"action": "workflow_dispatch",
"inputs": {
"NAME": "Jhon Doe"
}
}

Pretty straight right?

How would you test this action?

Once act is installed, you could just simply run:

act workflow_dispatch -e events.json

[Print name/Print name] 🚀 Start image=catthehacker/ubuntu:act-latest
[Print name/Print name] 🐳 docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=true
[Print name/Print name] 🐳 docker create image=catthehacker/ubuntu:act-latest platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[]
[Print name/Print name] 🐳 docker run image=catthehacker/ubuntu:act-latest platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[]
[Print name/Print name] ⭐ Run Main actions/checkout@v3
[Print name/Print name] ✅ Success - Main actions/checkout@v3
[Print name/Print name] ⭐ Run Main Print name
[Print name/Print name] ⭐ Run Main Print the name
[Print name/Print name] 🐳 docker exec cmd=[bash --noprofile --norc -e -o pipefail /var/run/act/workflow/1-composite-0.sh] user= workdir=
| Hello Jhon Doe!
[Print name/Print name] ✅ Success - Main Print the name
[Print name/Print name] ✅ Success - Main Print name
[Print name/Print name] ⭐ Run Post Print name
[Print name/Print name] ✅ Success - Post Print name
[Print name/Print name] 🏁 Job succeeded

Note how I set ‘workflow_dispatch’ as my first parameter, act will by default only execute actions with ‘push’.

Easy right?

This tool has saved me a lot of time not only testing simple actions just for the shake of the “It works on my machine” issue, but also on more complex actions that had logic which require prior heavy testing. All from my local computer but with the same env as a runner.

References:

That’s all folks!

--

--

Adrian Angel Sanz Melchor

Just a spanish DevOps who likes sharing useful knowledge, working proudly @ Cipher