CI Setup
This guide covers running Tapsmith tests in continuous integration environments.
Overview
Section titled “Overview”To run Tapsmith tests in CI, you need:
- Node.js 22+ installed.
- The Tapsmith daemon binary (installed automatically with
npm install tapsmith). - Android: An Android emulator running in headless mode and ADB on the PATH.
- iOS: A macOS runner with Xcode installed (simulators are managed by Tapsmith).
GitHub Actions (Android)
Section titled “GitHub Actions (Android)”Here is a complete GitHub Actions workflow that builds your app, starts an emulator, and runs Tapsmith tests.
name: Mobile Tests
on: push: branches: [main] pull_request: branches: [main]
jobs: test: runs-on: ubuntu-latest timeout-minutes: 30
steps: - uses: actions/checkout@v4
- name: Set up Node.js uses: actions/setup-node@v4 with: node-version: "22" cache: "npm"
- name: Install dependencies run: npm ci
- name: Set up JDK uses: actions/setup-java@v4 with: distribution: "temurin" java-version: "17"
- name: Build APK run: ./gradlew assembleDebug
- 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
- name: Start emulator uses: reactivecircus/android-emulator-runner@v2 with: api-level: 33 arch: x86_64 emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim script: npx tapsmith test
- name: Upload test artifacts if: always() uses: actions/upload-artifact@v4 with: name: tapsmith-results path: tapsmith-results/ retention-days: 14Key Points
Section titled “Key Points”- KVM acceleration is required for acceptable emulator performance on Linux CI runners. The
Enable KVMstep configures this. - The
android-emulator-runneraction handles downloading the system image, creating the AVD, and starting the emulator. Your test command runs in thescriptparameter after the emulator boots. - The
if: always()on the upload step ensures screenshots are uploaded even when tests fail.
GitHub Actions (iOS)
Section titled “GitHub Actions (iOS)”iOS tests require a macOS runner with Xcode installed. Tapsmith manages simulator lifecycle automatically.
name: iOS Tests
on: push: branches: [main] pull_request: branches: [main]
jobs: test: runs-on: macos-latest timeout-minutes: 30
steps: - uses: actions/checkout@v4
- name: Set up Node.js uses: actions/setup-node@v4 with: node-version: "22" cache: "npm"
- name: Install dependencies run: npm ci
# Required for iOS network capture — see "iOS network capture on CI" below. - name: Install mitmproxy (for iOS network capture redirector) if: ${{ true }} # omit this step if you disable network capture in the trace config run: | brew install mitmproxy sudo mitmproxy --mode local:Safari <<< 'q' || true # The redirector System Extension needs pre-approval on self-hosted runners; # GitHub-hosted macOS runners accept new SEs automatically on first launch.
- name: Build app for simulator run: | xcodebuild build-for-testing \ -project MyApp.xcodeproj \ -scheme MyApp \ -destination 'platform=iOS Simulator,name=iPhone 17'
- name: Run Tapsmith tests run: npx tapsmith test
- name: Upload test artifacts if: always() uses: actions/upload-artifact@v4 with: name: tapsmith-results path: tapsmith-results/ retention-days: 14Key Points
Section titled “Key Points”- macOS runner is required for iOS simulators. GitHub provides
macos-latestwith Xcode pre-installed. - Tapsmith boots and manages simulators automatically — no manual
xcrun simctlsetup needed. - Build your app for the iOS Simulator target (not a physical device) using
build-for-testingor your existing build pipeline.
iOS network capture on CI
Section titled “iOS network capture on CI”iOS network capture (trace + network.json in the trace viewer) routes simulator traffic through Tapsmith’s MITM proxy via a macOS Network Extension redirector that is bundled with mitmproxy. You need two one-time prerequisites on the CI runner for this to work:
brew install mitmproxy— Tapsmith discovers the redirector at/Applications/Mitmproxy Redirector.app(mitmproxy’s installer unpacks it there) or via$TAPSMITH_REDIRECTOR_APP. Alternatively, it will extract the redirector from mitmproxy’s brew cask tarball into~/.tapsmith/redirector/on first use.- System Extension approval — the redirector’s Network Extension must be approved on first launch. On GitHub-hosted macOS runners, SEs are accepted automatically on first launch, so no action is needed. On self-hosted runners, run
sudo systemextensionsctl developer ononce to bypass interactive approval, or pre-approve the extension manually.
If your tests do not need network capture, disable it in your tapsmith config (trace: { network: false }) and skip the mitmproxy install step above.
See iOS network capture for the full first-run walkthrough and troubleshooting.
General CI Tips
Section titled “General CI Tips”Emulator Startup
Section titled “Emulator Startup”Android emulators can take 1-3 minutes to boot in CI. Make sure your CI timeout accounts for this. The android-emulator-runner action waits for the emulator to finish booting before running your script.
If you are managing the emulator yourself:
# Create an AVDavdmanager create avd -n test -k "system-images;android-33;google_apis;x86_64" --force
# Start the emulator in the backgroundemulator -avd test -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim &
# Wait for the device to boot completelyadb wait-for-deviceadb shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done'
# Run testsnpx tapsmith testADB Setup
Section titled “ADB Setup”Most CI images with Android tooling already have ADB on the PATH. If yours does not:
export ANDROID_HOME=$HOME/android-sdkexport PATH=$PATH:$ANDROID_HOME/platform-toolsVerify ADB is working before running tests:
adb devices# Should list your emulator, e.g.:# emulator-5554 deviceTimeouts
Section titled “Timeouts”CI emulators are slower than local machines. Increase the default timeout in your CI config:
import { defineConfig } from "tapsmith";
export default defineConfig({ apk: "./app-debug.apk", timeout: 60_000, // 60 seconds for CI retries: 2, // Retry flaky tests});Or use a separate config for CI by checking an environment variable:
import { defineConfig } from "tapsmith";
const isCI = process.env.CI === "true";
export default defineConfig({ apk: "./app-debug.apk", timeout: isCI ? 60_000 : 30_000, retries: isCI ? 2 : 0, screenshot: isCI ? "always" : "only-on-failure",});Screenshot Artifacts
Section titled “Screenshot Artifacts”Set the screenshot option to "always" in CI to capture screenshots for every test. This makes debugging failures much easier when you cannot see the emulator screen.
Screenshots are saved to <outputDir>/screenshots/ (by default tapsmith-results/screenshots/). Upload this directory as a CI artifact so you can download and inspect screenshots after a run.
Each screenshot file is named with the test name and a timestamp:
tapsmith-results/screenshots/ user_can_log_in-1710345600000.png shows_error_on_invalid_credentials-1710345601234.pngTrace Artifacts
Section titled “Trace Artifacts”Enable trace recording in CI to get full step-by-step debugging for failures. The recommended mode is retain-on-failure, which records every test but only keeps the trace archive when a test fails:
npx tapsmith test --trace retain-on-failureUpload traces as CI artifacts alongside screenshots:
- name: Run tests run: npx tapsmith test --trace retain-on-failure
- name: Upload traces if: failure() uses: actions/upload-artifact@v4 with: name: tapsmith-traces path: tapsmith-results/traces/ retention-days: 30After downloading the artifact, open the trace locally:
npx tapsmith show-trace tapsmith-results/traces/trace-login_test.zipOr drop the .zip file onto trace.tapsmith.dev to view it in the browser without installing anything.
Network capture works in CI as well — HTTP/HTTPS requests made by the app are recorded in the trace and visible in the Network tab of the trace viewer. Android runs need no extra setup; iOS runs need brew install mitmproxy and (on self-hosted runners) a pre-approved System Extension — see iOS network capture on CI above.
Caching
Section titled “Caching”Cache the Android SDK and emulator system images to speed up CI runs:
- name: Cache Android SDK uses: actions/cache@v4 with: path: | ~/.android/avd ~/android-sdk key: android-sdk-${{ runner.os }}-api33Parallel Workers
Section titled “Parallel Workers”Tapsmith can run multiple workers in parallel as long as each worker has its own device or emulator instance. The recommended emulator-managed setup is:
import { defineConfig } from "tapsmith";
export default defineConfig({ apk: "./app-debug.apk", package: "com.example.myapp", workers: 4, launchEmulators: true, avd: "Pixel_9_API_35", timeout: 60_000,});With that config, npx tapsmith test will try to launch repeated instances of the
same AVD for all workers.
CI Sharding
Section titled “CI Sharding”If your CI environment cannot support multiple emulator instances on one host,
split the suite across multiple jobs instead. Use --shard=x/y to
deterministically assign test files to each job:
strategy: matrix: shard: [1, 2, 3]
steps: # ... setup steps ... - name: Run tests (shard ${{ matrix.shard }}/3) run: npx tapsmith test --shard=${{ matrix.shard }}/3
- name: Upload blob report if: always() uses: actions/upload-artifact@v4 with: name: blob-report-${{ matrix.shard }} path: blob-report/When --shard is used, Tapsmith automatically adds the blob reporter so results
can be merged after all shards complete.
Merging Sharded Reports
Section titled “Merging Sharded Reports”After all shard jobs finish, download the blob artifacts and merge them into a single HTML report:
merge-reports: needs: test runs-on: ubuntu-latest if: always() steps: - uses: actions/checkout@v4
- name: Download blob reports uses: actions/download-artifact@v4 with: pattern: blob-report-* path: all-blob-reports merge-multiple: true
- name: Merge reports run: npx tapsmith merge-reports all-blob-reports
- name: Upload HTML report uses: actions/upload-artifact@v4 with: name: tapsmith-report path: tapsmith-report/