[flutter_driver] Add waitForTappable to flutter_driver (#79581)
diff --git a/packages/flutter_driver/lib/src/common/deserialization_factory.dart b/packages/flutter_driver/lib/src/common/deserialization_factory.dart
index 215b0ed..97cd532 100644
--- a/packages/flutter_driver/lib/src/common/deserialization_factory.dart
+++ b/packages/flutter_driver/lib/src/common/deserialization_factory.dart
@@ -56,6 +56,7 @@
case 'tap': return Tap.deserialize(params, finderFactory);
case 'waitFor': return WaitFor.deserialize(params, finderFactory);
case 'waitForAbsent': return WaitForAbsent.deserialize(params, finderFactory);
+ case 'waitForTappable': return WaitForTappable.deserialize(params, finderFactory);
case 'waitForCondition': return WaitForCondition.deserialize(params);
case 'waitUntilNoTransientCallbacks': return WaitForCondition.deserialize(params);
case 'waitUntilNoPendingFrame': return WaitForCondition.deserialize(params);
diff --git a/packages/flutter_driver/lib/src/common/find.dart b/packages/flutter_driver/lib/src/common/find.dart
index b3cd5f6..cca7c0b 100644
--- a/packages/flutter_driver/lib/src/common/find.dart
+++ b/packages/flutter_driver/lib/src/common/find.dart
@@ -80,6 +80,24 @@
String get kind => 'waitForAbsent';
}
+/// A Flutter Driver command that waits until [finder] can be tapped.
+class WaitForTappable extends CommandWithTarget {
+ /// Creates a command that waits for the widget identified by [finder] to
+ /// be tappable within the [timeout] amiount of time.
+ ///
+ /// If [timeout] is not specified, the command defuts to no timeout.
+ WaitForTappable(SerializableFinder finder, {Duration? timeout})
+ : super(finder, timeout: timeout);
+
+ /// Deserialized this command from the value generated by [serialize].
+ WaitForTappable.deserialize(
+ Map<String, String> json, DeserializeFinderFactory finderFactory)
+ : super.deserialize(json, finderFactory);
+
+ @override
+ String get kind => 'waitForTappable';
+}
+
/// Base class for Flutter Driver finders, objects that describe how the driver
/// should search for elements.
abstract class SerializableFinder {
diff --git a/packages/flutter_driver/lib/src/common/handler_factory.dart b/packages/flutter_driver/lib/src/common/handler_factory.dart
index f0ba022..41ccd3a 100644
--- a/packages/flutter_driver/lib/src/common/handler_factory.dart
+++ b/packages/flutter_driver/lib/src/common/handler_factory.dart
@@ -169,6 +169,7 @@
case 'tap': return _tap(command, prober, finderFactory);
case 'waitFor': return _waitFor(command, finderFactory);
case 'waitForAbsent': return _waitForAbsent(command, finderFactory);
+ case 'waitForTappable': return _waitForTappable(command, finderFactory);
case 'waitForCondition': return _waitForCondition(command);
case 'waitUntilNoTransientCallbacks': return _waitUntilNoTransientCallbacks(command);
case 'waitUntilNoPendingFrame': return _waitUntilNoPendingFrame(command);
@@ -236,6 +237,14 @@
return Result.empty;
}
+ Future<Result> _waitForTappable(Command command, CreateFinderFactory finderFactory) async {
+ final WaitForTappable waitForTappableCommand = command as WaitForTappable;
+ await waitForElement(
+ finderFactory.createFinder(waitForTappableCommand.finder).hitTestable(),
+ );
+ return Result.empty;
+ }
+
Future<Result> _waitForCondition(Command command) async {
assert(command != null);
final WaitForCondition waitForConditionCommand = command as WaitForCondition;
diff --git a/packages/flutter_driver/lib/src/driver/driver.dart b/packages/flutter_driver/lib/src/driver/driver.dart
index 9b20532..56b7223 100644
--- a/packages/flutter_driver/lib/src/driver/driver.dart
+++ b/packages/flutter_driver/lib/src/driver/driver.dart
@@ -220,6 +220,11 @@
await sendCommand(WaitForAbsent(finder, timeout: timeout));
}
+ /// Waits until [finder] is tappable.
+ Future<void> waitForTappable(SerializableFinder finder, { Duration? timeout }) async {
+ await sendCommand(WaitForTappable(finder, timeout: timeout));
+ }
+
/// Waits until the given [waitCondition] is satisfied.
Future<void> waitForCondition(SerializableWaitCondition waitCondition, {Duration? timeout}) async {
await sendCommand(WaitForCondition(waitCondition, timeout: timeout));
diff --git a/packages/flutter_driver/test/src/real_tests/extension_test.dart b/packages/flutter_driver/test/src/real_tests/extension_test.dart
index 638bacc..6857a21 100644
--- a/packages/flutter_driver/test/src/real_tests/extension_test.dart
+++ b/packages/flutter_driver/test/src/real_tests/extension_test.dart
@@ -1130,6 +1130,41 @@
});
});
+ group('waitForTappable', () {
+ late FlutterDriverExtension driverExtension;
+
+ Future<Map<String, dynamic>> waitForTappable() async {
+ final SerializableFinder finder = ByValueKey('widgetOne');
+ final Map<String, String> arguments = WaitForTappable(finder).serialize();
+ final Map<String, dynamic> result = await driverExtension.call(arguments);
+ return result;
+ }
+
+ final Widget testWidget = MaterialApp(
+ home: Material(
+ child: Column(children: const<Widget> [
+ Text('Hello ', key: Key('widgetOne')),
+ SizedBox(
+ height: 0,
+ width: 0,
+ child: Text('World!', key: Key('widgetTwo')),
+ )
+ ],
+ ),
+ ),
+ );
+
+ testWidgets('returns true when widget is tappable', (
+ WidgetTester tester) async {
+ driverExtension = FlutterDriverExtension((String? arg) async => '', true, false);
+
+ await tester.pumpWidget(testWidget);
+
+ final Map<String, dynamic> waitForTappableResult = await waitForTappable();
+ expect(waitForTappableResult['isError'], isFalse);
+ });
+ });
+
group('waitUntilFrameSync', () {
late FlutterDriverExtension driverExtension;
Map<String, dynamic>? result;