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
* 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.
## [1.1.8] - 08/05/2020
* 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
* 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)
## [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
* 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
* 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 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
## [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
## [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
* 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
* Fixed lint warnings

View File

@ -981,7 +981,7 @@ After which the file will most likely look like this
#### 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.

View File

@ -5,6 +5,7 @@ export "FLUTTER_APPLICATION_PATH=C:\github\flutter_gherkin\example"
export "FLUTTER_TARGET=lib\main.dart"
export "FLUTTER_BUILD_DIR=build"
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_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1"

View File

@ -37,7 +37,7 @@ class _MyHomePageState extends State<MyHomePage> {
title: Text(widget.title),
),
drawer: Drawer(
key: const Key("drawer"),
key: const Key('drawer'),
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
@ -81,7 +81,7 @@ class _MyHomePageState extends State<MyHomePage> {
// to identify this specific Widget from inside our test suite and
// read the text.
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() {
final config = FlutterTestConfiguration()
..features = [Glob(r"features/**.feature")]
..features = [Glob('features//**.feature')]
..reporters = [
ProgressReporter(),
TestRunSummaryReporter(),
@ -31,17 +31,18 @@ Future<void> main() {
..customStepParameterDefinitions = [
ColourParameter(),
]
..restartAppBetweenScenarios = false
..targetAppWorkingDirecotry = "../"
..targetAppPath = "test_driver/app.dart"
..restartAppBetweenScenarios = true
..targetAppWorkingDirectory = '../'
..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
// ..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
// ..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
// ..flutterBuildTimeout = Duration(minutes: 3) // uncomment to change the default period that flutter is expected to build and start the app within
..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
// ..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
..exitAfterTestRun = true; // set to false if debugging to exit cleanly
return GherkinRunner().execute(config);
}

View File

@ -9,13 +9,13 @@ class HookExample extends Hook {
/// Run before any scenario in a test run have executed
@override
Future<void> onBeforeRun(TestConfiguration config) async {
print("before run hook");
print('before run hook');
}
/// Run after all scenarios in a test run have completed
@override
Future<void> onAfterRun(TestConfiguration config) async {
print("after run hook");
print('after run hook');
}
/// 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> {
ColourParameter()
: super("colour", RegExp(r"(red|green|blue)", caseSensitive: true), (c) {
: super('colour', RegExp(r'(red|green|blue)', caseSensitive: true), (c) {
switch (c.toLowerCase()) {
case "red":
case 'red':
return Colour.red;
case "green":
case 'green':
return Colour.green;
case "blue":
case 'blue':
default:
return Colour.blue;
}

View File

@ -20,5 +20,5 @@ class GivenIAddTheUsers extends Given1<Table> {
}
@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
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
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
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;
// Flutter specific implementations
export "src/flutter/flutter_world.dart";
export "src/flutter/flutter_test_configuration.dart";
export "src/flutter/utils/driver_utils.dart";
export 'src/flutter/flutter_world.dart';
export 'src/flutter/flutter_test_configuration.dart';
export 'src/flutter/utils/driver_utils.dart';
// Well known steps
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';
class FlutterRunProcessHandler extends ProcessHandler {
static const String FAIL_COLOR = "\u001b[33;31m"; // red
static const String WARN_COLOR = "\u001b[33;10m"; // yellow
static const String RESET_COLOR = "\u001b[33;0m";
static const String FAIL_COLOR = '\u001b[33;31m'; // red
static const String WARN_COLOR = '\u001b[33;10m'; // yellow
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
// `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(
r"observatory (?:debugger|url) .* available .*[:]? (http[s]?:.*\/).*",
static final RegExp _observatoryDebuggerUriRegex = RegExp(
r'observatory (?:debugger|url) .* available .*[:]? (http[s]?:.*\/).*',
caseSensitive: false,
multiLine: false,
);
static RegExp _noConnectedDeviceRegex = RegExp(
r"no connected device|no supported devices connected",
static final RegExp _noConnectedDeviceRegex = RegExp(
r'no connected device|no supported devices connected',
caseSensitive: false,
multiLine: false,
);
static RegExp _moreThanOneDeviceConnectedDeviceRegex = RegExp(
r"more than one device connected",
static final RegExp _moreThanOneDeviceConnectedDeviceRegex = RegExp(
r'more than one device connected',
caseSensitive: false,
multiLine: false,
);
static RegExp _errorMessageRegex = RegExp(
r"aborted|error|failure|unexpected|failed|exception",
static final RegExp _errorMessageRegex = RegExp(
r'aborted|error|failure|unexpected|failed|exception',
caseSensitive: false,
multiLine: false,
);
static RegExp _restartedApplicationSuccessRegex = RegExp(
r"Restarted application (.*)ms.",
static final RegExp _restartedApplicationSuccessRegex = RegExp(
r'Restarted application (.*)ms.',
caseSensitive: false,
multiLine: false,
);
Process _runningProcess;
Stream<String> _processStdoutStream;
List<StreamSubscription> _openSubscriptions = <StreamSubscription>[];
final List<StreamSubscription> _openSubscriptions = <StreamSubscription>[];
bool _buildApp = true;
bool _logFlutterProcessOutput = false;
bool _verboseFlutterLogs = false;
@ -81,28 +81,28 @@ class FlutterRunProcessHandler extends ProcessHandler {
_buildApp = build;
}
void setVerboseFluterlogs(bool verbose) {
void setVerboseFlutterLogs(bool verbose) {
_verboseFlutterLogs = verbose;
}
@override
Future<void> run() async {
final arguments = ["run", "--target=$_appTarget"];
final arguments = ['run', '--target=$_appTarget'];
if (_buildApp == false) {
arguments.add("--no-build");
arguments.add('--no-build');
}
if (_buildFlavor != null && _buildFlavor.isNotEmpty) {
arguments.add("--flavor=$_buildFlavor");
arguments.add('--flavor=$_buildFlavor');
}
if (_deviceTargetId != null && _deviceTargetId.isNotEmpty) {
arguments.add("--device-id=$_deviceTargetId");
arguments.add('--device-id=$_deviceTargetId');
}
if (_verboseFlutterLogs) {
arguments.add("--verbose");
arguments.add('--verbose');
}
if (_logFlutterProcessOutput) {
@ -112,7 +112,7 @@ class FlutterRunProcessHandler extends ProcessHandler {
}
_runningProcess = await Process.start(
"flutter",
'flutter',
arguments,
workingDirectory: _workingDirectory,
runInShell: true,
@ -126,21 +126,21 @@ class FlutterRunProcessHandler extends ProcessHandler {
.where((event) => event.isNotEmpty)
.listen((event) {
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 {
// This is most likely a depricated api usage warnings (from Gradle) and should not
// cause the test run to fail.
stdout.writeln("$WARN_COLOR$event$RESET_COLOR");
stdout.writeln('$WARN_COLOR$event$RESET_COLOR');
}
}));
}
@override
Future<int> terminate() async {
int exitCode = -1;
var exitCode = -1;
_ensureRunningProcess();
if (_runningProcess != null) {
_runningProcess.stdin.write("q");
_runningProcess.stdin.write('q');
_openSubscriptions.forEach((s) => s.cancel());
_openSubscriptions.clear();
exitCode = await _runningProcess.exitCode;
@ -152,10 +152,10 @@ class FlutterRunProcessHandler extends ProcessHandler {
Future<bool> restart({Duration timeout = const Duration(seconds: 90)}) async {
_ensureRunningProcess();
_runningProcess.stdin.write("R");
_runningProcess.stdin.write('R');
await _waitForStdOutMessage(
_restartedApplicationSuccessRegex,
"Timeout waiting for app restart",
'Timeout waiting for app restart',
timeout,
);
@ -171,7 +171,7 @@ class FlutterRunProcessHandler extends ProcessHandler {
]) async {
currentObservatoryUri = await _waitForStdOutMessage(
_observatoryDebuggerUriRegex,
"Timeout while waiting for observatory debugger uri",
'Timeout while waiting for observatory debugger uri',
timeout,
);
@ -208,12 +208,12 @@ class FlutterRunProcessHandler extends ProcessHandler {
sub?.cancel();
if (!completer.isCompleted) {
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)) {
sub?.cancel();
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() {
if (_runningProcess == null) {
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
/// 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)
/// Handy if your app is seperated from your tests as flutter needs to be able to find a pubspec file
String targetAppWorkingDirecotry;
/// Handy if your app is separated from your tests as flutter needs to be able to find a pubspec file
String targetAppWorkingDirectory;
/// The build flavor to run the tests against (optional)
/// Defaults to empty
String buildFlavor = "";
String buildFlavor = '';
/// If the application should be built prior to running the tests
/// Defaults to true
@ -40,7 +40,7 @@ class FlutterTestConfiguration extends TestConfiguration {
/// The target device id to run the tests against when multiple devices detected
/// Defaults to empty
String targetDeviceId = "";
String targetDeviceId = '';
/// Logs Flutter process output to stdout
/// 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
/// 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
/// 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,
FlutterWorld world,
) async {
FlutterTestConfiguration flutterConfig = config as FlutterTestConfiguration;
var flutterConfig = config as FlutterTestConfiguration;
world = world ?? FlutterWorld();
final driver = await createFlutterDriver(
@ -116,6 +116,7 @@ class FlutterTestConfiguration extends TestConfiguration {
..addAll([
ThenExpectElementToHaveValue(),
WhenTapWidget(),
WhenTapWidgetWithoutScroll(),
WhenTapBackButtonWidget(),
GivenOpenDrawer(),
WhenPauseStep(),
@ -128,14 +129,14 @@ class FlutterTestConfiguration extends TestConfiguration {
Future<FlutterDriver> _attemptDriverConnection(
String dartVmServiceUrl,
int attempt,
int maxAttemps,
int maxAttempts,
) async {
try {
return await FlutterDriver.connect(
dartVmServiceUrl: dartVmServiceUrl,
);
} catch (e) {
if (attempt > maxAttemps) {
if (attempt > maxAttempts) {
rethrow;
} else {
print(e);
@ -144,7 +145,7 @@ class FlutterTestConfiguration extends TestConfiguration {
return _attemptDriverConnection(
dartVmServiceUrl,
attempt + 1,
maxAttemps,
maxAttempts,
);
}
}

View File

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

View File

@ -4,6 +4,8 @@ import 'dart:io';
import 'package:gherkin/gherkin.dart';
import 'package:flutter_driver/flutter_driver.dart';
enum _FlutterDriverMessageLogLevel { info, warning, error }
/// The Flutter driver helpfully logs ALL messages to the stderr output
/// useless something is listening to the messages.
/// 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 logWarningMessages;
final bool logInfoMessages;
final List<StreamSubscription> subscriptions = List<StreamSubscription>();
FlutterDriverReporter({
this.logErrorMessages = true,
@ -23,38 +24,37 @@ class FlutterDriverReporter extends Reporter {
this.logInfoMessages = false,
});
@override
Future<void> onTestRunStarted() async {
if (logInfoMessages) {
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());
}));
}
driverLog = _driverLogMessageHandler;
}
@override
Future<void> dispose() async {
subscriptions.forEach((s) => s.cancel());
subscriptions.clear();
driverLog = null;
}
bool _isLevel(LogLevel level, Iterable<LogLevel> levels) =>
levels.contains(level);
void _driverLogMessageHandler(String source, String message) {
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`
class GivenOpenDrawer extends Given1WithWorld<String, FlutterWorld> {
@override
RegExp get pattern => RegExp(r"I (open|close) the drawer");
RegExp get pattern => RegExp(r'I (open|close) the drawer');
@override
Future<void> executeStep(String action) async {
final drawerFinder = find.byType("Drawer");
final drawerFinder = find.byType('Drawer');
final isOpen =
await FlutterDriverUtils.isPresent(drawerFinder, world.driver);
// 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
await world.driver
.scroll(drawerFinder, -300.0, 0.0, const Duration(milliseconds: 300));
} else if (!isOpen && action == "open") {
} else if (!isOpen && action == 'open') {
await FlutterDriverUtils.tap(
world.driver,
find.byTooltip("Open navigation menu"),
find.byTooltip('Open navigation menu'),
timeout: timeout,
);
}

View File

@ -11,5 +11,5 @@ class RestartAppStep extends WhenWithWorld<FlutterWorld> {
}
@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
extends Then2WithWorld<String, String, FlutterWorld> {
@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
Future<void> executeStep(String key, String value) async {

View File

@ -16,7 +16,7 @@ class ThenExpectWidgetToBePresent
extends When2WithWorld<String, int, FlutterWorld> {
@override
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
Future<void> executeStep(String key, int seconds) async {

View File

@ -22,5 +22,5 @@ class WhenFillFieldStep extends When2WithWorld<String, String, FlutterWorld> {
}
@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
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"`
class WhenTapBackButtonWidget extends WhenWithWorld<FlutterWorld> {
@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
Future<void> executeStep() async {

View File

@ -20,7 +20,7 @@ import 'package:gherkin/gherkin.dart';
class WhenTapWidget extends When1WithWorld<String, FlutterWorld> {
@override
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
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.
static Future<void> waitUntil(
FlutterDriver driver,
Future<bool> condition(), {
Future<bool> Function() condition, {
Duration timeout = const Duration(seconds: 10),
Duration pollInterval = const Duration(milliseconds: 500),
}) async {
return Future.microtask(() async {
final completer = Completer<void>();
int maxAttempts =
var maxAttempts =
(timeout.inMilliseconds / pollInterval.inMilliseconds).round();
int attempts = 0;
var attempts = 0;
while (attempts < maxAttempts) {
final result = await condition();

View File

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

View File

@ -1,10 +1,11 @@
name: flutter_gherkin
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
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.6.0 <3.0.0"
flutter: ">=1.13.0"
dependencies:
flutter:

View File

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

View File

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