diff --git a/internal/ios/ios.go b/internal/ios/ios.go index 36522ab..6353975 100644 --- a/internal/ios/ios.go +++ b/internal/ios/ios.go @@ -69,6 +69,15 @@ func LaunchApp(ctx context.Context, bundleID string, env map[string]string) erro return nil } +// BootedUDID returns the UDID of the currently booted iOS simulator, or "" if none is booted. +func BootedUDID(ctx context.Context) string { + d, _ := bootedSimulator(ctx) + if d == nil { + return "" + } + return d.UDID +} + func bootedSimulator(ctx context.Context) (*simDevice, error) { out, err := exec.CommandContext(ctx, "xcrun", "simctl", "list", "devices", "booted", "--json").Output() if err != nil { diff --git a/internal/testrun/driver.go b/internal/testrun/driver.go index ec2e1d7..e7336d3 100644 --- a/internal/testrun/driver.go +++ b/internal/testrun/driver.go @@ -12,6 +12,7 @@ import ( "github.com/priyanshujain/sanderling/internal/driver" "github.com/priyanshujain/sanderling/internal/driver/chrome" driverSidecar "github.com/priyanshujain/sanderling/internal/driver/sidecar" + "github.com/priyanshujain/sanderling/internal/ios" "github.com/priyanshujain/sanderling/internal/sidecar" ) @@ -35,10 +36,16 @@ func buildDriver(ctx context.Context, options Options, stdout io.Writer) (driver if err != nil { return nil, nil, err } - sidecarCommand := exec.CommandContext(ctx, "java", "-jar", jarPath, + sidecarArgs := []string{"-jar", jarPath, "--port", strconv.Itoa(sidecarPort), "--platform", options.Platform, - ) + } + if options.Platform == "ios" { + if udid := ios.BootedUDID(ctx); udid != "" { + sidecarArgs = append(sidecarArgs, "--udid", udid) + } + } + sidecarCommand := exec.CommandContext(ctx, "java", sidecarArgs...) sidecarCommand.Stdout = stdout sidecarCommand.Stderr = stdout sidecarCommand.Env = android.EnvWithAndroidPlatformTools(os.Environ()) diff --git a/internal/testrun/testrun.go b/internal/testrun/testrun.go index 3308b32..7e6a16a 100644 --- a/internal/testrun/testrun.go +++ b/internal/testrun/testrun.go @@ -110,7 +110,7 @@ func Execute(ctx context.Context, options Options, stdout io.Writer) error { acceptChannel <- acceptResult{conn: conn, err: acceptErr} }() - if err := activeDriver.Launch(ctx, options.BundleID, false); err != nil { + if err := activeDriver.Launch(ctx, options.BundleID, false, nil); err != nil { return fmt.Errorf("launch app: %w", err) } fmt.Fprintf(stdout, "launched %s; waiting for SDK to connect (%.0fs timeout)\n", options.BundleID, sdkAcceptTimeout.Seconds()) @@ -133,35 +133,40 @@ func Execute(ctx context.Context, options Options, stdout io.Writer) error { defer listener.Close() agentPort := listener.Addr().(*net.TCPAddr).Port - // Launch app via simctl with SANDERLING_PORT so the SDK can connect. - if err := ios.LaunchApp(ctx, options.BundleID, map[string]string{ + agentServer := agent.NewServer(listener) + + type acceptResult struct { + conn *agent.Conn + err error + } + acceptChannel := make(chan acceptResult, 1) + go func() { + acceptCtx, cancel := context.WithTimeout(ctx, sdkAcceptTimeout) + defer cancel() + conn, acceptErr := agentServer.Accept(acceptCtx) + acceptChannel <- acceptResult{conn: conn, err: acceptErr} + }() + + // Launch app via XCTest with SANDERLING_PORT so the SDK can connect. + if err := activeDriver.Launch(ctx, options.BundleID, false, map[string]string{ "SANDERLING_PORT": strconv.Itoa(agentPort), }); err != nil { return fmt.Errorf("launch app: %w", err) } fmt.Fprintf(stdout, "iOS app launched with SANDERLING_PORT=%d; waiting for SDK (%.0fs timeout)\n", agentPort, sdkAcceptTimeout.Seconds()) - agentServer := agent.NewServer(listener) - acceptCtx, acceptCancel := context.WithTimeout(ctx, sdkAcceptTimeout) - conn, acceptErr := agentServer.Accept(acceptCtx) - acceptCancel() - if acceptErr != nil { - return fmt.Errorf("accept SDK: %w", acceptErr) + result := <-acceptChannel + if result.err != nil { + return fmt.Errorf("accept SDK: %w", result.err) } - connection = conn + connection = result.conn defer connection.Close() hello := connection.Hello() fmt.Fprintf(stdout, "SDK connected: platform=%s app=%s sdk=%s\n", hello.Platform, hello.AppPackage, hello.Version) - // Initialize Maestro XCTest session after the app is running. - // clearState=false avoids relaunching the already-connected app. - if err := activeDriver.Launch(ctx, options.BundleID, false); err != nil { - fmt.Fprintf(stdout, "warn: maestro launch: %v\n", err) - } - case "web": fmt.Fprintln(stdout, "web mode: skipping SDK") - if err := activeDriver.Launch(ctx, options.BundleID, false); err != nil { + if err := activeDriver.Launch(ctx, options.BundleID, false, nil); err != nil { return fmt.Errorf("launch app: %w", err) } }