[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;