ios

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: 20 Imported by: 0

README

iOS runner

The ios Endly service provides iOS Simulator lifecycle and, incrementally, build/signing/deployment and E2E automation described in ios.md.

Implemented actions:

Action Description
ios:doctor Report Xcode tools, installed Simulator runtimes, and available Simulators
ios:build Run Simulator build/build-for-testing and discover/checksum .app/.xctestrun products
ios:simulator-start Attach to, clone, or create and boot a Simulator under a fenced lease
ios:simulator-stop Release the lease, shut down the Simulator, and delete only an owned clone
ios:device-list List paired physical iOS/iPadOS devices known to CoreDevice
ios:device-lease Acquire a process-shared fenced lease for one exact physical-device UDID
ios:device-release Release the lease without terminating, shutting down, or erasing the device
ios:destination-register Fence a provider/cloud device ID without local CoreDevice mutation
ios:destination-release Release an external registration without stopping provider infrastructure
ios:server-start Health-check/register external Appium or start a loopback-only managed server
ios:server-stop Release external registration or idempotently stop an owned server
ios:install Install one compatible Simulator or signed device .app with an explicit policy
ios:uninstall Remove an exact bundle through a valid destination lease
ios:launch Launch an exact bundle with argv-safe arguments/environment and return its PID
ios:terminate Terminate a Simulator bundle or exact physical-device PID through a valid lease
ios:test Run XCTest/test-without-building and normalize modern .xcresult summaries into assertions
ios:capture-start Start an owned Simulator unified-log stream with an optional predicate
ios:capture-stop Stop the stream and return sensitive log evidence metadata
ios:open Open an Appium XCUITest session with managed/prebuilt/preinstalled/external WDA
ios:attach Reconnect this process to an existing Appium session or descriptor
ios:run Run assigned app.*/device.* commands and retrying inline expectations
ios:repl Run a live terminal DSL, hierarchy inspector, screenshot tool, and command history
ios:artifact Store sensitive screenshot and bounded accessibility-source evidence through AFS
ios:close Idempotently close the Appium session
ios:cleanup Run every registered teardown in LIFO order and report all errors

The implementation uses argv-safe local command execution, Xcode destination build/test, archive/export signing inputs, devicectl physical-device lifecycle, and .xcresult tooling, 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, alerts, permissions, appearance, biometrics, and application lifecycle. Simulator capture can rotate bounded video segments; failed runs automatically retain screenshot, bounded source, bounded active-log tail, a checkpointed valid video segment, and a manifest while recording continues. Managed destinations and Appium ports use process-shared fenced leases, while owned processes still unwind through the LIFO context cleanup stack. Physical-device release never shuts down or erases hardware. Portable 0600 session descriptors let a second Endly process run ios:attach or inline ios:repl.attach; attachment is non-owning unless takeOwnership is explicit. A gated two-Simulator stress test verifies distinct Simulator/Appium/WDA/MJPEG/DerivedData resources under concurrent sessions. The bundled project contains one passing and one intentionally failing UI test; ios:test retains and normalizes the real .xcresult. collectDiagnostics defaults to never to avoid Xcode's ten-minute failure sysdiagnose and accepts on-failure when those diagnostics are wanted. open.wda owns all WDA lifecycle/signing/port capabilities and supports managed, prebuilt, preinstalled, and external; raw conflicting capabilities are rejected. Current compatibility rules reject preinstalled WDA below iOS 17 and on iOS 27+ Simulators, where direct XCTest-runner launch is not viable, with guidance to use another mode. Provider-neutral cloud mode fences an external destination, health-checks external Appium, supplies appReference plus namespaced provider capabilities, and never invokes local build/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. On non-Darwin hosts, ios registers the same action-compatible unsupported-platform stub so Endly still builds and reports a useful error. Connected-device hardware validation remains.

External farm shape:

- action: ios:destination-register
  request: {provider: example-farm, deviceID: iphone-remote, platformVersion: "18.5"}
- action: ios:server-start
  request: {destination: $destinationRegister.Lease, mode: external, serverURL: $secureAppiumURL}
- action: ios:open
  request:
    destination: $destinationRegister.Lease
    server: $serverStart.Server
    appReference: farm://apps/build-456
    capabilities:
      farm:options: {project: endly}

