Matrix builds
A matrix expands a single test into one instance per combination of its
variables — the cross product:
test:
name: integration
matrix:
node: ["20", "22"]
db: [postgres, mysql]
command: npm run test:integration
This runs 4 instances: integration (node=20, db=postgres),
integration (node=20, db=mysql), and so on. Instances run like a parallel
group.
Using matrix values
Matrix values are available as ${{ matrix.NAME }} templates and as
environment variables TESTFILE_MATRIX_<NAME> (the name upper-cased, so
matrix.node becomes TESTFILE_MATRIX_NODE):
test:
matrix:
node: ["20", "22"]
env:
NODE_VERSION: ${{ matrix.node }}
command: ./run-with-node.sh "$NODE_VERSION"
exclude and include
exclude removes combinations from the cross product; include appends
extra ones:
matrix:
node: ["20", "22"]
db: [postgres, mysql]
exclude:
- node: "20"
db: mysql
include:
- node: "23"
db: postgres
Matrix + services
Templates are also substituted in the instance’s services, which makes “test against three database versions” a three-line change:
test:
name: db tests
matrix:
postgres: ["15", "16", "17"]
services:
postgres:
container:
image: docker.io/library/postgres:${{ matrix.postgres }}
ports:
- "${{ ports.db }}:5432"
env:
POSTGRES_PASSWORD: test
ready:
log: database system is ready to accept connections
command: npm run test:db
Each instance starts (and stops) its own container with its own version —
unless the service is shared: true, where
instances with an identical resolved configuration reuse one.
A nested test can carry its own matrix too: it inherits the ancestors’
values and its own combination is merged on top (same-named variables
shadow the inherited ones).