Skip to content

CI Integration

In the other ddakit bases, the last operational seat is distribution. e2e-base has no app to ship, so CI integration takes that seat. implement-ci-workflow reads ci and targets from e2e-suite-config.json and generates .github/workflows/e2e.yml. If ci.provider is none, nothing is generated. Only the jobs for tracks present in targets go in; absent tracks get no job.

Tests split into several shards and run in parallel, and once each shard uploads a blob report, a separate job merges them into one.

jobs:
web:
strategy:
fail-fast: false
matrix: { shardIndex: [1,2,3,4], shardTotal: [4] }
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci
- run: npx playwright install --with-deps # not cached
- run: npx playwright test --project=chromium --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
- uses: actions/upload-artifact@v4
if: always()
with: { name: blob-report-${{ matrix.shardIndex }}, path: blob-report, retention-days: 1 }
merge-reports:
needs: [web]
if: always()
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci
- uses: actions/download-artifact@v4
with: { path: all-blob-reports, pattern: blob-report-* }
- run: npx playwright merge-reports --reporter html ./all-blob-reports
- uses: actions/upload-artifact@v4
with: { name: html-report, path: playwright-report, retention-days: 14 }

With fail-fast: false, one broken shard does not stop the rest from running to the end. Each shard’s blob is kept for one day, and the merged HTML report for 14.

Every job runs npx playwright install --with-deps. Caching looks faster, but when the Playwright version bumps, the cached binary and the version drift apart, and the OS-level dependencies --with-deps installs are not captured in the cache, so things break when the runner image changes. Installing every time is what stays clear of the forbidden pattern (§5).

electron runs on three OSes. Only the Linux runner lacks a display, so it gets wrapped in xvfb-run; mac and windows run as is.

electron:
strategy: { fail-fast: false, matrix: { os: [ubuntu-latest, windows-latest, macos-latest] } }
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci
- run: npx playwright install --with-deps
- name: e2e (Linux)
if: runner.os == 'Linux'
run: xvfb-run --auto-servernum -- npx playwright test --project=electron
- name: e2e (mac/win)
if: runner.os != 'Linux'
run: npx playwright test --project=electron

A runner.os branch runs a different command for Linux only, inside the same job. That electron is single-instance and so the config is workers: 1 was covered in tracks.

Android brings up an emulator with reactivecircus/android-emulator-runner. KVM has to be enabled for acceleration, and AVD caching cuts boot time.

mobile-android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules && sudo udevadm trigger --name-match=kvm
- uses: reactivecircus/android-emulator-runner@v2
with: { api-level: 30, arch: x86_64, force-avd-creation: false,
script: maestro test tests/mobile/flows } # or detox test

iOS brings up a simulator with xcrun simctl on a macos-latest runner. For a release gate that needs real-device verification or parallel scale-out, use Maestro Cloud (mobile-dev-inc/action-maestro-cloud) or a device farm. The cloud farm’s api-key and project-id go in only through ${{ secrets.* }}.

Secrets pass only through ${{ secrets.* }} and real keys are never hardcoded (S2). Watch that tokens and PII do not get captured in artifacts (S4). The SUT the workflow env points at is staging or dedicated test only, and production is banned (S6).

updates comes next. /update-from-base, which safely pulls new skills and hooks into a fork when the base ships them, and /adopt, which lays the pattern onto an app repo that already has some e2e.