This episode covers integrating Selenium tests into CI/CD: GitHub Actions, GitLab CI, Jenkins, and Azure Pipelines, headless execution in pipelines, test reporting, artifacts, and failure feedback, plus strategies for handling flaky tests in CI.

Tests that only run on your laptop aren't tests — they're stories. Episode 11 brings your Selenium suite into CI/CD, where tests run automatically on every code change, giving fast feedback when something breaks. We'll integrate with GitHub Actions, GitLab CI, Jenkins, and Azure Pipelines.
CI has its own challenges for browser automation: no monitor, clean environments, and limited resources. This episode answers them with headless execution, artifact management, and strategies for handling tests that often fail in the pipeline but pass locally.
On servers without a display, browsers must run in headless mode. Chrome and Firefox support this option:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
opsi = Options()
opsi.add_argument("--headless")
opsi.add_argument("--no-sandbox")
opsi.add_argument("--disable-dev-shm-usage")
driver = webdriver.Chrome(options=opsi)opsi.add_argument("--headless") runs Chrome without an interface. The next two arguments — --no-sandbox and --disable-dev-shm-usage — are common requirements in Linux containers to keep Chrome from crashing.
CI runs tests on a fresh machine every time. Make sure tests don't depend on local state: use test data prepared up front, and never assume folders or files from a developer's machine. All dependencies must be declared and reinstalled.
Save the workflow at .github/workflows/e2e.yml:
name: E2E
on:
push:
branches: [main]
pull_request:
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install -r requirements.txt
- name: Jalankan test
run: pytest tests -n 4 --maxfail=1
- name: Upload artifacts
uses: actions/upload-artifact@v4
if: failure()
with:
name: e2e-artifacts
path: artifacts/The E2E workflow above installs dependencies, runs tests with pytest tests -n 4 --maxfail=1, then uploads the artifacts folder only on failure — exactly the screenshot folder from episode 7.
GitLab CI uses .gitlab-ci.yml with runners that execute jobs:
e2e:
image: python:3.12
script:
- pip install -r requirements.txt
- pytest tests --maxfail=1
artifacts:
when: on_failure
paths:
- artifacts/The e2e job above uses the python:3.12 image, runs pytest, and stores artifacts/ on failure. The artifact concept is the same across all CI platforms.
Jenkins uses a Groovy-based Jenkinsfile. The principle is identical — install dependencies, run pytest, archive reports. For Jenkins, also make sure to use images tagged with Chrome and Firefox pre-installed, or run the grid as a separate service.
A good report tells three things: which test failed, why it failed, and what the evidence is. In episode 7 you already saved screenshots and HTML snapshots. In CI, make sure those reports become artifacts and are visible in the job summary.
Generate reports in formats pipelines can read:
pip install pytest-html
pytest tests --junitxml=report.xml --html=report.htmlpytest tests --junitxml=report.xml --html=report.html produces a standard JUnit XML report that every CI understands, plus an HTML report that's comfortable for humans to open. Both are uploaded as artifacts.
A flaky test is one that sometimes passes and sometimes fails without code changes. First strategy: don't rush to delete the test — invest time to find the root cause. If time is limited, use a temporary retry at the CI level:
- name: Jalankan test
run: pytest tests --maxfail=1
retry: 3The retry: 3 annotation makes GitHub Actions retry a failed job. This is temporary handling — episode 13 will cover eradicating flakiness at its root.
Set --maxfail=1 so the pipeline stops at the first failure, and run fast test subsets first. The principle: the faster the feedback, the faster the team fixes — and the lower the cost of the fix.
Info
Store failure screenshots as CI artifacts, not in the repository. A repository flooded with binary files that change on every run will make git history hard to read and clones slow.
Episode 11 turns your tests into a self-working system: integration with GitHub Actions, GitLab CI, Jenkins, and Azure Pipelines, headless execution in containers, reporting and artifacts for failure feedback, plus early tricks for handling flaky tests.
Key takeaways:
In episode 12 next, we'll cover browser and network security — automating secure login flows, handling 2FA, SSO, and OAuth, testing accessibility and secure content, and capturing console logs and network errors from the browser.