CLI
Every command takes only its own flags, and answers --help (or -h) by printing them without running:
tapsmith test --help, tapsmith help test. tapsmith --help lists the commands. A flag that belongs
to another command is refused (tapsmith show-trace t.zip --force-install is an error).
Value flags take their value either way — --workers 4 or --workers=4, -j 4 or -j4 or -j=4. A value
flag followed by another flag is an error, not a value: tapsmith test --device --workers 2 fails with
option '-d, --device <serial>' argument missing, instead of targeting a device called --workers. For a
value that really starts with -, use the = form: --grep=-slow.
An empty value (--device "$SERIAL" with an empty variable) means the flag was not given for --device,
--trace, --video, --grep, --grep-invert and --reporter: the run falls back to automatic device
selection or the config’s setting. An empty -c / --config is an error rather than a silent fallback to tapsmith.config.*, which
could run a different config than the one meant.
A usage error (unknown command or flag, missing or invalid value) prints one line naming the problem and the
command’s --help, and exits with code 1. An unknown command suggests the nearest one
(unknown command 'tset' / Did you mean test?), even when --help is also given. Commands with a --json
mode (init, verify, doctor, list-devices, telemetry, ios setup-device) report usage errors under
--json as the JSON error envelope on stdout, with code BAD_ARGS, or for init
UNKNOWN_FLAG / MISSING_FLAG_VALUE like its other errors. mcp-server keeps stdout for the protocol and
reports usage errors on stderr.
JSON output (--json)
Section titled “JSON output (--json)”init, verify, doctor, list-devices, telemetry and ios setup-device take --json, for scripts and
AI agents. The shapes below are a contract for scripts to rely on: new keys may be added, but renaming or
removing one, or changing what it means, is a breaking change.
-
stdout holds exactly one JSON document, and nothing else from Tapsmith: no banner, no progress, no ANSI codes. Two things are outside that promise:
--helpstill prints help, and code in your config file that writes to stdout (a top-levelconsole.log) still lands there, sincedoctor,verifyandtelemetryload the config. A command killed by a signal (Ctrl-C, a CI cancel) may print nothing.verifydoes not handle interrupts reliably yet: depending on which processes the signal reaches and when, an interrupted run reportsRUN_FAILED(exit 1), exits 130 with no output, or runs to completion. -
When the command could not do its job, the document is the error envelope and the exit code is 1:
{ "error": { "code": "DAEMON_NOT_FOUND", "message": "…", "fix": "…" } }codeis a stableSCREAMING_SNAKEidentifier (table below),messagea description for a person (it can span lines:verify’sRUN_FAILEDincludes the end of the run’s stderr),fixwhat to do about it, andcandidates(init only) the choices that made it undecidable. -
Otherwise the document is the command’s result, which never has a top-level
errorkey — so"error" in resulttells the two apart. A result can still mean failure:doctorwith a failing check andverifywith a failing test exit 1 and print their result with"ok": false. -
Optional keys are left out when they have no value, never set to
null(excepttelemetry’sconfigPath, which isnullwhen no config was loaded: none was found, or it failed to load, whichconfigConsulted: falsetells apart).
| Command | Error codes |
|---|---|
| every command above | BAD_ARGS (a usage error), UNEXPECTED_ERROR (a bug or an environment failure the command did not anticipate; init and verify included), UNSUPPORTED_NODE (Node.js is older than 22; nothing else runs) |
init |
UNKNOWN_FLAG, MISSING_FLAG_VALUE, INVALID_PLATFORM, INVALID_DEVICE_TYPE, NO_PLATFORM, NO_APK, AMBIGUOUS_APK, NO_PACKAGE, NO_IOS_APP, AMBIGUOUS_IOS_APP, NO_BUNDLE_ID, IOS_REQUIRES_MACOS, IOS_PHYSICAL_INTERACTIVE_ONLY, CONFIG_EXISTS, WRITE_FAILED (a file init writes cannot be written: names it and any files already written), NON_INTERACTIVE_TTY (no terminal and no --yes), JSON_REQUIRES_YES (--json in a terminal without --yes or a setup flag: the wizard has no JSON output) |
verify |
YARN_PNP_UNSUPPORTED (the project is a Yarn Plug’n’Play install, where Tapsmith can’t run tests; the fix is nodeLinker: node-modules), NO_CONFIG, CONFIG_ERROR, RUN_FAILED, PARSE_FAILED, NO_TESTS_RAN (the run executed no test: the file has none, every one was skipped, or a grep / grepInvert filtered them all out; a flaky test that passed on retry counts as having run) |
doctor |
none of its own: a check that cannot run is reported in the result, usually as a warn (Could not check …) |
list-devices |
DAEMON_NOT_FOUND, DAEMON_START_FAILED, LIST_DEVICES_FAILED |
telemetry |
TELEMETRY_WRITE_FAILED (enable / disable could not write ~/.tapsmith/telemetry.json) |
ios setup-device |
UNSUPPORTED_PLATFORM (not macOS) |
The results:
init --yes --json:{ configPath, filesCreated, warnings, nextSteps }— the config file written, every file created (including.gitignorewhen init added entries to it), warnings (strings) and next steps (strings). When the project cannot resolvetapsmith, which the config and example test import, the first next step is the install command for its package manager (--yesnever runs it) and a warning says so.verify --json:{ ok, passed, failed, skipped, duration, failures, testFile }— counts,durationin milliseconds,failuresas{ fullName, error, screenshotPath? }, and the test file run, relative to the config’srootDir.doctor --json:{ ok, checks, inventory }—okis false when any check hasstatusfail;checksare health checks (below);inventoryis{ avds, simulators, connectedDevices }: AVD names, simulators as{ name, udid, state, runtime }(runtimelikeiOS 26.5), and connected Android devices as{ serial, state }.ios setup-device --json:{ ok, checks, devices, notConnected }—okis the same verdict as the exit code;checksare health checks (below), ending withdevice-connected, which fails when no device is connected or a connected device is unpaired or has Developer Mode off, sookis false exactly when some check fails;devicesare the physical devicesxcrun devicectlcan reach now, as{ udid, name, osVersion, paired, developerMode, transport, fix? }, whereosVersionis empty when devicectl does not report it,developerModeisenabled,disabledorunknown,transportiswired,localNetworkorunknown, andfixis set on a device that is unpaired or has Developer Modedisabled(one line per problem).developerModeandtransportpass through what devicectl reports, so treat a value not listed here as unknown.notConnectedlists, as{ udid, name }, the devices devicectl only remembers (unplugged, out of Wi-Fi range, or paired with another Mac); they never affectok, andlist-devicesleaves them out.list-devices --json:{ devices }, each{ ready, platform, serial, name, osLabel, blockers, inUseBy }—platformisandroid,android-emu,ios-simorios-device;osLabelis likeiOS 18.1or empty when unknown;blockersare the fixes that would make it ready, empty whenready.inUseByis the other running Tapsmith session that has claimed the device (see Several sessions on one machine), as{ command, pid, project, since, daemonAddress }, ornullwhen none has;readydoes not take it into account.commandis liketapsmith test --uiortapsmith mcp-server,pidis that session’s root process,projectthe directory it was started in,sincethe ISO time it claimed the device, anddaemonAddressthe daemon it drives the device through, ornullwhen unknown.telemetry --json(every action):{ enabled, reason?, debug, stateFile, anonymousId?, endpoint, configPath, configConsulted, docs }—reasonisenv,configormachinewhen disabled; see Telemetry.
A health check is { id, status, label, detail?, fix? }. id is stable — match on it, not on label, which
can hold values (Node.js 22.1.0) and whose wording may change. status is pass, warn (not blocking) or
fail. detail is extra context (a path, where something was found, device names), which doctor’s text
output prints dimmed after the label; fix says how to resolve a warn or fail and may span several lines. doctor’s ids include node, node-arch, yarn-pnp, daemon, config, config-load, config-platform, no-platform, adb, android-home,
android-devices, android-agent, app-apk, android-emulator, avd-images, xcode, simctl, ios-sim-agent, mitm-ca,
mitmproxy, network-extension and system-proxy, each only where it applies; ios setup-device’s are
xcode-clt, devicectl, iproxy, signing, sudo-ddi-mount, ios-agent-runner, profile-expiry and
device-connected.
tapsmith test [files...]
Section titled “tapsmith test [files...]”Run test files. If no files are specified, discovers tests using the testMatch patterns from your config.
npx tapsmith testnpx tapsmith test tests/login.test.ts tests/signup.test.tsnpx tapsmith test tests/auth # every test file under a directorynpx tapsmith test 'tests/auth/*.test.ts' # a glob (quote it so the shell leaves it alone)npx tapsmith test login checkout # files whose path matches "login" or "checkout"As in Playwright, each argument is one of:
- a file (relative to
rootDiror the working directory, or absolute): run as named, even iftestMatchwould not discover it — except in a config withprojects, where a file runs under the projects whosetestMatchcovers it, so one that no (selected) project covers is reported and not run; - a directory: the test files under it;
- a glob: the test files it matches (a glob that matches none is read as the regular expression below instead, with a note saying so —
*,?,[]and{}are regex syntax too); - anything else: a case-insensitive regular expression matched against each test file’s path relative to
rootDir(/pattern/flagssets the flags; an invalid expression is matched as plain text).
Directories, globs and filters select from the files testMatch discovers, so they never pick up a helper module. With projects, a project that another project in the run depends on (a setup project) runs whole, as in Playwright, even when an argument also matched some of its files; an argument that selects only setup files, with nothing depending on them in the run, runs just those files. An argument that matches nothing prints a warning; if nothing is selected at all, the run fails with No tests found — before it starts the daemon or touches a device.
tapsmith test --device <serial> / tapsmith test -d <serial>
Section titled “tapsmith test --device <serial> / tapsmith test -d <serial>”Target a specific device by its ADB serial number (or simulator UDID). This is mainly useful for single-device debugging or reproducing an issue on one known device.
npx tapsmith test --device emulator-5554A pinned device hosts exactly one worker, in every mode — sequential, parallel,
watch and UI. --device combined with a --workers N the run cannot use is
refused: both are on the command line and ask for different runs. In a
single-platform config that is any N > 1 the run could otherwise use (more
than one file in a wave — --device X --workers 2 one.test.ts just runs on X);
in a config whose projects span
Android and iOS, --device (like a root device in the config) pins only the
projects of that device’s platform, and the other platform’s projects may use
the remaining workers — unless the device is pinned by several projects of its
platform (two apps, say), in which case the whole run uses one worker and runs
them one after another (UI and watch mode refuse that: they keep a worker per
target alive). Any other worker count — a workers value in the config,
or --workers N with a device pinned in the config or by use.devices
members — is capped to one for that device target, with a note saying so. For multi-worker
emulator runs, use config-based provisioning with workers, launchEmulators,
and avd instead.
tapsmith test --workers <n> / tapsmith test -j <n>
Section titled “tapsmith test --workers <n> / tapsmith test -j <n>”Run tests in parallel across n devices. Each worker gets its own device/emulator and daemon instance. Tests are distributed via a work-stealing queue for natural load balancing.
npx tapsmith test --workers 4npx tapsmith test -j 2Overrides the workers config option. Requires enough connected devices or launchEmulators: true with an avd configured. In parallel mode, each test result includes a workerIndex field and console reporters show [worker N] tags.
tapsmith test --shard=x/y / tapsmith test --shard x/y
Section titled “tapsmith test --shard=x/y / tapsmith test --shard x/y”Split the test suite deterministically across y machines, running only shard x. Each project’s files are split on their own, by position in that project’s sorted file list (file_index % total === current - 1), so every machine gets the same split whatever order the files are named in. A project another project depends on (a setup project) is not split: it runs whole on every shard that has files from one of its dependents, and is skipped on a shard that has none.
# In a CI matrix with 4 jobs:npx tapsmith test --shard=1/4npx tapsmith test --shard=2/4npx tapsmith test --shard=3/4npx tapsmith test --shard=4/4When sharding is active, the blob reporter is automatically added so results can be merged later with tapsmith merge-reports.
tapsmith test --grep <pattern> / tapsmith test -g <pattern>
Section titled “tapsmith test --grep <pattern> / tapsmith test -g <pattern>”Run only the tests whose fullName (describe > test) matches the given regular expression. Mirrors Playwright’s --grep. The tests it filters out are left out of the run entirely: they are not listed, and not counted as skipped.
npx tapsmith test --grep checkout # Only tests with "checkout" in their fullNamenpx tapsmith test -g "^login > " # Only tests inside the top-level "login" describenpx tapsmith test --grep "@smoke|@critical" # Run smoke + critical tests by tag-style suffixesThe pattern is compiled as a JavaScript RegExp. Combine with --grep-invert to further narrow the selection.
If a --grep / --grep-invert filter selects zero tests (e.g. a typo’d pattern), the run fails with No tests found and lists the tests there are, rather than reporting a green “0 tests” success. Like Playwright, Tapsmith reads the test names first, so this happens before it starts the daemon or touches a device (a file that fails to load leaves the question open, and the run goes ahead to report it). Watch and UI mode skip this check: an edit can add the test the pattern is waiting for, and UI mode lists every test to pick from.
The same filter can be set in tapsmith.config.ts:
export default defineConfig({ grep: /checkout/, // single pattern})
// Or match any of several patterns:export default defineConfig({ grep: [/login/, /signup/], // any pattern matches})Per-project filters are intersected with the root filter (a test must match both):
export default defineConfig({ grep: /smoke/, projects: [ { name: 'android', grep: /android/ }, // runs tests matching BOTH /smoke/ and /android/ ],})tapsmith test --grep-invert <pattern>
Section titled “tapsmith test --grep-invert <pattern>”Leave out the tests whose fullName matches the given regular expression: like --grep’s non-matches, they are not listed or counted as skipped. Mirrors Playwright’s --grep-invert.
npx tapsmith test --grep-invert slow # Run everything except "slow" testsnpx tapsmith test -g checkout --grep-invert wip # Checkout tests, excluding work-in-progressAlso configurable in tapsmith.config.ts as grepInvert: RegExp | RegExp[], with per-project entries unioned with the root entry.
tapsmith test --project <name>
Section titled “tapsmith test --project <name>”Run only the named project from your config. Repeat the flag to run
several. Mirrors Playwright’s --project. Any dependencies of the
selected projects run automatically, so setup projects are never skipped.
npx tapsmith test --project android # Only the "android" project (+ its deps)npx tapsmith test --project android --project ios # Both platformsThe flag requires a projects array in your config. An unknown name fails the
run with the list of available projects. Combine with file arguments to further
narrow a project to specific test files:
npx tapsmith test --project android tests/login.test.tstapsmith test --watch / tapsmith test -w
Section titled “tapsmith test --watch / tapsmith test -w”Watch test files for changes and re-run them automatically. The daemon, device, and agent are kept alive across re-runs, so only the app reset + test execution cost is paid (~1-2s per re-run).
npx tapsmith test --watchnpx tapsmith test -w tests/login.test.ts # Watch a specific fileSee Watch Mode for details.
tapsmith test --ui
Section titled “tapsmith test --ui”Open the interactive UI mode in the browser. Provides a web-based test runner with a test tree, click-to-run, live progress, and an MCP endpoint for AI agent integration.
npx tapsmith test --uinpx tapsmith test --ui --ui-port 8080 # Use a specific portSee UI Mode for details.
tapsmith test --config <path> / tapsmith test -c <path>
Section titled “tapsmith test --config <path> / tapsmith test -c <path>”Use a specific config file instead of the default tapsmith.config.ts:
npx tapsmith test -c tapsmith.config.ci.tsnpx tapsmith test --config=./configs/ios.config.mjstapsmith test --force-install
Section titled “tapsmith test --force-install”Reinstall the app under test (APK or .app) on every device the run sets up, even when the installed build already matches. Applies in every mode: the sequential run, each --workers worker, and the workers watch and UI mode start (a UI worker respawned mid-session does not reinstall). The Tapsmith agent is not reinstalled by this flag. Useful when the app’s installed state has drifted outside Tapsmith — a rebuilt APK is already detected and reinstalled without it.
npx tapsmith test --force-installWithout this flag, Tapsmith checks if the package is already installed and skips the install step to save time.
tapsmith test --reporter <name>
Section titled “tapsmith test --reporter <name>”Use one reporter for this run instead of the config’s reporter: a built-in one
(list, line, dot, json, junit, html, github or blob), or the path or package name of a
custom reporter module (see Custom reporters, under Reporters). For several reporters or reporter
options, set reporter in the config.
npx tapsmith test --reporter dotnpx tapsmith test --reporter json # writes tapsmith-results/results.jsontapsmith init [options]
Section titled “tapsmith init [options]”Set up a project. With no options, in a terminal, it runs the interactive wizard: it detects your environment (ADB, Xcode, simulators, emulators), offers the app builds it finds under android/ and ios/ (a typed path must exist), walks you through platform and app configuration, and generates a tapsmith.config.ts, an example test (tests/example.tapsmith.ts) and an AGENTS.md section. It adds tapsmith-results/ (traces, screenshots, JSON and JUnit results) and tapsmith-report/ (the HTML report) to the project’s .gitignore (creating one if there is none), skipping any git already ignores (a .gitignore it cannot update is a warning, not an error). Before writing anything it checks it can write every file (and, before its setup questions or the iOS agent build, that it can write to the project directory), and it writes the config last, replacing any existing one only once the new one is complete: a file it cannot write stops init with exit code 1 and WRITE_FAILED, naming the file and anything already written, in the wizard as with --yes. Ctrl-C or Esc at a wizard question stops it with “Setup cancelled.” and exit code 130, before it has written anything (at the install offer, which comes after the files are written, the message says so and gives the install command). In the wizard, it offers every iOS simulator, with a booted one selected, and its next steps start with npx tapsmith verify. The config’s testMatch is ['**/*.tapsmith.ts'], a suffix Jest and Vitest don’t run by default (outside a __tests__ folder, where Jest runs every file), so the project’s own unit-test run doesn’t pick up Tapsmith tests and Tapsmith doesn’t pick up its unit tests; when the project already has Tapsmith tests named *.test.ts or *.spec.ts (from an older init, say), init warns that the new config won’t run them. Run outside a project that has Tapsmith installed (npx tapsmith init before npm i -D tapsmith, or a global install), it offers to install it with the project’s package manager, since the files it writes import tapsmith; declined, it prints the command as the first next step. In a directory without a package.json it does not offer the install, since a bare npm i -D tapsmith would install into the nearest parent directory that has one (a monorepo root, say): it warns, naming that directory (run the install there if it is the project’s root), and the next step creates the package.json first (npm init -y && npm i -D tapsmith, or the package manager’s equivalent). doctor, verify and the config-load error give the same command. An install that exits non-zero but leaves tapsmith importable counts as done: pnpm 11 and later exit 1 over the dependency build scripts they skipped, and for pnpm init then prints the allowBuilds snippet that silences that error (see Installation). In an Expo project (expo among the dependencies, plus app.json or app.config.*) it gives the Expo build commands when it finds no build (npx expo prebuild, then a release ./gradlew assembleRelease or xcodebuild build, with EXPO_PUBLIC_TAPSMITH_HOOKS=1 when the app uses @tapsmith/react-native), and pre-fills the package and bundle ID prompts from the app config when it can’t read them from the build.
Pass --yes or any setup flag below (every flag but --json) to run non-interactively instead, for scripts and AI agents: anything not given is auto-detected, and a choice that cannot be made (two APKs, say) exits 1 naming the candidates and the flag that picks one. --json only changes the output, so an agent passes --yes --json. Without a terminal and without --yes or a setup flag, init exits 1 rather than waiting for input. iOS physical devices need the interactive wizard, for its code-signing preflight.
| Flag | Meaning |
|---|---|
-y, --yes |
Accept auto-detected defaults for anything not specified |
--platform <list> |
android, ios, or android,ios (default: inferred — Android when --apk is given or there is an android/ directory, iOS when --app is given or, on macOS, there is an ios/ directory; in a React Native or Expo project on macOS that leaves iOS out, init warns, with how to add it) |
--apk <path> |
Android APK (default: auto-detected under android/**/build/outputs/apk/) |
--package <id> |
Android package name (default: read from the APK, else, in an Expo project, expo.android.package from the app config, with a warning) |
--app <path> |
iOS simulator .app bundle (default: auto-detected under ios/) |
--bundle-id <id> |
iOS bundle identifier (default: read from Info.plist, else, in an Expo project, expo.ios.bundleIdentifier from the app config, with a warning) |
--avd <name> |
Android AVD to auto-launch (default: first available; with --network-capture, the first that supports HTTPS capture — Google Play images don’t, and init warns if it can only pick one) |
--simulator <name> |
iOS simulator name (default: a booted iOS simulator, else the newest available iPhone) |
--device-type <type> |
emulator, physical or both (default: emulator) |
--network-capture |
Enable HTTP(S) capture in traces |
--no-example-test |
Skip scaffolding tests/example.tapsmith.ts |
--no-agents-md |
Skip scaffolding the AGENTS.md section |
--force |
Overwrite an existing tapsmith.config.* |
--json |
Machine-readable output, on success and on errors (see JSON output); needs --yes or a setup flag |
npx tapsmith init # interactive wizardnpx tapsmith init --yes # accept every detected defaultnpx tapsmith init --yes --platform android --apk ./app-debug.apk --jsonSee Using Tapsmith with AI coding agents for the non-interactive setup loop.
tapsmith doctor [--json] [-c <path>]
Section titled “tapsmith doctor [--json] [-c <path>]”Run a non-interactive system health check. Verifies all prerequisites: Node.js version, daemon binary, config file, ADB (Android), connected devices, agent APKs, AVD system image compatibility, Xcode (iOS), simulators, and network capture dependencies. Each check prints ✓, ⚠ (warning) or ✗ (error); most warnings and errors are followed by a ↳ line with the fix, often the exact command to run. Exits with code 0 unless a check is an error.
Which platforms are checked follows the config. A platform the config targets (per project use.platform, else the root platform; when unset, iOS if app, simulator or iosXctestrun is set, otherwise Android) is always checked. When it is the config’s only platform, a missing ADB or Xcode is an error, as is an iOS-only config on a non-Mac; when the config targets both, it is a warning as long as the other platform can run here (select its projects with --project), and an error when neither can. A config (or project) that sets app, simulator or iosXctestrun without platform is an error (config-platform), because tapsmith test refuses it. A platform the config does not target is skipped, with a – skipped: … line saying so. With no config file (or one that cannot be loaded), doctor checks whatever is installed: a missing ADB or Xcode is a warning that the platform’s checks were skipped, not an error — unless neither platform can run here (no ADB on PATH, and no Xcode or not a Mac), which is a no-platform error: doctor exits 1 and its summary line ends — no platform can run tests on this machine. A missing ADB’s fix depends on what is there: the platform-tools directory to add to PATH when an Android SDK has it, installing platform-tools when the SDK lacks them, or installing Android Studio (or the command-line tools) and setting ANDROID_HOME when there is no SDK at all. An x64 Node running under Rosetta on Apple Silicon is a node-arch warning, with the fix (an arm64 Node, then reinstall dependencies); tapsmith init and tapsmith test print the same warning. A Yarn Plug’n’Play install is a yarn-pnp error, since tapsmith test, verify and mcp-server refuse to run there (see Installation). The mitmproxy and Network Extension checks run only when iOS is checked. When an Android project sets avd, doctor also looks for the emulator binary the way tapsmith test does ($ANDROID_HOME/emulator, $ANDROID_SDK_ROOT/emulator, the default SDK location, then PATH) and lists every path it tried; a missing emulator is an error when Tapsmith would launch the AVD, and a warning with launchEmulators: false or when this machine only runs a mixed config’s iOS projects.
--json prints { ok, checks, inventory }: the checks (with a fix wherever the text output has one) and the device inventory (AVDs, simulators, connected devices), described under JSON output. A skipped platform has no checks in the JSON. -c / --config checks a specific config file; without it, doctor finds the config the same way tapsmith test does (tapsmith.config.ts, .js or .mjs in the current directory).
npx tapsmith doctornpx tapsmith doctor --json -c tapsmith.config.ci.mjs # machine-readable, against a specific configtapsmith verify [--json] [-c <path>]
Section titled “tapsmith verify [--json] [-c <path>]”Prove the setup works end to end: runs one test file through the real tapsmith test path (daemon, device or emulator launch, app install) and reports whether it passed. It picks example.tapsmith.ts (or an older init’s example.test.ts) if the project has one, otherwise the first test file; a project with no tests yet gets a throwaway smoke test, removed afterwards. Exits 1 when the run fails, when no test ran (the file has none, every one was skipped, or a grep / grepInvert filtered them all out — a pass with nothing run proves nothing), when there is no tapsmith.config.* (unless -c names one), or when the config cannot be loaded.
--json prints { ok, passed, failed, skipped, duration, failures, testFile }, or the JSON error envelope. -c / --config uses a specific config file.
npx tapsmith verifynpx tapsmith verify --jsontapsmith telemetry [status|enable|disable] [--json]
Section titled “tapsmith telemetry [status|enable|disable] [--json]”Show or switch the anonymous usage telemetry described in Telemetry. status (the default) says whether this process would report and, if not, which switch decided it: the environment (TAPSMITH_TELEMETRY, DO_NOT_TRACK), the project config (telemetry: false), or the machine-wide switch. disable turns it off for every project on this machine by writing to ~/.tapsmith/telemetry.json; enable turns it back on (it cannot override the environment or a config opt-out). --json emits the status object for scripting (see JSON output). Pass -c <file> to consult a specific config.
npx tapsmith telemetry # statusnpx tapsmith telemetry disablenpx tapsmith telemetry enablenpx tapsmith telemetry status --jsontapsmith mcp-server [-c <path>]
Section titled “tapsmith mcp-server [-c <path>]”Run the MCP server on stdio, for an AI coding agent to launch as a subprocess. It runs its own headless test session, with its own daemon and device. -c / --config uses a specific config file. Stdout carries the MCP protocol, so messages and usage errors go to stderr. See the MCP Server Guide for setup.
claude mcp add tapsmith -- npx tapsmith mcp-servercodex mcp add tapsmith -- npx tapsmith mcp-server --config tapsmith.config.ios.mjstapsmith create-avd [--api <level>] [--name <name>] [--device <profile>] [--abi <abi>] [--force] [--install-tools]
Section titled “tapsmith create-avd [--api <level>] [--name <name>] [--device <profile>] [--abi <abi>] [--force] [--install-tools]”Create an Android AVD that supports HTTPS network capture. Downloads a Google APIs system image with sdkmanager and creates the AVD with avdmanager — Google Play images (the ones Android Studio preselects) block adb root, so Tapsmith cannot decrypt HTTPS traffic on them (see Android emulator image requirements).
If the Android SDK command-line tools are missing (Android Studio doesn’t install them by default), the command offers to download and install them into $ANDROID_HOME/cmdline-tools/latest for you — pass --install-tools to skip the prompt in scripts/CI. When no java is available, Android Studio’s bundled JDK is used automatically. A system image that is already installed is not re-downloaded; the Android emulator is installed alongside it when the SDK lacks one. The AVD is created in the AVD home the emulator reads ($ANDROID_AVD_HOME when set), so Tapsmith can launch it straight away.
Defaults: API level 36, name Tapsmith_Phone_API_<api>, device profile medium_phone, ABI matching the machine’s architecture (arm64-v8a on Apple Silicon — including when Node runs under Rosetta — and x86_64 on Intel). --force overwrites an existing AVD with the same name.
npx tapsmith create-avd # Tapsmith_Phone_API_36npx tapsmith create-avd --api 34 --device pixel_7npx tapsmith create-avd --install-tools # non-interactive bootstrap (CI)tapsmith list-devices [--json]
Section titled “tapsmith list-devices [--json]”Print a table of every device Tapsmith can target: Android (ADB), iOS simulators (simctl), and iOS physical devices (devicectl). Each row shows a one-line status (Ready or an imperative fix). An Android device adb lists but cannot use — unauthorized (the USB-debugging prompt was not accepted), offline, or no permissions (Linux without udev rules) — is listed as not ready with the same fix tapsmith doctor gives; Tapsmith never selects one for a test run. When a run finds no usable device, its error also names any attached device adb cannot use, with its adb state. A device another running Tapsmith session has claimed shows In use by that session’s command and pid. With no devices at all, it says per platform how to start one — or, when that platform’s toolchain is missing (no ADB on PATH, no Xcode), what to install first. An Android test run that finds no device (or would launch an emulator) and an iOS run whose simulator cannot be found give the same toolchain advice. --json prints { devices }, or the JSON error envelope when the daemon cannot be started or cannot list devices.
npx tapsmith list-devicesnpx tapsmith list-devices --jsontapsmith show-trace <file.zip>
Section titled “tapsmith show-trace <file.zip>”Open the trace viewer in the default browser to inspect a recorded trace. Traces are written to
<outputDir>/traces/ (tapsmith-results/traces/ by default), named
trace-[<project>-]<test name>[-retry<n>]-<timestamp>.zip.
npx tapsmith show-trace tapsmith-results/traces/trace-my_test-1767225600000.zipThe trace viewer shows:
- Actions panel — chronological list of actions with icons, locators, and durations
- Timeline filmstrip — screenshot thumbnails for quick navigation
- Screenshot panel — before/after screenshots with tap coordinate overlays
- Detail tabs — Call info, Console output, Source code, View hierarchy, Network requests, Errors
- Keyboard navigation — Arrow keys or j/k to move between actions
tapsmith test --trace [mode]
Section titled “tapsmith test --trace [mode]”Record traces during test execution. Overrides the trace config option. mode is one of the
TraceMode values: off, on, on-first-retry, on-all-retries,
retain-on-failure, retain-on-first-failure, retain-on-failure-and-retries. A bare --trace means on.
Any other value is an error that lists the valid modes, so a typo such as --trace retain-on-falure fails
the run instead of silently recording nothing. The same check applies to trace in the config file, in a
project’s use, and in test.use().
npx tapsmith test --trace on # Record all testsnpx tapsmith test --trace retain-on-failure # Only keep traces for failuresA bare --trace directly followed by a test file reads the file as the mode (and fails): write
--trace on login.test.ts, or put the file first.
tapsmith test --video [mode]
Section titled “tapsmith test --video [mode]”Record an MP4 of the device screen for the lifetime of each test. Mirrors
Playwright’s video flag and overrides the video config option. Accepts
the same mode set as --trace, validated the same way (a bare --video
means on; an unknown mode, here or in the config, is an error). See
Video recording.
npx tapsmith test --video on # Record every testnpx tapsmith test --video retain-on-failure # Only keep videos for failed teststapsmith merge-reports [dir]
Section titled “tapsmith merge-reports [dir]”Merge blob reports from sharded CI runs into a single HTML report.
# After collecting all shard blob-report/ directories:npx tapsmith merge-reports # reads from blob-report/npx tapsmith merge-reports ./blobs # custom directorynpx tapsmith merge-reports -c tapsmith.config.ci.ts # reporters from a specific configThe merged results go to the reporters in the config (-c / --config picks the file).
It checks the blobs before merging. It refuses to merge, printing a one-line error and exiting with status 1, when:
- the directory holds no blob reports (
*.jsonl), for example after a failed artifact download; - a file is not a valid blob report (
Invalid blob file <name>: …); - a blob was written by a newer Tapsmith (its blob format is newer than this version reads);
- the sharded blobs aren’t one split: a shard appears twice, the blobs come from different
splits (say
1/3and2/4), or an unsharded blob sits among sharded ones. Merge each run, such as Android and iOS, from its own directory. A set of unsharded blobs is merged without these checks.
When a shard is missing, the shards that are present are still merged and reported, so CI
keeps a report to triage from. The merged status is failed, the missing shards are named, and
the command exits 1.
Once a complete set is merged, the command exits 0 even if the merged run has failed tests.
Playwright’s merge-reports does the same, since the shard jobs already carry the failure. The
last line gives the merged status, e.g.
Merged 3 blob reports (shards 1–3 of 3): failed — 41 passed, 2 failed.
A blob reporter in the config is skipped here, because merging reads blobs and never writes one. If
blob is the only reporter configured, the merge falls back to list.
tapsmith show-report [dir]
Section titled “tapsmith show-report [dir]”Open the HTML test report in the default browser.
npx tapsmith show-report # opens tapsmith-report/index.htmlnpx tapsmith show-report ./my-report # custom directorytapsmith ios
Section titled “tapsmith ios”iOS setup commands (macOS only), in two tracks:
- Physical iPhone/iPad:
tapsmith ios setup-device(preflight), thentapsmith ios build-agent(the signed runner). See docs/ios-physical-devices.md for the full walkthrough;tapsmith list-devices(above) lists physical devices too, with their UDIDs. - Network capture, under
tapsmith ios network: one setup for simulators, and a per-device profile for physical devices.
iOS simulators need no setup to run tests. tapsmith ios or
tapsmith ios network on its own prints that group’s help and exits 0.
Renamed after 0.5.0. These commands used to be top-level. The old names were removed without aliases; they now fail as unknown commands.
| 0.5.0 and earlier | Now |
|---|---|
tapsmith setup-ios-device |
tapsmith ios setup-device |
tapsmith build-ios-agent |
tapsmith ios build-agent |
tapsmith setup-ios |
tapsmith ios network setup-simulator |
tapsmith configure-ios-network <udid> |
tapsmith ios network configure <udid> |
tapsmith refresh-ios-network <udid> |
tapsmith ios network configure <udid> --refresh |
tapsmith verify-ios-network <udid> |
tapsmith ios network verify <udid> |
tapsmith ios setup-device [--json]
Section titled “tapsmith ios setup-device [--json]”Run the host-side preflight for physical iOS devices (macOS only). Takes no
arguments: it checks the Mac, then every device xcrun devicectl lists. Host checks: Xcode
command-line tools, xcrun devicectl, libimobiledevice (iproxy), a code
signing identity and an Apple Developer team registered with Xcode; and, as
advisory checks, passwordless sudo for the Developer Disk Image mount (so a
test run does not stop at a password prompt), a signed agent runner (found the way
tapsmith test finds it, taking the current directory as rootDir: see
ios build-agent below; a runner an earlier
Tapsmith version built for an npm install is flagged for a rebuild), and the
provisioning profile’s expiry. Device check: each listed device is paired and
does not have Developer Mode off (unknown, as on devices before iOS 16, passes).
devicectl also lists devices it remembers that are not plugged in. It does not
check the Developer Disk Image (Tapsmith mounts it when a test run starts) or
the macOS firewall (ios network configure --fix-firewall handles that).
Each check prints ✓, ⚠ (advisory, not blocking) or ✗ with the fix; the
command exits 1 if a required check fails, no device is listed, or any listed
device is unpaired or has Developer Mode off. When the
required checks pass, it ends with the steps it cannot check from the Mac
(trusting the developer certificate, turning off Auto-Lock) and notes that
tapsmith test finds the device and the runner itself. Either way it
ends by pointing at network capture: tapsmith ios network configure <udid>
for the device, tapsmith ios network setup-simulator for a simulator.
--json prints the same checks and the listed devices as { ok, checks, devices }
(see JSON output), with the same exit code.
tapsmith ios build-agent [--team-id <id>] [--cwd <path>] [--derived-data-path <path>] [-v]
Section titled “tapsmith ios build-agent [--team-id <id>] [--cwd <path>] [--derived-data-path <path>] [-v]”Build the signed TapsmithAgent XCUITest runner for physical devices with
your provisioning profile. Auto-detects the Apple Developer team ID from
Xcode’s preferences (or keychain) if --team-id is omitted. -v /
--verbose streams the raw xcodebuild output.
The agent source comes from <cwd>/ios-agent/ in a Tapsmith checkout (--cwd
points at one), or else from the copy the npm package extracts to
~/.tapsmith/ios-agent/. Build products go to .build-device inside that
source directory (ios-agent/.build-device, or ~/.tapsmith/ios-agent/.build-device) unless --derived-data-path says otherwise. Without iosXctestrun (or the TAPSMITH_IOS_XCTESTRUN environment
variable), tapsmith test uses the newest device runner under an
ios-agent/.build-device in or up to five levels above rootDir (a checkout),
and otherwise the one under ~/.tapsmith/ios-agent/.build-device (an npm
install), but only if the installed Tapsmith version built it. Upgrading
Tapsmith replaces ~/.tapsmith/ios-agent/ the next time Tapsmith builds an agent, so rebuild
after an upgrade; until then tapsmith test reports that the runner was built
by the earlier version. When the build finishes, the command says whether
tapsmith test will find the runner on its own. For a checkout build it also
prints an absolute iosXctestrun: line to pin it, and for a build the lookup will
not find (such as one with --derived-data-path) the line to add; it offers no
pin for the npm build, which an upgrade replaces. An iosXctestrun or TAPSMITH_IOS_XCTESTRUN
naming a file that does not exist stops the run before the agent starts, with
the path in the error.
tapsmith ios network
Section titled “tapsmith ios network”Network capture setup. Simulators capture through mitmproxy and a macOS
Network Extension (setup-simulator, once per Mac). Physical devices capture
through a Wi-Fi proxy profile (configure, then verify, once per device and
Wi-Fi network). See iOS network capture and
physical-device network tracing.
tapsmith ios network setup-simulator
Section titled “tapsmith ios network setup-simulator”First-run setup for iOS simulator network capture. Checks, one step at a
time, that mitmproxy is installed with Homebrew and that its Mitmproxy
Redirector Network Extension is registered and approved; for the approval step
it opens System Settings on the right pane and waits for you to allow it. Each
step prints what to do next, and every run ends by pointing physical-device
users at tapsmith ios setup-device.
Exits 0 when capture is ready, and also on a fresh Mac where the extension is not registered yet: that is expected, since the first simulator test run with network capture registers it and macOS then asks you to allow it. Exits 1 when mitmproxy is missing, the approval wait times out, or the extension’s state cannot be read.
npx tapsmith ios network setup-simulatortapsmith ios network configure <udid> [--refresh] [--ssid <name>] [--device-name <name>] [--fix-firewall]
Section titled “tapsmith ios network configure <udid> [--refresh] [--ssid <name>] [--device-name <name>] [--fix-firewall]”Generate a .mobileconfig profile that routes the physical device’s Wi-Fi
traffic through Tapsmith’s MITM proxy, and reveal it in Finder so you can
AirDrop it to the device. The command then prints the install walkthrough:
send and install the profile, trust the Tapsmith CA, enter the proxy (PAC) URL
in the device’s Wi-Fi settings, and verify. Decrypted capture is available for
clients that trust the Tapsmith CA; pinned or embedded-root clients may need
passthrough. --ssid targets a specific Wi-Fi network (defaults to the host’s
current SSID); --device-name sets the profile’s PayloadDisplayName;
--fix-firewall turns off macOS Application Firewall stealth mode (via sudo),
which otherwise drops the device’s connections to the proxy.
--refresh regenerates the profile after the Mac’s IP address or Wi-Fi network
changed. Its walkthrough starts with removing the old profile from the device,
and prints the new proxy URL to enter, since that URL carries the Mac’s IP.
tapsmith test warns with this command when it detects the drift.
tapsmith ios network verify <udid>
Section titled “tapsmith ios network verify <udid>”End-to-end sanity check that the installed profile plus the trusted CA actually produce decrypted HTTPS capture for a normal system-trust client. Starts the proxy, asks you to load an HTTPS page in Safari on the device, then reports whether Tapsmith saw the request and could decrypt the body. Exits non-zero on failure with fix-it hints for each failure mode.
tapsmith --version / tapsmith -v
Section titled “tapsmith --version / tapsmith -v”Print the Tapsmith version.
tapsmith --help / tapsmith -h / tapsmith help [command]
Section titled “tapsmith --help / tapsmith -h / tapsmith help [command]”Show the available commands, or one command’s options (tapsmith help test, same as tapsmith test --help). Nested commands work the same way: tapsmith help ios network configure is tapsmith ios network configure --help.
Help never runs the command: tapsmith --help init prints the command list rather than starting the wizard.
Video recording
Section titled “Video recording”Tapsmith records continuous screen video over the duration of each test,
mirroring Playwright’s video config (PILOT-114). Retained videos land in
<outputDir>/videos/ as <safe-test-name>-<timestamp>.mp4 and are surfaced
on TestResult.videoPath. The HTML reporter embeds an inline <video>
element and a download link in each test’s detail panel.
import { defineConfig } from 'tapsmith'
export default defineConfig({ video: 'retain-on-failure', // mode shorthand // — or — // video: { // mode: 'on', // size: { width: 1280, height: 720 }, // Android only // },})The supported modes are the same as trace:
| Mode | Behaviour |
|---|---|
off |
No recording (default). |
on |
Record every test, retain every video. |
retain-on-failure |
Record every test; keep videos only for failed tests. |
retain-on-first-failure |
Like retain-on-failure but limited to attempt 0. |
on-first-retry |
Record only on the first retry attempt. |
on-all-retries |
Record on every retry attempt (skips the first run). |
retain-on-failure-and-retries |
Record every test; keep the video for any run that failed or that is a retry. |
Recording cost: retain-on-failure still records (and pays the video
encode cost for) every test — it only deletes the file for passing tests
afterwards. on-first-retry starts no recorder at all on the first attempt,
eliminating encode load on healthy runs while still producing a video for
any test flaky or broken enough to be retried. The trade-off: it requires
retries >= 1 to ever produce a video (the runner warns at startup if
retries is 0), and the recorded retry won’t show first-attempt-only
failures. This mirrors Playwright’s on-first-retry caveat.
Implementation
Section titled “Implementation”| Platform | Recorder |
|---|---|
| Android | adb shell screenrecord (3-min hard cap per recording). |
| iOS Simulator | xcrun simctl io <udid> recordVideo --codec h264. |
| iOS physical device | ffmpeg -f avfoundation — requires ffmpeg on PATH. |
Android 3-minute cap: screenrecord truncates at 180 seconds. Recordings
of longer tests are truncated by the device-side encoder; the daemon emits
a one-time warning per test that exceeds the cap. Chained-segment recording
is on the roadmap.
iOS physical devices: ffmpeg’s AVFoundation backend captures the iPhone’s screen via the CoreMediaIO video device that appears on macOS once the device is paired in Xcode and you have accepted the “Trust This Computer” prompt. The daemon resolves the device by friendly name; if multiple iPhones are connected, name conflicts can cause the wrong device to be recorded. See ios-physical-devices.md.
size option: honoured on Android only (passed through as
screenrecord --size WxH). On iOS the daemon emits a one-time warning and
records at native resolution.

