Help us shape the future of Go! Take the 2026 Go Developer Survey

android

package
v0.96.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 21, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

README

Android runner

The android Endly service provides Android emulator lifecycle and, incrementally, build/deployment and E2E automation described in adntoid.md.

Implemented actions:

Action Description
android:doctor Report adb, emulator, Appium, connected devices, and available AVD readiness
android:build Run argv-safe Gradle-wrapper tasks and discover/checksum variant APK/AAB/APKS products
android:device-start Attach to an exact connected serial or start an owned AVD and wait for sys.boot_completed=1
android:device-stop Release a fenced lease and stop only an emulator started by this service
android:device-register Fence a provider/cloud device ID without local adb mutation
android:device-release Release an external registration without stopping provider infrastructure
android:server-start Health-check/register external Appium or start a loopback-only managed server
android:server-stop Release external registration or idempotently stop an owned server
android:install Apply an explicit state policy and install an APK, split APKs, APKS, or AAB
android:uninstall Remove an exact package through a valid device lease
android:launch Resolve the launch activity and start the exact package
android:terminate Force-stop the exact package through a valid lease
android:test Optionally install app and test APKs, run am instrument, and expose failures as assertions
android:capture-start Start owned logcat and optional segmented video capture
android:capture-stop Stop capture and return sensitive log/video evidence metadata
android:open Open an Appium UiAutomator2 session against valid device and server handles
android:attach Reconnect this process to an existing Appium session or descriptor
android:run Run assigned app.*/device.* commands and retrying inline expectations
android:repl Run a live terminal DSL, hierarchy inspector, screenshot tool, and command history
android:artifact Store sensitive screenshot and bounded UI-source evidence through AFS
android:close Idempotently close the Appium session
android:cleanup Run every registered teardown in LIFO order and report all errors

The implementation uses argv-safe local command execution, the project Gradle wrapper, Android instrumentation, a small W3C/Appium client, a closed mobile DSL AST, and injectable backends for deterministic lifecycle/protocol tests. The DSL supports strict plural lookup, first/last/nth/count, typed and textual commands, pointer gestures, richer element/device assertions, contexts, orientation, location, permissions, alerts, and application lifecycle. Deployment accepts a single APK, split APKs, APKS, or an AAB with file-backed signing secrets; capture can rotate bounded screen-recording segments. Failed runs automatically retain screenshot, bounded source, bounded active-log tail, a checkpointed valid video segment, and a manifest while recording continues. Managed devices and Appium ports use process-shared fenced leases, while owned processes still unwind through the LIFO context cleanup stack. open requires a registered ServerHandle and explicit testIDStrategy. Portable 0600 session descriptors let a second Endly process run android:attach or inline android:repl.attach; attachment is non-owning unless takeOwnership is explicit. A gated two-AVD stress test verifies distinct emulator/Appium/UiAutomator2/MJPEG resources under concurrent sessions. The real integration also installs a minimal device-side instrumentation APK and verifies that its intentional assertion failure is normalized rather than returned as infrastructure failure. Provider-neutral cloud mode fences an external device ID, health-checks external Appium, supplies appReference plus namespaced provider capabilities, and never runs adb install/capture/stop against farm infrastructure. Persistent remote execution uses Endly's authenticated control-plane sessions; see mobile remote workers. Provider-specific upload APIs remain adapters around appReference rather than runner-owned credentials.

External farm shape:

- action: android:device-register
  request: {provider: example-farm, deviceID: pixel-remote, platformVersion: "15"}
- action: android:server-start
  request: {lease: $deviceRegister.Lease, mode: external, serverURL: $secureAppiumURL}
- action: android:open
  request:
    lease: $deviceRegister.Lease
    server: $serverStart.Server
    appReference: farm://apps/build-123
    testIDStrategy: accessibilityId
    capabilities:
      farm:options: {project: endly}

Run the real host check:

endly -r=service/testing/runner/android/test/doctor.yaml

Run the complete emulator integration after provisioning an SDK and AVD:

ANDROID_SDK_ROOT=/path/to/android-sdk \
ENDLY_ANDROID_EMULATOR_INTEGRATION=1 \
ENDLY_ANDROID_TEST_AVD=endly_api_35 \
ENDLY_ANDROID_TEST_PROJECT=/path/to/android-project \
ENDLY_ANDROID_TEST_PACKAGE=com.example.app \
  go test ./service/testing/runner/android -run '^TestAndroidEmulatorIntegration$' -v -count=1

