diff --git a/maestro-cli/src/main/java/maestro/cli/App.kt b/maestro-cli/src/main/java/maestro/cli/App.kt index bb96b4ab6f..30caeda764 100644 --- a/maestro-cli/src/main/java/maestro/cli/App.kt +++ b/maestro-cli/src/main/java/maestro/cli/App.kt @@ -90,6 +90,12 @@ class App { @Option(names = ["--port"], hidden = true) var port: Int? = null + @Option( + names = ["--driver-host-port"], + description = ["AndroidDriver host port for instrumentation communication (default: 7001)"] + ) + var driverHostPort: Int? = null + @Option( names = ["--device", "--udid"], description = ["(Optional) Device ID to run on explicitly, can be a comma separated list of IDs: --device \"Emulator_1,Emulator_2\" "], diff --git a/maestro-cli/src/main/java/maestro/cli/command/PrintHierarchyCommand.kt b/maestro-cli/src/main/java/maestro/cli/command/PrintHierarchyCommand.kt index d3d71337c9..a8341ddd83 100644 --- a/maestro-cli/src/main/java/maestro/cli/command/PrintHierarchyCommand.kt +++ b/maestro-cli/src/main/java/maestro/cli/command/PrintHierarchyCommand.kt @@ -109,7 +109,7 @@ class PrintHierarchyCommand : Runnable { MaestroSessionManager.newSession( host = parent?.host, port = parent?.port, - driverHostPort = null, + driverHostPort = parent?.driverHostPort, teamId = appleTeamId, deviceId = parent?.deviceId, platform = parent?.platform, diff --git a/maestro-cli/src/main/java/maestro/cli/command/QueryCommand.kt b/maestro-cli/src/main/java/maestro/cli/command/QueryCommand.kt index 2c6d016f1c..af1c47207b 100644 --- a/maestro-cli/src/main/java/maestro/cli/command/QueryCommand.kt +++ b/maestro-cli/src/main/java/maestro/cli/command/QueryCommand.kt @@ -73,7 +73,7 @@ class QueryCommand : Runnable { MaestroSessionManager.newSession( host = parent?.host, port = parent?.port, - driverHostPort = null, + driverHostPort = parent?.driverHostPort, deviceId = parent?.deviceId, platform = parent?.platform, teamId = appleTeamId, diff --git a/maestro-cli/src/main/java/maestro/cli/command/RecordCommand.kt b/maestro-cli/src/main/java/maestro/cli/command/RecordCommand.kt index 59f00c1330..00dbee5fa7 100644 --- a/maestro-cli/src/main/java/maestro/cli/command/RecordCommand.kt +++ b/maestro-cli/src/main/java/maestro/cli/command/RecordCommand.kt @@ -130,7 +130,7 @@ class RecordCommand : Callable { return MaestroSessionManager.newSession( host = parent?.host, port = parent?.port, - driverHostPort = null, + driverHostPort = parent?.driverHostPort, deviceId = deviceId, teamId = appleTeamId, platform = parent?.platform, diff --git a/maestro-cli/src/main/java/maestro/cli/command/StartDeviceCommand.kt b/maestro-cli/src/main/java/maestro/cli/command/StartDeviceCommand.kt index 2f6341c7df..ee30bd1de7 100644 --- a/maestro-cli/src/main/java/maestro/cli/command/StartDeviceCommand.kt +++ b/maestro-cli/src/main/java/maestro/cli/command/StartDeviceCommand.kt @@ -99,7 +99,7 @@ class StartDeviceCommand : Callable { PrintUtils.message(if (p == Platform.IOS) "Launching simulator..." else "Launching emulator...") DeviceService.startDevice( device = device, - driverHostPort = parent?.port + driverHostPort = parent?.driverHostPort ) } } catch (e: LocaleValidationIosException) { diff --git a/maestro-cli/src/main/java/maestro/cli/command/StudioCommand.kt b/maestro-cli/src/main/java/maestro/cli/command/StudioCommand.kt index 6797cc75b6..310f6ae0d7 100644 --- a/maestro-cli/src/main/java/maestro/cli/command/StudioCommand.kt +++ b/maestro-cli/src/main/java/maestro/cli/command/StudioCommand.kt @@ -75,7 +75,7 @@ class StudioCommand : Callable { MaestroSessionManager.newSession( host = parent?.host, port = parent?.port, - driverHostPort = null, + driverHostPort = parent?.driverHostPort, teamId = appleTeamId, deviceId = parent?.deviceId, platform = parent?.platform, diff --git a/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt b/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt index 2eac9dae77..f7a12b0cc3 100644 --- a/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt +++ b/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt @@ -51,6 +51,7 @@ import maestro.cli.util.CiUtils import maestro.cli.util.EnvUtils import maestro.cli.util.FileUtils.isWebFlow import maestro.cli.util.PrintUtils +import maestro.cli.util.isPortAvailable import maestro.cli.insights.TestAnalysisManager import maestro.cli.view.greenBox import maestro.cli.view.box @@ -503,11 +504,27 @@ class TestCommand : Callable { } } - private fun selectPort(effectiveShards: Int): Int = - if (effectiveShards == 1) 7001 - else (7001..7128).shuffled().find { port -> - usedPorts.putIfAbsent(port, true) == null + private fun selectPort(effectiveShards: Int): Int { + // If user specified driver host port via CLI, use it + parent?.driverHostPort?.let { port -> + if (!isPortAvailable(port)) { + throw CliError("Port $port is already in use. Please specify a different port with --driver-host-port") + } + return port + } + + // Otherwise use default behavior + if (effectiveShards == 1) { + if (!isPortAvailable(7001)) { + throw CliError("Default port 7001 is already in use. Use --driver-host-port to specify a different port") + } + return 7001 + } + + return (7001..7128).shuffled().find { port -> + usedPorts.putIfAbsent(port, true) == null && isPortAvailable(port) } ?: error("No available ports found") + } private fun runSingleFlow( maestro: Maestro, diff --git a/maestro-cli/src/main/java/maestro/cli/util/SocketUtils.kt b/maestro-cli/src/main/java/maestro/cli/util/SocketUtils.kt index fcbab81721..5291b2633c 100644 --- a/maestro-cli/src/main/java/maestro/cli/util/SocketUtils.kt +++ b/maestro-cli/src/main/java/maestro/cli/util/SocketUtils.kt @@ -9,4 +9,12 @@ fun getFreePort(): Int { } catch (ignore: Exception) {} } ServerSocket(0).use { return it.localPort } +} + +fun isPortAvailable(port: Int): Boolean { + return try { + ServerSocket(port).use { true } + } catch (e: Exception) { + false + } } \ No newline at end of file