enableFlutterDriverExtension function Null safety

void enableFlutterDriverExtension(
  1. {DataHandler? handler,
  2. bool silenceErrors = false,
  3. bool enableTextEntryEmulation = true,
  4. List<FinderExtension>? finders,
  5. List<CommandExtension>? commands}
)

Enables Flutter Driver VM service extension.

This extension is required for tests that use package:flutter_driver to drive applications from a separate process. In order to allow the driver to interact with the application, this method changes the behavior of the framework in several ways - including keyboard interaction and text editing. Applications intended for release should never include this method.

Call this function prior to running your application, e.g. before you call runApp.

Optionally you can pass a DataHandler callback. It will be called if the test calls FlutterDriver.requestData.

silenceErrors will prevent exceptions from being logged. This is useful for tests where exceptions are expected. Defaults to false. Any errors will still be returned in the response field of the result JSON along with an isError boolean.

The enableTextEntryEmulation parameter controls whether the application interacts with the system's text entry methods or a mocked out version used by Flutter Driver. If it is set to false, FlutterDriver.enterText will fail, but testing the application with real keyboard input is possible. This value may be updated during a test by calling FlutterDriver.setTextEntryEmulation.

The finders and commands parameters are optional and used to add custom finders or commands, as in the following example.

void main() {
  enableFlutterDriverExtension(
    finders: <FinderExtension>[ SomeFinderExtension() ],
    commands: <CommandExtension>[ SomeCommandExtension() ],
  );

  app.main();
}
driver.sendCommand(SomeCommand(ByValueKey('Button'), 7));

Note: SomeFinder and SomeFinderExtension must be placed in different files to avoid dart:ui import issue. Imports relative to dart:ui can't be accessed from host runner, where flutter runtime is not accessible.

class SomeFinder extends SerializableFinder {
  const SomeFinder(this.title);

  final String title;

  @override
  String get finderType => 'SomeFinder';

  @override
  Map<String, String> serialize() => super.serialize()..addAll(<String, String>{
    'title': title,
  });
}
class SomeFinderExtension extends FinderExtension {

 String get finderType => 'SomeFinder';

 SerializableFinder deserialize(Map<String, String> params, DeserializeFinderFactory finderFactory) {
   return SomeFinder(json['title']);
 }

 Finder createFinder(SerializableFinder finder, CreateFinderFactory finderFactory) {
   Some someFinder = finder as SomeFinder;

   return find.byElementPredicate((Element element) {
     final Widget widget = element.widget;
     if (element.widget is SomeWidget) {
       return element.widget.title == someFinder.title;
     }
     return false;
   });
 }
}

Note: SomeCommand, SomeResult and SomeCommandExtension must be placed in different files to avoid dart:ui import issue. Imports relative to dart:ui can't be accessed from host runner, where flutter runtime is not accessible.

class SomeCommand extends CommandWithTarget {
  SomeCommand(SerializableFinder finder, this.times, {Duration? timeout})
      : super(finder, timeout: timeout);

  SomeCommand.deserialize(Map<String, String> json, DeserializeFinderFactory finderFactory)
      : times = int.parse(json['times']!),
        super.deserialize(json, finderFactory);

  @override
  Map<String, String> serialize() {
    return super.serialize()..addAll(<String, String>{'times': '$times'});
  }

  @override
  String get kind => 'SomeCommand';

  final int times;
}
class SomeCommandResult extends Result {
  const SomeCommandResult(this.resultParam);

  final String resultParam;

  @override
  Map<String, dynamic> toJson() {
    return <String, dynamic>{
      'resultParam': resultParam,
    };
  }
}
class SomeCommandExtension extends CommandExtension {
  @override
  String get commandKind => 'SomeCommand';

  @override
  Future<Result> call(Command command, WidgetController prober, CreateFinderFactory finderFactory, CommandHandlerFactory handlerFactory) async {
    final SomeCommand someCommand = command as SomeCommand;

    // Deserialize [Finder]:
    final Finder finder = finderFactory.createFinder(stubCommand.finder);

    // Wait for [Element]:
    handlerFactory.waitForElement(finder);

    // Alternatively, wait for [Element] absence:
    handlerFactory.waitForAbsentElement(finder);

    // Submit known [Command]s:
    for (int index = 0; i < someCommand.times; index++) {
      await handlerFactory.handleCommand(Tap(someCommand.finder), prober, finderFactory);
    }

    // Alternatively, use [WidgetController]:
    for (int index = 0; i < stubCommand.times; index++) {
      await prober.tap(finder);
    }

    return const SomeCommandResult('foo bar');
  }

  @override
  Command deserialize(Map<String, String> params, DeserializeFinderFactory finderFactory, DeserializeCommandFactory commandFactory) {
    return SomeCommand.deserialize(params, finderFactory);
  }
}

Implementation

void enableFlutterDriverExtension({ DataHandler? handler, bool silenceErrors = false, bool enableTextEntryEmulation = true, List<FinderExtension>? finders, List<CommandExtension>? commands}) {
  assert(WidgetsBinding.instance == null);
  _DriverBinding(handler, silenceErrors, enableTextEntryEmulation, finders ?? <FinderExtension>[], commands ?? <CommandExtension>[]);
  assert(WidgetsBinding.instance is _DriverBinding);
}