Playwright-style tests for real Android and iOS apps
Astur drives your app through native agents, UIAutomator on Android and XCUITest on iOS, with no Appium server in between. You write tests with the Playwright Test API you already know, and run them on emulators, simulators and real devices.
import { test, expect } from '@astur-mobile/test';
test('sign in', async ({ device }) => {
await device.getByLabel('Email').fill('qa@example.com');
await device.getByRole('button', { name: 'Sign in' }).tap();
await expect(device.getByText('Welcome')).toBeVisible();
});
It is built on Playwright Test, so retries, projects, reporters, the HTML report and the VS Code play button all work the way they already do.
Why Astur
- No server in the middle. Tests talk to a native agent on the device: UIAutomator on Android, XCUITest on iOS. No Appium server, no WebDriver bridge.
- The API you know.
getByRole,getByLabel,expect(...).toBeVisible(), fixtures, projects, retries: Playwright Test, pointed at a phone. - Whatever your app is built in. Native SDKs, React Native and Expo, Flutter, Jetpack Compose, SwiftUI, .NET MAUI, plus real DOM control of in-app WebViews and mobile Chrome and Safari.
- See before you write. The inspector mirrors the device, shows the live tree, ranks locators, and records taps into a spec you keep.
- Results you can trust. Assertions wait for the condition and fail when the device cannot be read. An action whose response is lost fails instead of running twice.
- Secure by default. Every agent and the inspector require a per-session token.
Quick start
npm create astur@latest
The scaffolder asks what you target and writes a working config and a first test. Then:
npx astur-mobile doctor # check your environment
npx astur-mobile test # run the suite
Adding Astur to an existing project instead:
npm install -D @astur-mobile/test astur-mobile @playwright/test
Requirements: Node.js 18 or newer. For Android, the SDK platform-tools (adb) and an emulator
or a device with USB debugging. For iOS, macOS with Xcode; real devices also need an Apple signing
team for the XCUITest runner. npx astur-mobile doctor checks all of it and says what is missing.
Writing a test
import { expect, test } from '@astur-mobile/test';
test.use({
astur: {
platform: 'android',
device: { kind: 'emulator' },
app: './apps/demo.apk'
}
});
test('login', async ({ device }) => {
await device.app.launch();
await device.getByLabel('Email').fill('qa@example.com');
await device.getByLabel('Password').fill('secret');
await device.getByRole('button', { name: 'Sign in' }).tap();
await expect(device.getByText('Welcome')).toBeVisible();
});
Finding elements
device.getByTestId('login-submit');
device.getByLabel('Email');
device.getByText('Welcome');
device.getByRole('button', { name: 'Sign in' });
device.getByPlaceholder('Search…');
device.getByType('android.widget.Button');
A screen of repeated rows needs more than one selector, so locators compose:
const row = device.getByType('Cell').filter({ hasText: 'Rye' });
await row.getByRole('button', { name: 'Add' }).tap();
device.getByRole('listitem').filter({ hasNotText: 'Sold out' }).first();
device.getByText('Retry').or(device.getByText('Try again'));
Scope to a parent, filter({ hasText, hasNotText, has, hasNot }), combine with and() / or(),
and pick with first() / last() / nth(i). See Locators.
For the rare element none of that can pin down, by.native({ ios, android }) sends a platform query
straight to the agent: an NSPredicate on iOS, a BySelector chain on Android.
Asserting
Assertions retry until they pass or time out, and every action auto-waits.
await expect(device.getByText('Welcome')).toBeVisible();
await expect(device.getByLabel('Email')).toHaveValue('qa@example.com');
await expect(device.getByRole('checkbox')).toBeChecked();
await expect(device.getByRole('menuitem')).toHaveCount(3);
await expect(device.getByTestId('hero')).toHaveScreenshot('hero.png');
toBeVisible, toBeHidden, toExist, toBeEnabled, toBeDisabled, toBeChecked, toBeSelected,
toBeFocused, toBeEmpty, toHaveText, toContainText, toHaveValue, toHaveLabel, toHaveType,
toHaveBounds, toHaveCount, toHaveScreenshot.
Gestures, state and the device
await device.getByTestId('card').dragTo(device.getByTestId('dropzone'));
await device.getByText('Footer link').scrollIntoView();
await device.getByText('Item').longPress();
await device.getByLabel('Email').inputValue();
await device.app.reset({ launch: true });
await device.orientation.landscape();
await device.screenshot({ path: 'test-results/home.png' });
What it can do
| Any native-rendering framework | Native SDK, React Native, Expo, Jetpack Compose, SwiftUI, .NET MAUI, NativeScript, Capacitor. Full table |
| Flutter | The live widget tree over the Dart VM service on Android; the accessibility tree on iOS |
| In-app WebViews | Real DOM automation with device.webContext(). WebViews |
| Mobile web | Drive a site in Chrome or Safari on the device itself. Mobile Web |
| Network observation | Assert on the HTTP calls an app makes, not just what it renders. Network |
| Visual comparison | toHaveScreenshot() with per-device baselines and masking. Visual Comparison |
| Inspector and codegen | Live element inspection and recorded specs. Inspector |
CLI
npx astur-mobile doctor # check the environment
npx astur-mobile devices # list devices, emulators, simulators
npx astur-mobile init # scaffold config and a first test
npx astur-mobile codegen # live inspector and spec recorder (alias: inspect)
npx astur-mobile screenshot # capture a device screen to a PNG
npx astur-mobile test # run the suite
Documentation
astur-mobile.github.io/Astur, also in Arabic and Spanish.
Getting Started · Configuration · Locators · Android · iOS · Platform Limits · Troubleshooting
Examples
Runnable Android and iOS suites live in examples/, and as a clonable starter in
astur-boilerplate. Both run against the Astur
demo app, which ships with them.
About the name
Astur is named after the astrolabe, the instrument that folded an observatory into something you could hold: turn the rete, sight a star along the alidade, read the answer off the limb. In tenth-century Aleppo, Mariam al-Asturlabiya was known for building them. Astur is the same idea for mobile testing: a lean native runtime in place of a heavy server stack.
Where a platform cannot do something, Astur says so instead of pretending. See Platform Limits.
Contributing and security
Contributions are welcome; see CONTRIBUTING.md. For vulnerabilities, follow SECURITY.md rather than opening a public issue.
If Astur is useful to you, a ⭐ on the repository helps other mobile teams find it.
Sponsors
Astur is built in the open and has no sponsors yet — be the first.
If Astur saves your team time, consider supporting development:
License
Apache 2.0. Copyright 2026 Amr Salem and Astur contributors.
"Astur" is a trademark; see the trademark policy.