Android testing
The obsidian-android-appium transport runs tests against Obsidian Mobile on an Android emulator or a
real device, via Appium WebView injection.
-
Install Android Studio, which includes the Android SDK and the
adbcommand-line tools. -
Create an Android Virtual Device (AVD):
- Device Manager → Create Virtual Device.
- Pick a phone profile (e.g. Pixel 7) and a system image (e.g. API 34).
- Give it a name (e.g.
obsidian_test) — that is the value you pass asavdName. - Provision it deliberately. Android Studio’s defaults are not enough; see AVD provisioning and apply it before you start using the device.
- You do not need to start the emulator manually — the harness auto-starts it.
To list existing AVD names:
Terminal window emulator -list-avds -
Install Obsidian on the emulator (Play Store or APK sideload) and grant storage permission — either through the app’s prompt or over
adb:Terminal window adb shell appops set md.obsidian MANAGE_EXTERNAL_STORAGE allow -
(Optional) Install Appium and the UiAutomator2 driver:
Terminal window npm install -g appiumappium driver install uiautomator2 -
Configure the runner:
vitest.config.ts export default defineConfig({test: {fileParallelism: false,globalSetup: ['obsidian-integration-testing/vitest-global-setup-plugin'],environmentOptions: {obsidianTransport: {type: 'obsidian-android-appium',appiumUrl: 'http://localhost:4723',avdName: 'obsidian_test'}}}});
AVD provisioning
Section titled “AVD provisioning”These are minimums, not suggestions. Following the setup above with Android Studio’s defaults produces a device that fails — and it fails in ways that look like plugin bugs, so the cost of getting this wrong is paid in debugging, not in an obvious error.
| Setting | Minimum | Android Studio’s default | Why |
|---|---|---|---|
disk.dataPartition.size | 16G | 6G | This is the one that actually bites — see below. |
hw.ramSize | 4096 | 2048 | The WebView has to become ready inside a fixed budget. |
vm.heapSize | 512 | 256 | Obsidian is a large WebView app. |
hw.cpu.ncore | 4+ | 4 | Raise it if the host has cores to spare; emulator startup is CPU-bound. |
Edit them in Device Manager → Edit → Show Advanced Settings, or directly in the AVD’s config.ini
(~/.android/avd/<name>.avd/config.ini); a size change needs a wipe of user data.
Why disk is the setting that matters. Every failed run leaks a temp-vault-* directory, and every
leaked vault stays registered for Obsidian to enumerate at startup — inside the same WebView-readiness
budget the run is already straining (see
Leftover cleanup). A full /data then produces
failures that look like anything but a full disk:
/dataat 92 % with 103 leftover vaults: runs failed in global setup withWEBVIEW_md.obsidiantiming out at the full 60 s. After a sweep the same context was found in 0.3 s./dataat 91 % with only 8 leftover vaults — the count alone is not the signal. The four disk-bound cases (the only ones creating folders and renaming files) timed out at webdriver’s 30 s wall, and the same four passed 6/6 in isolation on the same device.
Prefer a google_apis image over google_apis_playstore. A Play-Store image consumes most of a
default data partition on its own, and it blocks adb root (adbd cannot run as root in production builds) — so when /data does fill, you cannot inspect it to find out what is using the space.
pm trim-caches 5G recovers on the order of tens of megabytes and is the only lever left without root.
google_apis is smaller and does allow adb root; nothing in this harness needs the Play Store.
Health check — run this before blaming the plugin:
adb shell df -h /dataadb shell ls -d /sdcard/Documents/temp-vault-* | wc -lAnd apply the isolation rule: a suite that fails in the aggregate and passes alone is the device, not the code.
Options
Section titled “Options”Besides the required appiumUrl and avdName, the transport accepts these optional knobs, all with
sensible defaults:
| Option | Purpose | Default |
|---|---|---|
appId | App package (Android) or bundle ID (iOS). | 'md.obsidian' |
appiumStartTimeoutInMilliseconds | Max wait for the auto-started Appium server to become ready; only when the harness auto-starts it. | 180000 |
deviceIdleTimeoutInMilliseconds | Max wait after boot for a started emulator to go idle before the session; avoids inflated cold setup. 0 skips. | 60000 |
isAppiumConsoleVisible | Show the auto-started Appium server console window and live output. Hidden and quiet by default. | false |
isEmulatorVisible | Show the auto-started emulator window. Hidden (-no-window, headless) by default so it never steals focus. | false |
layoutReadyTimeoutInMilliseconds | Max wait for app.workspace.layoutReady after the vault (re)opens; raise on slow emulators. | 90000 |
leftoverMaxAgeInMilliseconds | Age gate for the host leftover sweep; the device sweep is unconditional. | 7200000 |
sessionConnectionRetryTimeoutInMilliseconds | Max wait to establish the Appium session (UiAutomator2 install + app launch); the dominant startup cost. | 180000 |
shouldAutoInstallAppiumDependencies | Auto-install missing Appium and the UiAutomator2 driver before auto-starting the server (global npm install -g). | true |
shouldAutoStartAppium | Auto-start the Appium server when it is not already reachable. | true |
shouldSweepLeftovers | Remove the temp vaults / instance profiles earlier runs leaked. | true |
vaultBasePath | Base device path where Obsidian stores vaults. | '/sdcard/Documents/' |
webviewTimeoutInMilliseconds | Max wait for the WebView context after the Appium session starts. | 60000 |
Troubleshooting
Section titled “Troubleshooting”“Process system isn’t responding”
Section titled ““Process system isn’t responding””A resource-starved emulator can raise a “Process system isn’t responding” ANR dialog during boot. If
it appears before Appium attaches, nothing can dismiss it and the run fails intermittently. As soon as the
device reports sys.boot_completed, the harness runs
adb shell settings put global hide_error_dialogs 1 so Android no longer draws crash/ANR dialogs. That
narrows but cannot fully close the race — an ANR that fires between boot completing and that command still
slips through. To eliminate it entirely, boot the AVD once, run the command yourself, save a snapshot, and
always boot from that snapshot. Either way, an ANR signals the emulator is under-provisioned, so check it
against AVD provisioning and confirm hardware acceleration (emulator -accel-check).
“Android AVD … not found” / “Appium server … exited during startup”
Section titled ““Android AVD … not found” / “Appium server … exited during startup””The Android setup fails fast, rather than spinning out a timeout, when the toolchain cannot be brought up — and names what is missing:
Android AVD "<name>" not found. Available AVDs: ...— theavdNameyou passed does not exist. Runemulator -list-avds, then either pointavdNameat a listed AVD or create the one you want (Android Studio Device Manager, oravdmanager create avd). AVD creation is not automated: it needs a system-image download, license acceptance, and hardware/API-level choices.Auto-started Appium server ... during startup/... did not become ready ...— the harness auto-started Appium (npx --no-install appium) but it exited or never responded; the message appends the captured server output. Usually a missing or broken toolchain: passisAppiumConsoleVisible: trueto watch the live server log, or manage Appium yourself (shouldAutoStartAppium: false, withappiumUrlpointing at your own running server).Appium was installed ... but is still not resolvable ...— the auto-install rannpm install -g appium, but the npm global bin directory is not onPATH(common with scoop- or nvm-managed Node). Add it toPATH(seenpm config get prefix), or setshouldAutoInstallAppiumDependencies: falseand install Appium yourself.
“Obsidian layout did not become ready”
Section titled ““Obsidian layout did not become ready””Registering a vault reloads the page, triggering a full Obsidian re-init — reopen the vault and reload
every plugin, the heaviest startup step. On a cold-booted or under-provisioned emulator that can exceed
the default 90000 ms budget and fail setup with
Obsidian layout did not become ready within 90000ms. Run the health check in
AVD provisioning first (a full /data presents exactly like this), then bring the
AVD up to the minimums there and, if still needed, raise the budget with
layoutReadyTimeoutInMilliseconds. It is headroom, not a substitute for adequate provisioning.