AppiumTransport
Transport that communicates with Obsidian Mobile via Appium WebView JS injection.
Evaluates expressions by switching to the WEBVIEW_md.obsidian context and calling execute(). Manages vaults by writing to the WebView’s localStorage (which Obsidian uses as its vault registry on mobile) and pushing files to the device.
Import:
import { AppiumTransport } from 'obsidian-integration-testing';Signature:
export class AppiumTransport implements ObsidianTransportImplements: ObsidianTransport
Constructor
new AppiumTransport(config: AppiumTransportConfig)Creates a new Appium transport.
Properties
| Property | Type | Description |
|---|---|---|
| beginConsoleCapture? | () => Promise<ConsoleCaptureHandle | undefined> | Begins capturing the target's native console/error stream so a later ObsidianTransport.readConsoleCaptureSince can return everything logged since this call. Used to surface the *real* plugin-load error when Obsidian swallows it before the harness's loadPlugin monkey-patch sees it (the error then only exists in the renderer/WebView console). Optional and platform-specific: implemented by AppiumTransport (tails adb logcat); absent on DesktopCdpTransport (desktop errors are surfaced in-renderer, and a developer can open DevTools directly).(Inherited from ObsidianTransport) |
| captureScreenshot? | (params: CaptureScreenshotParams) => Promise<Uint8Array> | Captures a PNG screenshot of the running Obsidian instance. Optional and platform-specific, like the other members here. Implemented by DesktopCdpTransport (CDP Page.captureScreenshot, optionally with the viewport pinned to an exact size) and by AppiumTransport (takeScreenshot, always the device's native framebuffer — the size knobs are ignored, so a mobile capture is sized by choosing an AVD with the wanted screen geometry).(Inherited from ObsidianTransport) |
| dispose? | () => Promise<void> | Disposes of transport resources (e.g. WebSocket connections, Appium sessions). (Inherited from ObsidianTransport) |
| disposeSync? | () => void | Synchronous disposal for use in process.on('exit') handlers where async work is not possible.Implementations should perform only synchronous cleanup here (e.g. killing child processes). Async operations like unregistering vaults are skipped. (Inherited from ObsidianTransport) |
| evaluate | (expression: string, options: TransportEvalOptions) => Promise<string> | Evaluates a JavaScript expression string inside Obsidian and returns the raw string result. The transport normalizes the output — stripping transport-specific prefixes and handling transport-specific errors — so callers receive a clean result string (e.g. a JSON string, or (no output)).(Inherited from ObsidianTransport) |
| isMobile | true | Indicates whether this transport is for a mobile platform. Always true for this transport. |
| preflightCheck | (vaultPath: string) => Promise<void> | Runs transport-specific preflight checks before evaluation. For example, the CLI transport verifies that the vault is registered, the CLI is enabled, and the CLI binary is in PATH. (Inherited from ObsidianTransport) |
| pushFiles? | (vaultPath: string, files: Record<string, Uint8Array>) => Promise<void> | Pushes files into a vault directory on the target device. On desktop this is a no-op (files are written to the local filesystem directly). On mobile this uses the device's file transfer mechanism (e.g. Appium pushFile).(Inherited from ObsidianTransport) |
| readConsoleCaptureSince? | (handle: ConsoleCaptureHandle | undefined) => Promise<string | undefined> | Reads the native console/error output captured since the matching ObsidianTransport.beginConsoleCapture call. A bounded, post-hoc, failure-path-only dump — not a live monitor. (Inherited from ObsidianTransport) |
| registerVault | (vaultPath: string) => Promise<void> | Registers a vault path so Obsidian can target it. On desktop: uses Electron IPC to open the vault and polls for readiness. On mobile: pushes only a minimal .obsidian marker so the folder is recognized as a vault, points the app's localStorage at it, and reloads.It does **not** carry the vault's contents across on either platform — that is ObsidianTransport.pushFiles' job, which TemporaryVault.register runs first. A caller driving a transport directly has to push before registering, or the app opens an empty vault with nothing raised to say so.(Inherited from ObsidianTransport) |
| unregisterVault | (vaultPath: string) => Promise<void> | Unregisters a vault path from the running Obsidian instance. On desktop: closes the vault window and removes it from the registry. On mobile: removes vault files from the device. (Inherited from ObsidianTransport) |
Methods
| Method | Returns | Description |
|---|---|---|
| beginConsoleCapture() | Promise<ConsoleCaptureHandle> | Begins a console capture by stamping the device log with a unique marker. Uses adb shell log to write a marker into logcat at capture start so a later AppiumTransport.readConsoleCaptureSince can slice out everything the WebView logged afterwards — without the invasive logcat -c buffer clear. |
| captureScreenshot(_params) | Promise<Uint8Array> | Captures a PNG screenshot of the device's screen. The image is always the device's native framebuffer — there is no mobile equivalent of the desktop viewport override, so CaptureScreenshotParams.widthInPixels / CaptureScreenshotParams.heightInPixels are ignored. Size a mobile capture by choosing an AVD whose screen geometry already matches what the image has to be. |
| dispose() | Promise<void> | Ends the Appium session. Only deletes the session if this transport owns it. Transports created via attach() (test workers reusing the global setup's session) skip deletion so the session remains available for the owning process. |
| disposeSync() | void | Synchronous teardown for process.on('exit') handlers.Only the trusted-input channel needs it: the Appium session is the owning process's to end, and an abrupt exit is exactly when the async AppiumTransport.dispose never runs — which would strand the adb port forward on the device. |
| evaluate(expression, _options) | Promise<string> | Evaluates a JavaScript expression inside Obsidian Mobile's WebView. Switches to the WEBVIEW_md.obsidian context, executes the expression, and returns the result string. |
| getSessionInfo() | AppiumSessionInfo | Returns the session connection info needed to reattach to this session from another process (e.g. a test worker). |
| preflightCheck(_vaultPath) | Promise<void> | Verifies that the Obsidian app is running and the WebView is available. |
| pushFiles(vaultPath, _files) | Promise<void> | Pushes vault files to the device via compressed adb push.Creates a tar.gz archive of the local vault directory, pushes it to the device as a single file, and extracts it in-place. This avoids the webdriver RangeError on large base64 payloads and is significantly faster than per-file browser.pushFile() calls. |
| readConsoleCaptureSince(handle) | Promise<string | undefined> | Dumps adb logcat and returns the WebView console/error output logged since the marker from AppiumTransport.beginConsoleCapture.Filters to the Chromium/WebView tags that carry JS console.* output and uncaught errors, and caps the result length. Bounded, post-hoc, failure-path-only — never a live monitor. |
| registerVault(vaultPath) | Promise<void> | Registers a vault on mobile by pushing files and configuring localStorage. The registration flow: 1. Push a minimal .obsidian/app.json to the device so Obsidian recognizes the vault — **unless one is already there**, which is the usual case, since register pushes the whole vault first and that carries the run's real app.json across. See pushObsidianMarker: the marker is a truncating overwrite, so pushing it unconditionally destroyed the headless defaults it had just been handed. 2. Switch to the WebView context 3. Add the vault to localStorage (mobile-external-vaults, mobile-selected-vault, enable-plugin-<path>) 4. Trigger location.reload() so Obsidian re-reads localStorage and opens the vault 5. Wait for globalThis.app to exist (the app's cold start), then — on a separate, tighter budget — for app.workspace.layoutReadyExisting vault registrations in localStorage are preserved (append, not overwrite) — except the **harness's own** temp-vault-* registrations from earlier runs, which are pruned when AppiumTransportConfig.shouldSweepLeftovers is on. Their directories are already gone (the factory sweeps them before the app launches), and it is this registry — not the filesystem — that Obsidian enumerates at startup, so leaving them is what makes each failed run slow the next one down. |
| unregisterVault(vaultPath) | Promise<void> | Unregisters a vault on mobile by removing it from localStorage. Preserves other vault registrations. If the unregistered vault was selected, switches to the first remaining vault (or clears the selection). The vault's **files are then removed from the device over adb, whether or not the localStorage step succeeded** — deliberately, because the localStorage step goes through the WebView and a dead WebView is exactly what most Android failures are. Routing the removal through the app was what made every failed run leak its vault (Vault cleanup error (non-fatal): no such window), so the filesystem-level removal must not depend on it. |
Links to this page: