feat(steps): update lib to use new step function syntax
This commit is contained in:
parent
b2cef2e4ec
commit
9ba831a695
|
@ -1,3 +1,6 @@
|
|||
## [1.1.8+3] - 19/07/2020
|
||||
* Updated Gherkin library version to allow for function step step implementation; updated docs to match.
|
||||
|
||||
## [1.1.8+2] - 11/05/2020
|
||||
* Fixed issue where the connection attempt of Flutter driver would not retry before throwing a connection error. This was causing an error on some machines trying to connect to an Android emulator (x86 & x86_64) that runs the googleapis (see https://github.com/flutter/flutter/issues/42433)
|
||||
* Added a before `onBeforeFlutterDriverConnect` and after `onAfterFlutterDriverConnect` Flutter driver connection method property to the test configuration `FlutterTestConfiguration` to enable custom logic before and after a driver connection attempt.
|
||||
|
|
157
README.md
157
README.md
|
@ -104,7 +104,6 @@ void main() {
|
|||
// are interested in testing.
|
||||
runApp(MyApp());
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
All this code does is enable the Flutter driver extension which is required to be able to automate the app and then runs your application.
|
||||
|
@ -134,30 +133,26 @@ import 'package:flutter_driver/flutter_driver.dart';
|
|||
import 'package:flutter_gherkin/flutter_gherkin.dart';
|
||||
import 'package:gherkin/gherkin.dart';
|
||||
|
||||
class TapButtonNTimesStep extends When2WithWorld<String, int, FlutterWorld> {
|
||||
TapButtonNTimesStep()
|
||||
: super(StepDefinitionConfiguration()..timeout = Duration(seconds: 10));
|
||||
|
||||
@override
|
||||
Future<void> executeStep(String input1, int input2) async {
|
||||
final locator = find.byValueKey(input1);
|
||||
for (var i = 0; i < input2; i += 1) {
|
||||
await FlutterDriverUtils.tap(world.driver, locator, timeout: timeout);
|
||||
StepDefinitionGeneric TapButtonNTimesStep() {
|
||||
return when2<String, int, FlutterWorld>(
|
||||
'I tap the {string} button {int} times',
|
||||
(key, count, context) async {
|
||||
final locator = find.byValueKey(key);
|
||||
for (var i = 0; i < count; i += 1) {
|
||||
await FlutterDriverUtils.tap(context.world.driver, locator);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
RegExp get pattern => RegExp(r"I tap the {string} button {int} times");
|
||||
},
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
As you can see the class inherits from `When2WithWorld` and specifies the types of the two input parameters. The third type `FlutterWorld` is a special Flutter context object that allow access to the Flutter driver instance within the step. If you did not need this you could inherit from `When2` which does not type the world context object but still provides two input parameters.
|
||||
As you can see the `when2` method is invoked specifying two input parameters. The third type `FlutterWorld` is a special world context object that allow access from the context object to the Flutter driver that allows you to interact with your app. If you did not need a custom world object or strongly typed parameters you can omit the type arguments completely.
|
||||
|
||||
The input parameters are retrieved via the pattern regex from well know parameter types `{string}` and `{int}` [explained below](#well-known-step-parameters). They are just special syntax to indicate you are expecting a string and an integer at those points in the step text. Therefore, when the step to execute is `When I tap the "increment" button 10 times` the parameters "increment" and 10 will be passed into the step as the correct types. Note that in the pattern you can use any regex capture group to indicate any input parameter. For example the regex ` ` ` RegExp(r"When I tap the {string} (button|icon) {int} times") ` ` ` indicates 3 parameters and would match to either of the below step text.
|
||||
|
||||
``` dart
|
||||
When I tap the "increment" button 10 times // passes 3 parameters "increment", "button" & 10
|
||||
When I tap the "increment" icon 2 times // passes 3 parameters "increment", "icon" & 2
|
||||
When I tap the "plus" icon 2 times // passes 3 parameters "plus", "icon" & 2
|
||||
```
|
||||
|
||||
It is worth noting that this library *does not* rely on mirrors (reflection) for many reasons but most prominently for ease of maintenance and to fall inline with the principles of Flutter not allowing reflection. All in all this make for a much easier to understand and maintain code base as well as much easier debugging for the user. The downside is that we have to be slightly more explicit by providing instances of custom code such as step definition, hook, reporters and custom parameters.
|
||||
|
@ -226,13 +221,11 @@ An infix boolean expression which defines the features and scenarios to run base
|
|||
#### order
|
||||
|
||||
Defaults to `ExecutionOrder.random`
|
||||
|
||||
The order by which scenarios will be run. Running an a random order may highlight any inter-test dependencies that should be fixed.
|
||||
|
||||
#### stepDefinitions
|
||||
|
||||
Defaults to `Iterable<StepDefinitionBase>`
|
||||
|
||||
Place instances of any custom step definition classes `Given` , `Then` , `When` , `And` , `But` that match to any custom steps defined in your feature files.
|
||||
|
||||
``` dart
|
||||
|
@ -253,13 +246,11 @@ Future<void> main() {
|
|||
..exitAfterTestRun = true; // set to false if debugging to exit cleanly
|
||||
return GherkinRunner().execute(config);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### defaultLanguage
|
||||
|
||||
Defaults to `en`
|
||||
|
||||
This specifies the default language the feature files are written in. See https://cucumber.io/docs/gherkin/reference/#overview for supported languages.
|
||||
|
||||
Note that this can be overridden in the feature itself by the use of a language block.
|
||||
|
@ -333,7 +324,7 @@ Attachment are pieces of data you can attach to a running scenario. This could
|
|||
|
||||
Attachments would typically be attached via a `Hook` for example `onAfterStep` .
|
||||
|
||||
```
|
||||
``` dart
|
||||
import 'package:gherkin/gherkin.dart';
|
||||
|
||||
class AttachScreenshotOnFailedStepHook extends Hook {
|
||||
|
@ -346,14 +337,13 @@ class AttachScreenshotOnFailedStepHook extends Hook {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
##### screenshot
|
||||
|
||||
To take a screenshot on a step failing you can used the pre-defined hook `AttachScreenshotOnFailedStepHook` and include it in the hook configuration of the tests config. This hook will take a screenshot and add it as an attachment to the scenario. If the `JsonReporter` is being used the screenshot will be embedded in the report which can be used to generate a HTML report which will ultimately display the screenshot under the failed step.
|
||||
|
||||
```
|
||||
``` dart
|
||||
import 'dart:async';
|
||||
import 'package:flutter_gherkin/flutter_gherkin.dart';
|
||||
import 'package:gherkin/gherkin.dart';
|
||||
|
@ -443,13 +433,11 @@ Future<void> main() {
|
|||
#### logFlutterProcessOutput
|
||||
|
||||
Defaults to `false`
|
||||
|
||||
If `true` the output from the flutter process is logged to the stdout / stderr streams. Useful when debugging app build or start failures
|
||||
|
||||
#### flutterBuildTimeout
|
||||
|
||||
Defaults to `90 seconds`
|
||||
|
||||
Specifies the period of time to wait for the Flutter build to complete and the app to be installed and in a state to be tested. Slower machines may need longer than the default 90 seconds to complete this process.
|
||||
|
||||
#### onBeforeFlutterDriverConnect
|
||||
|
@ -463,19 +451,16 @@ An async method that is called after a successful attempt by Flutter driver to c
|
|||
#### flutterDriverMaxConnectionAttempts
|
||||
|
||||
Defaults to `3`
|
||||
|
||||
Specifies the number of Flutter driver connection attempts to a running app before the test is aborted
|
||||
|
||||
#### flutterDriverReconnectionDelay
|
||||
|
||||
Defaults to `2 seconds`
|
||||
|
||||
Specifies the amount of time to wait after a failed Flutter driver connection attempt to the running app
|
||||
|
||||
#### exitAfterTestRun
|
||||
|
||||
Defaults to `true`
|
||||
|
||||
True to exit the program after all tests have run. You may want to set this to false during debugging.
|
||||
|
||||
### Flutter specific configuration options
|
||||
|
@ -491,13 +476,11 @@ To avoid tests starting on an app changed by a previous test it is suggested tha
|
|||
#### targetAppPath
|
||||
|
||||
Defaults to `lib/test_driver/app.dart`
|
||||
|
||||
This should point to the *testable* application that enables the Flutter driver extensions and thus is able to be automated. This application wil be started when the test run is started and restarted if the `restartAppBetweenScenarios` configuration property is set to true.
|
||||
|
||||
#### build
|
||||
|
||||
Defaults to `true`
|
||||
|
||||
This optional argument lets you specify if the target application should be built prior to running the first test. This defaults to `true`
|
||||
|
||||
#### buildFlavor
|
||||
|
@ -548,14 +531,13 @@ Would be implemented like so:
|
|||
``` dart
|
||||
import 'package:gherkin/gherkin.dart';
|
||||
|
||||
class GivenWellKnownUserIsLoggedIn extends Given1<String> {
|
||||
@override
|
||||
Future<void> executeStep(String wellKnownUsername) async {
|
||||
StepDefinitionGeneric GivenWellKnownUserIsLoggedIn() {
|
||||
return given1(
|
||||
RegExp(r'(Bob|Mary|Emma|Jon) has logged in'),
|
||||
(wellKnownUsername, context) async {
|
||||
// implement your code
|
||||
}
|
||||
|
||||
@override
|
||||
RegExp get pattern => RegExp(r"(Bob|Mary|Emma|Jon) has logged in");
|
||||
},
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -579,16 +561,15 @@ Would be implemented like so:
|
|||
``` dart
|
||||
import 'package:gherkin/gherkin.dart';
|
||||
|
||||
class ThenExpectAppleCount extends Then1<int> {
|
||||
@override
|
||||
Future<void> executeStep(int count) async {
|
||||
StepDefinitionGeneric ThenExpectAppleCount() {
|
||||
return then1(
|
||||
'I expect {int} apple(s)',
|
||||
(count, context) async {
|
||||
// example code
|
||||
final actualCount = await _getActualCount();
|
||||
expectMatch(actualCount, count);
|
||||
}
|
||||
|
||||
@override
|
||||
RegExp get pattern => RegExp(r"I expect {int} apple(s)");
|
||||
context.expectMatch(actualCount, count);
|
||||
},
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -602,23 +583,19 @@ For example, the below sets the step's timeout to 10 seconds.
|
|||
|
||||
``` dart
|
||||
import 'package:flutter_driver/flutter_driver.dart';
|
||||
import 'package:gherkin/gherkin.dart';
|
||||
import 'package:flutter_gherkin/flutter_gherkin.dart';
|
||||
import 'package:gherkin/gherkin.dart';
|
||||
|
||||
class TapButtonNTimesStep extends When2WithWorld<String, int, FlutterWorld> {
|
||||
TapButtonNTimesStep()
|
||||
: super(StepDefinitionConfiguration()..timeout = Duration(seconds: 10));
|
||||
|
||||
@override
|
||||
Future<void> executeStep(String input1, int input2) async {
|
||||
final locator = find.byValueKey(input1);
|
||||
for (var i = 0; i < input2; i += 1) {
|
||||
await world.driver.tap(locator, timeout: timeout);
|
||||
StepDefinitionGeneric TapButtonNTimesStep() {
|
||||
return given2<String, int, FlutterWorld>(
|
||||
'I tap the {string} button {int} times',
|
||||
(key, count, context) async {
|
||||
final locator = find.byValueKey(key);
|
||||
for (var i = 0; i < count; i += 1) {
|
||||
await FlutterDriverUtils.tap(context.world.driver, locator);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
RegExp get pattern => RegExp(r"I tap the {string} button {int} times");
|
||||
},
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -648,16 +625,14 @@ The matching step definition would then be:
|
|||
``` dart
|
||||
import 'package:gherkin/gherkin.dart';
|
||||
|
||||
class GivenIProvideAComment extends Given2<String, String> {
|
||||
@override
|
||||
Future<void> executeStep(String commentType, String comment) async {
|
||||
// TODO: implement executeStep
|
||||
}
|
||||
|
||||
@override
|
||||
RegExp get pattern => RegExp(r"I provide the following {string} comment");
|
||||
StepDefinitionGeneric GivenTheMultiLineComment() {
|
||||
return given1(
|
||||
'I provide the following {string} comment',
|
||||
(comment, context) async {
|
||||
// implement step
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Data tables
|
||||
|
@ -669,14 +644,15 @@ import 'package:gherkin/gherkin.dart';
|
|||
///
|
||||
/// For example:
|
||||
///
|
||||
/// `Given I add the users`
|
||||
/// `When I add the users`
|
||||
/// | Firstname | Surname | Age | Gender |
|
||||
/// | Woody | Johnson | 28 | Male |
|
||||
/// | Edith | Summers | 23 | Female |
|
||||
/// | Megan | Hill | 83 | Female |
|
||||
class GivenIAddTheUsers extends Given1<Table> {
|
||||
@override
|
||||
Future<void> executeStep(Table dataTable) async {
|
||||
StepDefinitionGeneric WhenIAddTheUsers() {
|
||||
return when1(
|
||||
'I add the users',
|
||||
(Table dataTable, context) async {
|
||||
for (var row in dataTable.rows) {
|
||||
// do something with row
|
||||
row.columns.forEach((columnValue) => print(columnValue));
|
||||
|
@ -687,10 +663,8 @@ class GivenIAddTheUsers extends Given1<Table> {
|
|||
final personOne = columns.elementAt(0);
|
||||
final personOneName = personOne["Firstname"];
|
||||
print('Name of first user: `$personOneName` ');
|
||||
}
|
||||
|
||||
@override
|
||||
RegExp get pattern => RegExp(r"I add the users");
|
||||
},
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -747,14 +721,13 @@ The step definition would then use this custom parameter like so:
|
|||
import 'package:gherkin/gherkin.dart';
|
||||
import 'colour_parameter.dart';
|
||||
|
||||
class GivenIPickAColour extends Given1<Colour> {
|
||||
@override
|
||||
Future<void> executeStep(Colour input1) async {
|
||||
print("The picked colour was: '$input1'");
|
||||
}
|
||||
|
||||
@override
|
||||
RegExp get pattern => RegExp(r"I pick the colour {colour}");
|
||||
StepDefinitionGeneric GivenIAddTheUsers() {
|
||||
return given1<Colour>(
|
||||
'I pick the colour {colour}',
|
||||
(colour, _) async {
|
||||
print("The picked colour was: '$colour'");
|
||||
},
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -770,18 +743,13 @@ Tags are a great way of organizing your features and marking them with filterabl
|
|||
|
||||
You can filter the scenarios by providing a tag expression to your configuration file. Tag expression are simple infix expressions such as:
|
||||
|
||||
`@smoke`
|
||||
|
||||
`@smoke and @perf`
|
||||
|
||||
`@billing or @onboarding`
|
||||
|
||||
`@smoke and not @ignore`
|
||||
|
||||
`@smoke`
|
||||
`@smoke and @perf`
|
||||
`@billing or @onboarding`
|
||||
`@smoke and not @ignore`
|
||||
You can even us brackets to ensure the order of precedence
|
||||
|
||||
`@smoke and not (@ignore or @todo)`
|
||||
|
||||
`@smoke and not (@ignore or @todo)`
|
||||
You can use the usual boolean statement "and", "or", "not"
|
||||
|
||||
Also see <https://docs.cucumber.io/cucumber/api/#tags>
|
||||
|
@ -929,7 +897,6 @@ You can create your own custom reporter by inheriting from the base `Reporter` c
|
|||
* `onException`
|
||||
* `message`
|
||||
* `dispose`
|
||||
|
||||
Once you have created your custom reporter don't forget to add it to the `reporters` configuration file property.
|
||||
|
||||
*Note*: PR's of new reporters are *always* welcome.
|
||||
|
|
|
@ -8,26 +8,20 @@ import 'steps/given_I_pick_a_colour_step.dart';
|
|||
import 'steps/tap_button_n_times_step.dart';
|
||||
|
||||
Future<void> main() {
|
||||
final config = FlutterTestConfiguration()
|
||||
..features = [Glob('features//**.feature')]
|
||||
..reporters = [
|
||||
ProgressReporter(),
|
||||
TestRunSummaryReporter(),
|
||||
JsonReporter(path: './report.json'),
|
||||
FlutterDriverReporter(
|
||||
logErrorMessages: true,
|
||||
logInfoMessages: true,
|
||||
logWarningMessages: true,
|
||||
),
|
||||
] // you can include the "StdoutReporter()" without the message level parameter for verbose log information
|
||||
final steps = [
|
||||
TapButtonNTimesStep(),
|
||||
GivenIPickAColour(),
|
||||
];
|
||||
|
||||
final config = FlutterTestConfiguration.DEFAULT(
|
||||
steps,
|
||||
featurePath: 'features//**.feature',
|
||||
targetAppPath: 'test_driver/app.dart',
|
||||
)
|
||||
..hooks = [
|
||||
HookExample(),
|
||||
// AttachScreenshotOnFailedStepHook(), // takes a screenshot of each step failure and attaches it to the world object
|
||||
]
|
||||
..stepDefinitions = [
|
||||
TapButtonNTimesStep(),
|
||||
GivenIPickAColour(),
|
||||
]
|
||||
..customStepParameterDefinitions = [
|
||||
ColourParameter(),
|
||||
]
|
||||
|
|
|
@ -2,8 +2,10 @@ Feature: Startup
|
|||
|
||||
Scenario: should increment counter
|
||||
Given I expect the "counter" to be "0"
|
||||
#! profile "action speed"
|
||||
When I tap the "increment" button
|
||||
And I tap the "increment" button
|
||||
#! end
|
||||
Then I expect the "counter" to be "2"
|
||||
|
||||
Scenario: counter should reset when app is restarted
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
Feature: Counter
|
||||
The counter should be incremented when the button is pressed.
|
||||
|
||||
@smoke
|
||||
Scenario: Counter increases when the button is pressed
|
||||
Given I pick the colour red
|
||||
Given I expect the "counter" to be "0"
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
Fonctionnalité: Counter
|
||||
The counter should be incremented when the button is pressed.
|
||||
|
||||
@smoke
|
||||
Scénario: Counter increases when the button is pressed
|
||||
Etant donné que I pick the colour red
|
||||
Et I expect the "counter" to be "0"
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,6 +1,6 @@
|
|||
import 'package:gherkin/gherkin.dart';
|
||||
|
||||
/// This step expects a multiline string proceeding it
|
||||
/// This step expects a data table
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
|
@ -9,16 +9,14 @@ import 'package:gherkin/gherkin.dart';
|
|||
/// | Woody | Johnson | 28 | Male |
|
||||
/// | Edith | Summers | 23 | Female |
|
||||
/// | Megan | Hill | 83 | Female |
|
||||
class GivenIAddTheUsers extends Given1<Table> {
|
||||
@override
|
||||
Future<void> executeStep(Table dataTable) async {
|
||||
// implement executeStep
|
||||
StepDefinitionGeneric WhenIAddTheUsers() {
|
||||
return when1(
|
||||
'I add the users',
|
||||
(Table dataTable, context) async {
|
||||
for (var row in dataTable.rows) {
|
||||
// do something with row
|
||||
row.columns.forEach((columnValue) => print(columnValue));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
RegExp get pattern => RegExp(r'I add the users');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import 'package:gherkin/gherkin.dart';
|
||||
import 'colour_parameter.dart';
|
||||
|
||||
class GivenIPickAColour extends Given1<Colour> {
|
||||
@override
|
||||
Future<void> executeStep(Colour input1) async {
|
||||
print("The picked colour was: '$input1'");
|
||||
}
|
||||
|
||||
@override
|
||||
RegExp get pattern => RegExp(r'I pick the colour {colour}');
|
||||
StepDefinitionGeneric GivenIPickAColour() {
|
||||
return given1(
|
||||
'I pick the colour {colour}',
|
||||
(Colour colour, _) async {
|
||||
print("The picked colour was: '$colour'");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:gherkin/gherkin.dart';
|
||||
|
||||
/// This step expects a multiline string proceeding it
|
||||
/// This step expects a multi-line string proceeding it
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
|
@ -8,12 +8,11 @@ import 'package:gherkin/gherkin.dart';
|
|||
/// """
|
||||
/// Some comment
|
||||
/// """
|
||||
class GivenIProvideAComment extends Given2<String, String> {
|
||||
@override
|
||||
Future<void> executeStep(String commentType, String comment) async {
|
||||
// implement executeStep
|
||||
}
|
||||
|
||||
@override
|
||||
RegExp get pattern => RegExp(r'I provide the following {string} comment');
|
||||
StepDefinitionGeneric GivenMultiLineString() {
|
||||
return given2(
|
||||
'I provide the following {string} comment',
|
||||
(commentType, comment, _) async {
|
||||
// implement step
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,18 +2,14 @@ import 'package:flutter_driver/flutter_driver.dart';
|
|||
import 'package:flutter_gherkin/flutter_gherkin.dart';
|
||||
import 'package:gherkin/gherkin.dart';
|
||||
|
||||
class TapButtonNTimesStep extends When2WithWorld<String, int, FlutterWorld> {
|
||||
TapButtonNTimesStep()
|
||||
: super(StepDefinitionConfiguration()..timeout = Duration(seconds: 10));
|
||||
|
||||
@override
|
||||
Future<void> executeStep(String input1, int input2) async {
|
||||
final locator = find.byValueKey(input1);
|
||||
for (var i = 0; i < input2; i += 1) {
|
||||
await FlutterDriverUtils.tap(world.driver, locator, timeout: timeout);
|
||||
StepDefinitionGeneric TapButtonNTimesStep() {
|
||||
return given2<String, int, FlutterWorld>(
|
||||
'I tap the {string} button {int} times',
|
||||
(key, count, context) async {
|
||||
final locator = find.byValueKey(key);
|
||||
for (var i = 0; i < count; i += 1) {
|
||||
await FlutterDriverUtils.tap(context.world.driver, locator);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
RegExp get pattern => RegExp(r'I tap the {string} button {int} times');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -187,7 +187,7 @@ class FlutterRunProcessHandler extends ProcessHandler {
|
|||
final completer = Completer<String>();
|
||||
StreamSubscription sub;
|
||||
sub = _processStdoutStream.timeout(
|
||||
timeout,
|
||||
timeout ?? const Duration(seconds: 90),
|
||||
onTimeout: (_) {
|
||||
sub?.cancel();
|
||||
if (!completer.isCompleted) {
|
||||
|
|
|
@ -12,12 +12,39 @@ import 'package:flutter_gherkin/src/flutter/steps/when_tap_widget_step.dart';
|
|||
import 'package:flutter_gherkin/src/flutter/steps/when_tap_the_back_button_step.dart';
|
||||
import 'package:flutter_driver/flutter_driver.dart';
|
||||
import 'package:gherkin/gherkin.dart';
|
||||
import 'package:glob/glob.dart';
|
||||
|
||||
import 'steps/then_expect_widget_to_be_present_step.dart';
|
||||
|
||||
class FlutterTestConfiguration extends TestConfiguration {
|
||||
String _observatoryDebuggerUri;
|
||||
|
||||
/// Provide a configuration object with default settings such as the reports and feature file location
|
||||
/// Additional setting on the configuration object can be set on the returned instance.
|
||||
static FlutterTestConfiguration DEFAULT(
|
||||
Iterable<StepDefinitionGeneric<World>> steps, {
|
||||
String featurePath = 'test_driver/features/**.feature',
|
||||
String targetAppPath = 'test_driver/app.dart',
|
||||
}) {
|
||||
return FlutterTestConfiguration()
|
||||
..features = [Glob(featurePath)]
|
||||
..reporters = [
|
||||
StdoutReporter(MessageLevel.error),
|
||||
ProgressReporter(),
|
||||
TestRunSummaryReporter(),
|
||||
JsonReporter(path: './report.json'),
|
||||
FlutterDriverReporter(
|
||||
logErrorMessages: true,
|
||||
logInfoMessages: false,
|
||||
logWarningMessages: false,
|
||||
),
|
||||
]
|
||||
..targetAppPath = targetAppPath
|
||||
..stepDefinitions = steps
|
||||
..restartAppBetweenScenarios = true
|
||||
..exitAfterTestRun = true;
|
||||
}
|
||||
|
||||
/// restarts the application under test between each scenario.
|
||||
/// Defaults to true to avoid the application being in an invalid state
|
||||
/// before each test
|
||||
|
|
|
@ -13,7 +13,7 @@ class FlutterWorld extends World {
|
|||
_driver = flutterDriver;
|
||||
}
|
||||
|
||||
void setFlutterProccessHandler(
|
||||
void setFlutterProcessHandler(
|
||||
FlutterRunProcessHandler flutterRunProcessHandler) {
|
||||
_flutterRunProcessHandler = flutterRunProcessHandler;
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ class FlutterAppRunnerHook extends Hook {
|
|||
Iterable<Tag> tags,
|
||||
) async {
|
||||
if (world is FlutterWorld) {
|
||||
world.setFlutterProccessHandler(_flutterRunProcessHandler);
|
||||
world.setFlutterProcessHandler(_flutterRunProcessHandler);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,26 +8,25 @@ import 'package:gherkin/gherkin.dart';
|
|||
/// Examples:
|
||||
///
|
||||
/// `Given I open the drawer`
|
||||
class GivenOpenDrawer extends Given1WithWorld<String, FlutterWorld> {
|
||||
@override
|
||||
RegExp get pattern => RegExp(r'I (open|close) the drawer');
|
||||
|
||||
@override
|
||||
Future<void> executeStep(String action) async {
|
||||
StepDefinitionGeneric GivenOpenDrawer() {
|
||||
return given1<String, FlutterWorld>(
|
||||
RegExp(r'I (open|close) the drawer'),
|
||||
(action, context) async {
|
||||
final drawerFinder = find.byType('Drawer');
|
||||
final isOpen =
|
||||
await FlutterDriverUtils.isPresent(world.driver, drawerFinder);
|
||||
final isOpen = await FlutterDriverUtils.isPresent(
|
||||
context.world.driver, drawerFinder);
|
||||
// https://github.com/flutter/flutter/issues/9002#issuecomment-293660833
|
||||
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));
|
||||
await context.world.driver.scroll(
|
||||
drawerFinder, -300.0, 0.0, const Duration(milliseconds: 300));
|
||||
} else if (!isOpen && action == 'open') {
|
||||
await FlutterDriverUtils.tap(
|
||||
world.driver,
|
||||
context.world.driver,
|
||||
find.byTooltip('Open navigation menu'),
|
||||
timeout: timeout,
|
||||
timeout: context.configuration?.timeout,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
import 'package:flutter_gherkin/flutter_gherkin.dart';
|
||||
import 'package:gherkin/gherkin.dart';
|
||||
|
||||
class RestartAppStep extends WhenWithWorld<FlutterWorld> {
|
||||
RestartAppStep()
|
||||
: super(StepDefinitionConfiguration()..timeout = Duration(seconds: 60));
|
||||
|
||||
@override
|
||||
Future<void> executeStep() async {
|
||||
await world.restartApp(timeout: timeout);
|
||||
}
|
||||
|
||||
@override
|
||||
RegExp get pattern => RegExp(r'I restart the app');
|
||||
StepDefinitionGeneric RestartAppStep() {
|
||||
return given<FlutterWorld>(
|
||||
'I restart the app',
|
||||
(context) async {
|
||||
await context.world.restartApp(
|
||||
timeout: context.configuration?.timeout,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -13,22 +13,20 @@ import 'package:gherkin/gherkin.dart';
|
|||
///
|
||||
/// `Then I expect the "controlKey" to be "Hello World"`
|
||||
/// `And I expect the "controlKey" to be "Hello World"`
|
||||
class ThenExpectElementToHaveValue
|
||||
extends Then2WithWorld<String, String, FlutterWorld> {
|
||||
@override
|
||||
RegExp get pattern => RegExp(r'I expect the {string} to be {string}$');
|
||||
|
||||
@override
|
||||
Future<void> executeStep(String key, String value) async {
|
||||
StepDefinitionGeneric ThenExpectElementToHaveValue() {
|
||||
return given2<String, String, FlutterWorld>(
|
||||
RegExp(r'I expect the {string} to be {string}$'),
|
||||
(key, value, context) async {
|
||||
try {
|
||||
final text = await FlutterDriverUtils.getText(
|
||||
world.driver, find.byValueKey(key),
|
||||
timeout: timeout * .9);
|
||||
expect(text, value);
|
||||
context.world.driver,
|
||||
find.byValueKey(key),
|
||||
);
|
||||
context.expect(text, value);
|
||||
} catch (e) {
|
||||
await reporter.message(
|
||||
"Step error '${pattern.pattern}': $e", MessageLevel.error);
|
||||
await context.reporter.message('Step error: $e', MessageLevel.error);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12,19 +12,17 @@ import 'package:gherkin/gherkin.dart';
|
|||
///
|
||||
/// `Then I expect the widget 'notification' to be present within 10 seconds`
|
||||
/// `Then I expect the button 'save' to be present within 1 second`
|
||||
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)$');
|
||||
|
||||
@override
|
||||
Future<void> executeStep(String key, int seconds) async {
|
||||
StepDefinitionGeneric ThenExpectWidgetToBePresent() {
|
||||
return given2<String, int, FlutterWorld>(
|
||||
RegExp(
|
||||
r'I expect the (?:button|element|label|icon|field|text|widget|dialog|popup) {string} to be present within {int} second(s)$'),
|
||||
(key, seconds, context) async {
|
||||
final isPresent = await FlutterDriverUtils.isPresent(
|
||||
world.driver,
|
||||
context.world.driver,
|
||||
find.byValueKey(key),
|
||||
timeout: Duration(seconds: seconds),
|
||||
);
|
||||
expect(isPresent, true);
|
||||
}
|
||||
context.expect(isPresent, true);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,19 +8,17 @@ import 'package:gherkin/gherkin.dart';
|
|||
/// Examples:
|
||||
/// Then I fill the "email" field with "bob@gmail.com"
|
||||
/// Then I fill the "name" field with "Woody Johnson"
|
||||
class WhenFillFieldStep extends When2WithWorld<String, String, FlutterWorld> {
|
||||
@override
|
||||
Future<void> executeStep(String key, String input2) async {
|
||||
StepDefinitionGeneric WhenFillFieldStep() {
|
||||
return given2<String, String, FlutterWorld>(
|
||||
'I fill the {string} field with {string}',
|
||||
(key, value, context) async {
|
||||
final finder = find.byValueKey(key);
|
||||
await world.driver.scrollIntoView(finder);
|
||||
await context.world.driver.scrollIntoView(finder);
|
||||
await FlutterDriverUtils.enterText(
|
||||
world.driver,
|
||||
context.world.driver,
|
||||
finder,
|
||||
input2,
|
||||
timeout: timeout * .9,
|
||||
value,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
RegExp get pattern => RegExp(r'I fill the {string} field with {string}');
|
||||
}
|
||||
|
|
|
@ -7,16 +7,11 @@ import 'package:gherkin/gherkin.dart';
|
|||
/// Examples:
|
||||
/// When I pause for 10 seconds
|
||||
/// When I pause for 120 seconds
|
||||
class WhenPauseStep extends When1<int> {
|
||||
WhenPauseStep()
|
||||
: super(StepDefinitionConfiguration()
|
||||
..timeout = const Duration(minutes: 5));
|
||||
|
||||
@override
|
||||
Future<void> executeStep(int seconds) async {
|
||||
await Future.delayed(Duration(seconds: seconds));
|
||||
}
|
||||
|
||||
@override
|
||||
RegExp get pattern => RegExp(r'I pause for {int} second(s)');
|
||||
StepDefinitionGeneric WhenPauseStep() {
|
||||
return when1(
|
||||
'I pause for {int} second(s)',
|
||||
(wait, _) async {
|
||||
await Future.delayed(Duration(seconds: wait));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,16 +10,14 @@ import 'package:gherkin/gherkin.dart';
|
|||
/// `When I tap the back button"`
|
||||
/// `When I tap the back element"`
|
||||
/// `When I tap the back widget"`
|
||||
class WhenTapBackButtonWidget extends WhenWithWorld<FlutterWorld> {
|
||||
@override
|
||||
RegExp get pattern => RegExp(r'I tap the back (?:button|element|widget)$');
|
||||
|
||||
@override
|
||||
Future<void> executeStep() async {
|
||||
StepDefinitionGeneric WhenTapBackButtonWidget() {
|
||||
return when1<String, FlutterWorld>(
|
||||
RegExp(r'I tap the back (?:button|element|widget)$'),
|
||||
(_, context) async {
|
||||
await FlutterDriverUtils.tap(
|
||||
world.driver,
|
||||
context.world.driver,
|
||||
find.pageBack(),
|
||||
timeout: timeout * .9,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -17,40 +17,35 @@ import 'package:gherkin/gherkin.dart';
|
|||
/// `When I tap "controlKey" field"`
|
||||
/// `When I tap "controlKey" text"`
|
||||
/// `When I tap "controlKey" widget"`
|
||||
class WhenTapWidget extends When1WithWorld<String, FlutterWorld> {
|
||||
@override
|
||||
RegExp get pattern => RegExp(
|
||||
r'I tap the {string} (?:button|element|label|icon|field|text|widget)$');
|
||||
|
||||
@override
|
||||
Future<void> executeStep(String key) async {
|
||||
StepDefinitionGeneric WhenTapWidget() {
|
||||
return when1<String, FlutterWorld>(
|
||||
RegExp(
|
||||
r'I tap the {string} (?:button|element|label|icon|field|text|widget)$'),
|
||||
(key, context) async {
|
||||
final finder = find.byValueKey(key);
|
||||
|
||||
await world.driver.scrollIntoView(
|
||||
await context.world.driver.scrollIntoView(
|
||||
finder,
|
||||
timeout: timeout * .45,
|
||||
);
|
||||
await FlutterDriverUtils.tap(
|
||||
world.driver,
|
||||
context.world.driver,
|
||||
finder,
|
||||
timeout: timeout * .45,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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 {
|
||||
StepDefinitionGeneric WhenTapWidgetWithoutScroll() {
|
||||
return when1<String, FlutterWorld>(
|
||||
RegExp(
|
||||
r'I tap the {string} (?:button|element|label|icon|field|text|widget) without scrolling it into view$'),
|
||||
(key, context) async {
|
||||
final finder = find.byValueKey(key);
|
||||
|
||||
await FlutterDriverUtils.tap(
|
||||
world.driver,
|
||||
context.world.driver,
|
||||
finder,
|
||||
timeout: timeout * .45,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
26
pubspec.lock
26
pubspec.lock
|
@ -7,14 +7,14 @@ packages:
|
|||
name: _fe_analyzer_shared
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
version: "5.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.39.8"
|
||||
version: "0.39.13"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -70,7 +70,7 @@ packages:
|
|||
name: coverage
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.13.9"
|
||||
version: "0.13.11"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -118,7 +118,7 @@ packages:
|
|||
name: gherkin
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.8+1"
|
||||
version: "1.1.8+2"
|
||||
glob:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -139,7 +139,7 @@ packages:
|
|||
name: http
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.12.1"
|
||||
version: "0.12.2"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -181,7 +181,7 @@ packages:
|
|||
name: js
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.6.1+1"
|
||||
version: "0.6.2"
|
||||
json_rpc_2:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -230,21 +230,21 @@ packages:
|
|||
name: node_interop
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
version: "1.1.1"
|
||||
node_io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: node_io
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
node_preamble:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: node_preamble
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.8"
|
||||
version: "1.4.12"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -314,7 +314,7 @@ packages:
|
|||
name: shelf
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.7.5"
|
||||
version: "0.7.7"
|
||||
shelf_packages_handler:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -403,7 +403,7 @@ packages:
|
|||
name: test
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.14.3"
|
||||
version: "1.14.4"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -438,7 +438,7 @@ packages:
|
|||
name: vm_service
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.0.3"
|
||||
version: "4.1.0"
|
||||
vm_service_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -473,7 +473,7 @@ packages:
|
|||
name: webkit_inspection_protocol
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.5.3"
|
||||
version: "0.7.3"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
name: flutter_gherkin
|
||||
description: A Gherkin / Cucumber parser and test runner for Dart and Flutter
|
||||
version: 1.1.8+2
|
||||
version: 1.1.8+3
|
||||
homepage: https://github.com/jonsamwell/flutter_gherkin
|
||||
|
||||
environment:
|
||||
|
@ -16,7 +16,7 @@ dependencies:
|
|||
sdk: flutter
|
||||
glob: ^1.1.7
|
||||
meta: ">=1.1.6 <2.0.0"
|
||||
gherkin: ^1.1.8+1
|
||||
gherkin: ^1.1.8+2
|
||||
# gherkin:
|
||||
# path: ../dart_gherkin
|
||||
|
||||
|
|
|
@ -1,13 +1,5 @@
|
|||
import 'package:flutter_gherkin/flutter_gherkin.dart';
|
||||
import 'package:flutter_gherkin/src/flutter/hooks/app_runner_hook.dart';
|
||||
import 'package:flutter_gherkin/src/flutter/steps/given_i_open_the_drawer_step.dart';
|
||||
import 'package:flutter_gherkin/src/flutter/steps/restart_app_step.dart';
|
||||
import 'package:flutter_gherkin/src/flutter/steps/then_expect_element_to_have_value_step.dart';
|
||||
import 'package:flutter_gherkin/src/flutter/steps/then_expect_widget_to_be_present_step.dart';
|
||||
import 'package:flutter_gherkin/src/flutter/steps/when_fill_field_step.dart';
|
||||
import 'package:flutter_gherkin/src/flutter/steps/when_pause_step.dart';
|
||||
import 'package:flutter_gherkin/src/flutter/steps/when_tap_the_back_button_step.dart';
|
||||
import 'package:flutter_gherkin/src/flutter/steps/when_tap_widget_step.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'mocks/step_definition_mock.dart';
|
||||
|
@ -31,21 +23,6 @@ void main() {
|
|||
config.prepare();
|
||||
expect(config.stepDefinitions, isNotNull);
|
||||
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(4), (x) => x is GivenOpenDrawer);
|
||||
expect(config.stepDefinitions.elementAt(5), (x) => x is WhenPauseStep);
|
||||
expect(
|
||||
config.stepDefinitions.elementAt(6), (x) => x is WhenFillFieldStep);
|
||||
expect(config.stepDefinitions.elementAt(7),
|
||||
(x) => x is ThenExpectWidgetToBePresent);
|
||||
expect(config.stepDefinitions.elementAt(8), (x) => x is RestartAppStep);
|
||||
});
|
||||
|
||||
test('common step definition added to existing steps', () {
|
||||
|
@ -58,8 +35,6 @@ void main() {
|
|||
expect(config.stepDefinitions.length, 10);
|
||||
expect(config.stepDefinitions.elementAt(0),
|
||||
(x) => x is MockStepDefinition);
|
||||
expect(config.stepDefinitions.elementAt(1),
|
||||
(x) => x is ThenExpectElementToHaveValue);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue