chore(lints): fixed linting and improved code quality

fix(reporters): fixed summary reporter being called after each feature
This commit is contained in:
Jon 2022-06-20 11:46:48 +10:00
parent 0961916daa
commit 551a4fc34c
43 changed files with 716 additions and 408 deletions

View File

@ -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"}
{"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"}

7
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
"recommendations": [
"dart-code.dart-code",
"dart-code.flutter",
"alexkrechik.cucumberautocomplete"
]
}

11
.vscode/settings.json vendored
View File

@ -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"
}

View File

@ -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

View File

@ -1 +1,29 @@
# include: package:pedantic/analysis_options.yaml
# 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

View File

@ -7,7 +7,7 @@ Future<void> 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

View File

@ -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

View File

@ -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
Then I expect the text "Page 1" to be present

View File

@ -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,

View File

@ -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),

View File

@ -8,7 +8,7 @@ part of 'gherkin_suite_test.dart';
class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner {
_CustomGherkinIntegrationTestRunner(
TestConfiguration configuration,
FlutterTestConfiguration configuration,
Future<void> Function(World) appMainFunction,
) : super(configuration, appMainFunction);
@ -21,58 +21,78 @@ class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner {
void testFeature0() {
runFeature(
'Swiping:',
<String>['@tag'],
() {
name: 'Swiping:',
tags: <String>['@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: <String>['@tag'],
'C:\\Development\\github\\flutter_gherkin\\example_with_integration_test\\.\\integration_test\\features\\swiping.feature',
tags: <String>['@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"`',
<String>[],
null,
dependencies,
hasToSkip,
name:
'Given I swipe right by 250 pixels on the "scrollable cards"`',
multiLineStrings: <String>[],
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',
<String>[],
null,
dependencies,
hasToSkip,
name: 'Then I expect the text "Page 2" to be present',
multiLineStrings: <String>[],
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"`',
<String>[],
null,
dependencies,
hasToSkip,
name:
'Given I swipe left by 250 pixels on the "scrollable cards"`',
multiLineStrings: <String>[],
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',
<String>[],
null,
dependencies,
hasToSkip,
name: 'Then I expect the text "Page 1" to be present',
multiLineStrings: <String>[],
table: null,
dependencies: dependencies,
skip: skip,
);
}
},
],
onBefore: () async => onBeforeRunFeature(
'Swiping',
<String>['@tag'],
name: 'Swiping',
path:
r'C:\\Development\\github\\flutter_gherkin\\example_with_integration_test\\.\\integration_test\\features\\swiping.feature',
tags: <String>['@tag'],
),
onAfter: () async => onAfterRunFeature(
name: 'Swiping',
path:
r'C:\\Development\\github\\flutter_gherkin\\example_with_integration_test\\.\\integration_test\\features\\swiping.feature',
tags: <String>['@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:',
<String>['@tag'],
() {
name: 'Checking data:',
tags: <String>['@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: <String>['@tag', '@debug'],
'C:\\Development\\github\\flutter_gherkin\\example_with_integration_test\\.\\integration_test\\features\\check.feature',
tags: <String>['@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"',
<String>[],
null,
dependencies,
hasToSkip,
);
},
(TestDependencies dependencies, bool hasToSkip) async {
return await runStep(
'When I tap the "add" button',
<String>[],
null,
dependencies,
hasToSkip,
);
},
(TestDependencies dependencies, bool hasToSkip) async {
return await runStep(
'And I fill the "todo" field with "Buy apples"',
<String>[],
null,
dependencies,
hasToSkip,
);
},
(TestDependencies dependencies, bool hasToSkip) async {
return await runStep(
'When I tap the "add" button',
<String>[],
null,
dependencies,
hasToSkip,
);
},
(TestDependencies dependencies, bool hasToSkip) async {
return await runStep(
'And I fill the "todo" field with "Buy blueberries"',
<String>[],
null,
dependencies,
hasToSkip,
);
},
(TestDependencies dependencies, bool hasToSkip) async {
return await runStep(
'When I tap the "add" button',
<String>[],
null,
dependencies,
hasToSkip,
);
},
(TestDependencies dependencies, bool hasToSkip) async {
return await runStep(
'Then I expect the todo list',
<String>[],
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',
<String>[],
null,
dependencies,
hasToSkip,
);
},
(TestDependencies dependencies, bool hasToSkip) async {
return await runStep(
'Given I have item with data',
<String>[
name: 'Given I have item with data',
multiLineStrings: <String>[
"""{
"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',
<String>['@tag'],
name: 'Checking data',
path:
r'C:\\Development\\github\\flutter_gherkin\\example_with_integration_test\\.\\integration_test\\features\\check.feature',
tags: <String>['@tag'],
),
onAfter: () async => onAfterRunFeature(
name: 'Checking data',
path:
r'C:\\Development\\github\\flutter_gherkin\\example_with_integration_test\\.\\integration_test\\features\\check.feature',
tags: <String>['@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:',
<String>['@tag'],
() {
name: 'Creating todos:',
tags: <String>['@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: <String>['@tag', '@tag1'],
'C:\\Development\\github\\flutter_gherkin\\example_with_integration_test\\.\\integration_test\\features\\create.feature',
tags: <String>['@tag'],
steps: [
(TestDependencies dependencies, bool hasToSkip) async {
(
TestDependencies dependencies,
bool skip,
) async {
return await runStep(
'Given I have item with data',
<String>[
name: 'Given I fill the "todo" field with "Buy spinach"',
multiLineStrings: <String>[],
table: null,
dependencies: dependencies,
skip: skip,
);
},
(
TestDependencies dependencies,
bool skip,
) async {
return await runStep(
name: 'When I tap the "add" button',
multiLineStrings: <String>[],
table: null,
dependencies: dependencies,
skip: skip,
);
},
(
TestDependencies dependencies,
bool skip,
) async {
return await runStep(
name: 'Then I expect the todo list',
multiLineStrings: <String>[],
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: <String>['@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: <String>['@tag'],
steps: [
(
TestDependencies dependencies,
bool skip,
) async {
return await runStep(
name: 'Given I fill the "todo" field with "Buy spinach"',
multiLineStrings: <String>[],
table: null,
dependencies: dependencies,
skip: skip,
);
},
(
TestDependencies dependencies,
bool skip,
) async {
return await runStep(
name: 'When I tap the "add" button',
multiLineStrings: <String>[],
table: null,
dependencies: dependencies,
skip: skip,
);
},
(
TestDependencies dependencies,
bool skip,
) async {
return await runStep(
name: 'Then I expect the todo list',
multiLineStrings: <String>[],
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: <String>[],
table: null,
dependencies: dependencies,
skip: skip,
);
},
(
TestDependencies dependencies,
bool skip,
) async {
return await runStep(
name: 'When I tap the "add" button',
multiLineStrings: <String>[],
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: <String>[],
table: null,
dependencies: dependencies,
skip: skip,
);
},
(
TestDependencies dependencies,
bool skip,
) async {
return await runStep(
name: 'When I tap the "add" button',
multiLineStrings: <String>[],
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: <String>[],
table: null,
dependencies: dependencies,
skip: skip,
);
},
(
TestDependencies dependencies,
bool skip,
) async {
return await runStep(
name: 'When I tap the "add" button',
multiLineStrings: <String>[],
table: null,
dependencies: dependencies,
skip: skip,
);
},
(
TestDependencies dependencies,
bool skip,
) async {
return await runStep(
name: 'Then I expect the todo list',
multiLineStrings: <String>[],
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: <String>[],
table: null,
dependencies: dependencies,
skip: skip,
);
},
(
TestDependencies dependencies,
bool skip,
) async {
return await runStep(
name: 'Given I have item with data',
multiLineStrings: <String>[
"""{
"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',
<String>['@tag'],
onAfter: () async => onAfterRunFeature(
name: 'Creating todos',
path:
r'C:\\Development\\github\\flutter_gherkin\\example_with_integration_test\\.\\integration_test\\features\\create.feature',
tags: <String>['@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<void> Function(World) appMainFunction,
) {
_CustomGherkinIntegrationTestRunner(configuration, appMainFunction).run();

View File

@ -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:

View File

@ -39,8 +39,9 @@ class WidgetTesterAppDriverAdapter
Future<void> _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<void> 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<void> 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<void> 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

View File

@ -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<void> 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<GherkinTestSuite> {
static const String TEMPLATE = '''
static const String template = '''
class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner {
_CustomGherkinIntegrationTestRunner(
TestConfiguration configuration,
FlutterTestConfiguration configuration,
Future<void> Function(World) appMainFunction,
) : super(configuration, appMainFunction);
@ -39,7 +43,7 @@ class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner {
}
void executeTestSuite(
TestConfiguration configuration,
FlutterTestConfiguration configuration,
Future<void> 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("'", "\\'");
}

View File

@ -1 +1 @@
enum BuildMode { Debug, Profile }
enum BuildMode { debug, profile }

View File

@ -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<StepDefinitionGeneric<World>> 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<void>.delayed(flutterDriverReconnectionDelay);

View File

@ -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<StepDefinitionGeneric<World>> 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<CustomParameter<dynamic>>? customStepParameterDefinitions,
Iterable<StepDefinitionGeneric<World>>? 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),
);
}

View File

@ -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);

View File

@ -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<void> 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}',
);
}
}
},

View File

@ -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<StepResult> Function(
TestDependencies dependencies,
bool skip,
);
typedef StartAppFn = Future<void> 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<void> Function(World world) appMainFunction;
AggregatedReporter _reporter = new AggregatedReporter();
final FlutterTestConfiguration configuration;
final StartAppFn appMainFunction;
final Timeout scenarioExecutionTimeout;
final AggregatedReporter _reporter = AggregatedReporter();
Hook? _hook;
Iterable<ExecutableStep>? _executableSteps;
Iterable<CustomParameter>? _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<void> 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<String>? tags,
void Function() runFeature,
) {
}) {
group(
name,
() {
runFeature();
run();
},
);
}
@protected
Future<void> onBeforeRunFeature(
String name,
Future<void> onBeforeRunFeature({
required String name,
required String path,
Iterable<String>? tags,
) async {
final debugInformation = RunnableDebugInformation('', 0, name);
}) async {
final debugInformation = RunnableDebugInformation(path, 0, name);
final featureTags =
(tags ?? Iterable<Tag>.empty()).map((t) => Tag(t.toString(), 0));
(tags ?? const Iterable<Tag>.empty()).map((t) => Tag(t.toString(), 0));
await reporter.feature.onStarted.maybeCall(
FeatureMessage(
name: name,
@ -112,13 +120,21 @@ abstract class GherkinIntegrationTestRunner {
}
@protected
Future<void> onAfterRunFeature(String name, String path) async {
Future<void> onAfterRunFeature({
required String name,
required String path,
required List<String>? 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<String>.empty())
.map(
(t) => Tag(t.toString(), 0),
)
.toList(growable: false),
),
);
}
@ -127,12 +143,7 @@ abstract class GherkinIntegrationTestRunner {
void runScenario({
required String name,
required Iterable<String>? tags,
required List<
Future<StepResult> Function(
TestDependencies dependencies,
bool skip,
)>
steps,
required List<StepFn> steps,
required String path,
Future<void> Function()? onBefore,
Future<void> 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<Tag>.empty()).map((t) => Tag(t.toString(), 0));
final scenarioTags = (tags ?? const Iterable<Tag>.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<StepResult> runStep(
String step,
Iterable<String> multiLineStrings,
dynamic table,
TestDependencies dependencies,
bool hasToSkip,
) async {
Future<StepResult> runStep({
required String name,
required Iterable<String> 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<Reporter>? 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<dynamic> _getStepParameters(
String step,
Iterable<String> multiLineStrings,
dynamic table,
ExecutableStep code,
) {
Iterable<dynamic> _getStepParameters({
required String step,
required Iterable<String> 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<void> _onBeforeStepRun(
World world,
String step,
table,
Iterable<String> multiLineStrings,
) async {
Future<void> _onBeforeStepRun({
required World world,
required String step,
required Iterable<String> 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),
);
}
}

View File

@ -7,7 +7,7 @@ import 'package:gherkin/gherkin.dart';
/// Examples:
///
/// `Given I open the drawer`
StepDefinitionGeneric GivenOpenDrawer() {
StepDefinitionGeneric givenOpenDrawer() {
return given1<String, FlutterWorld>(
RegExp(r'I (open|close) the drawer'),
(action, context) async {

View File

@ -1,7 +1,7 @@
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart';
StepDefinitionGeneric RestartAppStep() {
StepDefinitionGeneric restartAppStep() {
return given<FlutterWorld>(
'I restart the app',
(context) async {

View File

@ -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<String, String, String, FlutterWorld>(
'I expect a {string} that contains the text {string} to also contain the text {string}',
(ancestorType, leadingText, valueText, context) async {

View File

@ -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);
}

View File

@ -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<String, String, FlutterWorld>(
RegExp(
r'I tap the (?:button|element|label|field|text|widget) that contains the text {string} within the {string}'),

View File

@ -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<String, FlutterWorld>(
RegExp(
r'I tap the (?:button|element|label|icon|field|text|widget) of type {string}$'),

View File

@ -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<String, String, FlutterWorld>(
RegExp(
r'I tap the (?:button|element|label|icon|field|text|widget) of type {string} within the {string}$'),

View File

@ -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<String, FlutterWorld>(
RegExp(
r'I tap the (?:button|element|label|field|text|widget) that contains the text {string}$'),

View File

@ -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<String, Existence, FlutterWorld>(
RegExp(r'I expect the text {string} to be {existence}$'),
(text, exists, context) async {

View File

@ -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<String, Existence, String, FlutterWorld>(
RegExp(
r'I expect the text {string} to be {existence} within the {string}$'),

View File

@ -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<String, String, FlutterWorld>(
RegExp(r'I expect the {string} to be {string}$'),
(key, value, context) async {

View File

@ -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<String, int, FlutterWorld>(
RegExp(
r'I expect the (?:button|element|label|icon|field|text|widget|dialog|popup) {string} to be present within {int} second(s)$'),

View File

@ -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<String, Existence, FlutterWorld>(
'I wait until the {string} is {existence}',
(keyString, existence, context) async {

View File

@ -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<String, Existence, FlutterWorld>(
'I wait until the (?:button|element|label|icon|field|text|widget) of type {string} is {existence}',
(ofType, existence, context) async {

View File

@ -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<String, String, FlutterWorld>(
'I fill the {string} field with {string}',
(key, value, context) async {

View File

@ -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<String, FlutterWorld>(
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<String, FlutterWorld>(
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<String, int, FlutterWorld>(
RegExp(
r'I long press the {string} (?:button|element|label|icon|field|text|widget) for {int} milliseconds$'),

View File

@ -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<int, FlutterWorld>(
'I (?:pause|wait) for {int} second(?:s)?',
(wait, context) async {

View File

@ -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<FlutterWorld>(
RegExp(r'I tap the back (?:button|element|widget|icon|text)$'),
(context) async {

View File

@ -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<String, FlutterWorld>(
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<String, FlutterWorld>(
RegExp(
r'I tap the {string} (?:button|element|label|icon|field|text|widget) without scrolling it into view$'),

View File

@ -47,14 +47,11 @@ class FlutterDriverWorld extends FlutterTypedAdapterWorld<FlutterDriver,
Future<void> _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;
},
);
}
}

View File

@ -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
CALL dart format . --fix
CALL flutter test

View File

@ -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:

View File

@ -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: