feat(flutter): update to work with latest Flutter stable release

fix(spelling): fixed various spelling errors
fix(lint): fixed lint errors
This commit is contained in:
Jon Samwell 2020-05-08 09:36:55 +10:00
parent ba28cc8f30
commit f0f061b6d8
30 changed files with 211 additions and 171 deletions

View File

@ -1,32 +1,34 @@
## [1.1.7+7] - 25/03/2019 ## [1.1.8] - 08/05/2020
* Added the ability to test against an already running app; enabling you to debug a running application while it has tests executed against it. Setting the configuration property `runningAppProtocolEndpointUri` to the service protocol endpoint (found in stdout when an app has `--verbose` logging turrned on) will ensure that the existing app is connected to rather than starting a new instance of the app. NOTE: ensure the app you are trying to connect to calls `enableFlutterDriverExtension()` when it starts up otherwise the Flutter Driver will not be able to connect to it. * Updated library to work with the new way the Flutter stable branch manages logging for Flutter driver
* Fixed spelling mistake of `targetAppWorkingDirectory` & `flutterDriverMaxConnectionAttempts` in `FlutterTestConfiguration`
* Added the ability to test against an already running app; enabling you to debug a running application while it has tests executed against it. Setting the configuration property `runningAppProtocolEndpointUri` to the service protocol endpoint (found in stdout when an app has `--verbose` logging turned on) will ensure that the existing app is connected to rather than starting a new instance of the app. NOTE: ensure the app you are trying to connect to calls `enableFlutterDriverExtension()` when it starts up otherwise the Flutter Driver will not be able to connect to it.
## [1.1.7+6] - 04/03/2019 ## [1.1.7+6] - 04/03/2020
* Updated to latest Gherkin library (see https://github.com/jonsamwell/dart_gherkin/blob/master/CHANGELOG.md#117---04032020) - this includes a breaking change to the `Hook` inteface that will need to be updated if any of the `Scenerio` level methods are implemented * Updated to latest Gherkin library (see https://github.com/jonsamwell/dart_gherkin/blob/master/CHANGELOG.md#117---04032020) - this includes a breaking change to the `Hook` inteface that will need to be updated if any of the `Scenerio` level methods are implemented
* Ensured the well known step `I tap the ".." button` scroll the element into view first * Ensured the well known step `I tap the ".." button` scroll the element into view first
## [1.1.7+5] - 03/02/2019 ## [1.1.7+5] - 03/02/2020
* Updated to latest Gherkin library (see https://github.com/jonsamwell/dart_gherkin/blob/master/CHANGELOG.md#1164---03022020) * Updated to latest Gherkin library (see https://github.com/jonsamwell/dart_gherkin/blob/master/CHANGELOG.md#1164---03022020)
## [1.1.7+4] - 31/01/2019 ## [1.1.7+4] - 31/01/2020
* Update check to determine if any devices are connected to run tests against * Update check to determine if any devices are connected to run tests against
* When the flag `verboseFlutterProcessLogs` was true Flutter driver was preemptively connecting to the app when it was not ready * When the flag `verboseFlutterProcessLogs` was true Flutter driver was preemptively connecting to the app when it was not ready
## [1.1.7+3] - 08/01/2019 ## [1.1.7+3] - 08/01/2020
* Added retry logic to the Futter driver connect call to handle the seemingly random connection failures * Added retry logic to the Futter driver connect call to handle the seemingly random connection failures
* Ensured `AttachScreenshotOnFailedStepHook` cannot throw an unhandled exception causing the test run to stop * Ensured `AttachScreenshotOnFailedStepHook` cannot throw an unhandled exception causing the test run to stop
* Added new well known step `When I tap the back button` which finds and taps the default page back button * Added new well known step `When I tap the back button` which finds and taps the default page back button
* Added a new well known step `Then I expect the widget 'notification' to be present within 2 seconds` which expects a widget with a given key to be present within n seconds * Added a new well known step `Then I expect the widget 'notification' to be present within 2 seconds` which expects a widget with a given key to be present within n seconds
* Updated Gherkin library version * Updated Gherkin library version
## [1.1.7+2] - 07/01/2019 ## [1.1.7+2] - 07/01/2020
* Increased the Flutter driver reconnection delay to try and overcome some driver to app connection issues on slower machines * Increased the Flutter driver reconnection delay to try and overcome some driver to app connection issues on slower machines
## [1.1.7+1] - 07/01/2019 ## [1.1.7+1] - 07/01/2020
* Ensured when the Flutter driver is closed it cannot throw an unhandled exception causing the test run the stop * Ensured when the Flutter driver is closed it cannot throw an unhandled exception causing the test run the stop
* Updated Gherkin library version * Updated Gherkin library version
## [1.1.7] - 06/01/2019 ## [1.1.7] - 06/01/2020
* `WhenFillFieldStep` Ensure widget is scrolled into view before setting it's value * `WhenFillFieldStep` Ensure widget is scrolled into view before setting it's value
* Fixed lint warnings * Fixed lint warnings

View File

@ -981,7 +981,7 @@ After which the file will most likely look like this
#### Debugging the app under test #### Debugging the app under test
Setting the configuration property `runningAppProtocolEndpointUri` to the service protocol endpoint (found in stdout when an app has `--verbose` logging turrned on) will ensure that the existing app is connected to rather than starting a new instance of the app. Setting the configuration property `runningAppProtocolEndpointUri` to the service protocol endpoint (found in stdout when an app has `--verbose` logging turned on) will ensure that the existing app is connected to rather than starting a new instance of the app.
NOTE: ensure the app you are trying to connect to calls `enableFlutterDriverExtension()` when it starts up otherwise the Flutter Driver will not be able to connect to it. NOTE: ensure the app you are trying to connect to calls `enableFlutterDriverExtension()` when it starts up otherwise the Flutter Driver will not be able to connect to it.

View File

@ -5,6 +5,7 @@ export "FLUTTER_APPLICATION_PATH=C:\github\flutter_gherkin\example"
export "FLUTTER_TARGET=lib\main.dart" export "FLUTTER_TARGET=lib\main.dart"
export "FLUTTER_BUILD_DIR=build" export "FLUTTER_BUILD_DIR=build"
export "SYMROOT=${SOURCE_ROOT}/../build\ios" export "SYMROOT=${SOURCE_ROOT}/../build\ios"
export "OTHER_LDFLAGS=$(inherited) -framework Flutter"
export "FLUTTER_FRAMEWORK_DIR=C:\Google\flutter\bin\cache\artifacts\engine\ios" export "FLUTTER_FRAMEWORK_DIR=C:\Google\flutter\bin\cache\artifacts\engine\ios"
export "FLUTTER_BUILD_NAME=1.0.0" export "FLUTTER_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1" export "FLUTTER_BUILD_NUMBER=1"

View File

@ -37,7 +37,7 @@ class _MyHomePageState extends State<MyHomePage> {
title: Text(widget.title), title: Text(widget.title),
), ),
drawer: Drawer( drawer: Drawer(
key: const Key("drawer"), key: const Key('drawer'),
child: ListView( child: ListView(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
children: <Widget>[ children: <Widget>[
@ -81,7 +81,7 @@ class _MyHomePageState extends State<MyHomePage> {
// to identify this specific Widget from inside our test suite and // to identify this specific Widget from inside our test suite and
// read the text. // read the text.
key: const Key('counter'), key: const Key('counter'),
style: Theme.of(context).textTheme.display1, style: Theme.of(context).textTheme.headline4,
), ),
], ],
), ),

View File

@ -9,7 +9,7 @@ import 'steps/tap_button_n_times_step.dart';
Future<void> main() { Future<void> main() {
final config = FlutterTestConfiguration() final config = FlutterTestConfiguration()
..features = [Glob(r"features/**.feature")] ..features = [Glob('features//**.feature')]
..reporters = [ ..reporters = [
ProgressReporter(), ProgressReporter(),
TestRunSummaryReporter(), TestRunSummaryReporter(),
@ -31,17 +31,18 @@ Future<void> main() {
..customStepParameterDefinitions = [ ..customStepParameterDefinitions = [
ColourParameter(), ColourParameter(),
] ]
..restartAppBetweenScenarios = false ..restartAppBetweenScenarios = true
..targetAppWorkingDirecotry = "../" ..targetAppWorkingDirectory = '../'
..targetAppPath = "test_driver/app.dart" ..targetAppPath = 'test_driver/app.dart'
// ..buildFlavor = "staging" // uncomment when using build flavor and check android/ios flavor setup see android file android\app\build.gradle // ..buildFlavor = "staging" // uncomment when using build flavor and check android/ios flavor setup see android file android\app\build.gradle
// ..targetDeviceId = "all" // uncomment to run tests on all connected devices or set specific device target id // ..targetDeviceId = "all" // uncomment to run tests on all connected devices or set specific device target id
// ..tagExpression = "@smoke" // uncomment to see an example of running scenarios based on tag expressions // ..tagExpression = "@smoke" // uncomment to see an example of running scenarios based on tag expressions
// ..logFlutterProcessOutput = true // uncomment to see command invoked to start the flutter test app // ..logFlutterProcessOutput = true // uncomment to see command invoked to start the flutter test app
// ..verboseFlutterProcessLogs = true // uncomment to see the verbose output from the Flutter process // ..verboseFlutterProcessLogs = true // uncomment to see the verbose output from the Flutter process
// ..flutterBuildTimeout = Duration(minutes: 3) // uncomment to change the default period that flutter is expected to build and start the app within // ..flutterBuildTimeout = Duration(minutes: 3) // uncomment to change the default period that flutter is expected to build and start the app within
..runningAppProtocolEndpointUri = // ..runningAppProtocolEndpointUri =
'http://127.0.0.1:51540/bkegoer6eH8=/' // already running app observatory / service protocol uri (with enableFlutterDriverExtension method invoked) to test against if you use this set `restartAppBetweenScenarios` to false // 'http://127.0.0.1:51540/bkegoer6eH8=/' // already running app observatory / service protocol uri (with enableFlutterDriverExtension method invoked) to test against if you use this set `restartAppBetweenScenarios` to false
..exitAfterTestRun = true; // set to false if debugging to exit cleanly ..exitAfterTestRun = true; // set to false if debugging to exit cleanly
return GherkinRunner().execute(config); return GherkinRunner().execute(config);
} }

View File

@ -9,13 +9,13 @@ class HookExample extends Hook {
/// Run before any scenario in a test run have executed /// Run before any scenario in a test run have executed
@override @override
Future<void> onBeforeRun(TestConfiguration config) async { Future<void> onBeforeRun(TestConfiguration config) async {
print("before run hook"); print('before run hook');
} }
/// Run after all scenarios in a test run have completed /// Run after all scenarios in a test run have completed
@override @override
Future<void> onAfterRun(TestConfiguration config) async { Future<void> onAfterRun(TestConfiguration config) async {
print("after run hook"); print('after run hook');
} }
/// Run before a scenario and it steps are executed /// Run before a scenario and it steps are executed

File diff suppressed because one or more lines are too long

View File

@ -4,13 +4,13 @@ enum Colour { red, green, blue }
class ColourParameter extends CustomParameter<Colour> { class ColourParameter extends CustomParameter<Colour> {
ColourParameter() ColourParameter()
: super("colour", RegExp(r"(red|green|blue)", caseSensitive: true), (c) { : super('colour', RegExp(r'(red|green|blue)', caseSensitive: true), (c) {
switch (c.toLowerCase()) { switch (c.toLowerCase()) {
case "red": case 'red':
return Colour.red; return Colour.red;
case "green": case 'green':
return Colour.green; return Colour.green;
case "blue": case 'blue':
default: default:
return Colour.blue; return Colour.blue;
} }

View File

@ -20,5 +20,5 @@ class GivenIAddTheUsers extends Given1<Table> {
} }
@override @override
RegExp get pattern => RegExp(r"I add the users"); RegExp get pattern => RegExp(r'I add the users');
} }

View File

@ -8,5 +8,5 @@ class GivenIPickAColour extends Given1<Colour> {
} }
@override @override
RegExp get pattern => RegExp(r"I pick the colour {colour}"); RegExp get pattern => RegExp(r'I pick the colour {colour}');
} }

View File

@ -15,5 +15,5 @@ class GivenIProvideAComment extends Given2<String, String> {
} }
@override @override
RegExp get pattern => RegExp(r"I provide the following {string} comment"); RegExp get pattern => RegExp(r'I provide the following {string} comment');
} }

View File

@ -15,5 +15,5 @@ class TapButtonNTimesStep extends When2WithWorld<String, int, FlutterWorld> {
} }
@override @override
RegExp get pattern => RegExp(r"I tap the {string} button {int} times"); RegExp get pattern => RegExp(r'I tap the {string} button {int} times');
} }

