From 0961916daa100bd08782784d8c03daa94b8e31db Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 17 Jun 2022 16:25:39 +1000 Subject: [PATCH] feat(config): re-worked configuration so it can stay mostly immutable - Fix #195: Adding missing export for `wait_until_key_exists_step.dart` - Fix #226: Allow compatibility with dev and master flutter branches - Feat #218: Allow retry steps in case of intermittent failure by setting the configuration properties `stepMaxRetries` & `retryDelay` - Fix #210 & #191: Ability to take screenshots on web - Fix #198: Allow the use of implicit pumpAndSettle methods in the app driver to be turned off using the configuration property `waitImplicitlyAfterAction` --- .flutter-plugins | 2 +- .flutter-plugins-dependencies | 2 +- .gitignore | 20 +- CHANGELOG.md | 9 + .../android/app/build.gradle | 2 +- .../android/app/src/main/AndroidManifest.xml | 1 + .../android/build.gradle | 4 +- .../gradle/wrapper/gradle-wrapper.properties | 3 +- example_with_flutter_driver/pubspec.lock | 150 ++++++- example_with_flutter_driver/pubspec.yaml | 3 +- .../test_driver/test_harness.dart | 34 +- .../android/app/build.gradle | 6 +- .../android/app/src/main/AndroidManifest.xml | 3 +- .../android/build.gradle | 4 +- .../gradle/wrapper/gradle-wrapper.properties | 3 +- .../integration_test/features/create.feature | 8 - .../integration_test/features/swiping.feature | 1 - .../gherkin/configuration.dart | 42 +- .../gherkin_suite_test.g.dart | 391 +++++++++--------- .../components/add_todo_component.dart | 1 - .../lib/widgets/views/home_view.dart | 1 - example_with_integration_test/pubspec.lock | 43 +- example_with_integration_test/pubspec.yaml | 4 +- lib/flutter_gherkin.dart | 1 + .../widget_tester_app_driver_adapter.dart | 60 ++- .../gherkin_suite_test_generator.dart | 15 +- .../flutter_driver_test_configuration.dart | 133 ++++-- .../flutter_test_configuration.dart | 123 +++--- lib/src/flutter/hooks/app_runner_hook.dart | 8 +- .../reporters/flutter_driver_reporter.dart | 18 +- .../gherkin_integration_test_runner.dart | 56 ++- .../steps/sibling_contains_text_step.dart | 2 - lib/src/flutter/steps/swipe_step.dart | 1 - .../steps/tap_text_within_widget_step.dart | 1 - .../steps/tap_widget_of_type_step.dart | 1 - .../steps/tap_widget_of_type_within_step.dart | 1 - .../steps/tap_widget_with_text_step.dart | 1 - lib/src/flutter/steps/text_exists_step.dart | 1 - .../steps/text_exists_within_step.dart | 1 - .../steps/wait_until_key_exists_step.dart | 1 - .../steps/wait_until_type_exists_step.dart | 1 - pubspec.lock | 37 +- pubspec.yaml | 10 +- test/flutter_configuration_test.dart | 60 ++- 44 files changed, 743 insertions(+), 526 deletions(-) diff --git a/.flutter-plugins b/.flutter-plugins index 272ca46..19b2b88 100644 --- a/.flutter-plugins +++ b/.flutter-plugins @@ -1,2 +1,2 @@ # This is a generated file; do not edit or check into version control. -integration_test=C:\\Google\\flutter\\packages\\integration_test\\ +integration_test=C:\\Development\\flutter\\packages\\integration_test\\ diff --git a/.flutter-plugins-dependencies b/.flutter-plugins-dependencies index 37517a0..26a3440 100644 --- a/.flutter-plugins-dependencies +++ b/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"integration_test","path":"C:\\\\Google\\\\flutter\\\\packages\\\\integration_test\\\\","dependencies":[]}],"android":[{"name":"integration_test","path":"C:\\\\Google\\\\flutter\\\\packages\\\\integration_test\\\\","dependencies":[]}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"integration_test","dependencies":[]}],"date_created":"2021-11-24 07:12:59.207785","version":"2.5.3"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"integration_test","path":"C:\\\\Development\\\\flutter\\\\packages\\\\integration_test\\\\","native_build":true,"dependencies":[]}],"android":[{"name":"integration_test","path":"C:\\\\Development\\\\flutter\\\\packages\\\\integration_test\\\\","native_build":true,"dependencies":[]}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"integration_test","dependencies":[]}],"date_created":"2022-06-17 15:37:47.695519","version":"3.0.2"} \ No newline at end of file diff --git a/.gitignore b/.gitignore index f0e1ae8..25fbfc3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,23 @@ .DS_Store -.dart_tool/ -.packages -.pub/ - -build/ ios/.generated/ ios/Flutter/Generated.xcconfig ios/Runner/GeneratedPluginRegistrant.* node_modules package-lock.json + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +**/generated_plugin_registrant.dart +.packages +.pub-cache/ +.pub/ +build/ +flutter_*.png +linked_*.ds +unlinked.ds +unlinked_spec.ds \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 275c4c6..c9f33b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [3.0.0] - 17/06/2022 + +- Fix #195: Adding missing export for `wait_until_key_exists_step.dart` +- Fix #226: Allow compatibility with dev and master flutter branches +- Feat #218: Allow retry steps in case of intermittent failure by setting the configuration properties `stepMaxRetries` & `retryDelay` +- Fix #210 & #191: Ability to take screenshots on web +- Fix #198: Allow the use of implicit pumpAndSettle methods in the app driver to be turned off using the configuration property `waitImplicitlyAfterAction` + + ## [3.0.0-rc.9] - 18/11/2021 - Fix: #172: Fix for the `StdoutReporter` when running against the web diff --git a/example_with_flutter_driver/android/app/build.gradle b/example_with_flutter_driver/android/app/build.gradle index d6959fd..b014181 100644 --- a/example_with_flutter_driver/android/app/build.gradle +++ b/example_with_flutter_driver/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 29 + compileSdkVersion 31 sourceSets { main.java.srcDirs += 'src/main/kotlin' diff --git a/example_with_flutter_driver/android/app/src/main/AndroidManifest.xml b/example_with_flutter_driver/android/app/src/main/AndroidManifest.xml index efbe15c..fd6acc8 100644 --- a/example_with_flutter_driver/android/app/src/main/AndroidManifest.xml +++ b/example_with_flutter_driver/android/app/src/main/AndroidManifest.xml @@ -11,6 +11,7 @@ android:icon="@mipmap/ic_launcher"> =2.12.3 <3.0.0" + dart: ">=2.17.0 <3.0.0" flutter: ">=2.2.0" diff --git a/example_with_flutter_driver/pubspec.yaml b/example_with_flutter_driver/pubspec.yaml index 9490ad5..6106ae6 100644 --- a/example_with_flutter_driver/pubspec.yaml +++ b/example_with_flutter_driver/pubspec.yaml @@ -6,7 +6,8 @@ publish_to: 'none' version: 1.0.0+1 environment: - sdk: '>=2.12.0 <3.0.0' + sdk: '>=2.17.0 <3.0.0' + flutter: ">=2.2.0" dependencies: flutter: diff --git a/example_with_flutter_driver/test_driver/test_harness.dart b/example_with_flutter_driver/test_driver/test_harness.dart index ac7cdf0..0905ba6 100644 --- a/example_with_flutter_driver/test_driver/test_harness.dart +++ b/example_with_flutter_driver/test_driver/test_harness.dart @@ -3,22 +3,26 @@ import 'package:flutter_gherkin/flutter_gherkin_with_driver.dart'; import 'package:gherkin/gherkin.dart'; Future main() { - final config = FlutterDriverTestConfiguration.DEFAULT( - Iterable.empty(), - featurePath: 'features/**.feature', + final config = FlutterDriverTestConfiguration( + features: [RegExp('features/**.feature')], 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 and not @ignore' // 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 + targetAppWorkingDirectory: '../', + 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 and not @ignore', // 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 + ); return GherkinRunner().execute(config); } diff --git a/example_with_integration_test/android/app/build.gradle b/example_with_integration_test/android/app/build.gradle index fa23717..14f7512 100644 --- a/example_with_integration_test/android/app/build.gradle +++ b/example_with_integration_test/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 29 + compileSdkVersion 31 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -39,8 +39,8 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.example_with_integration_test" - minSdkVersion 16 - targetSdkVersion 29 + minSdkVersion 23 + targetSdkVersion 31 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } diff --git a/example_with_integration_test/android/app/src/main/AndroidManifest.xml b/example_with_integration_test/android/app/src/main/AndroidManifest.xml index 9178e7d..21dd7f8 100644 --- a/example_with_integration_test/android/app/src/main/AndroidManifest.xml +++ b/example_with_integration_test/android/app/src/main/AndroidManifest.xml @@ -6,11 +6,12 @@ additional functionality it is fine to subclass or reimplement FlutterApplication and put your custom class here. --> Future.value(), - ), - ] - ..createWorld = (config) => Future.value(CustomWorld()); + hooks: [ + ResetAppHook(), + ], + reporters: [ + StdoutReporter(MessageLevel.error) + ..setWriteLineFn(print) + ..setWriteFn(print), + ProgressReporter() + ..setWriteLineFn(print) + ..setWriteFn(print), + TestRunSummaryReporter() + ..setWriteLineFn(print) + ..setWriteFn(print), + ], + createWorld: (config) => Future.value(CustomWorld()), +); Future Function(World) appInitializationFn = (World world) async { // ensure a new injector instance is created each time diff --git a/example_with_integration_test/integration_test/gherkin_suite_test.g.dart b/example_with_integration_test/integration_test/gherkin_suite_test.g.dart index 094411b..c1178de 100644 --- a/example_with_integration_test/integration_test/gherkin_suite_test.g.dart +++ b/example_with_integration_test/integration_test/gherkin_suite_test.g.dart @@ -21,209 +21,152 @@ class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner { void testFeature0() { runFeature( - 'Checking data:', + 'Swiping:', ['@tag'], () { runScenario( - 'User can have data', - ['@tag', '@tag1'], - (TestDependencies dependencies) async { - await runStep( - 'Given I have item with data', - [ - """{ - "glossary": { - "title": "example glossary", - "GlossDiv": { - "title": "S", - "GlossList": { - "GlossEntry": { - "ID": "SGML", - "SortAs": "SGML", - "GlossTerm": "Standard Generalized Markup Language", - "Acronym": "SGML", - "Abbrev": "ISO 8879:1986", - "GlossDef": { - "para": "A meta-markup language, used to create markup languages such as DocBook.", - "GlossSeeAlso": [ - "GML", - "XML" - ] - }, - "GlossSee": "markup" - } - } - } - } -}""" - ], - null, - dependencies, - ); - }, + name: 'User can swipe cards left and right', + path: + 'C:\Development\github\flutter_gherkin\example_with_integration_test\.\integration_test\features\swiping.feature', + tags: ['@tag'], + steps: [ + (TestDependencies dependencies, bool hasToSkip) async { + return await runStep( + 'Given I swipe right by 250 pixels on the "scrollable cards"`', + [], + null, + dependencies, + hasToSkip, + ); + }, + (TestDependencies dependencies, bool hasToSkip) async { + return await runStep( + 'Then Then I expect the text "Page 2" to be present', + [], + null, + dependencies, + hasToSkip, + ); + }, + (TestDependencies dependencies, bool hasToSkip) async { + return await runStep( + 'Given I swipe left by 250 pixels on the "scrollable cards"`', + [], + null, + dependencies, + hasToSkip, + ); + }, + (TestDependencies dependencies, bool hasToSkip) async { + return await runStep( + 'Then Then I expect the text "Page 1" to be present', + [], + null, + dependencies, + hasToSkip, + ); + } + ], onBefore: () async => onBeforeRunFeature( - 'Checking data', + 'Swiping', ['@tag'], ), - onAfter: () async => onAfterRunFeature( - 'Checking data', - ), + onAfter: () async => onAfterRunFeature('Swiping', + 'C:\Development\github\flutter_gherkin\example_with_integration_test\.\integration_test\features\swiping.feature'), ); }, ); } void testFeature1() { - runFeature( - 'Swiping:', - ['@tag'], - () { - runScenario( - 'User can swipe cards left and right', - ['@tag', '@debug'], - (TestDependencies dependencies) async { - await runStep( - 'Given I swipe right by 250 pixels on the "scrollable cards"`', - [], - null, - dependencies, - ); - - await runStep( - 'Then Then I expect the text "Page 2" to be present', - [], - null, - dependencies, - ); - - await runStep( - 'Given I swipe left by 250 pixels on the "scrollable cards"`', - [], - null, - dependencies, - ); - - await runStep( - 'Then Then I expect the text "Page 1" to be present', - [], - null, - dependencies, - ); - }, - onBefore: () async => onBeforeRunFeature( - 'Swiping', - ['@tag'], - ), - onAfter: () async => onAfterRunFeature( - 'Swiping', - ), - ); - }, - ); - } - - void testFeature2() { runFeature( 'Creating todos:', ['@tag'], () { runScenario( - 'User can create a new todo item', - ['@tag', '@tag1', '@tag_two'], - (TestDependencies dependencies) async { - await runStep( - 'Given I fill the "todo" field with "Buy carrots"', - [], - null, - dependencies, - ); - - await runStep( - 'When I tap the \'add\' button', - [], - null, - dependencies, - ); - - await runStep( - 'Then I expect the todo list', - [], - GherkinTable.fromJson('[{"Todo":"Buy carrots"}]'), - dependencies, - ); - }, - onBefore: () async => onBeforeRunFeature( - 'Creating todos', - ['@tag'], - ), - onAfter: null, - ); - - runScenario( - 'User can create multiple new todo items', - ['@tag', '@debug'], - (TestDependencies dependencies) async { - await runStep( - 'Given I fill the "todo" field with "Buy carrots"', - [], - null, - dependencies, - ); - - await runStep( - 'When I tap the "add" button', - [], - null, - dependencies, - ); - - await runStep( - 'And I fill the "todo" field with "Buy apples"', - [], - null, - dependencies, - ); - - await runStep( - 'When I tap the "add" button', - [], - null, - dependencies, - ); - - await runStep( - 'And I fill the "todo" field with "Buy blueberries"', - [], - null, - dependencies, - ); - - await runStep( - 'When I tap the "add" button', - [], - null, - dependencies, - ); - - await runStep( - 'Then I expect the todo list', - [], - GherkinTable.fromJson( - '[{"Todo":"Buy blueberries"},{"Todo":"Buy apples"},{"Todo":"Buy carrots"}]'), - dependencies, - ); - - await runStep( - 'Given I wait 5 seconds for the animation to complete', - [], - null, - dependencies, - ); - - await runStep( - 'Given I have item with data', - [ - """{ + name: 'User can create multiple new todo items', + path: + 'C:\Development\github\flutter_gherkin\example_with_integration_test\.\integration_test\features\create.feature', + tags: ['@tag', '@debug'], + steps: [ + (TestDependencies dependencies, bool hasToSkip) async { + return await runStep( + 'Given I fill the "todo" field with "Buy carrots"', + [], + null, + dependencies, + hasToSkip, + ); + }, + (TestDependencies dependencies, bool hasToSkip) async { + return await runStep( + 'When I tap the "add" button', + [], + null, + dependencies, + hasToSkip, + ); + }, + (TestDependencies dependencies, bool hasToSkip) async { + return await runStep( + 'And I fill the "todo" field with "Buy apples"', + [], + null, + dependencies, + hasToSkip, + ); + }, + (TestDependencies dependencies, bool hasToSkip) async { + return await runStep( + 'When I tap the "add" button', + [], + null, + dependencies, + hasToSkip, + ); + }, + (TestDependencies dependencies, bool hasToSkip) async { + return await runStep( + 'And I fill the "todo" field with "Buy blueberries"', + [], + null, + dependencies, + hasToSkip, + ); + }, + (TestDependencies dependencies, bool hasToSkip) async { + return await runStep( + 'When I tap the "add" button', + [], + null, + dependencies, + hasToSkip, + ); + }, + (TestDependencies dependencies, bool hasToSkip) async { + return await runStep( + 'Then I expect the todo list', + [], + GherkinTable.fromJson( + '[{"Todo":"Buy blueberries"},{"Todo":"Buy apples"},{"Todo":"Buy carrots"}]'), + dependencies, + hasToSkip, + ); + }, + (TestDependencies dependencies, bool hasToSkip) async { + return await runStep( + 'Given I wait 5 seconds for the animation to complete', + [], + null, + dependencies, + hasToSkip, + ); + }, + (TestDependencies dependencies, bool hasToSkip) async { + return await runStep( + 'Given I have item with data', + [ + """{ "glossary": { "title": "example glossary", "GlossDiv": { @@ -248,15 +191,77 @@ class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner { } } }""" - ], - null, - dependencies, - ); - }, - onBefore: null, - onAfter: () async => onAfterRunFeature( + ], + null, + dependencies, + hasToSkip, + ); + } + ], + onBefore: () async => onBeforeRunFeature( 'Creating todos', + ['@tag'], ), + onAfter: () async => onAfterRunFeature('Creating todos', + 'C:\Development\github\flutter_gherkin\example_with_integration_test\.\integration_test\features\create.feature'), + ); + }, + ); + } + + void testFeature2() { + runFeature( + 'Checking data:', + ['@tag'], + () { + runScenario( + name: 'User can have data', + path: + 'C:\Development\github\flutter_gherkin\example_with_integration_test\.\integration_test\features\check.feature', + tags: ['@tag', '@tag1'], + steps: [ + (TestDependencies dependencies, bool hasToSkip) async { + return await runStep( + 'Given I have item with data', + [ + """{ + "glossary": { + "title": "example glossary", + "GlossDiv": { + "title": "S", + "GlossList": { + "GlossEntry": { + "ID": "SGML", + "SortAs": "SGML", + "GlossTerm": "Standard Generalized Markup Language", + "Acronym": "SGML", + "Abbrev": "ISO 8879:1986", + "GlossDef": { + "para": "A meta-markup language, used to create markup languages such as DocBook.", + "GlossSeeAlso": [ + "GML", + "XML" + ] + }, + "GlossSee": "markup" + } + } + } + } +}""" + ], + null, + dependencies, + hasToSkip, + ); + } + ], + onBefore: () async => onBeforeRunFeature( + 'Checking data', + ['@tag'], + ), + onAfter: () async => onAfterRunFeature('Checking data', + 'C:\Development\github\flutter_gherkin\example_with_integration_test\.\integration_test\features\check.feature'), ); }, ); diff --git a/example_with_integration_test/lib/widgets/components/add_todo_component.dart b/example_with_integration_test/lib/widgets/components/add_todo_component.dart index d780b35..62893ce 100644 --- a/example_with_integration_test/lib/widgets/components/add_todo_component.dart +++ b/example_with_integration_test/lib/widgets/components/add_todo_component.dart @@ -1,7 +1,6 @@ import 'package:example_with_integration_test/models/todo_model.dart'; import 'package:example_with_integration_test/widgets/view_utils_mixin.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:rxdart/rxdart.dart'; class AddTodoComponent extends StatefulWidget { diff --git a/example_with_integration_test/lib/widgets/views/home_view.dart b/example_with_integration_test/lib/widgets/views/home_view.dart index fbbaf8c..7318f1d 100644 --- a/example_with_integration_test/lib/widgets/views/home_view.dart +++ b/example_with_integration_test/lib/widgets/views/home_view.dart @@ -3,7 +3,6 @@ import 'package:example_with_integration_test/models/todo_model.dart'; import 'package:example_with_integration_test/models/todo_status_enum.dart'; import 'package:example_with_integration_test/widgets/components/add_todo_component.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import '../view_utils_mixin.dart'; diff --git a/example_with_integration_test/pubspec.lock b/example_with_integration_test/pubspec.lock index 938c21f..5bd4f82 100644 --- a/example_with_integration_test/pubspec.lock +++ b/example_with_integration_test/pubspec.lock @@ -21,7 +21,7 @@ packages: name: archive url: "https://pub.dartlang.org" source: hosted - version: "3.1.2" + version: "3.1.11" args: dependency: transitive description: @@ -35,7 +35,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.1" + version: "2.8.2" boolean_selector: dependency: transitive description: @@ -105,7 +105,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" charcode: dependency: transitive description: @@ -147,7 +147,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" + version: "1.16.0" convert: dependency: transitive description: @@ -175,7 +175,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" ffi: dependency: transitive description: @@ -213,7 +213,7 @@ packages: path: ".." relative: true source: path - version: "3.0.0-rc.9" + version: "3.0.0" flutter_simple_dependency_injection: dependency: "direct main" description: @@ -249,7 +249,7 @@ packages: name: gherkin url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "3.0.0+1" glob: dependency: transitive description: @@ -296,7 +296,7 @@ packages: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.3" + version: "0.6.4" json_annotation: dependency: "direct main" description: @@ -324,7 +324,14 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10" + version: "0.12.11" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.4" meta: dependency: transitive description: @@ -352,7 +359,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" path_provider_linux: dependency: transitive description: @@ -380,7 +387,7 @@ packages: name: platform url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.1.0" plugin_platform_interface: dependency: transitive description: @@ -401,7 +408,7 @@ packages: name: process url: "https://pub.dartlang.org" source: hosted - version: "4.2.3" + version: "4.2.4" pub_semver: dependency: transitive description: @@ -504,7 +511,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" stack_trace: dependency: transitive description: @@ -553,7 +560,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.2" + version: "0.4.9" timing: dependency: transitive description: @@ -574,21 +581,21 @@ packages: name: uuid url: "https://pub.dartlang.org" source: hosted - version: "3.0.5" + version: "3.0.6" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.2" vm_service: dependency: transitive description: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "7.1.1" + version: "8.2.2" watcher: dependency: transitive description: @@ -632,5 +639,5 @@ packages: source: hosted version: "3.1.0" sdks: - dart: ">=2.14.0 <3.0.0" + dart: ">=2.17.0 <3.0.0" flutter: ">=2.5.0" diff --git a/example_with_integration_test/pubspec.yaml b/example_with_integration_test/pubspec.yaml index 9ab64ac..9ee14fe 100644 --- a/example_with_integration_test/pubspec.yaml +++ b/example_with_integration_test/pubspec.yaml @@ -6,8 +6,8 @@ publish_to: 'none' version: 1.0.0+1 environment: - sdk: '>=2.14.0 <3.0.0' - flutter: ">=2.5.0" + sdk: '>=2.17.0 <3.0.0' + flutter: ">=2.2.0" dependencies: flutter: diff --git a/lib/flutter_gherkin.dart b/lib/flutter_gherkin.dart index 1ee9704..746c51d 100644 --- a/lib/flutter_gherkin.dart +++ b/lib/flutter_gherkin.dart @@ -29,6 +29,7 @@ export 'src/flutter/steps/text_exists_within_step.dart'; export 'src/flutter/steps/wait_until_key_exists_step.dart'; export 'src/flutter/steps/when_tap_the_back_button_step.dart'; export 'src/flutter/steps/wait_until_type_exists_step.dart'; +export 'src/flutter/steps/wait_until_key_exists_step.dart'; // Hooks export 'src/flutter/hooks/attach_screenshot_on_failed_step_hook.dart'; diff --git a/lib/src/flutter/adapters/widget_tester_app_driver_adapter.dart b/lib/src/flutter/adapters/widget_tester_app_driver_adapter.dart index 06b7792..062e65b 100644 --- a/lib/src/flutter/adapters/widget_tester_app_driver_adapter.dart +++ b/lib/src/flutter/adapters/widget_tester_app_driver_adapter.dart @@ -1,16 +1,22 @@ -import 'dart:async'; -import 'dart:ui' as ui show ImageByteFormat; +import 'dart:io' if (dart.library.html) 'dart:html'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; import 'app_driver_adapter.dart'; class WidgetTesterAppDriverAdapter extends AppDriverAdapter { - WidgetTesterAppDriverAdapter(WidgetTester rawAdapter) : super(rawAdapter); + IntegrationTestWidgetsFlutterBinding binding; + bool waitImplicitlyAfterAction; + + WidgetTesterAppDriverAdapter({ + required WidgetTester rawAdapter, + required this.binding, + required this.waitImplicitlyAfterAction, + }) : super(rawAdapter); @override Future waitForAppToSettle({ @@ -30,6 +36,21 @@ class WidgetTesterAppDriverAdapter } } + Future _implicitWait({ + Duration? duration = const Duration(milliseconds: 100), + Duration? timeout = const Duration(seconds: 30), + }) async { + if (waitImplicitlyAfterAction) { + try { + await nativeDriver.pumpAndSettle( + duration ?? const Duration(milliseconds: 100), + EnginePhase.sendSemanticsUpdate, + timeout ?? const Duration(seconds: 30), + ); + } catch (_) {} + } + } + @override Future widget( Finder finder, [ @@ -47,20 +68,15 @@ class WidgetTesterAppDriverAdapter } @override - Future> screenshot() { - var renderObject = nativeDriver.binding.renderViewElement?.renderObject; - - while (renderObject != null && !renderObject.isRepaintBoundary) { - renderObject = renderObject.parent as RenderObject; + Future> screenshot() async { + if (!kIsWeb && Platform.isAndroid) { + await binding.convertFlutterSurfaceToImage(); + await binding.pump(); } - assert(renderObject != null && !renderObject.debugNeedsPaint); - final layer = renderObject!.debugLayer as OffsetLayer; - - return layer - .toImage(renderObject.semanticBounds) - .then((value) => value.toByteData(format: ui.ImageByteFormat.png)) - .then((value) => value?.buffer.asUint8List() ?? List.empty()); + return binding.takeScreenshot( + 'screenshot_${DateTime.now().millisecondsSinceEpoch}', + ); } @override @@ -84,7 +100,7 @@ class WidgetTesterAppDriverAdapter Finder finder, { Duration? timeout = const Duration(seconds: 30), }) async { - await waitForAppToSettle(timeout: timeout); + await _implicitWait(timeout: timeout); final instance = await widget(finder); if (instance is Text) { @@ -109,7 +125,7 @@ class WidgetTesterAppDriverAdapter finder, text, ); - await waitForAppToSettle( + await _implicitWait( timeout: timeout, ); } @@ -120,7 +136,7 @@ class WidgetTesterAppDriverAdapter Duration? timeout = const Duration(seconds: 30), }) async { await nativeDriver.tap(finder); - await waitForAppToSettle( + await _implicitWait( timeout: timeout, ); } @@ -138,7 +154,7 @@ class WidgetTesterAppDriverAdapter duration: pressDuration, timeout: timeout, ); - await waitForAppToSettle(timeout: timeout); + await _implicitWait(timeout: timeout); } @override @@ -233,6 +249,6 @@ class WidgetTesterAppDriverAdapter @override Future pageBack() async { await nativeDriver.pageBack(); - await waitForAppToSettle(); + await _implicitWait(); } } diff --git a/lib/src/flutter/code_generation/generators/gherkin_suite_test_generator.dart b/lib/src/flutter/code_generation/generators/gherkin_suite_test_generator.dart index ac714d0..c87c35b 100644 --- a/lib/src/flutter/code_generation/generators/gherkin_suite_test_generator.dart +++ b/lib/src/flutter/code_generation/generators/gherkin_suite_test_generator.dart @@ -11,11 +11,11 @@ import 'package:source_gen/source_gen.dart'; class NoOpReporter extends MessageReporter { @override Future message(String message, MessageLevel level) async { - if(level == MessageLevel.info || level == MessageLevel.debug) { + if (level == MessageLevel.info || level == MessageLevel.debug) { print(message); - }else if(level == MessageLevel.warning) { + } else if (level == MessageLevel.warning) { print('\x1B[33m$message\x1B[0m'); - }else if(level == MessageLevel.error) { + } else if (level == MessageLevel.error) { print('\x1B[31m$message\x1B[0m'); } } @@ -63,7 +63,7 @@ void executeTestSuite( .getField('index')! .toIntValue()!; final executionOrder = ExecutionOrder.values[idx]; - final featureFiles = annotation + final featureFiles = annotation .read('featurePaths') .listValue .map((path) => Glob(path.toStringValue()!)) @@ -139,9 +139,10 @@ class FeatureFileTestGeneratorVisitor extends FeatureFileVisitor { '''; static const String SCENARIO_TEMPLATE = ''' runScenario( - '{{scenario_name}}', - {{tags}}, - [ + name: '{{scenario_name}}', + path: '{{path}}', + tags:{{tags}}, + steps: [ {{steps}} ], {{onBefore}} diff --git a/lib/src/flutter/configuration/flutter_driver_test_configuration.dart b/lib/src/flutter/configuration/flutter_driver_test_configuration.dart index adaf781..77782d5 100644 --- a/lib/src/flutter/configuration/flutter_driver_test_configuration.dart +++ b/lib/src/flutter/configuration/flutter_driver_test_configuration.dart @@ -1,28 +1,63 @@ import 'dart:async'; import 'dart:io'; import 'package:flutter_gherkin/flutter_gherkin_with_driver.dart'; -import 'package:flutter_gherkin/src/flutter/configuration/build_mode.dart'; -import 'package:flutter_gherkin/src/flutter/world/flutter_driver_world.dart'; -import 'package:flutter_gherkin/src/flutter/world/flutter_world.dart'; import 'package:flutter_gherkin/src/flutter/hooks/app_runner_hook.dart'; import 'package:flutter_driver/flutter_driver.dart'; import 'package:gherkin/gherkin.dart'; -import 'flutter_test_configuration.dart'; - class FlutterDriverTestConfiguration extends FlutterTestConfiguration { String? _observatoryDebuggerUri; + FlutterDriverTestConfiguration({ + String? featurePath = 'features/*.*.feature', + Iterable? features, + super.featureDefaultLanguage = 'en', + super.order = ExecutionOrder.random, + super.defaultTimeout = const Duration(seconds: 10), + super.featureFileMatcher = const IoFeatureFileAccessor(), + super.featureFileReader = const IoFeatureFileAccessor(), + super.stopAfterTestFailed = false, + super.tagExpression, + super.hooks, + super.reporters = const [], + super.createWorld, + super.waitImplicitlyAfterAction = true, + super.customStepParameterDefinitions, + super.stepDefinitions, + this.targetAppPath = 'test_driver/app.dart', + this.targetAppWorkingDirectory, + this.buildFlavor, + this.targetDeviceId, + this.runningAppProtocolEndpointUri, + this.onBeforeFlutterDriverConnect, + this.onAfterFlutterDriverConnect, + this.restartAppBetweenScenarios = true, + this.logFlutterProcessOutput = false, + this.keepAppRunningAfterTests = false, + this.verboseFlutterProcessLogs = false, + this.build = true, + this.buildMode = BuildMode.Debug, + this.flutterBuildTimeout = const Duration(seconds: 90), + this.flutterDriverReconnectionDelay = const Duration(seconds: 2), + this.flutterDriverMaxConnectionAttempts = 3, + }) : + // assert(featurePath != null && features != null), + super( + features: features != null ? features : [RegExp(featurePath!)], + ); + /// 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 FlutterDriverTestConfiguration DEFAULT( Iterable> steps, { String featurePath = 'features/*.*.feature', String targetAppPath = 'test_driver/app.dart', + String? targetAppWorkingDirectory, + bool restartAppBetweenScenarios = true, }) { - return FlutterDriverTestConfiguration() - ..features = [RegExp(featurePath)] - ..reporters = [ + return FlutterDriverTestConfiguration( + features: [RegExp(featurePath)], + reporters: [ StdoutReporter(MessageLevel.error), ProgressReporter(), TestRunSummaryReporter(), @@ -32,81 +67,84 @@ class FlutterDriverTestConfiguration extends FlutterTestConfiguration { logInfoMessages: false, logWarningMessages: false, ), - ] - ..targetAppPath = targetAppPath - ..stepDefinitions = steps - ..restartAppBetweenScenarios = true; + ], + targetAppPath: targetAppPath, + targetAppWorkingDirectory: targetAppWorkingDirectory, + stepDefinitions: steps, + restartAppBetweenScenarios: restartAppBetweenScenarios, + ); } /// restarts the application under test between each scenario. /// Defaults to true to avoid the application being in an invalid state /// before each test - bool restartAppBetweenScenarios = true; + final bool restartAppBetweenScenarios; /// The target app to run the tests against /// Defaults to "test_driver/app.dart" - String targetAppPath = 'test_driver/app.dart'; + final String targetAppPath; /// Option to define the working directory for the process that runs the app under test (optional) /// Handy if your app is separated from your tests as flutter needs to be able to find a pubspec file - String? targetAppWorkingDirectory; + final String? targetAppWorkingDirectory; /// The build flavor to run the tests against (optional) /// Defaults to null - String? buildFlavor; + final String? buildFlavor; /// The default build mode used for running tests is --debug. /// We are exposing the option to run the tests also in --profile mode - BuildMode buildMode = BuildMode.Debug; + final BuildMode buildMode; /// If the application should be built prior to running the tests /// Defaults to true - bool build = true; + final bool build; /// The target device id to run the tests against when multiple devices detected /// Defaults to null - String? targetDeviceId; + final String? targetDeviceId; /// Will keep the Flutter application running when done testing /// Defaults to false - bool keepAppRunningAfterTests = false; + final bool keepAppRunningAfterTests; /// Logs Flutter process output to stdout /// The Flutter process is use to start and driver the app under test. /// The output may contain build and run information /// Defaults to false - bool logFlutterProcessOutput = false; + final bool logFlutterProcessOutput; /// Sets the --verbose flag on the flutter process /// Defaults to false - bool verboseFlutterProcessLogs = false; + final bool verboseFlutterProcessLogs; /// Duration to wait for Flutter to build and start the app on the target device /// Slower machine may take longer to build and run a large app /// Defaults to 90 seconds - Duration flutterBuildTimeout = const Duration(seconds: 90); + final Duration flutterBuildTimeout; /// Duration to wait before reconnecting the Flutter driver to the app. /// On slower machines the app might not be in a state where the driver can successfully connect immediately /// Defaults to 2 seconds - Duration flutterDriverReconnectionDelay = const Duration(seconds: 2); + final Duration flutterDriverReconnectionDelay; /// The maximum times the flutter driver can try and connect to the running app /// Defaults to 3 - int flutterDriverMaxConnectionAttempts = 3; + final int flutterDriverMaxConnectionAttempts; /// 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=/` /// 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 - String? runningAppProtocolEndpointUri; + final String? runningAppProtocolEndpointUri; /// Called before any attempt to connect Flutter driver to the running application, Depending on your configuration this /// method will be called before each scenario is run. - Future Function()? onBeforeFlutterDriverConnect; + final Future Function()? onBeforeFlutterDriverConnect; /// Called after the successful connection of Flutter driver to the running application. Depending on your configuration this /// method will be called on each new connection usually before each scenario is run. - Future Function(FlutterDriver driver)? onAfterFlutterDriverConnect; + final Future Function(FlutterDriver driver)? + onAfterFlutterDriverConnect; void setObservatoryDebuggerUri(String uri) => _observatoryDebuggerUri = uri; @@ -157,20 +195,41 @@ class FlutterDriverTestConfiguration extends FlutterTestConfiguration { } @override - void prepare() { + TestConfiguration prepare() { super.prepare(); _ensureCorrectConfiguration(); final providedCreateWorld = createWorld; - createWorld = (config) async { - FlutterWorld? world; - if (providedCreateWorld != null) { - world = await providedCreateWorld(config) as FlutterWorld; - } - return await createFlutterWorld(config, world); - }; + return FlutterDriverTestConfiguration( + buildFlavor: this.buildFlavor, + customStepParameterDefinitions: this.customStepParameterDefinitions, + defaultTimeout: this.defaultTimeout, + featureDefaultLanguage: this.featureDefaultLanguage, + featureFileMatcher: this.featureFileMatcher, + featureFileReader: this.featureFileReader, + features: this.features, + onAfterFlutterDriverConnect: this.onAfterFlutterDriverConnect, + onBeforeFlutterDriverConnect: this.onBeforeFlutterDriverConnect, + order: this.order, + reporters: this.reporters, + restartAppBetweenScenarios: this.restartAppBetweenScenarios, + runningAppProtocolEndpointUri: this.runningAppProtocolEndpointUri, + stepDefinitions: this.stepDefinitions, + stopAfterTestFailed: this.stopAfterTestFailed, + tagExpression: this.tagExpression, + targetAppPath: this.targetAppPath, + targetAppWorkingDirectory: this.targetAppWorkingDirectory, + targetDeviceId: this.targetDeviceId, + createWorld: (config) async { + FlutterWorld? world; + if (providedCreateWorld != null) { + world = await providedCreateWorld(config) as FlutterWorld; + } - hooks = List.from(hooks ?? Iterable.empty())..add(FlutterAppRunnerHook()); + return await createFlutterWorld(config, world); + }, + hooks: List.from(hooks ?? Iterable.empty())..add(FlutterAppRunnerHook()), + ); } Future _attemptDriverConnection( diff --git a/lib/src/flutter/configuration/flutter_test_configuration.dart b/lib/src/flutter/configuration/flutter_test_configuration.dart index 920589e..32bf36f 100644 --- a/lib/src/flutter/configuration/flutter_test_configuration.dart +++ b/lib/src/flutter/configuration/flutter_test_configuration.dart @@ -1,34 +1,50 @@ import 'package:flutter_gherkin/flutter_gherkin_with_driver.dart'; import 'package:flutter_gherkin/src/flutter/parameters/existence_parameter.dart'; import 'package:flutter_gherkin/src/flutter/parameters/swipe_direction_parameter.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/sibling_contains_text_step.dart'; -import 'package:flutter_gherkin/src/flutter/steps/swipe_step.dart'; -import 'package:flutter_gherkin/src/flutter/steps/tap_text_within_widget_step.dart'; -import 'package:flutter_gherkin/src/flutter/steps/tap_widget_of_type_step.dart'; -import 'package:flutter_gherkin/src/flutter/steps/tap_widget_of_type_within_step.dart'; -import 'package:flutter_gherkin/src/flutter/steps/tap_widget_with_text_step.dart'; -import 'package:flutter_gherkin/src/flutter/steps/text_exists_step.dart'; -import 'package:flutter_gherkin/src/flutter/steps/text_exists_within_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/wait_until_key_exists_step.dart'; -import 'package:flutter_gherkin/src/flutter/steps/wait_until_type_exists_step.dart'; -import 'package:flutter_gherkin/src/flutter/steps/when_fill_field_step.dart'; import 'package:flutter_gherkin/src/flutter/steps/when_long_press_widget_step.dart'; -import 'package:flutter_gherkin/src/flutter/steps/when_pause_step.dart'; -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_test/flutter_test.dart'; import 'package:gherkin/gherkin.dart'; class FlutterTestConfiguration extends TestConfiguration { - + static final Iterable> _wellKnownParameters = [ + ExistenceParameter(), + SwipeDirectionParameter(), + ]; + static final _wellKnownStepDefinitions = [ + ThenExpectElementToHaveValue(), + WhenTapBackButtonWidget(), + WhenTapWidget(), + WhenTapWidgetWithoutScroll(), + WhenLongPressWidget(), + WhenLongPressWidgetWithoutScroll(), + WhenLongPressWidgetForDuration(), + GivenOpenDrawer(), + WhenPauseStep(), + WhenFillFieldStep(), + ThenExpectWidgetToBePresent(), + RestartAppStep(), + SiblingContainsTextStep(), + SwipeOnKeyStep(), + SwipeOnTextStep(), + TapTextWithinWidgetStep(), + TapWidgetOfTypeStep(), + TapWidgetOfTypeWithinStep(), + TapWidgetWithTextStep(), + TextExistsStep(), + TextExistsWithinStep(), + WaitUntilKeyExistsStep(), + WaitUntilTypeExistsStep(), + ]; + /// Enable semantics in a test by creating a [SemanticsHandle]. /// See: [testWidgets] and [WidgetController.ensureSemantics]. - bool semanticsEnabled = true; - + final bool semanticsEnabled; + + /// Set to `True` to wait implicit for pumpAndSettle() / waitForAppToSettle() functions after performing actions + /// Defaults to false + final bool waitImplicitlyAfterAction; + /// 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( @@ -36,49 +52,38 @@ class FlutterTestConfiguration extends TestConfiguration { String featurePath = 'integration_test/features/*.*.feature', String targetAppPath = 'test_driver/integration_test_driver.dart', }) { - return FlutterTestConfiguration() - ..reporters = [ + return FlutterTestConfiguration( + reporters: [ StdoutReporter(MessageLevel.error), ProgressReporter(), TestRunSummaryReporter(), // JsonReporter(path: './report.json'), - ] - ..stepDefinitions = steps; + ], + stepDefinitions: steps, + ); } - @override - void prepare() { - customStepParameterDefinitions = - List.from(customStepParameterDefinitions ?? Iterable.empty()) - ..addAll([ - ExistenceParameter(), - SwipeDirectionParameter(), - ]); - stepDefinitions = List.from(stepDefinitions ?? Iterable.empty()) - ..addAll([ - ThenExpectElementToHaveValue(), - WhenTapBackButtonWidget(), - WhenTapWidget(), - WhenTapWidgetWithoutScroll(), - WhenLongPressWidget(), - WhenLongPressWidgetWithoutScroll(), - WhenLongPressWidgetForDuration(), - GivenOpenDrawer(), - WhenPauseStep(), - WhenFillFieldStep(), - ThenExpectWidgetToBePresent(), - RestartAppStep(), - SiblingContainsTextStep(), - SwipeOnKeyStep(), - SwipeOnTextStep(), - TapTextWithinWidgetStep(), - TapWidgetOfTypeStep(), - TapWidgetOfTypeWithinStep(), - TapWidgetWithTextStep(), - TextExistsStep(), - TextExistsWithinStep(), - WaitUntilKeyExistsStep(), - WaitUntilTypeExistsStep(), - ]); - } + FlutterTestConfiguration({ + super.features = const [], + super.featureDefaultLanguage = 'en', + super.order = ExecutionOrder.random, + super.defaultTimeout = const Duration(seconds: 10), + super.featureFileMatcher = const IoFeatureFileAccessor(), + super.featureFileReader = const IoFeatureFileAccessor(), + super.stopAfterTestFailed = false, + super.tagExpression, + super.hooks, + super.reporters = const [], + super.createWorld, + this.semanticsEnabled = true, + this.waitImplicitlyAfterAction = false, + Iterable>? customStepParameterDefinitions, + Iterable>? stepDefinitions, + }) : super( + customStepParameterDefinitions: + List.from(customStepParameterDefinitions ?? Iterable.empty()) + ..addAll(_wellKnownParameters), + stepDefinitions: List.from(stepDefinitions ?? Iterable.empty()) + ..addAll(_wellKnownStepDefinitions), + ); } diff --git a/lib/src/flutter/hooks/app_runner_hook.dart b/lib/src/flutter/hooks/app_runner_hook.dart index 248fe6f..99d912a 100644 --- a/lib/src/flutter/hooks/app_runner_hook.dart +++ b/lib/src/flutter/hooks/app_runner_hook.dart @@ -1,4 +1,4 @@ -import 'dart:io'; +import 'dart:io' if (dart.library.html) 'dart:html'; import 'package:flutter_gherkin/src/flutter/configuration/flutter_driver_test_configuration.dart'; import 'package:flutter_gherkin/src/flutter/runners/flutter_run_process_handler.dart'; import 'package:flutter_gherkin/src/flutter/world/flutter_driver_world.dart'; @@ -38,9 +38,9 @@ class FlutterAppRunnerHook extends Hook { Future onAfterScenario( TestConfiguration config, String scenario, - Iterable tags, - {bool? passed,} - ) async { + Iterable tags, { + bool? passed, + }) async { final flutterConfig = _castConfig(config); haveRunFirstScenario = true; if (_flutterRunProcessHandler != null && diff --git a/lib/src/flutter/reporters/flutter_driver_reporter.dart b/lib/src/flutter/reporters/flutter_driver_reporter.dart index 1b81f5b..775495f 100644 --- a/lib/src/flutter/reporters/flutter_driver_reporter.dart +++ b/lib/src/flutter/reporters/flutter_driver_reporter.dart @@ -13,11 +13,14 @@ enum _FlutterDriverMessageLogLevel { info, warning, error } /// This can cause problems with CI servers for example as they will mark a process as failed if it logs to the /// stderr stream. So Flutter driver will log a normal info message to the stderr and thus make /// the process fail from the perspective of a CI server. -class FlutterDriverReporter extends Reporter { +class FlutterDriverReporter extends Reporter + implements DisposableReporter, TestReporter { final bool logErrorMessages; final bool logWarningMessages; final bool logInfoMessages; + DriverLogCallback? defaultCallback; + FlutterDriverReporter({ this.logErrorMessages = true, this.logWarningMessages = true, @@ -25,13 +28,18 @@ class FlutterDriverReporter extends Reporter { }); @override - Future onTestRunStarted() async { - driverLog = _driverLogMessageHandler; - } + ReportActionHandler get test => ReportActionHandler( + onStarted: ([_]) async { + defaultCallback = driverLog; + driverLog = _driverLogMessageHandler; + }, + ); @override Future dispose() async { - // driverLog = null; + if (defaultCallback != null) { + driverLog = defaultCallback!; + } } void _driverLogMessageHandler(String source, String message) { diff --git a/lib/src/flutter/runners/gherkin_integration_test_runner.dart b/lib/src/flutter/runners/gherkin_integration_test_runner.dart index 1b7496c..ba62c85 100644 --- a/lib/src/flutter/runners/gherkin_integration_test_runner.dart +++ b/lib/src/flutter/runners/gherkin_integration_test_runner.dart @@ -49,8 +49,7 @@ abstract class GherkinIntegrationTestRunner { } Future run() async { - _binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized() - as IntegrationTestWidgetsFlutterBinding; + _binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized(); _binding!.framePolicy = framePolicy ?? LiveTestWidgetsFlutterBindingFramePolicy.benchmarkLive; @@ -77,10 +76,8 @@ abstract class GherkinIntegrationTestRunner { } void setTestResultData(IntegrationTestWidgetsFlutterBinding binding) { - if (reporter is SerializableReporter) { - final json = (reporter as SerializableReporter).serialize(); - binding.reportData = {'gherkin_reports': json}; - } + final json = (reporter).serialize(); + binding.reportData = {'gherkin_reports': json}; } @protected @@ -115,10 +112,7 @@ abstract class GherkinIntegrationTestRunner { } @protected - Future onAfterRunFeature( - String name, - String path - ) async { + Future onAfterRunFeature(String name, String path) async { final debugInformation = RunnableDebugInformation(path, 0, name); await reporter.test.onFinished.maybeCall( TestMessage( @@ -130,11 +124,16 @@ abstract class GherkinIntegrationTestRunner { } @protected - void runScenario( - String name, - Iterable? tags, - List Function(TestDependencies dependencies, bool skip,)> - steps, String path, { + void runScenario({ + required String name, + required Iterable? tags, + required List< + Future Function( + TestDependencies dependencies, + bool skip, + )> + steps, + required String path, Future Function()? onBefore, Future Function()? onAfter, }) { @@ -257,7 +256,15 @@ abstract class GherkinIntegrationTestRunner { world = world ?? FlutterWidgetTesterWorld(); world.setAttachmentManager(attachmentManager); - (world as FlutterWorld).setAppAdapter(WidgetTesterAppDriverAdapter(tester)); + (world as FlutterWorld).setAppAdapter( + WidgetTesterAppDriverAdapter( + rawAdapter: tester, + binding: _binding!, + waitImplicitlyAfterAction: configuration is FlutterTestConfiguration + ? (configuration).waitImplicitlyAfterAction + : true, + ), + ); return TestDependencies( world, @@ -266,8 +273,13 @@ abstract class GherkinIntegrationTestRunner { } @protected - Future runStep(String step, Iterable multiLineStrings, - dynamic table, TestDependencies dependencies, bool hasToSkip,) async { + Future runStep( + String step, + Iterable multiLineStrings, + dynamic table, + TestDependencies dependencies, + bool hasToSkip, + ) async { final executable = _executableSteps!.firstWhereOrNull( (s) => s.expression.isMatch(step), ); @@ -294,8 +306,8 @@ abstract class GherkinIntegrationTestRunner { StepResult? result; if (hasToSkip) { - result = new StepResult( - 0, StepExecutionResult.skipped, resultReason: "Previous step(s) failed."); + result = new StepResult(0, StepExecutionResult.skipped, + resultReason: "Previous step(s) failed."); } else { for (int i = 0; i < this.configuration.stepMaxRetries + 1; i++) { result = await executable.step.run( @@ -426,7 +438,9 @@ abstract class GherkinIntegrationTestRunner { name: step, context: RunnableDebugInformation('', 0, step), result: result, - attachments: dependencies.attachmentManager.getAttachmentsForContext(step).toList(), + attachments: dependencies.attachmentManager + .getAttachmentsForContext(step) + .toList(), ), ); } diff --git a/lib/src/flutter/steps/sibling_contains_text_step.dart b/lib/src/flutter/steps/sibling_contains_text_step.dart index 4bb167d..b6d1f3a 100644 --- a/lib/src/flutter/steps/sibling_contains_text_step.dart +++ b/lib/src/flutter/steps/sibling_contains_text_step.dart @@ -1,6 +1,4 @@ import 'package:flutter_gherkin/flutter_gherkin.dart'; -import 'package:flutter_gherkin/src/flutter/adapters/app_driver_adapter.dart'; -import 'package:flutter_gherkin/src/flutter/world/flutter_world.dart'; import 'package:gherkin/gherkin.dart'; /// Discovers a widget by its text within the same parent. diff --git a/lib/src/flutter/steps/swipe_step.dart b/lib/src/flutter/steps/swipe_step.dart index badecc7..7ed3181 100644 --- a/lib/src/flutter/steps/swipe_step.dart +++ b/lib/src/flutter/steps/swipe_step.dart @@ -1,5 +1,4 @@ import 'package:flutter_gherkin/flutter_gherkin.dart'; -import 'package:flutter_gherkin/src/flutter/world/flutter_world.dart'; import 'package:gherkin/gherkin.dart'; import '../parameters/swipe_direction_parameter.dart'; diff --git a/lib/src/flutter/steps/tap_text_within_widget_step.dart b/lib/src/flutter/steps/tap_text_within_widget_step.dart index 08d2176..3b535f7 100644 --- a/lib/src/flutter/steps/tap_text_within_widget_step.dart +++ b/lib/src/flutter/steps/tap_text_within_widget_step.dart @@ -1,5 +1,4 @@ import 'package:flutter_gherkin/flutter_gherkin.dart'; -import 'package:flutter_gherkin/src/flutter/adapters/app_driver_adapter.dart'; import 'package:gherkin/gherkin.dart'; /// Taps a widget that contains the text within another widget. diff --git a/lib/src/flutter/steps/tap_widget_of_type_step.dart b/lib/src/flutter/steps/tap_widget_of_type_step.dart index 05bfd8f..1a94ea9 100644 --- a/lib/src/flutter/steps/tap_widget_of_type_step.dart +++ b/lib/src/flutter/steps/tap_widget_of_type_step.dart @@ -1,5 +1,4 @@ import 'package:flutter_gherkin/flutter_gherkin.dart'; -import 'package:flutter_gherkin/src/flutter/adapters/app_driver_adapter.dart'; import 'package:gherkin/gherkin.dart'; /// Taps a widget of type. diff --git a/lib/src/flutter/steps/tap_widget_of_type_within_step.dart b/lib/src/flutter/steps/tap_widget_of_type_within_step.dart index 456129e..c7b5a8d 100644 --- a/lib/src/flutter/steps/tap_widget_of_type_within_step.dart +++ b/lib/src/flutter/steps/tap_widget_of_type_within_step.dart @@ -1,5 +1,4 @@ import 'package:flutter_gherkin/flutter_gherkin.dart'; -import 'package:flutter_gherkin/src/flutter/adapters/app_driver_adapter.dart'; import 'package:gherkin/gherkin.dart'; /// Taps a widget of type within another widget. diff --git a/lib/src/flutter/steps/tap_widget_with_text_step.dart b/lib/src/flutter/steps/tap_widget_with_text_step.dart index 8ecd9b6..0781b0d 100644 --- a/lib/src/flutter/steps/tap_widget_with_text_step.dart +++ b/lib/src/flutter/steps/tap_widget_with_text_step.dart @@ -1,5 +1,4 @@ import 'package:flutter_gherkin/flutter_gherkin.dart'; -import 'package:flutter_gherkin/src/flutter/adapters/app_driver_adapter.dart'; import 'package:gherkin/gherkin.dart'; /// Taps a widget that contains text. diff --git a/lib/src/flutter/steps/text_exists_step.dart b/lib/src/flutter/steps/text_exists_step.dart index 182fda1..7e6690c 100644 --- a/lib/src/flutter/steps/text_exists_step.dart +++ b/lib/src/flutter/steps/text_exists_step.dart @@ -1,5 +1,4 @@ import 'package:flutter_gherkin/flutter_gherkin.dart'; -import 'package:flutter_gherkin/src/flutter/adapters/app_driver_adapter.dart'; import 'package:gherkin/gherkin.dart'; import '../parameters/existence_parameter.dart'; diff --git a/lib/src/flutter/steps/text_exists_within_step.dart b/lib/src/flutter/steps/text_exists_within_step.dart index e569a55..53d135c 100644 --- a/lib/src/flutter/steps/text_exists_within_step.dart +++ b/lib/src/flutter/steps/text_exists_within_step.dart @@ -1,5 +1,4 @@ import 'package:flutter_gherkin/flutter_gherkin.dart'; -import 'package:flutter_gherkin/src/flutter/adapters/app_driver_adapter.dart'; import 'package:gherkin/gherkin.dart'; import '../parameters/existence_parameter.dart'; diff --git a/lib/src/flutter/steps/wait_until_key_exists_step.dart b/lib/src/flutter/steps/wait_until_key_exists_step.dart index 65557b6..8c9a7cd 100644 --- a/lib/src/flutter/steps/wait_until_key_exists_step.dart +++ b/lib/src/flutter/steps/wait_until_key_exists_step.dart @@ -1,5 +1,4 @@ import 'package:flutter_gherkin/flutter_gherkin.dart'; -import 'package:flutter_gherkin/src/flutter/adapters/app_driver_adapter.dart'; import 'package:gherkin/gherkin.dart'; import '../parameters/existence_parameter.dart'; diff --git a/lib/src/flutter/steps/wait_until_type_exists_step.dart b/lib/src/flutter/steps/wait_until_type_exists_step.dart index f35be4f..c4fa6ea 100644 --- a/lib/src/flutter/steps/wait_until_type_exists_step.dart +++ b/lib/src/flutter/steps/wait_until_type_exists_step.dart @@ -1,5 +1,4 @@ import 'package:flutter_gherkin/flutter_gherkin.dart'; -import 'package:flutter_gherkin/src/flutter/adapters/app_driver_adapter.dart'; import 'package:gherkin/gherkin.dart'; import '../parameters/existence_parameter.dart'; diff --git a/pubspec.lock b/pubspec.lock index df06d64..0960fb4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,14 +7,14 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "30.0.0" + version: "40.0.0" analyzer: dependency: "direct main" description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "2.7.0" + version: "4.1.0" archive: dependency: transitive description: @@ -28,7 +28,7 @@ packages: name: args url: "https://pub.dartlang.org" source: hosted - version: "2.3.0" + version: "2.3.1" async: dependency: transitive description: @@ -49,7 +49,7 @@ packages: name: build url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.3.0" build_config: dependency: "direct dev" description: @@ -78,13 +78,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" - cli_util: - dependency: transitive - description: - name: cli_util - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.5" clock: dependency: transitive description: @@ -105,7 +98,7 @@ packages: name: convert url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.0.2" crypto: dependency: transitive description: @@ -119,7 +112,7 @@ packages: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "2.2.3" fake_async: dependency: transitive description: @@ -160,14 +153,14 @@ packages: name: gherkin url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.0.0+1" glob: dependency: "direct main" description: name: glob url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.1.0" integration_test: dependency: "direct main" description: flutter @@ -179,7 +172,7 @@ packages: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "4.3.0" + version: "4.5.0" logging: dependency: transitive description: @@ -214,7 +207,7 @@ packages: name: package_config url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.1.0" path: dependency: transitive description: @@ -249,14 +242,14 @@ packages: name: pub_semver url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" pubspec_parse: dependency: transitive description: name: pubspec_parse url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" sky_engine: dependency: transitive description: flutter @@ -268,7 +261,7 @@ packages: name: source_gen url: "https://pub.dartlang.org" source: hosted - version: "1.1.1" + version: "1.2.2" source_span: dependency: transitive description: @@ -366,7 +359,7 @@ packages: name: yaml url: "https://pub.dartlang.org" source: hosted - version: "3.1.0" + version: "3.1.1" sdks: - dart: ">=2.17.0-0 <3.0.0" + dart: ">=2.17.0 <3.0.0" flutter: ">=2.2.0" diff --git a/pubspec.yaml b/pubspec.yaml index 5c14074..18f7e8c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,10 +1,10 @@ name: flutter_gherkin description: A Gherkin / Cucumber parser and test runner for Dart and Flutter -version: 3.0.0-rc.9 +version: 3.0.0 homepage: https://github.com/jonsamwell/flutter_gherkin environment: - sdk: '>=2.12.3 <3.0.0' + sdk: '>=2.17.0 <3.0.0' flutter: ">=2.2.0" dependencies: @@ -16,15 +16,15 @@ dependencies: sdk: flutter flutter_driver: sdk: flutter - analyzer: ">=1.7.1 <3.0.0" + analyzer: '>=2.1.0 < 5.0.0' collection: ^1.15.0 - gherkin: ^3.0.0 + gherkin: ^3.0.0+1 source_gen: ^1.1.1 build: ^2.1.1 glob: ^2.0.2 dev_dependencies: - meta: ^1.7.0 + meta: '>=1.7.0 < 2.0.0' pedantic: ^1.11.1 build_config: ^1.0.0 diff --git a/test/flutter_configuration_test.dart b/test/flutter_configuration_test.dart index b3f3b56..95d9d30 100644 --- a/test/flutter_configuration_test.dart +++ b/test/flutter_configuration_test.dart @@ -6,43 +6,37 @@ import 'mocks/step_definition_mock.dart'; void main() { group('config', () { - group('prepare', () { - test('flutter app runner hook added', () { - final config = FlutterDriverTestConfiguration(); - expect(config.hooks, isNull); - config.prepare(); - expect(config.hooks, isNotNull); - expect(config.hooks!.length, 1); - expect(config.hooks!.elementAt(0), (x) => x is FlutterAppRunnerHook); - }); + test('flutter app runner hook added', () { + final config = FlutterDriverTestConfiguration(); + final newConfig = config.prepare(); - test('common steps definition added', () { - final config = FlutterDriverTestConfiguration(); - expect(config.stepDefinitions, isNull); + expect(newConfig.hooks, isNotNull); + expect(newConfig.hooks!.length, 1); + expect(newConfig.hooks!.elementAt(0), (x) => x is FlutterAppRunnerHook); + }); - config.prepare(); - expect(config.stepDefinitions, isNotNull); - expect(config.stepDefinitions!.length, 23); - expect(config.customStepParameterDefinitions, isNotNull); - expect(config.customStepParameterDefinitions!.length, 2); - }); + test('common steps definition added', () { + final config = FlutterDriverTestConfiguration(); + expect(config.stepDefinitions, isNotNull); + expect(config.stepDefinitions!.length, 23); + expect(config.customStepParameterDefinitions, isNotNull); + expect(config.customStepParameterDefinitions!.length, 2); + }); - test('common step definition added to existing steps', () { - final config = FlutterTestConfiguration() - ..stepDefinitions = [MockStepDefinition()] - ..customStepParameterDefinitions = [MockParameter()]; - expect(config.stepDefinitions!.length, 1); + test('common step definition added to existing steps', () { + final config = FlutterTestConfiguration( + stepDefinitions: [MockStepDefinition()], + customStepParameterDefinitions: [MockParameter()], + ); - config.prepare(); - expect(config.stepDefinitions, isNotNull); - expect(config.stepDefinitions!.length, 24); - expect(config.stepDefinitions!.elementAt(0), - (x) => x is MockStepDefinition); - expect(config.customStepParameterDefinitions, isNotNull); - expect(config.customStepParameterDefinitions!.length, 3); - expect(config.customStepParameterDefinitions!.elementAt(0), - (x) => x is MockParameter); - }); + expect(config.stepDefinitions, isNotNull); + expect(config.stepDefinitions!.length, 24); + expect( + config.stepDefinitions!.elementAt(0), (x) => x is MockStepDefinition); + expect(config.customStepParameterDefinitions, isNotNull); + expect(config.customStepParameterDefinitions!.length, 3); + expect(config.customStepParameterDefinitions!.elementAt(0), + (x) => x is MockParameter); }); }); }