From 9ba831a695d2d75108212b2feab74d001c0dfa49 Mon Sep 17 00:00:00 2001 From: Jon Samwell Date: Sun, 19 Jul 2020 11:28:52 +1000 Subject: [PATCH] feat(steps): update lib to use new step function syntax --- CHANGELOG.md | 3 + README.md | 239 ++++++++---------- example/test_driver/app_test.dart | 26 +- example/test_driver/features/counter.feature | 2 + .../features/counter_increases.feature | 1 - .../features/counter_increases_french.feature | 1 - example/test_driver/report.json | 2 +- .../steps/data_table_example_step.dart | 24 +- .../steps/given_I_pick_a_colour_step.dart | 15 +- .../steps/multiline_string_example_step.dart | 17 +- .../steps/tap_button_n_times_step.dart | 24 +- .../flutter/flutter_run_process_handler.dart | 2 +- .../flutter/flutter_test_configuration.dart | 27 ++ lib/src/flutter/flutter_world.dart | 2 +- lib/src/flutter/hooks/app_runner_hook.dart | 2 +- .../steps/given_i_open_the_drawer_step.dart | 43 ++-- lib/src/flutter/steps/restart_app_step.dart | 20 +- ...hen_expect_element_to_have_value_step.dart | 34 ++- ...then_expect_widget_to_be_present_step.dart | 28 +- .../flutter/steps/when_fill_field_step.dart | 28 +- lib/src/flutter/steps/when_pause_step.dart | 19 +- .../steps/when_tap_the_back_button_step.dart | 22 +- .../flutter/steps/when_tap_widget_step.dart | 59 ++--- pubspec.lock | 26 +- pubspec.yaml | 4 +- test/flutter_configuration_test.dart | 25 -- 26 files changed, 316 insertions(+), 379 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 249e100..a21c8ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/README.md b/README.md index 7238795..126d837 100644 --- a/README.md +++ b/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 { - TapButtonNTimesStep() - : super(StepDefinitionConfiguration()..timeout = Duration(seconds: 10)); - - @override - Future 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); - } - } - - @override - RegExp get pattern => RegExp(r"I tap the {string} button {int} times"); +StepDefinitionGeneric TapButtonNTimesStep() { + return when2( + '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); + } + }, + ); } ``` -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. +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. @@ -225,14 +220,12 @@ An infix boolean expression which defines the features and scenarios to run base #### order -Defaults to `ExecutionOrder.random` - +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` - +Defaults to `Iterable` 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 main() { ..exitAfterTestRun = true; // set to false if debugging to exit cleanly return GherkinRunner().execute(config); } - ``` #### defaultLanguage -Defaults to `en` - +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'; @@ -442,14 +432,12 @@ Future main() { #### logFlutterProcessOutput -Defaults to `false` - +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` - +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 @@ -462,20 +450,17 @@ An async method that is called after a successful attempt by Flutter driver to c #### flutterDriverMaxConnectionAttempts -Defaults to `3` - +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` - +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` - +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 @@ -490,15 +475,13 @@ To avoid tests starting on an app changed by a previous test it is suggested tha #### targetAppPath -Defaults to `lib/test_driver/app.dart` - +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` +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 @@ -515,7 +498,7 @@ This optional argument lets you specify device target id as `flutter run --devic #### runningAppProtocolEndpointUri An observatory url that the test runner can connect to instead of creating a new running instance of the target application -The 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=/` +The 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=/` You will have to add the `--verbose` flag to the command to start your flutter app to see this output and ensure `enableFlutterDriverExtension()` is called by the running app ## Features Files @@ -537,7 +520,7 @@ However, the domain language you choose will influence what keyword works best i `Given` steps are used to describe the initial state of a system. The execution of a `Given` step will usually put the system into well defined state. -To implement a `Given` step you can inherit from the ` ` ` Given ` ` ` class. +To implement a `Given` step you can inherit from the ` ` ` Given ` ` ` class. ``` dart Given Bob has logged in @@ -548,14 +531,13 @@ Would be implemented like so: ``` dart import 'package:gherkin/gherkin.dart'; -class GivenWellKnownUserIsLoggedIn extends Given1 { - @override - Future executeStep(String wellKnownUsername) async { - // implement your code - } - - @override - RegExp get pattern => RegExp(r"(Bob|Mary|Emma|Jon) has logged in"); +StepDefinitionGeneric GivenWellKnownUserIsLoggedIn() { + return given1( + RegExp(r'(Bob|Mary|Emma|Jon) has logged in'), + (wellKnownUsername, context) async { + // implement your code + }, + ); } ``` @@ -579,16 +561,15 @@ Would be implemented like so: ``` dart import 'package:gherkin/gherkin.dart'; -class ThenExpectAppleCount extends Then1 { - @override - Future executeStep(int count) async { - // example code - final actualCount = await _getActualCount(); - expectMatch(actualCount, count); - } - - @override - RegExp get pattern => RegExp(r"I expect {int} apple(s)"); +StepDefinitionGeneric ThenExpectAppleCount() { + return then1( + 'I expect {int} apple(s)', + (count, context) async { + // example code + final actualCount = await _getActualCount(); + 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 { - TapButtonNTimesStep() - : super(StepDefinitionConfiguration()..timeout = Duration(seconds: 10)); - - @override - Future 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); - } - } - - @override - RegExp get pattern => RegExp(r"I tap the {string} button {int} times"); +StepDefinitionGeneric TapButtonNTimesStep() { + return given2( + '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); + } + }, + ); } ``` @@ -648,16 +625,14 @@ The matching step definition would then be: ``` dart import 'package:gherkin/gherkin.dart'; -class GivenIProvideAComment extends Given2 { - @override - Future 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,28 +644,27 @@ 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 { - @override - Future executeStep(Table dataTable) async { - for (var row in dataTable.rows) { - // do something with row - row.columns.forEach((columnValue) => print(columnValue)); - } +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)); + } - // or get the table as a map (column values keyed by the header) - final columns = dataTable.asMap(); - 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"); + // or get the table as a map (column values keyed by the header) + final columns = dataTable.asMap(); + final personOne = columns.elementAt(0); + final personOneName = personOne["Firstname"]; + print('Name of first user: `$personOneName` '); + }, + ); } ``` @@ -707,7 +681,7 @@ In most scenarios theses parameters will be enough for you to write quite advanc | {int} | Matches an integer | {int}, {Int} | int | `Given I see {int} worm(s)` would match `Given I see 6 worms` | | {num} | Matches an number | {num}, {Num}, {float}, {Float} | num | `Given I see {num} worm(s)` would match `Given I see 0.75 worms` | -Note that you can combine there well known parameters in any step. For example `Given I {word} {int} worm(s)` would match `Given I "see" 6 worms` and also match `Given I "eat" 1 worm` +Note that you can combine there well known parameters in any step. For example `Given I {word} {int} worm(s)` would match `Given I "see" 6 worms` and also match `Given I "eat" 1 worm` #### Pluralization @@ -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 { - @override - Future 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( + '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 @@ -792,7 +760,7 @@ In order to allow features to be written in a number of languages, you can now w You can set the default language of feature files in your project via the configuration setting see [defaultLanguage](#defaultLanguage) -For example these two features are the same the keywords are just written in different languages. Note the ` ` ` # language: de ` ` ` on the second feature. English is the default language. +For example these two features are the same the keywords are just written in different languages. Note the ` ` ` # language: de ` ` ` on the second feature. English is the default language. ``` Feature: Calculator @@ -918,18 +886,17 @@ A reporter is a class that is able to report on the progress of the test run. In You can create your own custom reporter by inheriting from the base `Reporter` class and overriding the one or many of the methods to direct the output message. The `Reporter` defines the following methods that can be overridden. All methods must return a `Future` and can be async. -* `onTestRunStarted` -* `onTestRunFinished` -* `onFeatureStarted` -* `onFeatureFinished` -* `onScenarioStarted` -* `onScenarioFinished` -* `onStepStarted` -* `onStepFinished` -* `onException` -* `message` -* `dispose` - +* `onTestRunStarted` +* `onTestRunFinished` +* `onFeatureStarted` +* `onFeatureFinished` +* `onScenarioStarted` +* `onScenarioFinished` +* `onStepStarted` +* `onStepFinished` +* `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. diff --git a/example/test_driver/app_test.dart b/example/test_driver/app_test.dart index f096c2b..b755c06 100644 --- a/example/test_driver/app_test.dart +++ b/example/test_driver/app_test.dart @@ -8,26 +8,20 @@ import 'steps/given_I_pick_a_colour_step.dart'; import 'steps/tap_button_n_times_step.dart'; Future 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(), ] diff --git a/example/test_driver/features/counter.feature b/example/test_driver/features/counter.feature index 2912743..02c2c27 100644 --- a/example/test_driver/features/counter.feature +++ b/example/test_driver/features/counter.feature @@ -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 diff --git a/example/test_driver/features/counter_increases.feature b/example/test_driver/features/counter_increases.feature index fbd2ffe..da3de58 100644 --- a/example/test_driver/features/counter_increases.feature +++ b/example/test_driver/features/counter_increases.feature @@ -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" diff --git a/example/test_driver/features/counter_increases_french.feature b/example/test_driver/features/counter_increases_french.feature index 896b3c4..fa2285a 100644 --- a/example/test_driver/features/counter_increases_french.feature +++ b/example/test_driver/features/counter_increases_french.feature @@ -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" diff --git a/example/test_driver/report.json b/example/test_driver/report.json index af93cbd..50f0002 100644 --- a/example/test_driver/report.json +++ b/example/test_driver/report.json @@ -1 +1 @@ -[{"description":"","id":"startup","keyword":"Feature","line":1,"name":"Startup","uri":".\\features\\counter.feature","elements":[{"keyword":"Scenario","type":"scenario","id":"startup;should increment counter","name":"should increment counter","description":"","line":3,"steps":[{"keyword":"Given ","name":"I expect the \"counter\" to be \"0\"","line":4,"match":{"location":".\\features\\counter.feature:4"},"result":{"status":"passed","duration":48000000}},{"keyword":"When ","name":"I tap the \"increment\" button","line":5,"match":{"location":".\\features\\counter.feature:5"},"result":{"status":"passed","duration":334000000}},{"keyword":"And ","name":"I tap the \"increment\" button","line":6,"match":{"location":".\\features\\counter.feature:6"},"result":{"status":"passed","duration":259000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"2\"","line":7,"match":{"location":".\\features\\counter.feature:7"},"result":{"status":"passed","duration":25000000}}]},{"keyword":"Scenario","type":"scenario","id":"startup;counter should reset when app is restarted","name":"counter should reset when app is restarted","description":"","line":9,"steps":[{"keyword":"Given ","name":"I expect the \"counter\" to be \"0\"","line":10,"match":{"location":".\\features\\counter.feature:10"},"result":{"status":"passed","duration":40000000}},{"keyword":"When ","name":"I tap the \"increment\" button","line":11,"match":{"location":".\\features\\counter.feature:11"},"result":{"status":"passed","duration":311000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"1\"","line":12,"match":{"location":".\\features\\counter.feature:12"},"result":{"status":"passed","duration":28000000}},{"keyword":"When ","name":"I restart the app","line":13,"match":{"location":".\\features\\counter.feature:13"},"result":{"status":"passed","duration":3143000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"0\"","line":14,"match":{"location":".\\features\\counter.feature:14"},"result":{"status":"passed","duration":42000000}}]}]},{"description":"","id":"counter","keyword":"Feature","line":1,"name":"Counter","uri":".\\features\\counter_increases_scenerio_outline_example.feature","elements":[{"keyword":"Scenario Outline","type":"scenario","id":"counter;counter increases when the button is pressed (example 1)","name":"Counter increases when the button is pressed (Example 1)","description":"","line":5,"tags":[{"line":4,"name":"@scenario_outline"}],"steps":[{"keyword":"Given ","name":"I pick the colour red","line":6,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:6"},"result":{"status":"passed","duration":0}},{"keyword":"Given ","name":"I expect the \"counter\" to be \"0\"","line":7,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:7"},"result":{"status":"passed","duration":40000000}},{"keyword":"When ","name":"I tap the \"increment\" button 1 times","line":8,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:8"},"result":{"status":"passed","duration":309000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"1\"","line":9,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:9"},"result":{"status":"passed","duration":27000000}}]},{"keyword":"Scenario Outline","type":"scenario","id":"counter;counter increases when the button is pressed (example 2)","name":"Counter increases when the button is pressed (Example 2)","description":"","line":5,"tags":[{"line":4,"name":"@scenario_outline"}],"steps":[{"keyword":"Given ","name":"I pick the colour red","line":6,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:6"},"result":{"status":"passed","duration":0}},{"keyword":"Given ","name":"I expect the \"counter\" to be \"0\"","line":7,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:7"},"result":{"status":"passed","duration":40000000}},{"keyword":"When ","name":"I tap the \"increment\" button 2 times","line":8,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:8"},"result":{"status":"passed","duration":535000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"2\"","line":9,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:9"},"result":{"status":"passed","duration":26000000}}]},{"keyword":"Scenario Outline","type":"scenario","id":"counter;counter increases when the button is pressed (example 3)","name":"Counter increases when the button is pressed (Example 3)","description":"","line":5,"tags":[{"line":4,"name":"@scenario_outline"}],"steps":[{"keyword":"Given ","name":"I pick the colour red","line":6,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:6"},"result":{"status":"passed","duration":0}},{"keyword":"Given ","name":"I expect the \"counter\" to be \"0\"","line":7,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:7"},"result":{"status":"passed","duration":43000000}},{"keyword":"When ","name":"I tap the \"increment\" button 5 times","line":8,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:8"},"result":{"status":"passed","duration":1265000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"5\"","line":9,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:9"},"result":{"status":"passed","duration":29000000}}]},{"keyword":"Scenario Outline","type":"scenario","id":"counter;counter increases when the button is pressed (example 4)","name":"Counter increases when the button is pressed (Example 4)","description":"","line":5,"tags":[{"line":4,"name":"@scenario_outline"}],"steps":[{"keyword":"Given ","name":"I pick the colour red","line":6,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:6"},"result":{"status":"passed","duration":0}},{"keyword":"Given ","name":"I expect the \"counter\" to be \"0\"","line":7,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:7"},"result":{"status":"passed","duration":39000000}},{"keyword":"When ","name":"I tap the \"increment\" button 10 times","line":8,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:8"},"result":{"status":"passed","duration":2472000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"10\"","line":9,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:9"},"result":{"status":"passed","duration":30000000}}]}]},{"description":"","id":"custom parameter example","keyword":"Feature","line":1,"name":"Custom Parameter Example","uri":".\\features\\custom_parameter_example.feature","elements":[{"keyword":"Scenario","type":"scenario","id":"custom parameter example;custom colour parameter","name":"Custom colour parameter","description":"","line":4,"steps":[{"keyword":"Given ","name":"I pick the colour red","line":5,"match":{"location":".\\features\\custom_parameter_example.feature:5"},"result":{"status":"passed","duration":0}},{"keyword":"Given ","name":"I pick the colour green","line":6,"match":{"location":".\\features\\custom_parameter_example.feature:6"},"result":{"status":"passed","duration":0}},{"keyword":"Given ","name":"I pick the colour blue","line":7,"match":{"location":".\\features\\custom_parameter_example.feature:7"},"result":{"status":"passed","duration":0}}]}]},{"description":"","id":"counter","keyword":"Feature","line":2,"name":"Counter","uri":".\\features\\counter_increases_french.feature","elements":[{"keyword":"Scenario","type":"scenario","id":"counter;counter increases when the button is pressed","name":"Counter increases when the button is pressed","description":"","line":6,"tags":[{"line":5,"name":"@smoke"}],"steps":[{"keyword":"Etant ","name":"donné que I pick the colour red","line":7,"match":{"location":".\\features\\counter_increases_french.feature:7"},"result":{"status":"passed","duration":0}},{"keyword":"Et ","name":"I expect the \"counter\" to be \"0\"","line":8,"match":{"location":".\\features\\counter_increases_french.feature:8"},"result":{"status":"passed","duration":40000000}},{"keyword":"Quand ","name":"I tap the \"increment\" button 10 times","line":9,"match":{"location":".\\features\\counter_increases_french.feature:9"},"result":{"status":"passed","duration":2485000000}},{"keyword":"Alors ","name":"I expect the \"counter\" to be \"10\"","line":10,"match":{"location":".\\features\\counter_increases_french.feature:10"},"result":{"status":"passed","duration":31000000}}]}]},{"description":"","id":"startup","keyword":"Feature","line":1,"name":"Startup","uri":".\\features\\app_restart.feature","elements":[{"keyword":"Scenario","type":"scenario","id":"startup;counter should reset when app is restarted","name":"counter should reset when app is restarted","description":"","line":3,"steps":[{"keyword":"Given ","name":"I expect the \"counter\" to be \"0\"","line":4,"match":{"location":".\\features\\app_restart.feature:4"},"result":{"status":"passed","duration":42000000}},{"keyword":"When ","name":"I tap the \"increment\" button","line":5,"match":{"location":".\\features\\app_restart.feature:5"},"result":{"status":"passed","duration":323000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"1\"","line":6,"match":{"location":".\\features\\app_restart.feature:6"},"result":{"status":"passed","duration":26000000}},{"keyword":"When ","name":"I restart the app","line":7,"match":{"location":".\\features\\app_restart.feature:7"},"result":{"status":"passed","duration":3107000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"0\"","line":8,"match":{"location":".\\features\\app_restart.feature:8"},"result":{"status":"passed","duration":41000000}}]}]},{"description":"","id":"drawer","keyword":"Feature","line":1,"name":"Drawer","uri":".\\features\\drawer.feature","elements":[{"keyword":"Scenario","type":"scenario","id":"drawer;should open the drawer","name":"should open the drawer","description":"","line":3,"tags":[{"line":2,"name":"@debug"}],"steps":[{"keyword":"Given ","name":"I open the drawer","line":4,"match":{"location":".\\features\\drawer.feature:4"},"result":{"status":"passed","duration":1446000000}},{"keyword":"Given ","name":"I close the drawer","line":5,"match":{"location":".\\features\\drawer.feature:5"},"result":{"status":"passed","duration":403000000}}]}]},{"description":"","id":"counter","keyword":"Feature","line":1,"name":"Counter","uri":".\\features\\sub-features\\counter_increases.feature","elements":[{"keyword":"Scenario","type":"scenario","id":"counter;counter increases when the button is pressed","name":"Counter increases when the button is pressed","description":"","line":5,"tags":[{"line":4,"name":"@perf"}],"steps":[{"keyword":"Given ","name":"I expect the \"counter\" to be \"0\"","line":6,"match":{"location":".\\features\\sub-features\\counter_increases.feature:6"},"result":{"status":"passed","duration":41000000}},{"keyword":"When ","name":"I tap the \"increment\" button 20 times","line":7,"match":{"location":".\\features\\sub-features\\counter_increases.feature:7"},"result":{"status":"passed","duration":4911000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"20\"","line":8,"match":{"location":".\\features\\sub-features\\counter_increases.feature:8"},"result":{"status":"passed","duration":26000000}}]}]},{"description":"","id":"counter","keyword":"Feature","line":1,"name":"Counter","uri":".\\features\\counter_increases.feature","elements":[{"keyword":"Scenario","type":"scenario","id":"counter;counter increases when the button is pressed","name":"Counter increases when the button is pressed","description":"","line":5,"tags":[{"line":4,"name":"@smoke"}],"steps":[{"keyword":"Given ","name":"I pick the colour red","line":6,"match":{"location":".\\features\\counter_increases.feature:6"},"result":{"status":"passed","duration":0}},{"keyword":"Given ","name":"I expect the \"counter\" to be \"0\"","line":7,"match":{"location":".\\features\\counter_increases.feature:7"},"result":{"status":"passed","duration":40000000}},{"keyword":"When ","name":"I tap the \"increment\" button 10 times","line":8,"match":{"location":".\\features\\counter_increases.feature:8"},"result":{"status":"passed","duration":2480000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"10\"","line":9,"match":{"location":".\\features\\counter_increases.feature:9"},"result":{"status":"passed","duration":25000000}}]}]}] \ No newline at end of file +[{"description":"","id":"startup","keyword":"Feature","line":1,"name":"Startup","uri":".\\features\\app_restart.feature","elements":[{"keyword":"Scenario","type":"scenario","id":"startup;counter should reset when app is restarted","name":"counter should reset when app is restarted","description":"","line":3,"steps":[{"keyword":"Given ","name":"I expect the \"counter\" to be \"0\"","line":4,"match":{"location":".\\features\\app_restart.feature:4"},"result":{"status":"passed","duration":50000000}},{"keyword":"When ","name":"I tap the \"increment\" button","line":5,"match":{"location":".\\features\\app_restart.feature:5"},"result":{"status":"passed","duration":318000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"1\"","line":6,"match":{"location":".\\features\\app_restart.feature:6"},"result":{"status":"passed","duration":28000000}},{"keyword":"When ","name":"I restart the app","line":7,"match":{"location":".\\features\\app_restart.feature:7"},"result":{"status":"passed","duration":3149000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"0\"","line":8,"match":{"location":".\\features\\app_restart.feature:8"},"result":{"status":"passed","duration":39000000}}]}]},{"description":"","id":"startup","keyword":"Feature","line":1,"name":"Startup","uri":".\\features\\counter.feature","elements":[{"keyword":"Scenario","type":"scenario","id":"startup;should increment counter","name":"should increment counter","description":"","line":3,"steps":[{"keyword":"Given ","name":"I expect the \"counter\" to be \"0\"","line":4,"match":{"location":".\\features\\counter.feature:4"},"result":{"status":"passed","duration":37000000}},{"keyword":"When ","name":"I tap the \"increment\" button","line":6,"match":{"location":".\\features\\counter.feature:6"},"result":{"status":"passed","duration":317000000}},{"keyword":"And ","name":"I tap the \"increment\" button","line":7,"match":{"location":".\\features\\counter.feature:7"},"result":{"status":"passed","duration":255000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"2\"","line":9,"match":{"location":".\\features\\counter.feature:9"},"result":{"status":"passed","duration":28000000}}]},{"keyword":"Scenario","type":"scenario","id":"startup;counter should reset when app is restarted","name":"counter should reset when app is restarted","description":"","line":11,"steps":[{"keyword":"Given ","name":"I expect the \"counter\" to be \"0\"","line":12,"match":{"location":".\\features\\counter.feature:12"},"result":{"status":"passed","duration":37000000}},{"keyword":"When ","name":"I tap the \"increment\" button","line":13,"match":{"location":".\\features\\counter.feature:13"},"result":{"status":"passed","duration":301000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"1\"","line":14,"match":{"location":".\\features\\counter.feature:14"},"result":{"status":"passed","duration":25000000}},{"keyword":"When ","name":"I restart the app","line":15,"match":{"location":".\\features\\counter.feature:15"},"result":{"status":"passed","duration":3117000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"0\"","line":16,"match":{"location":".\\features\\counter.feature:16"},"result":{"status":"passed","duration":39000000}}]}]},{"description":"","id":"counter","keyword":"Feature","line":1,"name":"Counter","uri":".\\features\\counter_increases_scenerio_outline_example.feature","elements":[{"keyword":"Scenario Outline","type":"scenario","id":"counter;counter increases when the button is pressed (example 1)","name":"Counter increases when the button is pressed (Example 1)","description":"","line":5,"tags":[{"line":4,"name":"@scenario_outline"}],"steps":[{"keyword":"Given ","name":"I pick the colour red","line":6,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:6"},"result":{"status":"passed","duration":0}},{"keyword":"Given ","name":"I expect the \"counter\" to be \"0\"","line":7,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:7"},"result":{"status":"passed","duration":38000000}},{"keyword":"When ","name":"I tap the \"increment\" button 1 times","line":8,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:8"},"result":{"status":"passed","duration":319000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"1\"","line":9,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:9"},"result":{"status":"passed","duration":27000000}}]},{"keyword":"Scenario Outline","type":"scenario","id":"counter;counter increases when the button is pressed (example 2)","name":"Counter increases when the button is pressed (Example 2)","description":"","line":5,"tags":[{"line":4,"name":"@scenario_outline"}],"steps":[{"keyword":"Given ","name":"I pick the colour red","line":6,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:6"},"result":{"status":"passed","duration":0}},{"keyword":"Given ","name":"I expect the \"counter\" to be \"0\"","line":7,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:7"},"result":{"status":"passed","duration":38000000}},{"keyword":"When ","name":"I tap the \"increment\" button 2 times","line":8,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:8"},"result":{"status":"passed","duration":540000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"2\"","line":9,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:9"},"result":{"status":"passed","duration":28000000}}]},{"keyword":"Scenario Outline","type":"scenario","id":"counter;counter increases when the button is pressed (example 3)","name":"Counter increases when the button is pressed (Example 3)","description":"","line":5,"tags":[{"line":4,"name":"@scenario_outline"}],"steps":[{"keyword":"Given ","name":"I pick the colour red","line":6,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:6"},"result":{"status":"passed","duration":0}},{"keyword":"Given ","name":"I expect the \"counter\" to be \"0\"","line":7,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:7"},"result":{"status":"passed","duration":40000000}},{"keyword":"When ","name":"I tap the \"increment\" button 5 times","line":8,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:8"},"result":{"status":"passed","duration":1298000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"5\"","line":9,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:9"},"result":{"status":"passed","duration":28000000}}]},{"keyword":"Scenario Outline","type":"scenario","id":"counter;counter increases when the button is pressed (example 4)","name":"Counter increases when the button is pressed (Example 4)","description":"","line":5,"tags":[{"line":4,"name":"@scenario_outline"}],"steps":[{"keyword":"Given ","name":"I pick the colour red","line":6,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:6"},"result":{"status":"passed","duration":0}},{"keyword":"Given ","name":"I expect the \"counter\" to be \"0\"","line":7,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:7"},"result":{"status":"passed","duration":40000000}},{"keyword":"When ","name":"I tap the \"increment\" button 10 times","line":8,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:8"},"result":{"status":"passed","duration":2506000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"10\"","line":9,"match":{"location":".\\features\\counter_increases_scenerio_outline_example.feature:9"},"result":{"status":"passed","duration":27000000}}]}]},{"description":"","id":"drawer","keyword":"Feature","line":1,"name":"Drawer","uri":".\\features\\drawer.feature","elements":[{"keyword":"Scenario","type":"scenario","id":"drawer;should open the drawer","name":"should open the drawer","description":"","line":3,"tags":[{"line":2,"name":"@debug"}],"steps":[{"keyword":"Given ","name":"I open the drawer","line":4,"match":{"location":".\\features\\drawer.feature:4"},"result":{"status":"passed","duration":1462000000}},{"keyword":"Given ","name":"I close the drawer","line":5,"match":{"location":".\\features\\drawer.feature:5"},"result":{"status":"passed","duration":405000000}}]}]},{"description":"","id":"counter","keyword":"Feature","line":2,"name":"Counter","uri":".\\features\\counter_increases_french.feature","elements":[{"keyword":"Scenario","type":"scenario","id":"counter;counter increases when the button is pressed","name":"Counter increases when the button is pressed","description":"","line":5,"steps":[{"keyword":"Etant ","name":"donné que I pick the colour red","line":6,"match":{"location":".\\features\\counter_increases_french.feature:6"},"result":{"status":"passed","duration":0}},{"keyword":"Et ","name":"I expect the \"counter\" to be \"0\"","line":7,"match":{"location":".\\features\\counter_increases_french.feature:7"},"result":{"status":"passed","duration":38000000}},{"keyword":"Quand ","name":"I tap the \"increment\" button 10 times","line":8,"match":{"location":".\\features\\counter_increases_french.feature:8"},"result":{"status":"passed","duration":2499000000}},{"keyword":"Alors ","name":"I expect the \"counter\" to be \"10\"","line":9,"match":{"location":".\\features\\counter_increases_french.feature:9"},"result":{"status":"passed","duration":28000000}}]}]},{"description":"","id":"counter","keyword":"Feature","line":1,"name":"Counter","uri":".\\features\\sub-features\\counter_increases.feature","elements":[{"keyword":"Scenario","type":"scenario","id":"counter;counter increases when the button is pressed","name":"Counter increases when the button is pressed","description":"","line":5,"tags":[{"line":4,"name":"@perf"}],"steps":[{"keyword":"Given ","name":"I expect the \"counter\" to be \"0\"","line":6,"match":{"location":".\\features\\sub-features\\counter_increases.feature:6"},"result":{"status":"passed","duration":37000000}},{"keyword":"When ","name":"I tap the \"increment\" button 20 times","line":7,"match":{"location":".\\features\\sub-features\\counter_increases.feature:7"},"result":{"status":"passed","duration":4918000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"20\"","line":8,"match":{"location":".\\features\\sub-features\\counter_increases.feature:8"},"result":{"status":"passed","duration":31000000}}]}]},{"description":"","id":"counter","keyword":"Feature","line":1,"name":"Counter","uri":".\\features\\counter_increases.feature","elements":[{"keyword":"Scenario","type":"scenario","id":"counter;counter increases when the button is pressed","name":"Counter increases when the button is pressed","description":"","line":4,"steps":[{"keyword":"Given ","name":"I pick the colour red","line":5,"match":{"location":".\\features\\counter_increases.feature:5"},"result":{"status":"passed","duration":0}},{"keyword":"Given ","name":"I expect the \"counter\" to be \"0\"","line":6,"match":{"location":".\\features\\counter_increases.feature:6"},"result":{"status":"passed","duration":39000000}},{"keyword":"When ","name":"I tap the \"increment\" button 10 times","line":7,"match":{"location":".\\features\\counter_increases.feature:7"},"result":{"status":"passed","duration":2489000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"10\"","line":8,"match":{"location":".\\features\\counter_increases.feature:8"},"result":{"status":"passed","duration":31000000}}]}]},{"description":"","id":"custom parameter example","keyword":"Feature","line":1,"name":"Custom Parameter Example","uri":".\\features\\custom_parameter_example.feature","elements":[{"keyword":"Scenario","type":"scenario","id":"custom parameter example;custom colour parameter","name":"Custom colour parameter","description":"","line":4,"steps":[{"keyword":"Given ","name":"I pick the colour red","line":5,"match":{"location":".\\features\\custom_parameter_example.feature:5"},"result":{"status":"passed","duration":0}},{"keyword":"Given ","name":"I pick the colour green","line":6,"match":{"location":".\\features\\custom_parameter_example.feature:6"},"result":{"status":"passed","duration":0}},{"keyword":"Given ","name":"I pick the colour blue","line":7,"match":{"location":".\\features\\custom_parameter_example.feature:7"},"result":{"status":"passed","duration":0}}]}]}] \ No newline at end of file diff --git a/example/test_driver/steps/data_table_example_step.dart b/example/test_driver/steps/data_table_example_step.dart index 60dc656..45440de 100644 --- a/example/test_driver/steps/data_table_example_step.dart +++ b/example/test_driver/steps/data_table_example_step.dart @@ -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
{ - @override - Future executeStep(Table dataTable) async { - // implement executeStep - 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'); +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)); + } + }, + ); } diff --git a/example/test_driver/steps/given_I_pick_a_colour_step.dart b/example/test_driver/steps/given_I_pick_a_colour_step.dart index 51d0f19..f8e02e9 100644 --- a/example/test_driver/steps/given_I_pick_a_colour_step.dart +++ b/example/test_driver/steps/given_I_pick_a_colour_step.dart @@ -1,12 +1,11 @@ import 'package:gherkin/gherkin.dart'; import 'colour_parameter.dart'; -class GivenIPickAColour extends Given1 { - @override - Future 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'"); + }, + ); } diff --git a/example/test_driver/steps/multiline_string_example_step.dart b/example/test_driver/steps/multiline_string_example_step.dart index 8ef3a3c..542a4cd 100644 --- a/example/test_driver/steps/multiline_string_example_step.dart +++ b/example/test_driver/steps/multiline_string_example_step.dart @@ -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 { - @override - Future 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 + }, + ); } diff --git a/example/test_driver/steps/tap_button_n_times_step.dart b/example/test_driver/steps/tap_button_n_times_step.dart index 79e72c8..4fdda0f 100644 --- a/example/test_driver/steps/tap_button_n_times_step.dart +++ b/example/test_driver/steps/tap_button_n_times_step.dart @@ -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 { - TapButtonNTimesStep() - : super(StepDefinitionConfiguration()..timeout = Duration(seconds: 10)); - - @override - Future 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); - } - } - - @override - RegExp get pattern => RegExp(r'I tap the {string} button {int} times'); +StepDefinitionGeneric TapButtonNTimesStep() { + return given2( + '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); + } + }, + ); } diff --git a/lib/src/flutter/flutter_run_process_handler.dart b/lib/src/flutter/flutter_run_process_handler.dart index ead597d..a2a04e8 100644 --- a/lib/src/flutter/flutter_run_process_handler.dart +++ b/lib/src/flutter/flutter_run_process_handler.dart @@ -187,7 +187,7 @@ class FlutterRunProcessHandler extends ProcessHandler { final completer = Completer(); StreamSubscription sub; sub = _processStdoutStream.timeout( - timeout, + timeout ?? const Duration(seconds: 90), onTimeout: (_) { sub?.cancel(); if (!completer.isCompleted) { diff --git a/lib/src/flutter/flutter_test_configuration.dart b/lib/src/flutter/flutter_test_configuration.dart index 152f0c7..b9b9dc1 100644 --- a/lib/src/flutter/flutter_test_configuration.dart +++ b/lib/src/flutter/flutter_test_configuration.dart @@ -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> 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 diff --git a/lib/src/flutter/flutter_world.dart b/lib/src/flutter/flutter_world.dart index 1bf0da1..7421101 100644 --- a/lib/src/flutter/flutter_world.dart +++ b/lib/src/flutter/flutter_world.dart @@ -13,7 +13,7 @@ class FlutterWorld extends World { _driver = flutterDriver; } - void setFlutterProccessHandler( + void setFlutterProcessHandler( FlutterRunProcessHandler flutterRunProcessHandler) { _flutterRunProcessHandler = flutterRunProcessHandler; } diff --git a/lib/src/flutter/hooks/app_runner_hook.dart b/lib/src/flutter/hooks/app_runner_hook.dart index e1df35a..05bac95 100644 --- a/lib/src/flutter/hooks/app_runner_hook.dart +++ b/lib/src/flutter/hooks/app_runner_hook.dart @@ -56,7 +56,7 @@ class FlutterAppRunnerHook extends Hook { Iterable tags, ) async { if (world is FlutterWorld) { - world.setFlutterProccessHandler(_flutterRunProcessHandler); + world.setFlutterProcessHandler(_flutterRunProcessHandler); } } diff --git a/lib/src/flutter/steps/given_i_open_the_drawer_step.dart b/lib/src/flutter/steps/given_i_open_the_drawer_step.dart index 173513f..bc6b512 100644 --- a/lib/src/flutter/steps/given_i_open_the_drawer_step.dart +++ b/lib/src/flutter/steps/given_i_open_the_drawer_step.dart @@ -8,26 +8,25 @@ import 'package:gherkin/gherkin.dart'; /// Examples: /// /// `Given I open the drawer` -class GivenOpenDrawer extends Given1WithWorld { - @override - RegExp get pattern => RegExp(r'I (open|close) the drawer'); - - @override - Future executeStep(String action) async { - final drawerFinder = find.byType('Drawer'); - final isOpen = - await FlutterDriverUtils.isPresent(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)); - } else if (!isOpen && action == 'open') { - await FlutterDriverUtils.tap( - world.driver, - find.byTooltip('Open navigation menu'), - timeout: timeout, - ); - } - } +StepDefinitionGeneric GivenOpenDrawer() { + return given1( + RegExp(r'I (open|close) the drawer'), + (action, context) async { + final drawerFinder = find.byType('Drawer'); + 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 context.world.driver.scroll( + drawerFinder, -300.0, 0.0, const Duration(milliseconds: 300)); + } else if (!isOpen && action == 'open') { + await FlutterDriverUtils.tap( + context.world.driver, + find.byTooltip('Open navigation menu'), + timeout: context.configuration?.timeout, + ); + } + }, + ); } diff --git a/lib/src/flutter/steps/restart_app_step.dart b/lib/src/flutter/steps/restart_app_step.dart index f9a574c..ecb924b 100644 --- a/lib/src/flutter/steps/restart_app_step.dart +++ b/lib/src/flutter/steps/restart_app_step.dart @@ -1,15 +1,13 @@ import 'package:flutter_gherkin/flutter_gherkin.dart'; import 'package:gherkin/gherkin.dart'; -class RestartAppStep extends WhenWithWorld { - RestartAppStep() - : super(StepDefinitionConfiguration()..timeout = Duration(seconds: 60)); - - @override - Future executeStep() async { - await world.restartApp(timeout: timeout); - } - - @override - RegExp get pattern => RegExp(r'I restart the app'); +StepDefinitionGeneric RestartAppStep() { + return given( + 'I restart the app', + (context) async { + await context.world.restartApp( + timeout: context.configuration?.timeout, + ); + }, + ); } diff --git a/lib/src/flutter/steps/then_expect_element_to_have_value_step.dart b/lib/src/flutter/steps/then_expect_element_to_have_value_step.dart index 1a525d1..ebcb132 100644 --- a/lib/src/flutter/steps/then_expect_element_to_have_value_step.dart +++ b/lib/src/flutter/steps/then_expect_element_to_have_value_step.dart @@ -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 { - @override - RegExp get pattern => RegExp(r'I expect the {string} to be {string}$'); - - @override - Future executeStep(String key, String value) async { - try { - final text = await FlutterDriverUtils.getText( - world.driver, find.byValueKey(key), - timeout: timeout * .9); - expect(text, value); - } catch (e) { - await reporter.message( - "Step error '${pattern.pattern}': $e", MessageLevel.error); - rethrow; - } - } +StepDefinitionGeneric ThenExpectElementToHaveValue() { + return given2( + RegExp(r'I expect the {string} to be {string}$'), + (key, value, context) async { + try { + final text = await FlutterDriverUtils.getText( + context.world.driver, + find.byValueKey(key), + ); + context.expect(text, value); + } catch (e) { + await context.reporter.message('Step error: $e', MessageLevel.error); + rethrow; + } + }, + ); } diff --git a/lib/src/flutter/steps/then_expect_widget_to_be_present_step.dart b/lib/src/flutter/steps/then_expect_widget_to_be_present_step.dart index 27d1727..b95a1e0 100644 --- a/lib/src/flutter/steps/then_expect_widget_to_be_present_step.dart +++ b/lib/src/flutter/steps/then_expect_widget_to_be_present_step.dart @@ -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 { - @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 executeStep(String key, int seconds) async { - final isPresent = await FlutterDriverUtils.isPresent( - world.driver, - find.byValueKey(key), - timeout: Duration(seconds: seconds), - ); - expect(isPresent, true); - } +StepDefinitionGeneric ThenExpectWidgetToBePresent() { + return given2( + 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( + context.world.driver, + find.byValueKey(key), + timeout: Duration(seconds: seconds), + ); + context.expect(isPresent, true); + }, + ); } diff --git a/lib/src/flutter/steps/when_fill_field_step.dart b/lib/src/flutter/steps/when_fill_field_step.dart index b87a2d8..30f744f 100644 --- a/lib/src/flutter/steps/when_fill_field_step.dart +++ b/lib/src/flutter/steps/when_fill_field_step.dart @@ -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 { - @override - Future executeStep(String key, String input2) async { - final finder = find.byValueKey(key); - await world.driver.scrollIntoView(finder); - await FlutterDriverUtils.enterText( - world.driver, - finder, - input2, - timeout: timeout * .9, - ); - } - - @override - RegExp get pattern => RegExp(r'I fill the {string} field with {string}'); +StepDefinitionGeneric WhenFillFieldStep() { + return given2( + 'I fill the {string} field with {string}', + (key, value, context) async { + final finder = find.byValueKey(key); + await context.world.driver.scrollIntoView(finder); + await FlutterDriverUtils.enterText( + context.world.driver, + finder, + value, + ); + }, + ); } diff --git a/lib/src/flutter/steps/when_pause_step.dart b/lib/src/flutter/steps/when_pause_step.dart index e85fa1b..c19d987 100644 --- a/lib/src/flutter/steps/when_pause_step.dart +++ b/lib/src/flutter/steps/when_pause_step.dart @@ -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 { - WhenPauseStep() - : super(StepDefinitionConfiguration() - ..timeout = const Duration(minutes: 5)); - - @override - Future 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)); + }, + ); } diff --git a/lib/src/flutter/steps/when_tap_the_back_button_step.dart b/lib/src/flutter/steps/when_tap_the_back_button_step.dart index dcc7170..839f815 100644 --- a/lib/src/flutter/steps/when_tap_the_back_button_step.dart +++ b/lib/src/flutter/steps/when_tap_the_back_button_step.dart @@ -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 { - @override - RegExp get pattern => RegExp(r'I tap the back (?:button|element|widget)$'); - - @override - Future executeStep() async { - await FlutterDriverUtils.tap( - world.driver, - find.pageBack(), - timeout: timeout * .9, - ); - } +StepDefinitionGeneric WhenTapBackButtonWidget() { + return when1( + RegExp(r'I tap the back (?:button|element|widget)$'), + (_, context) async { + await FlutterDriverUtils.tap( + context.world.driver, + find.pageBack(), + ); + }, + ); } diff --git a/lib/src/flutter/steps/when_tap_widget_step.dart b/lib/src/flutter/steps/when_tap_widget_step.dart index 1bd5f41..4f5d5c8 100644 --- a/lib/src/flutter/steps/when_tap_widget_step.dart +++ b/lib/src/flutter/steps/when_tap_widget_step.dart @@ -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 { - @override - RegExp get pattern => RegExp( - r'I tap the {string} (?:button|element|label|icon|field|text|widget)$'); +StepDefinitionGeneric WhenTapWidget() { + return when1( + RegExp( + r'I tap the {string} (?:button|element|label|icon|field|text|widget)$'), + (key, context) async { + final finder = find.byValueKey(key); - @override - Future executeStep(String key) async { - final finder = find.byValueKey(key); - - await world.driver.scrollIntoView( - finder, - timeout: timeout * .45, - ); - await FlutterDriverUtils.tap( - world.driver, - finder, - timeout: timeout * .45, - ); - } + await context.world.driver.scrollIntoView( + finder, + ); + await FlutterDriverUtils.tap( + context.world.driver, + finder, + ); + }, + ); } -class WhenTapWidgetWithoutScroll extends When1WithWorld { - @override - RegExp get pattern => RegExp( - r'I tap the {string} (?:button|element|label|icon|field|text|widget) without scrolling it into view$'); +StepDefinitionGeneric WhenTapWidgetWithoutScroll() { + return when1( + 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); - @override - Future executeStep(String key) async { - final finder = find.byValueKey(key); - - await FlutterDriverUtils.tap( - world.driver, - finder, - timeout: timeout * .45, - ); - } + await FlutterDriverUtils.tap( + context.world.driver, + finder, + ); + }, + ); } diff --git a/pubspec.lock b/pubspec.lock index 5d865a9..e408033 100644 --- a/pubspec.lock +++ b/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: diff --git a/pubspec.yaml b/pubspec.yaml index 223fc37..3c97fad 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 diff --git a/test/flutter_configuration_test.dart b/test/flutter_configuration_test.dart index 2c4406e..607540a 100644 --- a/test/flutter_configuration_test.dart +++ b/test/flutter_configuration_test.dart @@ -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); }); }); });