Skip to content

Commit

Permalink
Prototype of custom selectors (#75)
Browse files Browse the repository at this point in the history
* start work

* start creating dummy selector test

* implement very simple, basic, dummy, rudimentary, stupid, selector algorithm

* create MaestroDriveHelper

* first working MVP

* delete first prototype (from Wednesday) of selector (with strings)

* use Symbols, lol

* update README and add example test

* example: move notifications to a separate screen to make testing easier

* fix selector test

* add MaestroFinder.text

* add support for RegExp and fix notification test

* add support for complex selector expression for top-level $

* delete obsolete test/widget_test.dart

* add some docs

* cleanups

* rename enum `Chainers` to `Chainer`

* remove global `$`, provide it in `maestroTest` instead

* rename `initialFinder` to `parentFinder`

* example: use `Key` instead of `ValueKey`

* add missing tap() from comment

* run example app tests in CI

* make MaestroFinder extends Finder

* flutter format --set-exit-if-changed

* rename instances of `MaestroTester` to `$`

* maestro_test: fix readme

* remove outdated docs

* remove `Chainer`, create `MaestroFinder.withDescendant()` method

* add `@isTest` annotation to `maestroTest()`

* flutter format

* remove `Chainers`

* `$`: add support for `IconData`

* `maestroTest()`: add optional args for `testWidgets()`

* clean up READMEs

* Bump versions

Co-authored-by: Mateusz Wojtczak <[email protected]>
  • Loading branch information
bartekpacia and mateuszwojtczak authored Jul 1, 2022
1 parent 382a317 commit c97911e
Show file tree
Hide file tree
Showing 24 changed files with 634 additions and 228 deletions.
17 changes: 8 additions & 9 deletions .github/workflows/maestro_test-prepare.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,25 +51,24 @@ jobs:
- name: flutter pub get
run: flutter pub get

- name: flutter format
run: flutter format --set-exit-if-changed .

- name: flutter analyze
run: flutter analyze --no-fatal-infos

# We don't have any tests, yet.
# - name: flutter test
# run: flutter test
- name: flutter pub get (example app)
working-directory: ./packages/maestro_test/example
run: flutter pub get

- name: flutter format (example app)
working-directory: ./packages/maestro_test/example
run: flutter format --set-exit-if-changed .

- name: flutter pub get (example app)
working-directory: ./packages/maestro_test/example
run: flutter pub get

- name: flutter analyze (example app)
working-directory: ./packages/maestro_test/example
run: flutter analyze --no-fatal-infos

- name: flutter format (example app)
- name: flutter test (example app)
working-directory: ./packages/maestro_test/example
run: flutter format --set-exit-if-changed .
run: flutter test
2 changes: 1 addition & 1 deletion AutomatorServer/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ android {
minSdkVersion 26
targetSdkVersion 31
versionCode 1
versionName "0.2.0"
versionName "0.3.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down
64 changes: 14 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Maestro

Simple, easy-to-learn, Flutter-native UI testing framework eliminating
limitations of `flutter_driver`.
limitations and improving experience of `flutter_driver` and `flutter_test`.

[![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]
[![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

Expand Down Expand Up @@ -34,9 +34,14 @@ $ maestro drive

## Package

`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.
`maestro_test` package builds on top of `flutter_driver` and `flutter_test`. It
makes it easy to:

- control the native device features, such as switching between apps or enabling
Wi-Fi

- write widget tests in a new, consice way using powerful [custom
selectors][custom_selectors]

### Installation

Expand All @@ -49,49 +54,7 @@ dev_dependencies:

### Usage

```dart
// 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();
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();
},
);
}
```
See README of [package:maestro_test][]

## Release process

Expand All @@ -111,3 +74,4 @@ git tag -a "maestro_cli-v0.0.4" -m "Release notes go here"
[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
[custom_selectors]: https://github.com/leancodepl/maestro/tree/feature/custom_selectors/packages/maestro_test#custom-selectors
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.3.0

- Add support for new features in [maestro_test
0.3.0](https://pub.dev/packages/maestro_test/changelog#030)

## 0.2.0

- Add support for new features in [maestro_test
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.2.0';
const version = '0.3.0';

const maestroPackage = 'maestro_test';
const maestroCliPackage = 'maestro_cli';
Expand Down
2 changes: 1 addition & 1 deletion packages/maestro_cli/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: maestro_cli
description: CLI for Maestro.
version: 0.2.0
version: 0.3.0
homepage: https://github.com/leancodepl/maestro

environment:
Expand Down
3 changes: 3 additions & 0 deletions packages/maestro_test/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 0.3.0
- Add selector engine

## 0.2.0

- Introduce `Selector` class, which can be passed into `Maestro.tap(selector)`.
Expand Down
69 changes: 43 additions & 26 deletions packages/maestro_test/README.md
Original file line number Diff line number Diff line change
@@ -1,64 +1,81 @@
# maestro_test

[![maestro_test on pub.dev][pub_badge]][pub_link]
[![code style][pub_badge_style]][pub_badge_link]
[![maestro_test on pub.dev][pub_badge]][pub_link] [![code
style][pub_badge_style]][pub_badge_link]

`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.

It also provides a new custom selector system to make writing Flutter widget
tests more concisce, and writing them faster & more fun.

### Installation

Add `maestro_test` as a dev dependency in `pubspec.yaml`:

```
dev_dependencies:
maestro_test: ^0.2.0
maestro_test: ^0.3.0
```

### Usage

```dart
// integration_test/app_test.dart
import 'package:example/app.dart';
// example/integration_test/example_test.dart
import 'package:example/main.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:maestro_test/maestro_test.dart';
void main() {
final maestro = Maestro.forTest();
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();
maestroTest(
'counter state is the same after going to Home and going back',
($) async {
await tester.pumpWidgetAndSettle(const MyApp());
await tester.tap(find.byType(FloatingActionButton));
await tester.pumpAndSettle();
expect(findCounterText().data, '1');
await $(FloatingActionButton).tap();
expect($(#counterText).text, '1');
await maestro.pressHome();
await maestro.pressDoubleRecentApps();
expect(findCounterText().data, '1');
await tester.tap(find.byType(FloatingActionButton));
await tester.pumpAndSettle();
expect(findCounterText().data, '2');
expect($(#counterText).text, '1');
await $(FloatingActionButton).tap();
expect($(#counterText).text, '2');
await maestro.openNotifications();
await maestro.openHalfNotificationShade();
await maestro.pressBack();
},
);
}
```

### Custom selectors

```dart
import 'package:example/main.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:maestro_test/maestro_test.dart';
void main() {
maestroTest(
'counter state is the same after going to Home and switching apps',
($) async {
await $.pumpWidgetAndSettle(const MyApp());
// Find widget with text 'Log in' which is a descendant of widget with key
// box1 which is a descendant of a Scaffold widget and tap on it.
await $(Scaffold).$(#box1).$('Log in').tap();
// Selects the first Scrollable which has a Text descendant
$(Scrollable).withDescendant(Text);
},
);
}
```

[pub_badge]: https://img.shields.io/pub/v/maestro_test.svg
Expand Down
4 changes: 4 additions & 0 deletions packages/maestro_test/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
include: package:leancode_lint/analysis_options_package.yaml

linter:
rules:
public_member_api_docs: false
12 changes: 3 additions & 9 deletions packages/maestro_test/example/integration_test/app_test.dart
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
import 'package:example/main.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();
final maestro = Maestro.forTest();

testWidgets(
maestroTest(
'counter state is the same after going to Home and switching apps',
(tester) async {
await tester.pumpWidget(const MyApp());
await tester.pumpAndSettle();

await maestro.isRunning();
($) async {
await $.pumpWidgetAndSettle(const MyApp());

await maestro.openFullNotificationShade();
await maestro.tap(const Selector(text: 'Bluetooth'));
Expand Down
28 changes: 28 additions & 0 deletions packages/maestro_test/example/integration_test/example_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import 'package:example/main.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:maestro_test/maestro_test.dart';

void main() {
final maestro = Maestro.forTest();

maestroTest(
'counter state is the same after going to Home and switching apps',
($) async {
await $.pumpWidgetAndSettle(const MyApp());

await $(FloatingActionButton).tap();
expect($(#counterText).text, '1');

await maestro.pressHome();
await maestro.pressDoubleRecentApps();

expect($(#counterText).text, '1');
await $(FloatingActionButton).tap();
expect($(#counterText).text, '2');

await maestro.openHalfNotificationShade();
await maestro.pressBack();
},
);
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import 'package:example/main.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();
final maestro = Maestro.forTest();

testWidgets(
'sends a notification',
(tester) async {
await tester.pumpWidget(const MyApp());
await tester.pumpAndSettle();
maestroTest(
'sends a notification and taps on it',
($) async {
await $.pumpWidgetAndSettle(const MyApp());

await tester.tap(find.textContaining('ID=1')); // appears on top
await tester.tap(find.textContaining('ID=2')); // also appears on top
await $('Open notifications screen').tap();

await $(RegExp('.*ID=1')).tap(); // appears on top
await $(RegExp('.*ID=2')).tap(); // also appears on top

(await maestro.getNotifications()).forEach(print);

Expand Down
Loading

0 comments on commit c97911e

Please sign in to comment.