🐙Testfile

Tekton

The tekton/ folder of the repository holds the GitHub Action’s sibling for Kubernetes-native CI: a reusable Tekton Task. Install it once per namespace, then running a Testfile is a single task in any pipeline:

kubectl apply -f https://raw.githubusercontent.com/testfile-dev/testfile/main/tekton/testfile-task.yaml
  tasks:
    - name: test
      runAfter: [fetch]
      taskRef:
        name: testfile
      workspaces:
        - name: source
          workspace: source

The task fetches the runner with npx and executes testfile start against the source workspace — the checkout a git-clone task produced. The TaskRun fails when tests fail. Because the runner owns services, parallelism and matrices, the pipeline stays this one task instead of a translation of your test setup into Tekton YAML. tekton/testfile-pipeline.yaml is a complete clone-then-test pipeline to start from.

Services run on the cluster

On GitHub’s runners the services a Testfile declares run on docker. A Tekton step is already a container, with no docker beside it — but it has something better: the cluster itself. The task pins TESTFILE_ENGINE=kubernetes, so the runner’s kubernetes engine starts each service as a pod in the TaskRun’s own namespace, wires service-to-service DNS, forwards the declared ports to localhost, and tears everything down at the end. The Testfile stays engine-neutral: the same file runs its postgres on docker on a laptop and as a pod on the cluster in CI.

Two things make that work:

A Testfile without container services needs neither — the task runs with any ServiceAccount and never looks for kubectl.

Parameters

ParameterDefaultDescription
path.Testfile or directory containing one, relative to the workspace.
filter / filter-name / filter-tags / filter-matrix–Same as the -f/-n/-t/-m CLI filters.
changedfalseRun only tests whose inputs match files changed against the base branch (--changed). Needs history in the checkout — set the clone’s depth to 0.
changed-since–Base branch/ref for changed, e.g. origin/main.
fail-fastfalseAbort the whole run at the first failure.
max-parallel–Global cap on concurrently running tests.
shard–Run only this shard of the selected tests, e.g. 2/4 — for splitting one suite across parallel TaskRuns.
reporter / output–Write machine-readable results (junit or json) to a file in the workspace.
doctortrueRun testfile doctor first: every missing tool, engine or taken port that would fail the run anyway fails early, in one readable report.
enginekubernetesContainer engine for the declared services. Empty auto-detects — which inside a step pod still means kubernetes, there is no docker to find.
kubectl-versionstablekubectl to download when the image has none; a pinned v1.31.0 beats resolving stable on every run.
imagenode:22Image the steps run in — needs node and npm (and curl, to fetch kubectl).
variants[]What tells this run apart from the other legs of a matrix, as key=value pairs (array parameter). Recorded in run.yaml; merging needs it.
labels[]Extra labels to record, as key=value pairs (array parameter). A key you set yourself wins over an automatic one.
auto-labelstrueLabel the run with the Tekton context — see below.

Two results come back: outcome (passed or failed — useful in a finally task, which runs on failure too) and run-id, the id of the recorded run under .testfile/runs/ in the workspace, so a later task can pack or upload exactly this run.

What a run is labelled with

Like the action, the task labels every recorded run with where it came from, so a history that collects runs from many pipelines can be narrowed down afterwards (testfile serve filters by label):

KeyValue
citekton
ci-runthe TaskRun name, to get from a recorded run back to its logs
namespacethe namespace the run happened in

Tekton injects no git context into a task, so branch and commit labels are the pipeline’s to pass — the clone task knows them:

      params:
        - name: labels
          value:
            - branch=$(params.revision)
            - sha=$(tasks.fetch.results.commit)

Bringing runs home

The recorded run — run.yaml, per-test logs, service logs, collected artifacts — lands in .testfile/runs/ on the source workspace. Back it with a PVC (a volumeClaimTemplate in the PipelineRun) and it survives the pod, but Tekton has no artifact store to browse it in. The S3 route fits Tekton best: one more task behind test, running on failure too, pushing the run to a bucket —

    - name: upload-run
      runAfter: [test]
      taskRef:
        name: testfile-upload   # or an inline taskSpec around these commands
      # push the run whether the tests passed or not - the interesting run
      # is the one that failed
      params:
        - name: script
          value: |
            npx --yes @testfile.dev/runner s3 push s3://my-bucket/testfile-runs
      workspaces:
        - name: source
          workspace: source

— and on a laptop the same bucket is the run history:

testfile s3 pull s3://my-bucket/testfile-runs
testfile runs        # CI runs are now part of the history

Any S3-compatible endpoint the aws CLI reaches works, including MinIO on the same cluster (AWS_ENDPOINT_URL). Without a bucket, testfile archive pack in a follow-up task produces a single .tgz on the workspace to copy out however you like, and testfile archive import ingests it locally — see sharing runs.

More than one leg

shard splits one suite across parallel TaskRuns (shard: 1/3 … 3/3), and Tekton’s matrix turns that into one pipeline task:

    - name: test
      taskRef:
        name: testfile
      matrix:
        params:
          - name: shard
            value: ["1/2", "2/2"]

Each leg records its shard as a variant (shard=1/2) on its own — inside a matrix no other parameter can tell the legs apart — so testfile merge can unite the recorded runs into a single verdict later, exactly as with the action’s platform matrix.

Test bodies with a container: are the one thing the kubernetes engine does not cover — a body container runs locally, next to the runner, and a step pod has no engine for it. Keep test bodies plain commands (the step’s image is the environment they run in) and leave containers to the services.