The gated test builds with the project Gradle wrapper, starts a clean owned AVD, installs and launches the APK, captures logcat and a non-empty screenshot, terminates the app, and stops the emulator. Set the documented Appium environment variables to include UiAutomator2 session and source capture.

The reference validation uses API 35 endly_api_35 on arm64 with Appium 3.7.0 and UiAutomator2 8.6.1. It additionally opens a managed session, resolves a native view through the DSL, asserts visibility, captures Appium screenshot/source evidence, and verifies that no emulator or Appium process remains.

Add ENDLY_ANDROID_AAB_INTEGRATION=1 and point ENDLY_ANDROID_BUNDLETOOL at the official bundletool-all JAR to build an AAB, generate device-targeted signed APKS using file-backed passwords, and install it on the real emulator before the remaining suite runs. This path is verified with bundletool 1.18.3; Endly passes its already-resolved adb path explicitly rather than relying on ANDROID_HOME or PATH.

Run the two-lane isolation stress test with two provisioned AVD names:

ENDLY_ANDROID_PARALLEL_INTEGRATION=1 \
ENDLY_ANDROID_PARALLEL_AVDS=api35_lane_1,api35_lane_2 \
  go test ./service/testing/runner/android -run '^TestAndroidParallelEmulatorStress$' -v -count=1

Run a live inspector with test/repl.yaml. Once setup completes, Endly displays an android[session]> prompt and executes each entered DSL command immediately:

:status
:tree workspace
heading = app.getByText("Choose your workspace").text()
app.getByClass("android.widget.EditText").fill("https://example.test")
expect(app.getByClass("android.widget.EditText")).toHaveText("https://example.test", 10000)
:screenshot
:history
:quit

Inspector commands are :status, :source, :tree [filter], :find <text>, :screenshot, :history, !<number>, :help, :close, and :quit. In a real terminal, Tab completes DSL/meta commands and Up/Down navigate persistent history. Command errors are printed and the prompt continues unless failOnError is enabled. Ctrl-C returns to the workflow so deferred cleanup runs.

For a deliberate cross-process handoff, create the session on an attached device and external Appium server with descriptorPath plus keepSession: true. A later process can enter the inspector directly:

action: android:repl
request:
  attach:
    descriptorPath: /secure/run/android-session.json
    takeOwnership: false
  historyPath: /secure/run/android-history.jsonl

Use takeOwnership: true only when :close or cleanup should delete the remote Appium session and its descriptor.

Documentation

Index

Constants

View Source
const ServiceID = "android"

Variables

This section is empty.

Functions

func New

func New() endly.Service

Types

type AndroidDevice

type AndroidDevice struct {
	Serial string
	State  string
	Detail string
}

type AndroidSigningProfile

type AndroidSigningProfile struct {
	KeystorePath      string
	KeyAlias          string
	StorePasswordFile string
	KeyPasswordFile   string
}

type ArtifactRequest

type ArtifactRequest struct {
	SessionID      string
	Lease          *DeviceLease
	Directory      string
	Screenshot     bool
	PageSource     bool
	MaxSourceBytes int
}

func (*ArtifactRequest) Init

func (r *ArtifactRequest) Init() error

func (*ArtifactRequest) Validate

func (r *ArtifactRequest) Validate() error

type ArtifactResponse

type ArtifactResponse struct {
	Artifacts []*mobile.Evidence
}

type AttachRequest

type AttachRequest struct {
	SessionID        string
	DescriptorPath   string
	BackendSessionID string
	ServerURL        string
	TargetID         string
	TestIDStrategy   string
	TakeOwnership    bool
}

AttachRequest reconnects this Endly process to an Appium session created by another process. DescriptorPath is mutually exclusive with the explicit backend fields.

func (*AttachRequest) Validate

func (r *AttachRequest) Validate() error

type AttachResponse

type AttachResponse struct {
	Session SessionHandle
}

type BuildArtifact

type BuildArtifact struct {
	Kind   string
	Path   string
	SHA256 string
	Size   int64
}

type BuildRequest

type BuildRequest struct {
	ProjectDir string
	Module     string
	Variant    string
	Tasks      []string
	GradleArgs []string
	TimeoutMs  int
}

func (*BuildRequest) Init

func (r *BuildRequest) Init() error

func (*BuildRequest) Validate

func (r *BuildRequest) Validate() error

type BuildResponse

type BuildResponse struct {
	Artifacts  []BuildArtifact
	Stdout     string
	Stderr     string
	DurationMs int
}

type CaptureHandle

type CaptureHandle struct {
	ID      string
	Lease   DeviceLease
	PID     int
	LogPath string
}

type CaptureStartRequest

