91e75e620b
CI: cua-driver distro-compat matrix / debian:12 (glibc 2.36) (push) Has been cancelled
CI: SPDX Headers / Check SPDX headers (warn-only) (push) Has been cancelled
CD: Docs MCP Server / build (linux/amd64) (push) Has been cancelled
CD: Docs MCP Server / build (linux/arm64) (push) Has been cancelled
CD: Docs MCP Server / merge (push) Has been cancelled
CI: cua-driver distro-compat matrix / Resolve release version (push) Has been cancelled
CI: cua-driver distro-compat matrix / fedora:41 (glibc 2.40) (push) Has been cancelled
CI: cua-driver distro-compat matrix / rockylinux:9 (glibc 2.34) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:22.04 (glibc 2.35) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:24.04 (glibc 2.39) (push) Has been cancelled
CI: cua-driver distro-compat matrix / Distro compat summary (push) Has been cancelled
CI: Rust Linux unit / Rust Linux unit and compile (push) Has been cancelled
CI: Rust Windows unit / Rust Windows unit and compile (push) Has been cancelled
CI: Nix Linux Rust source / Nix / compositor build (push) Has been cancelled
CI: Nix Linux Rust source / Nix / driver package (push) Has been cancelled
CI: Nix Linux Rust source / Nix / Rust unit tests (push) Has been cancelled
92 lines
2.8 KiB
Python
92 lines
2.8 KiB
Python
"""Launch a persistent local Android VM via the CLI.
|
|
|
|
cua sb launch android:14 --local --json
|
|
# parse name from JSON output, connect with SDK, run assertions
|
|
cua sb delete <name> --local
|
|
|
|
Mirrors tests/integration/sandbox_sdk/test_android_local_vm.py but exercises the CLI
|
|
launch path and persistent state tracking instead of Sandbox.ephemeral().
|
|
Requires Android SDK (emulator + adb) to be installed.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import json
|
|
import subprocess
|
|
|
|
import pytest
|
|
from cua_sandbox import Sandbox
|
|
|
|
pytestmark = pytest.mark.asyncio
|
|
|
|
|
|
def _has_java() -> bool:
|
|
"""Only Java must be pre-installed — the Android SDK auto-installs on first run."""
|
|
try:
|
|
from cua_sandbox.runtime.android_emulator import _java_env
|
|
|
|
_java_env()
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def _cua(*args: str) -> subprocess.CompletedProcess:
|
|
return subprocess.run(["cua", *args], capture_output=True, text=True)
|
|
|
|
|
|
def _ls_names() -> list[str]:
|
|
r = _cua("sb", "ls", "--all", "--json")
|
|
if r.returncode != 0:
|
|
return []
|
|
return [s["name"] for s in json.loads(r.stdout)]
|
|
|
|
|
|
@pytest.mark.skipif(not _has_java(), reason="Java not found (required to auto-install Android SDK)")
|
|
async def test_android_local_vm():
|
|
result = _cua("sb", "launch", "android:14", "--local", "--json")
|
|
assert result.returncode == 0, f"launch failed:\n{result.stderr}"
|
|
name = json.loads(result.stdout)["name"]
|
|
|
|
assert name in _ls_names(), f"'{name}' not found in `cua sb ls --all` after launch"
|
|
|
|
try:
|
|
async with Sandbox.connect(name, local=True) as sb:
|
|
screenshot = await sb.screenshot()
|
|
assert screenshot[:4] == b"\x89PNG"
|
|
assert len(screenshot) > 10000
|
|
|
|
w, h = await sb.screen.size()
|
|
assert w > 0 and h > 0
|
|
|
|
await sb.mobile.home()
|
|
finally:
|
|
_cua("sb", "delete", name, "--local")
|
|
assert name not in _ls_names(), f"'{name}' still in `cua sb ls --all` after delete"
|
|
|
|
|
|
async def main():
|
|
r = _cua("sb", "launch", "android:14", "--local", "--json")
|
|
print(f"launch exit={r.returncode}")
|
|
name = json.loads(r.stdout)["name"]
|
|
print(f"name: {name}")
|
|
|
|
try:
|
|
async with Sandbox.connect(name, local=True) as sb:
|
|
screenshot = await sb.screenshot()
|
|
print(f"Screenshot: {len(screenshot)} bytes")
|
|
with open("/tmp/cli_android_local_vm.png", "wb") as f:
|
|
f.write(screenshot)
|
|
w, h = await sb.screen.size()
|
|
print(f"Screen size: {w}x{h}")
|
|
await sb.mobile.tap(w // 2, h // 2)
|
|
await sb.mobile.home()
|
|
print("Tap + home: OK")
|
|
finally:
|
|
_cua("sb", "delete", name, "--local")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|