Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions cli/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ import (

const (
agentVersionIOS = "0.0.20"
agentVersionTVOS = "0.0.20"
agentVersionAndroid = "1.2.4"
iosRunnerBundleID = "com.mobilenext.devicekit-iosUITests.xctrunner"
tvosRunnerBundleID = "com.mobilenext.devicekit-tvosUITests.xctrunner"
androidPackageName = "com.mobilenext.devicekit"
)

Expand All @@ -26,6 +28,9 @@ var agentChecksums = map[string]string{
"devicekit-ios-Sim-arm64.zip": "8040f4918892f63d79713b5824184ac5f296c5ec9b23266c25af34777550f28c",
"devicekit-ios-Sim-x86_64.zip": "78a8f2d208a22523efbaa5cb2a735557e807f877bb8ec1a1c31c886f2e425684",
"devicekit-ios-runner.ipa": "f5fe88d4169c39001ed012101651c5ac00e8ab54aefb72c74455e7037c2e8205",
// tvOS simulator runner is published from the same devicekit-ios release.
// Update this checksum whenever the tvOS runner artifact is rebuilt/republished.
"devicekit-tvos-Sim-arm64.zip": "49061f17046055c7e89dbf27067a59ab0bffd9d7d14d63031d5411c5963ccb81",
Comment thread
setoelkahfi marked this conversation as resolved.
"devicekit.apk": "63b1111fbd3b986c7452bc7c28150b1e9c0d611b2ecd7f6917a0f50a84d0836b",
}

Expand Down Expand Up @@ -130,6 +135,13 @@ var agentInstallCmd = &cobra.Command{
default:
return fmt.Errorf("unsupported device type: %s", device.DeviceType())
}
case "tvos":
switch device.DeviceType() {
case "simulator":
installErr = installAgentOnSimulator(device)
default:
return fmt.Errorf("unsupported tvOS device type: %s (only the tvOS Simulator is supported)", device.DeviceType())
}
case "android":
installErr = installAgentOnAndroid(device)
default:
Expand Down Expand Up @@ -195,6 +207,8 @@ func agentPackageForPlatform(platform string) string {
return androidPackageName
case "ios":
return iosRunnerBundleID
case "tvos":
return tvosRunnerBundleID
default:
return ""
}
Expand All @@ -206,6 +220,8 @@ func agentVersionForPlatform(platform string) string {
return agentVersionAndroid
case "ios":
return agentVersionIOS
case "tvos":
return agentVersionTVOS
default:
return ""
}
Expand Down Expand Up @@ -259,8 +275,21 @@ func installAgentOnSimulator(device devices.ControllableDevice) error {
arch = "arm64"
}

filename := fmt.Sprintf("devicekit-ios-Sim-%s.zip", arch)
agentURL := fmt.Sprintf("https://github.com/mobile-next/devicekit-ios/releases/download/%s/%s", agentVersionIOS, filename)
var filename, version string
switch device.Platform() {
case "tvos":
// The tvOS runner is currently published for Apple Silicon (arm64) only.
if arch != "arm64" {
return fmt.Errorf("the tvOS simulator runner is only available for arm64 (Apple Silicon)")
}
filename = "devicekit-tvos-Sim-arm64.zip"
version = agentVersionTVOS
default:
filename = fmt.Sprintf("devicekit-ios-Sim-%s.zip", arch)
version = agentVersionIOS
}

agentURL := fmt.Sprintf("https://github.com/mobile-next/devicekit-ios/releases/download/%s/%s", version, filename)

tmpDir, err := os.MkdirTemp("", "mobilecli-agent-*")
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion devices/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ func GetDeviceInfoList(opts DeviceListOptions) ([]DeviceInfo, error) {

// get model for devices
model := ""
if d.Platform() == "ios" {
if d.Platform() == "ios" || d.Platform() == "tvos" {
if d.DeviceType() == "real" {
if iosDevice, ok := d.(*IOSDevice); ok {
model = iosDevice.ProductType
Expand Down
1 change: 1 addition & 0 deletions devices/ios.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const (
deviceKitAppLaunchTimeout = 5 * time.Second
deviceKitBroadcastTimeout = 5 * time.Second
agentRunnerBundleID = "com.mobilenext.devicekit-iosUITests.xctrunner"
agentRunnerBundleIDTVOS = "com.mobilenext.devicekit-tvosUITests.xctrunner"
)

// deviceInfoCache caches device name and OS version to avoid expensive GetValues() calls
Expand Down
46 changes: 37 additions & 9 deletions devices/simulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,12 @@ type SimulatorDevice struct {
wdaClient *wda.WdaClient
}

// parseSimulatorVersion parses iOS version from simulator runtime string
// parseSimulatorVersion parses the OS version from a simulator runtime string
// e.g., "com.apple.CoreSimulator.SimRuntime.iOS-18-6" -> "18.6"
// e.g., "com.apple.CoreSimulator.SimRuntime.tvOS-26-5" -> "26.5"
func parseSimulatorVersion(runtime string) string {
// Use regex to extract iOS version from runtime string
re := regexp.MustCompile(`iOS-(\d+)-(\d+)`)
// Use regex to extract the OS version from the runtime string
re := regexp.MustCompile(`(?:iOS|tvOS|watchOS|xrOS)-(\d+)-(\d+)`)
matches := re.FindStringSubmatch(runtime)
if len(matches) == 3 {
return matches[1] + "." + matches[2]
Expand All @@ -69,11 +70,38 @@ func parseSimulatorVersion(runtime string) string {
return runtime
}

// simulatorPlatform derives the mobilecli platform identifier from a simulator
// runtime string. tvOS simulators report "tvos"; everything else defaults to "ios".
func simulatorPlatform(runtime string) string {
if strings.Contains(runtime, "tvOS") {
return "tvos"
}
return "ios"
}

func (s SimulatorDevice) ID() string { return s.UDID }
func (s SimulatorDevice) Name() string { return s.Simulator.Name }
func (s SimulatorDevice) Platform() string { return "ios" }
func (s SimulatorDevice) Platform() string { return simulatorPlatform(s.Runtime) }
func (s SimulatorDevice) DeviceType() string { return "simulator" }
func (s SimulatorDevice) Version() string { return parseSimulatorVersion(s.Runtime) }

// agentRunnerBundleIDForPlatform returns the runner bundle id suffix expected
// for this simulator's platform (iOS vs tvOS).
func (s SimulatorDevice) agentRunnerBundleIDForPlatform() string {
if s.Platform() == "tvos" {
return agentRunnerBundleIDTVOS
}
return agentRunnerBundleID
}

// agentRunnerProcessNameForPlatform returns the runner process name used to
// locate the running agent for this simulator's platform.
func (s SimulatorDevice) agentRunnerProcessNameForPlatform() string {
if s.Platform() == "tvos" {
return "devicekit-tvosUITests-Runner"
}
return "devicekit-iosUITests-Runner"
}
func (s SimulatorDevice) State() string {
if s.Simulator.State == "Booted" {
return "online"
Expand Down Expand Up @@ -300,7 +328,7 @@ func (s SimulatorDevice) findInstalledAgentBundleID() (string, error) {
}

for bundleID := range installedApps {
if strings.HasSuffix(bundleID, agentRunnerBundleID) {
if strings.HasSuffix(bundleID, s.agentRunnerBundleIDForPlatform()) {
return bundleID, nil
}
}
Expand Down Expand Up @@ -589,7 +617,7 @@ func (s *SimulatorDevice) Info() (*FullDeviceInfo, error) {
DeviceInfo: DeviceInfo{
ID: s.UDID,
Name: s.Simulator.Name,
Platform: "ios",
Platform: s.Platform(),
Type: "simulator",
Version: parseSimulatorVersion(s.Runtime),
State: s.State(),
Expand Down Expand Up @@ -715,7 +743,7 @@ func listAllProcesses() ([]ProcessInfo, error) {
return processes, nil
}

func findWdaProcessForDevice(deviceUDID string) (int, string, error) {
func findWdaProcessForDevice(deviceUDID, runnerProcessName string) (int, string, error) {
processes, err := listAllProcesses()
if err != nil {
return 0, "", err
Expand All @@ -724,7 +752,7 @@ func findWdaProcessForDevice(deviceUDID string) (int, string, error) {
devicePath := fmt.Sprintf("/Library/Developer/CoreSimulator/Devices/%s", deviceUDID)

for _, proc := range processes {
if strings.Contains(proc.Command, devicePath) && strings.Contains(proc.Command, "devicekit-iosUITests-Runner") {
if strings.Contains(proc.Command, devicePath) && strings.Contains(proc.Command, runnerProcessName) {
return proc.PID, proc.Command, nil
}
}
Expand Down Expand Up @@ -763,7 +791,7 @@ func extractEnvValue(output, envVar string) (string, error) {
}

func (s *SimulatorDevice) getWdaEnvPort(envVar string) (int, error) {
pid, processInfo, err := findWdaProcessForDevice(s.UDID)
pid, processInfo, err := findWdaProcessForDevice(s.UDID, s.agentRunnerProcessNameForPlatform())
if err != nil {
utils.Verbose("Could not find WDA process: %v", err)
return 0, err
Expand Down
8 changes: 8 additions & 0 deletions devices/wda/press-button.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ func (c *WdaClient) PressButton(key string) error {
"VOLUME_DOWN": "volumeDown",
"HOME": "home",
"LOCK": "lock",
// tvOS Siri Remote buttons (handled by the tvOS device.io.button handler).
"UP": "up",
"DOWN": "down",
"LEFT": "left",
"RIGHT": "right",
"SELECT": "select",
"MENU": "menu",
"PLAY_PAUSE": "playPause",
}

if key == "ENTER" {
Expand Down