Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New Feature] Sharding / Parallel Execution #1732

Merged
merged 8 commits into from
Jun 17, 2024
9 changes: 6 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@ apkParser = "2.6.10"
appdirs = "1.2.1"
axml = "2.1.2"
commons-codec = "1.15"
commonsLang3 = "3.13.0"
dadb = "1.2.6"
detekt = "1.19.0"
googleAndroidMaterial = "1.6.0"
googleFindbugs = "3.0.2"
googleGson = "2.9.0"
googleProtobuf = "3.21.9"
googleProtobufKotlinLite = "3.21.9"
googleProtobufPlugin = "0.9.1"
googleTruth = "1.1.3"
graaljs = "22.0.0" # Latest version that supports Java 8
grpc = "1.50.2"
grpcKotlinStub = "1.3.0"
imageComparison = "4.4.0"
hiddenapibypass = "4.3"
jackson = "2.13.4"
jansi = "2.4.0"
jarchivelib = "1.2.0"
Expand All @@ -47,10 +47,12 @@ squareRetrofit = "2.9.0"
squareMockWebServer = "4.11.0"
wiremock = "2.35.0"
logback="1.2.6"
coroutines = "1.6.3"

[libraries]
android-tools-apkparser = { module = "com.android.tools.apkparser:apkanalyzer", version.ref = "androidToolsApkParser" }
android-tools-sdk = { module = "com.android.tools:sdk-common", version.ref = "androidToolsSdk" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidxAppcompat" }
androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidxConstraintlayout" }
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidxCore" }
Expand All @@ -61,8 +63,8 @@ apk-parser = { module = "net.dongliu:apk-parser", version.ref = "apkParser" }
appdirs = { module = "net.harawata:appdirs", version.ref = "appdirs" }
axml = { module = "de.upb.cs.swt:axml", version.ref = "axml" }
commons-codec = { module = "commons-codec:commons-codec", version.ref = "commons-codec" }
commons-lang3 = { module = "org.apache.commons:commons-lang3", version.ref = "commonsLang3" }
dadb = { module = "dev.mobile:dadb", version.ref = "dadb" }
google-android-material = { module = "com.google.android.material:material", version.ref = "googleAndroidMaterial" }
google-findbugs = { module = "com.google.code.findbugs:jsr305", version.ref = "googleFindbugs" }
google-gson = { module = "com.google.code.gson:gson", version.ref = "googleGson" }
google-protobuf = { module = "com.google.protobuf:protoc", version.ref = "googleProtobuf" }
Expand All @@ -79,6 +81,7 @@ grpc-protobuf-kotlin = { module = "io.grpc:grpc-protobuf-kotlin", version.ref =
grpc-protobuf-lite = { module = "io.grpc:grpc-protobuf-lite", version.ref = "grpc" }
grpc-protoc-gen-java = { module = "io.grpc:protoc-gen-grpc-java", version.ref = "grpc"}
grpc-stub = { module = "io.grpc:grpc-stub", version.ref = "grpc" }
hiddenapibypass = { module = "org.lsposed.hiddenapibypass:hiddenapibypass", version.ref = "hiddenapibypass" }
image-comparison = { module = "com.github.romankh3:image-comparison", version.ref = "imageComparison" }
jackson-core-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
jackson-dataformat-xml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-xml", version.ref = "jackson" }
Expand Down
19 changes: 14 additions & 5 deletions maestro-android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ protobuf {
}
}

kotlin.sourceSets.all {
kotlin.sourceSets.configureEach {
// Prevent build warnings for grpc's generated opt-in code
languageSettings.optIn("kotlin.RequiresOptIn")
}
Expand Down Expand Up @@ -116,8 +116,17 @@ tasks.register("copyMaestroServer", Copy) {
}
}

tasks.named("assemble") { finalizedBy("copyMaestroAndroid") }
tasks.named("assembleAndroidTest") { finalizedBy("copyMaestroServer") }
tasks.named("assemble") {
lint.enabled = false
lintVitalRelease.enabled = false
finalizedBy("copyMaestroAndroid")
}

tasks.named("assembleAndroidTest") {
lint.enabled = false
lintVitalRelease.enabled = false
finalizedBy("copyMaestroServer")
}

sourceSets {
generated {
Expand All @@ -144,8 +153,8 @@ dependencies {
implementation(libs.ktor.server.content.negotiation)
implementation(libs.ktor.serial.gson)

implementation('org.apache.commons:commons-lang3:3.13.0')
implementation('org.lsposed.hiddenapibypass:hiddenapibypass:4.3')
implementation(libs.commons.lang3)
implementation(libs.hiddenapibypass)

androidTestImplementation(libs.androidx.test.junit)
androidTestImplementation(libs.androidx.espresso.core)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,22 @@ import com.google.protobuf.ByteString
import io.grpc.Status
import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder
import io.grpc.stub.StreamObserver
import maestro_android.*
import maestro_android.MaestroAndroid
import maestro_android.MaestroDriverGrpc
import maestro_android.addMediaResponse
import maestro_android.checkWindowUpdatingResponse
import maestro_android.deviceInfo
import maestro_android.eraseAllTextResponse
import maestro_android.inputTextResponse
import maestro_android.launchAppResponse
import maestro_android.screenshotResponse
import maestro_android.setLocationResponse
import maestro_android.tapResponse
import maestro_android.viewHierarchyResponse
import org.junit.Test
import org.junit.runner.RunWith
import java.io.ByteArrayOutputStream
import java.io.OutputStream
import java.lang.IllegalStateException
import java.util.IllegalFormatException
import kotlin.system.measureTimeMillis

/**
Expand All @@ -76,7 +85,11 @@ class MaestroDriverService {
val uiDevice = UiDevice.getInstance(instrumentation)
val uiAutomation = instrumentation.uiAutomation

NettyServerBuilder.forPort(7001)
val port = InstrumentationRegistry.getArguments().getString("port", "7001").toInt()

println("Server running on port [ $port ]")

NettyServerBuilder.forPort(port)
.addService(Service(uiDevice, uiAutomation))
.build()
.start()
Expand All @@ -85,7 +98,6 @@ class MaestroDriverService {
Thread.sleep(100)
}
}

}

class Service(
Expand Down
1 change: 1 addition & 0 deletions maestro-cli/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ dependencies {
implementation(libs.ktor.client.cio)
implementation(libs.jarchivelib)
implementation(libs.commons.codec)
implementation(libs.kotlinx.coroutines.core)
implementation("org.jetbrains.kotlinx:kotlinx-html:$kotlinxHtmlVersion")

testImplementation(libs.junit.jupiter.api)
Expand Down
5 changes: 3 additions & 2 deletions maestro-cli/src/main/java/maestro/cli/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ import maestro.debuglog.DebugLogStore
import picocli.CommandLine
import picocli.CommandLine.Command
import picocli.CommandLine.Option
import java.util.*
import java.util.Properties
import kotlin.random.Random
import kotlin.system.exitProcess

@Command(
Expand Down Expand Up @@ -66,7 +67,7 @@ class App {
@Option(names = ["--port"], hidden = true)
var port: Int? = null

@Option(names = ["--device", "--udid"], description = ["(Optional) Select a device to run on explicitly"])
@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\" "])
var deviceId: String? = null
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@ class PrintHierarchyCommand : Runnable {
private val parent: App? = null

override fun run() {
MaestroSessionManager.newSession(parent?.host, parent?.port, parent?.deviceId) { session ->
MaestroSessionManager.newSession(
host = parent?.host,
port = parent?.port,
driverHostPort = parent?.port,
deviceId = parent?.deviceId,
) { session ->
Insights.onInsightsUpdated {
val message = StringBuilder()
val level = it.level.toString().lowercase().replaceFirstChar(Char::uppercase)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ class QueryCommand : Runnable {
lateinit var commandSpec: Model.CommandSpec

override fun run() {
MaestroSessionManager.newSession(parent?.host, parent?.port, parent?.deviceId) { session ->
MaestroSessionManager.newSession(
host = parent?.host,
port = parent?.port,
driverHostPort = parent?.port,
deviceId = parent?.deviceId
) { session ->
val filters = mutableListOf<ElementFilter>()

text?.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,12 @@ class RecordCommand : Callable<Int> {
TestDebugReporter.install(debugOutputPathAsString = debugOutput)
val path = TestDebugReporter.getDebugOutputPath()

return MaestroSessionManager.newSession(parent?.host, parent?.port, parent?.deviceId) { session ->
return MaestroSessionManager.newSession(
host = parent?.host,
port = parent?.port,
driverHostPort = parent?.port,
deviceId = parent?.deviceId
) { session ->
val maestro = session.maestro
val device = session.device

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package maestro.cli.command

import maestro.cli.App
import maestro.cli.CliError
import maestro.cli.device.DeviceCreateUtil
import maestro.cli.device.DeviceService
Expand Down Expand Up @@ -27,6 +28,9 @@ import java.util.concurrent.Callable
)
class StartDeviceCommand : Callable<Int> {

@CommandLine.ParentCommand
private val parent: App? = null

@CommandLine.Option(
order = 0,
names = ["--platform"],
Expand Down Expand Up @@ -87,9 +91,12 @@ class StartDeviceCommand : Callable<Int> {
try {
val (deviceLanguage, deviceCountry) = LocaleUtils.parseLocaleParams(locale, maestroPlatform)

DeviceCreateUtil.getOrCreateDevice(p, o, deviceLanguage, deviceCountry, forceCreate).let {
DeviceCreateUtil.getOrCreateDevice(p, o, deviceLanguage, deviceCountry, forceCreate).let { device ->
PrintUtils.message(if (p == Platform.IOS) "Launching simulator..." else "Launching emulator...")
DeviceService.startDevice(it)
DeviceService.startDevice(
device = device,
driverHostPort = parent?.port
)
}
} catch (e: LocaleValidationIosException) {
val locales = LocaleUtils.IOS_SUPPORTED_LOCALES.joinToString("\n")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,13 @@ class StudioCommand : Callable<Int> {

TestDebugReporter.install(debugOutputPathAsString = debugOutput)

MaestroSessionManager.newSession(parent?.host, parent?.port, parent?.deviceId, true) { session ->
MaestroSessionManager.newSession(
host = parent?.host,
port = parent?.port,
driverHostPort = parent?.port,
deviceId = parent?.deviceId,
isStudio = true
) { session ->
val port = getFreePort()
MaestroStudio.start(port, session.maestro)

Expand Down
Loading
Loading