Skip to content

WonderTwin in GitLab CI

Run a twin against your test suite in a GitLab CI pipeline. Capture replay artifacts on every run, including failures.

Drop this into .gitlab-ci.yml (or merge into your existing config):

test-with-twin:
image: ubuntu:24.04
variables:
WT_TWIN_URL: "http://127.0.0.1:<TWIN_PORT>"
CI: "true"
before_script:
- apt-get update -qq && apt-get install -y -qq curl ca-certificates
- curl -sSL "https://github.com/wondertwin-ai/wondertwin/releases/latest/download/wt-linux-amd64" -o /usr/local/bin/wt
- chmod +x /usr/local/bin/wt
- |
if [ -n "$WONDERTWIN_LICENSE" ]; then
printf '%s' "$WONDERTWIN_LICENSE" > /tmp/license.json
wt license install /tmp/license.json
rm /tmp/license.json
fi
- wt install <TWIN>@<TWIN_VERSION>
- wt up > /tmp/twin.log 2>&1 &
- echo $! > /tmp/twin.pid
- |
for i in $(seq 1 30); do
curl -sf "$WT_TWIN_URL/admin/state" > /dev/null && break
sleep 0.2
done
- |
wt runs start \
--twin "$WT_TWIN_URL" \
--seed 42 \
--run-id "${CI_PIPELINE_ID}-${CI_JOB_ID}"
script:
- <TEST_COMMAND>
after_script:
- |
wt runs finish \
--twin "$WT_TWIN_URL" \
--run-id "${CI_PIPELINE_ID}-${CI_JOB_ID}" \
--export wondertwin-replay.jsonl.gz || true
- |
if [ -f /tmp/twin.pid ]; then
kill "$(cat /tmp/twin.pid)" 2>/dev/null || true
fi
artifacts:
name: "wondertwin-replay-${CI_PIPELINE_ID}"
paths:
- wondertwin-replay.jsonl.gz
when: always
expire_in: 14 days
  1. Pick your twin and version. Replace <TWIN> (e.g. stripe, twilio), <TWIN_VERSION> (e.g. 0.1.0), and <TWIN_PORT> (twin-stripe: 4111).
  2. Replace <TEST_COMMAND> with your actual test invocation.
  3. Set the license CI/CD variable. In GitLab: Settings → CI/CD → Variables → Add variable. Key: WONDERTWIN_LICENSE. Value: the full contents of the license JSON file (paste it as-is; do not base64-encode). Mark as Masked if your value is short enough; otherwise mark as File and adjust the snippet to read from the path.
  4. Tell your test code where the twin is. The job exports WT_TWIN_URL. Most SDKs let you override the base URL via env var.
  5. Choose a base image. ubuntu:24.04 is the safe default. If your tests need a specific runtime, use node:20, golang:1.22, python:3.12, etc. — and add apt-get install -y curl ca-certificates if those aren’t already in the image.

Add --fixtures to the wt runs start invocation:

- |
wt runs start \
--twin "$WT_TWIN_URL" \
--seed 42 \
--fixtures fixtures/stripe-snapshot.json \
--run-id "${CI_PIPELINE_ID}-${CI_JOB_ID}"

Without an explicit run lifecycle (using wt runs wrap)

Section titled “Without an explicit run lifecycle (using wt runs wrap)”

Replace the script block with a single wrap call. before_script still installs wt and starts the twin; after_script only kills the twin process:

script:
- |
wt runs wrap \
--twin "$WT_TWIN_URL" \
--seed 42 \
--run-id "${CI_PIPELINE_ID}-${CI_JOB_ID}" \
--export wondertwin-replay.jsonl.gz \
-- <TEST_COMMAND>

wrap always finishes the run on exit. Cleaner for simple test commands.

Use a service container instead of wt up &

Section titled “Use a service container instead of wt up &”

GitLab CI supports services: for sidecar containers. If you publish the twin as a Docker image:

test-with-twin:
image: ubuntu:24.04
services:
- name: wondertwin/twin-<TWIN>:<TWIN_VERSION>
alias: twin
variables:
WT_TWIN_URL: "http://twin:<TWIN_PORT>"

This is cleaner if you have a stable container registry path for the twin. See the [twin docker docs] for image availability — not all twins ship as containers today.

before_script:
- curl -sSL "https://github.com/wondertwin-ai/wondertwin/releases/download/v0.4.2/wt-linux-amd64" -o /usr/local/bin/wt
- chmod +x /usr/local/bin/wt
- wt install stripe@0.1.0

My replay artifact is missing on failed jobs

Section titled “My replay artifact is missing on failed jobs”

GitLab CI’s artifacts.when: always ensures the artifact uploads regardless of the job’s exit status. If you don’t see it, confirm the after_script block’s wt runs finish --export actually ran — check the job log for the export line. The || true after the export prevents the after_script from failing if the run was already finished elsewhere.

before_script background process gets killed prematurely

Section titled “before_script background process gets killed prematurely”

GitLab CI runners terminate background processes when the job ends. The after_script block is the last to run; the twin survives until that block kills it. If you see the twin dying before script: finishes, check that you’re not running with --clean-shell or similar runner option that aggressively cleans up.

The health check loop is bounded at 6 seconds. If the twin doesn’t come up, the loop exits and the next step’s wt runs start fails — surfacing a clear error. If the loop appears to hang, your runner may have a different seq semantics; replace with for i in 1 2 3 ... 30; explicitly or use a counter.

See the GitHub Actions guide’s troubleshooting — the causes are platform-agnostic.