feat(config): re-worked configuration so it can stay mostly immutable

- Fix #195: Adding missing export for `wait_until_key_exists_step.dart`
- Fix #226: Allow compatibility with dev and master flutter branches
- Feat #218: Allow retry steps in case of intermittent failure by setting the configuration properties `stepMaxRetries` & `retryDelay`
- Fix #210 & #191: Ability to take screenshots on web
- Fix #198: Allow the use of implicit pumpAndSettle methods in the app driver to be turned off using the configuration property `waitImplicitlyAfterAction`
This commit is contained in:
Jon 2022-06-17 16:25:39 +10:00
parent 5c15f0c2c0
commit 0961916daa
44 changed files with 743 additions and 526 deletions

View File

@ -1,2 +1,2 @@
# This is a generated file; do not edit or check into version control.
integration_test=C:\\Google\\flutter\\packages\\integration_test\\
integration_test=C:\\Development\\flutter\\packages\\integration_test\\

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:\\\\Google\\\\flutter\\\\packages\\\\integration_test\\\\","dependencies":[]}],"android":[{"name":"integration_test","path":"C:\\\\Google\\\\flutter\\\\packages\\\\integration_test\\\\","dependencies":[]}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"integration_test","dependencies":[]}],"date_created":"2021-11-24 07:12:59.207785","version":"2.5.3"}
{"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"}

20
.gitignore vendored
View File

@ -1,13 +1,23 @@
.DS_Store
.dart_tool/
.packages
.pub/
build/
ios/.generated/
ios/Flutter/Generated.xcconfig
ios/Runner/GeneratedPluginRegistrant.*
node_modules
package-lock.json
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
**/generated_plugin_registrant.dart
.packages
.pub-cache/
.pub/
build/
flutter_*.png
linked_*.ds
unlinked.ds
unlinked_spec.ds

View File

@ -1,3 +1,12 @@
## [3.0.0] - 17/06/2022
- Fix #195: Adding missing export for `wait_until_key_exists_step.dart`
- Fix #226: Allow compatibility with dev and master flutter branches
- Feat #218: Allow retry steps in case of intermittent failure by setting the configuration properties `stepMaxRetries` & `retryDelay`
- Fix #210 & #191: Ability to take screenshots on web
- Fix #198: Allow the use of implicit pumpAndSettle methods in the app driver to be turned off using the configuration property `waitImplicitlyAfterAction`
## [3.0.0-rc.9] - 18/11/2021
- Fix: #172: Fix for the `StdoutReporter` when running against the web

View File

