feat(cli): write a GitHub Actions or GitLab CI/CD pipeline in e2e init

KA
4h ago
+2257-30

e2e init gets a CI step. In a project with the web or mobile engine it asks which CI service to set up, then writes one job for each platform the config's targets run on: web, iOS, and Android.

◆  Add a CI pipeline that runs the tests?
│  ○ GitHub Actions
│  ● GitLab CI/CD (in every merge request and on the default branch)
│  ○ None
Targets in the configJobs
web onlyweb, running every target
iOS and/or Android onlyios and/or android
web and mobileall of them, each with --target <its targets>
  • --ci github-actions|gitlab|none answers the question. --yes alone writes none, since a pipeline runs on the next push. The wizard preselects the service the repository points to: its remote, a .github/ folder, or a .gitlab-ci.yml.
  • Targets come from the new config init writes, or from loading an existing one the way e2e run does (loadConfigModule, and resolvePlatform/targetName from config/targets.ts). The load happens after the user confirms and after the install, so the config never runs on a cancelled wizard and an example's engine is installed first. A config that still doesn't load falls back to package.json: web engine only, one web job runs every target; mobile engine, no pipeline.
  • Web job: the project's package manager (npm, pnpm, Yarn 1, Yarn 2+, Bun), Chromium with a cache, e2e run --reporter list,junit. It runs every target that isn't iOS or Android, HTTP tests included.
  • Mobile jobs on GitHub follow e2e-inc/e2e-react-native's workflow: macos-26 with a Release simulator build, the image's own iPhone 17 Pro on a pinned iOS runtime, and E2E_DEVICE set to its UDID; ubuntu-latest with KVM, an x86_64 release APK, and an API 36 emulator, with a system-image install that retries a download that drops or comes back empty.
  • The app build: Expo (expo run:ios, expo prebuild + Gradle, no prebuild when android/ is committed), bare React Native (xcodebuild on ios/*.xcworkspace, through Bundler with a Gemfile; android/gradlew), or a step that fails until replaced, which init names.
  • GitLab's hosted runners can't run a simulator or an emulator, so its mobile jobs target self-managed shell runners with tags to replace, as on docs/ci/gitlab.mdx; init says so, and asks for device: process.env.E2E_DEVICE when the config lacks it.
  • Node comes from .nvmrc or .node-version, else 24. A project in a subfolder or a workspace member gets the file at the repository root, the install at the workspace root, and the jobs in the project folder.
  • Model key: GitHub passes the repository secret in the run step's env; GitLab gets CI/CD variables on its own. init reads the gateway from the config's import and prints the gh secret set or glab variable set command.
  • init never overwrites a pipeline file. Beside an existing .gitlab-ci.yml it writes .gitlab/e2e.gitlab-ci.yml and prints the include: to add, renaming the jobs with -run when the pipeline has one of their names and saying when its stages: leave out test. A workflow or pipeline that already runs e2e run gets nothing. Nothing is written through a symlinked .github or .gitlab.
  • Warnings for what would fail on the first push: no app.command (web job only), a subscription gateway or a loopback model endpoint, a build step to replace, GitLab mobile runners.
  • The pipelines are YAML templates in packages/e2e/templates/ci/, each a complete pipeline for npm at the repository root and an Expo app, which actionlint and glab ci lint accept as is. init edits a copy with the yaml package's Document API (new runtime dependency, pure JS), keeping comments and formatting; a platform with no targets loses its job. For the template's own case the output equals the template byte for byte.
  • GitHub action pins live only in the templates and match the docs and the e2e skill; tests/unit/init-ci.test.ts fails when they drift.
  • Telemetry: e2e_init_completed gets a ci property.
flowchart TD
  A[web or mobile engine in package.json?] -- no --> N[no question]
  A -- yes --> B{e2e run already in a workflow or .gitlab-ci.yml?}
  B -- yes --> N
  B -- no --> C[ask, preselect from remote / .github / .gitlab-ci.yml]
  C --> D[confirm changes, install]
  D --> E[targets: new config, or load the existing one]
  E --> F[web job for non-phone targets, ios and android jobs for phone targets]
  F --> G[.github/workflows/e2e.yml, .gitlab-ci.yml, or .gitlab/e2e.gitlab-ci.yml to include]

Verified

Ran it locally: yes. The built CLI wrote the pipelines into real repos, and each ran unedited on hosted runners:

CaseWhereResult
GitHub, web + iOS + Android, Expo, existing config with three targetse2e-react-native#5, its own workflow replaced by the generated oneRN_RESULT
GitHub Actions, npm, repo roote2e-github-actions#2, merged5/5, agent tests included
GitHub Actions, pnpm in site/, .nvmrc at the roote2e-github-actions#35/5
GitHub Actions, pnpm version from site/package.jsone2e-github-actions#5passed
GitHub, web job from the YAML templatee2e-github-actions#65/5, cache replayed
GitLab, npm, new .gitlab-ci.ymle2e-gitlab-ci!1, merged5/5 in the MR test report
GitLab, beside an existing .gitlab-ci.yml via include:e2e-gitlab-ci!25/5
GitLab, pnpm via corepack on node:26e2e-gitlab-ci!55/5
GitLab, web job from the YAML templatee2e-gitlab-ci!65/5
  • actionlint and glab ci lint pass on every combination rendered: web only, mobile only, both, each build kind, npm/pnpm/Yarn/Bun, a subfolder, and a workspace member. The GitLab file for e2e-react-native passes glab ci lint too.
  • Packed tarball in a scratch project, and the interactive wizard in a pseudo-terminal: the question above, with the service preselected from the remote.
  • Not run on a runner: the bare React Native build, and GitLab's mobile jobs (gitlab.com's hosted runners can't run them).

Two fresh-context reviews found issues, all fixed: the first nine in the web-only version, then eight in the mobile one (the config loaded before the install, so an example got no pipeline; the GitLab header lost with the web job; and smaller ones). cubic's findings are fixed or answered in their threads, and kdy1's suggestion to keep the pipelines as YAML templates is in.

Tracked in ENG-1084.