Skip to the content

Running from CI

After your CI builds the app, Trailcam uploads the build, runs your flows on it and waits for the results. A failing flow fails the job, and on GitHub, the pull request shows each flow's result.

The project's CI tab in Trailcam has a step-by-step guide for GitHub Actions, GitLab, Bitrise, CircleCI, Codemagic, Xcode Cloud and EAS Build, with your project's ID filled in. This page explains what those guides do.

GitHub Actions

Link the repository on the project's CI tab first. Then add the Trailcam action after the step that builds your app:

name: Trailcam
on:
  pull_request:
  push:
    branches: [main]
permissions:
  contents: read
  id-token: write # Signs in with GitHub: no key to keep, once the repository is linked.
jobs:
  android:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v7
      - run: ./gradlew assembleDebug
      - uses: trailcam/action@v1
        with:
          project: <your project's ID>
          build: app/build/outputs/apk/debug/app-debug.apk

With id-token: write, the action signs in with GitHub's own short-lived token for the run, so there's no key to store. Pull requests from forks get no token, so they can't run flows.

iOS

An iOS simulator build needs a Mac, but waiting for results doesn't. GitHub bills a macOS runner's minutes at several times a Linux one's, so build on macOS and hand the app to a Linux job. Zip it with ditto, which keeps the app's permissions that artifacts would otherwise drop:

jobs:
  ios-build:
    runs-on: macos-26
    steps:
      - uses: actions/checkout@v7
      - run: xcodebuild build -scheme MyApp -configuration Debug -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -derivedDataPath build CODE_SIGNING_ALLOWED=NO
      - run: ditto -c -k --keepParent build/Build/Products/Debug-iphonesimulator/MyApp.app MyApp.zip
      - uses: actions/upload-artifact@v7
        with:
          name: ios-build
          path: MyApp.zip
          retention-days: 1
  ios:
    needs: ios-build
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/download-artifact@v8
        with:
          name: ios-build
      - uses: trailcam/action@v1
        with:
          project: <your project's ID>
          build: MyApp.zip

More devices

Each build runs on its platform's default device, an iPhone 17 or a Pixel 8, unless the action names others. Every flow runs once on each:

- uses: trailcam/action@v1
  with:
    project: <your project's ID>
    build: app/build/outputs/apk/debug/app-debug.apk
    devices: |
      pixel-8
      model=pixel-8,os=11
      pixel-tablet

The devices table lists them all.

Inputs and outputs

InputWhat it is
projectRequired. The project's ID, from its CI tab.
buildRequired. The builds to run on, one per line: an emulator APK, or an iOS simulator build (the .app folder, or a zip).
flowsFlows to run, one name per line. Without it, every flow.
devicesDevices to run on, one per line, as --device names them. Without it, each platform's default.
waittrue (the default) waits for the results; false starts the runs and carries on.
timeout-minutesHow long to wait for results (default 30).
keyA CI key, only for a repository that isn't linked. Pass it from a secret.
OutputWhat it is
resultpassed, failed, couldnt_run, stopped, or running (when not waiting).
urlThe page with these results in Trailcam.
results-idThe results' ID, for Trailcam's API.
summaryHow the runs went, in a sentence.

To pin the action to an exact release, use its commit SHA instead of v1; Dependabot and Renovate keep pinned SHAs up to date.

Status checks and comments

With the repository linked, each commit gets a status check per project, "Trailcam · Acme Shop", and each pull request one comment with every flow's result on each device, updated as runs finish.

  • A failing flow fails the check.
  • When Trailcam couldn't run a flow, the check is neutral: it's our problem, not your app's.
  • Stopped runs mark the check as cancelled.
  • Re-run on GitHub runs the same flows on the same builds again.

Other CI services

Everywhere else, use Trailcam's command-line tool. It needs Node 20.12 or later, and reads the commit, branch and pull request from each service's own variables:

npx --yes @trailcam/cli@1 run --project <id> --build app/build/outputs/apk/debug/app-debug.apk

Add your CI key to the service's secrets as TRAILCAM_KEY. Name one build per platform, with --build twice for both. The tool checks each build before uploading it, so a wrong file is explained in seconds.

CI keys

A CI key (tck_…) belongs to one project. It can upload builds to that project, run its flows on the builds it uploaded, and read the results of the runs it started; nothing else. Owners and admins make keys on the project's CI tab. A key is shown once, when it's made, and can be revoked at any time.

How runs from CI count

Each flow on each device is one run, as anywhere else in Trailcam. Flows with errors are skipped and listed in the results instead of running. Runs that couldn't run are free and retried for you. See what counts as a run.