@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 29
compileSdkVersion 31
sourceSets {
main.java.srcDirs += 'src/main/kotlin'

View File

@ -11,6 +11,7 @@
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"

View File

@ -1,12 +1,12 @@
buildscript {
ext.kotlin_version = '1.3.50'
ext.kotlin_version = '1.6.10'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath 'com.android.tools.build:gradle:7.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

View File

@ -3,4 +3,5 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
org.gradle.jvmargs=-Xmx4608m

View File

@ -1,20 +1,41 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
url: "https://pub.dartlang.org"
source: hosted
version: "31.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.0"
archive:
dependency: transitive
description:
name: archive
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.2"
version: "3.1.11"
args:
dependency: transitive
description:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.1"
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.6.1"
version: "2.8.2"
boolean_selector:
dependency: transitive
description:
@ -22,20 +43,34 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
build:
dependency: transitive
description:
name: build
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.0"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "1.2.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.3.1"
cli_util:
dependency: transitive
description:
name: cli_util
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.5"
clock:
dependency: transitive
description:
@ -49,7 +84,14 @@ packages:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0"
version: "1.16.0"
convert:
dependency: transitive
description:
name: convert
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.2"
crypto:
dependency: transitive
description:
@ -57,20 +99,27 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
dart_style:
dependency: transitive
description:
name: dart_style
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.1"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.3.0"
file:
dependency: transitive
description:
name: file
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.0"
version: "6.1.2"
flutter:
dependency: "direct main"
description: flutter
@ -87,7 +136,7 @@ packages:
path: ".."
relative: true
source: path
version: "2.0.0"
version: "3.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
@ -104,59 +153,101 @@ packages:
name: gherkin
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
version: "3.0.0+1"
glob:
dependency: transitive
description:
name: glob
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
integration_test:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
logging:
dependency: transitive
description:
name: logging
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.10"
version: "0.12.11"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.4"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "1.7.0"
package_config:
dependency: transitive
description:
name: package_config
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
version: "1.8.1"
platform:
dependency: transitive
description:
name: platform
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
version: "3.1.0"
process:
dependency: transitive
description:
name: process
url: "https://pub.dartlang.org"
source: hosted
version: "4.2.1"
version: "4.2.4"
pub_semver:
dependency: transitive
description:
name: pub_semver
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_gen:
dependency: transitive
description:
name: source_gen
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.2"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.1"
version: "1.8.2"
stack_trace:
dependency: transitive
description:
@ -198,7 +289,7 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.0"
version: "0.4.9"
typed_data:
dependency: transitive
description:
@ -206,20 +297,34 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
uuid:
dependency: transitive
description:
name: uuid
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.6"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
version: "2.1.2"
vm_service:
dependency: transitive
description:
name: vm_service
url: "https://pub.dartlang.org"
source: hosted
version: "6.2.0"
version: "8.2.2"
watcher:
dependency: transitive
description:
name: watcher
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
webdriver:
dependency: transitive
description:
@ -227,6 +332,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
yaml:
dependency: transitive
description:
name: yaml
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.1"
sdks:
dart: ">=2.12.3 <3.0.0"
dart: ">=2.17.0 <3.0.0"
flutter: ">=2.2.0"

View File

@ -6,7 +6,8 @@ publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=2.12.0 <3.0.0'
sdk: '>=2.17.0 <3.0.0'
flutter: ">=2.2.0"
dependencies:
flutter:

View File

@ -3,22 +3,26 @@ import 'package:flutter_gherkin/flutter_gherkin_with_driver.dart';
import 'package:gherkin/gherkin.dart';
Future<void> main() {
final config = FlutterDriverTestConfiguration.DEFAULT(
Iterable.empty(),
featurePath: 'features/**.feature',
final config = FlutterDriverTestConfiguration(
features: [RegExp('features/**.feature')],
targetAppPath: 'test_driver/app.dart',
)
..restartAppBetweenScenarios = true
..targetAppWorkingDirectory = '../'
..targetAppPath = 'test_driver/app.dart';
// ..buildFlavor = "staging" // uncomment when using build flavor and check android/ios flavor setup see android file android\app\build.gradle
// ..targetDeviceId = "all" // uncomment to run tests on all connected devices or set specific device target id
// ..tagExpression = '@smoke and not @ignore' // uncomment to see an example of running scenarios based on tag expressions
// ..logFlutterProcessOutput = true // uncomment to see command invoked to start the flutter test app
// ..verboseFlutterProcessLogs = true // uncomment to see the verbose output from the Flutter process
// ..flutterBuildTimeout = Duration(minutes: 3) // uncomment to change the default period that flutter is expected to build and start the app within
// ..runningAppProtocolEndpointUri =
// 'http://127.0.0.1:51540/bkegoer6eH8=/' // already running app observatory / service protocol uri (with enableFlutterDriverExtension method invoked) to test against if you use this set `restartAppBetweenScenarios` to false
targetAppWorkingDirectory: '../',
buildFlavor:
"staging", // uncomment when using build flavor and check android/ios flavor setup see android file android\app\build.gradle
targetDeviceId:
"all", // uncomment to run tests on all connected devices or set specific device target id
tagExpression:
'@smoke and not @ignore', // uncomment to see an example of running scenarios based on tag expressions
logFlutterProcessOutput:
true, // uncomment to see command invoked to start the flutter test app
verboseFlutterProcessLogs:
true, // uncomment to see the verbose output from the Flutter process
flutterBuildTimeout: Duration(
minutes:
3), // uncomment to change the default period that flutter is expected to build and start the app within
runningAppProtocolEndpointUri:
'http://127.0.0.1:51540/bkegoer6eH8=/', // already running app observatory / service protocol uri (with enableFlutterDriverExtension method invoked) to test against if you use this set `restartAppBetweenScenarios` to false
);
return GherkinRunner().execute(config);
}

View File

@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 29
compileSdkVersion 31
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
@ -39,8 +39,8 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.example_with_integration_test"
minSdkVersion 16
targetSdkVersion 29
minSdkVersion 23
targetSdkVersion 31
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}

View File

@ -6,11 +6,12 @@
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:name="${applicationName}"
android:label="example_with_integration_test"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"

View File

@ -1,12 +1,12 @@
buildscript {
ext.kotlin_version = '1.3.50'
ext.kotlin_version = '1.6.10'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath 'com.android.tools.build:gradle:7.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

View File

@ -3,4 +3,5 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
org.gradle.jvmargs=-Xmx4608m

View File

@ -1,14 +1,6 @@
@tag
Feature: Creating todos
@tag1 @tag_two
Scenario: User can create a new todo item
Given I fill the "todo" field with "Buy carrots"
When I tap the 'add' button
Then I expect the todo list
| Todo |
| Buy carrots |
@debug
Scenario: User can create multiple new todo items
Given I fill the "todo" field with "Buy carrots"

View File

@ -1,7 +1,6 @@
@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

View File

@ -12,34 +12,30 @@ import 'steps/when_await_animation.dart';
import 'steps/when_step_has_timeout.dart';
import 'world/custom_world.dart';
FlutterTestConfiguration gherkinTestConfiguration =
FlutterTestConfiguration.DEFAULT(
[
FlutterTestConfiguration gherkinTestConfiguration = FlutterTestConfiguration(
tagExpression: '@debug',
stepDefinitions: [
thenIExpectTheTodos,
whenAnAnimationIsAwaited,
whenStepHasTimeout,
givenTheData
],
)
// ..tagExpression = '@debug'
..hooks = [
ResetAppHook(),
]
..reporters = [
StdoutReporter(MessageLevel.error)
..setWriteLineFn(print)
..setWriteFn(print),
ProgressReporter()
..setWriteLineFn(print)
..setWriteFn(print),
TestRunSummaryReporter()
..setWriteLineFn(print)
..setWriteFn(print),
JsonReporter(
writeReport: (_, __) => Future<void>.value(),
),
]
..createWorld = (config) => Future.value(CustomWorld());
hooks: [
ResetAppHook(),
],
reporters: [
StdoutReporter(MessageLevel.error)
..setWriteLineFn(print)
..setWriteFn(print),
ProgressReporter()
..setWriteLineFn(print)
..setWriteFn(print),
TestRunSummaryReporter()
..setWriteLineFn(print)
..setWriteFn(print),
],
createWorld: (config) => Future.value(CustomWorld()),
);
Future<void> Function(World) appInitializationFn = (World world) async {
// ensure a new injector instance is created each time

View File

@ -21,209 +21,152 @@ class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner {
void testFeature0() {
runFeature(
'Checking data:',
'Swiping:',
<String>['@tag'],
() {
runScenario(
'User can have data',
<String>['@tag', '@tag1'],
(TestDependencies dependencies) async {
await runStep(
'Given I have item with data',
<String>[
"""{
"glossary": {
"title": "example glossary",
"GlossDiv": {
"title": "S",
"GlossList": {
"GlossEntry": {
"ID": "SGML",
"SortAs": "SGML",
"GlossTerm": "Standard Generalized Markup Language",
"Acronym": "SGML",
"Abbrev": "ISO 8879:1986",
"GlossDef": {
"para": "A meta-markup language, used to create markup languages such as DocBook.",
"GlossSeeAlso": [
"GML",
"XML"
]
},
"GlossSee": "markup"
}
}
}
}
}"""
],
null,
dependencies,
);
},
name: 'User can swipe cards left and right',
path:
'C:\Development\github\flutter_gherkin\example_with_integration_test\.\integration_test\features\swiping.feature',
tags: <String>['@tag'],
steps: [
(TestDependencies dependencies, bool hasToSkip) async {
return await runStep(
'Given I swipe right by 250 pixels on the "scrollable cards"`',
<String>[],
null,
dependencies,
hasToSkip,
);
},
(TestDependencies dependencies, bool hasToSkip) async {
return await runStep(
'Then Then I expect the text "Page 2" to be present',
<String>[],
null,
dependencies,
hasToSkip,
);
},
(TestDependencies dependencies, bool hasToSkip) async {
return await runStep(
'Given I swipe left by 250 pixels on the "scrollable cards"`',
<String>[],
null,
dependencies,
hasToSkip,
);
},
(TestDependencies dependencies, bool hasToSkip) async {
return await runStep(
'Then Then I expect the text "Page 1" to be present',
<String>[],
null,
dependencies,
hasToSkip,
);
}
],
onBefore: () async => onBeforeRunFeature(
'Checking data',
'Swiping',
<String>['@tag'],
),
onAfter: () async => onAfterRunFeature(
'Checking data',
),
onAfter: () async => onAfterRunFeature('Swiping',
'C:\Development\github\flutter_gherkin\example_with_integration_test\.\integration_test\features\swiping.feature'),
);
},
);
}
void testFeature1() {
runFeature(
'Swiping:',
<String>['@tag'],
() {
runScenario(
'User can swipe cards left and right',
<String>['@tag', '@debug'],
(TestDependencies dependencies) async {
await runStep(
'Given I swipe right by 250 pixels on the "scrollable cards"`',
<String>[],
null,
dependencies,
);
await runStep(
'Then Then I expect the text "Page 2" to be present',
<String>[],
null,
dependencies,
);
await runStep(
'Given I swipe left by 250 pixels on the "scrollable cards"`',
<String>[],
null,
dependencies,
);
await runStep(
'Then Then I expect the text "Page 1" to be present',
<String>[],
null,
dependencies,
);
},
onBefore: () async => onBeforeRunFeature(
'Swiping',
<String>['@tag'],
),
onAfter: () async => onAfterRunFeature(
'Swiping',
),
);
},
);
}
void testFeature2() {
runFeature(
'Creating todos:',
<String>['@tag'],
() {
runScenario(
'User can create a new todo item',
<String>['@tag', '@tag1', '@tag_two'],
(TestDependencies dependencies) async {
await runStep(
'Given I fill the "todo" field with "Buy carrots"',
<String>[],
null,
dependencies,
);
await runStep(
'When I tap the \'add\' button',
<String>[],
null,
dependencies,
);
await runStep(
'Then I expect the todo list',
<String>[],
GherkinTable.fromJson('[{"Todo":"Buy carrots"}]'),
dependencies,
);
},
onBefore: () async => onBeforeRunFeature(
'Creating todos',
<String>['@tag'],
),
onAfter: null,
);
runScenario(
'User can create multiple new todo items',
<String>['@tag', '@debug'],
(TestDependencies dependencies) async {
await runStep(
'Given I fill the "todo" field with "Buy carrots"',
<String>[],
null,
dependencies,
);
await runStep(
'When I tap the "add" button',
<String>[],
null,
dependencies,
);
await runStep(
'And I fill the "todo" field with "Buy apples"',
<String>[],
null,
dependencies,
);
await runStep(
'When I tap the "add" button',
<String>[],
null,
dependencies,
);
await runStep(
'And I fill the "todo" field with "Buy blueberries"',
<String>[],
null,
dependencies,
);
await runStep(
'When I tap the "add" button',
<String>[],
null,
dependencies,
);
await runStep(
'Then I expect the todo list',
<String>[],
GherkinTable.fromJson(
'[{"Todo":"Buy blueberries"},{"Todo":"Buy apples"},{"Todo":"Buy carrots"}]'),
dependencies,
);
await runStep(
'Given I wait 5 seconds for the animation to complete',
<String>[],
null,
dependencies,
);
await runStep(
'Given I have item with data',
<String>[
"""{
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', '@debug'],
steps: [
(TestDependencies dependencies, bool hasToSkip) 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>[
"""{
"glossary": {
"title": "example glossary",
"GlossDiv": {
@ -248,15 +191,77 @@ class _CustomGherkinIntegrationTestRunner extends GherkinIntegrationTestRunner {
}
}
}"""
],
null,
dependencies,
);
},
onBefore: null,
onAfter: () async => onAfterRunFeature(
],
null,
dependencies,
hasToSkip,
);
}
],
onBefore: () async => onBeforeRunFeature(
'Creating todos',
<String>['@tag'],
),
onAfter: () async => onAfterRunFeature('Creating todos',
'C:\Development\github\flutter_gherkin\example_with_integration_test\.\integration_test\features\create.feature'),
);
},
);
}
void testFeature2() {
runFeature(
'Checking data:',
<String>['@tag'],
() {
runScenario(
name: 'User can have data',
path:
'C:\Development\github\flutter_gherkin\example_with_integration_test\.\integration_test\features\check.feature',
tags: <String>['@tag', '@tag1'],
steps: [
(TestDependencies dependencies, bool hasToSkip) async {
return await runStep(
'Given I have item with data',
<String>[
"""{
"glossary": {
"title": "example glossary",
"GlossDiv": {
"title": "S",
"GlossList": {
"GlossEntry": {
"ID": "SGML",
"SortAs": "SGML",
"GlossTerm": "Standard Generalized Markup Language",
"Acronym": "SGML",
"Abbrev": "ISO 8879:1986",
"GlossDef": {
"para": "A meta-markup language, used to create markup languages such as DocBook.",
"GlossSeeAlso": [
"GML",
"XML"
]
},
"GlossSee": "markup"
}
}
}
}
}"""
],
null,
dependencies,
hasToSkip,
);
}
],
onBefore: () async => onBeforeRunFeature(
'Checking data',
<String>['@tag'],
),
onAfter: () async => onAfterRunFeature('Checking data',
'C:\Development\github\flutter_gherkin\example_with_integration_test\.\integration_test\features\check.feature'),
);
},
);

View File

@ -1,7 +1,6 @@
import 'package:example_with_integration_test/models/todo_model.dart';
import 'package:example_with_integration_test/widgets/view_utils_mixin.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:rxdart/rxdart.dart';
class AddTodoComponent extends StatefulWidget {

View File

@ -3,7 +3,6 @@ import 'package:example_with_integration_test/models/todo_model.dart';
import 'package:example_with_integration_test/models/todo_status_enum.dart';
import 'package:example_with_integration_test/widgets/components/add_todo_component.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import '../view_utils_mixin.dart';

View File

@ -21,7 +21,7 @@ packages:
name: archive
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.2"
version: "3.1.11"
args:
dependency: transitive
description:
@ -35,7 +35,7 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.1"
version: "2.8.2"
boolean_selector:
dependency: transitive
description:
@ -105,7 +105,7 @@ packages:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "1.2.0"
charcode:
dependency: transitive
description:
@ -147,7 +147,7 @@ packages:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0"
version: "1.16.0"
convert:
dependency: transitive
description:
@ -175,7 +175,7 @@ packages:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.3.0"
ffi:
dependency: transitive
description:
@ -213,7 +213,7 @@ packages:
path: ".."
relative: true
source: path
version: "3.0.0-rc.9"
version: "3.0.0"
flutter_simple_dependency_injection:
dependency: "direct main"
description:
@ -249,7 +249,7 @@ packages:
name: gherkin
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.8"
version: "3.0.0+1"
glob:
dependency: transitive
description:
@ -296,7 +296,7 @@ packages:
name: js
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.3"
version: "0.6.4"
json_annotation:
dependency: "direct main"
description:
@ -324,7 +324,14 @@ packages:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.10"
version: "0.12.11"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.4"
meta:
dependency: transitive
description:
@ -352,7 +359,7 @@ packages:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
version: "1.8.1"
path_provider_linux:
dependency: transitive
description:
@ -380,7 +387,7 @@ packages:
name: platform
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
version: "3.1.0"
plugin_platform_interface:
dependency: transitive
description:
@ -401,7 +408,7 @@ packages:
name: process
url: "https://pub.dartlang.org"
source: hosted
version: "4.2.3"
version: "4.2.4"
pub_semver:
dependency: transitive
description:
@ -504,7 +511,7 @@ packages:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.1"
version: "1.8.2"
stack_trace:
dependency: transitive
description:
@ -553,7 +560,7 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.2"
version: "0.4.9"
timing:
dependency: transitive
description:
@ -574,21 +581,21 @@ packages:
name: uuid
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.5"
version: "3.0.6"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
version: "2.1.2"
vm_service:
dependency: transitive
description:
name: vm_service
url: "https://pub.dartlang.org"
source: hosted
version: "7.1.1"
version: "8.2.2"
watcher:
dependency: transitive
description:
@ -632,5 +639,5 @@ packages:
source: hosted
version: "3.1.0"
sdks:
dart: ">=2.14.0 <3.0.0"
dart: ">=2.17.0 <3.0.0"
flutter: ">=2.5.0"

View File

@ -6,8 +6,8 @@ publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=2.14.0 <3.0.0'
flutter: ">=2.5.0"
sdk: '>=2.17.0 <3.0.0'
flutter: ">=2.2.0"
dependencies:
flutter:

View File

@ -29,6 +29,7 @@ export 'src/flutter/steps/text_exists_within_step.dart';
export 'src/flutter/steps/wait_until_key_exists_step.dart';
export 'src/flutter/steps/when_tap_the_back_button_step.dart';
export 'src/flutter/steps/wait_until_type_exists_step.dart';
export 'src/flutter/steps/wait_until_key_exists_step.dart';
// Hooks
export 'src/flutter/hooks/attach_screenshot_on_failed_step_hook.dart';

View File

@ -1,16 +1,22 @@
import 'dart:async';
import 'dart:ui' as ui show ImageByteFormat;
import 'dart:io' if (dart.library.html) 'dart:html';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'app_driver_adapter.dart';
class WidgetTesterAppDriverAdapter
extends AppDriverAdapter<WidgetTester, Finder, Widget> {
WidgetTesterAppDriverAdapter(WidgetTester rawAdapter) : super(rawAdapter);
IntegrationTestWidgetsFlutterBinding binding;
bool waitImplicitlyAfterAction;
WidgetTesterAppDriverAdapter({
required WidgetTester rawAdapter,
required this.binding,
required this.waitImplicitlyAfterAction,
}) : super(rawAdapter);
@override
Future<int> waitForAppToSettle({
@ -30,6 +36,21 @@ class WidgetTesterAppDriverAdapter
}
}
Future<void> _implicitWait({
Duration? duration = const Duration(milliseconds: 100),
Duration? timeout = const Duration(seconds: 30),
}) async {
if (waitImplicitlyAfterAction) {
try {
await nativeDriver.pumpAndSettle(
duration ?? const Duration(milliseconds: 100),
EnginePhase.sendSemanticsUpdate,
timeout ?? const Duration(seconds: 30),
);
} catch (_) {}
}
}
@override
Future<T> widget<T extends Widget>(
Finder finder, [
@ -47,20 +68,15 @@ class WidgetTesterAppDriverAdapter
}
@override
Future<List<int>> screenshot() {
var renderObject = nativeDriver.binding.renderViewElement?.renderObject;
while (renderObject != null && !renderObject.isRepaintBoundary) {
renderObject = renderObject.parent as RenderObject;
Future<List<int>> screenshot() async {
if (!kIsWeb && Platform.isAndroid) {
await binding.convertFlutterSurfaceToImage();
await binding.pump();
}
assert(renderObject != null && !renderObject.debugNeedsPaint);
final layer = renderObject!.debugLayer as OffsetLayer;
return layer
.toImage(renderObject.semanticBounds)
.then((value) => value.toByteData(format: ui.ImageByteFormat.png))
.then((value) => value?.buffer.asUint8List() ?? List<int>.empty());
return binding.takeScreenshot(
'screenshot_${DateTime.now().millisecondsSinceEpoch}',
);
}
@override
@ -84,7 +100,7 @@ class WidgetTesterAppDriverAdapter
Finder finder, {
Duration? timeout = const Duration(seconds: 30),
}) async {
await waitForAppToSettle(timeout: timeout);
await _implicitWait(timeout: timeout);
final instance = await widget(finder);
if (instance is Text) {
@ -109,7 +125,7 @@ class WidgetTesterAppDriverAdapter
finder,
text,
);
await waitForAppToSettle(
await _implicitWait(
timeout: timeout,
);
}
@ -120,7 +136,7 @@ class WidgetTesterAppDriverAdapter
Duration? timeout = const Duration(seconds: 30),
}) async {
await nativeDriver.tap(finder);
await waitForAppToSettle(
await _implicitWait(
timeout: timeout,
);
}
@ -138,7 +154,7 @@ class WidgetTesterAppDriverAdapter
duration: pressDuration,
timeout: timeout,
);
await waitForAppToSettle(timeout: timeout);
await _implicitWait(timeout: timeout);
}
@override
@ -233,6 +249,6 @@ class WidgetTesterAppDriverAdapter
@override
Future<void> pageBack() async {
await nativeDriver.pageBack();
await waitForAppToSettle();
await _implicitWait();
}
}

View File

@ -11,11 +11,11 @@ import 'package:source_gen/source_gen.dart';
class NoOpReporter extends MessageReporter {
@override
Future<void> message(String message, MessageLevel level) async {
if(level == MessageLevel.info || level == MessageLevel.debug) {
if (level == MessageLevel.info || level == MessageLevel.debug) {
print(message);
}else if(level == MessageLevel.warning) {
} else if (level == MessageLevel.warning) {
print('\x1B[33m$message\x1B[0m');
}else if(level == MessageLevel.error) {
} else if (level == MessageLevel.error) {
print('\x1B[31m$message\x1B[0m');
}
}
@ -63,7 +63,7 @@ void executeTestSuite(
.getField('index')!
.toIntValue()!;
final executionOrder = ExecutionOrder.values[idx];
final featureFiles = annotation
final featureFiles = annotation
.read('featurePaths')
.listValue
.map((path) => Glob(path.toStringValue()!))
@ -139,9 +139,10 @@ class FeatureFileTestGeneratorVisitor extends FeatureFileVisitor {
''';
static const String SCENARIO_TEMPLATE = '''
runScenario(
'{{scenario_name}}',
{{tags}},
[
name: '{{scenario_name}}',
path: '{{path}}',
tags:{{tags}},
steps: [
{{steps}}
],
{{onBefore}}

View File

@ -1,28 +1,63 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter_gherkin/flutter_gherkin_with_driver.dart';
import 'package:flutter_gherkin/src/flutter/configuration/build_mode.dart';
import 'package:flutter_gherkin/src/flutter/world/flutter_driver_world.dart';
import 'package:flutter_gherkin/src/flutter/world/flutter_world.dart';
import 'package:flutter_gherkin/src/flutter/hooks/app_runner_hook.dart';
import 'package:flutter_driver/flutter_driver.dart';
import 'package:gherkin/gherkin.dart';
import 'flutter_test_configuration.dart';
class FlutterDriverTestConfiguration extends FlutterTestConfiguration {
String? _observatoryDebuggerUri;
FlutterDriverTestConfiguration({
String? featurePath = 'features/*.*.feature',
Iterable<Pattern>? features,
super.featureDefaultLanguage = 'en',
super.order = ExecutionOrder.random,
super.defaultTimeout = const Duration(seconds: 10),
super.featureFileMatcher = const IoFeatureFileAccessor(),
super.featureFileReader = const IoFeatureFileAccessor(),
super.stopAfterTestFailed = false,
super.tagExpression,
super.hooks,
super.reporters = const [],
super.createWorld,
super.waitImplicitlyAfterAction = true,
super.customStepParameterDefinitions,
super.stepDefinitions,
this.targetAppPath = 'test_driver/app.dart',
this.targetAppWorkingDirectory,
this.buildFlavor,
this.targetDeviceId,
this.runningAppProtocolEndpointUri,
this.onBeforeFlutterDriverConnect,
this.onAfterFlutterDriverConnect,
this.restartAppBetweenScenarios = true,
this.logFlutterProcessOutput = false,
this.keepAppRunningAfterTests = false,
this.verboseFlutterProcessLogs = false,
this.build = true,
this.buildMode = BuildMode.Debug,
this.flutterBuildTimeout = const Duration(seconds: 90),
this.flutterDriverReconnectionDelay = const Duration(seconds: 2),
this.flutterDriverMaxConnectionAttempts = 3,
}) :
// assert(featurePath != null && features != null),
super(
features: features != null ? features : [RegExp(featurePath!)],
);
/// Provide a configuration object with default settings such as the reports and feature file location
/// Additional setting on the configuration object can be set on the returned instance.
static FlutterDriverTestConfiguration DEFAULT(
Iterable<StepDefinitionGeneric<World>> steps, {
String featurePath = 'features/*.*.feature',
String targetAppPath = 'test_driver/app.dart',
String? targetAppWorkingDirectory,
bool restartAppBetweenScenarios = true,
}) {
return FlutterDriverTestConfiguration()
..features = [RegExp(featurePath)]
..reporters = [
return FlutterDriverTestConfiguration(
features: [RegExp(featurePath)],
reporters: [
StdoutReporter(MessageLevel.error),
ProgressReporter(),
TestRunSummaryReporter(),
@ -32,81 +67,84 @@ class FlutterDriverTestConfiguration extends FlutterTestConfiguration {
logInfoMessages: false,
logWarningMessages: false,
),
]
..targetAppPath = targetAppPath
..stepDefinitions = steps
..restartAppBetweenScenarios = true;
],
targetAppPath: targetAppPath,
targetAppWorkingDirectory: targetAppWorkingDirectory,
stepDefinitions: steps,
restartAppBetweenScenarios: restartAppBetweenScenarios,
);
}
/// restarts the application under test between each scenario.
/// Defaults to true to avoid the application being in an invalid state
/// before each test
bool restartAppBetweenScenarios = true;
final bool restartAppBetweenScenarios;
/// The target app to run the tests against
/// Defaults to "test_driver/app.dart"
String targetAppPath = 'test_driver/app.dart';
final String targetAppPath;
/// Option to define the working directory for the process that runs the app under test (optional)
/// Handy if your app is separated from your tests as flutter needs to be able to find a pubspec file
String? targetAppWorkingDirectory;
final String? targetAppWorkingDirectory;
/// The build flavor to run the tests against (optional)
/// Defaults to null
String? buildFlavor;
final String? buildFlavor;
/// The default build mode used for running tests is --debug.
/// We are exposing the option to run the tests also in --profile mode
BuildMode buildMode = BuildMode.Debug;
final BuildMode buildMode;
/// If the application should be built prior to running the tests
/// Defaults to true
bool build = true;
final bool build;
/// The target device id to run the tests against when multiple devices detected
/// Defaults to null
String? targetDeviceId;
final String? targetDeviceId;
/// Will keep the Flutter application running when done testing
/// Defaults to false
bool keepAppRunningAfterTests = false;
final bool keepAppRunningAfterTests;
/// Logs Flutter process output to stdout
/// The Flutter process is use to start and driver the app under test.
/// The output may contain build and run information
/// Defaults to false
bool logFlutterProcessOutput = false;
final bool logFlutterProcessOutput;
/// Sets the --verbose flag on the flutter process
/// Defaults to false
bool verboseFlutterProcessLogs = false;
final bool verboseFlutterProcessLogs;
/// Duration to wait for Flutter to build and start the app on the target device
/// Slower machine may take longer to build and run a large app
/// Defaults to 90 seconds
Duration flutterBuildTimeout = const Duration(seconds: 90);
final Duration flutterBuildTimeout;
/// Duration to wait before reconnecting the Flutter driver to the app.
/// On slower machines the app might not be in a state where the driver can successfully connect immediately
/// Defaults to 2 seconds
Duration flutterDriverReconnectionDelay = const Duration(seconds: 2);
final Duration flutterDriverReconnectionDelay;
/// The maximum times the flutter driver can try and connect to the running app
/// Defaults to 3
int flutterDriverMaxConnectionAttempts = 3;
final int flutterDriverMaxConnectionAttempts;
/// An observatory url that the test runner can connect to instead of creating a new running instance of the target application
/// Url takes the form of `http://127.0.0.1:51540/EM72VtRsUV0=/` and usually printed to stdout in the form `Connecting to service protocol: http://127.0.0.1:51540/EM72VtRsUV0=/`
/// You will have to add the `--verbose` flag to the command to start your flutter app to see this output and ensure `enableFlutterDriverExtension()` is called by the running app
String? runningAppProtocolEndpointUri;
final String? runningAppProtocolEndpointUri;
/// Called before any attempt to connect Flutter driver to the running application, Depending on your configuration this
/// method will be called before each scenario is run.
Future<void> Function()? onBeforeFlutterDriverConnect;
final Future<void> Function()? onBeforeFlutterDriverConnect;
/// Called after the successful connection of Flutter driver to the running application. Depending on your configuration this
/// method will be called on each new connection usually before each scenario is run.
Future<void> Function(FlutterDriver driver)? onAfterFlutterDriverConnect;
final Future<void> Function(FlutterDriver driver)?
onAfterFlutterDriverConnect;
void setObservatoryDebuggerUri(String uri) => _observatoryDebuggerUri = uri;
@ -157,20 +195,41 @@ class FlutterDriverTestConfiguration extends FlutterTestConfiguration {
}
@override
void prepare() {
TestConfiguration prepare() {
super.prepare();
_ensureCorrectConfiguration();
final providedCreateWorld = createWorld;
createWorld = (config) async {
FlutterWorld? world;
if (providedCreateWorld != null) {
world = await providedCreateWorld(config) as FlutterWorld;
}
return await createFlutterWorld(config, world);
};
return FlutterDriverTestConfiguration(
buildFlavor: this.buildFlavor,
customStepParameterDefinitions: this.customStepParameterDefinitions,
defaultTimeout: this.defaultTimeout,
featureDefaultLanguage: this.featureDefaultLanguage,
featureFileMatcher: this.featureFileMatcher,
featureFileReader: this.featureFileReader,
features: this.features,
onAfterFlutterDriverConnect: this.onAfterFlutterDriverConnect,
onBeforeFlutterDriverConnect: this.onBeforeFlutterDriverConnect,
order: this.order,
reporters: this.reporters,
restartAppBetweenScenarios: this.restartAppBetweenScenarios,
runningAppProtocolEndpointUri: this.runningAppProtocolEndpointUri,
stepDefinitions: this.stepDefinitions,
stopAfterTestFailed: this.stopAfterTestFailed,
tagExpression: this.tagExpression,
targetAppPath: this.targetAppPath,
targetAppWorkingDirectory: this.targetAppWorkingDirectory,
targetDeviceId: this.targetDeviceId,
createWorld: (config) async {
FlutterWorld? world;
if (providedCreateWorld != null) {
world = await providedCreateWorld(config) as FlutterWorld;
}
hooks = List.from(hooks ?? Iterable.empty())..add(FlutterAppRunnerHook());
return await createFlutterWorld(config, world);
},
hooks: List.from(hooks ?? Iterable.empty())..add(FlutterAppRunnerHook()),
);
}
Future<FlutterDriver> _attemptDriverConnection(

View File

@ -1,34 +1,50 @@
import 'package:flutter_gherkin/flutter_gherkin_with_driver.dart';
import 'package:flutter_gherkin/src/flutter/parameters/existence_parameter.dart';
import 'package:flutter_gherkin/src/flutter/parameters/swipe_direction_parameter.dart';
import 'package:flutter_gherkin/src/flutter/steps/given_i_open_the_drawer_step.dart';
import 'package:flutter_gherkin/src/flutter/steps/restart_app_step.dart';
import 'package:flutter_gherkin/src/flutter/steps/sibling_contains_text_step.dart';
import 'package:flutter_gherkin/src/flutter/steps/swipe_step.dart';
import 'package:flutter_gherkin/src/flutter/steps/tap_text_within_widget_step.dart';
import 'package:flutter_gherkin/src/flutter/steps/tap_widget_of_type_step.dart';
import 'package:flutter_gherkin/src/flutter/steps/tap_widget_of_type_within_step.dart';
import 'package:flutter_gherkin/src/flutter/steps/tap_widget_with_text_step.dart';
import 'package:flutter_gherkin/src/flutter/steps/text_exists_step.dart';
import 'package:flutter_gherkin/src/flutter/steps/text_exists_within_step.dart';
import 'package:flutter_gherkin/src/flutter/steps/then_expect_element_to_have_value_step.dart';
import 'package:flutter_gherkin/src/flutter/steps/then_expect_widget_to_be_present_step.dart';
import 'package:flutter_gherkin/src/flutter/steps/wait_until_key_exists_step.dart';
import 'package:flutter_gherkin/src/flutter/steps/wait_until_type_exists_step.dart';
import 'package:flutter_gherkin/src/flutter/steps/when_fill_field_step.dart';
import 'package:flutter_gherkin/src/flutter/steps/when_long_press_widget_step.dart';
import 'package:flutter_gherkin/src/flutter/steps/when_pause_step.dart';
import 'package:flutter_gherkin/src/flutter/steps/when_tap_widget_step.dart';
import 'package:flutter_gherkin/src/flutter/steps/when_tap_the_back_button_step.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:gherkin/gherkin.dart';
class FlutterTestConfiguration extends TestConfiguration {
static final Iterable<CustomParameter<dynamic>> _wellKnownParameters = [
ExistenceParameter(),
SwipeDirectionParameter(),
];
static final _wellKnownStepDefinitions = [
ThenExpectElementToHaveValue(),
WhenTapBackButtonWidget(),
WhenTapWidget(),
WhenTapWidgetWithoutScroll(),
WhenLongPressWidget(),
WhenLongPressWidgetWithoutScroll(),
WhenLongPressWidgetForDuration(),
GivenOpenDrawer(),
WhenPauseStep(),
WhenFillFieldStep(),
ThenExpectWidgetToBePresent(),
RestartAppStep(),
SiblingContainsTextStep(),
SwipeOnKeyStep(),
SwipeOnTextStep(),
TapTextWithinWidgetStep(),
TapWidgetOfTypeStep(),
TapWidgetOfTypeWithinStep(),
TapWidgetWithTextStep(),
TextExistsStep(),
TextExistsWithinStep(),
WaitUntilKeyExistsStep(),
WaitUntilTypeExistsStep(),
];
/// Enable semantics in a test by creating a [SemanticsHandle].
/// See: [testWidgets] and [WidgetController.ensureSemantics].
bool semanticsEnabled = true;
final bool semanticsEnabled;
/// Set to `True` to wait implicit for pumpAndSettle() / waitForAppToSettle() functions after performing actions
/// Defaults to false
final bool waitImplicitlyAfterAction;
/// Provide a configuration object with default settings such as the reports and feature file location
/// Additional setting on the configuration object can be set on the returned instance.
static FlutterTestConfiguration DEFAULT(
@ -36,49 +52,38 @@ class FlutterTestConfiguration extends TestConfiguration {
String featurePath = 'integration_test/features/*.*.feature',
String targetAppPath = 'test_driver/integration_test_driver.dart',
}) {
return FlutterTestConfiguration()
..reporters = [
return FlutterTestConfiguration(
reporters: [
StdoutReporter(MessageLevel.error),
ProgressReporter(),
TestRunSummaryReporter(),
// JsonReporter(path: './report.json'),
]
..stepDefinitions = steps;
],
stepDefinitions: steps,
);
}
@override
void prepare() {
customStepParameterDefinitions =
List.from(customStepParameterDefinitions ?? Iterable.empty())
..addAll([
ExistenceParameter(),
SwipeDirectionParameter(),
]);
stepDefinitions = List.from(stepDefinitions ?? Iterable.empty())
..addAll([
ThenExpectElementToHaveValue(),
WhenTapBackButtonWidget(),
WhenTapWidget(),
WhenTapWidgetWithoutScroll(),
WhenLongPressWidget(),
WhenLongPressWidgetWithoutScroll(),
WhenLongPressWidgetForDuration(),
GivenOpenDrawer(),
WhenPauseStep(),
WhenFillFieldStep(),
ThenExpectWidgetToBePresent(),
RestartAppStep(),
SiblingContainsTextStep(),
SwipeOnKeyStep(),
SwipeOnTextStep(),
TapTextWithinWidgetStep(),
TapWidgetOfTypeStep(),
TapWidgetOfTypeWithinStep(),
TapWidgetWithTextStep(),
TextExistsStep(),
TextExistsWithinStep(),
WaitUntilKeyExistsStep(),
WaitUntilTypeExistsStep(),
]);
}
FlutterTestConfiguration({
super.features = const <Pattern>[],
super.featureDefaultLanguage = 'en',
super.order = ExecutionOrder.random,
super.defaultTimeout = const Duration(seconds: 10),
super.featureFileMatcher = const IoFeatureFileAccessor(),
super.featureFileReader = const IoFeatureFileAccessor(),
super.stopAfterTestFailed = false,
super.tagExpression,
super.hooks,
super.reporters = const [],
super.createWorld,
this.semanticsEnabled = true,
this.waitImplicitlyAfterAction = false,
Iterable<CustomParameter<dynamic>>? customStepParameterDefinitions,
Iterable<StepDefinitionGeneric<World>>? stepDefinitions,
}) : super(
customStepParameterDefinitions:
List.from(customStepParameterDefinitions ?? Iterable.empty())
..addAll(_wellKnownParameters),
stepDefinitions: List.from(stepDefinitions ?? Iterable.empty())
..addAll(_wellKnownStepDefinitions),
);
}

View File

@ -1,4 +1,4 @@
import 'dart:io';
import 'dart:io' if (dart.library.html) 'dart:html';
import 'package:flutter_gherkin/src/flutter/configuration/flutter_driver_test_configuration.dart';
import 'package:flutter_gherkin/src/flutter/runners/flutter_run_process_handler.dart';
import 'package:flutter_gherkin/src/flutter/world/flutter_driver_world.dart';
@ -38,9 +38,9 @@ class FlutterAppRunnerHook extends Hook {
Future<void> onAfterScenario(
TestConfiguration config,
String scenario,
Iterable<Tag> tags,
{bool? passed,}
) async {
Iterable<Tag> tags, {
bool? passed,
}) async {
final flutterConfig = _castConfig(config);
haveRunFirstScenario = true;
if (_flutterRunProcessHandler != null &&

View File

@ -13,11 +13,14 @@ enum _FlutterDriverMessageLogLevel { info, warning, error }
/// This can cause problems with CI servers for example as they will mark a process as failed if it logs to the
/// stderr stream. So Flutter driver will log a normal info message to the stderr and thus make
/// the process fail from the perspective of a CI server.
class FlutterDriverReporter extends Reporter {
class FlutterDriverReporter extends Reporter
implements DisposableReporter, TestReporter {
final bool logErrorMessages;
final bool logWarningMessages;
final bool logInfoMessages;
DriverLogCallback? defaultCallback;
FlutterDriverReporter({
this.logErrorMessages = true,
this.logWarningMessages = true,
@ -25,13 +28,18 @@ class FlutterDriverReporter extends Reporter {
});
@override
Future<void> onTestRunStarted() async {
driverLog = _driverLogMessageHandler;
}
ReportActionHandler<TestMessage> get test => ReportActionHandler(
onStarted: ([_]) async {
defaultCallback = driverLog;
driverLog = _driverLogMessageHandler;
},
);
@override
Future<void> dispose() async {
// driverLog = null;
if (defaultCallback != null) {
driverLog = defaultCallback!;
}
}
void _driverLogMessageHandler(String source, String message) {

View File

@ -49,8 +49,7 @@ abstract class GherkinIntegrationTestRunner {
}
Future<void> run() async {
_binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized()
as IntegrationTestWidgetsFlutterBinding;
_binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
_binding!.framePolicy =
framePolicy ?? LiveTestWidgetsFlutterBindingFramePolicy.benchmarkLive;
@ -77,10 +76,8 @@ abstract class GherkinIntegrationTestRunner {
}
void setTestResultData(IntegrationTestWidgetsFlutterBinding binding) {
if (reporter is SerializableReporter) {
final json = (reporter as SerializableReporter).serialize();
binding.reportData = {'gherkin_reports': json};
}
final json = (reporter).serialize();
binding.reportData = {'gherkin_reports': json};
}
@protected
@ -115,10 +112,7 @@ abstract class GherkinIntegrationTestRunner {
}
@protected
Future<void> onAfterRunFeature(
String name,
String path
) async {
Future<void> onAfterRunFeature(String name, String path) async {
final debugInformation = RunnableDebugInformation(path, 0, name);
await reporter.test.onFinished.maybeCall(
TestMessage(
@ -130,11 +124,16 @@ abstract class GherkinIntegrationTestRunner {
}
@protected
void runScenario(
String name,
Iterable<String>? tags,
List<Future<StepResult> Function(TestDependencies dependencies, bool skip,)>
steps, String path, {
void runScenario({
required String name,
required Iterable<String>? tags,
required List<
Future<StepResult> Function(
TestDependencies dependencies,
bool skip,
)>
steps,
required String path,
Future<void> Function()? onBefore,
Future<void> Function()? onAfter,
}) {
@ -257,7 +256,15 @@ abstract class GherkinIntegrationTestRunner {
world = world ?? FlutterWidgetTesterWorld();
world.setAttachmentManager(attachmentManager);
(world as FlutterWorld).setAppAdapter(WidgetTesterAppDriverAdapter(tester));
(world as FlutterWorld).setAppAdapter(
WidgetTesterAppDriverAdapter(
rawAdapter: tester,
binding: _binding!,
waitImplicitlyAfterAction: configuration is FlutterTestConfiguration
? (configuration).waitImplicitlyAfterAction
: true,
),
);
return TestDependencies(
world,
@ -266,8 +273,13 @@ abstract class GherkinIntegrationTestRunner {
}
@protected
Future<StepResult> runStep(String step, Iterable<String> multiLineStrings,
dynamic table, TestDependencies dependencies, bool hasToSkip,) async {
Future<StepResult> runStep(
String step,
Iterable<String> multiLineStrings,
dynamic table,
TestDependencies dependencies,
bool hasToSkip,
) async {
final executable = _executableSteps!.firstWhereOrNull(
(s) => s.expression.isMatch(step),
);
@ -294,8 +306,8 @@ abstract class GherkinIntegrationTestRunner {
StepResult? result;
if (hasToSkip) {
result = new StepResult(
0, StepExecutionResult.skipped, resultReason: "Previous step(s) failed.");
result = new StepResult(0, StepExecutionResult.skipped,
resultReason: "Previous step(s) failed.");
} else {
for (int i = 0; i < this.configuration.stepMaxRetries + 1; i++) {
result = await executable.step.run(
@ -426,7 +438,9 @@ abstract class GherkinIntegrationTestRunner {
name: step,
context: RunnableDebugInformation('', 0, step),
result: result,
attachments: dependencies.attachmentManager.getAttachmentsForContext(step).toList(),
attachments: dependencies.attachmentManager
.getAttachmentsForContext(step)
.toList(),
),
);
}

View File

@ -1,6 +1,4 @@
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:flutter_gherkin/src/flutter/adapters/app_driver_adapter.dart';
import 'package:flutter_gherkin/src/flutter/world/flutter_world.dart';
import 'package:gherkin/gherkin.dart';
/// Discovers a widget by its text within the same parent.

View File

@ -1,5 +1,4 @@
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:flutter_gherkin/src/flutter/world/flutter_world.dart';
import 'package:gherkin/gherkin.dart';
import '../parameters/swipe_direction_parameter.dart';

View File

@ -1,5 +1,4 @@
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:flutter_gherkin/src/flutter/adapters/app_driver_adapter.dart';
import 'package:gherkin/gherkin.dart';
/// Taps a widget that contains the text within another widget.

View File

@ -1,5 +1,4 @@
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:flutter_gherkin/src/flutter/adapters/app_driver_adapter.dart';
import 'package:gherkin/gherkin.dart';
/// Taps a widget of type.

View File

@ -1,5 +1,4 @@
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:flutter_gherkin/src/flutter/adapters/app_driver_adapter.dart';
import 'package:gherkin/gherkin.dart';
/// Taps a widget of type within another widget.

View File

@ -1,5 +1,4 @@
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:flutter_gherkin/src/flutter/adapters/app_driver_adapter.dart';
import 'package:gherkin/gherkin.dart';
/// Taps a widget that contains text.

View File

@ -1,5 +1,4 @@
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:flutter_gherkin/src/flutter/adapters/app_driver_adapter.dart';
import 'package:gherkin/gherkin.dart';
import '../parameters/existence_parameter.dart';

View File

@ -1,5 +1,4 @@
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:flutter_gherkin/src/flutter/adapters/app_driver_adapter.dart';
import 'package:gherkin/gherkin.dart';
import '../parameters/existence_parameter.dart';

View File

@ -1,5 +1,4 @@
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:flutter_gherkin/src/flutter/adapters/app_driver_adapter.dart';
import 'package:gherkin/gherkin.dart';
import '../parameters/existence_parameter.dart';

View File

@ -1,5 +1,4 @@
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:flutter_gherkin/src/flutter/adapters/app_driver_adapter.dart';
import 'package:gherkin/gherkin.dart';
import '../parameters/existence_parameter.dart';

View File

@ -7,14 +7,14 @@ packages:
name: _fe_analyzer_shared
url: "https://pub.dartlang.org"
source: hosted
version: "30.0.0"
version: "40.0.0"
analyzer:
dependency: "direct main"
description:
name: analyzer
url: "https://pub.dartlang.org"
source: hosted
version: "2.7.0"
version: "4.1.0"
archive:
dependency: transitive
description:
@ -28,7 +28,7 @@ packages:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.0"
version: "2.3.1"
async:
dependency: transitive
description:
@ -49,7 +49,7 @@ packages:
name: build
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
version: "2.3.0"
build_config:
dependency: "direct dev"
description:
@ -78,13 +78,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
cli_util:
dependency: transitive
description:
name: cli_util
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.5"
clock:
dependency: transitive
description:
@ -105,7 +98,7 @@ packages:
name: convert
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
version: "3.0.2"
crypto:
dependency: transitive
description:
@ -119,7 +112,7 @@ packages:
name: dart_style
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.0"
version: "2.2.3"
fake_async:
dependency: transitive
description:
@ -160,14 +153,14 @@ packages:
name: gherkin
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
version: "3.0.0+1"
glob:
dependency: "direct main"
description:
name: glob
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
version: "2.1.0"
integration_test:
dependency: "direct main"
description: flutter
@ -179,7 +172,7 @@ packages:
name: json_annotation
url: "https://pub.dartlang.org"
source: hosted
version: "4.3.0"
version: "4.5.0"
logging:
dependency: transitive
description:
@ -214,7 +207,7 @@ packages:
name: package_config
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
version: "2.1.0"
path:
dependency: transitive
description:
@ -249,14 +242,14 @@ packages:
name: pub_semver
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
version: "2.1.1"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "1.2.0"
sky_engine:
dependency: transitive
description: flutter
@ -268,7 +261,7 @@ packages:
name: source_gen
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
version: "1.2.2"
source_span:
dependency: transitive
description:
@ -366,7 +359,7 @@ packages:
name: yaml
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.0"
version: "3.1.1"
sdks:
dart: ">=2.17.0-0 <3.0.0"
dart: ">=2.17.0 <3.0.0"
flutter: ">=2.2.0"

View File

@ -1,10 +1,10 @@
name: flutter_gherkin
description: A Gherkin / Cucumber parser and test runner for Dart and Flutter
version: 3.0.0-rc.9
version: 3.0.0
homepage: https://github.com/jonsamwell/flutter_gherkin
environment:
sdk: '>=2.12.3 <3.0.0'
sdk: '>=2.17.0 <3.0.0'
flutter: ">=2.2.0"
dependencies:
@ -16,15 +16,15 @@ dependencies:
sdk: flutter
flutter_driver:
sdk: flutter
analyzer: ">=1.7.1 <3.0.0"
analyzer: '>=2.1.0 < 5.0.0'
collection: ^1.15.0
gherkin: ^3.0.0
gherkin: ^3.0.0+1
source_gen: ^1.1.1
build: ^2.1.1
glob: ^2.0.2
dev_dependencies:
meta: ^1.7.0
meta: '>=1.7.0 < 2.0.0'
pedantic: ^1.11.1
build_config: ^1.0.0

View File

@ -6,43 +6,37 @@ import 'mocks/step_definition_mock.dart';
void main() {
group('config', () {
group('prepare', () {
test('flutter app runner hook added', () {
final config = FlutterDriverTestConfiguration();
expect(config.hooks, isNull);
config.prepare();
expect(config.hooks, isNotNull);
expect(config.hooks!.length, 1);
expect(config.hooks!.elementAt(0), (x) => x is FlutterAppRunnerHook);
});
test('flutter app runner hook added', () {
final config = FlutterDriverTestConfiguration();
final newConfig = config.prepare();
test('common steps definition added', () {
final config = FlutterDriverTestConfiguration();
expect(config.stepDefinitions, isNull);
expect(newConfig.hooks, isNotNull);
expect(newConfig.hooks!.length, 1);
expect(newConfig.hooks!.elementAt(0), (x) => x is FlutterAppRunnerHook);
});
config.prepare();
expect(config.stepDefinitions, isNotNull);
expect(config.stepDefinitions!.length, 23);
expect(config.customStepParameterDefinitions, isNotNull);
expect(config.customStepParameterDefinitions!.length, 2);
});
test('common steps definition added', () {
final config = FlutterDriverTestConfiguration();
expect(config.stepDefinitions, isNotNull);
expect(config.stepDefinitions!.length, 23);
expect(config.customStepParameterDefinitions, isNotNull);
expect(config.customStepParameterDefinitions!.length, 2);
});
test('common step definition added to existing steps', () {
final config = FlutterTestConfiguration()
..stepDefinitions = [MockStepDefinition()]
..customStepParameterDefinitions = [MockParameter()];
expect(config.stepDefinitions!.length, 1);
test('common step definition added to existing steps', () {
final config = FlutterTestConfiguration(
stepDefinitions: [MockStepDefinition()],
customStepParameterDefinitions: [MockParameter()],
);
config.prepare();
expect(config.stepDefinitions, isNotNull);
expect(config.stepDefinitions!.length, 24);
expect(config.stepDefinitions!.elementAt(0),
(x) => x is MockStepDefinition);
expect(config.customStepParameterDefinitions, isNotNull);
expect(config.customStepParameterDefinitions!.length, 3);
expect(config.customStepParameterDefinitions!.elementAt(0),
(x) => x is MockParameter);
});
expect(config.stepDefinitions, isNotNull);
expect(config.stepDefinitions!.length, 24);
expect(
config.stepDefinitions!.elementAt(0), (x) => x is MockStepDefinition);
expect(config.customStepParameterDefinitions, isNotNull);
expect(config.customStepParameterDefinitions!.length, 3);
expect(config.customStepParameterDefinitions!.elementAt(0),
(x) => x is MockParameter);
});
});
}