enableFlutterDriverExtension function

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() ],
  );

  runApp(const MyHomeWidget());
}

class SomeFinderExtension extends FinderExtension {
  @override
  String get finderType => 'SomeFinder';

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

  @override
  Finder createFinder(SerializableFinder finder, CreateFinderFactory finderFactory) {
    final SomeFinder someFinder = finder as SomeFinder;

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

// Use this class in a test anywhere where a SerializableFinder is expected.
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 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(someCommand.finder);

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

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

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

    // Alternatively, use [WidgetController]:
    for (int i = 0; i < someCommand.times; i++) {
      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);
  }
}

// Pass an instance of this class to `FlutterDriver.sendCommand` to invoke
// the custom command during a test.
class SomeCommand extends CommandWithTarget {
  SomeCommand(super.finder, this.times, {super.timeout});

  SomeCommand.deserialize(super.json, super.finderFactory)
      : times = int.parse(json['times']!),
        super.deserialize();

  @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,
    };
  }
}

Implementation

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