diff --git a/CHANGELOG.md b/CHANGELOG.md index 7939fc9..852ecd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,32 +1,34 @@ -## [1.1.7+7] - 25/03/2019 -* Added the ability to test against an already running app; enabling you to debug a running application while it has tests executed against it. Setting the configuration property `runningAppProtocolEndpointUri` to the service protocol endpoint (found in stdout when an app has `--verbose` logging turrned on) will ensure that the existing app is connected to rather than starting a new instance of the app. NOTE: ensure the app you are trying to connect to calls `enableFlutterDriverExtension()` when it starts up otherwise the Flutter Driver will not be able to connect to it. +## [1.1.8] - 08/05/2020 +* Updated library to work with the new way the Flutter stable branch manages logging for Flutter driver +* Fixed spelling mistake of `targetAppWorkingDirectory` & `flutterDriverMaxConnectionAttempts` in `FlutterTestConfiguration` +* Added the ability to test against an already running app; enabling you to debug a running application while it has tests executed against it. Setting the configuration property `runningAppProtocolEndpointUri` to the service protocol endpoint (found in stdout when an app has `--verbose` logging turned on) will ensure that the existing app is connected to rather than starting a new instance of the app. NOTE: ensure the app you are trying to connect to calls `enableFlutterDriverExtension()` when it starts up otherwise the Flutter Driver will not be able to connect to it. -## [1.1.7+6] - 04/03/2019 +## [1.1.7+6] - 04/03/2020 * Updated to latest Gherkin library (see https://github.com/jonsamwell/dart_gherkin/blob/master/CHANGELOG.md#117---04032020) - this includes a breaking change to the `Hook` inteface that will need to be updated if any of the `Scenerio` level methods are implemented * Ensured the well known step `I tap the ".." button` scroll the element into view first -## [1.1.7+5] - 03/02/2019 +## [1.1.7+5] - 03/02/2020 * Updated to latest Gherkin library (see https://github.com/jonsamwell/dart_gherkin/blob/master/CHANGELOG.md#1164---03022020) -## [1.1.7+4] - 31/01/2019 +## [1.1.7+4] - 31/01/2020 * Update check to determine if any devices are connected to run tests against * When the flag `verboseFlutterProcessLogs` was true Flutter driver was preemptively connecting to the app when it was not ready -## [1.1.7+3] - 08/01/2019 +## [1.1.7+3] - 08/01/2020 * Added retry logic to the Futter driver connect call to handle the seemingly random connection failures * Ensured `AttachScreenshotOnFailedStepHook` cannot throw an unhandled exception causing the test run to stop * Added new well known step `When I tap the back button` which finds and taps the default page back button * Added a new well known step `Then I expect the widget 'notification' to be present within 2 seconds` which expects a widget with a given key to be present within n seconds * Updated Gherkin library version -## [1.1.7+2] - 07/01/2019 +## [1.1.7+2] - 07/01/2020 * Increased the Flutter driver reconnection delay to try and overcome some driver to app connection issues on slower machines -## [1.1.7+1] - 07/01/2019 +## [1.1.7+1] - 07/01/2020 * Ensured when the Flutter driver is closed it cannot throw an unhandled exception causing the test run the stop * Updated Gherkin library version -## [1.1.7] - 06/01/2019 +## [1.1.7] - 06/01/2020 * `WhenFillFieldStep` Ensure widget is scrolled into view before setting it's value * Fixed lint warnings diff --git a/README.md b/README.md index 1542ec8..eedcd00 100644 --- a/README.md +++ b/README.md @@ -981,7 +981,7 @@ After which the file will most likely look like this #### Debugging the app under test -Setting the configuration property `runningAppProtocolEndpointUri` to the service protocol endpoint (found in stdout when an app has `--verbose` logging turrned on) will ensure that the existing app is connected to rather than starting a new instance of the app. +Setting the configuration property `runningAppProtocolEndpointUri` to the service protocol endpoint (found in stdout when an app has `--verbose` logging turned on) will ensure that the existing app is connected to rather than starting a new instance of the app. NOTE: ensure the app you are trying to connect to calls `enableFlutterDriverExtension()` when it starts up otherwise the Flutter Driver will not be able to connect to it. diff --git a/example/ios/Flutter/flutter_export_environment.sh b/example/ios/Flutter/flutter_export_environment.sh index f8b0bc7..37acd34 100644 --- a/example/ios/Flutter/flutter_export_environment.sh +++ b/example/ios/Flutter/flutter_export_environment.sh @@ -5,6 +5,7 @@ export "FLUTTER_APPLICATION_PATH=C:\github\flutter_gherkin\example" export "FLUTTER_TARGET=lib\main.dart" export "FLUTTER_BUILD_DIR=build" export "SYMROOT=${SOURCE_ROOT}/../build\ios" +export "OTHER_LDFLAGS=$(inherited) -framework Flutter" export "FLUTTER_FRAMEWORK_DIR=C:\Google\flutter\bin\cache\artifacts\engine\ios" export "FLUTTER_BUILD_NAME=1.0.0" export "FLUTTER_BUILD_NUMBER=1" diff --git a/example/lib/main.dart b/example/lib/main.dart index ccf3055..beeef73 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -37,7 +37,7 @@ class _MyHomePageState extends State { title: Text(widget.title), ), drawer: Drawer( - key: const Key("drawer"), + key: const Key('drawer'), child: ListView( padding: EdgeInsets.zero, children: [ @@ -81,7 +81,7 @@ class _MyHomePageState extends State { // to identify this specific Widget from inside our test suite and // read the text. key: const Key('counter'), - style: Theme.of(context).textTheme.display1, + style: Theme.of(context).textTheme.headline4, ), ], ), diff --git a/example/test_driver/app_test.dart b/example/test_driver/app_test.dart index 871daad..bb12530 100644 --- a/example/test_driver/app_test.dart +++ b/example/test_driver/app_test.dart @@ -9,7 +9,7 @@ import 'steps/tap_button_n_times_step.dart'; Future main() { final config = FlutterTestConfiguration() - ..features = [Glob(r"features/**.feature")] + ..features = [Glob('features//**.feature')] ..reporters = [ ProgressReporter(), TestRunSummaryReporter(), @@ -31,17 +31,18 @@ Future main() { ..customStepParameterDefinitions = [ ColourParameter(), ] - ..restartAppBetweenScenarios = false - ..targetAppWorkingDirecotry = "../" - ..targetAppPath = "test_driver/app.dart" + ..restartAppBetweenScenarios = true + ..targetAppWorkingDirectory = '../' + ..targetAppPath = 'test_driver/app.dart' // ..buildFlavor = "staging" // uncomment when using build flavor and check android/ios flavor setup see android file android\app\build.gradle // ..targetDeviceId = "all" // uncomment to run tests on all connected devices or set specific device target id // ..tagExpression = "@smoke" // uncomment to see an example of running scenarios based on tag expressions // ..logFlutterProcessOutput = true // uncomment to see command invoked to start the flutter test app // ..verboseFlutterProcessLogs = true // uncomment to see the verbose output from the Flutter process // ..flutterBuildTimeout = Duration(minutes: 3) // uncomment to change the default period that flutter is expected to build and start the app within - ..runningAppProtocolEndpointUri = - 'http://127.0.0.1:51540/bkegoer6eH8=/' // already running app observatory / service protocol uri (with enableFlutterDriverExtension method invoked) to test against if you use this set `restartAppBetweenScenarios` to false + // ..runningAppProtocolEndpointUri = + // 'http://127.0.0.1:51540/bkegoer6eH8=/' // already running app observatory / service protocol uri (with enableFlutterDriverExtension method invoked) to test against if you use this set `restartAppBetweenScenarios` to false ..exitAfterTestRun = true; // set to false if debugging to exit cleanly + return GherkinRunner().execute(config); } diff --git a/example/test_driver/hooks/hook_example.dart b/example/test_driver/hooks/hook_example.dart index 0ef3082..253564c 100644 --- a/example/test_driver/hooks/hook_example.dart +++ b/example/test_driver/hooks/hook_example.dart @@ -9,13 +9,13 @@ class HookExample extends Hook { /// Run before any scenario in a test run have executed @override Future onBeforeRun(TestConfiguration config) async { - print("before run hook"); + print('before run hook'); } /// Run after all scenarios in a test run have completed @override Future onAfterRun(TestConfiguration config) async { - print("after run hook"); + print('after run hook'); } /// Run before a scenario and it steps are executed diff --git a/example/test_driver/report.json b/example/test_driver/report.json index cfade83..63300b7 100644 --- a/example/test_driver/report.json +++ b/example/test_driver/report.json @@ -1 +1 @@ -[{"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":47000000}},{"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":4963000000}},{"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":"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":5,"match":{"location":".\\features\\counter.feature:5"},"result":{"status":"passed","duration":318000000}},{"keyword":"And ","name":"I tap the \"increment\" button","line":6,"match":{"location":".\\features\\counter.feature:6"},"result":{"status":"passed","duration":263000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"2\"","line":7,"match":{"location":".\\features\\counter.feature:7"},"result":{"status":"passed","duration":31000000}}]},{"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":39000000}},{"keyword":"When ","name":"I tap the \"increment\" button","line":11,"match":{"location":".\\features\\counter.feature:11"},"result":{"status":"passed","duration":331000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"1\"","line":12,"match":{"location":".\\features\\counter.feature:12"},"result":{"status":"passed","duration":29000000}},{"keyword":"When ","name":"I restart the app","line":13,"match":{"location":".\\features\\counter.feature:13"},"result":{"status":"passed","duration":3066000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"0\"","line":14,"match":{"location":".\\features\\counter.feature:14"},"result":{"status":"passed","duration":41000000}}]}]},{"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":38000000}},{"keyword":"When ","name":"I tap the \"increment\" button 10 times","line":8,"match":{"location":".\\features\\counter_increases.feature:8"},"result":{"status":"passed","duration":2496000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"10\"","line":9,"match":{"location":".\\features\\counter_increases.feature:9"},"result":{"status":"passed","duration":28000000}}]}]},{"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":2546000000}},{"keyword":"Alors ","name":"I expect the \"counter\" to be \"10\"","line":10,"match":{"location":".\\features\\counter_increases_french.feature:10"},"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":"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":37000000}},{"keyword":"When ","name":"I tap the \"increment\" button","line":5,"match":{"location":".\\features\\app_restart.feature:5"},"result":{"status":"passed","duration":307000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"1\"","line":6,"match":{"location":".\\features\\app_restart.feature:6"},"result":{"status":"passed","duration":25000000}},{"keyword":"When ","name":"I restart the app","line":7,"match":{"location":".\\features\\app_restart.feature:7"},"result":{"status":"passed","duration":3075000000}},{"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":"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":1455000000}},{"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\\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":293000000}},{"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":29000000}}]},{"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":37000000}},{"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":560000000}},{"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":34000000}}]},{"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":44000000}},{"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":1285000000}},{"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":30000000}}]},{"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":2515000000}},{"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}}]}]}] \ No newline at end of file +[{"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":1000000}},{"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":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":45000000}},{"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":4940000000}},{"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":37000000}}]}]},{"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":313000000}},{"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":35000000}}]},{"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":548000000}},{"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":31000000}}]},{"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":37000000}},{"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":1293000000}},{"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":25000000}}]},{"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":38000000}},{"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":2500000000}},{"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":"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":39000000}},{"keyword":"Quand ","name":"I tap the \"increment\" button 10 times","line":9,"match":{"location":".\\features\\counter_increases_french.feature:9"},"result":{"status":"passed","duration":2490000000}},{"keyword":"Alors ","name":"I expect the \"counter\" to be \"10\"","line":10,"match":{"location":".\\features\\counter_increases_french.feature:10"},"result":{"status":"passed","duration":29000000}}]}]},{"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":45000000}},{"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":31000000}},{"keyword":"When ","name":"I restart the app","line":7,"match":{"location":".\\features\\app_restart.feature:7"},"result":{"status":"passed","duration":3114000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"0\"","line":8,"match":{"location":".\\features\\app_restart.feature:8"},"result":{"status":"passed","duration":38000000}}]}]},{"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":39000000}},{"keyword":"When ","name":"I tap the \"increment\" button 10 times","line":8,"match":{"location":".\\features\\counter_increases.feature:8"},"result":{"status":"passed","duration":2493000000}},{"keyword":"Then ","name":"I expect the \"counter\" to be \"10\"","line":9,"match":{"location":".\\features\\counter_increases.feature:9"},"result":{"status":"passed","duration":34000000}}]}]},{"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":38000000}},{"keyword":"When ","name":"I tap the \"increment\" button","line":5,"match":{"location":".\\features\\counter.feature:5"},"result":{"status":"passed","duration":329000000}},{"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":30000000}}]},{"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":37000000}},{"keyword":"When ","name":"I tap the \"increment\" button","line":11,"match":{"location":".\\features\\counter.feature:11"},"result":{"status":"passed","duration":312000000}},{"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":3096000000}},{"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":"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":1452000000}},{"keyword":"Given ","name":"I close the drawer","line":5,"match":{"location":".\\features\\drawer.feature:5"},"result":{"status":"passed","duration":400000000}}]}]}] \ No newline at end of file diff --git a/example/test_driver/steps/colour_parameter.dart b/example/test_driver/steps/colour_parameter.dart index dcb45d5..90b2e0a 100644 --- a/example/test_driver/steps/colour_parameter.dart +++ b/example/test_driver/steps/colour_parameter.dart @@ -4,13 +4,13 @@ enum Colour { red, green, blue } class ColourParameter extends CustomParameter { ColourParameter() - : super("colour", RegExp(r"(red|green|blue)", caseSensitive: true), (c) { + : super('colour', RegExp(r'(red|green|blue)', caseSensitive: true), (c) { switch (c.toLowerCase()) { - case "red": + case 'red': return Colour.red; - case "green": + case 'green': return Colour.green; - case "blue": + case 'blue': default: return Colour.blue; } diff --git a/example/test_driver/steps/data_table_example_step.dart b/example/test_driver/steps/data_table_example_step.dart index de9bf7f..60dc656 100644 --- a/example/test_driver/steps/data_table_example_step.dart +++ b/example/test_driver/steps/data_table_example_step.dart @@ -20,5 +20,5 @@ class GivenIAddTheUsers extends Given1 { } @override - RegExp get pattern => RegExp(r"I add the users"); + RegExp get pattern => RegExp(r'I add the users'); } 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 a228f7f..51d0f19 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 @@ -8,5 +8,5 @@ class GivenIPickAColour extends Given1 { } @override - RegExp get pattern => RegExp(r"I pick the colour {colour}"); + RegExp get pattern => RegExp(r'I pick the colour {colour}'); } diff --git a/example/test_driver/steps/multiline_string_example_step.dart b/example/test_driver/steps/multiline_string_example_step.dart index 67bc3d2..8ef3a3c 100644 --- a/example/test_driver/steps/multiline_string_example_step.dart +++ b/example/test_driver/steps/multiline_string_example_step.dart @@ -15,5 +15,5 @@ class GivenIProvideAComment extends Given2 { } @override - RegExp get pattern => RegExp(r"I provide the following {string} comment"); + RegExp get pattern => RegExp(r'I provide the following {string} comment'); } 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 13b7e90..79e72c8 100644 --- a/example/test_driver/steps/tap_button_n_times_step.dart +++ b/example/test_driver/steps/tap_button_n_times_step.dart @@ -15,5 +15,5 @@ class TapButtonNTimesStep extends When2WithWorld { } @override - RegExp get pattern => RegExp(r"I tap the {string} button {int} times"); + RegExp get pattern => RegExp(r'I tap the {string} button {int} times'); } diff --git a/lib/flutter_gherkin.dart b/lib/flutter_gherkin.dart index 540c225..e69ee88 100644 --- a/lib/flutter_gherkin.dart +++ b/lib/flutter_gherkin.dart @@ -1,9 +1,9 @@ library flutter_gherkin; // Flutter specific implementations -export "src/flutter/flutter_world.dart"; -export "src/flutter/flutter_test_configuration.dart"; -export "src/flutter/utils/driver_utils.dart"; +export 'src/flutter/flutter_world.dart'; +export 'src/flutter/flutter_test_configuration.dart'; +export 'src/flutter/utils/driver_utils.dart'; // Well known steps export 'src/flutter/steps/given_i_open_the_drawer_step.dart'; diff --git a/lib/src/flutter/flutter_run_process_handler.dart b/lib/src/flutter/flutter_run_process_handler.dart index 0fd9331..ead597d 100644 --- a/lib/src/flutter/flutter_run_process_handler.dart +++ b/lib/src/flutter/flutter_run_process_handler.dart @@ -4,45 +4,45 @@ import 'dart:io'; import 'package:gherkin/gherkin.dart'; class FlutterRunProcessHandler extends ProcessHandler { - static const String FAIL_COLOR = "\u001b[33;31m"; // red - static const String WARN_COLOR = "\u001b[33;10m"; // yellow - static const String RESET_COLOR = "\u001b[33;0m"; + static const String FAIL_COLOR = '\u001b[33;31m'; // red + static const String WARN_COLOR = '\u001b[33;10m'; // yellow + static const String RESET_COLOR = '\u001b[33;0m'; // the flutter process usually outputs something like the below to indicate the app is ready to be connected to // `An Observatory debugger and profiler on AOSP on IA Emulator is available at: http://127.0.0.1:51322/BI_fyYaeoCE=/` - static RegExp _observatoryDebuggerUriRegex = RegExp( - r"observatory (?:debugger|url) .* available .*[:]? (http[s]?:.*\/).*", + static final RegExp _observatoryDebuggerUriRegex = RegExp( + r'observatory (?:debugger|url) .* available .*[:]? (http[s]?:.*\/).*', caseSensitive: false, multiLine: false, ); - static RegExp _noConnectedDeviceRegex = RegExp( - r"no connected device|no supported devices connected", + static final RegExp _noConnectedDeviceRegex = RegExp( + r'no connected device|no supported devices connected', caseSensitive: false, multiLine: false, ); - static RegExp _moreThanOneDeviceConnectedDeviceRegex = RegExp( - r"more than one device connected", + static final RegExp _moreThanOneDeviceConnectedDeviceRegex = RegExp( + r'more than one device connected', caseSensitive: false, multiLine: false, ); - static RegExp _errorMessageRegex = RegExp( - r"aborted|error|failure|unexpected|failed|exception", + static final RegExp _errorMessageRegex = RegExp( + r'aborted|error|failure|unexpected|failed|exception', caseSensitive: false, multiLine: false, ); - static RegExp _restartedApplicationSuccessRegex = RegExp( - r"Restarted application (.*)ms.", + static final RegExp _restartedApplicationSuccessRegex = RegExp( + r'Restarted application (.*)ms.', caseSensitive: false, multiLine: false, ); Process _runningProcess; Stream _processStdoutStream; - List _openSubscriptions = []; + final List _openSubscriptions = []; bool _buildApp = true; bool _logFlutterProcessOutput = false; bool _verboseFlutterLogs = false; @@ -81,28 +81,28 @@ class FlutterRunProcessHandler extends ProcessHandler { _buildApp = build; } - void setVerboseFluterlogs(bool verbose) { + void setVerboseFlutterLogs(bool verbose) { _verboseFlutterLogs = verbose; } @override Future run() async { - final arguments = ["run", "--target=$_appTarget"]; + final arguments = ['run', '--target=$_appTarget']; if (_buildApp == false) { - arguments.add("--no-build"); + arguments.add('--no-build'); } if (_buildFlavor != null && _buildFlavor.isNotEmpty) { - arguments.add("--flavor=$_buildFlavor"); + arguments.add('--flavor=$_buildFlavor'); } if (_deviceTargetId != null && _deviceTargetId.isNotEmpty) { - arguments.add("--device-id=$_deviceTargetId"); + arguments.add('--device-id=$_deviceTargetId'); } if (_verboseFlutterLogs) { - arguments.add("--verbose"); + arguments.add('--verbose'); } if (_logFlutterProcessOutput) { @@ -112,7 +112,7 @@ class FlutterRunProcessHandler extends ProcessHandler { } _runningProcess = await Process.start( - "flutter", + 'flutter', arguments, workingDirectory: _workingDirectory, runInShell: true, @@ -126,21 +126,21 @@ class FlutterRunProcessHandler extends ProcessHandler { .where((event) => event.isNotEmpty) .listen((event) { if (event.contains(_errorMessageRegex)) { - stderr.writeln("${FAIL_COLOR}Flutter build error: $event$RESET_COLOR"); + stderr.writeln('${FAIL_COLOR}Flutter build error: $event$RESET_COLOR'); } else { // This is most likely a depricated api usage warnings (from Gradle) and should not // cause the test run to fail. - stdout.writeln("$WARN_COLOR$event$RESET_COLOR"); + stdout.writeln('$WARN_COLOR$event$RESET_COLOR'); } })); } @override Future terminate() async { - int exitCode = -1; + var exitCode = -1; _ensureRunningProcess(); if (_runningProcess != null) { - _runningProcess.stdin.write("q"); + _runningProcess.stdin.write('q'); _openSubscriptions.forEach((s) => s.cancel()); _openSubscriptions.clear(); exitCode = await _runningProcess.exitCode; @@ -152,10 +152,10 @@ class FlutterRunProcessHandler extends ProcessHandler { Future restart({Duration timeout = const Duration(seconds: 90)}) async { _ensureRunningProcess(); - _runningProcess.stdin.write("R"); + _runningProcess.stdin.write('R'); await _waitForStdOutMessage( _restartedApplicationSuccessRegex, - "Timeout waiting for app restart", + 'Timeout waiting for app restart', timeout, ); @@ -171,7 +171,7 @@ class FlutterRunProcessHandler extends ProcessHandler { ]) async { currentObservatoryUri = await _waitForStdOutMessage( _observatoryDebuggerUriRegex, - "Timeout while waiting for observatory debugger uri", + 'Timeout while waiting for observatory debugger uri', timeout, ); @@ -208,12 +208,12 @@ class FlutterRunProcessHandler extends ProcessHandler { sub?.cancel(); if (!completer.isCompleted) { stderr.writeln( - "${FAIL_COLOR}No connected devices found to run app on and tests against$RESET_COLOR"); + '${FAIL_COLOR}No connected devices found to run app on and tests against$RESET_COLOR'); } } else if (_moreThanOneDeviceConnectedDeviceRegex.hasMatch(logLine)) { sub?.cancel(); if (!completer.isCompleted) { - stderr.writeln("$FAIL_COLOR$logLine$RESET_COLOR"); + stderr.writeln('$FAIL_COLOR$logLine$RESET_COLOR'); } } }, @@ -226,7 +226,7 @@ class FlutterRunProcessHandler extends ProcessHandler { void _ensureRunningProcess() { if (_runningProcess == null) { throw Exception( - "FlutterRunProcessHandler: flutter run process is not active"); + 'FlutterRunProcessHandler: flutter run process is not active'); } } } diff --git a/lib/src/flutter/flutter_test_configuration.dart b/lib/src/flutter/flutter_test_configuration.dart index 266b4bd..2ff241f 100644 --- a/lib/src/flutter/flutter_test_configuration.dart +++ b/lib/src/flutter/flutter_test_configuration.dart @@ -24,15 +24,15 @@ class FlutterTestConfiguration extends TestConfiguration { /// The target app to run the tests against /// Defaults to "lib/test_driver/app.dart" - String targetAppPath = "lib/test_driver/app.dart"; + String targetAppPath = 'lib/test_driver/app.dart'; /// Option to define the working directory for the process that runs the app under test (optional) - /// Handy if your app is seperated from your tests as flutter needs to be able to find a pubspec file - String targetAppWorkingDirecotry; + /// Handy if your app is separated from your tests as flutter needs to be able to find a pubspec file + String targetAppWorkingDirectory; /// The build flavor to run the tests against (optional) /// Defaults to empty - String buildFlavor = ""; + String buildFlavor = ''; /// If the application should be built prior to running the tests /// Defaults to true @@ -40,7 +40,7 @@ class FlutterTestConfiguration extends TestConfiguration { /// The target device id to run the tests against when multiple devices detected /// Defaults to empty - String targetDeviceId = ""; + String targetDeviceId = ''; /// Logs Flutter process output to stdout /// The Flutter process is use to start and driver the app under test. @@ -64,7 +64,7 @@ class FlutterTestConfiguration extends TestConfiguration { /// The maximum times the flutter driver can try and connect to the running app /// Defaults to 3 - int flutterDriverMaxConnectionAttemps = 3; + int flutterDriverMaxConnectionAttempts = 3; /// An observatory url that the test runner can connect to instead of creating a new running instance of the target application /// Url takes the form of `http://127.0.0.1:51540/EM72VtRsUV0=/` and usually printed to stdout in the form `Connecting to service protocol: http://127.0.0.1:51540/EM72VtRsUV0=/` @@ -84,7 +84,7 @@ class FlutterTestConfiguration extends TestConfiguration { TestConfiguration config, FlutterWorld world, ) async { - FlutterTestConfiguration flutterConfig = config as FlutterTestConfiguration; + var flutterConfig = config as FlutterTestConfiguration; world = world ?? FlutterWorld(); final driver = await createFlutterDriver( @@ -116,6 +116,7 @@ class FlutterTestConfiguration extends TestConfiguration { ..addAll([ ThenExpectElementToHaveValue(), WhenTapWidget(), + WhenTapWidgetWithoutScroll(), WhenTapBackButtonWidget(), GivenOpenDrawer(), WhenPauseStep(), @@ -128,14 +129,14 @@ class FlutterTestConfiguration extends TestConfiguration { Future _attemptDriverConnection( String dartVmServiceUrl, int attempt, - int maxAttemps, + int maxAttempts, ) async { try { return await FlutterDriver.connect( dartVmServiceUrl: dartVmServiceUrl, ); } catch (e) { - if (attempt > maxAttemps) { + if (attempt > maxAttempts) { rethrow; } else { print(e); @@ -144,7 +145,7 @@ class FlutterTestConfiguration extends TestConfiguration { return _attemptDriverConnection( dartVmServiceUrl, attempt + 1, - maxAttemps, + maxAttempts, ); } } diff --git a/lib/src/flutter/hooks/app_runner_hook.dart b/lib/src/flutter/hooks/app_runner_hook.dart index 65413a0..e1df35a 100644 --- a/lib/src/flutter/hooks/app_runner_hook.dart +++ b/lib/src/flutter/hooks/app_runner_hook.dart @@ -69,10 +69,10 @@ class FlutterAppRunnerHook extends Hook { } else { _flutterRunProcessHandler = FlutterRunProcessHandler() ..setLogFlutterProcessOutput(config.logFlutterProcessOutput) - ..setVerboseFluterlogs(config.verboseFlutterProcessLogs) + ..setVerboseFlutterLogs(config.verboseFlutterProcessLogs) ..setApplicationTargetFile(config.targetAppPath) ..setDriverConnectionDelay(config.flutterDriverReconnectionDelay) - ..setWorkingDirectory(config.targetAppWorkingDirecotry) + ..setWorkingDirectory(config.targetAppWorkingDirectory) ..setBuildRequired(haveRunFirstScenario ? false : config.build) ..setBuildFlavor(config.buildFlavor) ..setDeviceTargetId(config.targetDeviceId); @@ -88,7 +88,7 @@ class FlutterAppRunnerHook extends Hook { Future _terminateApp() async { if (_flutterRunProcessHandler != null) { - stdout.writeln("Terminating Flutter app under test"); + stdout.writeln('Terminating Flutter app under test'); await _flutterRunProcessHandler.terminate(); _flutterRunProcessHandler = null; } @@ -96,7 +96,7 @@ class FlutterAppRunnerHook extends Hook { Future _restartApp() async { if (_flutterRunProcessHandler != null) { - stdout.writeln("Restarting Flutter app under test"); + stdout.writeln('Restarting Flutter app under test'); await _flutterRunProcessHandler.restart(); } } diff --git a/lib/src/flutter/reporters/flutter_driver_reporter.dart b/lib/src/flutter/reporters/flutter_driver_reporter.dart index a00fdce..2c97005 100644 --- a/lib/src/flutter/reporters/flutter_driver_reporter.dart +++ b/lib/src/flutter/reporters/flutter_driver_reporter.dart @@ -4,6 +4,8 @@ import 'dart:io'; import 'package:gherkin/gherkin.dart'; import 'package:flutter_driver/flutter_driver.dart'; +enum _FlutterDriverMessageLogLevel { info, warning, error } + /// The Flutter driver helpfully logs ALL messages to the stderr output /// useless something is listening to the messages. /// This reporter listens to the messages from the driver so nothing is logged @@ -15,7 +17,6 @@ class FlutterDriverReporter extends Reporter { final bool logErrorMessages; final bool logWarningMessages; final bool logInfoMessages; - final List subscriptions = List(); FlutterDriverReporter({ this.logErrorMessages = true, @@ -23,38 +24,37 @@ class FlutterDriverReporter extends Reporter { this.logInfoMessages = false, }); + @override Future onTestRunStarted() async { - if (logInfoMessages) { - subscriptions.add(flutterDriverLog - .where((log) => _isLevel(log.level, [LogLevel.info, LogLevel.trace])) - .listen((log) { - stdout.writeln(log.toString()); - })); - } - - if (logWarningMessages) { - subscriptions.add(flutterDriverLog - .where((log) => _isLevel(log.level, [LogLevel.warning])) - .listen((log) { - stdout.writeln(log.toString()); - })); - } - - if (logErrorMessages) { - subscriptions.add(flutterDriverLog - .where( - (log) => _isLevel(log.level, [LogLevel.critical, LogLevel.error])) - .listen((log) { - stderr.writeln(log.toString()); - })); - } + driverLog = _driverLogMessageHandler; } + @override Future dispose() async { - subscriptions.forEach((s) => s.cancel()); - subscriptions.clear(); + driverLog = null; } - bool _isLevel(LogLevel level, Iterable levels) => - levels.contains(level); + void _driverLogMessageHandler(String source, String message) { + final level = _getMessageLevel(message); + final log = '$source $message'; + + if (logWarningMessages && level == _FlutterDriverMessageLogLevel.warning) { + stdout.writeln(log); + } else if (logErrorMessages && + level == _FlutterDriverMessageLogLevel.error) { + stderr.writeln(log); + } else { + stdout.writeln(log); + } + } + + _FlutterDriverMessageLogLevel _getMessageLevel(String message) { + if (message.toLowerCase().contains('warning')) { + return _FlutterDriverMessageLogLevel.warning; + } else if (message.toLowerCase().contains('error')) { + return _FlutterDriverMessageLogLevel.error; + } else { + return _FlutterDriverMessageLogLevel.info; + } + } } 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 19216c0..17113f4 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 @@ -10,22 +10,22 @@ import 'package:gherkin/gherkin.dart'; /// `Given I open the drawer` class GivenOpenDrawer extends Given1WithWorld { @override - RegExp get pattern => RegExp(r"I (open|close) the drawer"); + RegExp get pattern => RegExp(r'I (open|close) the drawer'); @override Future executeStep(String action) async { - final drawerFinder = find.byType("Drawer"); + final drawerFinder = find.byType('Drawer'); final isOpen = await FlutterDriverUtils.isPresent(drawerFinder, world.driver); // https://github.com/flutter/flutter/issues/9002#issuecomment-293660833 - if (isOpen && action == "close") { + if (isOpen && action == 'close') { // Swipe to the left across the whole app to close the drawer await world.driver .scroll(drawerFinder, -300.0, 0.0, const Duration(milliseconds: 300)); - } else if (!isOpen && action == "open") { + } else if (!isOpen && action == 'open') { await FlutterDriverUtils.tap( world.driver, - find.byTooltip("Open navigation menu"), + find.byTooltip('Open navigation menu'), timeout: timeout, ); } diff --git a/lib/src/flutter/steps/restart_app_step.dart b/lib/src/flutter/steps/restart_app_step.dart index 167c3df..f9a574c 100644 --- a/lib/src/flutter/steps/restart_app_step.dart +++ b/lib/src/flutter/steps/restart_app_step.dart @@ -11,5 +11,5 @@ class RestartAppStep extends WhenWithWorld { } @override - RegExp get pattern => RegExp(r"I restart the app"); + RegExp get pattern => RegExp(r'I restart the app'); } 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 c533481..1a525d1 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 @@ -16,7 +16,7 @@ import 'package:gherkin/gherkin.dart'; class ThenExpectElementToHaveValue extends Then2WithWorld { @override - RegExp get pattern => RegExp(r"I expect the {string} to be {string}$"); + RegExp get pattern => RegExp(r'I expect the {string} to be {string}$'); @override Future executeStep(String key, String value) async { 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 dd999a9..24f0ba0 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 @@ -16,7 +16,7 @@ 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)$"); + 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 { diff --git a/lib/src/flutter/steps/when_fill_field_step.dart b/lib/src/flutter/steps/when_fill_field_step.dart index 0b46fda..b87a2d8 100644 --- a/lib/src/flutter/steps/when_fill_field_step.dart +++ b/lib/src/flutter/steps/when_fill_field_step.dart @@ -22,5 +22,5 @@ class WhenFillFieldStep extends When2WithWorld { } @override - RegExp get pattern => RegExp(r"I fill the {string} field with {string}"); + RegExp get pattern => RegExp(r'I fill the {string} field with {string}'); } diff --git a/lib/src/flutter/steps/when_pause_step.dart b/lib/src/flutter/steps/when_pause_step.dart index 15397de..e85fa1b 100644 --- a/lib/src/flutter/steps/when_pause_step.dart +++ b/lib/src/flutter/steps/when_pause_step.dart @@ -18,5 +18,5 @@ class WhenPauseStep extends When1 { } @override - RegExp get pattern => RegExp(r"I pause for {int} second(s)"); + RegExp get pattern => RegExp(r'I pause for {int} second(s)'); } 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 1bedfd6..dcc7170 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 @@ -12,7 +12,7 @@ import 'package:gherkin/gherkin.dart'; /// `When I tap the back widget"` class WhenTapBackButtonWidget extends WhenWithWorld { @override - RegExp get pattern => RegExp(r"I tap the back (?:button|element|widget)$"); + RegExp get pattern => RegExp(r'I tap the back (?:button|element|widget)$'); @override Future executeStep() async { diff --git a/lib/src/flutter/steps/when_tap_widget_step.dart b/lib/src/flutter/steps/when_tap_widget_step.dart index b109170..1bd5f41 100644 --- a/lib/src/flutter/steps/when_tap_widget_step.dart +++ b/lib/src/flutter/steps/when_tap_widget_step.dart @@ -20,7 +20,7 @@ import 'package:gherkin/gherkin.dart'; class WhenTapWidget extends When1WithWorld { @override RegExp get pattern => RegExp( - r"I tap the {string} (?:button|element|label|icon|field|text|widget)$"); + r'I tap the {string} (?:button|element|label|icon|field|text|widget)$'); @override Future executeStep(String key) async { @@ -37,3 +37,20 @@ class WhenTapWidget extends When1WithWorld { ); } } + +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$'); + + @override + Future executeStep(String key) async { + final finder = find.byValueKey(key); + + await FlutterDriverUtils.tap( + world.driver, + finder, + timeout: timeout * .45, + ); + } +} diff --git a/lib/src/flutter/utils/driver_utils.dart b/lib/src/flutter/utils/driver_utils.dart index af32846..f72b3c5 100644 --- a/lib/src/flutter/utils/driver_utils.dart +++ b/lib/src/flutter/utils/driver_utils.dart @@ -75,15 +75,15 @@ class FlutterDriverUtils { /// condition does not return true with the timeout period. static Future waitUntil( FlutterDriver driver, - Future condition(), { + Future Function() condition, { Duration timeout = const Duration(seconds: 10), Duration pollInterval = const Duration(milliseconds: 500), }) async { return Future.microtask(() async { final completer = Completer(); - int maxAttempts = + var maxAttempts = (timeout.inMilliseconds / pollInterval.inMilliseconds).round(); - int attempts = 0; + var attempts = 0; while (attempts < maxAttempts) { final result = await condition(); diff --git a/pubspec.lock b/pubspec.lock index b328652..1fd9bf9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,56 +7,56 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "3.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.39.4" + version: "0.39.8" archive: dependency: transitive description: name: archive url: "https://pub.dartlang.org" source: hosted - version: "2.0.11" + version: "2.0.13" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "1.5.2" + version: "1.6.0" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.0" + version: "2.4.1" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "2.0.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "1.1.3" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.11" + version: "1.14.12" convert: dependency: transitive description: @@ -77,7 +77,7 @@ packages: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.1.3" + version: "2.1.4" csslib: dependency: transitive description: @@ -139,7 +139,7 @@ packages: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.12.0+4" + version: "0.12.1" http_multi_server: dependency: transitive description: @@ -160,21 +160,21 @@ packages: name: image url: "https://pub.dartlang.org" source: hosted - version: "2.1.4" + version: "2.1.12" intl: dependency: transitive description: name: intl url: "https://pub.dartlang.org" source: hosted - version: "0.16.0" + version: "0.16.1" io: dependency: transitive description: name: io url: "https://pub.dartlang.org" source: hosted - version: "0.3.3" + version: "0.3.4" js: dependency: transitive description: @@ -237,7 +237,7 @@ packages: name: node_io url: "https://pub.dartlang.org" source: hosted - version: "1.0.1+2" + version: "1.1.0" node_preamble: dependency: transitive description: @@ -251,14 +251,7 @@ packages: name: package_config url: "https://pub.dartlang.org" source: hosted - version: "1.9.2" - package_resolver: - dependency: transitive - description: - name: package_resolver - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.10" + version: "1.9.3" path: dependency: transitive description: @@ -272,7 +265,7 @@ packages: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.8.0+1" + version: "1.9.0" petitparser: dependency: transitive description: @@ -307,14 +300,14 @@ packages: name: pub_semver url: "https://pub.dartlang.org" source: hosted - version: "1.4.2" + version: "1.4.4" quiver: dependency: transitive description: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.1.3" shelf: dependency: transitive description: @@ -328,7 +321,7 @@ packages: name: shelf_packages_handler url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.0" shelf_static: dependency: transitive description: @@ -354,7 +347,7 @@ packages: name: source_map_stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.1.5" + version: "2.0.0" source_maps: dependency: transitive description: @@ -368,7 +361,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.5.5" + version: "1.7.0" stack_trace: dependency: transitive description: @@ -390,6 +383,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.5" + sync_http: + dependency: transitive + description: + name: sync_http + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" term_glyph: dependency: transitive description: @@ -403,21 +403,21 @@ packages: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.9.4" + version: "1.14.3" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.11" + version: "0.2.15" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.2.15" + version: "0.3.4" typed_data: dependency: transitive description: @@ -438,7 +438,7 @@ packages: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "2.3.1" + version: "4.0.2" vm_service_client: dependency: transitive description: @@ -452,7 +452,7 @@ packages: name: watcher url: "https://pub.dartlang.org" source: hosted - version: "0.9.7+14" + version: "0.9.7+15" web_socket_channel: dependency: transitive description: @@ -460,19 +460,34 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + webdriver: + dependency: transitive + description: + name: webdriver + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.3" xml: dependency: transitive description: name: xml url: "https://pub.dartlang.org" source: hosted - version: "3.5.0" + version: "3.6.1" yaml: dependency: transitive description: name: yaml url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "2.2.1" sdks: dart: ">=2.7.0 <3.0.0" + flutter: ">=1.13.0" diff --git a/pubspec.yaml b/pubspec.yaml index 4d9a75f..59f37bd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,10 +1,11 @@ name: flutter_gherkin description: A Gherkin / Cucumber parser and test runner for Dart and Flutter -version: 1.1.7+7 +version: 1.1.8 homepage: https://github.com/jonsamwell/flutter_gherkin environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.6.0 <3.0.0" + flutter: ">=1.13.0" dependencies: flutter: diff --git a/test/flutter_configuration_test.dart b/test/flutter_configuration_test.dart index 3cea705..2c4406e 100644 --- a/test/flutter_configuration_test.dart +++ b/test/flutter_configuration_test.dart @@ -13,9 +13,9 @@ import 'package:test/test.dart'; import 'mocks/step_definition_mock.dart'; void main() { - group("config", () { - group("prepare", () { - test("flutter app runner hook added", () { + group('config', () { + group('prepare', () { + test('flutter app runner hook added', () { final config = FlutterTestConfiguration(); expect(config.hooks, isNull); config.prepare(); @@ -24,36 +24,38 @@ void main() { expect(config.hooks.elementAt(0), (x) => x is FlutterAppRunnerHook); }); - test("common steps definition added", () { + test('common steps definition added', () { final config = FlutterTestConfiguration(); expect(config.stepDefinitions, isNull); config.prepare(); expect(config.stepDefinitions, isNotNull); - expect(config.stepDefinitions.length, 8); + expect(config.stepDefinitions.length, 9); expect(config.stepDefinitions.elementAt(0), (x) => x is ThenExpectElementToHaveValue); expect(config.stepDefinitions.elementAt(1), (x) => x is WhenTapWidget); expect(config.stepDefinitions.elementAt(2), + (x) => x is WhenTapWidgetWithoutScroll); + expect(config.stepDefinitions.elementAt(3), (x) => x is WhenTapBackButtonWidget); expect( - config.stepDefinitions.elementAt(3), (x) => x is GivenOpenDrawer); - expect(config.stepDefinitions.elementAt(4), (x) => x is WhenPauseStep); + config.stepDefinitions.elementAt(4), (x) => x is GivenOpenDrawer); + expect(config.stepDefinitions.elementAt(5), (x) => x is WhenPauseStep); expect( - config.stepDefinitions.elementAt(5), (x) => x is WhenFillFieldStep); - expect(config.stepDefinitions.elementAt(6), + config.stepDefinitions.elementAt(6), (x) => x is WhenFillFieldStep); + expect(config.stepDefinitions.elementAt(7), (x) => x is ThenExpectWidgetToBePresent); - expect(config.stepDefinitions.elementAt(7), (x) => x is RestartAppStep); + expect(config.stepDefinitions.elementAt(8), (x) => x is RestartAppStep); }); - test("common step definition added to existing steps", () { + test('common step definition added to existing steps', () { final config = FlutterTestConfiguration() ..stepDefinitions = [MockStepDefinition()]; expect(config.stepDefinitions.length, 1); config.prepare(); expect(config.stepDefinitions, isNotNull); - expect(config.stepDefinitions.length, 9); + expect(config.stepDefinitions.length, 10); expect(config.stepDefinitions.elementAt(0), (x) => x is MockStepDefinition); expect(config.stepDefinitions.elementAt(1), diff --git a/test/mocks/step_definition_mock.dart b/test/mocks/step_definition_mock.dart index 5af0f67..820c76f 100644 --- a/test/mocks/step_definition_mock.dart +++ b/test/mocks/step_definition_mock.dart @@ -1,6 +1,6 @@ import 'package:gherkin/gherkin.dart'; -typedef Future OnRunCode(Iterable parameters); +typedef OnRunCode = Future Function(Iterable parameters); class MockStepDefinition extends StepDefinitionBase { bool hasRun = false;