πŸ™Testfile

Exporting pipelines

testfile export translates a Testfile into a pipeline that runs the same tests without the runner: a GitHub Actions workflow, a GitLab CI configuration, a Tekton Pipeline, or a plain bash script. Sequences, parallel groups, matrices, services, conditions, retries and hooks all come along β€” as native jobs, matrices and sidecars where the target has them, as generated shell where it does not.

testfile export github     # writes .github/workflows/testfile.yaml
testfile export gitlab     # writes .gitlab-ci.yml
testfile export tekton     # writes .tekton/testfile-pipeline.yaml
testfile export bash       # writes testfile-ci.sh (executable)

When you can run the runner in CI, prefer it: the GitHub Action, the GitLab template and the Tekton Task keep the pipeline a single job and record the run β€” run.yaml, per-test logs, service logs, artifacts, caching, the viewer. The export is for the cases where that is not wanted: a pipeline that must not depend on Node.js or on the runner, a migration away from (or toward) Testfile, or a readable answer to β€œwhat would this suite look like as a workflow?”. An exported pipeline runs the tests and fails when they fail β€” it does not write run.yaml, collect logs or artifacts, or use the result cache.

Each generated file starts with a Generated by testfile export header. Re-run the command after editing the Testfile instead of editing the output; the command refuses to overwrite a file that lacks the header (that is somebody’s hand-written work) unless --force is given.

Options

testfile export <target> [path] [options]
OptionDescription
-f, --filter <value>Export only tests matching by name/path, tag, or key:value matrix β€” the same filters start takes. (repeatable)
-n, --filter-name <name-or-path>Only tests whose path contains this. (repeatable)
-t, --filter-tags <tags>Only tests tagged with any of these comma-separated tags. (repeatable)
-m, --filter-matrix <key:value>Only matrix instances with this value. (repeatable)
-l, --label <key=value>Write a TESTFILE_LABEL_<KEY> variable into the pipeline (workflow env on GitHub, variables on GitLab, step env on Tekton, an export in the script). (repeatable)
-o, --output <file>Where to write the pipeline (defaults per target, next to the Testfile), or - for stdout.
--image <image>Container image for GitLab jobs and Tekton steps (Tekton default: node:22; GitLab: none unless given).
--runs-on <label>GitHub Actions runner label (default ubuntu-latest).
--forceOverwrite a file that was not generated by this command.

--failed, --changed and --shard do not apply here: they select against a recorded run or a git checkout at run time, which an exported pipeline does not have.

How a suite becomes a pipeline

bash is the closest translation, because nothing has to be cut into jobs: one script runs sequences in order, parallel groups as background processes (needs becomes dependency waves), expands matrices, starts and stops services exactly where the Testfile declares them, and prints a pass/fail/skipped summary. Exit code 0 only when nothing (hard) failed.

GitHub Actions and GitLab CI are cut into jobs the way a human would write them:

Tekton gets one task per test, with runAfter edges for sequences and needs. All tasks share the source workspace (a PVC, cloned by a git-clone task like the Tekton Task’s pipeline), so unlike the job-based exports nothing has to be repeated: a sequence’s earlier tasks leave their state on the workspace. Container services become sidecars of the tasks that declared them; process services run inside the step’s shell.

Testfile templates are rewritten rather than resolved: ${{ env.X || d }} becomes the shell’s ${X:-d}, ${{ ports.web }} the pinned port number, ${{ matrix.node }} either the baked value or the TESTFILE_MATRIX_NODE variable the native matrix feeds. if conditions are evaluated at run time, in shell, with TESTFILE_OS/TESTFILE_ARCH detected by the script.

What each target supports

Legend: βœ“ supported Β· ~ supported with the noted caveat Β· βœ— not exported (a note in the generated header says so where it matters).

FeaturebashGitHubGitLabTekton
command / script / shellβœ“βœ“βœ“βœ“
sequenceβœ“~ steps in a job; leading tests repeat per branch job~ same as GitHubβœ“ runAfter over a shared workspace
parallel (nested too)βœ“ background wavesβœ“ one job per branchβœ“βœ“ parallel tasks
needsβœ“ waves + result gates~ merged into one job (shared state)~ same as GitHubβœ“ runAfter
matrix (incl. exclude/include)βœ“ expandedβœ“ native strategy.matrixβœ“ parallel: matrix, one entry per combination~ native matrix.params for plain cross products on a single test; otherwise expanded
maxParallelβœ— noted~ only on a matrix (max-parallel)βœ— notedβœ— noted
foreach / includeβœ“ expanded at export timeβœ“βœ“βœ“
if conditionsβœ“ at run timeβœ“βœ“βœ“
env / envFile / portsβœ“ (random ports pinned at export)βœ“βœ“βœ“
secrets~ taken from the environmentβœ“ wired as ${{ secrets.NAME }}~ define them as CI/CD variables~ provide them to the pod
Process services + readiness (http/tcp/log/exec)βœ“βœ“βœ“βœ“ in-step
Container servicesβœ“ via podman/docker (TESTFILE_ENGINE)βœ“ same~ same; the runner must be able to run containers (shell executor or docker:dind)βœ“ sidecars; readiness by http/tcp only, once containers βœ—
Service needsβœ“ started in dependency orderβœ“βœ“βœ“
shared services~ one instance per declaring scope, noted~~~
setup / teardownβœ“ where declared~ group hooks run once per job~ same~ once per task
retryβœ“βœ“βœ“βœ“
continueOnErrorβœ“ soft failure in the summaryβœ“ continue-on-errorβœ“ allow_failure / soft stepβœ“ onError: continue
timeout~ via the timeout command where installed~ same~ same~ same
container on a testβœ“ engine CLIβœ“~ needs a container-capable runnerβœ“ becomes the step’s image
Run recording (run.yaml, logs), artifactsβœ— by designβœ—βœ—βœ—
Result caching (inputs)βœ— tests always runβœ—βœ—βœ—
--failed / --changed / --shard / watch modeβœ—βœ—βœ—βœ—
Env isolation / forwardEnv / secret maskingβœ— tests see the CI job’s environmentβœ—βœ—βœ—

Semantics that shift slightly in every export, all of them consequences of running without the runner:

Examples

Only the fast tests, labelled, to stdout:

testfile export github -t fast -l tier=smoke -o -

One matrix leg for a debugging pipeline:

testfile export gitlab -m node:22

A Tekton pipeline whose steps run on your build image:

testfile export tekton --image registry.example.com/build:latest
kubectl apply -f .tekton/testfile-pipeline.yaml

The generated pipeline needs the same prerequisites the Task’s pipeline does: the git-clone catalog task and a PVC-backed source workspace β€” see Tekton for both.

Run the bash export anywhere a shell is:

testfile export bash && ./testfile-ci.sh

The scripts assume bash, and curl where ready: http is used; container services additionally need podman or docker (pick one with TESTFILE_ENGINE, default docker).