From 1b060e39d66a24a605ea0a90e1b9bb3ed0c0bfd4 Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Sat, 2 Jul 2022 20:42:20 +0200 Subject: [PATCH 01/12] make it possible to pass another `MaestroFinder` to `MaestroFinder.withDescendant` --- packages/maestro_test/lib/src/custom_selectors.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/maestro_test/lib/src/custom_selectors.dart b/packages/maestro_test/lib/src/custom_selectors.dart index 818ab0975..5cb032093 100644 --- a/packages/maestro_test/lib/src/custom_selectors.dart +++ b/packages/maestro_test/lib/src/custom_selectors.dart @@ -137,6 +137,10 @@ class MaestroTester { } Finder _createFinder(dynamic expression) { + if (expression is MaestroFinder) { + return expression.finder; + } + if (expression is Type) { return find.byType(expression); } @@ -158,7 +162,7 @@ Finder _createFinder(dynamic expression) { } throw ArgumentError( - 'expression must be of type `Type`, `Symbol`, `String`, `Pattern`, or `IconData`', + 'expression must be of type `MaestroFinder`, `Type`, `Symbol`, `String`, `Pattern`, or `IconData`', ); } From c2e5a750e446146e1c0195c3decc71ca2811f6c8 Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Sat, 2 Jul 2022 22:16:17 +0200 Subject: [PATCH 02/12] update readme --- packages/maestro_test/README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/maestro_test/README.md b/packages/maestro_test/README.md index aef69d9a1..4809731e7 100644 --- a/packages/maestro_test/README.md +++ b/packages/maestro_test/README.md @@ -19,7 +19,7 @@ dev_dependencies: maestro_test: ^0.3.0 ``` -### Usage +### Using features of the underlying native platform ```dart // example/integration_test/example_test.dart @@ -63,16 +63,22 @@ import 'package:maestro_test/maestro_test.dart'; void main() { maestroTest( - 'counter state is the same after going to Home and switching apps', + 'logs in successfully', ($) async { await $.pumpWidgetAndSettle(const MyApp()); + await $(#emailInput).enterText('user@leancode.co'); + await $(#passwordInput).enterText('ny4ncat'); + // 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); + + // Selects the first Scrollable which has a Button descendant which has a Text descendant + $(Scrollable).withDescendant($(Text).withDescendant(Text)); }, ); } From 3c9a112d8152fe45797fd75e20a6f2f9111a1c40 Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Mon, 4 Jul 2022 08:18:14 +0200 Subject: [PATCH 03/12] MaestroTester: forward WidgetTester.pump() and WidgetTester.pumpAndSettle() --- .../lib/src/custom_selectors.dart | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/packages/maestro_test/lib/src/custom_selectors.dart b/packages/maestro_test/lib/src/custom_selectors.dart index 5cb032093..5acac5c9f 100644 --- a/packages/maestro_test/lib/src/custom_selectors.dart +++ b/packages/maestro_test/lib/src/custom_selectors.dart @@ -111,6 +111,33 @@ class MaestroTester { final WidgetTester tester; + MaestroFinder call(dynamic matching) { + return _$( + matching: matching, + tester: tester, + parentFinder: null, + ); + } + + /// See [WidgetTester.pumpWidget]. + Future pumpWidget( + Widget widget, [ + Duration? duration, + EnginePhase phase = EnginePhase.sendSemanticsUpdate, + ]) async { + await tester.pumpWidget(widget, duration, phase); + } + + /// See [WidgetTester.pumpAndSettle]. + Future pumpAndSettle( + [Duration duration = const Duration(milliseconds: 100), + EnginePhase phase = EnginePhase.sendSemanticsUpdate, + Duration timeout = const Duration(minutes: 10)]) async { + await tester.pumpAndSettle(); + } + + /// A convenience method combining [WidgetTester.pumpWidget] and + /// [WidgetTester.pumpAndSettle]. Future pumpWidgetAndSettle( Widget widget, [ Duration? pumpWidgetDuration, @@ -126,14 +153,6 @@ class MaestroTester { pumpAndSettleTimeout, ); } - - MaestroFinder call(dynamic matching) { - return _$( - matching: matching, - tester: tester, - parentFinder: null, - ); - } } Finder _createFinder(dynamic expression) { From 51fc2c1794986fcf474f6be4bf4cb8523ab19707 Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Mon, 4 Jul 2022 08:58:17 +0200 Subject: [PATCH 04/12] add tests --- .../lib/src/custom_selectors.dart | 10 +-- packages/maestro_test/lib/src/extensions.dart | 1 + .../test/custom_selectors_test.dart | 73 +++++++++++++++++++ 3 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 packages/maestro_test/test/custom_selectors_test.dart diff --git a/packages/maestro_test/lib/src/custom_selectors.dart b/packages/maestro_test/lib/src/custom_selectors.dart index 5acac5c9f..3e27d0a58 100644 --- a/packages/maestro_test/lib/src/custom_selectors.dart +++ b/packages/maestro_test/lib/src/custom_selectors.dart @@ -156,10 +156,6 @@ class MaestroTester { } Finder _createFinder(dynamic expression) { - if (expression is MaestroFinder) { - return expression.finder; - } - if (expression is Type) { return find.byType(expression); } @@ -180,8 +176,12 @@ Finder _createFinder(dynamic expression) { return find.byIcon(expression); } + if (expression is MaestroFinder) { + return expression.finder; + } + throw ArgumentError( - 'expression must be of type `MaestroFinder`, `Type`, `Symbol`, `String`, `Pattern`, or `IconData`', + 'expression must be of type `Type`, `Symbol`, `String`, `Pattern`, `IconData`, or `MaestroFinder`', ); } diff --git a/packages/maestro_test/lib/src/extensions.dart b/packages/maestro_test/lib/src/extensions.dart index d52925a6b..62694bb4b 100644 --- a/packages/maestro_test/lib/src/extensions.dart +++ b/packages/maestro_test/lib/src/extensions.dart @@ -8,6 +8,7 @@ extension IsOk on http.Response { extension SymbolX on Symbol { String get name { + // Kinda hacky but works well. Might require adjustements on the web though. final symbol = toString(); return symbol.substring(8, symbol.length - 2); } diff --git a/packages/maestro_test/test/custom_selectors_test.dart b/packages/maestro_test/test/custom_selectors_test.dart new file mode 100644 index 000000000..efa0813f0 --- /dev/null +++ b/packages/maestro_test/test/custom_selectors_test.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:maestro_test/src/custom_selectors.dart'; + +void main() { + group('finds widget by', () { + maestroTest('type', ($) async { + await $.pumpWidgetAndSettle( + const MaterialApp( + home: Text('Hello'), + ), + ); + expect($(Text), findsOneWidget); + }); + + maestroTest('key', ($) async { + await $.pumpWidgetAndSettle( + const MaterialApp( + home: Text('Hello', key: ValueKey('hello')), + ), + ); + expect($(#hello), findsOneWidget); + }); + + maestroTest('text', ($) async { + await $.pumpWidgetAndSettle( + const MaterialApp(home: Text('Hello')), + ); + expect($('Hello'), findsOneWidget); + }); + + maestroTest('text it contains', ($) async { + await $.pumpWidgetAndSettle( + const MaterialApp(home: Text('Hello')), + ); + expect($(RegExp('Hello')), findsOneWidget); + expect($(RegExp('Hell.*')), findsOneWidget); + expect($(RegExp('.*ello')), findsOneWidget); + expect($(RegExp('.*ell.*')), findsOneWidget); + }); + + maestroTest('icon', ($) async { + await $.pumpWidgetAndSettle( + const MaterialApp( + home: Icon(Icons.code), + ), + ); + + expect($(Icons.code), findsOneWidget); + }); + + maestroTest( + 'text using a nested MaestroFinder', + ($) async { + await $.pumpWidgetAndSettle( + const MaterialApp(home: Text('Hello')), + ); + expect($($('Hello')), findsOneWidget); + }, + ); + + maestroTest( + 'text using many nested MaestroFinders', + ($) async { + await $.pumpWidgetAndSettle( + const MaterialApp(home: Text('Hello')), + ); + + expect($($($($('Hello')))), findsOneWidget); + }, + ); + }); +} From 6f7a6232226940049fec0fa4facdbd343da38726 Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Mon, 4 Jul 2022 09:20:22 +0200 Subject: [PATCH 05/12] hit bug with parent finders not working --- .../test/custom_selectors_test.dart | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/maestro_test/test/custom_selectors_test.dart b/packages/maestro_test/test/custom_selectors_test.dart index efa0813f0..5f5758c6c 100644 --- a/packages/maestro_test/test/custom_selectors_test.dart +++ b/packages/maestro_test/test/custom_selectors_test.dart @@ -16,7 +16,7 @@ void main() { maestroTest('key', ($) async { await $.pumpWidgetAndSettle( const MaterialApp( - home: Text('Hello', key: ValueKey('hello')), + home: Text('Hello', key: Key('hello')), ), ); expect($(#hello), findsOneWidget); @@ -70,4 +70,30 @@ void main() { }, ); }); + + group('finds widget by parent', () { + maestroTest('simple parent', ($) async { + await $.pumpWidgetAndSettle( + const MaterialApp( + key: Key('app'), + home: Scaffold(body: Text('Hello', key: Key('hello'))), + ), + ); + + expect($(MaterialApp), findsOneWidget); + expect($(#xd).$(#hello), findsOneWidget); + + expect( + find.descendant( + of: find.byType(MaterialApp), + matching: find.byType(Text), + ), + findsOneWidget, + ); + + //expect($(MaterialApp).$(Text), findsOneWidget); + //expect($(MaterialApp).$('Text'), findsOneWidget); + //expect($(MaterialApp).$(#hello), findsOneWidget); + }); + }); } From 331a93d42ab8991a5b1c30cafaf6fad7b2e867c0 Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Mon, 4 Jul 2022 10:13:27 +0200 Subject: [PATCH 06/12] add .idea to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e43b0f988..4befed30a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .DS_Store +.idea From f838711a4983029895a62cebe51a68130f4ad07f Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Mon, 4 Jul 2022 10:13:58 +0200 Subject: [PATCH 07/12] fix bug with parent finders not working --- .../lib/src/custom_selectors.dart | 18 +++++++-- .../test/custom_selectors_test.dart | 39 +++++++++++-------- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/packages/maestro_test/lib/src/custom_selectors.dart b/packages/maestro_test/lib/src/custom_selectors.dart index 3e27d0a58..c37a59978 100644 --- a/packages/maestro_test/lib/src/custom_selectors.dart +++ b/packages/maestro_test/lib/src/custom_selectors.dart @@ -41,8 +41,10 @@ void maestroTest( ); } -class MaestroFinder extends Finder { - MaestroFinder({required this.finder, required this.tester}); +class MaestroFinder extends MatchFinder { + MaestroFinder({required this.finder, required this.tester}) { + print('Created MaestroFinder with finder: $this'); + } final Finder finder; final WidgetTester tester; @@ -83,7 +85,7 @@ class MaestroFinder extends Finder { return _$( matching: matching, tester: tester, - parentFinder: finder, + parentFinder: this, ); } @@ -97,6 +99,11 @@ class MaestroFinder extends Finder { ); } + @override + Iterable evaluate() { + return finder.evaluate(); + } + @override Iterable apply(Iterable candidates) { return finder.apply(candidates); @@ -104,6 +111,11 @@ class MaestroFinder extends Finder { @override String get description => finder.description; + + @override + bool matches(Element candidate) { + return (finder as MatchFinder).matches(candidate); + } } class MaestroTester { diff --git a/packages/maestro_test/test/custom_selectors_test.dart b/packages/maestro_test/test/custom_selectors_test.dart index 5f5758c6c..51155f818 100644 --- a/packages/maestro_test/test/custom_selectors_test.dart +++ b/packages/maestro_test/test/custom_selectors_test.dart @@ -74,26 +74,33 @@ void main() { group('finds widget by parent', () { maestroTest('simple parent', ($) async { await $.pumpWidgetAndSettle( - const MaterialApp( - key: Key('app'), - home: Scaffold(body: Text('Hello', key: Key('hello'))), + MaterialApp( + key: const Key('app'), + home: Column( + children: [ + Container( + key: const Key('container'), + child: const Text('Hello 1', key: Key('helloText')), + ), + const SizedBox( + key: Key('sizedbox'), + child: Text('Hello 2', key: Key('helloText')), + ), + ], + ), ), ); - expect($(MaterialApp), findsOneWidget); - expect($(#xd).$(#hello), findsOneWidget); - - expect( - find.descendant( - of: find.byType(MaterialApp), - matching: find.byType(Text), - ), - findsOneWidget, - ); + expect($(MaterialApp).$(Text), findsNWidgets(2)); + expect($(MaterialApp).$(#helloText), findsNWidgets(2)); + expect($(Container).$(Text), findsOneWidget); + expect($(SizedBox).$(Text), findsOneWidget); + expect($(Container).$('Hello 2'), findsNothing); + expect($(SizedBox).$('Hello 1'), findsNothing); - //expect($(MaterialApp).$(Text), findsOneWidget); - //expect($(MaterialApp).$('Text'), findsOneWidget); - //expect($(MaterialApp).$(#hello), findsOneWidget); + expect($(MaterialApp).$(Container).$(Text), findsOneWidget); + expect($(MaterialApp).$(Container).$('Hello 1'), findsOneWidget); + expect($(MaterialApp).$(SizedBox).$('Hello 2'), findsOneWidget); }); }); } From ba8d0f644fb163994de4ff8f510bf933ab56d726 Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Mon, 4 Jul 2022 10:17:44 +0200 Subject: [PATCH 08/12] add running tests of `package:maestro_test` to `maestro_test-prepare` workflow --- .github/workflows/maestro_test-prepare.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/maestro_test-prepare.yaml b/.github/workflows/maestro_test-prepare.yaml index 5cd09987b..567d083af 100644 --- a/.github/workflows/maestro_test-prepare.yaml +++ b/.github/workflows/maestro_test-prepare.yaml @@ -57,6 +57,9 @@ jobs: - name: flutter analyze run: flutter analyze --no-fatal-infos + - name: flutter test + run: flutter test + - name: flutter pub get (example app) working-directory: ./packages/maestro_test/example run: flutter pub get From 70c6265a38a2c7d6bae7dab2d4398cef6bcf7741 Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Mon, 4 Jul 2022 10:48:38 +0200 Subject: [PATCH 09/12] add first simple tests for MaestroFinder.withDescendant() --- .../lib/src/custom_selectors.dart | 4 +--- .../test/custom_selectors_test.dart | 20 +++++++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/maestro_test/lib/src/custom_selectors.dart b/packages/maestro_test/lib/src/custom_selectors.dart index c37a59978..33d404b15 100644 --- a/packages/maestro_test/lib/src/custom_selectors.dart +++ b/packages/maestro_test/lib/src/custom_selectors.dart @@ -42,9 +42,7 @@ void maestroTest( } class MaestroFinder extends MatchFinder { - MaestroFinder({required this.finder, required this.tester}) { - print('Created MaestroFinder with finder: $this'); - } + MaestroFinder({required this.finder, required this.tester}); final Finder finder; final WidgetTester tester; diff --git a/packages/maestro_test/test/custom_selectors_test.dart b/packages/maestro_test/test/custom_selectors_test.dart index 51155f818..110f3adb5 100644 --- a/packages/maestro_test/test/custom_selectors_test.dart +++ b/packages/maestro_test/test/custom_selectors_test.dart @@ -71,10 +71,8 @@ void main() { ); }); - group('finds widget by parent', () { - maestroTest('simple parent', ($) async { - await $.pumpWidgetAndSettle( - MaterialApp( + group('smoke tests', () { + Widget app() => MaterialApp( key: const Key('app'), home: Column( children: [ @@ -86,10 +84,13 @@ void main() { key: Key('sizedbox'), child: Text('Hello 2', key: Key('helloText')), ), + const SizedBox(child: Icon(Icons.code)), ], ), - ), - ); + ); + + maestroTest('finds by parent', ($) async { + await $.pumpWidgetAndSettle(app()); expect($(MaterialApp).$(Text), findsNWidgets(2)); expect($(MaterialApp).$(#helloText), findsNWidgets(2)); @@ -102,5 +103,12 @@ void main() { expect($(MaterialApp).$(Container).$('Hello 1'), findsOneWidget); expect($(MaterialApp).$(SizedBox).$('Hello 2'), findsOneWidget); }); + + maestroTest('finds by parent and with descendant', ($) async { + await $.pumpWidgetAndSettle(app()); + + expect($(SizedBox).withDescendant(Text), findsOneWidget); + expect($(Column).withDescendant('Hello 2'), findsOneWidget); + }); }); } From 90d2a8143c4cd12f35741c53c224a00a7db36aea Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Mon, 4 Jul 2022 15:17:25 +0200 Subject: [PATCH 10/12] add a truckload of docs --- packages/maestro_test/analysis_options.yaml | 4 - .../lib/src/custom_selectors.dart | 88 ++++++++++++++++++- .../maestro_test/lib/src/notification.dart | 1 + packages/maestro_test/lib/src/selector.dart | 7 ++ 4 files changed, 95 insertions(+), 5 deletions(-) diff --git a/packages/maestro_test/analysis_options.yaml b/packages/maestro_test/analysis_options.yaml index 6c0a949cd..31551b63c 100644 --- a/packages/maestro_test/analysis_options.yaml +++ b/packages/maestro_test/analysis_options.yaml @@ -1,5 +1 @@ include: package:leancode_lint/analysis_options_package.yaml - -linter: - rules: - public_member_api_docs: false diff --git a/packages/maestro_test/lib/src/custom_selectors.dart b/packages/maestro_test/lib/src/custom_selectors.dart index 33d404b15..c89c4268d 100644 --- a/packages/maestro_test/lib/src/custom_selectors.dart +++ b/packages/maestro_test/lib/src/custom_selectors.dart @@ -41,12 +41,34 @@ void maestroTest( ); } +/// A decorator around [Finder] that provides Maestro _custom selector_ (also +/// known as `$`). +/// +/// class MaestroFinder extends MatchFinder { + /// Creates a new [MaestroFinder] with the given [finder] and [tester]. + /// + /// Usually, you won't use this constructor directly. Instead, you'll use the + /// [MaestroTester] (which is provided by [MaestroTesterCallback] in + /// [maestroTest]) and [MaestroFinder.$]. MaestroFinder({required this.finder, required this.tester}); + /// Finder that this [MaestroFinder] wraps. final Finder finder; + + /// Widget tester that this [MaestroFinder] wraps. final WidgetTester tester; + /// Taps on the widget resolved by this finder. + /// + /// If more than one widget is found, the [index]-th widget is tapped, instead + /// of throwing an exception (like [WidgetTester.tap] does). + /// + /// This method automatically calls [WidgetTester.pumpAndSettle] after tap. If + /// you want to disable this behavior, pass `false` to [andSettle]. + /// + /// See also: + /// - [WidgetController.tap] (which [WidgetTester] extends from) Future tap({bool andSettle = true, int index = 0}) async { await tester.tap(finder.at(index)); @@ -57,6 +79,18 @@ class MaestroFinder extends MatchFinder { } } + /// Enters text into the widget resolved by this finder. + /// + /// If more than one widget is found, [text] in entered into the [index]-th + /// widget, instead of throwing an exception (like [WidgetTester.enterText] + /// does). + /// + /// This method automatically calls [WidgetTester.pumpAndSettle] after + /// entering text. If you want to disable this behavior, pass `false` to + /// [andSettle]. + /// + /// See also: + /// - [WidgetTester.enterText] Future enterText( String text, { bool andSettle = true, @@ -79,6 +113,8 @@ class MaestroFinder extends MatchFinder { return (finder.evaluate().first.widget as Text).data; } + /// Returns a [MaestroFinder] that looks for [matching] in descendants of this + /// [MaestroFinder]. MaestroFinder $(dynamic matching) { return _$( matching: matching, @@ -116,11 +152,49 @@ class MaestroFinder extends MatchFinder { } } +/// A [MaestroFinder] wraps a [WidgetTester]. +/// +/// This is a [callable +/// class](https://dart.dev/guides/language/language-tour#callable-classes), +/// which means that you can call it like a method. class MaestroTester { - MaestroTester(this.tester); + /// Creates a new [MaestroTester] with the given WidgetTester [tester]. + /// + /// Usually, you won't to directly create instance of this class. Instead, + /// you'll use the Instead, you'll use the [MaestroTester] which is provided + /// by [MaestroTesterCallback] in [maestroTest], like this: + /// + /// ```dart + /// import 'package:maestro_test/maestro_test.dart'; + /// + /// void main() { + /// maestroTest('Counter increments smoke test', (maestroTester) async { + /// await maestroTester.pumpWidgetAndSettle(const MyApp()); + /// await maestroTester(#startAppButton).tap(); + /// }); + /// } + /// ``` + /// + /// To make test code more concise, `maestroTester` variable is usually called + /// `$`, like this: + /// + /// ```dart + /// import 'package:maestro_test/maestro_test.dart'; + /// void main() { + /// maestroTest('Counter increments smoke test', ($) async { + /// await $.pumpWidgetAndSettle(const MyApp()); + /// await $(#startAppButton).tap(); + /// }); + /// } + /// ``` + /// + const MaestroTester(this.tester); final WidgetTester tester; + /// Returns a [MaestroFinder] that matches [matching]. + /// + /// Refer to MaestroFinder call(dynamic matching) { return _$( matching: matching, @@ -165,6 +239,18 @@ class MaestroTester { } } +/// Creates a [Finder] from [expression]. +/// +/// The [Finder] that this method returns depends on the type of [expression]. +/// Supported [expression] types are: +/// - [Type], which translates to [CommonFinders.byType] +/// - [Symbol], which translates to [CommonFinders.byKey] +/// - [String], which translates to [CommonFinders.text] +/// - [Pattern], which translates to [CommonFinders.textContaining]. Example +/// [Pattern] is a [RegExp]. +/// - [IconData], which translates to [CommonFinders.byIcon] +/// - [MaestroFinder], which returns a [Finder] that the [MaestroFinder] passed +/// as [expression] resolves to. Finder _createFinder(dynamic expression) { if (expression is Type) { return find.byType(expression); diff --git a/packages/maestro_test/lib/src/notification.dart b/packages/maestro_test/lib/src/notification.dart index 17d6b41c3..e0b18bc2b 100644 --- a/packages/maestro_test/lib/src/notification.dart +++ b/packages/maestro_test/lib/src/notification.dart @@ -13,6 +13,7 @@ class Notification with _$Notification { required String content, }) = _Notification; + /// Creates a new [Notification] from JSON. factory Notification.fromJson(Map json) => _$NotificationFromJson(json); } diff --git a/packages/maestro_test/lib/src/selector.dart b/packages/maestro_test/lib/src/selector.dart index d726d8fb3..1e45fb2e7 100644 --- a/packages/maestro_test/lib/src/selector.dart +++ b/packages/maestro_test/lib/src/selector.dart @@ -1,4 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:maestro_test/src/custom_selectors.dart'; part 'selector.freezed.dart'; part 'selector.g.dart'; @@ -31,8 +32,13 @@ class _AndroidWidgetClasses implements WidgetClasses { String get toggle => 'android.widget.Switch'; } +/// Matches widgets on the underlying native platform. +/// +/// This *does not* match Flutter widgets. If you want to use Maestro's _custom +/// selector_, see [MaestroTester] and [MaestroFinder]. @freezed class Selector with _$Selector { + /// Creates a new [Selector]. const factory Selector({ String? text, String? textStartsWith, @@ -48,6 +54,7 @@ class Selector with _$Selector { String? packageName, }) = _Selector; + /// Creates a new [Selector] from JSON. factory Selector.fromJson(Map json) => _$SelectorFromJson(json); } From 0f2593b0a5cb056372e99a49ce3f133126a32099 Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Mon, 4 Jul 2022 15:36:38 +0200 Subject: [PATCH 11/12] add more docs --- packages/maestro_test/analysis_options.yaml | 5 ++ .../lib/src/custom_selectors.dart | 78 ++++++++++--------- packages/maestro_test/lib/src/extensions.dart | 12 ++- 3 files changed, 56 insertions(+), 39 deletions(-) diff --git a/packages/maestro_test/analysis_options.yaml b/packages/maestro_test/analysis_options.yaml index 31551b63c..7572fb244 100644 --- a/packages/maestro_test/analysis_options.yaml +++ b/packages/maestro_test/analysis_options.yaml @@ -1 +1,6 @@ include: package:leancode_lint/analysis_options_package.yaml + +analyzer: + exclude: + - lib/**/*.freezed.dart + - lib/**/*.g.dart diff --git a/packages/maestro_test/lib/src/custom_selectors.dart b/packages/maestro_test/lib/src/custom_selectors.dart index c89c4268d..8c851fdbf 100644 --- a/packages/maestro_test/lib/src/custom_selectors.dart +++ b/packages/maestro_test/lib/src/custom_selectors.dart @@ -123,6 +123,10 @@ class MaestroFinder extends MatchFinder { ); } + /// Returns a [MaestroFinder] that this method was called on. + /// + /// Checks whether the [Widget] that this [MaestroFinder] was called on has + /// [matching] as a descendant. MaestroFinder withDescendant(dynamic matching) { return MaestroFinder( tester: tester, @@ -154,42 +158,43 @@ class MaestroFinder extends MatchFinder { /// A [MaestroFinder] wraps a [WidgetTester]. /// -/// This is a [callable -/// class](https://dart.dev/guides/language/language-tour#callable-classes), -/// which means that you can call it like a method. +/// Usually, you won't create a [MaestroFinder] instance directly. Instead, +/// you'll use the [MaestroTester] which is provided by [MaestroTesterCallback] +/// in [maestroTest], like this: +/// +/// ```dart +/// import 'package:maestro_test/maestro_test.dart'; +/// +/// void main() { +/// maestroTest('Counter increments smoke test', (maestroTester) async { +/// await maestroTester.pumpWidgetAndSettle(const MyApp()); +/// await maestroTester(#startAppButton).tap(); +/// }); +/// } +/// ``` +/// +/// To make test code more concise, `maestroTester` variable is usually called +/// `$`, like this: +/// +/// ```dart +/// import 'package:maestro_test/maestro_test.dart'; +/// void main() { +/// maestroTest('Counter increments smoke test', ($) async { +/// await $.pumpWidgetAndSettle(const MyApp()); +/// await $(#startAppButton).tap(); +/// }); +/// } +/// ``` +/// You can call [MaestroTester] just like a normal method, because it is a +/// [callable class][callable-class]. +/// +/// [callable-class]: +/// https://dart.dev/guides/language/language-tour#callable-classes class MaestroTester { /// Creates a new [MaestroTester] with the given WidgetTester [tester]. - /// - /// Usually, you won't to directly create instance of this class. Instead, - /// you'll use the Instead, you'll use the [MaestroTester] which is provided - /// by [MaestroTesterCallback] in [maestroTest], like this: - /// - /// ```dart - /// import 'package:maestro_test/maestro_test.dart'; - /// - /// void main() { - /// maestroTest('Counter increments smoke test', (maestroTester) async { - /// await maestroTester.pumpWidgetAndSettle(const MyApp()); - /// await maestroTester(#startAppButton).tap(); - /// }); - /// } - /// ``` - /// - /// To make test code more concise, `maestroTester` variable is usually called - /// `$`, like this: - /// - /// ```dart - /// import 'package:maestro_test/maestro_test.dart'; - /// void main() { - /// maestroTest('Counter increments smoke test', ($) async { - /// await $.pumpWidgetAndSettle(const MyApp()); - /// await $(#startAppButton).tap(); - /// }); - /// } - /// ``` - /// const MaestroTester(this.tester); + /// Widget tester that this [MaestroTester] wraps. final WidgetTester tester; /// Returns a [MaestroFinder] that matches [matching]. @@ -213,10 +218,11 @@ class MaestroTester { } /// See [WidgetTester.pumpAndSettle]. - Future pumpAndSettle( - [Duration duration = const Duration(milliseconds: 100), - EnginePhase phase = EnginePhase.sendSemanticsUpdate, - Duration timeout = const Duration(minutes: 10)]) async { + Future pumpAndSettle([ + Duration duration = const Duration(milliseconds: 100), + EnginePhase phase = EnginePhase.sendSemanticsUpdate, + Duration timeout = const Duration(minutes: 10), + ]) async { await tester.pumpAndSettle(); } diff --git a/packages/maestro_test/lib/src/extensions.dart b/packages/maestro_test/lib/src/extensions.dart index 62694bb4b..7b1b19275 100644 --- a/packages/maestro_test/lib/src/extensions.dart +++ b/packages/maestro_test/lib/src/extensions.dart @@ -1,14 +1,20 @@ import 'package:http/http.dart' as http; -extension IsOk on http.Response { +/// Provides a method to easily check the meaning of the HTTP status code. +extension IsResponseSuccessul on http.Response { + /// Returns true if the status code is 2xx, false otherwise. bool get successful { return (statusCode ~/ 100) == 2; } } -extension SymbolX on Symbol { +/// Makes it possible to retrieve a name that this [Symbol] was created with. +extension SymbolName on Symbol { + /// Returns the name that this [Symbol] was created with. + /// + /// It's kinda hacky, but works well. Might require adjustements to work on + /// the web though. String get name { - // Kinda hacky but works well. Might require adjustements on the web though. final symbol = toString(); return symbol.substring(8, symbol.length - 2); } From 2136c242ebbd15ee5ae8206d1e68f320e768ad0b Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Mon, 4 Jul 2022 15:43:42 +0200 Subject: [PATCH 12/12] revamp directory structure to better show what is native and what not --- packages/maestro_test/lib/maestro_test.dart | 7 ++----- .../lib/src/{ => custom_selectors}/custom_selectors.dart | 0 packages/maestro_test/lib/src/{ => native}/maestro.dart | 4 +++- packages/maestro_test/lib/src/native/native.dart | 3 +++ .../maestro_test/lib/src/{ => native}/native_widget.dart | 0 .../lib/src/{ => native}/native_widget.freezed.dart | 0 .../maestro_test/lib/src/{ => native}/native_widget.g.dart | 0 .../maestro_test/lib/src/{ => native}/notification.dart | 0 .../lib/src/{ => native}/notification.freezed.dart | 0 .../maestro_test/lib/src/{ => native}/notification.g.dart | 0 packages/maestro_test/lib/src/{ => native}/selector.dart | 2 +- .../lib/src/{ => native}/selector.freezed.dart | 0 packages/maestro_test/lib/src/{ => native}/selector.g.dart | 0 packages/maestro_test/test/custom_selectors_test.dart | 2 +- 14 files changed, 10 insertions(+), 8 deletions(-) rename packages/maestro_test/lib/src/{ => custom_selectors}/custom_selectors.dart (100%) rename packages/maestro_test/lib/src/{ => native}/maestro.dart (98%) create mode 100644 packages/maestro_test/lib/src/native/native.dart rename packages/maestro_test/lib/src/{ => native}/native_widget.dart (100%) rename packages/maestro_test/lib/src/{ => native}/native_widget.freezed.dart (100%) rename packages/maestro_test/lib/src/{ => native}/native_widget.g.dart (100%) rename packages/maestro_test/lib/src/{ => native}/notification.dart (100%) rename packages/maestro_test/lib/src/{ => native}/notification.freezed.dart (100%) rename packages/maestro_test/lib/src/{ => native}/notification.g.dart (100%) rename packages/maestro_test/lib/src/{ => native}/selector.dart (95%) rename packages/maestro_test/lib/src/{ => native}/selector.freezed.dart (100%) rename packages/maestro_test/lib/src/{ => native}/selector.g.dart (100%) diff --git a/packages/maestro_test/lib/maestro_test.dart b/packages/maestro_test/lib/maestro_test.dart index dc8142be3..f7ae09011 100644 --- a/packages/maestro_test/lib/maestro_test.dart +++ b/packages/maestro_test/lib/maestro_test.dart @@ -2,8 +2,5 @@ /// flutter_driver. library maestro_test; -export 'src/custom_selectors.dart'; -export 'src/maestro.dart'; -export 'src/native_widget.dart'; -export 'src/notification.dart'; -export 'src/selector.dart'; +export 'src/custom_selectors/custom_selectors.dart'; +export 'src/native/native.dart'; diff --git a/packages/maestro_test/lib/src/custom_selectors.dart b/packages/maestro_test/lib/src/custom_selectors/custom_selectors.dart similarity index 100% rename from packages/maestro_test/lib/src/custom_selectors.dart rename to packages/maestro_test/lib/src/custom_selectors/custom_selectors.dart diff --git a/packages/maestro_test/lib/src/maestro.dart b/packages/maestro_test/lib/src/native/maestro.dart similarity index 98% rename from packages/maestro_test/lib/src/maestro.dart rename to packages/maestro_test/lib/src/native/maestro.dart index be6f81f55..68bce1abe 100644 --- a/packages/maestro_test/lib/src/maestro.dart +++ b/packages/maestro_test/lib/src/native/maestro.dart @@ -4,8 +4,10 @@ import 'package:http/http.dart' as http; import 'package:integration_test/integration_test.dart'; import 'package:logging/logging.dart' as logging; import 'package:logging/logging.dart'; -import 'package:maestro_test/maestro_test.dart'; import 'package:maestro_test/src/extensions.dart'; +import 'package:maestro_test/src/native/native_widget.dart'; +import 'package:maestro_test/src/native/notification.dart'; +import 'package:maestro_test/src/native/selector.dart'; /// Provides functionality to control the device. /// diff --git a/packages/maestro_test/lib/src/native/native.dart b/packages/maestro_test/lib/src/native/native.dart new file mode 100644 index 000000000..3ee6396a8 --- /dev/null +++ b/packages/maestro_test/lib/src/native/native.dart @@ -0,0 +1,3 @@ +export 'maestro.dart'; +export 'notification.dart'; +export 'selector.dart'; diff --git a/packages/maestro_test/lib/src/native_widget.dart b/packages/maestro_test/lib/src/native/native_widget.dart similarity index 100% rename from packages/maestro_test/lib/src/native_widget.dart rename to packages/maestro_test/lib/src/native/native_widget.dart diff --git a/packages/maestro_test/lib/src/native_widget.freezed.dart b/packages/maestro_test/lib/src/native/native_widget.freezed.dart similarity index 100% rename from packages/maestro_test/lib/src/native_widget.freezed.dart rename to packages/maestro_test/lib/src/native/native_widget.freezed.dart diff --git a/packages/maestro_test/lib/src/native_widget.g.dart b/packages/maestro_test/lib/src/native/native_widget.g.dart similarity index 100% rename from packages/maestro_test/lib/src/native_widget.g.dart rename to packages/maestro_test/lib/src/native/native_widget.g.dart diff --git a/packages/maestro_test/lib/src/notification.dart b/packages/maestro_test/lib/src/native/notification.dart similarity index 100% rename from packages/maestro_test/lib/src/notification.dart rename to packages/maestro_test/lib/src/native/notification.dart diff --git a/packages/maestro_test/lib/src/notification.freezed.dart b/packages/maestro_test/lib/src/native/notification.freezed.dart similarity index 100% rename from packages/maestro_test/lib/src/notification.freezed.dart rename to packages/maestro_test/lib/src/native/notification.freezed.dart diff --git a/packages/maestro_test/lib/src/notification.g.dart b/packages/maestro_test/lib/src/native/notification.g.dart similarity index 100% rename from packages/maestro_test/lib/src/notification.g.dart rename to packages/maestro_test/lib/src/native/notification.g.dart diff --git a/packages/maestro_test/lib/src/selector.dart b/packages/maestro_test/lib/src/native/selector.dart similarity index 95% rename from packages/maestro_test/lib/src/selector.dart rename to packages/maestro_test/lib/src/native/selector.dart index 1e45fb2e7..a8226846f 100644 --- a/packages/maestro_test/lib/src/selector.dart +++ b/packages/maestro_test/lib/src/native/selector.dart @@ -1,5 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:maestro_test/src/custom_selectors.dart'; +import 'package:maestro_test/src/custom_selectors/custom_selectors.dart'; part 'selector.freezed.dart'; part 'selector.g.dart'; diff --git a/packages/maestro_test/lib/src/selector.freezed.dart b/packages/maestro_test/lib/src/native/selector.freezed.dart similarity index 100% rename from packages/maestro_test/lib/src/selector.freezed.dart rename to packages/maestro_test/lib/src/native/selector.freezed.dart diff --git a/packages/maestro_test/lib/src/selector.g.dart b/packages/maestro_test/lib/src/native/selector.g.dart similarity index 100% rename from packages/maestro_test/lib/src/selector.g.dart rename to packages/maestro_test/lib/src/native/selector.g.dart diff --git a/packages/maestro_test/test/custom_selectors_test.dart b/packages/maestro_test/test/custom_selectors_test.dart index 110f3adb5..de2eac2c0 100644 --- a/packages/maestro_test/test/custom_selectors_test.dart +++ b/packages/maestro_test/test/custom_selectors_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:maestro_test/src/custom_selectors.dart'; +import 'package:maestro_test/src/custom_selectors/custom_selectors.dart'; void main() { group('finds widget by', () {