Platforms
Native Android apps
Test a Jetpack Compose or Views app on an Android emulator, built from its own Gradle project, with no change to its code.
danwo tests a native Android app, written with Jetpack Compose or Views, the same way it tests an Expo app. It builds your Gradle project, routes the emulator's traffic through its proxy, and runs your tests, invariants and fault matrix against it.
You don't change the app's code. It keeps calling its real hosts over HTTPS. Android apps trust only the system's certificates by default, so danwo's build adds a network security config that also trusts the one it installs on the emulator. Your project's files are not touched.
Build the app
npx @danwo/cli build-android androidbuilt android/.danwo/builds/56bf08b42328c942.apk (dev.danwo.examples.compose) in 108 s (log: android/.danwo/builds/build-android.log)android is the folder with your Gradle settings file and gradlew. danwo:
- runs your wrapper,
./gradlew assembleRelease, with Android Studio's JDK whenJAVA_HOMEis unset; - hands Gradle an init script that adds danwo's network config to the app module, for this build only;
- signs an unsigned APK with the debug key, so it installs;
- reads the package name from the APK.
Gradle runs with --no-daemon and compiles Kotlin in its own process, so no daemon is left
running. An unchanged project isn't built again: the build is keyed by a hash of its sources.
| Option | What it does | Default |
|---|---|---|
--variant <name> | the variant to assemble: debug, freeRelease | release |
--force | builds again even when the sources are the same | off |
--verbose | prints Gradle's output | off |
What the build adds
The network config trusts the system's certificates and the user-installed ones, where danwo puts each run's, and allows plain HTTP:
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>It replaces any networkSecurityConfig your app sets, in danwo's build only. The init script adds
it through the Android Gradle plugin's variant API (sources.res and sources.manifests); it is
checked with AGP 9.4.
This build trusts user-installed certificates. Use it only for testing; ship your store builds from your own Gradle tasks.
Bind the emulator in the config
buildAndroid from @danwo/mobile does the same build from your config. It returns the APK and its
package for withAndroidDevice:
const build = await buildAndroid({ project: "android" })
return withAndroidDevice(backend, {
avd: "danwo_spike",
worker: workerIndex,
app: { package: build.package ?? "dev.danwo.examples.compose", apk: build.apk },
})Route your API's production host to the local copy, as on iOS, so the app calls the URL it ships
with. The full config is in examples/with-compose.
Make the screen findable
Compose reports a testTag to the accessibility tree only when the screen asks it to. Set
testTagsAsResourceId once, at the root:
Surface(modifier = Modifier.fillMaxSize().semantics { testTagsAsResourceId = true }) {
OutlinedTextField(
value = email,
onValueChange = { email = it },
label = { Text("Work email") }, // getByLabel("Work email")
modifier = Modifier.testTag("email"), // getByTestId("email")
)
}| Compose | Locator |
|---|---|
Modifier.testTag, with testTagsAsResourceId | getByTestId |
a text field's label | getByLabel |
Button { Text("Join") } | getByRole("button", { name: "Join" }) |
Modifier.semantics { heading() } | getByRole("heading") |
Text("You're on the list") | getByText |
In Views, the view's android:id is its test id and contentDescription its label.
What reaches the proxy
| What | Reaches the proxy |
|---|---|
HttpURLConnection, OkHttp, Ktor on OkHttp | yes: they use the device's proxy |
| a Custom Tab | yes |
| an app that pins its certificates | no: turn pinning off in the build you test |
| a client with its own proxy setting | only if it follows the system's |