When using GitHub actions we usually chain all the checks, but when some tests take a long time, having the CI fail at the first check, can consume a lot of time from our developers, who need to fix this first step, re-run the CI and then see if it fails anywhere else.
Instead, we can make all the checks run at the same time, and let our developers see all the failing cases, using a matrix
Example with chained tests
A generic GitHub action for a javascript PR could look like the following:
on:
pull_request:
jobs:
test-code:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install
- run: npm run eslint
- run: npm run prettier
- run: npm run test
- run: npm run e2e
- run: npm run build
So every test is quite linear and it is executed one after the other.
stateDiagram
direction LR
[*] --> install
install --> eslint
eslint --> prettier
prettier --> test
test --> e2e
e2e --> build
build --> [*]
But if the pull request fails in two points, for example, in eslint and e2e, the users won’t see the second case. They will only see that eslint is failing.
stateDiagram
classDef errorEvent fill:#f00,color:white,font-weight:bold,stroke-width:2px,stroke:yellow
direction LR
[*] --> install
install --> eslint
eslint --> error
class error errorEvent
They will have to fix the eslint test, push their changes and wait for the CI again.
If we are working on a big project, where one of the early steps consumes a lot of time, this could mean that our developers will let this run and forget about it until it’s too late, and they have moved their attention somewhere else.
Using a matrix to see all the checks at once
What we can do, instead, is use a matrix strategy. This allows us to run all the checks simultaneously and detect every failing case at once.

A modified example of the previous action would be the following:
on:
pull_request:
jobs:
test-code:
runs-on: ubuntu-latest
strategy:
fail-fast: false # Important
matrix:
command:
- eslint
- prettier
- test
- e2e
- build
steps:
- uses: actions/checkout@v4
- run: npm install
- run: npm run ${{ matrix.command }}
This will make all the tests run concurrently and, in the case that one of them fails, we will be able to visualize them without having to necessarily fix them one by one.
stateDiagram
direction LR
classDef errorEvent fill:#f00,color:white,font-weight:bold,stroke-width:2px,stroke:yellow
[*] --> install
install --> eslint
eslint --> error
install --> prettier
prettier --> [*]
install --> test
test --> [*]
install --> e2e
e2e --> error
install --> build
build --> [*]
class error errorEvent

This allows your developers to work on fixing every failing check at once, instead of having to push and wait.

Combining tests into a final step
You’ll probably don’t want to have X amounts of required status checks in your branch protection rules, else you will end up with a very populated configuration that will look like this (and in your case it could be way worse):

If you add a new step, remove one, or simply want to copy the rules of main into release-v1 branch, you’ll have to do a lot of manual work (which could be prone to errors).
Instead, there is a simple solution that we can apply!
We make a new job which needs the matrix job:
on:
pull_request:
jobs:
test-code:
runs-on: ubuntu-latest
strategy:
fail-fast: false # Important
matrix:
command:
- eslint
- prettier
- test
- e2e
- build
steps:
- uses: actions/checkout@v4
- run: npm install
- run: npm run ${{ matrix.command }}
conclude:
runs-on: ubuntu-latest
name: Checks passed
needs: [test-code] # It will only run if test-code was completed
steps:
- run: echo 'Good job! All the tests passed 🚀'
This allows us to only require the job conclude as our required status check.
conclude will only run if all the jobs inside the matrix are completed successfully. If one of them fails, the job is skipped and the PR won’t be mergeable.
stateDiagram
direction LR
final: Checks Passed
state Matrix {
direction LR
[*] --> install
install --> eslint
eslint --> [*]
install --> prettier
prettier --> [*]
install --> test
test --> [*]
install --> e2e
e2e --> [*]
install --> build
build --> [*]
}
[*] --> Matrix
Matrix --> final
Now we can add or remove checks from the matrix without having to change the required status checks in the branch protection rules.