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,