CI/CD Integration with GitHub Actions, GitLab CI, and Jenkins: Core Concepts and Mental Model
Chapter 21 separated runner workers from browser/Grid capacity. Chapter 22 adds the delivery system around them. A CI platform can schedule a Selenium command, provision execution infrastructure, collect artifacts, and enforce a gate—but it does not change what a WebDriver session, locator, wait, or assertion means.
Learning objectives
- Separate CI orchestration from Selenium/WebDriver semantics and AUT behavior.
- Trace a commit through job provisioning, browser/Grid access, test execution, exit status, evidence, and gating.
- Identify the state owned by the runner workspace, browser session, Grid, AUT, CI secret store, and artifact store.
- Inspect versions and returned capabilities before trusting a CI result.
- Explain why a green browser-launch step is not the same as delivery-quality evidence.
1. A browser that launches in CI is not yet a trustworthy gate
Local tests inherit invisible machine state: installed browsers, cached Python packages, environment variables, open ports, user profiles, and files left by previous runs. CI is valuable because it can recreate a controlled environment from source. It becomes dangerous when the workflow silently depends on unrecorded runner state or discards the only evidence explaining a failure.
If a test command exits non-zero but the workflow masks that exit code, the pipeline can be green while the browser test is red. If artifacts are uploaded only after success, a red job may preserve less evidence than a green one. Both are failures of CI semantics, not Selenium syntax.
2. Commit → job → test command → evidence → gate
The following diagram visualizes the relationships described in Commit → job → test command → evidence → gate. Read the nodes in sequence and use the arrows to connect the conceptual state changes to the explanation around the diagram.
flowchart TD C[Commit or change] --> J[CI job] J --> D[Dependencies + browser / Grid] D --> S[Portable Selenium suite command] S --> W[WebDriver session] W --> A[AUT] S --> R[Exit status + result] S --> E[Evidence: capabilities/screenshots/logs] R --> G[Gate / report] E --> G[Gate / triage context]
The CI platform schedules the job and creates a workspace. Dependency setup creates the Python environment. Browser or Grid provisioning creates execution capacity. The same Selenium code should then create its WebDriver session, interact with the AUT, assert outcomes, and quit. The suite process returns a status code. CI interprets that status and separately stores evidence. A provider-specific YAML or Groovy file is therefore orchestration around a portable test contract.
3. Objects, state stores, and trust boundaries
The following table organizes the key choices and evidence for Objects, state stores, and trust boundaries. Use it together with the surrounding prose so the rows serve as a comparison aid rather than standalone rules.
| Layer | State it owns | Must be recorded/controlled | Common confusion |
|---|---|---|---|
| Repository/test command | Test intent, dependencies, configuration schema | Pinned Selenium dependency; one reproducible entry command | Putting CI-only behavior inside page objects |
| CI job/workspace | Checkout, env vars, caches, files, job concurrency | Runner image/label, commit SHA, workspace paths | Treating cache as browser/session state |
| Secret store | Tokens/credentials injected at runtime | Least privilege; never echo values | Committing secrets in YAML |
| Browser/WebDriver session | Capabilities, cookies, windows, DOM references | Session ID, browser/version, teardown | Assuming CI job ID equals browser session ID |
| Grid/service container | Remote session capacity/routing | Grid version, endpoint, slot/queue evidence | Assuming Grid provides test-data isolation |
| AUT/test environment | Application/business state | Base URL, test tenant/data identity | Letting concurrent jobs mutate one shared environment |
| Artifact store | Screenshots, logs, reports, manifests | Unique names, retention, redaction | Uploading only successful-run artifacts |
4. Read-only preflight before the first test mutation
A CI test should start by making invisible state visible. Record Selenium and Python versions, the requested browser lane, the effective base URL, the commit/job identity when available, and—after session creation—the returned capabilities and session ID. Never dump the entire environment because it can contain secrets.
import json
import os
import platform
import selenium
safe_ci = {
"selenium": selenium.__version__,
"python": platform.python_version(),
"browser_request": os.environ.get("LAB_BROWSER", "chrome"),
"base_url": os.environ.get("LAB_BASE_URL", "http://127.0.0.1:8765"),
"github_run_id": os.environ.get("GITHUB_RUN_ID"),
"gitlab_job_id": os.environ.get("CI_JOB_ID"),
"jenkins_build_tag": os.environ.get("BUILD_TAG"),
}
print(json.dumps(safe_ci, indent=2))
Commands such as env, set, or
indiscriminate diagnostic dumps can expose CI secrets. Log an
allow-listed diagnostic manifest instead.
5. Exit status is the gate signal; artifacts are diagnostic context
Keep these channels separate. The test process returns zero only when the selected suite passed according to its assertion semantics. A screenshot or log cannot override a failing assertion. Conversely, artifact upload should normally run even when the test command fails, because the red run is precisely when operators need evidence.
portable command -> exit 0 -> CI job may pass -> gate can be green
portable command -> exit !=0 -> CI job fails -> evidence still uploads
artifact upload -> succeeds -> does not rewrite the test result
6. GitHub Actions, GitLab CI, and Jenkins are different orchestrators around the same contract
GitHub Actions expresses jobs and matrices in workflow YAML. GitLab
CI uses .gitlab-ci.yml, runners, jobs,
parallel:matrix, and artifact policies. Jenkins
commonly uses a versioned Jenkinsfile with agents,
stages, and post conditions. Their syntax differs; the
Selenium suite should not. The portable boundary is: install
dependencies → supply explicit configuration → run one command →
preserve its status → collect evidence.
7. DevOps connection: evidence makes Selenium part of delivery control
A useful gate answers more than “did Chrome open?” It identifies the commit, runner image or agent class, Selenium version, actual browser/version, returned capabilities, selected environment, test result, and artifact bundle. With those facts, a remote operator can distinguish a product regression from a browser-image update, Grid incident, test defect, or CI provisioning problem.
Knowledge check
What is the portable unit that should survive a move from GitHub Actions to Jenkins?
The provider-neutral suite command and its configuration/result/evidence contract—not the workflow YAML syntax.
Why must artifact upload run even when the Selenium command fails?
Because first-failure screenshots, logs, capability manifests, and reports are most valuable on red runs.
Does a Grid service container change Selenium locator or wait semantics?
No. It changes where the WebDriver session executes; test semantics remain Selenium/WebDriver concerns.
Why should a CI job record returned browser capabilities?
Hosted/self-hosted browser state can change. Returned capabilities identify what actually executed the test.
What is wrong with printing every environment variable for diagnostics?
The environment can contain secrets. Diagnostics should use an explicit safe allow-list.
Official references and current-version notes
- Selenium 4.47 release notes
- Selenium downloads — current stable client and Grid versions
- GitHub-hosted runners reference
- GitHub Ubuntu 24.04 runner image inventory
- actions/checkout
- actions/setup-python
- actions/upload-artifact
- GitLab CI job artifacts
- GitLab CI/CD YAML reference
- Jenkins Pipeline tests and artifacts
- Jenkins Declarative Pipeline syntax
The mandatory examples pin Selenium Python to 4.47.0.
The complete GitHub Actions example uses current major action
lines actions/checkout@v7,
actions/setup-python@v7, and
actions/upload-artifact@v7. Hosted runner browser
packages are deliberately not pinned here because runner
images update; every run records the actual browser and returned
WebDriver capabilities.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.