taqwright

Run in Docker

Skip the local toolchain install — the repo's Docker setup bundles Node 24, Appium 3, JDK, the Android SDK command-line tools, and the UiAutomator2 driver into one image. Useful for reproducible CI and zero-setup onboarding.

Run in Docker (optional)

Skip the local toolchain install — the repo's Docker setup bundles Node 24, Appium 3, JDK 21, the Android SDK command-line tools, and the UiAutomator2 driver into one image. Useful for CI (reproducible runs against a cloud device) and onboarding (contributors who don't want to install Appium / Android Studio locally).

Coverage: Android works fully inside Docker. iOS goes through a cloud provider (BrowserStack / LambdaTest) — XCUITest needs Xcode, which only runs on macOS, so it can't go in a container. Real-device USB passthrough is Linux-only; Mac users connect to emulators via ADB-over-TCP.

The repo ships a Dockerfile and a docker-compose.yml with three profiles. Pick the one that matches what you're doing.

Build the image (one-time)

All three profile services share one image (taqwright:local). Build it once — subsequent up / run commands reuse the cache.

docker compose build appium
Why appium? All three services in docker-compose.yml are profile-gated, so a bare docker compose build sees no services and prints No services to build. Naming any service builds the shared image. Equivalent: docker compose --profile local-android build.

First-time build: 3–6 minutes on Apple Silicon (Rosetta-emulating the amd64 SDK), 2–4 minutes on Linux/Intel. Final image: ~1.3 GB.

What's in the image

Source of truth is the Dockerfile at the repo root. At a glance, the build layers are:

LayerWhat & why~Size
node:24-bookworm Debian-based Node 24 LTS — matches engines.node in package.json. ~250 MB
System tools openjdk-17-jre-headless (Appium's UiAutomator2 driver needs 11+; 17 is what Debian bookworm ships by default) plus Debian's adb package (built for both amd64 and arm64 — Google's own platform-tools is x86_64-only on Linux, so we use Debian's so the image runs natively on Apple Silicon). Plus curl + unzip + ca-certificates for the SDK download. ~210 MB
Android cmdline-tools Downloaded from Google's CDN at build time. Gives you sdkmanager (used at build time to accept SDK licenses). Java-based, so arch-neutral. We skip installing the platform-tools / system-images / emulator via sdkmanager — Debian's adb covers our adb need, and emulators don't work in Docker Desktop anyway. ~150 MB
Appium 3 + UiAutomator2 driver npm install -g appium@^3 then appium driver install uiautomator2. No XCUITest — iOS goes through cloud providers. ~200 MB

Env baked in (no shell setup needed inside the container):

JAVA_HOME=/usr/lib/jvm/java-17-openjdk-{amd64,arm64}   # arch-suffix set by buildx
ANDROID_HOME=/opt/android-sdk
ANDROID_SDK_ROOT=/opt/android-sdk
PATH=$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$PATH

Default command: appium --address 0.0.0.0 --port 4723 --base-path /. The cloud-profile services override this with npx taqwright test --project=….

When to rebuild: the Dockerfile itself changes, engines.node moves in package.json, or the pinned Appium / UiAutomator2 versions change. Other code changes don't require a rebuild — the test sources mount in via volumes: - .:/work.

Local dev — Android emulator on the host

Appium runs in the container; the emulator runs on your host. The container's adb connects to the emulator over TCP (port 5555). Works on macOS, Windows, and Linux Docker Desktop. Setup is five steps — three on the host, two inside the container.

Why the emulator stays on the host: Android emulators need hardware virtualization (Apple's Hypervisor.framework on Mac, /dev/kvm on Linux). Docker Desktop on Mac / Windows runs Docker itself inside a VM — nested virtualization isn't passed through, so an emulator in the container would either refuse to boot or crawl. The container is the Appium runtime; the device is always external.

Prerequisite: at least one AVD created in Android Studio (or via avdmanager). Check what you have:

emulator -list-avds
# Pixel_7_API_34         ← if you see names, you're set
#                        # if empty: open Android Studio → Device Manager → Create Device

Host-side setup (per emulator session)

# 1. Boot the emulator in the background:
emulator -avd Pixel_7_API_34 &

# 2. Wait for Android to finish booting (~30s typically):
adb wait-for-device
adb shell getprop sys.boot_completed     # prints "1" when ready

# 3. Confirm host sees it:
adb devices
# List of devices attached
# emulator-5554    device

# 4. Put it into TCP-listen mode so the container can reach it on :5555.
# NOTE: this gets reset every emulator reboot — re-run after each boot.
adb tcpip 5555
# restarting in TCP mode port: 5555

Container-side setup

What the compose file wires up for you (no manual config needed):

# 5. Start the Appium container (detached so you can exec into it):
docker compose --profile local-android up -d appium

# 6. From inside the container, connect adb to the host emulator:
docker compose exec appium adb connect host.docker.internal:5555
# connected to host.docker.internal:5555

# 7. Verify the container now sees the emulator:
docker compose exec appium adb devices
# List of devices attached
# host.docker.internal:5555    device       ← anything other than "device"
#                                              means try again from step 4

The chain is live: host emulator ← TCP :5555 ← container's adb ← Appium → port :4723 → your host. Verify Appium is reachable:

curl -s http://localhost:4723/status
# {"value":{"ready":true,"message":"The server is ready...","build":{"version":"3.x.x"}}}

From here, you can:

If step 7 prints offline, empty, or step 6 says failed to authenticate:
  • failed to authenticate → the container's adb has a different keypair than your host. The compose file mounts ~/.android from your host into the container (read-only) so the keys match — make sure that volume is present. If you're running an older compose that doesn't have it, copy the keys manually: docker compose cp ~/.android/adbkey appium:/root/.android/adbkey && docker compose cp ~/.android/adbkey.pub appium:/root/.android/adbkey.pub && docker compose exec appium adb kill-server, then retry step 6.
  • Emulator went to sleep → adb disconnect host.docker.internal:5555 in the container, then re-run step 6.
  • Emulator was rebooted → re-run step 4 (adb tcpip 5555) on the host, then step 6.
  • Host shows no device in step 3 → no emulator running. Boot one via step 1.
  • The emulator may show "Allow USB debugging from this computer?" — click Always allow.

When you're done, kill the emulator: adb emu kill (or close its window). And tear down the container: docker compose --profile local-android down.

CI — cloud Android (BrowserStack / LambdaTest)

No local Appium and no local emulator. The test runner container talks directly to the cloud provider. Define an android-cloud project in your taqwright.config.ts with device.provider: 'browserstack' (or 'lambdatest') and your credentials in env.

export BROWSERSTACK_USERNAME=...
export BROWSERSTACK_ACCESS_KEY=...

docker compose --profile ci-cloud-android run --rm tests-cloud-android

The container mounts your repo at /work, forwards your credentials, and runs npx taqwright test --project=android-cloud. Results land in ./playwright-report/ on the host.

CI — cloud iOS

Same image, same env vars; just a different project name. Your ios-cloud project in taqwright.config.ts needs platform: Platform.IOS and device.provider: 'browserstack' (or 'lambdatest') — the cloud handles all the iOS-specific machinery.

export BROWSERSTACK_USERNAME=...
export BROWSERSTACK_ACCESS_KEY=...

docker compose --profile ci-cloud-ios run --rm tests-cloud-ios

Verify the image

Run the doctor inside the container — it'll confirm Node / Appium / adb / java are all present:

docker compose run --rm tests-cloud-android npx taqwright doctor

Expected output — the macOS-only lines (xcrun, Xcode, ffmpeg, iOS simulator) are absent because the container is Linux, and only the uiautomator2 Appium driver is in the image (iOS goes through the cloud):

taqwright doctor (v0.0.1)
  [ok] Node.js >= 24  — v24.x.x
  [ok] adb (Android SDK)  — on PATH
  [ok] ANDROID_HOME (Appium adb lookup)  — ANDROID_HOME=/opt/android-sdk
  [ok] java (JDK for UiAutomator2)  — on PATH
  [ok] JAVA_HOME (UiAutomator2 JDK)  — JAVA_HOME=/usr/lib/jvm/java-17-openjdk-arm64
  [ok] Appium (test server)  — on PATH (v3.x.x)
  [ok] Appium drivers  — uiautomator2; not installed: xcuitest (iOS)
Apple Silicon Macs: the image is amd64; Docker Desktop runs it under Rosetta. Initial build is slower (SDK download under emulation) but test runs are fine. A native arm64 variant is on the roadmap.