diff --git a/packages/flutter_driver/lib/src/common/deserialization_factory.dart b/packages/flutter_driver/lib/src/common/deserialization_factory.dart index 215b0edaa3b..97cd532e4b3 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 @@ mixin DeserializeCommandFactory { 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 b3cd5f6593a..cca7c0b4c15 100644 --- a/packages/flutter_driver/lib/src/common/find.dart +++ b/packages/flutter_driver/lib/src/common/find.dart @@ -80,6 +80,24 @@ class WaitForAbsent extends CommandWithTarget { 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 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 f0ba0221e61..41ccd3a0d75 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 @@ mixin CommandHandlerFactory { 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 @@ mixin CommandHandlerFactory { return Result.empty; } + Future _waitForTappable(Command command, CreateFinderFactory finderFactory) async { + final WaitForTappable waitForTappableCommand = command as WaitForTappable; + await waitForElement( + finderFactory.createFinder(waitForTappableCommand.finder).hitTestable(), + ); + return Result.empty; + } + Future _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 9b20532cc02..56b7223c03b 100644 --- a/packages/flutter_driver/lib/src/driver/driver.dart +++ b/packages/flutter_driver/lib/src/driver/driver.dart @@ -220,6 +220,11 @@ abstract class FlutterDriver { await sendCommand(WaitForAbsent(finder, timeout: timeout)); } + /// Waits until [finder] is tappable. + Future waitForTappable(SerializableFinder finder, { Duration? timeout }) async { + await sendCommand(WaitForTappable(finder, timeout: timeout)); + } + /// Waits until the given [waitCondition] is satisfied. Future 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 638baccbe31..6857a2130f6 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 @@ void main() { }); }); + group('waitForTappable', () { + late FlutterDriverExtension driverExtension; + + Future> waitForTappable() async { + final SerializableFinder finder = ByValueKey('widgetOne'); + final Map arguments = WaitForTappable(finder).serialize(); + final Map result = await driverExtension.call(arguments); + return result; + } + + final Widget testWidget = MaterialApp( + home: Material( + child: Column(children: const [ + 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 waitForTappableResult = await waitForTappable(); + expect(waitForTappableResult['isError'], isFalse); + }); + }); + group('waitUntilFrameSync', () { late FlutterDriverExtension driverExtension; Map? result;