@taqwright/taqwright
v1.2.1
Published
E2E mobile testing on the Playwright runner
Maintainers
Readme
E2E mobile UI testing on the Playwright runner, with a flat locator API on top of Appium 3.
✅ The 1.x API is stable. It follows semver — breaking changes land only in a new major. Install it with
npm i -D @taqwright/taqwright. Bug reports and feedback are very welcome — open an issue.
import { test, expect } from '@taqwright/taqwright';
test('User can login', async ({ mobile }) => {
await mobile.getByLabel('Username').fill('admin');
await mobile.getByLabel('Password').fill('password');
await mobile.getByText('Login').click();
await expect(mobile.getByText('Welcome')).toBeVisible();
});📚 Full documentation: taqwright.dev/docs — install taqwright, write and generate tests, run and debug them, and scale out in parallel.
Why Taqwright?
If you've used Playwright, you already know Taqwright. It keeps the Playwright-style API
you already write — getByRole, expect, test fixtures — and drives real iOS and Android
devices through Appium 3 over WebDriver. Record a test as you tap through the app with the
built-in codegen. Locate elements by role, text or label, and drop down to id, xpath,
UiAutomator, NSPredicate or class-chain when an app has no accessibility metadata. Run the
same spec against native, React Native and Flutter apps, on a local emulator or simulator
or on a cloud device provider such as BrowserStack, LambdaTest, Digital.ai Testing or pCloudy.
Features
- Playwright-style API —
mobile.getByRole('button').click(), just likepage.getByRole('button').click() - Cross-platform — one API for iOS and Android
- Native, React Native & Flutter — drive any app
- Auto-waiting — actions wait for elements to be visible, enabled, and stable before interacting
- Retry assertions —
expect(locator).toBeVisible()polls until satisfied or times out - Rich locators —
getByRole/getByText/getByLabel, plusxpath/ UiAutomator / NSPredicate / class-chain - Built-in codegen —
npx taqwright codegenrecords a test as you tap through the app - Cloud devices in ~10 seconds — point a project at BrowserStack, LambdaTest, Digital.ai Testing, or pCloudy with a few lines of config
- Auto-discover devices —
device.autoDiscoverfinds and assigns emulators/simulators to each worker - Parallel testing, handled — taqwright spawns and manages a dedicated Appium session per worker automatically
- Traces, video & reports — per-action traces, full-run videos, and Playwright reporters
- Test fixtures —
@taqwright/taqwrightadds amobilefixture to Playwright Test
Requirements
You need Node.js 24.x or newer — that one taqwright cannot install for you.
For Android, the recommended setup is one command, which handles everything else:
npx taqwright install --with-avdThat vendors a complete, self-contained toolchain — Temurin JDK 21, the Android
cmdline-tools and platform-tools (adb), Appium 3, the uiautomator2
driver, and an emulator — into taqwright's own directory. No sudo, no global npm
install -g, and no shell changes: your JAVA_HOME, ANDROID_HOME and
~/.zshrc/~/.bashrc are left untouched, and taqwright points only its own commands
at what it installed. It downloads roughly 700 MB, plus about 1 GB more for the
emulator, so give it a few minutes on a first run. Drop --with-avd if you'd rather
test on a physical device. npx taqwright init offers the same install as a prompt
while scaffolding, and npx taqwright doctor reports what is and isn't in place
(it only checks — it never installs anything).
You do not need to start Appium yourself. The scaffolded taqwright.config.ts
sets appium.autoStart: true, so npx taqwright test launches the server on the
configured port for you.
For iOS, the xcuitest driver is installed automatically on macOS, but the
Apple side of the stack cannot be: you need full Xcode (not just the Command
Line Tools) with its license accepted, and at least one iOS simulator runtime
(xcodebuild -downloadPlatform iOS). Run npx taqwright doctor to confirm.
Manual setup (fallback)
The managed install above is the supported, recommended path. These steps are the
fallback for when you'd rather own the toolchain yourself — you already have an
Android SDK, you need a specific Appium version, or you're on CI with its own
provisioning. taqwright uses whatever is on your PATH and skips the managed
directory entirely:
- A booted Android emulator, iOS simulator, or connected device.
- Appium 3.x (
npm i -g appium@^3), with the relevant driver installed:- Android:
appium driver install uiautomator2 - iOS:
appium driver install xcuitest
- Android:
- Platform tools on
PATH:adb(Android),xcrun(iOS, macOS only),java(JDK 17+, for UiAutomator2), withANDROID_HOMEandJAVA_HOMEexported. - Set
appium.autoStart: falsein your config if you want to run the server yourself onlocalhost:4723; otherwise taqwright still starts it for you.
Appium 2.x works on a best-effort basis but isn't covered by CI.
Install
npm install --save-dev @taqwright/taqwrightIn package.json it looks like:
"devDependencies": {
"@taqwright/taqwright": "^1.1.0"
}The package imports as @taqwright/taqwright and the CLI command is taqwright.
Quick start
npx taqwright init # interactive scaffolder — creates package.json, tsconfig, sample test
npx taqwright doctor # verify your env (adb, xcrun, java, appium)
npx taqwright devices # list local emulators / simulators
npx taqwright codegen # record a test as you tap through the app (Playwright-codegen-style)
npx taqwright test # run your testsStart from the sample project
Prefer a ready-made project? Clone the demo, install, and run it to see a passing suite right away.
Taqwright/taqwright-demo is a full taqwright suite for the bundled DemoApp (a Flutter app). It runs on local emulators / simulators or on real devices in the cloud via BrowserStack, and the app binaries (.apk / .app) ship in the repo so it works out of the box.
git clone https://github.com/Taqwright/taqwright-demo
cd taqwright-demo
npm install
npm test # run the default suiteParallel testing demo
The local Android suite ships four run modes, each its own project:
| Script | Project | Mode | Workers |
| --------------------- | ---------------- | ------------------------------------------------- | ------- |
| npm run test:single | android-single | one emulator, pinned udid (emulator-5554) | 1 |
| npm run test:pool | android-pool-2 | hand-written 2-emulator udid pool (5554+5556) | 2 |
| npm run test:auto1 | android-auto-1 | auto-detect host AVDs | 1 |
| npm run test:auto2 | android-auto-2 | auto-detect host AVDs | 2 |
npm run test:auto2 runs the android-auto-2 project — device.autoDiscover with workers: 2. taqwright finds your host AVDs, boots two of them, and fans the specs across both in parallel (one Appium server + one device per worker, with isolated driver ports). No hand-written device pool required.
It needs two AVDs available (Pixel_10_Pro_XL + Pixel_10_Pro_XL_2) — check with npx taqwright devices.
Configure
Create taqwright.config.ts at your project root:
import { defineConfig, Platform } from '@taqwright/taqwright';
export default defineConfig({
timeout: 30_000,
projects: [
{
name: 'android',
use: {
platform: Platform.ANDROID,
device: { provider: 'emulator', name: /Pixel 10 Pro XL/ },
buildPath: '/abs/path/to/app.apk',
appBundleId: 'com.example.app',
resetBetweenTests: true,
},
},
],
});Every defineConfig / use option is documented in the Configuration guide.
Cloud devices
Point a project's device.provider at a cloud grid and taqwright handles the app
upload, session capabilities and dashboard status reporting for you. The same
config drives both taqwright test and the taqwright inspect / codegen
inspector. Credentials are read from the environment — never from the config file.
| Grid | Env vars | buildPath for an already-uploaded build |
| -------------- | ---------------------------------------------------------------------- | ----------------------------------------- |
| browserstack | BROWSERSTACK_USERNAME / BROWSERSTACK_ACCESS_KEY | bs://<app-id> |
| lambdatest | LAMBDATEST_USERNAME / LAMBDATEST_ACCESS_KEY | lt://<app-id> |
| digitalai | DIGITALAI_CLOUD_SERVER / DIGITALAI_ACCESS_KEY (no username) | cloud:<bundleId> |
| pcloudy | PCLOUDY_USERNAME / PCLOUDY_API_KEY, optionally PCLOUDY_CLOUD_URL | pcloudy:<fileName> |
DIGITALAI_CLOUD_SERVER is your tenant URL (e.g. https://mycloud.experitest.com).
Every cloud test carries a taqwright-session attachment holding its grid session id, added when
the session starts, so a reporter can link each result to its session and video exactly, pass or
fail, without relying on the session name written to the dashboard.
PCLOUDY_CLOUD_URL defaults to https://device.pcloudy.com (Global). Set it to your
account's regional cloud — https://sg.pcloudy.com (Singapore) or
https://us.pcloudy.com (United States) — or to an enterprise cloud URL. The wrong
region fails the session with "plan doesnot support… or expired" or "App not
available" rather than an auth error. The inspector offers the same choice as a
Location picker, and shows each device's exact deviceFullName on its tile.
{
name: 'pcloudy',
use: {
platform: Platform.ANDROID,
device: {
provider: 'pcloudy',
name: 'Motorola Moto G5',
osVersion: '7.0.0',
// Required in practice: pCloudy device names end with an opaque
// per-device alias that cannot be derived from name + osVersion.
// Copy the exact string from the `taqwright inspect` device picker.
deviceFullName: 'Motorola_MotoG5_Android_7.0.0_ea8b0',
// durationInMinutes: 10, // booking window; must fit your account balance
},
// A local .apk/.ipa is uploaded on first use. pCloudy references builds by
// FILE NAME rather than by URL, and it appends its own timestamp on
// upload — `app.apk` is stored as `app-1786171090.apk`. Take the name from
// the upload log and reuse it as `buildPath` to skip re-uploading.
buildPath: '/abs/path/to/app.apk',
appBundleId: 'com.example.app',
},
}Set workers above 1 on a cloud project to fan out independent sessions — cloud
grids need no device.pool. Note the build is uploaded once per worker, so
pre-uploading and using the scheme-prefixed buildPath above avoids repeat
uploads.
API reference
A quick reference to the most-used API. See taqwright.dev/docs for the full reference.
Fixtures
import { test, expect } from '@taqwright/taqwright';
test('example', async ({ mobile }) => {
// `mobile` drives the device; `mobile.raw` is the underlying WebDriver client.
});mobile— the device under test (aMobile).rawDriver— the raw WebDriver client (escape hatch); alsomobile.raw.
Locators — mobile.getBy*(…) → Locator
| Method | Notes |
| --------------------------------- | -------------------------------------------------------------- |
| getByText(text \| RegExp) | Visible text (exact or partial) |
| getByLabel(label) | Accessibility label |
| getById(id) / getByTestId(id) | resource-id (Android) / accessibility-id (iOS) |
| getByPlaceholder(text) | Input placeholder |
| getByRole(role, { name? }) | Semantic role — button, link, textbox, switch, image |
| getByType(type) | Class / type name |
| getByXpath(xpath) | XPath expression |
| getByUiSelector(selector) | Android UiAutomator2 (Android-only) |
| getByPredicate(predicate) | iOS NSPredicate (iOS-only) |
| getByClassChain(chain) | iOS class chain (iOS-only) |
| getByCss(selector) | CSS (WebView contexts) |
Refine a Locator: .filter({ has, hasNot, hasText, hasNotText, visible }), .first() / .last() / .nth(n), .locator(child), .and(other) / .or(other), .all(), .count().
Actions — on a Locator
click() / tap() · doubleTap() · longPress() · fill(value) · clear() · pressSequentially(text, { delay }) · press(key) · focus() · blur() · check() / uncheck() · selectOption(value) · scrollIntoView() · swipeUp/Down/Left/Right() · pinchIn() / pinchOut() · dragTo(target) / dragToPoint({ x, y }) · screenshot()
Queries — on a Locator
getText() · getValue() · getAttribute(name) · isVisible() · isEnabled() · isChecked() · isFocused() · isEditable() · isInViewport() · isEmpty() · boundingBox() · count() · all() · allInnerTexts()
Assertions — expect(locator).… (auto-retrying)
toBeVisible() · toBeHidden() · toBeEnabled() · toBeDisabled() · toBeChecked() · toBeEditable() · toBeFocused() · toBeAttached() · toBeInViewport() · toBeEmpty() · toHaveText(string | RegExp) · toContainText(string) · toHaveValue(string | RegExp) · toHaveCount(n) · toHaveAttribute(name, value)
.not negates the boolean-state matchers (e.g. expect(loc).not.toBeChecked()). The same checks are also available directly on a locator as locator.assert*().
Device & gestures — mobile.…
- Screen:
tap(point),swipe(dir),scroll(dir),screenshot(),getScreenSize(),setOrientation(o)/getOrientation() - Keys:
press(key),pressButton(HOME | BACK | VOLUME_UP | …),hideKeyboard(),isKeyboardShown(),goBack() - App:
installApp(path)/uninstallApp(),launchApp()/activateApp()/terminateApp(),isAppInstalled(),queryAppState(),backgroundApp(s),getCurrentApp() - WebView / context:
getContexts()/getContext()/switchContext(name),switchToWebView()/switchToNative() - System:
getClipboard()/setClipboard(t),getLocation()/setLocation(loc),setPermission()(Android),getNetworkConnection()/setNetworkConnection()(Android),pushFile()/pullFile(),getDeviceLogs(),getDeviceTime(),setLocale(),openDeepLink(url),acceptAlert()/dismissAlert()/getAlertText(),startScreenRecording()/stopScreenRecording() - Escape hatch:
mobile.raw— the underlying WebDriver client
CLI
Run taqwright <command> --help for the full option list.
taqwright test [filter...]
Run your tests (delegates to the Playwright runner; unknown flags are forwarded).
-c, --config <file> · --project <name...> · --grep <re> / --grep-invert <re> · --reporter <r> · --retries <n> · --timeout <ms> · --workers <n> · --shard <x/n> · --list · --pass-with-no-tests
taqwright init [dir]
Scaffold a new project (interactive).
--platform <android|ios|both> · --test-dir <name> · --install / --no-install · --install-toolchain / --no-install-toolchain · --with-avd / --no-with-avd · --demo-app / --no-demo-app · -y, --yes
taqwright codegen
Open the inspector and auto-start recording on Connect (alias of inspect --record).
-c, --config <file> · --project <name> · --port <n> (default 4280) · --host <host> (default localhost) · --no-open
taqwright devices
List connected devices, simulators, and emulators.
taqwright doctor
Check your environment for mobile-development readiness. --json for machine-readable output.
taqwright install
Auto-install the Android toolchain (JDK + SDK + Appium) — zero-touch.
--force (reinstall) · --with-avd (also create an emulator) · --print-env (print shell export lines)
taqwright show-report [report]
Serve the HTML report. --host <host> (default localhost) · --port <port> (default 9323)
taqwright merge-reports <dir>
Merge blob reports into a single report. --reporter <r> · -c, --config <file>
taqwright telemetry [status|enable|disable]
Shows whether anonymous usage telemetry is on and why, or turns it on or off for this machine. See Telemetry.
Telemetry
taqwright collects anonymous usage counts to learn how it is used: which versions, platforms and providers, and how big and how long test runs are. It is on by default and easy to turn off.
What is sent: one small event at the end of each taqwright test run, and nothing else:
| Field | Example | Values |
| ----------- | ------------- | ------------------------------------------------------------------------------------ |
| v | 1.3.0 | the taqwright version |
| node | 24 | the Node.js major version |
| os | darwin | darwin, linux, win32 or other |
| ci | true | whether the run is in CI |
| cmd | test | the command |
| platforms | ["android"] | android and/or ios, for the projects that ran |
| provider | lambdatest | local, emulator, lambdatest, browserstack, digitalai, pcloudy or other |
| tests | 11-50 | how many tests ran: 1-10, 11-50, 51-200 or 200+ |
| duration | 1-5m | how long the run took: <1m, 1-5m, 5-15m or 15m+ |
| passed | true | whether the run passed |
What is never sent: test names, file paths, app or bundle ids, device names, error messages, hostnames, user or repository names, environment variables — and no id of any kind, so one run cannot be linked to another.
Where it goes: https://www.taqwright.ai/api/telemetry, run by the taqwright team. The server
keeps only daily counts of these values (never individual events, never your IP address) for
13 months. Sending never slows a run by more than about a second, never retries, never prints
anything and never changes a run's result. A --list run, or one with no tests, sends nothing.
Turning it off — any one of these, and an off switch always wins:
export TAQWRIGHT_TELEMETRY=0 # this shell / CI job
export DO_NOT_TRACK=1 # the cross-tool convention
npx taqwright telemetry disable # this machine (saved in ~/.taqwright/telemetry.json)// taqwright.config.ts — this project, for everyone who runs it
export default defineConfig({ telemetry: false, projects: [/* … */] });npx taqwright telemetry status says whether it is on, and why.
Guides
| Guide | What it covers |
| --------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| Installation | Set up a configured project ready for its first test. |
| Writing tests | Drive live devices and assert UI state with auto-waiting locators. |
| Generating tests | Record tests in a browser-based device viewer that ranks selectors. |
| Running & debugging | Per-action traces, full-run videos, and Playwright reporters. |
| Parallel runs | Scale out with local device pools or cloud providers. |
| Configuration | Every defineConfig / use option. |
Acknowledgements
- Appium — the underlying mobile automation server taqwright drives.
- Playwright — test runner, reporter, fixture machinery.
- AppWright — thanks for the inspiration.
License
Apache-2.0
