Skip to content

Commit 8c24ce0

Browse files
authored
Add SetPermissions command (mobile-dev-inc#2775)
* Add setPermissions command * Improve Android permissions call and catch block
1 parent c295b9c commit 8c24ce0

File tree

12 files changed

+150
-5
lines changed

12 files changed

+150
-5
lines changed

maestro-client/src/main/java/maestro/Errors.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ sealed class MaestroException(override val message: String, cause: Throwable? =
2525

2626
class UnableToClearState(message: String, cause: Throwable? = null) : MaestroException(message, cause)
2727

28+
class UnableToSetPermissions(message: String, cause: Throwable? = null) : MaestroException(message, cause)
29+
2830
class AppCrash(message: String, cause: Throwable? = null): MaestroException(message, cause)
2931

3032
class DriverTimeout(message: String, val debugMessage: String? = null, cause: Throwable? = null): MaestroException(message, cause)

maestro-client/src/main/java/maestro/drivers/AndroidDriver.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -926,9 +926,11 @@ class AndroidDriver(
926926

927927
private fun setPermissionInternal(appId: String, permission: String, permissionValue: String) {
928928
try {
929-
dadb.shell("pm $permissionValue $appId $permission")
929+
shell("pm $permissionValue $appId $permission")
930930
} catch (exception: Exception) {
931-
/* no-op */
931+
// We don't need to be loud about this. IOExceptions were caught in shell. Remaining issues are likely due
932+
// to "all" containing permissions that the app doesn't support.
933+
logger.debug("Failed to set permission $permission for app $appId: ${exception.message}")
932934
}
933935
}
934936

maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,24 @@ data class LaunchAppCommand(
520520
}
521521
}
522522

523+
data class SetPermissionsCommand(
524+
val appId: String,
525+
var permissions: Map<String, String>,
526+
override val label: String? = null,
527+
override val optional: Boolean = false,
528+
) : Command {
529+
530+
override val originalDescription: String
531+
get() = "Set permissions"
532+
533+
override fun evaluateScripts(jsEngine: JsEngine): SetPermissionsCommand {
534+
return copy(
535+
appId = appId.evaluateScripts(jsEngine),
536+
label = label?.evaluateScripts(jsEngine)
537+
)
538+
}
539+
}
540+
523541
data class ApplyConfigurationCommand(
524542
val config: MaestroConfig,
525543
override val label: String? = null,

maestro-orchestra-models/src/main/java/maestro/orchestra/MaestroCommand.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ data class MaestroCommand(
4242
val inputTextCommand: InputTextCommand? = null,
4343
val inputRandomTextCommand: InputRandomCommand? = null,
4444
val launchAppCommand: LaunchAppCommand? = null,
45+
val setPermissionsCommand: SetPermissionsCommand? = null,
4546
val applyConfigurationCommand: ApplyConfigurationCommand? = null,
4647
val openLinkCommand: OpenLinkCommand? = null,
4748
val pressKeyCommand: PressKeyCommand? = null,
@@ -87,6 +88,7 @@ data class MaestroCommand(
8788
inputTextCommand = command as? InputTextCommand,
8889
inputRandomTextCommand = command as? InputRandomCommand,
8990
launchAppCommand = command as? LaunchAppCommand,
91+
setPermissionsCommand = command as? SetPermissionsCommand,
9092
applyConfigurationCommand = command as? ApplyConfigurationCommand,
9193
openLinkCommand = command as? OpenLinkCommand,
9294
pressKeyCommand = command as? PressKeyCommand,
@@ -132,6 +134,7 @@ data class MaestroCommand(
132134
inputTextCommand != null -> inputTextCommand
133135
inputRandomTextCommand != null -> inputRandomTextCommand
134136
launchAppCommand != null -> launchAppCommand
137+
setPermissionsCommand != null -> setPermissionsCommand
135138
applyConfigurationCommand != null -> applyConfigurationCommand
136139
openLinkCommand != null -> openLinkCommand
137140
pressKeyCommand != null -> pressKeyCommand

maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ class Orchestra(
344344
is InputTextCommand -> inputTextCommand(command)
345345
is InputRandomCommand -> inputTextRandomCommand(command)
346346
is LaunchAppCommand -> launchAppCommand(command)
347+
is SetPermissionsCommand -> setPermissionsCommand(command)
347348
is OpenLinkCommand -> openLinkCommand(command, config)
348349
is PressKeyCommand -> pressKeyCommand(command)
349350
is EraseTextCommand -> eraseTextCommand(command)
@@ -993,14 +994,18 @@ class Orchestra(
993994
if (command.clearState == true) {
994995
maestro.clearAppState(command.appId)
995996
}
997+
} catch (e: Exception) {
998+
logger.error("Failed to clear state", e)
999+
throw MaestroException.UnableToClearState("Unable to clear state for app ${command.appId}: ${e.message}", e)
1000+
}
9961001

1002+
try {
9971003
// For testing convenience, default to allow all on app launch
9981004
val permissions = command.permissions ?: mapOf("all" to "allow")
9991005
maestro.setPermissions(command.appId, permissions)
1000-
10011006
} catch (e: Exception) {
1002-
logger.error("Failed to clear state", e)
1003-
throw MaestroException.UnableToClearState("Unable to clear state for app ${command.appId}", cause = e)
1007+
logger.error("Failed to set permissions", e)
1008+
throw MaestroException.UnableToSetPermissions("Unable to set permissions for app ${command.appId}: ${e.message}", e)
10041009
}
10051010

10061011
try {
@@ -1017,6 +1022,16 @@ class Orchestra(
10171022
return true
10181023
}
10191024

1025+
private fun setPermissionsCommand(command: SetPermissionsCommand): Boolean {
1026+
try {
1027+
maestro.setPermissions(command.appId, command.permissions)
1028+
} catch (e: Exception) {
1029+
throw MaestroException.UnableToSetPermissions("Unable to set permissions for app ${command.appId}: ${e.message}", e)
1030+
}
1031+
1032+
return true
1033+
}
1034+
10201035
private fun clearKeychainCommand(): Boolean {
10211036
maestro.clearKeychain()
10221037

maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import maestro.orchestra.ScrollUntilVisibleCommand
6060
import maestro.orchestra.SetAirplaneModeCommand
6161
import maestro.orchestra.SetLocationCommand
6262
import maestro.orchestra.SetOrientationCommand
63+
import maestro.orchestra.SetPermissionsCommand
6364
import maestro.orchestra.StartRecordingCommand
6465
import maestro.orchestra.StopAppCommand
6566
import maestro.orchestra.StopRecordingCommand
@@ -109,6 +110,7 @@ data class YamlFluentCommand(
109110
val inputRandomCountryName: YamlInputRandomCountryName? = null,
110111
val inputRandomColorName: YamlInputRandomColorName? = null,
111112
val launchApp: YamlLaunchApp? = null,
113+
val setPermissions: YamlSetPermissions? = null,
112114
val swipe: YamlSwipe? = null,
113115
val openLink: YamlOpenLink? = null,
114116
val openBrowser: String? = null,
@@ -151,6 +153,7 @@ data class YamlFluentCommand(
151153
private fun _toCommands(flowPath: Path, appId: String): List<MaestroCommand> {
152154
return when {
153155
launchApp != null -> listOf(launchApp(launchApp, appId))
156+
setPermissions != null -> listOf(setPermissions(command = setPermissions, appId))
154157
tapOn != null -> listOf(tapCommand(tapOn))
155158
longPressOn != null -> listOf(tapCommand(longPressOn, longPress = true))
156159
assertVisible != null -> listOf(
@@ -706,6 +709,17 @@ data class YamlFluentCommand(
706709
)
707710
}
708711

712+
private fun setPermissions(command: YamlSetPermissions, appId: String): MaestroCommand {
713+
return MaestroCommand(
714+
SetPermissionsCommand(
715+
appId = command.appId ?: appId,
716+
permissions = command.permissions,
717+
label = command.label,
718+
optional = command.optional,
719+
)
720+
)
721+
}
722+
709723
private fun tapCommand(
710724
tapOn: YamlElementSelectorUnion,
711725
longPress: Boolean = false,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package maestro.orchestra.yaml
2+
3+
import com.fasterxml.jackson.annotation.JsonAlias
4+
5+
data class YamlSetPermissions(
6+
@JsonAlias("url")
7+
val appId: String?,
8+
val permissions: Map<String, String>,
9+
val label: String? = null,
10+
val optional: Boolean = false,
11+
)

maestro-orchestra/src/test/java/maestro/orchestra/MaestroCommandSerializationTest.kt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,37 @@ internal class MaestroCommandSerializationTest {
350350
.isEqualTo(command)
351351
}
352352

353+
@Test
354+
fun `serialize SetPermissionsCommand`() {
355+
// given
356+
val command = MaestroCommand(
357+
SetPermissionsCommand("com.twitter.android", permissions = mapOf("all" to "deny", "notifications" to "unset"))
358+
)
359+
360+
// when
361+
val serializedCommandJson = command.toJson()
362+
val deserializedCommand = objectMapper.readValue(serializedCommandJson, MaestroCommand::class.java)
363+
364+
// then
365+
@Language("json")
366+
val expectedJson = """
367+
{
368+
"setPermissionsCommand" : {
369+
"appId" : "com.twitter.android",
370+
"permissions" : {
371+
"all" : "deny",
372+
"notifications" : "unset"
373+
},
374+
"optional" : false
375+
}
376+
}
377+
""".trimIndent()
378+
assertThat(serializedCommandJson)
379+
.isEqualTo(expectedJson)
380+
assertThat(deserializedCommand)
381+
.isEqualTo(command)
382+
}
383+
353384
@Test
354385
fun `serialize ApplyConfigurationCommand`() {
355386
// given

maestro-orchestra/src/test/java/maestro/orchestra/yaml/YamlCommandReaderTest.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import maestro.orchestra.ScrollCommand
4242
import maestro.orchestra.ScrollUntilVisibleCommand
4343
import maestro.orchestra.SetAirplaneModeCommand
4444
import maestro.orchestra.SetLocationCommand
45+
import maestro.orchestra.SetPermissionsCommand
4546
import maestro.orchestra.StartRecordingCommand
4647
import maestro.orchestra.StopAppCommand
4748
import maestro.orchestra.StopRecordingCommand
@@ -760,6 +761,21 @@ internal class YamlCommandReaderTest {
760761
assertThat(tapCommand.originalDescription).isEqualTo("Double tap on \"Submit\" at 50%, 90%")
761762
}
762763

764+
@Test
765+
fun setPermissions(
766+
@YamlFile("030_setPermissions.yaml") commands: List<Command>
767+
) {
768+
assertThat(commands).containsExactly(
769+
ApplyConfigurationCommand(MaestroConfig(
770+
appId = "com.example.app",
771+
)),
772+
SetPermissionsCommand(
773+
appId = "com.example.app",
774+
permissions = mapOf("all" to "deny", "notifications" to "unset")
775+
),
776+
)
777+
}
778+
763779

764780
private fun commands(vararg commands: Command): List<MaestroCommand> =
765781
commands.map(::MaestroCommand).toList()
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
appId: com.example.app
2+
---
3+
- setPermissions:
4+
permissions:
5+
all: deny
6+
notifications: unset

0 commit comments

Comments
 (0)