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

Add installApp command #1854

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import okio.sink
import org.rauschig.jarchivelib.ArchiveFormat
import org.rauschig.jarchivelib.ArchiverFactory
import java.io.File
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.io.path.absolute

Expand Down Expand Up @@ -73,6 +72,7 @@ class CloudInteractor(
): Int {
if (appBinaryId == null && appFile == null) throw CliError("Missing required parameter for option '--app-file' or '--app-binary-id'")
if (!flowFile.exists()) throw CliError("File does not exist: ${flowFile.absolutePath}")
if (appFile?.toPath()?.startsWith(flowFile.toPath().absolute())?.not() == true) throw CliError("App file does not exist: ${flowFile.absolutePath}")
bartekpacia marked this conversation as resolved.
Show resolved Hide resolved
if (mapping?.exists() == false) throw CliError("File does not exist: ${mapping.absolutePath}")
if (async && reportFormat != ReportFormat.NOOP) throw CliError("Cannot use --format with --async")

Expand Down
2 changes: 2 additions & 0 deletions maestro-client/src/main/java/maestro/Driver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,6 @@ interface Driver {
fun isAirplaneModeEnabled(): Boolean

fun setAirplaneMode(enabled: Boolean)

fun installApp(path: String)
}
4 changes: 4 additions & 0 deletions maestro-client/src/main/java/maestro/Maestro.kt
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,10 @@ class Maestro(
driver.setAirplaneMode(enabled)
}

fun installApp(path: String?) {
driver.installApp(path.orEmpty())
}

companion object {

private val LOGGER = LoggerFactory.getLogger(Maestro::class.java)
Expand Down
6 changes: 6 additions & 0 deletions maestro-client/src/main/java/maestro/drivers/AndroidDriver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,12 @@ class AndroidDriver(
shell("cmd connectivity airplane-mode $value")
}

override fun installApp(path: String) {
val apkFile = File(path)
install(apkFile)
}


private fun broadcastAirplaneMode(enabled: Boolean) {
val command = "am broadcast -a android.intent.action.AIRPLANE_MODE --ez state $enabled"
try {
Expand Down
4 changes: 4 additions & 0 deletions maestro-client/src/main/java/maestro/drivers/IOSDriver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,10 @@ class IOSDriver(
LOGGER.warn("Airplane mode is not available on iOS simulators")
}

override fun installApp(path: String) {
iosDevice.installApp(path)
}

private fun addMediaToDevice(mediaFile: File) {
val namedSource = NamedSource(
mediaFile.name,
Expand Down
4 changes: 4 additions & 0 deletions maestro-client/src/main/java/maestro/drivers/WebDriver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,10 @@ class WebDriver(val isStudio: Boolean) : Driver {
TODO("Not yet implemented")
}

override fun installApp(path: String) {
TODO("Not yet implemented")
}

companion object {
private const val SCREENSHOT_DIFF_THRESHOLD = 0.005
private const val RETRY_FETCHING_CONTENT_DESCRIPTION = 10
Expand Down
12 changes: 12 additions & 0 deletions maestro-ios-driver/src/main/kotlin/util/LocalSimulatorUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,18 @@ object LocalSimulatorUtils {
)
}

fun install(deviceId: String, path: String) {
runCommand(
listOf(
"xcrun",
"simctl",
"install",
deviceId,
path,
)
)
}

fun uninstall(deviceId: String, bundleId: String) {
runCommand(
listOf(
Expand Down
2 changes: 2 additions & 0 deletions maestro-ios/src/main/java/ios/IOSDevice.kt
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ interface IOSDevice : AutoCloseable {
fun eraseText(charactersToErase: Int)

fun addMedia(path: String)

fun installApp(path: String)
}

interface IOSScreenRecording : AutoCloseable
4 changes: 4 additions & 0 deletions maestro-ios/src/main/java/ios/LocalIOSDevice.kt
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,8 @@ class LocalIOSDevice(
override fun addMedia(path: String) {
simctlIOSDevice.addMedia(path)
}

override fun installApp(path: String) {
simctlIOSDevice.installApp(path)
}
}
7 changes: 7 additions & 0 deletions maestro-ios/src/main/java/ios/simctl/SimctlIOSDevice.kt
Original file line number Diff line number Diff line change
Expand Up @@ -172,4 +172,11 @@ class SimctlIOSDevice(
stopScreenRecording()
}

override fun installApp(path: String) {
LocalSimulatorUtils.install(
deviceId = deviceId,
path = path
)
}

}
4 changes: 4 additions & 0 deletions maestro-ios/src/main/java/ios/xctest/XCTestIOSDevice.kt
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ class XCTestIOSDevice(
execute { client.eraseText(charactersToErase, appIds) }
}

override fun installApp(path: String) {
error("Not supported")
}

private fun activeAppId(): String {
return execute {
val appIds = getInstalledApps()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,21 @@ data class ToggleAirplaneModeCommand(
}
}

data class InstallAppCommand(
val path: String? = null,
override val label: String? = null,
override val optional: Boolean = false,
): Command {
override fun description(): String {
return label ?: "Installing $path"
}

override fun evaluateScripts(jsEngine: JsEngine): Command {
return this
}

}

internal fun tapOnDescription(isLongPress: Boolean?, repeat: TapRepeat?): String {
return if (isLongPress == true) "Long press"
else if (repeat != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ data class MaestroCommand(
val addMediaCommand: AddMediaCommand? = null,
val setAirplaneModeCommand: SetAirplaneModeCommand? = null,
val toggleAirplaneModeCommand: ToggleAirplaneModeCommand? = null,
val installAppCommand: InstallAppCommand? = null,
) {

constructor(command: Command) : this(
Expand Down Expand Up @@ -109,6 +110,7 @@ data class MaestroCommand(
addMediaCommand = command as? AddMediaCommand,
setAirplaneModeCommand = command as? SetAirplaneModeCommand,
toggleAirplaneModeCommand = command as? ToggleAirplaneModeCommand,
installAppCommand = command as? InstallAppCommand,
)

fun asCommand(): Command? = when {
Expand Down Expand Up @@ -151,6 +153,7 @@ data class MaestroCommand(
addMediaCommand != null -> addMediaCommand
setAirplaneModeCommand != null -> setAirplaneModeCommand
toggleAirplaneModeCommand != null -> toggleAirplaneModeCommand
installAppCommand != null -> installAppCommand
else -> null
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ class Orchestra(
is AddMediaCommand -> addMediaCommand(command.mediaPaths)
is SetAirplaneModeCommand -> setAirplaneMode(command)
is ToggleAirplaneModeCommand -> toggleAirplaneMode()
is InstallAppCommand -> installApp(command)
else -> true
}.also { mutating ->
if (mutating) {
Expand All @@ -303,6 +304,12 @@ class Orchestra(
}
}

private fun installApp(command: InstallAppCommand): Boolean {
maestro.installApp(command.path)

return true
}

private fun setAirplaneMode(command: SetAirplaneModeCommand): Boolean {
when (command.value) {
AirplaneValue.Enable -> maestro.setAirplaneModeState(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ data class YamlFluentCommand(
val addMedia: YamlAddMedia? = null,
val setAirplaneMode: YamlSetAirplaneMode? = null,
val toggleAirplaneMode: YamlToggleAirplaneMode? = null,
val installApp: YamlInstallApp? = null,
) {

@SuppressWarnings("ComplexMethod")
Expand Down Expand Up @@ -251,6 +252,14 @@ data class YamlFluentCommand(
val tapRepeat = TapRepeat(2, delay)
listOf(tapCommand(doubleTapOn, tapRepeat = tapRepeat))
}
installApp != null -> listOf(
MaestroCommand(
InstallAppCommand(
path = installApp.path,
label = installApp.label
)
)
)
setAirplaneMode != null -> listOf(MaestroCommand(SetAirplaneModeCommand(setAirplaneMode.value, setAirplaneMode.label, setAirplaneMode.optional)))
toggleAirplaneMode != null -> listOf(MaestroCommand(ToggleAirplaneModeCommand(toggleAirplaneMode.label, toggleAirplaneMode.optional)))
else -> throw SyntaxError("Invalid command: No mapping provided for $this")
Expand Down Expand Up @@ -726,6 +735,10 @@ data class YamlFluentCommand(
assertNoDefectsWithAI = YamlAssertNoDefectsWithAI()
)

"installApp" -> YamlFluentCommand(
installApp = YamlInstallApp()
)

else -> throw SyntaxError("Invalid command: \"$stringCommand\"")
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package maestro.orchestra.yaml

import com.fasterxml.jackson.annotation.JsonCreator

data class YamlInstallApp(
val path: String? = null,
val label: String? = null,
) {
companion object {
@JvmStatic
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
fun parse(path: String) = YamlInstallApp(
path = path,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,10 @@ class FakeDriver : Driver {
this.airplaneMode = enabled
}

override fun installApp(path: String) {
events.add(Event.InstallApp)
}

sealed class Event {

data class Tap(
Expand Down Expand Up @@ -486,6 +490,8 @@ class FakeDriver : Driver {
object StartRecording : Event()

object StopRecording : Event()

object InstallApp: Event()
}

interface UserInteraction
Expand Down
20 changes: 19 additions & 1 deletion maestro-test/src/test/kotlin/maestro/test/IntegrationTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3100,7 +3100,25 @@ class IntegrationTest {
}

@Test
fun `Case 117 - Scroll until view is visible - with speed and timeout evaluate`() {
fun `Case 117 - Install app`() {
// Given
val commands = readCommands("116_install_app")

val driver = driver {
}

// When
Maestro(driver).use {
orchestra(it).runFlow(commands)
}

// Then
// No test failure
driver.assertHasEvent(Event.InstallApp)
}

@Test
fun `Case 118 - Scroll until view is visible - with speed and timeout evaluate`() {
// Given
val commands = readCommands("117_scroll_until_visible_speed")
val expectedDuration = "601"
Expand Down
4 changes: 4 additions & 0 deletions maestro-test/src/test/resources/116_install_app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
appId: com.example.app
---
- installApp:
"maestro-client/src/main/resources/maestro-app.apk"
Loading