Skip to content

Configuration

Tapsmith is configured through a tapsmith.config.ts file in your project root. All options have sensible defaults, so a minimal config is just a few lines.

Create tapsmith.config.ts in your project root:

import { defineConfig } from "tapsmith";
export default defineConfig({
apk: "./app/build/outputs/apk/debug/app-debug.apk",
});

Tapsmith also supports tapsmith.config.js and tapsmith.config.mjs if you prefer plain JavaScript.

For clean emulators or CI devices, apk is the important setting because it lets Tapsmith install the app under test itself. activity is optional and mainly useful as a stability hint when you want Tapsmith to launch a specific activity. For emulator-managed runs, the recommended path is launchEmulators + avd.

OptionTypeDefaultDescription
platform"android" | "ios"auto-detectedTarget platform. Auto-detected from apk (Android) or app (iOS).
apkstringundefinedPath to the APK under test (Android).
appstringundefinedPath to the .app bundle under test (iOS). For simulators, build a simulator-slice .app. For physical devices, the .app must be code-signed with a profile matching the device — see iOS physical devices.
packagestringundefinedPackage name (Android) or bundle identifier (iOS) of the app under test. When set, Tapsmith launches the app before tests.
activitystringundefinedOptional activity name to launch (Android only). Usually not needed; Tapsmith will try the default launcher activity automatically.
timeoutnumber30000Default timeout in milliseconds for actions and assertions.
typingDelaynumber0Delay in milliseconds between keystrokes when typing text. Helps prevent dropped characters on slow CI simulators/emulators. Can be overridden per-call via type("text", { delay: 50 }).
retriesnumber0Number of times to retry a failed test.
screenshotScreenshotMode"only-on-failure"When to capture screenshots: "always", "only-on-failure", or "never".
testMatchstring[]["**/*.test.ts", "**/*.spec.ts"]Glob patterns for discovering test files.
daemonAddressstring"localhost:50051"Address of the Tapsmith daemon (host:port).
daemonBinstringundefinedPath to the tapsmith-core binary. If unset, Tapsmith auto-resolves it from several common locations (including npm packages and monorepo build outputs) before falling back to PATH.
devicestringundefinedExplicit single-device override. Useful for debugging or forcing one specific physical device/emulator/simulator.
deviceStrategy"prefer-connected" | "avd-only"contextualOptional override for device selection (Android). Defaults to "avd-only" when avd is set, otherwise "prefer-connected".
rootDirstringprocess.cwd()Working directory for test discovery.
outputDirstring"tapsmith-results"Directory for screenshots and other artifacts.
agentApkstringauto-resolvedPath to the Tapsmith agent APK (Android). When installed via npm, the bundled APK is used automatically. Only set this to override with a custom build.
agentTestApkstringauto-resolvedPath to the Tapsmith agent test APK (Android). When installed via npm, the bundled APK is used automatically. Only set this to override with a custom build.
iosXctestrunstringundefinedPath to the iOS agent .xctestrun file. Simulator and device builds are NOT interchangeable — build one with xcodebuild -destination 'platform=iOS Simulator,…' for simulators, or tapsmith build-ios-agent for physical devices. Use one project per target with its own iosXctestrun.
simulatorstringundefinediOS simulator name or UDID. Run xcrun simctl list devices to see available simulators. For physical iOS devices, use device with the UDID instead — see iOS physical devices.
reporterReporterConfigauto-detectedReporter output configuration. Defaults to list locally and dot in CI.
workersnumber1Number of parallel workers. Each worker needs its own device/emulator/simulator.
shard{ current: number; total: number }undefinedShard specification for splitting a run across multiple machines. Usually set via --shard=x/y.
launchEmulatorsbooleanfalseAutomatically launch Android emulators to fill the requested worker count.
avdstringundefinedAVD name to use for launchEmulators (Android). When set, Tapsmith launches repeated instances of this AVD.
traceTraceMode | Partial<TraceConfig>"off"Trace recording mode. See TraceMode below.
videoVideoMode | Partial<VideoConfig>"off"Continuous video recording of the device screen. See VideoMode below.
grepRegExp | RegExp[]undefinedRun only tests whose fullName (describe > test) matches at least one of these regular expressions. Mirrors Playwright’s grep and the --grep / -g CLI flag.
grepInvertRegExp | RegExp[]undefinedSkip tests whose fullName matches any of these regular expressions. Mirrors Playwright’s grepInvert and the --grep-invert CLI flag.
baseURLstringundefinedBase URL for the request fixture. Relative paths in request.get("/path") are resolved against this.
extraHTTPHeadersRecord<string, string>undefinedDefault headers sent with every request fixture call (e.g., Authorization). Per-request headers override these when names collide.
doubleTapIntervalnumber100Default interval in milliseconds between the two taps in doubleTap(). Can be overridden per-call via doubleTap({ intervalMs: 150 }).
resetAppDeepLinkstringundefinedDeep link URI to navigate to between test files for a soft app reset. When set, Tapsmith opens this deep link instead of force-stopping and relaunching the app between files. Faster than a full restart when the app has a “reset state” deep link handler.
resetAppWaitMsnumberundefinedTime in milliseconds to wait after navigating the resetAppDeepLink before starting the next test file. Gives the app time to finish resetting.
testIgnorestring[][]Glob patterns for excluding test files from discovery. Files matching any pattern are skipped even if they match testMatch.
type ScreenshotMode = "always" | "only-on-failure" | "never";
  • "always" — Capture a screenshot after every test, pass or fail.
  • "only-on-failure" — Capture a screenshot only when a test fails. This is the default.
  • "never" — Never capture screenshots.