Run the real host check:

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

Run the complete bundled-fixture integration on an installed Simulator runtime:

ENDLY_IOS_EMULATOR_INTEGRATION=1 \
  go test ./service/testing/runner/ios -run '^TestIOSSimulatorIntegration$' -v -count=1

This creates an owned Simulator, builds and checksums test/fixture/FixtureApp, installs and launches it, captures unified logs and a non-empty screenshot, terminates the app, and deletes the Simulator.

To include the complete managed-Appium/XCUITest DSL path:

export PATH=/path/to/node-v24/bin:$PATH
ENDLY_IOS_EMULATOR_INTEGRATION=1 \
ENDLY_IOS_APPIUM_INTEGRATION=1 \
ENDLY_IOS_APPIUM_EXECUTABLE=/path/to/appium \
ENDLY_IOS_APPIUM_HOME=/path/to/appium-home \
  go test ./service/testing/runner/ios -run '^TestIOSSimulatorIntegration$' -v -count=1

That path additionally starts owned Appium/WDA processes, locates the fixture by accessibility identifier, verifies its initial text, taps the increment button, waits for Count: 1, captures Appium screenshot/source evidence, and verifies complete cleanup.

Run the two-lane isolation stress test:

ENDLY_IOS_PARALLEL_INTEGRATION=1 \
  go test ./service/testing/runner/ios -run '^TestIOSParallelSimulatorStress$' -v -count=1

Run the WDA lifecycle compatibility suite:

ENDLY_IOS_WDA_MODES_INTEGRATION=1 \
  go test ./service/testing/runner/ios -run '^TestIOSWDAModesIntegration$' -v -count=1

Query CoreDevice through the physical-device parser:

ENDLY_IOS_DEVICE_LIST_INTEGRATION=1 \
  go test ./service/testing/runner/ios -run '^TestPhysicalDeviceListIntegration$' -v -count=1

Build and checksum a real unsigned archive without requiring signing credentials:

ENDLY_IOS_ARCHIVE_INTEGRATION=1 \
  go test ./service/testing/runner/ios -run '^TestIOSArchiveIntegration$' -v -count=1

Run test/repl.yaml to enter the live inspector. Once setup completes, Endly displays an ios[session]> prompt:

