07.11 — CI#
Three workflows:
main_ci.yml(fast, lint + typecheck),dev_ci.yml(same for dev/feature/fix branches),e2e.yml(manual, Redis service + Firefox).
Overview#
| Workflow | Trigger | Platform | Effect |
|---|---|---|---|
main_ci.yml | push/PR main | ubuntu + windows × py 3.14 | make check-coding-style (mypy + ruff). No browser. |
dev_ci.yml | push dev / feature/_ / fix/_ | ubuntu × py 3.14 | id., but Ubuntu only |
e2e.yml | workflow_dispatch (manual) | ubuntu + Redis service + Firefox + geckodriver | Full run of the e2e suite. Upload .screenshots/, .ocarina_logs/, .reports/. |
main_ci.yml#
name: Main branch CI
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
defaults:
run:
shell: bash
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
python-version: ["3.14"]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with: { python-version: "3.14" }
- uses: actions/cache@v5
with:
path: .venv
key: venv-${{ runner.os }}-py3.14-${{ hashFiles('pyproject.toml') }}
- name: Install
run: make install-on-ci
- name: Check coding style
run: |
. .venv/bin/activate
make check-coding-style- OS matrix: ubuntu + windows — verifies Python portability.
- No browser: just
make check-coding-style(mypy + ruff). Fast, no Selenium / Redis / OTP. .venvcache: reused between runs.
dev_ci.yml#
on:
push:
branches: [dev, "feature/**", "fix/**"]
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
# … identical to main_ci.yml, just Ubuntu only …Identical to main_ci.yml but Ubuntu only (Windows only runs on main).
e2e.yml#
name: e2e CI
on:
workflow_dispatch:
jobs:
e2e:
runs-on: ubuntu-latest
environment: OC # ← GitHub env that holds IGOR_API_KEY
services:
redis:
image: redis:8.8.0
ports: [6379:6379]
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with: { python-version: 3.14 }
- uses: actions/cache@v5
with:
path: .venv
key: venv-${{ runner.os }}-py-${{ hashFiles('pyproject.toml') }}
- name: Install geckodriver
run: |
GECKO_VERSION=0.35.0
wget https://github.com/mozilla/geckodriver/releases/download/v$GECKO_VERSION/geckodriver-v$GECKO_VERSION-linux64.tar.gz
tar -xvzf geckodriver-v$GECKO_VERSION-linux64.tar.gz
chmod +x geckodriver
- name: Setup Firefox
uses: browser-actions/setup-firefox@...
- name: Install project + ocarina
run: |
make install-on-ci
- name: Run e2e cycle
env:
IGOR_API_KEY: ${{ secrets.IGOR_API_KEY }}
DASH_USERNAME: ${{ secrets.DASH_USERNAME }}
DASH_PASSWORD: ${{ secrets.DASH_PASSWORD }}
REDIS_URL: redis://localhost:6379
run: |
. .venv/bin/activate
python -u src/main.py --driver-path ./geckodriver --browser firefox --workers 3
- name: Upload artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-traces
path: |
.screenshots/
.ocarina_logs/
.reports/Flow#
1. services.redis#
services:
redis:
image: redis:8.8.0
ports: [6379:6379]
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
...GitHub Actions spins up a Redis container as a sidecar, reachable at localhost:6379. Health-checked via redis-cli ping.
2. environment: OC#
environment: OCBinds the workflow to a GitHub Environment named OC.
- Holds the secrets (
IGOR_API_KEY,DASH_USERNAME,DASH_PASSWORD). - Can require manual approval before running — useful for quota-consuming workflows.
3. Install geckodriver#
- name: Install geckodriver
run: |
GECKO_VERSION=0.35.0
wget https://github.com/mozilla/geckodriver/releases/download/v$GECKO_VERSION/geckodriver-v$GECKO_VERSION-linux64.tar.gz
tar -xvzf geckodriver-v$GECKO_VERSION-linux64.tar.gz
chmod +x geckodriver4. Setup Firefox#
- uses: browser-actions/setup-firefox@<sha>Installs a Firefox version compatible with geckodriver.
5. Run e2e cycle#
python -u src/main.py --driver-path ./geckodriver --browser firefox --workers 3-u: unbuffered stdout (real-time logs).--workers 3: 3 browsers in parallel.--driver-path ./geckodriver: relative path from the root.--browser firefox.
--logger not specified; defaults to terminal+file.
6. Env vars#
env:
IGOR_API_KEY: ${{ secrets.IGOR_API_KEY }}
DASH_USERNAME: ${{ secrets.DASH_USERNAME }}
DASH_PASSWORD: ${{ secrets.DASH_PASSWORD }}
REDIS_URL: redis://localhost:6379IGOR_API_KEY ≡ API_SECRET (see ../06-tests-workers/). REDIS_URL points at the sidecar.
7. Upload artifacts#
- name: Upload artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-traces
path: |
.screenshots/
.ocarina_logs/
.reports/if: always(): uploads even on failure — so you can analyze why it failed.
.screenshots/: all screenshots..ocarina_logs/: full log tree (campaigns/suites/tests)..reports/: DOCX + JSON plugin output.
workflow_dispatch#
Why only workflow_dispatch?
- Cost: an e2e run takes several minutes, spins up 3 browsers, hundreds of requests to Igoristan and tests-workers.
- Vercel quotas:
tests-workerscalls consume quota. - Stability: e2e runs can fail for reasons outside the project (Vercel down, GitHub Pages down). No appetite for polluting CI on every PR.
Sample timeline#
0:00 Trigger workflow_dispatch
0:00 Lock environment "OC" (1 run at a time)
0:00 Spawn Redis sidecar container
0:00 Checkout
0:30 Setup Python 3.14
0:45 Cache restore .venv (hit or miss)
1:30 pip install requirements + ocarina (if miss)
2:00 Download + install geckodriver 0.35.0
2:30 Setup Firefox
3:00 Warmup Redis client
3:00 ──── E2E cycle starts ────
3:00 Smoke campaigns (global + corsicamon)
4:00 Main campaigns:
- Dashboard login (3 suites, ~10 tests)
- Randomness (1 suite, 6 tests)
- Sacred upload (2 suites, ~4 tests)
- Corsicamon (2 suites, ~6 tests)
8:00 ──── E2E cycle ends ────
8:00 Post-exec plugins (DOCX + JSON in parallel)
8:30 pretty_print_results + sys.exit
8:30 Upload artifacts (screenshots, logs, reports)
9:00 Done.