View File

@ -1,9 +1,9 @@
library flutter_gherkin; library flutter_gherkin;
// Flutter specific implementations // Flutter specific implementations
export "src/flutter/flutter_world.dart"; export 'src/flutter/flutter_world.dart';
export "src/flutter/flutter_test_configuration.dart"; export 'src/flutter/flutter_test_configuration.dart';
export "src/flutter/utils/driver_utils.dart"; export 'src/flutter/utils/driver_utils.dart';
// Well known steps // Well known steps
export 'src/flutter/steps/given_i_open_the_drawer_step.dart'; export 'src/flutter/steps/given_i_open_the_drawer_step.dart';

View File

@ -4,45 +4,45 @@ import 'dart:io';
import 'package:gherkin/gherkin.dart'; import 'package:gherkin/gherkin.dart';
class FlutterRunProcessHandler extends ProcessHandler { class FlutterRunProcessHandler extends ProcessHandler {
static const String FAIL_COLOR = "\u001b[33;31m"; // red static const String FAIL_COLOR = '\u001b[33;31m'; // red
static const String WARN_COLOR = "\u001b[33;10m"; // yellow static const String WARN_COLOR = '\u001b[33;10m'; // yellow
static const String RESET_COLOR = "\u001b[33;0m"; static const String RESET_COLOR = '\u001b[33;0m';
// the flutter process usually outputs something like the below to indicate the app is ready to be connected to // the flutter process usually outputs something like the below to indicate the app is ready to be connected to
// `An Observatory debugger and profiler on AOSP on IA Emulator is available at: http://127.0.0.1:51322/BI_fyYaeoCE=/` // `An Observatory debugger and profiler on AOSP on IA Emulator is available at: http://127.0.0.1:51322/BI_fyYaeoCE=/`
static RegExp _observatoryDebuggerUriRegex = RegExp( static final RegExp _observatoryDebuggerUriRegex = RegExp(
r"observatory (?:debugger|url) .* available .*[:]? (http[s]?:.*\/).*", r'observatory (?:debugger|url) .* available .*[:]? (http[s]?:.*\/).*',
caseSensitive: false, caseSensitive: false,
multiLine: false, multiLine: false,
); );
static RegExp _noConnectedDeviceRegex = RegExp( static final RegExp _noConnectedDeviceRegex = RegExp(
r"no connected device|no supported devices connected", r'no connected device|no supported devices connected',
caseSensitive: false, caseSensitive: false,
multiLine: false, multiLine: false,
); );
static RegExp _moreThanOneDeviceConnectedDeviceRegex = RegExp( static final RegExp _moreThanOneDeviceConnectedDeviceRegex = RegExp(
r"more than one device connected", r'more than one device connected',
caseSensitive: false, caseSensitive: false,
multiLine: false, multiLine: false,
); );
static RegExp _errorMessageRegex = RegExp( static final RegExp _errorMessageRegex = RegExp(
r"aborted|error|failure|unexpected|failed|exception", r'aborted|error|failure|unexpected|failed|exception',
caseSensitive: false, caseSensitive: false,
multiLine: false, multiLine: false,
); );
static RegExp _restartedApplicationSuccessRegex = RegExp( static final RegExp _restartedApplicationSuccessRegex = RegExp(
r"Restarted application (.*)ms.", r'Restarted application (.*)ms.',
caseSensitive: false, caseSensitive: false,
multiLine: false, multiLine: false,
); );
Process _runningProcess; Process _runningProcess;
Stream<String> _processStdoutStream; Stream<String> _processStdoutStream;
List<StreamSubscription> _openSubscriptions = <StreamSubscription>[]; final List<StreamSubscription> _openSubscriptions = <StreamSubscription>[];
bool _buildApp = true; bool _buildApp = true;
bool _logFlutterProcessOutput = false; bool _logFlutterProcessOutput = false;
bool _verboseFlutterLogs = false; bool _verboseFlutterLogs = false;
@ -81,28 +81,28 @@ class FlutterRunProcessHandler extends ProcessHandler {
_buildApp = build; _buildApp = build;
} }
void setVerboseFluterlogs(bool verbose) { void setVerboseFlutterLogs(bool verbose) {
_verboseFlutterLogs = verbose; _verboseFlutterLogs = verbose;
} }
@override @override
Future<void> run() async { Future<void> run() async {
final arguments = ["run", "--target=$_appTarget"]; final arguments = ['run', '--target=$_appTarget'];
if (_buildApp == false) { if (_buildApp == false) {
arguments.add("--no-build"); arguments.add('--no-build');
} }
if (_buildFlavor != null && _buildFlavor.isNotEmpty) { if (_buildFlavor != null && _buildFlavor.isNotEmpty) {
arguments.add("--flavor=$_buildFlavor"); arguments.add('--flavor=$_buildFlavor');
} }
if (_deviceTargetId != null && _deviceTargetId.isNotEmpty) { if (_deviceTargetId != null && _deviceTargetId.isNotEmpty) {
arguments.add("--device-id=$_deviceTargetId"); arguments.add('--device-id=$_deviceTargetId');
} }
if (_verboseFlutterLogs) { if (_verboseFlutterLogs) {
arguments.add("--verbose"); arguments.add('--verbose');
} }
if (_logFlutterProcessOutput) { if (_logFlutterProcessOutput) {
@ -112,7 +112,7 @@ class FlutterRunProcessHandler extends ProcessHandler {
} }
_runningProcess = await Process.start( _runningProcess = await Process.start(
"flutter", 'flutter',
arguments, arguments,
workingDirectory: _workingDirectory, workingDirectory: _workingDirectory,
runInShell: true, runInShell: true,
@ -126,21 +126,21 @@ class FlutterRunProcessHandler extends ProcessHandler {
.where((event) => event.isNotEmpty) .where((event) => event.isNotEmpty)
.listen((event) { .listen((event) {
if (event.contains(_errorMessageRegex)) { if (event.contains(_errorMessageRegex)) {
stderr.writeln("${FAIL_COLOR}Flutter build error: $event$RESET_COLOR"); stderr.writeln('${FAIL_COLOR}Flutter build error: $event$RESET_COLOR');
} else { } else {
// This is most likely a depricated api usage warnings (from Gradle) and should not // This is most likely a depricated api usage warnings (from Gradle) and should not
// cause the test run to fail. // cause the test run to fail.
stdout.writeln("$WARN_COLOR$event$RESET_COLOR"); stdout.writeln('$WARN_COLOR$event$RESET_COLOR');
} }
})); }));
} }
@override @override
Future<int> terminate() async { Future<int> terminate() async {
int exitCode = -1; var exitCode = -1;
_ensureRunningProcess(); _ensureRunningProcess();
if (_runningProcess != null) { if (_runningProcess != null) {
_runningProcess.stdin.write("q"); _runningProcess.stdin.write('q');
_openSubscriptions.forEach((s) => s.cancel()); _openSubscriptions.forEach((s) => s.cancel());
_openSubscriptions.clear(); _openSubscriptions.clear();
exitCode = await _runningProcess.exitCode; exitCode = await _runningProcess.exitCode;
@ -152,10 +152,10 @@ class FlutterRunProcessHandler extends ProcessHandler {
Future<bool> restart({Duration timeout = const Duration(seconds: 90)}) async { Future<bool> restart({Duration timeout = const Duration(seconds: 90)}) async {
_ensureRunningProcess(); _ensureRunningProcess();
_runningProcess.stdin.write("R"); _runningProcess.stdin.write('R');
await _waitForStdOutMessage( await _waitForStdOutMessage(
_restartedApplicationSuccessRegex, _restartedApplicationSuccessRegex,
"Timeout waiting for app restart", 'Timeout waiting for app restart',
timeout, timeout,
); );
@ -171,7 +171,7 @@ class FlutterRunProcessHandler extends ProcessHandler {
]) async { ]) async {
currentObservatoryUri = await _waitForStdOutMessage( currentObservatoryUri = await _waitForStdOutMessage(
_observatoryDebuggerUriRegex, _observatoryDebuggerUriRegex,
"Timeout while waiting for observatory debugger uri", 'Timeout while waiting for observatory debugger uri',
timeout, timeout,
); );
@ -208,12 +208,12 @@ class FlutterRunProcessHandler extends ProcessHandler {
sub?.cancel(); sub?.cancel();
if (!completer.isCompleted) { if (!completer.isCompleted) {
stderr.writeln( stderr.writeln(
"${FAIL_COLOR}No connected devices found to run app on and tests against$RESET_COLOR"); '${FAIL_COLOR}No connected devices found to run app on and tests against$RESET_COLOR');
} }
} else if (_moreThanOneDeviceConnectedDeviceRegex.hasMatch(logLine)) { } else if (_moreThanOneDeviceConnectedDeviceRegex.hasMatch(logLine)) {
sub?.cancel(); sub?.cancel();
if (!completer.isCompleted) { if (!completer.isCompleted) {
stderr.writeln("$FAIL_COLOR$logLine$RESET_COLOR"); stderr.writeln('$FAIL_COLOR$logLine$RESET_COLOR');
} }
} }
}, },
@ -226,7 +226,7 @@ class FlutterRunProcessHandler extends ProcessHandler {
void _ensureRunningProcess() { void _ensureRunningProcess() {
if (_runningProcess == null) { if (_runningProcess == null) {
throw Exception( throw Exception(
"FlutterRunProcessHandler: flutter run process is not active"); 'FlutterRunProcessHandler: flutter run process is not active');
} }
} }
} }

View File

@ -24,15 +24,15 @@ class FlutterTestConfiguration extends TestConfiguration {
/// The target app to run the tests against /// The target app to run the tests against
/// Defaults to "lib/test_driver/app.dart" /// Defaults to "lib/test_driver/app.dart"
String targetAppPath = "lib/test_driver/app.dart"; String targetAppPath = 'lib/test_driver/app.dart';
/// Option to define the working directory for the process that runs the app under test (optional) /// Option to define the working directory for the process that runs the app under test (optional)
/// Handy if your app is seperated from your tests as flutter needs to be able to find a pubspec file /// Handy if your app is separated from your tests as flutter needs to be able to find a pubspec file
String targetAppWorkingDirecotry; String targetAppWorkingDirectory;
/// The build flavor to run the tests against (optional) /// The build flavor to run the tests against (optional)
/// Defaults to empty /// Defaults to empty
String buildFlavor = ""; String buildFlavor = '';
/// If the application should be built prior to running the tests /// If the application should be built prior to running the tests
/// Defaults to true /// Defaults to true
@ -40,7 +40,7 @@ class FlutterTestConfiguration extends TestConfiguration {
/// The target device id to run the tests against when multiple devices detected /// The target device id to run the tests against when multiple devices detected
/// Defaults to empty /// Defaults to empty
String targetDeviceId = ""; String targetDeviceId = '';
/// Logs Flutter process output to stdout /// Logs Flutter process output to stdout
/// The Flutter process is use to start and driver the app under test. /// The Flutter process is use to start and driver the app under test.
@ -64,7 +64,7 @@ class FlutterTestConfiguration extends TestConfiguration {
/// The maximum times the flutter driver can try and connect to the running app /// The maximum times the flutter driver can try and connect to the running app
/// Defaults to 3 /// Defaults to 3
int flutterDriverMaxConnectionAttemps = 3; int flutterDriverMaxConnectionAttempts = 3;
/// An observatory url that the test runner can connect to instead of creating a new running instance of the target application /// An observatory url that the test runner can connect to instead of creating a new running instance of the target application
/// Url takes the form of `http://127.0.0.1:51540/EM72VtRsUV0=/` and usually printed to stdout in the form `Connecting to service protocol: http://127.0.0.1:51540/EM72VtRsUV0=/` /// Url takes the form of `http://127.0.0.1:51540/EM72VtRsUV0=/` and usually printed to stdout in the form `Connecting to service protocol: http://127.0.0.1:51540/EM72VtRsUV0=/`
@ -84,7 +84,7 @@ class FlutterTestConfiguration extends TestConfiguration {
TestConfiguration config, TestConfiguration config,
FlutterWorld world, FlutterWorld world,
) async { ) async {
FlutterTestConfiguration flutterConfig = config as FlutterTestConfiguration; var flutterConfig = config as FlutterTestConfiguration;
world = world ?? FlutterWorld(); world = world ?? FlutterWorld();
final driver = await createFlutterDriver( final driver = await createFlutterDriver(
@ -116,6 +116,7 @@ class FlutterTestConfiguration extends TestConfiguration {
..addAll([ ..addAll([
ThenExpectElementToHaveValue(), ThenExpectElementToHaveValue(),
WhenTapWidget(), WhenTapWidget(),
WhenTapWidgetWithoutScroll(),
WhenTapBackButtonWidget(), WhenTapBackButtonWidget(),
GivenOpenDrawer(), GivenOpenDrawer(),
WhenPauseStep(), WhenPauseStep(),
@ -128,14 +129,14 @@ class FlutterTestConfiguration extends TestConfiguration {
Future<FlutterDriver> _attemptDriverConnection( Future<FlutterDriver> _attemptDriverConnection(
String dartVmServiceUrl, String dartVmServiceUrl,
int attempt, int attempt,
int maxAttemps, int maxAttempts,
) async { ) async {
try { try {
return await FlutterDriver.connect( return await FlutterDriver.connect(
dartVmServiceUrl: dartVmServiceUrl, dartVmServiceUrl: dartVmServiceUrl,
); );
} catch (e) { } catch (e) {
if (attempt > maxAttemps) { if (attempt > maxAttempts) {
rethrow; rethrow;
} else { } else {
print(e); print(e);
@ -144,7 +145,7 @@ class FlutterTestConfiguration extends TestConfiguration {
return _attemptDriverConnection( return _attemptDriverConnection(
dartVmServiceUrl, dartVmServiceUrl,
attempt + 1, attempt + 1,
maxAttemps, maxAttempts,
); );
} }
} }

View File

@ -69,10 +69,10 @@ class FlutterAppRunnerHook extends Hook {
} else { } else {
_flutterRunProcessHandler = FlutterRunProcessHandler() _flutterRunProcessHandler = FlutterRunProcessHandler()
..setLogFlutterProcessOutput(config.logFlutterProcessOutput) ..setLogFlutterProcessOutput(config.logFlutterProcessOutput)
..setVerboseFluterlogs(config.verboseFlutterProcessLogs) ..setVerboseFlutterLogs(config.verboseFlutterProcessLogs)
..setApplicationTargetFile(config.targetAppPath) ..setApplicationTargetFile(config.targetAppPath)
..setDriverConnectionDelay(config.flutterDriverReconnectionDelay) ..setDriverConnectionDelay(config.flutterDriverReconnectionDelay)
..setWorkingDirectory(config.targetAppWorkingDirecotry) ..setWorkingDirectory(config.targetAppWorkingDirectory)
..setBuildRequired(haveRunFirstScenario ? false : config.build) ..setBuildRequired(haveRunFirstScenario ? false : config.build)
..setBuildFlavor(config.buildFlavor) ..setBuildFlavor(config.buildFlavor)
..setDeviceTargetId(config.targetDeviceId); ..setDeviceTargetId(config.targetDeviceId);
@ -88,7 +88,7 @@ class FlutterAppRunnerHook extends Hook {
Future<void> _terminateApp() async { Future<void> _terminateApp() async {
if (_flutterRunProcessHandler != null) { if (_flutterRunProcessHandler != null) {
stdout.writeln("Terminating Flutter app under test"); stdout.writeln('Terminating Flutter app under test');
await _flutterRunProcessHandler.terminate(); await _flutterRunProcessHandler.terminate();
_flutterRunProcessHandler = null; _flutterRunProcessHandler = null;
} }
@ -96,7 +96,7 @@ class FlutterAppRunnerHook extends Hook {
Future<void> _restartApp() async { Future<void> _restartApp() async {
if (_flutterRunProcessHandler != null) { if (_flutterRunProcessHandler != null) {
stdout.writeln("Restarting Flutter app under test"); stdout.writeln('Restarting Flutter app under test');
await _flutterRunProcessHandler.restart(); await _flutterRunProcessHandler.restart();
} }
} }

View File

@ -4,6 +4,8 @@ import 'dart:io';
import 'package:gherkin/gherkin.dart'; import 'package:gherkin/gherkin.dart';
import 'package:flutter_driver/flutter_driver.dart'; import 'package:flutter_driver/flutter_driver.dart';
enum _FlutterDriverMessageLogLevel { info, warning, error }
/// The Flutter driver helpfully logs ALL messages to the stderr output /// The Flutter driver helpfully logs ALL messages to the stderr output
/// useless something is listening to the messages. /// useless something is listening to the messages.
/// This reporter listens to the messages from the driver so nothing is logged /// This reporter listens to the messages from the driver so nothing is logged
@ -15,7 +17,6 @@ class FlutterDriverReporter extends Reporter {
final bool logErrorMessages; final bool logErrorMessages;
final bool logWarningMessages; final bool logWarningMessages;
final bool logInfoMessages; final bool logInfoMessages;
final List<StreamSubscription> subscriptions = List<StreamSubscription>();
FlutterDriverReporter({ FlutterDriverReporter({
this.logErrorMessages = true, this.logErrorMessages = true,
@ -23,38 +24,37 @@ class FlutterDriverReporter extends Reporter {
this.logInfoMessages = false, this.logInfoMessages = false,
}); });
@override
Future<void> onTestRunStarted() async { Future<void> onTestRunStarted() async {
if (logInfoMessages) { driverLog = _driverLogMessageHandler;
subscriptions.add(flutterDriverLog
.where((log) => _isLevel(log.level, [LogLevel.info, LogLevel.trace]))
.listen((log) {
stdout.writeln(log.toString());
}));
}
if (logWarningMessages) {
subscriptions.add(flutterDriverLog
.where((log) => _isLevel(log.level, [LogLevel.warning]))
.listen((log) {
stdout.writeln(log.toString());
}));
}
if (logErrorMessages) {
subscriptions.add(flutterDriverLog
.where(
(log) => _isLevel(log.level, [LogLevel.critical, LogLevel.error]))
.listen((log) {
stderr.writeln(log.toString());
}));
}
} }
@override
Future<void> dispose() async { Future<void> dispose() async {
subscriptions.forEach((s) => s.cancel()); driverLog = null;
subscriptions.clear();
} }
bool _isLevel(LogLevel level, Iterable<LogLevel> levels) => void _driverLogMessageHandler(String source, String message) {
levels.contains(level); final level = _getMessageLevel(message);
final log = '$source $message';
if (logWarningMessages && level == _FlutterDriverMessageLogLevel.warning) {
stdout.writeln(log);
} else if (logErrorMessages &&
level == _FlutterDriverMessageLogLevel.error) {
stderr.writeln(log);
} else {
stdout.writeln(log);
}
}
_FlutterDriverMessageLogLevel _getMessageLevel(String message) {
if (message.toLowerCase().contains('warning')) {
return _FlutterDriverMessageLogLevel.warning;
} else if (message.toLowerCase().contains('error')) {
return _FlutterDriverMessageLogLevel.error;
} else {
return _FlutterDriverMessageLogLevel.info;
}
}
} }

View File

@ -10,22 +10,22 @@ import 'package:gherkin/gherkin.dart';
/// `Given I open the drawer` /// `Given I open the drawer`
class GivenOpenDrawer extends Given1WithWorld<String, FlutterWorld> { class GivenOpenDrawer extends Given1WithWorld<String, FlutterWorld> {
@override @override
RegExp get pattern => RegExp(r"I (open|close) the drawer"); RegExp get pattern => RegExp(r'I (open|close) the drawer');
@override @override
Future<void> executeStep(String action) async { Future<void> executeStep(String action) async {
final drawerFinder = find.byType("Drawer"); final drawerFinder = find.byType('Drawer');
final isOpen = final isOpen =
await FlutterDriverUtils.isPresent(drawerFinder, world.driver); await FlutterDriverUtils.isPresent(drawerFinder, world.driver);
// https://github.com/flutter/flutter/issues/9002#issuecomment-293660833 // https://github.com/flutter/flutter/issues/9002#issuecomment-293660833
if (isOpen && action == "close") { if (isOpen && action == 'close') {
// Swipe to the left across the whole app to close the drawer // Swipe to the left across the whole app to close the drawer
await world.driver await world.driver
.scroll(drawerFinder, -300.0, 0.0, const Duration(milliseconds: 300)); .scroll(drawerFinder, -300.0, 0.0, const Duration(milliseconds: 300));
} else if (!isOpen && action == "open") { } else if (!isOpen && action == 'open') {
await FlutterDriverUtils.tap( await FlutterDriverUtils.tap(
world.driver, world.driver,
find.byTooltip("Open navigation menu"), find.byTooltip('Open navigation menu'),
timeout: timeout, timeout: timeout,
); );
} }

View File

@ -11,5 +11,5 @@ class RestartAppStep extends WhenWithWorld<FlutterWorld> {
} }
@override @override
RegExp get pattern => RegExp(r"I restart the app"); RegExp get pattern => RegExp(r'I restart the app');
} }

View File

@ -16,7 +16,7 @@ import 'package:gherkin/gherkin.dart';
class ThenExpectElementToHaveValue class ThenExpectElementToHaveValue
extends Then2WithWorld<String, String, FlutterWorld> { extends Then2WithWorld<String, String, FlutterWorld> {
@override @override
RegExp get pattern => RegExp(r"I expect the {string} to be {string}$"); RegExp get pattern => RegExp(r'I expect the {string} to be {string}$');
@override @override
Future<void> executeStep(String key, String value) async { Future<void> executeStep(String key, String value) async {

View File

@ -16,7 +16,7 @@ class ThenExpectWidgetToBePresent
extends When2WithWorld<String, int, FlutterWorld> { extends When2WithWorld<String, int, FlutterWorld> {
@override @override
RegExp get pattern => RegExp( RegExp get pattern => RegExp(
r"I expect the (?:button|element|label|icon|field|text|widget|dialog|popup) {string} to be present within {int} second(s)$"); r'I expect the (?:button|element|label|icon|field|text|widget|dialog|popup) {string} to be present within {int} second(s)$');
@override @override
Future<void> executeStep(String key, int seconds) async { Future<void> executeStep(String key, int seconds) async {

View File

@ -22,5 +22,5 @@ class WhenFillFieldStep extends When2WithWorld<String, String, FlutterWorld> {
} }
@override @override
RegExp get pattern => RegExp(r"I fill the {string} field with {string}"); RegExp get pattern => RegExp(r'I fill the {string} field with {string}');
} }

View File

@ -18,5 +18,5 @@ class WhenPauseStep extends When1<int> {
} }
@override @override
RegExp get pattern => RegExp(r"I pause for {int} second(s)"); RegExp get pattern => RegExp(r'I pause for {int} second(s)');
} }

View File

@ -12,7 +12,7 @@ import 'package:gherkin/gherkin.dart';
/// `When I tap the back widget"` /// `When I tap the back widget"`
class WhenTapBackButtonWidget extends WhenWithWorld<FlutterWorld> { class WhenTapBackButtonWidget extends WhenWithWorld<FlutterWorld> {
@override @override
RegExp get pattern => RegExp(r"I tap the back (?:button|element|widget)$"); RegExp get pattern => RegExp(r'I tap the back (?:button|element|widget)$');
@override @override
Future<void> executeStep() async { Future<void> executeStep() async {

View File

@ -20,7 +20,7 @@ import 'package:gherkin/gherkin.dart';
class WhenTapWidget extends When1WithWorld<String, FlutterWorld> { class WhenTapWidget extends When1WithWorld<String, FlutterWorld> {
@override @override
RegExp get pattern => RegExp( RegExp get pattern => RegExp(
r"I tap the {string} (?:button|element|label|icon|field|text|widget)$"); r'I tap the {string} (?:button|element|label|icon|field|text|widget)$');
@override @override
Future<void> executeStep(String key) async { Future<void> executeStep(String key) async {
@ -37,3 +37,20 @@ class WhenTapWidget extends When1WithWorld<String, FlutterWorld> {
); );
} }
} }
class WhenTapWidgetWithoutScroll extends When1WithWorld<String, FlutterWorld> {
@override
RegExp get pattern => RegExp(
r'I tap the {string} (?:button|element|label|icon|field|text|widget) without scrolling it into view$');
@override
Future<void> executeStep(String key) async {
final finder = find.byValueKey(key);
await FlutterDriverUtils.tap(
world.driver,
finder,
timeout: timeout * .45,
);
}
}

View File

@ -75,15 +75,15 @@ class FlutterDriverUtils {
/// condition does not return true with the timeout period. /// condition does not return true with the timeout period.
static Future<void> waitUntil( static Future<void> waitUntil(
FlutterDriver driver, FlutterDriver driver,
Future<bool> condition(), { Future<bool> Function() condition, {
Duration timeout = const Duration(seconds: 10), Duration timeout = const Duration(seconds: 10),
Duration pollInterval = const Duration(milliseconds: 500), Duration pollInterval = const Duration(milliseconds: 500),
}) async { }) async {
return Future.microtask(() async { return Future.microtask(() async {
final completer = Completer<void>(); final completer = Completer<void>();
int maxAttempts = var maxAttempts =
(timeout.inMilliseconds / pollInterval.inMilliseconds).round(); (timeout.inMilliseconds / pollInterval.inMilliseconds).round();
int attempts = 0; var attempts = 0;
while (attempts < maxAttempts) { while (attempts < maxAttempts) {
final result = await condition(); final result = await condition();

View File

@ -7,56 +7,56 @@ packages:
name: _fe_analyzer_shared name: _fe_analyzer_shared
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.3" version: "3.0.0"
analyzer: analyzer:
dependency: transitive dependency: transitive
description: description:
name: analyzer name: analyzer
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.39.4" version: "0.39.8"
archive: archive:
dependency: transitive dependency: transitive
description: description:
name: archive name: archive
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.11" version: "2.0.13"
args: args:
dependency: transitive dependency: transitive
description: description:
name: args name: args
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.5.2" version: "1.6.0"
async: async:
dependency: transitive dependency: transitive
description: description:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.4.0" version: "2.4.1"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
name: boolean_selector name: boolean_selector
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.5" version: "2.0.0"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
name: charcode name: charcode
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.2" version: "1.1.3"
collection: collection:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.14.11" version: "1.14.12"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -77,7 +77,7 @@ packages:
name: crypto name: crypto
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.3" version: "2.1.4"
csslib: csslib:
dependency: transitive dependency: transitive
description: description:
@ -139,7 +139,7 @@ packages:
name: http name: http
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.0+4" version: "0.12.1"
http_multi_server: http_multi_server:
dependency: transitive dependency: transitive
description: description:
@ -160,21 +160,21 @@ packages:
name: image name: image
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.4" version: "2.1.12"
intl: intl:
dependency: transitive dependency: transitive
description: description:
name: intl name: intl
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.16.0" version: "0.16.1"
io: io:
dependency: transitive dependency: transitive
description: description:
name: io name: io
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.3.3" version: "0.3.4"
js: js:
dependency: transitive dependency: transitive
description: description:
@ -237,7 +237,7 @@ packages:
name: node_io name: node_io
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1+2" version: "1.1.0"
node_preamble: node_preamble:
dependency: transitive dependency: transitive
description: description:
@ -251,14 +251,7 @@ packages:
name: package_config name: package_config
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.9.2" version: "1.9.3"
package_resolver:
dependency: transitive
description:
name: package_resolver
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.10"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -272,7 +265,7 @@ packages:
name: pedantic name: pedantic
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0+1" version: "1.9.0"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
@ -307,14 +300,14 @@ packages:
name: pub_semver name: pub_semver
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.4.2" version: "1.4.4"
quiver: quiver:
dependency: transitive dependency: transitive
description: description:
name: quiver name: quiver
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.5" version: "2.1.3"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
@ -328,7 +321,7 @@ packages:
name: shelf_packages_handler name: shelf_packages_handler
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.4" version: "2.0.0"
shelf_static: shelf_static:
dependency: transitive dependency: transitive
description: description:
@ -354,7 +347,7 @@ packages:
name: source_map_stack_trace name: source_map_stack_trace
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.5" version: "2.0.0"
source_maps: source_maps:
dependency: transitive dependency: transitive
description: description:
@ -368,7 +361,7 @@ packages:
name: source_span name: source_span
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.5.5" version: "1.7.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -390,6 +383,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.5" version: "1.0.5"
sync_http:
dependency: transitive
description:
name: sync_http
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@ -403,21 +403,21 @@ packages:
name: test name: test
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.9.4" version: "1.14.3"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.11" version: "0.2.15"
test_core: test_core:
dependency: transitive dependency: transitive
description: description:
name: test_core name: test_core
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.15" version: "0.3.4"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -438,7 +438,7 @@ packages:
name: vm_service name: vm_service
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.3.1" version: "4.0.2"
vm_service_client: vm_service_client:
dependency: transitive dependency: transitive
description: description:
@ -452,7 +452,7 @@ packages:
name: watcher name: watcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.9.7+14" version: "0.9.7+15"
web_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:
@ -460,19 +460,34 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
webdriver:
dependency: transitive
description:
name: webdriver
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
webkit_inspection_protocol:
dependency: transitive
description:
name: webkit_inspection_protocol
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.3"
xml: xml:
dependency: transitive dependency: transitive
description: description:
name: xml name: xml
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.5.0" version: "3.6.1"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:
name: yaml name: yaml
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.0" version: "2.2.1"
sdks: sdks:
dart: ">=2.7.0 <3.0.0" dart: ">=2.7.0 <3.0.0"
flutter: ">=1.13.0"

View File

@ -1,10 +1,11 @@
name: flutter_gherkin name: flutter_gherkin
description: A Gherkin / Cucumber parser and test runner for Dart and Flutter description: A Gherkin / Cucumber parser and test runner for Dart and Flutter
version: 1.1.7+7 version: 1.1.8
homepage: https://github.com/jonsamwell/flutter_gherkin homepage: https://github.com/jonsamwell/flutter_gherkin
environment: environment:
sdk: ">=2.1.0 <3.0.0" sdk: ">=2.6.0 <3.0.0"
flutter: ">=1.13.0"
dependencies: dependencies:
flutter: flutter:

View File

@ -13,9 +13,9 @@ import 'package:test/test.dart';
import 'mocks/step_definition_mock.dart'; import 'mocks/step_definition_mock.dart';
void main() { void main() {
group("config", () { group('config', () {
group("prepare", () { group('prepare', () {
test("flutter app runner hook added", () { test('flutter app runner hook added', () {
final config = FlutterTestConfiguration(); final config = FlutterTestConfiguration();
expect(config.hooks, isNull); expect(config.hooks, isNull);
config.prepare(); config.prepare();
@ -24,36 +24,38 @@ void main() {
expect(config.hooks.elementAt(0), (x) => x is FlutterAppRunnerHook); expect(config.hooks.elementAt(0), (x) => x is FlutterAppRunnerHook);
}); });
test("common steps definition added", () { test('common steps definition added', () {
final config = FlutterTestConfiguration(); final config = FlutterTestConfiguration();
expect(config.stepDefinitions, isNull); expect(config.stepDefinitions, isNull);
config.prepare(); config.prepare();
expect(config.stepDefinitions, isNotNull); expect(config.stepDefinitions, isNotNull);
expect(config.stepDefinitions.length, 8); expect(config.stepDefinitions.length, 9);
expect(config.stepDefinitions.elementAt(0), expect(config.stepDefinitions.elementAt(0),
(x) => x is ThenExpectElementToHaveValue); (x) => x is ThenExpectElementToHaveValue);
expect(config.stepDefinitions.elementAt(1), (x) => x is WhenTapWidget); expect(config.stepDefinitions.elementAt(1), (x) => x is WhenTapWidget);
expect(config.stepDefinitions.elementAt(2), expect(config.stepDefinitions.elementAt(2),
(x) => x is WhenTapWidgetWithoutScroll);
expect(config.stepDefinitions.elementAt(3),
(x) => x is WhenTapBackButtonWidget); (x) => x is WhenTapBackButtonWidget);
expect( expect(
config.stepDefinitions.elementAt(3), (x) => x is GivenOpenDrawer); config.stepDefinitions.elementAt(4), (x) => x is GivenOpenDrawer);
expect(config.stepDefinitions.elementAt(4), (x) => x is WhenPauseStep); expect(config.stepDefinitions.elementAt(5), (x) => x is WhenPauseStep);
expect( expect(
config.stepDefinitions.elementAt(5), (x) => x is WhenFillFieldStep); config.stepDefinitions.elementAt(6), (x) => x is WhenFillFieldStep);
expect(config.stepDefinitions.elementAt(6), expect(config.stepDefinitions.elementAt(7),
(x) => x is ThenExpectWidgetToBePresent); (x) => x is ThenExpectWidgetToBePresent);
expect(config.stepDefinitions.elementAt(7), (x) => x is RestartAppStep); expect(config.stepDefinitions.elementAt(8), (x) => x is RestartAppStep);
}); });
test("common step definition added to existing steps", () { test('common step definition added to existing steps', () {
final config = FlutterTestConfiguration() final config = FlutterTestConfiguration()
..stepDefinitions = [MockStepDefinition()]; ..stepDefinitions = [MockStepDefinition()];
expect(config.stepDefinitions.length, 1); expect(config.stepDefinitions.length, 1);
config.prepare(); config.prepare();
expect(config.stepDefinitions, isNotNull); expect(config.stepDefinitions, isNotNull);
expect(config.stepDefinitions.length, 9); expect(config.stepDefinitions.length, 10);
expect(config.stepDefinitions.elementAt(0), expect(config.stepDefinitions.elementAt(0),
(x) => x is MockStepDefinition); (x) => x is MockStepDefinition);
expect(config.stepDefinitions.elementAt(1), expect(config.stepDefinitions.elementAt(1),

View File

@ -1,6 +1,6 @@
import 'package:gherkin/gherkin.dart'; import 'package:gherkin/gherkin.dart';
typedef Future<void> OnRunCode(Iterable parameters); typedef OnRunCode = Future<void> Function(Iterable parameters);
class MockStepDefinition extends StepDefinitionBase<World> { class MockStepDefinition extends StepDefinitionBase<World> {
bool hasRun = false; bool hasRun = false;