:status
:tree increment
count = app.getByTestId("count").text()
app.getByTestId("increment").tap()
expect(app.getByTestId("count")).toHaveText("Count: 1", 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, select an existing Simulator with keepBooted: true, use an external Appium server, and open with descriptorPath plus keepSession: true. A later process can enter the inspector directly:

action: ios:repl
request:
  attach:
    descriptorPath: /secure/run/ios-session.json
    takeOwnership: false
  historyPath: /secure/run/ios-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 = "ios"

Variables

This section is empty.

Functions

func New

func New() endly.Service

Types

type Artifact

type Artifact struct {
	Kind        string
	BundleID    string
	HostPath    string
	ArtifactURL string
	SHA256      string
	Size        int64
}

type ArtifactRequest

type ArtifactRequest struct {
	SessionID      string
	Destination    *DestinationLease
	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
	TakeOwnership    bool
}

func (*AttachRequest) Validate

func (r *AttachRequest) Validate() error

type AttachResponse

type AttachResponse struct {
	Session SessionHandle
}

type BuildRequest

type BuildRequest struct {
	ProjectPath        string
	WorkspacePath      string
	Scheme             string
	Configuration      string
	Destination        DestinationLease
	DerivedDataPath    string
	Mode               string
	SDK                string
	ArchivePath        string
	ExportPath         string
	ExportOptionsPlist string
	Signing            *IOSSigningProfile
	BuildSettings      map[string]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  []Artifact
	Stdout     string
	Stderr     string
	DurationMs int
}

type CaptureHandle

type CaptureHandle struct {
	ID          string
	Destination DestinationLease
	PID         int
	LogPath     string
}

type CaptureStartRequest

type CaptureStartRequest struct {
	Destination    DestinationLease
	Predicate      string
	LogPath        string
	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
	Destination *DestinationLease
}

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 DestinationLease

type DestinationLease struct {
	ID                string
	Fence             uint64
	UDID              string
	Name              string
	Runtime           string
	Kind              string // simulator or device; empty is legacy simulator
	OwnedClone        bool
	PreserveOnRelease bool
	ProcessLease      *mobile.LeaseHandle
}

func (DestinationLease) IsDevice

func (l DestinationLease) IsDevice() bool

func (DestinationLease) IsExternal

func (l DestinationLease) IsExternal() bool

type DestinationRegisterRequest

type DestinationRegisterRequest struct {
	Provider        string
	DeviceID        string
	PlatformVersion string
}

func (*DestinationRegisterRequest) Validate

func (r *DestinationRegisterRequest) Validate() error

type DestinationRegisterResponse

type DestinationRegisterResponse struct {
	Lease DestinationLease
}

type DestinationReleaseRequest

type DestinationReleaseRequest struct {
	Lease DestinationLease
}

func (*DestinationReleaseRequest) Validate

func (r *DestinationReleaseRequest) Validate() error

type DestinationReleaseResponse

type DestinationReleaseResponse struct {
	Released bool
	Warning  string
}

type DeviceLeaseRequest

type DeviceLeaseRequest struct {
	UDID      string
	TimeoutMs int
}

func (*DeviceLeaseRequest) Init

func (r *DeviceLeaseRequest) Init() error

func (*DeviceLeaseRequest) Validate

func (r *DeviceLeaseRequest) Validate() error

type DeviceLeaseResponse

type DeviceLeaseResponse struct {
	Lease  DestinationLease
	Device PhysicalDevice
}

type DeviceListRequest

type DeviceListRequest struct {
	TimeoutMs int
}

func (*DeviceListRequest) Init

func (r *DeviceListRequest) Init() error

type DeviceListResponse

type DeviceListResponse struct {
	Devices []PhysicalDevice
}

type DeviceReleaseRequest

type DeviceReleaseRequest struct {
	Lease DestinationLease
}

func (*DeviceReleaseRequest) Validate

func (r *DeviceReleaseRequest) Validate() error

type DeviceReleaseResponse

type DeviceReleaseResponse struct {
	Released bool
	Warning  string
}

type DoctorRequest

type DoctorRequest struct {
	Required []string
}

func (*DoctorRequest) Init

func (r *DoctorRequest) Init() error

type DoctorResponse

type DoctorResponse struct {
	Ready      bool
	Checks     []mobile.Check
	Runtimes   []string
	Simulators []IOSSimulator
	Devices    []PhysicalDevice
}

func (*DoctorResponse) IsOutput

func (r *DoctorResponse) IsOutput() bool

func (*DoctorResponse) Messages

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

type IOSSigningProfile

type IOSSigningProfile struct {
	Style               string // automatic or manual
	TeamID              string
	Identity            string
	ProvisioningProfile string
	KeychainPath        string // must already be unlocked by the worker
}

type IOSSimulator

type IOSSimulator struct {
	UDID        string
	Name        string
	State       string
	Runtime     string
	IsAvailable bool
}

type InstallRequest

type InstallRequest struct {
	Destination DestinationLease
	App         Artifact
	BundleID    string
	State       string
}

func (*InstallRequest) Init

func (r *InstallRequest) Init() error

func (*InstallRequest) Validate

func (r *InstallRequest) Validate() error

type InstallResponse

type InstallResponse struct {
	Installed bool
	BundleID  string
	AppPath   string
	State     string
}

type LaunchRequest

type LaunchRequest struct {
	Destination DestinationLease
	BundleID    string
	Arguments   []string
	Environment map[string]string
}

func (*LaunchRequest) Validate

func (r *LaunchRequest) Validate() error

type LaunchResponse

type LaunchResponse struct {
	PID    int
	Output string
}

type OpenRequest

type OpenRequest struct {
	SessionID      string
	Destination    DestinationLease
	Server         ServerHandle
	BundleID       string
	App            *Artifact
	AppReference   string
	Capabilities   map[string]interface{}
	DescriptorPath string
	KeepSession    bool
	WDA            *WDAOptions
}

func (*OpenRequest) Init

func (r *OpenRequest) Init() error

func (*OpenRequest) Validate

func (r *OpenRequest) Validate() error

type OpenResponse

type OpenResponse struct {
	Session SessionHandle
}

type PhysicalDevice

type PhysicalDevice struct {
	Identifier    string
	UDID          string
	Name          string
	Platform      string
	OSVersion     string
	State         string
	Connection    string
	PairingState  string
	DeveloperMode string
}

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 {
	Destination      DestinationLease
	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
	Destination      DestinationLease
	Server           ServerHandle
}

type SimulatorStartRequest

type SimulatorStartRequest struct {
	UDID          string
	BaseName      string
	CloneName     string
	DeviceType    string
	Runtime       string
	Erase         bool
	KeepBooted    bool
	BootTimeoutMs int
}

func (*SimulatorStartRequest) Init

func (r *SimulatorStartRequest) Init() error

func (*SimulatorStartRequest) Validate

func (r *SimulatorStartRequest) Validate() error

type SimulatorStartResponse

type SimulatorStartResponse struct {
	Lease DestinationLease
}

type SimulatorStopRequest

type SimulatorStopRequest struct {
	Lease DestinationLease
}

func (*SimulatorStopRequest) Validate

func (r *SimulatorStopRequest) Validate() error

type SimulatorStopResponse

type SimulatorStopResponse struct {
	Shutdown bool
	Deleted  bool
	Warning  string
}

type TerminateRequest

type TerminateRequest struct {
	Destination DestinationLease
	BundleID    string
	PID         int
}

func (*TerminateRequest) Validate

func (r *TerminateRequest) Validate() error

type TerminateResponse

type TerminateResponse struct{ Terminated bool }

type TestRequest

type TestRequest struct {
	Destination        DestinationLease
	ProjectPath        string
	WorkspacePath      string
	Scheme             string
	XCTestRunPath      string
	Mode               string
	OnlyTesting        []string
	SkipTesting        []string
	ResultBundlePath   string
	DerivedDataPath    string
	CollectDiagnostics string
	TimeoutMs          int
}

func (*TestRequest) Init

func (r *TestRequest) Init() error

func (*TestRequest) Validate

func (r *TestRequest) Validate() error

type TestResponse

type TestResponse struct {
	Summary     XCTestSummary
	Output      string
	DurationMs  int
	Validations []*assertly.Validation
}

func (*TestResponse) Assertion

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

type UninstallRequest

type UninstallRequest struct {
	Destination DestinationLease
	BundleID    string
}

func (*UninstallRequest) Validate

func (r *UninstallRequest) Validate() error

type UninstallResponse

type UninstallResponse struct {
	Removed  bool
	BundleID string
}

type WDAOptions

type WDAOptions struct {
	Mode                  string // managed, prebuilt, preinstalled, external
	DerivedDataPath       string
	PrebuiltWDAPath       string
	WebDriverAgentURL     string
	UpdatedBundleID       string
	UpdatedBundleIDSuffix *string
	XcodeOrgID            string
	XcodeSigningID        string
	XcodeConfigFile       string
	KeychainPath          string
	KeychainPasswordFile  string
	LocalPort             int
	MJPEGServerPort       int
	UseNewWDA             *bool
	PrebuildWDA           bool
	LaunchTimeoutMs       int
	ConnectionTimeoutMs   int
	StartupRetries        int
	StartupRetryMs        int
}

WDAOptions owns the WebDriverAgent lifecycle capabilities so callers cannot smuggle conflicting raw capabilities into OpenRequest.Capabilities.

func (*WDAOptions) Init

func (o *WDAOptions) Init()

func (*WDAOptions) Validate

func (o *WDAOptions) Validate() error

type XCTestFailure

type XCTestFailure struct {
	TestName       string `json:"testName"`
	TargetName     string `json:"targetName"`
	FailureText    string `json:"failureText"`
	TestIdentifier string `json:"testIdentifierString"`
}

type XCTestSummary

type XCTestSummary struct {
	Title            string          `json:"title"`
	Result           string          `json:"result"`
	TotalTestCount   int             `json:"totalTestCount"`
	PassedTests      int             `json:"passedTests"`
	FailedTests      int             `json:"failedTests"`
	SkippedTests     int             `json:"skippedTests"`
	ExpectedFailures int             `json:"expectedFailures"`
	TestFailures     []XCTestFailure `json:"testFailures"`
}

Jump to

Keyboard shortcuts

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