type CaptureStartRequest struct {
	Lease          DeviceLease
	Package        string
	LogPath        string
	Clear          bool
	Video          bool
	VideoDirectory string
	SegmentMs      int
}

func (*CaptureStartRequest) Init

func (r *CaptureStartRequest) Init() error

func (*CaptureStartRequest) Validate

func (r *CaptureStartRequest) Validate() error

type CaptureStartResponse

type CaptureStartResponse struct{ Capture CaptureHandle }

type CaptureStopRequest

type CaptureStopRequest struct{ Capture CaptureHandle }

func (*CaptureStopRequest) Validate

func (r *CaptureStopRequest) Validate() error

type CaptureStopResponse

type CaptureStopResponse struct {
	Stopped   bool
	Artifact  *mobile.Evidence
	Artifacts []*mobile.Evidence
	Errors    []string
	Warning   string
}

type CleanupRequest

type CleanupRequest struct {
	Session *SessionHandle
	Server  *ServerHandle
	Lease   *DeviceLease
}

type CleanupResponse

type CleanupResponse struct {
	Errors []mobile.CleanupError
}

type CloseRequest

type CloseRequest struct {
	SessionID string
}

func (*CloseRequest) Validate

func (r *CloseRequest) Validate() error

type CloseResponse

type CloseResponse struct {
	Closed  bool
	Warning string
}

type DeviceLease

type DeviceLease struct {
	ID              string
	Fence           uint64
	Serial          string
	AVD             string
	PID             int
	Owned           bool
	LogPath         string
	AndroidSDKRoot  string
	External        bool
	Provider        string
	PlatformVersion string
	ProcessLease    *mobile.LeaseHandle
	SerialLease     *mobile.LeaseHandle
}

type DeviceRegisterRequest

type DeviceRegisterRequest struct {
	Provider        string
	DeviceID        string
	PlatformVersion string
}

func (*DeviceRegisterRequest) Validate

func (r *DeviceRegisterRequest) Validate() error

type DeviceRegisterResponse

type DeviceRegisterResponse struct {
	Lease DeviceLease
}

type DeviceReleaseRequest

type DeviceReleaseRequest struct {
	Lease DeviceLease
}

func (*DeviceReleaseRequest) Validate

func (r *DeviceReleaseRequest) Validate() error

type DeviceReleaseResponse

type DeviceReleaseResponse struct {
	Released bool
	Warning  string
}

type DeviceStartRequest

type DeviceStartRequest struct {
	AndroidSDKRoot string
	AVD            string
	Serial         string
	Port           int
	WipeData       bool
	NoWindow       bool
	NoSnapshot     bool
	Animations     *bool
	BootTimeoutMs  int
	PollIntervalMs int
	LogPath        string
	EmulatorArgs   []string
}

func (*DeviceStartRequest) Init

func (r *DeviceStartRequest) Init() error

func (*DeviceStartRequest) Validate

func (r *DeviceStartRequest) Validate() error

type DeviceStartResponse

type DeviceStartResponse struct {
	Lease DeviceLease
}

type DeviceStopRequest

type DeviceStopRequest struct {
	Lease DeviceLease
}

func (*DeviceStopRequest) Validate

func (r *DeviceStopRequest) Validate() error

type DeviceStopResponse

type DeviceStopResponse struct {
	Stopped bool
	Warning string
}

type DoctorRequest

type DoctorRequest struct {
	AndroidSDKRoot string
	Required       []string
}

func (*DoctorRequest) Init

func (r *DoctorRequest) Init() error

type DoctorResponse

type DoctorResponse struct {
	Ready          bool
	AndroidSDKRoot string
	Checks         []mobile.Check
	Devices        []AndroidDevice
	AVDs           []string
}

func (*DoctorResponse) IsOutput

func (r *DoctorResponse) IsOutput() bool

func (*DoctorResponse) Messages

func (r *DoctorResponse) Messages() []*msg.Message

type InstallRequest

type InstallRequest struct {
	Lease          DeviceLease
	APKPath        string
	APKPaths       []string
	APKSPath       string
	AABPath        string
	BundletoolPath string
	JavaPath       string
	Signing        *AndroidSigningProfile
	Package        string
	State          string
	GrantAll       bool
	AllowTest      bool
	AllowDowngrade bool
}

func (*InstallRequest) Init

func (r *InstallRequest) Init() error

func (*InstallRequest) Validate

func (r *InstallRequest) Validate() error

type InstallResponse

type InstallResponse struct {
	Installed bool
	Package   string
	APKPath   string
	Artifacts []string
	State     string
}

type InstrumentationCase

type InstrumentationCase struct {
	Class  string
	Name   string
	Status string
	Stack  string
}

type LaunchRequest

type LaunchRequest struct {
	Lease    DeviceLease
	Package  string
	Activity string
}

func (*LaunchRequest) Validate

func (r *LaunchRequest) Validate() error

type LaunchResponse

type LaunchResponse struct {
	Component string
	Output    string
}

type OpenRequest

type OpenRequest struct {
	SessionID      string
	Lease          DeviceLease
	Server         ServerHandle
	Package        string
	Activity       string
	AppReference   string
	TestIDStrategy string
	Capabilities   map[string]interface{}
	DescriptorPath string
	KeepSession    bool
}

func (*OpenRequest) Init

func (r *OpenRequest) Init() error

func (*OpenRequest) Validate

func (r *OpenRequest) Validate() error

type OpenResponse

type OpenResponse struct {
	Session SessionHandle
}

type REPLRequest

type REPLRequest struct {
	SessionID         string
	Attach            *AttachRequest
	Prompt            string
	ArtifactDirectory string
	ActionTimeoutMs   int
	PollIntervalMs    int
	MaxSourceBytes    int
	MaxTreeNodes      int
	FailOnError       bool
	HistoryPath       string
	MaxHistory        int
}

func (*REPLRequest) Init

func (r *REPLRequest) Init() error

func (*REPLRequest) Validate

func (r *REPLRequest) Validate() error

type REPLResponse

type REPLResponse struct {
	SessionID string
	Result    *mobile.REPLResult
}

type RunRequest

type RunRequest struct {
	SessionID        string
	Commands         []interface{}
	ActionTimeoutMs  int
	PollIntervalMs   int
	FailureArtifacts *mobile.FailureArtifactOptions
}

func (*RunRequest) Init

func (r *RunRequest) Init() error

func (*RunRequest) Validate

func (r *RunRequest) Validate() error

type RunResponse

type RunResponse struct {
	Data        map[string]interface{}
	Steps       []mobile.ExecutionStep
	Validations []*assertly.Validation
	Failures    []*mobile.FailureEvidence
}

func (*RunResponse) Assertion

func (r *RunResponse) Assertion() []*assertly.Validation

type ServerHandle

type ServerHandle struct {
	ID           string
	Endpoint     string
	Ownership    string
	PID          int
	LogPath      string
	ProcessLease *mobile.LeaseHandle
}

type ServerStartRequest

type ServerStartRequest struct {
	Lease            DeviceLease
	Mode             string
	ServerURL        string
	Executable       string
	AppiumHome       string
	Address          string
	Port             int
	BasePath         string
	LogPath          string
	StartupTimeoutMs int
}

func (*ServerStartRequest) Init

func (r *ServerStartRequest) Init() error

func (*ServerStartRequest) Validate

func (r *ServerStartRequest) Validate() error

type ServerStartResponse

type ServerStartResponse struct{ Server ServerHandle }

type ServerStopRequest

type ServerStopRequest struct{ Server ServerHandle }

func (*ServerStopRequest) Validate

func (r *ServerStopRequest) Validate() error

type ServerStopResponse

type ServerStopResponse struct {
	Stopped bool
	Warning string
}

type SessionHandle

type SessionHandle struct {
	ID               string
	BackendSessionID string
	Lease            DeviceLease
	Server           ServerHandle
}

type TerminateRequest

type TerminateRequest struct {
	Lease   DeviceLease
	Package string
}

func (*TerminateRequest) Validate

func (r *TerminateRequest) Validate() error

type TerminateResponse

type TerminateResponse struct{ Terminated bool }

type TestRequest

type TestRequest struct {
	Lease       DeviceLease
	AppAPKPath  string
	TestAPKPath string
	TestPackage string
	Runner      string
	Class       string
	Arguments   map[string]string
	GrantAll    bool
	TimeoutMs   int
}

func (*TestRequest) Init

func (r *TestRequest) Init() error

func (*TestRequest) Validate

func (r *TestRequest) Validate() error

type TestResponse

type TestResponse struct {
	Cases       []InstrumentationCase
	Passed      int
	Failed      int
	Skipped     int
	DurationMs  int
	Output      string
	Validations []*assertly.Validation
}

func (*TestResponse) Assertion

func (r *TestResponse) Assertion() []*assertly.Validation

type UninstallRequest

type UninstallRequest struct {
	Lease   DeviceLease
	Package string
}

func (*UninstallRequest) Validate

func (r *UninstallRequest) Validate() error

type UninstallResponse

type UninstallResponse struct {
	Removed bool
	Package string
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL