android

package
v0.91.0 Latest Latest
Warning

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

Go to latest
Published: Oct 8, 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