Mobile tests that feel
like web tests.
Tapsmith is a test framework for native iOS and Android. We took the parts of Playwright that engineers actually love: the locators, the auto‑waiting, the trace viewer, and rebuilt them for real devices.
import { test, expect } from "tapsmith"
test("user can complete checkout", async ({ device }) => {
await device.getByRole("button", { name: "Add to cart" }).tap()
await device.getByText("Checkout").tap()
// mock the payment API
await device.route("**/api/payment", async (route) => {
await route.fulfill({ json: { status: "success" } })
})
await expect(device.getByText("Order Confirmed")).toBeVisible()
})
Open source under the Apache 2.0 licence. Native agents on XCUITest and UIAutomator2, no browser shims or half-working bridges.
The pain you're used to, gone.
Read the guide →Mock the network without a mock server.
In Detox, mocking an API means setting up a separate server and managing fixtures. In Tapsmith, it’s device.route("**/api/payment", r => r.fulfill({ json })). One line, inline, done.
Locators that survive redesigns.
No fragile XPaths or test IDs sprinkled through production code. getByRole("button", { name: "Sign in" }) finds the element the same way a screen reader does, on both platforms, from one test file.
Replay the failure, don’t reconstruct it.
Every run records a trace: every tap, every network call, every frame. When a test fails at 3 am, open the trace viewer and scrub through it like a video instead of staring at a stack trace.
How it stacks up
We've all tried the others.
This is a snapshot, not a sales pitch. Every tool in this list does something well; we just think the table tilts our way for most teams shipping consumer mobile apps in 2026.
✓ supported ◐ partial / via plugin — not available
An API you can guess your way through.
// Same readable API across platforms
await device.getByRole("button", { name: "Sign in" }).tap()
await device.getByLabel("Email").type("[email protected]")
await device.getByText("Forgot password?").tap() // Mock any HTTP/HTTPS call, no proxy setup
await device.route("**/api/feed", route =>
route.fulfill({ json: fixtures.emptyFeed })
)
await device.route("**/payment", route => route.abort()) // Precise gesture control
await device.swipe("left")
await device.getByRole("image").pinchOut({ scale: 2 })
await device.getByLabel("Reorder").dragTo(device.getByText("Pinned")) Early access
Tapsmith is in early access. If you've tried it on a real project, we'd love to feature your experience here.
Developer tools
See exactly what happened, and when.
Trace viewer
Every test run records a full trace. Scrub through a timeline of screenshots, see every tap and gesture overlaid on the screen, inspect every network request and response. When a test fails in CI, download the trace and replay it locally. No guessing, no "works on my machine."
npx tapsmith show-trace trace.zip
UI mode
A browser-based interactive runner. Click any test to run it, see results stream in live, browse traces and screenshots inline, and mirror the device screen, all without leaving the browser.
npx tapsmith test --ui
Spend the rest of the afternoon on something more interesting.
Install Tapsmith, point it at your app, and write your first test in five minutes.