type ReporterDescription = string | [string, Record<string, unknown>];
type ReporterConfig = ReporterDescription | ReporterDescription[];

Built-in reporter names are:

  • "list"
  • "line"
  • "dot"
  • "json"
  • "junit"
  • "html"
  • "github"
  • "blob"

Examples:

reporter: "list"
reporter: ["json", { outputFile: "tapsmith-report.json" }]
reporter: [["html", { outputFolder: "tapsmith-report" }], "list"]
type TraceMode = "off" | "on" | "on-first-retry" | "on-all-retries" | "retain-on-failure" | "retain-on-first-failure";
  • "off" — No tracing.
  • "on" — Record and keep traces for every test.
  • "on-first-retry" — Record traces only on the first retry of a failed test.
  • "on-all-retries" — Record traces on every retry.
  • "retain-on-failure" — Always record, but delete the trace zip if the test passes.
  • "retain-on-first-failure" — Always record, but only keep traces for the first failure (attempt 0).

For fine-grained control, pass an object instead of a mode string:

interface TraceConfig {
mode: TraceMode; // Recording mode (default: "off")
screenshots: boolean; // Capture before/after screenshots (default: true)
snapshots: boolean; // Capture view hierarchy XML (default: true)
sources: boolean; // Include test source files (default: true)
attachments: boolean; // Include user attachments (default: true)
network: boolean; // Capture HTTP/HTTPS traffic via proxy (default: true)
deviceLogs: boolean; // Stream device logs — Android logcat / iOS
// simulator syslog — into the trace (default: true)
networkHosts?: string[]; // Hostname allowlist (glob patterns). When set,
// only entries whose host matches a pattern are
// kept in the trace archive.
networkIgnoreHosts?: string[]; // Hostname denylist (glob patterns). Entries
// whose host matches a pattern are dropped.
// Combines with `networkHosts`: entry is kept
// iff it matches allow AND does NOT match deny.
}

When network is enabled, the Rust daemon starts an HTTP proxy and configures the device to route traffic through it. HTTPS traffic is decrypted using an auto-generated CA certificate installed on the device.

On Android emulators the HTTP proxy is set globally (settings put global http_proxy), so every app and system process on the emulator routes through it — including Google Play Services, connectivity checks, push, ad attribution, etc. On physical iOS a system-wide Wi-Fi proxy has the same characteristic. (iOS simulators are the exception: the macOS Network Extension redirector filters per-PID.)

Two patterns, pick whichever fits:

Allowlist — only keep entries from your app’s hosts:

trace: {
mode: "on",
networkHosts: ["*.myapp.com", "api.partner.example"],
}

Denylist — keep everything except known-noisy hosts:

trace: {
mode: "on",
networkIgnoreHosts: [
// Android emulator system traffic
"connectivitycheck.gstatic.com",
"*.googleapis.com",
"play.googleapis.com",
"mtalk.google.com",
"android.clients.google.com",
"www.google.com",
"clients*.google.com",
// iOS background (physical devices only)
"*.apple.com",
"*.icloud.com",
"captive.apple.com",
],
}

Both accept glob patterns (* matches any single segment, ** or a leading *. matches any number). Matching is case-insensitive. When both are set, the entry is kept iff it matches the allowlist AND does NOT match the denylist — deny wins.

Example:

trace: {
mode: "retain-on-failure",
screenshots: true,
snapshots: true,
sources: false,
network: true,
deviceLogs: true,
}
type VideoMode = "off" | "on" | "on-first-retry" | "on-all-retries" | "retain-on-failure" | "retain-on-first-failure";

The mode set is identical to TraceMode and the semantics match exactly — "on" records every test, "retain-on-failure" records but discards passing-test videos, etc.

interface VideoConfig {
mode: VideoMode; // Recording mode (default: "off")
size?: { width: number; height: number }; // Output resolution. Honoured on
// Android only (passed as
// `screenrecord --size WxH`); iOS records at
// native resolution and emits a one-time
// warning when `size` is set.
}

Recordings land in <outputDir>/videos/ as MP4 files and are surfaced as TestResult.videoPath. The HTML reporter embeds them inline. Implementation: Android uses adb shell screenrecord (3-min hard cap per recording, accepted in v1); iOS Simulator uses xcrun simctl io recordVideo; iOS physical devices use ffmpeg -f avfoundation and require ffmpeg on PATH. See the full reference at api-reference.md#video-recording.

import { defineConfig } from "tapsmith";
export default defineConfig({
apk: "./app-debug.apk",
});
import { defineConfig } from "tapsmith";
export default defineConfig({
app: "./build/MyApp.app",
});
import { defineConfig } from "tapsmith";
export default defineConfig({
apk: "./app-debug.apk",
timeout: 15_000, // 15 seconds instead of 30
});
import { defineConfig } from "tapsmith";
export default defineConfig({
apk: "./app-release.apk",
package: "com.example.myapp",
});

If your app has an unusual launcher setup, you can also provide activity, but most apps do not need it:

import { defineConfig } from "tapsmith";
export default defineConfig({
apk: "./app-release.apk",
package: "com.example.myapp",
activity: ".MainActivity", // Optional
});
import { defineConfig } from "tapsmith";
export default defineConfig({
apk: "./app/build/outputs/apk/debug/app-debug.apk",
timeout: 60_000, // Longer timeout for slower CI emulators
retries: 2, // Retry failed tests up to 2 times
screenshot: "always", // Capture screenshots for every test
outputDir: "test-artifacts", // CI-friendly output directory
reporter: ["junit", { outputFile: "tapsmith-junit.xml" }],
});
import { defineConfig } from "tapsmith";
export default defineConfig({
app: "./build/MyApp.app",
package: "com.example.myapp",
simulator: "iPhone 17",
timeout: 60_000,
retries: 2,
screenshot: "always",
outputDir: "test-artifacts",
reporter: ["junit", { outputFile: "tapsmith-junit.xml" }],
});
import { defineConfig } from "tapsmith";
export default defineConfig({
apk: "./app-debug.apk",
testMatch: [
"e2e/**/*.test.ts",
"integration/**/*.spec.ts",
],
});

This is the recommended setup for parallel local or CI runs when you want Tapsmith to manage emulator instances for you:

import { defineConfig } from "tapsmith";
export default defineConfig({
apk: "./app-release.apk",
package: "com.example.myapp",
workers: 4,
launchEmulators: true,
avd: "Pixel_9_API_35",
});

With this setup, Tapsmith will try to launch repeated read-only instances of the same AVD for all workers.

If you want the opposite behavior, set deviceStrategy: "prefer-connected" to let Tapsmith reuse unrelated healthy connected devices first even when avd is configured.

If you need to reproduce an issue on one known device, set device or use the --device CLI flag:

import { defineConfig } from "tapsmith";
export default defineConfig({
apk: "./app-debug.apk",
device: "emulator-5554",
});

The CLI flag takes precedence over the config file:

Terminal window
# Overrides the device from config
npx tapsmith test --device R5CR10XXXXX

For multi-worker runs, prefer launchEmulators + avd instead of device.

If you are running the Tapsmith daemon on a different host or port:

import { defineConfig } from "tapsmith";
export default defineConfig({
apk: "./app-debug.apk",
daemonAddress: "192.168.1.100:50051",
});

If you build the Tapsmith agent artifacts outside the default location, point Tapsmith at them explicitly:

import { defineConfig } from "tapsmith";
export default defineConfig({
apk: "./app-debug.apk",
agentApk: "../agent/app/build/outputs/apk/debug/app-debug.apk",
agentTestApk: "../agent/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk",
});

Mirroring Playwright’s projects concept, you can define named groups of test files that each target their own device. This is the canonical way to run the same suite against Android and iOS in a single tapsmith test invocation.

import { defineConfig } from "tapsmith";
export default defineConfig({
// Top-level fields are inherited by every project as defaults.
package: "com.example.app",
timeout: 30_000,
projects: [
{
name: "Pixel 6",
use: {
platform: "android",
avd: "Pixel_6_API_34",
apk: "./android/app-debug.apk",
launchEmulators: true,
},
},
{
name: "iPhone 16",
use: {
platform: "ios",
simulator: "iPhone 16",
app: "./ios/MyApp.app",
iosXctestrun: "./ios-agent/TapsmithAgent.xctestrun",
},
},
],
});

Each project provisions its own device, daemon, and agent. There are two ways to control parallelism:

Global budget (Playwright-style): the top-level workers field is split across projects proportionally to file count, with at least 1 per project.

export default defineConfig({
workers: 4,
projects: [
{ name: "Pixel 6", use: { /* ... */ } },
{ name: "iPhone 16", use: { /* ... */ } },
],
});

Explicit per-project workers (recommended for multi-device configs): each project sets its own workers count. These are additive — they do not consume from the global budget — so you can mix explicit and unset projects in the same config.

export default defineConfig({
projects: [
{ name: "Pixel 6", workers: 2, use: { /* ... */ } },
{ name: "iPhone 16", workers: 1, use: { /* ... */ } },
],
});

With tapsmith test, the Android project runs on 2 devices and the iOS project runs on 1 — concurrently. The total worker count (3) is computed automatically.

If the total comes out to 1 (e.g. global workers: 1 and no per-project overrides), Tapsmith runs the projects sequentially, tearing down and re-provisioning the device between each — useful when you only have one machine and want to exercise both platforms in CI.

The same configuration also works with --ui and --watch. UI mode shows each project’s tests grouped under its name, and routes file execution to the matching device. Watch mode re-runs only the affected project’s files on its own device when you edit a test.

Inside a project, the use field accepts the same device-shaping fields as the top-level config (platform, avd, simulator, app, apk, package, iosXctestrun, launchEmulators, etc.) plus the existing timeout, screenshot, retries, trace, and appState overrides.

Note: A single project must not mix Android (avd/apk) and iOS (simulator/app) fields. Tapsmith validates this at startup.

Use sharding when you want to split a suite across multiple CI jobs:

import { defineConfig } from "tapsmith";
export default defineConfig({
apk: "./app-debug.apk",
shard: { current: 1, total: 4 },
});

In practice, most users set this via the CLI instead:

Terminal window
npx tapsmith test --shard=1/4

If your app supports a deep link that resets state (faster than a full force-stop/relaunch cycle), configure it:

import { defineConfig } from "tapsmith";
export default defineConfig({
apk: "./app-debug.apk",
package: "com.example.myapp",
resetAppDeepLink: "myapp://reset",
resetAppWaitMs: 1000, // Wait 1s after the deep link for the reset to complete
});

Between test files, Tapsmith navigates the deep link instead of force-stopping and relaunching the app. This is significantly faster for apps that support it.

Configure defaults for the request fixture used in tests:

import { defineConfig } from "tapsmith";
export default defineConfig({
apk: "./app-debug.apk",
baseURL: "https://api.example.com",
extraHTTPHeaders: {
Authorization: "Bearer my-ci-token",
"X-Test-Run": "true",
},
});

With baseURL set, relative paths in request.get("/users") resolve to https://api.example.com/users. Per-request headers override extraHTTPHeaders when names collide.

Tapsmith searches for configuration files in this order:

  1. tapsmith.config.ts
  2. tapsmith.config.js
  3. tapsmith.config.mjs

If no config file is found, Tapsmith uses the default values for all options.

For .ts config files, Tapsmith relies on tsx or ts-node being available in your environment. If you installed Tapsmith via npm, this should work out of the box.