Skip to main content
Glama
XCRunnerCLIUtils.kt5.1 kB
package util import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import net.harawata.appdirs.AppDirsFactory import java.io.File import java.nio.file.Files import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.util.concurrent.TimeUnit import kotlin.io.path.absolutePathString object XCRunnerCLIUtils { private const val APP_NAME = "maestro" private const val APP_AUTHOR = "mobile_dev" private const val LOG_DIR_DATE_FORMAT = "yyyy-MM-dd_HHmmss" private const val MAX_COUNT_XCTEST_LOGS = 5 private val dateFormatter by lazy { DateTimeFormatter.ofPattern(LOG_DIR_DATE_FORMAT) } internal val logDirectory by lazy { val parentName = AppDirsFactory.getInstance().getUserLogDir(APP_NAME, null, APP_AUTHOR) val logsDirectory = File(parentName, "xctest_runner_logs") File(parentName).apply { if (!exists()) mkdir() if (!logsDirectory.exists()) logsDirectory.mkdir() val existing = logsDirectory.listFiles() ?: emptyArray() val toDelete = existing.sortedByDescending { it.name } val count = toDelete.size if (count > MAX_COUNT_XCTEST_LOGS) toDelete.forEach { it.deleteRecursively() } } logsDirectory } fun clearLogs() { logDirectory.listFiles()?.forEach { it.deleteRecursively() } } fun listApps(deviceId: String): Set<String> { val process = Runtime.getRuntime().exec(arrayOf("bash", "-c", "xcrun simctl listapps $deviceId | plutil -convert json - -o -")) val json = String(process.inputStream.readBytes()) if (json.isEmpty()) return emptySet() val mapper = jacksonObjectMapper() val appsMap = mapper.readValue(json, Map::class.java) as Map<String, Any> return appsMap.keys } fun setProxy(host: String, port: Int) { ProcessBuilder("networksetup", "-setwebproxy", "Wi-Fi", host, port.toString()) .redirectErrorStream(true) .start() .waitFor() ProcessBuilder("networksetup", "-setsecurewebproxy", "Wi-Fi", host, port.toString()) .redirectErrorStream(true) .start() .waitFor() } fun resetProxy() { ProcessBuilder("networksetup", "-setwebproxystate", "Wi-Fi", "off") .redirectErrorStream(true) .start() .waitFor() ProcessBuilder("networksetup", "-setsecurewebproxystate", "Wi-Fi", "off") .redirectErrorStream(true) .start() .waitFor() } fun uninstall(bundleId: String, deviceId: String) { CommandLineUtils.runCommand( listOf( "xcrun", "simctl", "uninstall", deviceId, bundleId ) ) } private fun runningApps(deviceId: String): Map<String, Int?> { val process = ProcessBuilder( "xcrun", "simctl", "spawn", deviceId, "launchctl", "list" ).start() val processOutput = process.inputStream.bufferedReader().readLines() if (!process.waitFor(3000, TimeUnit.MILLISECONDS)) { return emptyMap() } return processOutput .asSequence() .drop(1) .toList() .map { line -> line.split("\\s+".toRegex()) } .filter { parts -> parts.count() <= 3 } .associate { parts -> parts[2] to parts[0].toIntOrNull() } .mapKeys { (key, _) -> // Fixes issue with iOS 14.0 where process names are sometimes prefixed with "UIKitApplication:" // and ending with [stuff] key .substringBefore("[") .replace("UIKitApplication:", "") } } fun pidForApp(bundleId: String, deviceId: String): Int? { return runningApps(deviceId)[bundleId] } fun runXcTestWithoutBuild(deviceId: String, xcTestRunFilePath: String, port: Int, snapshotKeyHonorModalViews: Boolean?): Process { val date = dateFormatter.format(LocalDateTime.now()) val outputFile = File(logDirectory, "xctest_runner_$date.log") val logOutputDir = Files.createTempDirectory("maestro_xctestrunner_xcodebuild_output") val params = mutableMapOf("TEST_RUNNER_PORT" to port.toString()) if (snapshotKeyHonorModalViews != null) { params["TEST_RUNNER_snapshotKeyHonorModalViews"] = snapshotKeyHonorModalViews.toString() } return CommandLineUtils.runCommand( listOf( "xcodebuild", "test-without-building", "-xctestrun", xcTestRunFilePath, "-destination", "id=$deviceId", "-derivedDataPath", logOutputDir.absolutePathString() ), waitForCompletion = false, outputFile = outputFile, params = params, ) } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mobile-dev-inc/Maestro'

If you have feedback or need assistance with the MCP directory API, please join our Discord server