diff --git a/.flutter-plugins-dependencies b/.flutter-plugins-dependencies index 26a3440..ee490a0 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:\\\\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 +{"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-20 11:46:06.283670","version":"3.0.2"} \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..5b4b3d9 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "dart-code.dart-code", + "dart-code.flutter", + "alexkrechik.cucumberautocomplete" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 2bc5fb1..ef0e83b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,13 +1,18 @@ { "cSpell.words": [ - "Errored", - "Serializable", "agnostically", + "analyzer", + "dialog", + "Errored", + "Flavor", "microtask", "multiline", "pubspec", + "rxdart", "scrollable", + "Serializable", "todos", "writeln" - ] + ], + "cSpell.language": "en-GB" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c9f33b1..8b66aae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,10 @@ - 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` +- Fix #198: Allow the use of implicit pumpAndSettle methods in the app driver to be turned off using the configuration property `waitImplicitlyAfterAction`. Off by default +* BREAKING CHANGE: +- `NEW API FOR REPORTERS`: All reporters implement (do not extend) separated interfaces see https://github.com/jonsamwell/dart_gherkin/blob/master/CHANGELOG.md#300---16052022 ## [3.0.0-rc.9] - 18/11/2021 diff --git a/analysis_options.yaml b/analysis_options.yaml index 32b823f..839cc64 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1 +1,29 @@ -# include: package:pedantic/analysis_options.yaml \ No newline at end of file +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options \ No newline at end of file diff --git a/example_with_flutter_driver/test_driver/test_harness.dart b/example_with_flutter_driver/test_driver/test_harness.dart index 0905ba6..0455808 100644 --- a/example_with_flutter_driver/test_driver/test_harness.dart +++ b/example_with_flutter_driver/test_driver/test_harness.dart @@ -7,7 +7,7 @@ Future main() { features: [RegExp('features/**.feature')], targetAppPath: 'test_driver/app.dart', targetAppWorkingDirectory: '../', - buildFlavor: + buildFlavour: "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 diff --git a/example_with_integration_test/integration_test/features/create.feature b/example_with_integration_test/integration_test/features/create.feature index 3140c53..ff7a899 100644 --- a/example_with_integration_test/integration_test/features/create.feature +++ b/example_with_integration_test/integration_test/features/create.feature @@ -1,7 +1,13 @@ @tag Feature: Creating todos - @debug + Scenario: User can create single todo item + Given I fill the "todo" field with "Buy spinach" + When I tap the "add" button + Then I expect the todo list + | Todo | + | Buy spinach | + Scenario: User can create multiple new todo items Given I fill the "todo" field with "Buy carrots" When I tap the "add" button diff --git a/example_with_integration_test/integration_test/features/swiping.feature b/example_with_integration_test/integration_test/features/swiping.feature index f6a32c0..ff5e5a4 100644 --- a/example_with_integration_test/integration_test/features/swiping.feature +++ b/example_with_integration_test/integration_test/features/swiping.feature @@ -1,9 +1,10 @@ @tag Feature: Swiping + @debug Scenario: User can swipe cards left and right Given I swipe right by 250 pixels on the "scrollable cards"` - Then Then I expect the text "Page 2" to be present + Then I expect the text "Page 2" to be present Given I swipe left by 250 pixels on the "scrollable cards"` - Then Then I expect the text "Page 1" to be present \ No newline at end of file + Then I expect the text "Page 1" to be present \ No newline at end of file diff --git a/example_with_integration_test/integration_test/gherkin/configuration.dart b/example_with_integration_test/integration_test/gherkin/configuration.dart index d828064..d7a64df 100644 --- a/example_with_integration_test/integration_test/gherkin/configuration.dart +++ b/example_with_integration_test/integration_test/gherkin/configuration.dart @@ -13,7 +13,7 @@ import 'steps/when_step_has_timeout.dart'; import 'world/custom_world.dart'; FlutterTestConfiguration gherkinTestConfiguration = FlutterTestConfiguration( - tagExpression: '@debug', + // tagExpression: '@debug', stepDefinitions: [ thenIExpectTheTodos, whenAnAnimationIsAwaited, diff --git a/example_with_integration_test/integration_test/gherkin/steps/multiline_string_with_formatted_json.dart b/example_with_integration_test/integration_test/gherkin/steps/multiline_string_with_formatted_json.dart index 6fe9640..c477124 100644 --- a/example_with_integration_test/integration_test/gherkin/steps/multiline_string_with_formatted_json.dart +++ b/example_with_integration_test/integration_test/gherkin/steps/multiline_string_with_formatted_json.dart @@ -3,7 +3,7 @@ import 'package:gherkin/gherkin.dart'; final givenTheData = given1( 'I have item with data', (jsonString, context) async { - print(jsonString); + // print(jsonString); }, configuration: StepDefinitionConfiguration() ..timeout = const Duration(seconds: 5), 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 c1178de..a925da4 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 @@ -8,7 +8,7 @@ part of 'gherkin_suite_test.dart'; class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner { _CustomGherkinIntegrationTestRunner( - TestConfiguration configuration, + FlutterTestConfiguration configuration, Future Function(World) appMainFunction, ) : super(configuration, appMainFunction); @@ -21,58 +21,78 @@ class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner { void testFeature0() { runFeature( - 'Swiping:', - ['@tag'], - () { + name: 'Swiping:', + tags: ['@tag'], + run: () { runScenario( 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'], + 'C:\\Development\\github\\flutter_gherkin\\example_with_integration_test\\.\\integration_test\\features\\swiping.feature', + tags: ['@tag', '@debug'], steps: [ - (TestDependencies dependencies, bool hasToSkip) async { + ( + TestDependencies dependencies, + bool skip, + ) async { return await runStep( - 'Given I swipe right by 250 pixels on the "scrollable cards"`', - [], - null, - dependencies, - hasToSkip, + name: + 'Given I swipe right by 250 pixels on the "scrollable cards"`', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, ); }, - (TestDependencies dependencies, bool hasToSkip) async { + ( + TestDependencies dependencies, + bool skip, + ) async { return await runStep( - 'Then Then I expect the text "Page 2" to be present', - [], - null, - dependencies, - hasToSkip, + name: 'Then I expect the text "Page 2" to be present', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, ); }, - (TestDependencies dependencies, bool hasToSkip) async { + ( + TestDependencies dependencies, + bool skip, + ) async { return await runStep( - 'Given I swipe left by 250 pixels on the "scrollable cards"`', - [], - null, - dependencies, - hasToSkip, + name: + 'Given I swipe left by 250 pixels on the "scrollable cards"`', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, ); }, - (TestDependencies dependencies, bool hasToSkip) async { + ( + TestDependencies dependencies, + bool skip, + ) async { return await runStep( - 'Then Then I expect the text "Page 1" to be present', - [], - null, - dependencies, - hasToSkip, + name: 'Then I expect the text "Page 1" to be present', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, ); - } + }, ], onBefore: () async => onBeforeRunFeature( - 'Swiping', - ['@tag'], + name: 'Swiping', + path: + r'C:\\Development\\github\\flutter_gherkin\\example_with_integration_test\\.\\integration_test\\features\\swiping.feature', + tags: ['@tag'], + ), + onAfter: () async => onAfterRunFeature( + name: 'Swiping', + path: + r'C:\\Development\\github\\flutter_gherkin\\example_with_integration_test\\.\\integration_test\\features\\swiping.feature', + tags: ['@tag'], ), - onAfter: () async => onAfterRunFeature('Swiping', - 'C:\Development\github\flutter_gherkin\example_with_integration_test\.\integration_test\features\swiping.feature'), ); }, ); @@ -80,92 +100,22 @@ class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner { void testFeature1() { runFeature( - 'Creating todos:', - ['@tag'], - () { + name: 'Checking data:', + tags: ['@tag'], + run: () { runScenario( - name: 'User can create multiple new todo items', + name: 'User can have data', path: - 'C:\Development\github\flutter_gherkin\example_with_integration_test\.\integration_test\features\create.feature', - tags: ['@tag', '@debug'], + 'C:\\Development\\github\\flutter_gherkin\\example_with_integration_test\\.\\integration_test\\features\\check.feature', + tags: ['@tag', '@tag1'], steps: [ - (TestDependencies dependencies, bool hasToSkip) async { + ( + TestDependencies dependencies, + bool skip, + ) 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', - [ + name: 'Given I have item with data', + multiLineStrings: [ """{ "glossary": { "title": "example glossary", @@ -192,18 +142,24 @@ class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner { } }""" ], - null, - dependencies, - hasToSkip, + table: null, + dependencies: dependencies, + skip: skip, ); - } + }, ], onBefore: () async => onBeforeRunFeature( - 'Creating todos', - ['@tag'], + name: 'Checking data', + path: + r'C:\\Development\\github\\flutter_gherkin\\example_with_integration_test\\.\\integration_test\\features\\check.feature', + tags: ['@tag'], + ), + onAfter: () async => onAfterRunFeature( + name: 'Checking data', + path: + r'C:\\Development\\github\\flutter_gherkin\\example_with_integration_test\\.\\integration_test\\features\\check.feature', + tags: ['@tag'], ), - onAfter: () async => onAfterRunFeature('Creating todos', - 'C:\Development\github\flutter_gherkin\example_with_integration_test\.\integration_test\features\create.feature'), ); }, ); @@ -211,19 +167,206 @@ class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner { void testFeature2() { runFeature( - 'Checking data:', - ['@tag'], - () { + name: 'Creating todos:', + tags: ['@tag'], + run: () { runScenario( - name: 'User can have data', + name: 'User can create single todo item', path: - 'C:\Development\github\flutter_gherkin\example_with_integration_test\.\integration_test\features\check.feature', - tags: ['@tag', '@tag1'], + 'C:\\Development\\github\\flutter_gherkin\\example_with_integration_test\\.\\integration_test\\features\\create.feature', + tags: ['@tag'], steps: [ - (TestDependencies dependencies, bool hasToSkip) async { + ( + TestDependencies dependencies, + bool skip, + ) async { return await runStep( - 'Given I have item with data', - [ + name: 'Given I fill the "todo" field with "Buy spinach"', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'When I tap the "add" button', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'Then I expect the todo list', + multiLineStrings: [], + table: GherkinTable.fromJson('[{"Todo":"Buy spinach"}]'), + dependencies: dependencies, + skip: skip, + ); + }, + ], + onBefore: () async => onBeforeRunFeature( + name: 'Creating todos', + path: + r'C:\\Development\\github\\flutter_gherkin\\example_with_integration_test\\.\\integration_test\\features\\create.feature', + tags: ['@tag'], + ), + ); + + runScenario( + 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'], + steps: [ + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'Given I fill the "todo" field with "Buy spinach"', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'When I tap the "add" button', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'Then I expect the todo list', + multiLineStrings: [], + table: GherkinTable.fromJson('[{"Todo":"Buy spinach"}]'), + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'Given I fill the "todo" field with "Buy carrots"', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'When I tap the "add" button', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'And I fill the "todo" field with "Buy apples"', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'When I tap the "add" button', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'And I fill the "todo" field with "Buy blueberries"', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'When I tap the "add" button', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'Then I expect the todo list', + multiLineStrings: [], + table: GherkinTable.fromJson( + '[{"Todo":"Buy blueberries"},{"Todo":"Buy apples"},{"Todo":"Buy carrots"}]'), + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'Given I wait 5 seconds for the animation to complete', + multiLineStrings: [], + table: null, + dependencies: dependencies, + skip: skip, + ); + }, + ( + TestDependencies dependencies, + bool skip, + ) async { + return await runStep( + name: 'Given I have item with data', + multiLineStrings: [ """{ "glossary": { "title": "example glossary", @@ -250,18 +393,18 @@ class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner { } }""" ], - null, - dependencies, - hasToSkip, + table: null, + dependencies: dependencies, + skip: skip, ); - } + }, ], - onBefore: () async => onBeforeRunFeature( - 'Checking data', - ['@tag'], + onAfter: () async => onAfterRunFeature( + name: 'Creating todos', + path: + r'C:\\Development\\github\\flutter_gherkin\\example_with_integration_test\\.\\integration_test\\features\\create.feature', + tags: ['@tag'], ), - onAfter: () async => onAfterRunFeature('Checking data', - 'C:\Development\github\flutter_gherkin\example_with_integration_test\.\integration_test\features\check.feature'), ); }, ); @@ -269,7 +412,7 @@ class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner { } void executeTestSuite( - TestConfiguration configuration, + FlutterTestConfiguration configuration, Future Function(World) appMainFunction, ) { _CustomGherkinIntegrationTestRunner(configuration, appMainFunction).run(); diff --git a/lib/src/flutter/adapters/flutter_driver_app_driver_adapter.dart b/lib/src/flutter/adapters/flutter_driver_app_driver_adapter.dart index 00844c1..77ef36c 100644 --- a/lib/src/flutter/adapters/flutter_driver_app_driver_adapter.dart +++ b/lib/src/flutter/adapters/flutter_driver_app_driver_adapter.dart @@ -154,9 +154,9 @@ class FlutterDriverAppDriverAdapter @override SerializableFinder findBy( dynamic data, - FindType type, + FindType findType, ) { - switch (type) { + switch (findType) { case FindType.key: return find.byValueKey(data.toString()); case FindType.text: 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 062e65b..84d753c 100644 --- a/lib/src/flutter/adapters/widget_tester_app_driver_adapter.dart +++ b/lib/src/flutter/adapters/widget_tester_app_driver_adapter.dart @@ -39,8 +39,9 @@ class WidgetTesterAppDriverAdapter Future _implicitWait({ Duration? duration = const Duration(milliseconds: 100), Duration? timeout = const Duration(seconds: 30), + bool? force, }) async { - if (waitImplicitlyAfterAction) { + if (waitImplicitlyAfterAction || force == true) { try { await nativeDriver.pumpAndSettle( duration ?? const Duration(milliseconds: 100), @@ -154,35 +155,16 @@ class WidgetTesterAppDriverAdapter duration: pressDuration, timeout: timeout, ); + await _implicitWait(timeout: timeout); } - @override - Future scroll( - Finder finder, { - double? dx, - double? dy, - Duration? duration = const Duration(milliseconds: 200), - Duration? timeout = const Duration(seconds: 30), - }) async { - final scrollableFinder = findByDescendant( - finder, - find.byType(Scrollable), - matchRoot: true, - ); - final state = nativeDriver.state(scrollableFinder) as ScrollableState; - final position = state.position; - position.jumpTo(dy ?? dx ?? 0); - - await nativeDriver.pump(); - } - @override Finder findBy( dynamic data, - FindType type, + FindType findType, ) { - switch (type) { + switch (findType) { case FindType.key: return find.byKey(data is Key ? data : Key(data)); case FindType.text: @@ -222,6 +204,31 @@ class WidgetTesterAppDriverAdapter ); } + @override + Future scroll( + Finder finder, { + double? dx, + double? dy, + Duration? duration = const Duration(milliseconds: 200), + Duration? timeout = const Duration(seconds: 30), + }) async { + final scrollableFinder = findByDescendant( + finder, + find.byType(Scrollable), + matchRoot: true, + ); + final state = nativeDriver.state(scrollableFinder) as ScrollableState; + final position = state.position; + position.jumpTo(dy ?? dx ?? 0); + + // must force a pump and settle to ensure the scroll is performed + await _implicitWait( + duration: duration, + timeout: timeout, + force: true, + ); + } + @override Future scrollUntilVisible( Finder item, { @@ -235,6 +242,12 @@ class WidgetTesterAppDriverAdapter dy ?? dx ?? 0, scrollable: scrollable, ); + + // must force a pump and settle to ensure the scroll is performed + await _implicitWait( + timeout: timeout, + force: true, + ); } @override @@ -244,6 +257,12 @@ class WidgetTesterAppDriverAdapter }) async { await nativeDriver.ensureVisible(finder); await waitForAppToSettle(); + + // must force a pump and settle to ensure the scroll is performed + await _implicitWait( + timeout: timeout, + force: true, + ); } @override 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 c87c35b..6a2d650 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 @@ -1,5 +1,6 @@ import 'dart:io'; +// ignore: implementation_imports import 'package:build/src/builder/build_step.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:flutter_gherkin/src/flutter/code_generation/annotations/gherkin_full_test_suite_annotation.dart'; @@ -12,10 +13,13 @@ class NoOpReporter extends MessageReporter { @override Future message(String message, MessageLevel level) async { if (level == MessageLevel.info || level == MessageLevel.debug) { + // ignore: avoid_print print(message); } else if (level == MessageLevel.warning) { + // ignore: avoid_print print('\x1B[33m$message\x1B[0m'); } else if (level == MessageLevel.error) { + // ignore: avoid_print print('\x1B[31m$message\x1B[0m'); } } @@ -23,10 +27,10 @@ class NoOpReporter extends MessageReporter { class GherkinSuiteTestGenerator extends GeneratorForAnnotation { - static const String TEMPLATE = ''' + static const String template = ''' class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner { _CustomGherkinIntegrationTestRunner( - TestConfiguration configuration, + FlutterTestConfiguration configuration, Future Function(World) appMainFunction, ) : super(configuration, appMainFunction); @@ -39,7 +43,7 @@ class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner { } void executeTestSuite( - TestConfiguration configuration, + FlutterTestConfiguration configuration, Future Function(World) appMainFunction, ) { _CustomGherkinIntegrationTestRunner(configuration, appMainFunction).run(); @@ -77,7 +81,7 @@ void executeTestSuite( final featureExecutionFunctionsBuilder = StringBuffer(); final generator = FeatureFileTestGenerator(); - final featuresToExecute = new StringBuffer(); + final featuresToExecute = StringBuffer(); var id = 0; for (var featureFile in featureFiles) { @@ -95,7 +99,7 @@ void executeTestSuite( } } - return TEMPLATE + return template .replaceAll('{{feature_functions}}', featureExecutionFunctionsBuilder.toString()) .replaceAll( @@ -126,44 +130,42 @@ class FeatureFileTestGenerator { } class FeatureFileTestGeneratorVisitor extends FeatureFileVisitor { - static const String FUNCTION_TEMPLATE = ''' + static const String functionTemplate = ''' void testFeature{{feature_number}}() { runFeature( - '{{feature_name}}:', - {{tags}}, - () { + name: '{{feature_name}}:', + tags: {{tags}}, + run: () { {{scenarios}} }, ); } '''; - static const String SCENARIO_TEMPLATE = ''' + static const String scenarioTemplate = ''' runScenario( name: '{{scenario_name}}', path: '{{path}}', tags:{{tags}}, - steps: [ - {{steps}} - ], + steps: [{{steps}},], {{onBefore}} {{onAfter}} ); '''; - static const String STEP_TEMPLATE = ''' -(TestDependencies dependencies, bool hasToSkip) async { + static const String stepTemplate = ''' +(TestDependencies dependencies, bool skip,) async { return await runStep( - '{{step_name}}', - {{step_multi_line_strings}}, - {{step_table}}, - dependencies, - hasToSkip, + name: '{{step_name}}', + multiLineStrings: {{step_multi_line_strings}}, + table: {{step_table}}, + dependencies: dependencies, + skip: skip, );} '''; - static const String ON_BEFORE_SCENARIO_RUN = ''' - onBefore: () async => onBeforeRunFeature('{{feature_name}}', {{feature_tags}},), + static const String onBeforeScenarioRun = ''' + onBefore: () async => onBeforeRunFeature(name:'{{feature_name}}', path:r'{{path}}', tags:{{feature_tags}},), '''; - static const String ON_AFTER_SCENARIO_RUN = ''' - onAfter: () async => onAfterRunFeature('{{feature_name}}', '{{path}}'), + static const String onAfterScenarioRun = ''' + onAfter: () async => onAfterRunFeature(name:'{{feature_name}}', path:r'{{path}}', tags:{{feature_tags}},), '''; final StringBuffer _buffer = StringBuffer(); @@ -204,7 +206,7 @@ class FeatureFileTestGeneratorVisitor extends FeatureFileVisitor { ) async { if (childScenarioCount > 0) { _currentFeatureCode = _replaceVariable( - FUNCTION_TEMPLATE, + functionTemplate, 'feature_number', _id.toString(), ); @@ -227,14 +229,14 @@ class FeatureFileTestGeneratorVisitor extends FeatureFileVisitor { {required bool isFirst, required bool isLast}) async { _flushScenario(); _currentScenarioCode = _replaceVariable( - SCENARIO_TEMPLATE, + scenarioTemplate, 'onBefore', - isFirst ? ON_BEFORE_SCENARIO_RUN : '', + isFirst ? onBeforeScenarioRun : '', ); _currentScenarioCode = _replaceVariable( _currentScenarioCode!, 'onAfter', - isLast ? ON_AFTER_SCENARIO_RUN : '', + isLast ? onAfterScenarioRun : '', ); _currentScenarioCode = _replaceVariable( _currentScenarioCode!, @@ -270,7 +272,7 @@ class FeatureFileTestGeneratorVisitor extends FeatureFileVisitor { GherkinTable? table, ) async { var code = _replaceVariable( - STEP_TEMPLATE, + stepTemplate, 'step_name', _escapeText(name), ); @@ -326,5 +328,6 @@ class FeatureFileTestGeneratorVisitor extends FeatureFileVisitor { return content.replaceAll('{{$property}}', value); } - String _escapeText(String text) => text.replaceAll("'", "\\'"); + String _escapeText(String text) => + text.replaceAll("\\", "\\\\").replaceAll("'", "\\'"); } diff --git a/lib/src/flutter/configuration/build_mode.dart b/lib/src/flutter/configuration/build_mode.dart index 2563335..bd97a14 100644 --- a/lib/src/flutter/configuration/build_mode.dart +++ b/lib/src/flutter/configuration/build_mode.dart @@ -1 +1 @@ -enum BuildMode { Debug, Profile } +enum BuildMode { debug, profile } diff --git a/lib/src/flutter/configuration/flutter_driver_test_configuration.dart b/lib/src/flutter/configuration/flutter_driver_test_configuration.dart index 77782d5..0917800 100644 --- a/lib/src/flutter/configuration/flutter_driver_test_configuration.dart +++ b/lib/src/flutter/configuration/flutter_driver_test_configuration.dart @@ -26,7 +26,7 @@ class FlutterDriverTestConfiguration extends FlutterTestConfiguration { super.stepDefinitions, this.targetAppPath = 'test_driver/app.dart', this.targetAppWorkingDirectory, - this.buildFlavor, + this.buildFlavour, this.targetDeviceId, this.runningAppProtocolEndpointUri, this.onBeforeFlutterDriverConnect, @@ -36,18 +36,19 @@ class FlutterDriverTestConfiguration extends FlutterTestConfiguration { this.keepAppRunningAfterTests = false, this.verboseFlutterProcessLogs = false, this.build = true, - this.buildMode = BuildMode.Debug, + 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!)], + features: 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. + // ignore: non_constant_identifier_names static FlutterDriverTestConfiguration DEFAULT( Iterable> steps, { String featurePath = 'features/*.*.feature', @@ -88,9 +89,9 @@ class FlutterDriverTestConfiguration extends FlutterTestConfiguration { /// Handy if your app is separated from your tests as flutter needs to be able to find a pubspec file final String? targetAppWorkingDirectory; - /// The build flavor to run the tests against (optional) + /// The build flavour to run the tests against (optional) /// Defaults to null - final String? buildFlavor; + final String? buildFlavour; /// The default build mode used for running tests is --debug. /// We are exposing the option to run the tests also in --profile mode @@ -201,25 +202,25 @@ class FlutterDriverTestConfiguration extends FlutterTestConfiguration { final providedCreateWorld = createWorld; 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, + buildFlavour: buildFlavour, + customStepParameterDefinitions: customStepParameterDefinitions, + defaultTimeout: defaultTimeout, + featureDefaultLanguage: featureDefaultLanguage, + featureFileMatcher: featureFileMatcher, + featureFileReader: featureFileReader, + features: features, + onAfterFlutterDriverConnect: onAfterFlutterDriverConnect, + onBeforeFlutterDriverConnect: onBeforeFlutterDriverConnect, + order: order, + reporters: reporters, + restartAppBetweenScenarios: restartAppBetweenScenarios, + runningAppProtocolEndpointUri: runningAppProtocolEndpointUri, + stepDefinitions: stepDefinitions, + stopAfterTestFailed: stopAfterTestFailed, + tagExpression: tagExpression, + targetAppPath: targetAppPath, + targetAppWorkingDirectory: targetAppWorkingDirectory, + targetDeviceId: targetDeviceId, createWorld: (config) async { FlutterWorld? world; if (providedCreateWorld != null) { @@ -228,7 +229,10 @@ class FlutterDriverTestConfiguration extends FlutterTestConfiguration { return await createFlutterWorld(config, world); }, - hooks: List.from(hooks ?? Iterable.empty())..add(FlutterAppRunnerHook()), + hooks: List.from(hooks ?? const Iterable.empty()) + ..add( + FlutterAppRunnerHook(), + ), ); } @@ -244,8 +248,9 @@ class FlutterDriverTestConfiguration extends FlutterTestConfiguration { if (attempt > maxAttempts) { throw e; } else { + // ignore: avoid_print print( - 'Fluter driver error connecting to application at `$dartVmServiceUrl`,' + 'Flutter driver error connecting to application at `$dartVmServiceUrl`,' 'retrying after delay of $flutterDriverReconnectionDelay', ); await Future.delayed(flutterDriverReconnectionDelay); diff --git a/lib/src/flutter/configuration/flutter_test_configuration.dart b/lib/src/flutter/configuration/flutter_test_configuration.dart index 32bf36f..c8b3035 100644 --- a/lib/src/flutter/configuration/flutter_test_configuration.dart +++ b/lib/src/flutter/configuration/flutter_test_configuration.dart @@ -12,29 +12,29 @@ class FlutterTestConfiguration extends TestConfiguration { 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(), + thenExpectElementToHaveValue(), + whenTapBackButtonWidget(), + whenTapWidget(), + whenTapWidgetWithoutScroll(), + whenLongPressWidget(), + whenLongPressWidgetWithoutScroll(), + whenLongPressWidgetForDuration(), + givenOpenDrawer(), + whenPauseStep(), + whenFillFieldStep(), + thenExpectWidgetToBePresent(), + restartAppStep(), + siblingContainsTextStep(), + tapTextWithinWidgetStep(), + tapWidgetOfTypeStep(), + tapWidgetOfTypeWithinStep(), + tapWidgetWithTextStep(), + textExistsStep(), + textExistsWithinStep(), + waitUntilKeyExistsStep(), + waitUntilTypeExistsStep(), ]; /// Enable semantics in a test by creating a [SemanticsHandle]. @@ -47,7 +47,7 @@ class FlutterTestConfiguration extends TestConfiguration { /// 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( + static FlutterTestConfiguration standard( Iterable> steps, { String featurePath = 'integration_test/features/*.*.feature', String targetAppPath = 'test_driver/integration_test_driver.dart', @@ -57,7 +57,6 @@ class FlutterTestConfiguration extends TestConfiguration { StdoutReporter(MessageLevel.error), ProgressReporter(), TestRunSummaryReporter(), - // JsonReporter(path: './report.json'), ], stepDefinitions: steps, ); @@ -80,10 +79,11 @@ class FlutterTestConfiguration extends TestConfiguration { Iterable>? customStepParameterDefinitions, Iterable>? stepDefinitions, }) : super( - customStepParameterDefinitions: - List.from(customStepParameterDefinitions ?? Iterable.empty()) - ..addAll(_wellKnownParameters), - stepDefinitions: List.from(stepDefinitions ?? Iterable.empty()) - ..addAll(_wellKnownStepDefinitions), + customStepParameterDefinitions: List.from( + customStepParameterDefinitions ?? const Iterable.empty(), + )..addAll(_wellKnownParameters), + stepDefinitions: List.from( + stepDefinitions ?? const 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 99d912a..81e19b3 100644 --- a/lib/src/flutter/hooks/app_runner_hook.dart +++ b/lib/src/flutter/hooks/app_runner_hook.dart @@ -76,7 +76,7 @@ class FlutterAppRunnerHook extends Hook { ..setWorkingDirectory(config.targetAppWorkingDirectory) ..setBuildRequired(haveRunFirstScenario ? false : config.build) ..setKeepAppRunning(config.keepAppRunningAfterTests) - ..setBuildFlavor(config.buildFlavor) + ..setBuildFlavour(config.buildFlavour) ..setBuildMode(config.buildMode) ..setDeviceTargetId(config.targetDeviceId); diff --git a/lib/src/flutter/runners/flutter_run_process_handler.dart b/lib/src/flutter/runners/flutter_run_process_handler.dart index 5727876..88a3ae6 100644 --- a/lib/src/flutter/runners/flutter_run_process_handler.dart +++ b/lib/src/flutter/runners/flutter_run_process_handler.dart @@ -5,10 +5,6 @@ import 'package:flutter_gherkin/src/flutter/configuration/build_mode.dart'; 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'; - // 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=/` // `Observatory URL on device: http://127.0.0.1:37849/t2xp9hvaxNs=/` @@ -49,10 +45,10 @@ class FlutterRunProcessHandler extends ProcessHandler { bool _logFlutterProcessOutput = false; bool _verboseFlutterLogs = false; bool _keepAppRunning = false; - BuildMode _buildMode = BuildMode.Debug; + BuildMode _buildMode = BuildMode.debug; String? _workingDirectory; String? _appTarget; - String? _buildFlavor; + String? _buildFlavour; String? _deviceTargetId; Duration _driverConnectionDelay = const Duration(seconds: 2); String? currentObservatoryUri; @@ -73,8 +69,8 @@ class FlutterRunProcessHandler extends ProcessHandler { _workingDirectory = workingDirectory; } - void setBuildFlavor(String? buildFlavor) { - _buildFlavor = buildFlavor; + void setBuildFlavour(String? buildFlavour) { + _buildFlavour = buildFlavour; } void setBuildMode(BuildMode buildMode) { @@ -101,9 +97,9 @@ class FlutterRunProcessHandler extends ProcessHandler { Future run() async { final arguments = ['run', '--target=$_appTarget']; - if (_buildMode == BuildMode.Debug) { + if (_buildMode == BuildMode.debug) { arguments.add('--debug'); - } else if (_buildMode == BuildMode.Profile) { + } else if (_buildMode == BuildMode.profile) { arguments.add('--profile'); } @@ -111,8 +107,8 @@ class FlutterRunProcessHandler extends ProcessHandler { arguments.add('--no-build'); } - if (_buildFlavor != null && _buildFlavor!.isNotEmpty) { - arguments.add('--flavor=$_buildFlavor'); + if (_buildFlavour != null && _buildFlavour!.isNotEmpty) { + arguments.add('--flavor=$_buildFlavour'); } if (_deviceTargetId != null && _deviceTargetId!.isNotEmpty) { @@ -148,11 +144,15 @@ 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( + '${StdoutReporter.kFailColor}Flutter build error: $event${StdoutReporter.kResetColor}', + ); } else { // This is most likely a deprecated api usage warnings (from Gradle) and should not // cause the test run to fail. - stdout.writeln('$WARN_COLOR$event$RESET_COLOR'); + stdout.writeln( + '${StdoutReporter.kWarnColor}$event${StdoutReporter.kResetColor}', + ); } })); } @@ -163,7 +163,9 @@ class FlutterRunProcessHandler extends ProcessHandler { _ensureRunningProcess(); if (_runningProcess != null) { _runningProcess!.stdin.write('q'); - _openSubscriptions.forEach((s) => s.cancel()); + for (var s in _openSubscriptions) { + s.cancel(); + } _openSubscriptions.clear(); exitCode = await _runningProcess!.exitCode; _runningProcess = null; @@ -232,12 +234,17 @@ 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'); + '${StdoutReporter.kFailColor}' + 'No connected devices found to run app on and tests against' + '${StdoutReporter.kResetColor}', + ); } } else if (_moreThanOneDeviceConnectedDeviceRegex.hasMatch(logLine)) { sub?.cancel(); if (!completer.isCompleted) { - stderr.writeln('$FAIL_COLOR$logLine$RESET_COLOR'); + stderr.writeln( + '${StdoutReporter.kFailColor}$logLine${StdoutReporter.kResetColor}', + ); } } }, diff --git a/lib/src/flutter/runners/gherkin_integration_test_runner.dart b/lib/src/flutter/runners/gherkin_integration_test_runner.dart index ba62c85..622c3e4 100644 --- a/lib/src/flutter/runners/gherkin_integration_test_runner.dart +++ b/lib/src/flutter/runners/gherkin_integration_test_runner.dart @@ -5,6 +5,13 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:collection/collection.dart'; +typedef StepFn = Future Function( + TestDependencies dependencies, + bool skip, +); + +typedef StartAppFn = Future Function(World world); + class TestDependencies { final World world; final AttachmentManager attachmentManager; @@ -18,25 +25,25 @@ class TestDependencies { abstract class GherkinIntegrationTestRunner { final TagExpressionEvaluator _tagExpressionEvaluator = TagExpressionEvaluator(); - final TestConfiguration configuration; - final Future Function(World world) appMainFunction; - AggregatedReporter _reporter = new AggregatedReporter(); + final FlutterTestConfiguration configuration; + final StartAppFn appMainFunction; + final Timeout scenarioExecutionTimeout; + final AggregatedReporter _reporter = AggregatedReporter(); Hook? _hook; Iterable? _executableSteps; Iterable? _customParameters; - IntegrationTestWidgetsFlutterBinding? _binding; + late final IntegrationTestWidgetsFlutterBinding _binding; AggregatedReporter get reporter => _reporter; Hook get hook => _hook!; LiveTestWidgetsFlutterBindingFramePolicy? get framePolicy => null; - Timeout scenarioExecutionTimeout = const Timeout(Duration(minutes: 10)); - GherkinIntegrationTestRunner( this.configuration, - this.appMainFunction, - ) { + this.appMainFunction, { + this.scenarioExecutionTimeout = const Timeout(Duration(minutes: 10)), + }) { configuration.prepare(); _registerReporters(configuration.reporters); _hook = _registerHooks(configuration.hooks); @@ -51,7 +58,7 @@ abstract class GherkinIntegrationTestRunner { Future run() async { _binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - _binding!.framePolicy = + _binding.framePolicy = framePolicy ?? LiveTestWidgetsFlutterBindingFramePolicy.benchmarkLive; tearDownAll( @@ -71,7 +78,7 @@ abstract class GherkinIntegrationTestRunner { void onRunComplete() { _safeInvokeFuture(() async => await reporter.test.onFinished.maybeCall()); _safeInvokeFuture(() async => await hook.onAfterRun(configuration)); - setTestResultData(_binding!); + setTestResultData(_binding); _safeInvokeFuture(() async => await reporter.dispose()); } @@ -81,27 +88,28 @@ abstract class GherkinIntegrationTestRunner { } @protected - void runFeature( - String name, + void runFeature({ + required String name, + required void Function() run, Iterable? tags, - void Function() runFeature, - ) { + }) { group( name, () { - runFeature(); + run(); }, ); } @protected - Future onBeforeRunFeature( - String name, + Future onBeforeRunFeature({ + required String name, + required String path, Iterable? tags, - ) async { - final debugInformation = RunnableDebugInformation('', 0, name); + }) async { + final debugInformation = RunnableDebugInformation(path, 0, name); final featureTags = - (tags ?? Iterable.empty()).map((t) => Tag(t.toString(), 0)); + (tags ?? const Iterable.empty()).map((t) => Tag(t.toString(), 0)); await reporter.feature.onStarted.maybeCall( FeatureMessage( name: name, @@ -112,13 +120,21 @@ abstract class GherkinIntegrationTestRunner { } @protected - Future onAfterRunFeature(String name, String path) async { + Future onAfterRunFeature({ + required String name, + required String path, + required List? tags, + }) async { final debugInformation = RunnableDebugInformation(path, 0, name); - await reporter.test.onFinished.maybeCall( - TestMessage( - target: Target.feature, + await reporter.feature.onFinished.maybeCall( + FeatureMessage( name: name, context: debugInformation, + tags: (tags ?? const Iterable.empty()) + .map( + (t) => Tag(t.toString(), 0), + ) + .toList(growable: false), ), ); } @@ -127,12 +143,7 @@ abstract class GherkinIntegrationTestRunner { void runScenario({ required String name, required Iterable? tags, - required List< - Future Function( - TestDependencies dependencies, - bool skip, - )> - steps, + required List steps, required String path, Future Function()? onBefore, Future Function()? onAfter, @@ -144,11 +155,12 @@ abstract class GherkinIntegrationTestRunner { if (onBefore != null) { await onBefore(); } - var failed = false; + bool failed = false; final debugInformation = RunnableDebugInformation(path, 0, name); - final scenarioTags = - (tags ?? Iterable.empty()).map((t) => Tag(t.toString(), 0)); + final scenarioTags = (tags ?? const Iterable.empty()).map( + (t) => Tag(t.toString(), 0), + ); final dependencies = await createTestDependencies( configuration, tester, @@ -190,7 +202,6 @@ abstract class GherkinIntegrationTestRunner { } catch (e) { failed = true; hasToSkip = true; - // rethrow; } } } finally { @@ -213,13 +224,11 @@ abstract class GherkinIntegrationTestRunner { await onAfter(); } - cleanupScenarioRun(dependencies); + cleanUpScenarioRun(dependencies); } }, timeout: scenarioExecutionTimeout, - semanticsEnabled: configuration is FlutterTestConfiguration - ? (configuration as FlutterTestConfiguration).semanticsEnabled - : true, + semanticsEnabled: configuration.semanticsEnabled, ); } else { _safeInvokeFuture( @@ -237,7 +246,13 @@ abstract class GherkinIntegrationTestRunner { World world, ) async { await appMainFunction(world); - await tester.pumpAndSettle(); + + // need to pump so app is initialised + await tester.pumpAndSettle( + const Duration(milliseconds: 200), + EnginePhase.sendSemanticsUpdate, + const Duration(milliseconds: 2000), + ); } @protected @@ -259,7 +274,7 @@ abstract class GherkinIntegrationTestRunner { (world as FlutterWorld).setAppAdapter( WidgetTesterAppDriverAdapter( rawAdapter: tester, - binding: _binding!, + binding: _binding, waitImplicitlyAfterAction: configuration is FlutterTestConfiguration ? (configuration).waitImplicitlyAfterAction : true, @@ -273,43 +288,46 @@ abstract class GherkinIntegrationTestRunner { } @protected - Future runStep( - String step, - Iterable multiLineStrings, - dynamic table, - TestDependencies dependencies, - bool hasToSkip, - ) async { + Future runStep({ + required String name, + required Iterable multiLineStrings, + required dynamic table, + required TestDependencies dependencies, + required bool skip, + }) async { final executable = _executableSteps!.firstWhereOrNull( - (s) => s.expression.isMatch(step), + (s) => s.expression.isMatch(name), ); if (executable == null) { - final message = 'Step definition not found for text: `$step`'; + final message = 'Step definition not found for text: `$name`'; throw GherkinStepNotDefinedException(message); } var parameters = _getStepParameters( - step, - multiLineStrings, - table, - executable, + step: name, + multiLineStrings: multiLineStrings, + table: table, + code: executable, ); await _onBeforeStepRun( - dependencies.world, - step, - table, - multiLineStrings, + world: dependencies.world, + step: name, + table: table, + multiLineStrings: multiLineStrings, ); StepResult? result; - if (hasToSkip) { - result = new StepResult(0, StepExecutionResult.skipped, - resultReason: "Previous step(s) failed."); + if (skip) { + result = StepResult( + 0, + StepExecutionResult.skipped, + resultReason: 'Previous step(s) failed', + ); } else { - for (int i = 0; i < this.configuration.stepMaxRetries + 1; i++) { + for (int i = 0; i < configuration.stepMaxRetries + 1; i++) { result = await executable.step.run( dependencies.world, reporter, @@ -319,20 +337,16 @@ abstract class GherkinIntegrationTestRunner { if (!_isNegativeResult(result.result)) { break; } else { - await Future.delayed(this.configuration.retryDelay); + await Future.delayed(configuration.retryDelay); } } } await _onAfterStepRun( - step, + name, result!, dependencies, ); - if (result is ErroredStepResult) { - // result.resultReason = result.exception.toString(); - } - return result; } @@ -343,7 +357,7 @@ abstract class GherkinIntegrationTestRunner { } @protected - void cleanupScenarioRun(TestDependencies dependencies) { + void cleanUpScenarioRun(TestDependencies dependencies) { _safeInvokeFuture( () async => dependencies.attachmentManager.dispose(), ); @@ -354,7 +368,9 @@ abstract class GherkinIntegrationTestRunner { void _registerReporters(Iterable? reporters) { if (reporters != null) { - reporters.forEach((r) => _reporter.addReporter(r)); + for (var r in reporters) { + _reporter.addReporter(r); + } } } @@ -404,12 +420,12 @@ abstract class GherkinIntegrationTestRunner { .toList(growable: false); } - Iterable _getStepParameters( - String step, - Iterable multiLineStrings, - dynamic table, - ExecutableStep code, - ) { + Iterable _getStepParameters({ + required String step, + required Iterable multiLineStrings, + required ExecutableStep code, + GherkinTable? table, + }) { var parameters = code.expression.getParameters(step); if (multiLineStrings.isNotEmpty) { parameters = parameters.toList()..addAll(multiLineStrings); @@ -433,7 +449,7 @@ abstract class GherkinIntegrationTestRunner { result, ); - await reporter.step.onStarted.maybeCall( + await reporter.step.onFinished.maybeCall( StepMessage( name: step, context: RunnableDebugInformation('', 0, step), @@ -445,12 +461,12 @@ abstract class GherkinIntegrationTestRunner { ); } - Future _onBeforeStepRun( - World world, - String step, - table, - Iterable multiLineStrings, - ) async { + Future _onBeforeStepRun({ + required World world, + required String step, + required Iterable multiLineStrings, + GherkinTable? table, + }) async { await hook.onBeforeStep(world, step); await reporter.step.onStarted.maybeCall( StepMessage( @@ -475,6 +491,9 @@ abstract class GherkinIntegrationTestRunner { ) { return tagExpression == null || tagExpression.isEmpty ? true - : _tagExpressionEvaluator.evaluate(tagExpression, tags!.toList()); + : _tagExpressionEvaluator.evaluate( + tagExpression, + tags!.toList(growable: false), + ); } } 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 ec7a258..bd9c921 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 @@ -7,7 +7,7 @@ import 'package:gherkin/gherkin.dart'; /// Examples: /// /// `Given I open the drawer` -StepDefinitionGeneric GivenOpenDrawer() { +StepDefinitionGeneric givenOpenDrawer() { return given1( RegExp(r'I (open|close) the drawer'), (action, context) async { diff --git a/lib/src/flutter/steps/restart_app_step.dart b/lib/src/flutter/steps/restart_app_step.dart index e0602eb..dfb51e2 100644 --- a/lib/src/flutter/steps/restart_app_step.dart +++ b/lib/src/flutter/steps/restart_app_step.dart @@ -1,7 +1,7 @@ import 'package:flutter_gherkin/flutter_gherkin.dart'; import 'package:gherkin/gherkin.dart'; -StepDefinitionGeneric RestartAppStep() { +StepDefinitionGeneric restartAppStep() { return given( 'I restart the app', (context) async { diff --git a/lib/src/flutter/steps/sibling_contains_text_step.dart b/lib/src/flutter/steps/sibling_contains_text_step.dart index b6d1f3a..d71a3c8 100644 --- a/lib/src/flutter/steps/sibling_contains_text_step.dart +++ b/lib/src/flutter/steps/sibling_contains_text_step.dart @@ -11,7 +11,7 @@ import 'package:gherkin/gherkin.dart'; /// Examples: /// /// `Then I expect a "Row" that contains the text "X" to also contain the text "Y"` -StepDefinitionGeneric SiblingContainsTextStep() { +StepDefinitionGeneric siblingContainsTextStep() { return given3( 'I expect a {string} that contains the text {string} to also contain the text {string}', (ancestorType, leadingText, valueText, context) async { diff --git a/lib/src/flutter/steps/swipe_step.dart b/lib/src/flutter/steps/swipe_step.dart index 7ed3181..43d312a 100644 --- a/lib/src/flutter/steps/swipe_step.dart +++ b/lib/src/flutter/steps/swipe_step.dart @@ -1,3 +1,5 @@ +// ignore_for_file: avoid_renaming_method_parameters + import 'package:flutter_gherkin/flutter_gherkin.dart'; import 'package:gherkin/gherkin.dart'; @@ -17,7 +19,7 @@ mixin _SwipeHelper await world.appDriver.scroll( finder, dx: offset.toDouble(), - duration: Duration(milliseconds: 500), + duration: const Duration(milliseconds: 500), timeout: timeout, ); } else { @@ -27,7 +29,7 @@ mixin _SwipeHelper await world.appDriver.scroll( finder, dy: offset.toDouble(), - duration: Duration(milliseconds: 500), + duration: const Duration(milliseconds: 500), timeout: timeout, ); } @@ -49,7 +51,7 @@ class SwipeOnKeyStep int swipeAmount, String key, ) async { - final finder = this.world.appDriver.findBy(key, FindType.key); + final finder = world.appDriver.findBy(key, FindType.key); await swipeOnFinder(finder, direction, swipeAmount); } @@ -72,7 +74,7 @@ class SwipeOnTextStep int swipeAmount, String text, ) async { - final finder = this.world.appDriver.findBy(text, FindType.text); + final finder = world.appDriver.findBy(text, FindType.text); await swipeOnFinder(finder, direction, swipeAmount); } 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 3b535f7..dc5ffa5 100644 --- a/lib/src/flutter/steps/tap_text_within_widget_step.dart +++ b/lib/src/flutter/steps/tap_text_within_widget_step.dart @@ -7,7 +7,7 @@ import 'package:gherkin/gherkin.dart'; /// Examples: /// /// `Then I tap the label that contains the text "Logout" within the "user_settings_list"` -StepDefinitionGeneric TapTextWithinWidgetStep() { +StepDefinitionGeneric tapTextWithinWidgetStep() { return given2( RegExp( r'I tap the (?:button|element|label|field|text|widget) that contains the text {string} within the {string}'), 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 1a94ea9..cf45fb0 100644 --- a/lib/src/flutter/steps/tap_widget_of_type_step.dart +++ b/lib/src/flutter/steps/tap_widget_of_type_step.dart @@ -8,7 +8,7 @@ import 'package:gherkin/gherkin.dart'; /// `Then I tap the element of type "MaterialButton"` /// `Then I tap the label of type "ListTile"` /// `Then I tap the field of type "TextField"` -StepDefinitionGeneric TapWidgetOfTypeStep() { +StepDefinitionGeneric tapWidgetOfTypeStep() { return given1( RegExp( r'I tap the (?:button|element|label|icon|field|text|widget) of type {string}$'), 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 c7b5a8d..b0d3787 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 @@ -6,7 +6,7 @@ import 'package:gherkin/gherkin.dart'; /// Examples: /// /// `Then I tap the element of type "MaterialButton" within the "user_settings_list"` -StepDefinitionGeneric TapWidgetOfTypeWithinStep() { +StepDefinitionGeneric tapWidgetOfTypeWithinStep() { return when2( RegExp( r'I tap the (?:button|element|label|icon|field|text|widget) of type {string} within the {string}$'), 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 0781b0d..157f1bb 100644 --- a/lib/src/flutter/steps/tap_widget_with_text_step.dart +++ b/lib/src/flutter/steps/tap_widget_with_text_step.dart @@ -8,7 +8,7 @@ import 'package:gherkin/gherkin.dart'; /// `Then I tap the label that contains the text "Logout"` /// `Then I tap the button that contains the text "Sign up"` /// `Then I tap the widget that contains the text "My User Profile"` -StepDefinitionGeneric TapWidgetWithTextStep() { +StepDefinitionGeneric tapWidgetWithTextStep() { return then1( RegExp( r'I tap the (?:button|element|label|field|text|widget) that contains the text {string}$'), diff --git a/lib/src/flutter/steps/text_exists_step.dart b/lib/src/flutter/steps/text_exists_step.dart index 7e6690c..c1d9d59 100644 --- a/lib/src/flutter/steps/text_exists_step.dart +++ b/lib/src/flutter/steps/text_exists_step.dart @@ -8,8 +8,8 @@ import '../parameters/existence_parameter.dart'; /// Examples: /// /// `Then I expect the text "Logout" to be present` -/// `But I expect the text "Signup" to be absent` -StepDefinitionGeneric TextExistsStep() { +/// `But I expect the text "Sign up" to be absent` +StepDefinitionGeneric textExistsStep() { return then2( RegExp(r'I expect the text {string} to be {existence}$'), (text, exists, context) async { diff --git a/lib/src/flutter/steps/text_exists_within_step.dart b/lib/src/flutter/steps/text_exists_within_step.dart index 53d135c..bd9cda2 100644 --- a/lib/src/flutter/steps/text_exists_within_step.dart +++ b/lib/src/flutter/steps/text_exists_within_step.dart @@ -8,8 +8,8 @@ import '../parameters/existence_parameter.dart'; /// Examples: /// /// `Then I expect the text "Logout" to be present within the "user_settings_list"` -/// `But I expect the text "Signup" to be absent within the "login_screen"` -StepDefinitionGeneric TextExistsWithinStep() { +/// `But I expect the text "Sign up" to be absent within the "login_screen"` +StepDefinitionGeneric textExistsWithinStep() { return then3( RegExp( r'I expect the text {string} to be {existence} within the {string}$'), 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 c0c175f..2547f2f 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 @@ -12,7 +12,7 @@ import 'package:gherkin/gherkin.dart'; /// /// `Then I expect the "controlKey" to be "Hello World"` /// `And I expect the "controlKey" to be "Hello World"` -StepDefinitionGeneric ThenExpectElementToHaveValue() { +StepDefinitionGeneric thenExpectElementToHaveValue() { return given2( RegExp(r'I expect the {string} to be {string}$'), (key, value, context) 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 d281f60..1c86cdc 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 @@ -11,7 +11,7 @@ import 'package:gherkin/gherkin.dart'; /// /// `Then I expect the widget 'notification' to be present within 10 seconds` /// `Then I expect the button 'save' to be present within 1 second` -StepDefinitionGeneric ThenExpectWidgetToBePresent() { +StepDefinitionGeneric thenExpectWidgetToBePresent() { return given2( RegExp( r'I expect the (?:button|element|label|icon|field|text|widget|dialog|popup) {string} to be present within {int} second(s)$'), 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 8c9a7cd..71c9dcf 100644 --- a/lib/src/flutter/steps/wait_until_key_exists_step.dart +++ b/lib/src/flutter/steps/wait_until_key_exists_step.dart @@ -9,7 +9,7 @@ import '../parameters/existence_parameter.dart'; /// /// `Then I wait until the "login_loading_indicator" is absent` /// `And I wait until the "login_screen" is present` -StepDefinitionGeneric WaitUntilKeyExistsStep() { +StepDefinitionGeneric waitUntilKeyExistsStep() { return then2( 'I wait until the {string} is {existence}', (keyString, existence, context) async { 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 c4fa6ea..540b215 100644 --- a/lib/src/flutter/steps/wait_until_type_exists_step.dart +++ b/lib/src/flutter/steps/wait_until_type_exists_step.dart @@ -9,7 +9,7 @@ import '../parameters/existence_parameter.dart'; /// /// `Then I wait until the element of type "ProgressIndicator" is absent` /// `And I wait until the button of type the "MaterialButton" is present` -StepDefinitionGeneric WaitUntilTypeExistsStep() { +StepDefinitionGeneric waitUntilTypeExistsStep() { return then2( 'I wait until the (?:button|element|label|icon|field|text|widget) of type {string} is {existence}', (ofType, existence, context) async { diff --git a/lib/src/flutter/steps/when_fill_field_step.dart b/lib/src/flutter/steps/when_fill_field_step.dart index 79bc23c..814a85a 100644 --- a/lib/src/flutter/steps/when_fill_field_step.dart +++ b/lib/src/flutter/steps/when_fill_field_step.dart @@ -7,7 +7,7 @@ import 'package:gherkin/gherkin.dart'; /// Examples: /// Then I fill the "email" field with "bob@gmail.com" /// Then I fill the "name" field with "Woody Johnson" -StepDefinitionGeneric WhenFillFieldStep() { +StepDefinitionGeneric whenFillFieldStep() { return given2( 'I fill the {string} field with {string}', (key, value, context) async { diff --git a/lib/src/flutter/steps/when_long_press_widget_step.dart b/lib/src/flutter/steps/when_long_press_widget_step.dart index 2cdfc38..f55958b 100644 --- a/lib/src/flutter/steps/when_long_press_widget_step.dart +++ b/lib/src/flutter/steps/when_long_press_widget_step.dart @@ -16,7 +16,7 @@ import 'package:gherkin/gherkin.dart'; /// `When I long press "controlKey" field` /// `When I long press "controlKey" text` /// `When I long press "controlKey" widget` -StepDefinitionGeneric WhenLongPressWidget() { +StepDefinitionGeneric whenLongPressWidget() { return when1( RegExp( r'I long press the {string} (?:button|element|label|icon|field|text|widget)$'), @@ -31,7 +31,7 @@ StepDefinitionGeneric WhenLongPressWidget() { } /// Long presses the widget found with the given control key, without scrolling into view -StepDefinitionGeneric WhenLongPressWidgetWithoutScroll() { +StepDefinitionGeneric whenLongPressWidgetWithoutScroll() { return when1( RegExp( r'I long press the {string} (?:button|element|label|icon|field|text|widget) without scrolling it into view$'), @@ -46,7 +46,7 @@ StepDefinitionGeneric WhenLongPressWidgetWithoutScroll() { } /// Long presses the widget found with the given control key, for the given duration -StepDefinitionGeneric WhenLongPressWidgetForDuration() { +StepDefinitionGeneric whenLongPressWidgetForDuration() { return when2( RegExp( r'I long press the {string} (?:button|element|label|icon|field|text|widget) for {int} milliseconds$'), diff --git a/lib/src/flutter/steps/when_pause_step.dart b/lib/src/flutter/steps/when_pause_step.dart index 30ab8e0..0e9d392 100644 --- a/lib/src/flutter/steps/when_pause_step.dart +++ b/lib/src/flutter/steps/when_pause_step.dart @@ -8,7 +8,7 @@ import 'package:gherkin/gherkin.dart'; /// Examples: /// When I pause for 10 seconds /// When I pause for 120 seconds -StepDefinitionGeneric WhenPauseStep() { +StepDefinitionGeneric whenPauseStep() { return when1( 'I (?:pause|wait) for {int} second(?:s)?', (wait, context) async { 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 c643636..385540c 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 @@ -8,7 +8,7 @@ import 'package:gherkin/gherkin.dart'; /// `When I tap the back button"` /// `When I tap the back element"` /// `When I tap the back widget"` -StepDefinitionGeneric WhenTapBackButtonWidget() { +StepDefinitionGeneric whenTapBackButtonWidget() { return when( RegExp(r'I tap the back (?:button|element|widget|icon|text)$'), (context) async { diff --git a/lib/src/flutter/steps/when_tap_widget_step.dart b/lib/src/flutter/steps/when_tap_widget_step.dart index 46916f4..43d35bc 100644 --- a/lib/src/flutter/steps/when_tap_widget_step.dart +++ b/lib/src/flutter/steps/when_tap_widget_step.dart @@ -16,7 +16,7 @@ import 'package:gherkin/gherkin.dart'; /// `When I tap "controlKey" field"` /// `When I tap "controlKey" text"` /// `When I tap "controlKey" widget"` -StepDefinitionGeneric WhenTapWidget() { +StepDefinitionGeneric whenTapWidget() { return when1( RegExp( r'I tap the {string} (?:button|element|label|icon|field|text|widget)$'), @@ -37,7 +37,7 @@ StepDefinitionGeneric WhenTapWidget() { ); } -StepDefinitionGeneric WhenTapWidgetWithoutScroll() { +StepDefinitionGeneric whenTapWidgetWithoutScroll() { return when1( RegExp( r'I tap the {string} (?:button|element|label|icon|field|text|widget) without scrolling it into view$'), diff --git a/lib/src/flutter/world/flutter_driver_world.dart b/lib/src/flutter/world/flutter_driver_world.dart index 7575f4b..d904f40 100644 --- a/lib/src/flutter/world/flutter_driver_world.dart +++ b/lib/src/flutter/world/flutter_driver_world.dart @@ -47,14 +47,11 @@ class FlutterDriverWorld extends FlutterTypedAdapterWorld _closeDriver({ Duration? timeout = const Duration(seconds: 60), }) async { - // ignore: unnecessary_null_comparison - if (rawAppDriver != null) { - await rawAppDriver.close().catchError( - (e, st) { - // Avoid an unhandled error - return null; - }, - ); - } + await rawAppDriver.close().catchError( + (e, st) { + // Avoid an unhandled error + return null; + }, + ); } } diff --git a/pre-publish-checks.cmd b/pre-publish-checks.cmd index 901a269..f29a6d9 100644 --- a/pre-publish-checks.cmd +++ b/pre-publish-checks.cmd @@ -1,4 +1,3 @@ CALL flutter analyze -CALL "C:\Google\flutter\bin\cache\dart-sdk\bin\dartfmt" . -w -CALL flutter packages upgrade -CALL flutter test --no-sound-null-safety \ No newline at end of file +CALL dart format . --fix +CALL flutter test \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 0960fb4..0f3a157 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,14 +7,28 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "40.0.0" + version: "39.0.0" analyzer: dependency: "direct main" description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "4.1.0" + version: "4.0.0" + analyzer_plugin: + dependency: transitive + description: + name: analyzer_plugin + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.0" + ansicolor: + dependency: transitive + description: + name: ansicolor + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" archive: dependency: transitive description: @@ -106,6 +120,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.1" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.2" + dart_code_metrics: + dependency: "direct dev" + description: + name: dart_code_metrics + url: "https://pub.dartlang.org" + source: hosted + version: "4.15.2" dart_style: dependency: transitive description: @@ -137,6 +165,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" flutter_test: dependency: "direct main" description: flutter @@ -161,6 +196,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.15.0" integration_test: dependency: "direct main" description: flutter @@ -173,6 +215,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.5.0" + lints: + dependency: transitive + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" logging: dependency: transitive description: @@ -222,6 +271,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.11.1" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.0" platform: dependency: transitive description: @@ -353,6 +409,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.0" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.0" yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 18f7e8c..5b6b3a9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,5 +27,7 @@ dev_dependencies: meta: '>=1.7.0 < 2.0.0' pedantic: ^1.11.1 build_config: ^1.0.0 + flutter_lints: ^2.0.1 + dart_code_metrics: ^4.15.2 flutter: