diff --git a/AutomatorServer/app/build.gradle b/AutomatorServer/app/build.gradle index f9867f0b2..46bc02abf 100644 --- a/AutomatorServer/app/build.gradle +++ b/AutomatorServer/app/build.gradle @@ -13,7 +13,7 @@ android { minSdkVersion 26 targetSdkVersion 32 versionCode 1 - versionName "0.0.8" + versionName "0.0.9" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/README.md b/README.md index 007f31e05..a7478b901 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ Simple, easy-to-learn, Flutter-native UI testing framework eliminating limitations of `flutter_driver`. [![maestro_test on pub.dev][pub_badge_test]][pub_link_test] - [![maestro_cli on pub.dev][pub_badge_cli]][pub_link_cli] +[![code style][pub_badge_style]][pub_badge_link] ## CLI @@ -34,7 +34,7 @@ $ maestro drive ## Package -The `maestro_test` package builds on top of `flutter_driver` to make it easy to +`maestro_test` package builds on top of `flutter_driver` to make it easy to control the native device. It does this by using Android's [UIAutomator][ui_automator] library. @@ -103,9 +103,11 @@ git tag -a "maestro_cli-v0.0.4" -m "Release notes go here" 2. Push it! GitHub Actions will take care of the rest. -[pub_badge_test]: https://img.shields.io/pub/v/maestro_test.svg +[pub_badge_test]: https://img.shields.io/pub/v/maestro_test?label=maestro_test [pub_link_test]: https://pub.dartlang.org/packages/maestro_test -[pub_badge_cli]: https://img.shields.io/pub/v/maestro_cli.svg +[pub_badge_cli]: https://img.shields.io/pub/v/maestro_cli?label=maestro_cli +[pub_badge_style]: https://img.shields.io/badge/style-leancode__lint-black +[pub_badge_link]: https://pub.dartlang.org/packages/lean_code_lint [pub_link_cli]: https://pub.dartlang.org/packages/maestro_cli [ui_automator]: https://developer.android.com/training/testing/other-components/ui-automator [annotated_tag]: https://git-scm.com/book/en/v2/Git-Basics-Tagging#_annotated_tags diff --git a/packages/maestro_cli/CHANGELOG.md b/packages/maestro_cli/CHANGELOG.md index 081e45bcb..d5ac1275c 100644 --- a/packages/maestro_cli/CHANGELOG.md +++ b/packages/maestro_cli/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.0.9 + +- Add `--device` option for `maestro drive`, which allows you to specify the + device to use. Devices can be obtained using `adb devices`. + ## 0.0.8 - Fix `maestro drive` on Windows crashing with ProcessException. diff --git a/packages/maestro_cli/README.md b/packages/maestro_cli/README.md index 7e3cc6294..7f6e4bff3 100644 --- a/packages/maestro_cli/README.md +++ b/packages/maestro_cli/README.md @@ -1,8 +1,9 @@ # maestro_cli -Command-line tool for [maestro_test][pub_link_test]. - [![maestro_cli on pub.dev][pub_badge]][pub_link] +[![code style][pub_badge_style]][pub_badge_link] + +Command-line tool to make working with [maestro_test][pub_link_test] easier. ## Installation @@ -19,9 +20,11 @@ $ dart pub global activate maestro_cli 3. Go to `packages/maestro_cli`. 4. Run `dart pub global activate --source path .` -Now you can should be able to run `maestro` in your terminal. If you can't and -the error is something along the lines of "command not found", make sure that -you've added appropriate directories to PATH: +### Troubleshooting + +If you can't run `maestro` from the terminal and the error is something along +the lines of "command not found", make sure that you've added appropriate +directories to PATH: - on Unix-like systems, add `$HOME/.pub-cache/bin` - on Windows, add `%USERPROFILE%\AppData\Local\Pub\Cache\bin` @@ -57,3 +60,7 @@ Run `maestro bootstrap` to automatically do 1, 2, 3, 4, and most of 5. [pub_badge]: https://img.shields.io/pub/v/maestro_cli.svg [pub_link]: https://pub.dartlang.org/packages/maestro_cli [pub_link_test]: https://pub.dartlang.org/packages/maestro_test +[pub_badge]: https://img.shields.io/pub/v/maestro_cli.svg +[pub_link]: https://pub.dartlang.org/packages/maestro_cli +[pub_badge_style]: https://img.shields.io/badge/style-leancode__lint-black +[pub_badge_link]: https://pub.dartlang.org/packages/lean_code_lint diff --git a/packages/maestro_cli/lib/src/commands/drive_command.dart b/packages/maestro_cli/lib/src/commands/drive_command.dart index cb58bdb8d..a988be8c2 100644 --- a/packages/maestro_cli/lib/src/commands/drive_command.dart +++ b/packages/maestro_cli/lib/src/commands/drive_command.dart @@ -27,6 +27,10 @@ class DriveCommand extends Command { 'driver', abbr: 'd', help: 'Dart file which starts flutter_driver.', + ) + ..addOption( + 'device', + help: 'Serial number of ADB device to use.', ); } @@ -65,6 +69,8 @@ class DriveCommand extends Command { throw const FormatException('`driver` argument is not a string'); } + final device = argResults?['device'] as String?; + final options = MaestroDriveOptions( host: host, port: int.parse(portStr), @@ -72,10 +78,14 @@ class DriveCommand extends Command { driver: driver, ); - await adb.installApps(); - await adb.forwardPorts(options.port); - await adb.runServer(); - await flutter_driver.runTestsWithOutput(options.driver, options.target); + await adb.installApps(device: device); + await adb.forwardPorts(options.port, device: device); + await adb.runServer(device: device); + await flutter_driver.runTestsWithOutput( + options.driver, + options.target, + device: device, + ); return 0; } diff --git a/packages/maestro_cli/lib/src/common/constants.dart b/packages/maestro_cli/lib/src/common/constants.dart index 37444d099..3e05483f8 100644 --- a/packages/maestro_cli/lib/src/common/constants.dart +++ b/packages/maestro_cli/lib/src/common/constants.dart @@ -1,5 +1,5 @@ /// Version of Maestro CLI. Must be kept in sync with pubspec.yaml. -const version = '0.0.8'; +const version = '0.0.9'; const maestroPackage = 'maestro_test'; const maestroCliPackage = 'maestro_cli'; diff --git a/packages/maestro_cli/lib/src/external/adb.dart b/packages/maestro_cli/lib/src/external/adb.dart index c5727108c..517b270d2 100644 --- a/packages/maestro_cli/lib/src/external/adb.dart +++ b/packages/maestro_cli/lib/src/external/adb.dart @@ -4,10 +4,10 @@ import 'dart:io'; import 'package:maestro_cli/src/common/common.dart'; import 'package:path/path.dart' as path; -Future installApps() async { +Future installApps({String? device}) async { final progress1 = log.progress('Installing server'); try { - await _installApk(serverArtifactFile); + await _installApk(serverArtifactFile, device: device); } catch (err) { progress1.fail('Failed to install server'); rethrow; @@ -16,7 +16,7 @@ Future installApps() async { final progress2 = log.progress('Installing instrumentation'); try { - await _installApk(instrumentationArtifactFile); + await _installApk(instrumentationArtifactFile, device: device); } catch (err) { progress2.fail('Failed to install instrumentation'); rethrow; @@ -25,12 +25,16 @@ Future installApps() async { progress2.complete('Installed instrumentation'); } -Future forwardPorts(int port) async { +Future forwardPorts(int port, {String? device}) async { final progress = log.progress('Forwarding ports'); final result = await Process.run( 'adb', [ + if (device != null) ...[ + '-s', + device, + ], 'forward', 'tcp:$port', 'tcp:$port', @@ -46,7 +50,7 @@ Future forwardPorts(int port) async { progress.complete('Forwarded ports'); } -Future runServer() async { +Future runServer({String? device}) async { final progress = log.progress('Starting instrumentation server'); Process process; @@ -54,6 +58,10 @@ Future runServer() async { process = await Process.start( 'adb', [ + if (device != null) ...[ + '-s', + device, + ], 'shell', 'am', 'instrument', @@ -82,10 +90,14 @@ Future runServer() async { progress.complete('Started instrumentation server'); } -Future _installApk(String name) async { +Future _installApk(String name, {String? device}) async { final result = await Process.run( 'adb', [ + if (device != null) ...[ + '-s', + device, + ], 'install', path.join(artifactPath, name), ], diff --git a/packages/maestro_cli/lib/src/external/flutter_driver.dart b/packages/maestro_cli/lib/src/external/flutter_driver.dart index 7324116af..7e2c52ae7 100644 --- a/packages/maestro_cli/lib/src/external/flutter_driver.dart +++ b/packages/maestro_cli/lib/src/external/flutter_driver.dart @@ -4,7 +4,7 @@ import 'package:maestro_cli/src/common/common.dart'; /// Runs flutter driver with the given [driver] and [target] and waits until the /// drive is done. -Future runTests(String driver, String target) async { +Future runTests(String driver, String target, {String? device}) async { log.info('Running tests...'); final res = await Process.run( @@ -15,6 +15,10 @@ Future runTests(String driver, String target) async { driver, '--target', target, + if (device != null) ...[ + '--device-id', + device, + ], ], runInShell: true, ); @@ -28,7 +32,11 @@ Future runTests(String driver, String target) async { /// drive is done. /// /// Prints standard output of "flutter drive". -Future runTestsWithOutput(String driver, String target) async { +Future runTestsWithOutput( + String driver, + String target, { + String? device, +}) async { log.info('Running tests with output...'); final res = await Process.start( @@ -39,11 +47,15 @@ Future runTestsWithOutput(String driver, String target) async { driver, '--target', target, + if (device != null) ...[ + '--device-id', + device, + ], ], runInShell: true, ); - final sub = res.stdout.listen((msg) { + final stdOutSub = res.stdout.listen((msg) { final text = 'driver: ${systemEncoding.decode(msg)}'; if (text.contains('I/flutter')) { log.info(text); @@ -52,6 +64,19 @@ Future runTestsWithOutput(String driver, String target) async { } }); - await res.exitCode; - await sub.cancel(); + final stdErrSub = res.stderr.listen((msg) { + final text = 'driver: ${systemEncoding.decode(msg)}'; + log.severe(text); + }); + + final exitCode = await res.exitCode; + await stdOutSub.cancel(); + await stdErrSub.cancel(); + + final msg = 'flutter_driver exited with code $exitCode'; + if (exitCode == 0) { + log.info(msg); + } else { + log.severe(msg); + } } diff --git a/packages/maestro_cli/pubspec.yaml b/packages/maestro_cli/pubspec.yaml index c99cacf9e..eadd695c5 100644 --- a/packages/maestro_cli/pubspec.yaml +++ b/packages/maestro_cli/pubspec.yaml @@ -1,6 +1,6 @@ name: maestro_cli description: CLI for Maestro. -version: 0.0.8 +version: 0.0.9 homepage: https://github.com/leancodepl/maestro environment: diff --git a/packages/maestro_test/README.md b/packages/maestro_test/README.md index ded904b24..018fe66e4 100644 --- a/packages/maestro_test/README.md +++ b/packages/maestro_test/README.md @@ -1,28 +1,68 @@ # maestro_test -TODO: Put a short description of the package here that helps potential users -know whether this package might be useful for them. +[![maestro_test on pub.dev][pub_badge]][pub_link] +[![code style][pub_badge_style]][pub_badge_link] -## Features +`maestro_test` package builds on top of `flutter_driver` to make it easy to +control the native device from Dart. It does this by using Android's +[UIAutomator][ui_automator] library. -TODO: List what your package can do. Maybe include images, gifs, or videos. +### Installation -## Getting started +Add `maestro_test` as a dev dependency in `pubspec.yaml`: -TODO: List prerequisites and provide or point to information on how to -start using the package. - -## Usage +``` +dev_dependencies: + maestro_test: ^0.0.3 +``` -TODO: Include short and useful examples for package users. Add longer examples -to `/example` folder. +### Usage ```dart -const like = 'sample'; -``` +// integration_test/app_test.dart +import 'package:example/app.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:maestro_test/maestro_test.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + Automator.init(verbose: true); + final automator = Automator.instance; + + testWidgets( + "counter state is the same after going to Home and switching apps", + (WidgetTester tester) async { + Text findCounterText() { + return tester + .firstElement(find.byKey(const ValueKey('counterText'))) + .widget as Text; + } + + await tester.pumpWidget(const MyApp()); + await tester.pumpAndSettle(); -## Additional information + await tester.tap(find.byType(FloatingActionButton)); + await tester.pumpAndSettle(); + expect(findCounterText().data, '1'); + + await automator.pressHome(); + + await automator.pressDoubleRecentApps(); + + expect(findCounterText().data, '1'); + await tester.tap(find.byType(FloatingActionButton)); + await tester.pumpAndSettle(); + expect(findCounterText().data, '2'); + + await automator.openNotifications(); + }, + ); +} + +``` -TODO: Tell users more about the package: where to find more information, how to -contribute to the package, how to file issues, what response they can expect -from the package authors, and more. +[pub_badge]: https://img.shields.io/pub/v/maestro_test.svg +[pub_link]: https://pub.dartlang.org/packages/maestro_test +[pub_badge_style]: https://img.shields.io/badge/style-leancode__lint-black +[pub_badge_link]: https://pub.dartlang.org/packages/lean_code_lint