Skip to content

Commit

Permalink
Feat/use_actual_host_and_port (#68)
Browse files Browse the repository at this point in the history
* example: add button to show notification

* downgrade Android Gradle Plugin to 7.0.4 (to use with IDEA)

* send much more data in NativeWidget

* add second counter Text

* start work on notifications

* respect custom host and port

* fix

* rename `Automator` to `Maestro`

* add <uses-permission android:name="android.permission.BLUETOOTH" />

* remove bluetooth from example, it's broken

* add support for tapping notifications by index

* update bootstrap template

* example app: update Gradle

* maestro_cli: respect verbose flag

* extract `env` and `dartDefine` to a common method

* protect against invalid --dart-define keys/values

* remove usages of 'Automator'; replace with 'Maestro'

* maestro_cli: set version to 0.1.5

* maestro_test: set version to 0.1.4
  • Loading branch information
bartekpacia authored Jun 19, 2022
1 parent 2f5a1c9 commit e961234
Show file tree
Hide file tree
Showing 38 changed files with 434 additions and 203 deletions.
6 changes: 3 additions & 3 deletions AutomatorServer/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ apply plugin: "kotlinx-serialization"


android {
compileSdkVersion 32
compileSdkVersion 31

namespace "pl.leancode.automatorserver"

defaultConfig {
applicationId "pl.leancode.automatorserver"
minSdkVersion 26
targetSdkVersion 32
targetSdkVersion 31
versionCode 1
versionName "0.1.4"
versionName "0.1.5"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import org.junit.runner.RunWith
class AutomatorServer {
@Test
fun startServer() {
Logger.i("Starting server")
Logger.i("Starting server...")

val serverInstrumentation = ServerInstrumentation.instance
try {
Expand All @@ -20,5 +20,7 @@ class AutomatorServer {
e.printStackTrace()
Logger.e("Exception thrown: ", e)
}

Logger.i("Server stopped")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import kotlin.concurrent.schedule
@Serializable
data class TapCommand(val index: Int)

@Serializable
data class TapOnNotificationCommand(val index: Int)

@Serializable
data class EnterTextCommand(val index: Int, val text: String)

Expand All @@ -34,6 +37,7 @@ const val ButtonClass = "android.widget.Button"

@Serializable
data class WidgetsQuery(
val fullyQualifiedName: String? = null,
val className: String? = null,
val enabled: Boolean? = null,
val focused: Boolean? = null,
Expand All @@ -43,7 +47,8 @@ data class WidgetsQuery(
) {
fun isEmpty(): Boolean {
return (
className == null &&
fullyQualifiedName == null &&
className == null &&
clazz() == null &&
enabled == null &&
focused == null &&
Expand Down Expand Up @@ -101,6 +106,11 @@ class ServerInstrumentation {
UIAutomatorInstrumentation.instance.openNotifications()
Response(OK)
},
"tapOnNotification" bind POST to {
val body = Json.decodeFromString<TapOnNotificationCommand>(it.bodyString())
UIAutomatorInstrumentation.instance.tapOnNotification(body.index)
Response(OK)
},
"tap" bind POST to {
val body = Json.decodeFromString<TapCommand>(it.bodyString())
UIAutomatorInstrumentation.instance.tap(body.index)
Expand Down Expand Up @@ -150,9 +160,13 @@ class ServerInstrumentation {
}
)

val port = UIAutomatorInstrumentation.instance.port ?: throw Exception("Could not start server: port is null")

Logger.i("Starting server on port $port")

server = router.withFilter(catcher)
.withFilter(printer)
.asServer(Netty(8081))
.asServer(Netty(port))
.start()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package pl.leancode.automatorserver

import android.os.Bundle
import android.os.SystemClock
import android.widget.Button
import android.widget.EditText
Expand All @@ -9,6 +10,8 @@ import androidx.test.uiautomator.Configurator
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
import junit.framework.TestCase.assertTrue
import kotlinx.serialization.Serializable

@Serializable
Expand All @@ -18,6 +21,10 @@ data class NativeWidget(
val contentDescription: String?,
val focused: Boolean?,
val enabled: Boolean?,
val childCount: Int?,
val resourceName: String?,
val applicationPackage: String?,
val children: List<NativeWidget>?,
) {
companion object {
fun fromUiObject(obj: UiObject2): NativeWidget {
Expand All @@ -27,6 +34,10 @@ data class NativeWidget(
contentDescription = obj.contentDescription,
focused = obj.isFocused,
enabled = obj.isEnabled,
childCount = obj.childCount,
resourceName = obj.resourceName,
applicationPackage = obj.applicationPackage,
children = obj.children?.map { fromUiObject(it) },
)
}
}
Expand All @@ -46,9 +57,12 @@ class UIAutomatorInstrumentation {
Logger.i("\tuiAutomationFlags: ${configurator.uiAutomationFlags}")
}

private fun getUiDevice(): UiDevice {
return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
}
private fun getUiDevice(): UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())

private fun getArguments(): Bundle = InstrumentationRegistry.getArguments()

val port: Int?
get() = getArguments().getString("MAESTRO_PORT")?.toInt()

private fun executeShellCommand(cmd: String) {
val device = getUiDevice()
Expand Down Expand Up @@ -116,7 +130,12 @@ class UIAutomatorInstrumentation {
return arrayListOf()
}

var selector = By.clazz(query.clazz())
var selector = if (query.fullyQualifiedName != null) {
Logger.i("Selector for fully qualified name ${query.fullyQualifiedName}")
By.clazz(query.fullyQualifiedName)
} else {
By.clazz(query.clazz())
}

selector = selector.apply {
query.enabled?.let {
Expand All @@ -140,7 +159,9 @@ class UIAutomatorInstrumentation {
}
}

return device.findObjects(selector).map { NativeWidget.fromUiObject(it) }
return device.findObjects(selector).map {
NativeWidget.fromUiObject(it)
}
}

fun tap(index: Int) {
Expand Down Expand Up @@ -171,6 +192,22 @@ class UIAutomatorInstrumentation {
delay()
}

fun tapOnNotification(index: Int) {
val device = getUiDevice()
device.wait(Until.hasObject(By.pkg("com.android.systemui")), 2000)

val notificationStackScroller = UiSelector()
.packageName("com.android.systemui")
.resourceId("com.android.systemui:id/notification_stack_scroller")
val notificationStackScrollerUiObject = device.findObject(notificationStackScroller)
assertTrue(notificationStackScrollerUiObject.exists())

val notiSelectorUiObject = notificationStackScrollerUiObject.getChild(UiSelector().index(index))
assertTrue(notiSelectorUiObject.exists())

notiSelectorUiObject.click()
}

companion object {
val instance = UIAutomatorInstrumentation()
}
Expand Down
16 changes: 8 additions & 8 deletions AutomatorServer/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.INTERNET"/>

<application
android:allowBackup="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AutomatorServer" />
android:allowBackup="false"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="false"
android:theme="@style/Theme.AutomatorServer"/>

</manifest>
</manifest>
2 changes: 1 addition & 1 deletion AutomatorServer/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ buildscript {
}

dependencies {
classpath "com.android.tools.build:gradle:7.2.1"
classpath "com.android.tools.build:gradle:7.0.4"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"

Expand Down
8 changes: 8 additions & 0 deletions packages/adb/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 0.0.4

- Fix wrong order of arguments for `instrument()`

## 0.0.3

- Make it possible to pass arguments to `instrument()` method

## 0.0.2

- Fix bugs that arose during migration.
Expand Down
6 changes: 6 additions & 0 deletions packages/adb/lib/src/adb.dart
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ Future<void> instrument({
String? device,
void Function(String)? onStdout,
void Function(String)? onStderr,
Map<String, String> arguments = const {},
}) async {
final process = await Process.start(
'adb',
Expand All @@ -103,6 +104,11 @@ Future<void> instrument({
'am',
'instrument',
'-w',
for (final arg in arguments.entries) ...[
'-e',
arg.key,
arg.value,
],
'$packageName/$intentClass',
],
runInShell: true,
Expand Down
2 changes: 1 addition & 1 deletion packages/adb/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: adb
description: Simple wrapper for adb.
version: 0.0.2
version: 0.0.4
homepage: https://github.com/leancodepl/maestro

environment:
Expand Down
5 changes: 5 additions & 0 deletions packages/maestro_cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.1.5

- Allow for running on many devices simultaneously.
- A usual portion of smaller improvements and bug fixes.

## 0.1.4

- Be more noisy when an error occurs.
Expand Down
13 changes: 7 additions & 6 deletions packages/maestro_cli/lib/src/command_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ Future<int> maestroCommandRunner(List<String> args) async {
}
}

bool debug = false;
bool debugFlag = false;
bool verboseFlag = false;

class MaestroCommandRunner extends CommandRunner<int> {
MaestroCommandRunner()
Expand Down Expand Up @@ -56,10 +57,10 @@ class MaestroCommandRunner extends CommandRunner<int> {
Future<int?> run(Iterable<String> args) async {
await setUpLogger(); // argParser.parse() can fail, so we setup logger early
final results = argParser.parse(args);
final verboseFlag = results['verbose'] as bool;
verboseFlag = results['verbose'] as bool;
final helpFlag = results['help'] as bool;
final versionFlag = results['version'] as bool;
debug = results['debug'] as bool;
debugFlag = results['debug'] as bool;

await setUpLogger(verbose: verboseFlag);

Expand All @@ -72,13 +73,13 @@ class MaestroCommandRunner extends CommandRunner<int> {
return super.run(args);
}

if (debug) {
log.info('Using debug artifacts');
if (debugFlag) {
log.info('Debug mode enabled. Non-versioned artifacts will be used.');
}

if (_commandRequiresArtifacts(results.arguments)) {
try {
await _ensureArtifactsArePresent(debug);
await _ensureArtifactsArePresent(debugFlag);
} catch (err, st) {
log.severe(null, err, st);
return 1;
Expand Down
2 changes: 1 addition & 1 deletion packages/maestro_cli/lib/src/common/constants.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// Version of Maestro CLI. Must be kept in sync with pubspec.yaml.
const version = '0.1.4';
const version = '0.1.5';

const maestroPackage = 'maestro_test';
const maestroCliPackage = 'maestro_cli';
Expand Down
4 changes: 4 additions & 0 deletions packages/maestro_cli/lib/src/common/logging.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ StreamSubscription<void>? _sub;
/// - [Level.INFO], printed in white
/// - [Level.FINE], printed in grey and only when [verbose] is true
Future<void> setUpLogger({bool verbose = false}) async {
if (verbose) {
print('Verbose mode enabled');
}

Logger.root.level = Level.ALL;

await _sub?.cancel();
Expand Down
Loading

0 comments on commit e961234

Please sign in to comment.