Checkpoint Lab — CI/CD Integration with GitHub Actions, GitLab CI, and Jenkins
Prove a complete CI/CD operating contract locally and with one concrete GitHub Actions job: force a Robot failure, retain evidence while the gate stays non-zero, repair the expectation, and verify a clean run with a version manifest and provider mappings for GitLab and Jenkins.
Learning objectives
- Build and predict the state transitions of a local CI simulation before running it.
- Demonstrate that an intentional Robot failure produces a non-zero process status and retains native/xUnit evidence.
- Run the repaired contract to a clean gate without deleting or overwriting the original failed evidence.
- Create a current GitHub Actions job and map its semantics concisely to GitLab CI/CD and Jenkins.
- Deliver an evidence packet and a short operator runbook that another engineer can reproduce.
Use only the synthetic suite in this lesson. Keep the fake token fake. Do not point the exercise at a browser cloud, database, SSH server, API, deployment environment, production repository secret, or real customer system.
1. Checkpoint architecture
flowchart TD A[Local or CI trigger] --> B[requirements-ci.txt] B --> C[tools/ci_run.py] C --> D[Robot 7.4.2 suite] D --> E[exit code] D --> F[output.xml / log / report / xUnit] C --> G[runtime manifest + exit-code.txt] E --> H[Gate] F --> I[Immutable run evidence] G --> I J[Fake provider secret] -. presence only .-> C K[GitHub Actions wrapper] -. same command .-> C L[GitLab / Jenkins mapping] -. same contract .-> C
The checkpoint has one intentional source of failure:
EXPECTED_GATE. Everything else should be deterministic.
Failed-run evidence and repaired-run evidence must be stored in
different directories or copied before the second execution so the
repair cannot overwrite the diagnosis.
2. Preflight and exact assumptions
| Component | Checkpoint baseline | Reason |
|---|---|---|
| Robot Framework | 7.4.2 | Stable course baseline |
| Python | 3.13 example runtime | Same interpreter generation in local/GitHub/GitLab examples; re-check your environment |
| Pabot | not required; 5.2.2 optional | Parallelism is outside the mandatory checkpoint |
| GitHub Actions | checkout/setup-python/upload-artifact major v7 | Current action generation at 2026-08-31 |
| GitLab | current artifacts + JUnit report syntax | Provider mapping only |
| Jenkins | Declarative Pipeline + core archiveArtifacts + JUnit step | Provider mapping only |
| External SUT | none | Synthetic local gate only |
python --version
python -m pip --version
python -m robot --version
python -c "from pathlib import Path; print('repo=', Path.cwd())"
3. Build the checkpoint files
Use the same requirements-ci.txt,
suites/ci_contract.robot, and
tools/ci_run.py from Lesson 2. Add one tiny operator
note:
# RUNBOOK.md
1. Run from repository root with the pinned virtual environment.
2. Never use a real credential; RF_FAKE_TOKEN is lab-only.
3. FAIL exercise: RF_EXPECTED_GATE=FAIL -> expect non-zero.
4. Copy artifacts/robot to artifacts/evidence-fail before repair.
5. PASS exercise: unset RF_EXPECTED_GATE -> expect zero.
6. Copy artifacts/robot to artifacts/evidence-pass.
7. Compare exit-code.txt, output.xml, xunit.xml, and runtime-manifest.txt.
8. Do not delete the failed packet until review is complete.
4. Predict before execution
Write down at least these two predictions before running anything:
| Prediction | Expected mutation | Independent verification |
|---|---|---|
| Forced failure |
Robot test
Release Gate Matches Expected State becomes
FAIL; process exits non-zero
|
shell status + exit-code.txt + output.xml/xunit.xml |
| Evidence retention | HTML/XML/manifest files exist even though the gate is closed | filesystem listing and open log.html |
| Fake secret handling | manifest says the variable is present but contains no value | grep/search evidence packet for the fake token value |
| Repair | same source/runtime with default expected PASS exits zero | pass packet + version manifest comparison |
5. Execute the controlled failure and preserve it
# Bash/zsh
export RF_FAKE_TOKEN='fake-training-token'
export RF_EXPECTED_GATE='FAIL'
python tools/ci_run.py
rc=$?
echo "failed-run-exit=$rc"
mkdir -p artifacts/evidence-fail
cp -R artifacts/robot/. artifacts/evidence-fail/
# Windows PowerShell:
# $env:RF_FAKE_TOKEN='fake-training-token'
# $env:RF_EXPECTED_GATE='FAIL'
# python . ools\ci_run.py
# $rc=$LASTEXITCODE
# New-Item -ItemType Directory -Force .rtifacts\evidence-fail | Out-Null
# Copy-Item .rtifacts
obot\* .rtifacts\evidence-fail\ -Recurse -Force
Expected: exit code 1 and a complete failed evidence packet. If the process is zero, stop and repair status propagation before continuing. If the process is non-zero but no Robot evidence exists, distinguish “Robot ran and failed” from “Robot never started/imported.”
6. Verify the failed packet independently
python -c "from pathlib import Path; p=Path('artifacts/evidence-fail'); print(sorted(x.name for x in p.iterdir()))"
python -c "from pathlib import Path; print(Path('artifacts/evidence-fail/exit-code.txt').read_text().strip())"
# Safe secret-leak check using the known fake training value.
python -c "from pathlib import Path; token='fake-training-token'; hits=[str(p) for p in Path('artifacts/evidence-fail').glob('*') if p.is_file() and token in p.read_text(errors='ignore')]; print('secret_hits=', hits)"
The expected secret_hits list is empty. Open
log.html and confirm the failure reason is the
synthetic mismatch, not a setup/import/environment error.
7. Repair without erasing first-failure evidence
# Bash/zsh
unset RF_EXPECTED_GATE
python tools/ci_run.py
rc=$?
echo "repaired-run-exit=$rc"
mkdir -p artifacts/evidence-pass
cp -R artifacts/robot/. artifacts/evidence-pass/
# PowerShell:
# Remove-Item Env:RF_EXPECTED_GATE -ErrorAction SilentlyContinue
# python . ools\ci_run.py
# $rc=$LASTEXITCODE
# New-Item -ItemType Directory -Force .rtifacts\evidence-pass | Out-Null
# Copy-Item .rtifacts
obot\* .rtifacts\evidence-pass\ -Recurse -Force
Expected repaired status: 0. Compare the two manifests
to ensure the interpreter/platform stayed the same. The important
change should be the controlled expectation and resulting Robot
status—not an unexplained dependency or target change.
8. Concrete provider job: GitHub Actions
name: robot-checkpoint
on:
workflow_dispatch:
pull_request:
permissions:
contents: read
jobs:
robot:
runs-on: ubuntu-latest
env:
RF_FAKE_TOKEN: ${{ secrets.RF_FAKE_TOKEN }}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: '3.13'
- name: Install
run: python -m pip install --requirement requirements-ci.txt
- name: Robot gate
run: python tools/ci_run.py
- name: Retain evidence on every outcome
if: always()
uses: actions/upload-artifact@v7
with:
name: robot-${{ github.run_id }}-${{ github.job }}
path: artifacts/robot/
if-no-files-found: warn
For a failure-path proof in a disposable branch, temporarily set a
non-secret environment input such as
RF_EXPECTED_GATE=FAIL at the job level, run once,
preserve the artifact, then remove that injection and rerun. Do not
use a real credential or modify production branch protection merely
to demonstrate failure.
9. Concise mappings for GitLab CI/CD and Jenkins
| Invariant | GitLab CI/CD | Jenkins |
|---|---|---|
| Same command | script: python tools/ci_run.py |
sh 'python tools/ci_run.py' (or Windows
equivalent)
|
| Failure remains non-zero | default script semantics | shell step fails stage on non-zero |
| Artifacts after failure | artifacts: when: always |
post { always { archiveArtifacts ... } } |
| xUnit surface | artifacts:reports:junit |
junit Pipeline step |
| Fake secret | lab CI/CD variable | lab credential binding or literal simulation only |
| Run identity | pipeline/job variables in artifact naming | build number/job identity in archive context |
10. Required evidence packet
- The exact provider-neutral command and dependency manifest.
- Python/Robot version manifest and repository/run identity.
-
Failed run process exit code and preserved
output.xml,log.html,report.html,xunit.xml. - Proof that the fake secret value is absent from evidence.
- Repaired run with exit code 0 and comparable version manifest.
- One concrete GitHub Actions job plus concise GitLab/Jenkins mapping.
-
The operator
RUNBOOK.mddescribing failure, preservation, repair, and cleanup.
11. Cleanup and rollback
After review, delete only the disposable lab directory or its
generated artifacts/ subtree. If you created a provider
secret containing the fake token, remove that lab secret. Do not
delete repository history, real CI credentials, or unrelated run
artifacts. If the exercise was performed on a branch, revert only
the intentional failure injection and keep the useful
provider-neutral runner if it is being adopted.
12. Verification checklist
-
☐ Exactly the same
python tools/ci_run.pycommand works locally and in the concrete CI job. - ☐ Forced Robot failure closes the gate with a non-zero status.
- ☐ Failed-run evidence remains available and is not overwritten by the repaired run.
-
☐
output.xml, log/report, xUnit, runtime manifest, and exit code are present where expected. - ☐ No real credential was used; fake value is absent from retained logs/artifacts.
- ☐ Repaired run exits zero without changing unrelated versions or target state.
- ☐ GitLab/Jenkins mappings preserve the same status/evidence contract.
Knowledge check
The forced-failure run exits 0 but xUnit clearly shows a failed Robot test. What must be repaired before this checkpoint can pass?
The status propagation path. Inspect the wrapper, shell operators, --nostatusrc usage, and CI step semantics. A correct artifact cannot compensate for a false-green gate.
Why copy the failed packet before executing the repaired run?
The default artifact directory is reused locally. Preserving the first packet prevents the repaired run from overwriting the evidence needed to explain the original failure.
GitHub uploads artifacts with always(). Does that
make the job successful?
No. The upload step is a finalization path; the earlier Robot step remains failed and the job should remain failed unless some separate configuration explicitly changes status.
Which file is the richest Robot-native source for later post-processing: xUnit or output.xml?
output.xml. xUnit is a generic CI derivative designed for broad test-report compatibility.
A team proposes both a 10-job matrix and
pabot --processes 8 immediately. What should the
runbook require first?
Evidence of isolated mutable resources and a measured bottleneck. Otherwise the design can create 80 concurrent Robot executors, contention, and artifact collisions without proving a benefit.
Chapter 25 completion and bridge to Chapter 26
This chapter adds a production CI/CD operating contract to the Robot Framework model: reproducible command, pinned runtime, explicit target/input boundaries, authentic process status, provider-independent evidence, secret minimization, and failure-path artifact retention. Chapter 26 builds on that contract by moving the runtime into containers and examining image provenance, filesystem mounts, users, networking, result volumes, and distributed services without assuming containers automatically create isolation.
Current primary references
- Robot Framework 7.4.2 User Guide — execution, return codes, output files, xUnit, selection and result semantics.
- Robot Framework releases — current stable/prerelease status.
- Pabot 5.2.2 and Pabot documentation — optional parallel execution.
- actions/checkout, actions/setup-python, and actions/upload-artifact — current GitHub Actions examples.
- GitLab CI/CD YAML reference and unit test reports.
- Jenkins recording tests and artifacts, Pipeline syntax, and the JUnit step.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.