diff --git a/cli/agent.go b/cli/agent.go index 47552fb..a6ba5a1 100644 --- a/cli/agent.go +++ b/cli/agent.go @@ -15,7 +15,7 @@ import ( ) const ( - agentVersionIOS = "0.0.22" + agentVersionIOS = "0.0.23" agentVersionAndroid = "1.2.4" iosRunnerBundleID = "com.mobilenext.devicekit-iosUITests.xctrunner" androidPackageName = "com.mobilenext.devicekit" @@ -23,9 +23,9 @@ const ( // pinned SHA-256 checksums for agent artifacts, keyed by download filename var agentChecksums = map[string]string{ - "devicekit-ios-Sim-arm64.zip": "0ee133ef245a2b1a5097123be2a43acac8975ab23e708e3b31f94b5074b84dfc", - "devicekit-ios-Sim-x86_64.zip": "4d0ab31b0ce1bb977a7196c15ef097a3268bff9f442f37b09da9b330fe76ddac", - "devicekit-ios-runner.ipa": "3704509ea7ce17a47e3ffa6c16fb11ebf706321e008a947cfdab4bc0cfed3416", + "devicekit-ios-Sim-arm64.zip": "db7cc329f8d756be9ad60cf15ae57107ed831c53d35be240708dd2baf8da2d4e", + "devicekit-ios-Sim-x86_64.zip": "9de3e48798a6abf600c1bdddefcd3465e2ecccdf373683358b10183e680d6e3a", + "devicekit-ios-runner.ipa": "38e052df988101cba2820c01ba14b6e7193470ca729d4b86a6e44a9a4fbb55fa", "devicekit.apk": "63b1111fbd3b986c7452bc7c28150b1e9c0d611b2ecd7f6917a0f50a84d0836b", } diff --git a/devices/devicekit/requests.go b/devices/devicekit/requests.go index e980529..5ca46ab 100644 --- a/devices/devicekit/requests.go +++ b/devices/devicekit/requests.go @@ -7,11 +7,14 @@ import ( "fmt" "io" "net/http" + "strings" "time" "github.com/mobile-next/mobilecli/utils" ) +type AgentStartupDiagnostic func() (string, error) + type jsonRPCRequest struct { JSONRPC string `json:"jsonrpc"` Method string `json:"method"` @@ -85,6 +88,10 @@ func (c *WdaClient) CallRPCWithTimeout(method string, params any, timeout time.D } func (c *WdaClient) WaitForAgent() error { + return c.WaitForAgentWithDiagnostics(nil) +} + +func (c *WdaClient) WaitForAgentWithDiagnostics(readDiagnostic AgentStartupDiagnostic) error { // Set timeout for the entire operation ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) defer cancel() @@ -95,12 +102,19 @@ func (c *WdaClient) WaitForAgent() error { for { select { case <-ctx.Done(): + if diagnostic := readAgentStartupDiagnostic(readDiagnostic); diagnostic != "" { + return fmt.Errorf("timed out waiting for WebDriverAgent to be ready; agent stderr:\n%s", diagnostic) + } return fmt.Errorf("timed out waiting for WebDriverAgent to be ready") case <-ticker.C: _, err := c.GetStatus() if err != nil { utils.Verbose("WebDriverAgent not ready yet: %v", err) + diagnostic := readAgentStartupDiagnostic(readDiagnostic) + if isFatalAgentStartupDiagnostic(diagnostic) { + return fmt.Errorf("WebDriverAgent failed to start:\n%s", diagnostic) + } continue } @@ -109,3 +123,23 @@ func (c *WdaClient) WaitForAgent() error { } } } + +func readAgentStartupDiagnostic(readDiagnostic AgentStartupDiagnostic) string { + if readDiagnostic == nil { + return "" + } + + diagnostic, err := readDiagnostic() + if err != nil { + utils.Verbose("Failed to read WebDriverAgent startup diagnostics: %v", err) + return "" + } + + return strings.TrimSpace(diagnostic) +} + +func isFatalAgentStartupDiagnostic(diagnostic string) bool { + return strings.Contains(diagnostic, "Library not loaded:") || + strings.Contains(diagnostic, "Termination Reason:") || + strings.Contains(diagnostic, "Fatal error:") +} diff --git a/devices/simulator.go b/devices/simulator.go index e5c1664..1640649 100644 --- a/devices/simulator.go +++ b/devices/simulator.go @@ -29,6 +29,7 @@ type AppInfo struct { CFBundleIdentifier string `json:"CFBundleIdentifier"` CFBundleDisplayName string `json:"CFBundleDisplayName"` CFBundleVersion string `json:"CFBundleVersion"` + Path string `json:"Path"` } // devicePlist represents the structure of device.plist @@ -198,8 +199,16 @@ func filterSimulatorsByDownloadsDirectory(simulators []Simulator) []Simulator { } func (s SimulatorDevice) LaunchAppWithEnv(bundleID string, env map[string]string) error { + return s.launchAppWithEnv(bundleID, env, "") +} + +func (s SimulatorDevice) launchAppWithEnv(bundleID string, env map[string]string, stderrPath string) error { // Build simctl command - fullArgs := append([]string{"simctl", "launch"}, s.UDID, bundleID) + fullArgs := []string{"simctl", "launch"} + if stderrPath != "" { + fullArgs = append(fullArgs, "--stderr="+stderrPath) + } + fullArgs = append(fullArgs, s.UDID, bundleID) cmd := exec.Command("xcrun", fullArgs...) // Set environment variables with SIMCTL_CHILD_ prefix for this command only @@ -215,6 +224,13 @@ func (s SimulatorDevice) LaunchAppWithEnv(bundleID string, env map[string]string return nil } +func simulatorAgentDiagnosticPaths(homeDir string, udid string, launchID int64) (string, string) { + filename := fmt.Sprintf("devicekit-agent-%d.stderr", launchID) + simulatorPath := filepath.Join("/private/tmp", filename) + hostPath := filepath.Join(homeDir, "Library", "Developer", "CoreSimulator", "Devices", udid, "data", "tmp", filename) + return simulatorPath, hostPath +} + func (s SimulatorDevice) LaunchApp(bundleID string, opts LaunchOptions) error { if opts.Activity != "" { return fmt.Errorf("--activity is not supported on iOS") @@ -469,7 +485,19 @@ func (s *SimulatorDevice) StartAgent(config StartAgentConfig) error { "DEVICEKIT_LISTEN_PORT": strconv.Itoa(usePort), } - err = s.LaunchAppWithEnv(agentBundleID, env) + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("failed to locate simulator startup diagnostics: %w", err) + } + + simulatorStderrPath, hostStderrPath := simulatorAgentDiagnosticPaths(homeDir, s.UDID, time.Now().UnixNano()) + defer func() { + if err := os.Remove(hostStderrPath); err != nil && !os.IsNotExist(err) { + utils.Verbose("Failed to remove WebDriverAgent startup diagnostics: %v", err) + } + }() + + err = s.launchAppWithEnv(agentBundleID, env, simulatorStderrPath) if err != nil { return err } @@ -481,7 +509,16 @@ func (s *SimulatorDevice) StartAgent(config StartAgentConfig) error { config.OnProgress("Waiting for agent to start") } - err = s.wdaClient.WaitForAgent() + err = s.wdaClient.WaitForAgentWithDiagnostics(func() (string, error) { + contents, err := os.ReadFile(hostStderrPath) + if os.IsNotExist(err) { + return "", nil + } + if err != nil { + return "", err + } + return string(contents), nil + }) if err != nil { _ = s.TerminateApp(agentBundleID) return err @@ -537,10 +574,20 @@ func (s *SimulatorDevice) ListApps(onlyLaunchable bool) ([]InstalledAppInfo, err var apps []InstalledAppInfo for _, app := range appsMap { + version := app.CFBundleVersion + if app.Path != "" { + metadata, metadataErr := utils.ParseAppMetadata(app.Path) + if metadataErr != nil { + utils.Verbose("failed to read app metadata at %s, using build version %s: %v", app.Path, app.CFBundleVersion, metadataErr) + } else if metadata.Version != "" { + version = metadata.Version + } + } + apps = append(apps, InstalledAppInfo{ PackageName: app.CFBundleIdentifier, AppName: app.CFBundleDisplayName, - Version: app.CFBundleVersion, + Version: version, }) }