Skip to content

Commit

Permalink
Different QOL improvements (#92)
Browse files Browse the repository at this point in the history
* maestrO_test: improve docs

* AutomatorServer: add doubleTap()

* maestro_test: add double tap

* maestro_test: add MaestroTester.dragUntilVisible

* rearrange code

* make `$` accept `Key`

* maestro_test: automatically call `pumpAndSettle()` after `dragUntilVisible()`

* MaestroTester.dragUntilVisible: select the first finder by default

* add `sleep` parameter for `maestroTest()`

* make forwarded methods of `MaestroTester` accept less arguments

* break `custom_selectors` into few files

* add more doc comments

* add more docs

* maestroTest: don't sleep by default

* add additional test cases for find.byKey

* add Key as supported type to ArgumentError
  • Loading branch information
bartekpacia authored Jul 6, 2022
1 parent 76bc9bd commit d861647
Show file tree
Hide file tree
Showing 10 changed files with 564 additions and 316 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,11 @@ class ServerInstrumentation {
UIAutomatorInstrumentation.instance.tap(body)
Response(OK)
},
"doubleTap" bind POST to {
val body = Json.decodeFromString<SelectorQuery>(it.bodyString())
UIAutomatorInstrumentation.instance.doubleTap(body)
Response(OK)
},
"enterTextByIndex" bind POST to {
val body = Json.decodeFromString<EnterTextByIndexCommand>(it.bodyString())
UIAutomatorInstrumentation.instance.enterText(body.index, body.text)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,21 @@ class UIAutomatorInstrumentation {

val uiObject = device.findObject(selector)

Logger.d("Clicking on UIObject ${uiObject.text}")
Logger.d("Clicking on UIObject with text: ${uiObject.text}")
uiObject.click()
}

fun doubleTap(query: SelectorQuery) {
Logger.d("doubleTap()")

val device = getUiDevice()
val selector = query.toUiSelector()
Logger.d("Selector: $selector")

val uiObject = device.findObject(selector)

Logger.d("Double clicking on UIObject with text: ${uiObject.text}")
uiObject.click()
uiObject.click()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'package:args/command_runner.dart';

import '../../common/common.dart';
import 'package:maestro_cli/src/common/common.dart';

class DoctorCommand extends Command<int> {
DoctorCommand() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import 'package:args/command_runner.dart';
import 'package:maestro_cli/src/common/common.dart';
import 'package:pub_updater/pub_updater.dart';

import '../../common/common.dart';

class UpdateCommand extends Command<int> {
UpdateCommand() : _pubUpdater = PubUpdater();

Expand Down
147 changes: 147 additions & 0 deletions packages/maestro_test/lib/src/custom_selectors/common.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import 'dart:io' as io;

import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:maestro_test/src/custom_selectors/maestro_finder.dart';
import 'package:maestro_test/src/custom_selectors/maestro_tester.dart';
import 'package:maestro_test/src/extensions.dart';
import 'package:meta/meta.dart';

/// Signature for callback to [maestroTest].
typedef MaestroTesterCallback = Future<void> Function(MaestroTester $);

/// Like [testWidgets], but with Maestro custom selector support.
///
/// If you want to not close the app immediately after the test completes, use
/// [sleep].
///
/// ### Custom selectors
///
/// Custom selectors greatly simplify writing widget tests.
///
/// ### Using the default [WidgetTester]
/// If you need to do something using Flutter's [WidgetTester], you can access
/// it like this:
///
/// ```dart
/// maestroTest(
/// 'increase counter text',
/// ($) async {
/// await $.tester.tap(find.byIcon(Icons.add));
/// },
/// );
/// ```
@isTest
void maestroTest(
String description,
MaestroTesterCallback callback, {
bool? skip,
Timeout? timeout,
bool semanticsEnabled = true,
TestVariant<Object?> variant = const DefaultTestVariant(),
Duration sleep = Duration.zero,
dynamic tags,
}) {
return testWidgets(
description,
(widgetTester) async {
await callback(MaestroTester(widgetTester));
io.sleep(sleep);
},
skip: skip,
timeout: timeout,
semanticsEnabled: semanticsEnabled,
variant: variant,
tags: tags,
);
}

/// Creates a [Finder] from [matching].
///
/// ### Usage
///
/// Usually, you won't use `createFinder` directly. Instead, you'll use
/// [MaestroTester.call] and [MaestroFinder.$], like this:
///
/// ```dart
/// maestroTest(
/// 'increase counter text',
/// ($) async {
/// // calls createFinder method under the hood
/// await $(Scaffold).$(#passwordTextField).enterText('my password');
/// },
/// );
/// ```
///
/// ### What does this method accept?
///
/// The [Finder] that this method returns depends on the type of [matching].
/// Supported types of [matching] are:
/// - [Type], which translates to [CommonFinders.byType], for example:
/// ```dart
/// final finder = createFinder(Button);
/// ```
/// - [Key], which translates to [CommonFinders.byKey], for example:
/// ```dart
/// final finder = createFinder(Key('signInWithGoogle'));
/// ```
/// - [Symbol], which translates to [CommonFinders.byKey], for example:
/// ```dart
/// final finder = createFinder(#signInWithGoogle);
/// ```
/// - [String], which translates to [CommonFinders.text], for example:
/// ```dart
/// final finder = createFinder('Sign in with Google');
/// ```
/// - [Pattern], which translates to [CommonFinders.textContaining]. Example
/// [Pattern] is a [RegExp].
/// ```dart
/// final finder = createFinder(RegExp('.*in with.*'));
/// ```
/// - [IconData], which translates to [CommonFinders.byIcon], for example:
/// ```dart
/// final finder = createFinder(Icons.add);
/// ```
/// - [MaestroFinder], which returns a [Finder] that the [MaestroFinder], for
/// example: passed as [matching] resolves to.
/// ```dart
/// final finder = createFinder($(Text('Sign in with Google')));
/// ```
///
/// See also:
/// - [MaestroTester.call]
/// - [MaestroFinder.$]
/// - [MaestroFinder.resolve]
Finder createFinder(dynamic matching) {
if (matching is Type) {
return find.byType(matching);
}

if (matching is Key) {
return find.byKey(matching);
}

if (matching is Symbol) {
return find.byKey(Key(matching.name));
}

if (matching is String) {
return find.text(matching);
}

if (matching is Pattern) {
return find.textContaining(matching);
}

if (matching is IconData) {
return find.byIcon(matching);
}

if (matching is MaestroFinder) {
return matching.finder;
}

throw ArgumentError(
'expression of type ${matching.runtimeType} is not one of supported types `Type`, `Key`, `Symbol`, `String`, `Pattern`, `IconData`, or `MaestroFinder`',
);
}
Loading

0 comments on commit d861647

Please sign in to comment.