Chapter 25Lesson 03180–240 min

CI/CD Integration with GitHub Actions, GitLab CI, and Jenkins: Configuration, Design Patterns, and Trade-Offs

Evaluate CI/CD design choices by the state they change: runtime reproducibility, evidence quality, secret exposure, execution isolation, parallel contention, and the difference between native Robot results and provider-facing derivatives.

Design trade-offsCachingContainersPabot 5.2.2xUnitEnvironment promotion

Learning objectives

  • Choose between one portable runner and provider-specific scripts using observable maintenance and failure behavior.
  • Decide when dependency caching is worth the stale-state risk and how to make cache keys depend on dependency inputs.
  • Separate host-runner and container-runner reproducibility from the actual SUT/network topology.
  • Introduce Pabot or CI matrices only when resource ownership and artifact naming are isolated.
  • Use native Robot HTML/XML and xUnit for their intended audiences without losing the authoritative result evidence.
Decision rule

Prefer the simplest design that preserves the same source, command, dependency set, target contract, exit semantics, and evidence locally and in CI. Add provider features only when they solve a measured operational problem.

1. One portable command versus provider-specific scripts

A provider-neutral runner centralizes Robot options, output paths, version manifest generation, and status propagation. This reduces drift when several CI systems are used or when engineers reproduce a failure locally. Provider-specific scripts can still be justified when operating systems or platform facilities differ materially, but they should delegate to a common core rather than fork the testing rules.

Choice Benefit Cost / failure mode Preferred use
One Python runner Cross-platform, locally reproducible, one status/evidence contract Wrapper becomes a small maintained component Default
Separate Bash/PowerShell/Groovy/YAML logic Can exploit platform-native features Selection/options drift; quoting differences; harder reproduction Thin orchestration only
Direct robot command everywhere Minimal indirection Harder to record manifests and controlled post-run metadata consistently Small repositories with very simple needs

2. Dependency cache versus clean install

A cache changes installation state, not Robot semantics. A healthy cache key is derived from the operating system/interpreter and dependency lock or requirements hash. A stale or overly broad cache can make two runners with identical source execute different dependency bytes.

Use a clean install first while stabilizing a new suite. Add caching only after measuring install time. Never cache generated Robot result directories as dependency caches; result artifacts and dependency caches have different lifecycles and trust models.

Good cache identity inputs
  OS / architecture
  Python major.minor (or exact interpreter policy)
  hash(requirements-ci.txt or lock file)

Do not key only on
  branch name
  "latest"
  a manually chosen cache label that survives dependency changes

3. Container job versus host runner

A container image can pin userland packages and Python more tightly, but it introduces image provenance, image pull, filesystem mounts, UID/GID, DNS, and network boundaries. “localhost” inside a job container refers to that container, not automatically to the CI host or another service. Chapter 26 treats containers in depth; here the decision is simply whether a container materially improves the Robot run contract.

Host runner Container job
Fewer networking layers; easier access to host tools More reproducible userland if image is pinned
More vulnerable to runner drift Image/tag/digest becomes another dependency
Self-hosted machine state may leak between jobs Workspace mounts and service DNS must be explicit
Good for small local/free learning path Good when system dependencies must be controlled

4. Fail fast versus always upload artifacts

These are not opposites. The test command should fail immediately when its contract fails, while a separate finalization path should still upload evidence. In GitHub Actions this is typically an artifact step with if: always(); in GitLab, artifacts: when: always; in Jenkins Declarative Pipeline, a stage or pipeline post { always { ... } } block.

Do not wrap the Robot command in a success-forcing expression merely to reach the upload step when the platform already has a failure-finalization primitive. That turns “retain evidence” into “ignore gate status.”

5. Serial execution, Pabot, and CI matrices

There are two independent concurrency layers: Pabot can create multiple Robot subprocesses inside one CI job, while the CI provider can create multiple jobs/matrix entries. Stacking both multiplies contention and artifact volume.

Approach State boundary When justified Primary risk
Serial Robot One process/workspace Default, small or stateful suites Longer runtime
Pabot --processes N Multiple Robot subprocesses in one job Suites/tests own isolated mutable state Shared files/ports/accounts, merged-artifact collisions
CI matrix/shards Multiple provider workspaces/jobs Independent environment variants or coarse shards Duplicate setup cost, shared SUT contention
Matrix + Pabot Both layers Large, measured, strongly isolated workloads Oversubscription and difficult diagnostics
# Optional measured replacement for the serial command.
# Keep Robot output paths/job identity isolated before enabling this.
pabot --processes 4 --outputdir artifacts/robot suites/

Pabot 5.2.2 is the current stable baseline used here. Its default split is suite level; --testlevelsplit changes granularity. Do not add it simply because a CI runner has spare CPUs.

6. xUnit integration versus native Robot evidence

xUnit gives GitLab/Jenkins and many generic test tools a familiar test-report format. Robot's output.xml remains the richer machine-readable source, while log.html is usually the fastest way for a human to investigate keyword-level detail. Publish both when the provider can render xUnit: the derivative improves discoverability without replacing the source-of-truth evidence.

Artifact Audience Strength Do not assume
output.xml Robot-aware tooling Rich execution model; Rebot input Provider will render it natively
log.html Engineer/operator Detailed human diagnosis Machine dashboards can parse it
report.html Engineer/reviewer High-level summary Contains every keyword detail
xunit.xml Generic CI test UI Portable test case status Represents all Robot-specific metadata

7. Environment promotion versus hard-coded URLs

Do not encode staging or production endpoints in Robot keywords. The suite should consume an explicit environment identifier or target URL from a controlled configuration layer, then validate it against an allowlist before mutation. The CI provider chooses which approved environment is targeted; the repository-owned test architecture defines how that target is validated and tested.

Promotion should carry the same versioned suite and runner contract forward. Changing tests, dependencies, and environment at the same time destroys the ability to attribute a failure.

8. Configuration boundaries

Configuration Owner Example
Robot core Repository run contract --outputdir, tags/profile, xUnit option
External Robot libraries requirements/lock file RequestsLibrary, Browser, DatabaseLibrary versions
Python environment runner/image Python 3.13, pip, virtualenv/image digest
SUT/test environment environment platform base URL, disposable DB, fixture IDs
RobotCode/editor developer tooling local analysis/profile settings; not a CI gate dependency unless explicitly chosen
CI provider workflow YAML/Jenkinsfile trigger, runner, permissions, secrets, artifacts, cache
Container/cloud orchestration later platform layer image, network, service identity, volume

9. Worked design scenario

A 12-minute serial suite has 60 independent API tests. Installation costs 20 seconds. Four CI runners are available, but all tests currently write to the same tenant. The correct first optimization is not enabling an eight-process Pabot run. First make tenant/test-data ownership independent. After isolation, measure whether one Pabot job, four CI shards, or a hybrid provides the best runtime/evidence trade-off. The optimization target is trustworthy wall-clock time, not maximum concurrency.

Knowledge check

Why can a dependency cache make a reproducible suite non-reproducible?

Does post { always { ... } } in Jenkins mean the Robot stage should be forced to return zero?

When would a CI matrix be preferable to Pabot?

Why should xUnit and output.xml both be kept?

Summary and bridge

The baseline stays intentionally boring: pinned runtime, one command, explicit target, true exit status, always-retained evidence. Caches, containers, parallelism, matrices, and provider report features are optimizations around that contract. Lesson 4 shows how the contract fails in production and how to diagnose the first broken layer without hiding it.

Next lesson

CI/CD Integration with GitHub Actions, GitLab CI, and Jenkins: Diagnostics, Failure Modes, and Production Practices

Continue with CI/CD Integration with GitHub Actions, GitLab CI, and Jenkins: Diagnostics, Failure Modes, and Production Practices. It builds directly on the state, evidence, and operating assumptions established here, so carry those constraints forward rather than treating the next page as an isolated topic.

Current primary references

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.