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. # 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 .DS_Store
.dart_tool/
.packages
.pub/
build/
ios/.generated/ ios/.generated/
ios/Flutter/Generated.xcconfig ios/Flutter/Generated.xcconfig
ios/Runner/GeneratedPluginRegistrant.* ios/Runner/GeneratedPluginRegistrant.*
node_modules node_modules
package-lock.json 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 ## [3.0.0-rc.9] - 18/11/2021
- Fix: #172: Fix for the `StdoutReporter` when running against the web - 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" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android { android {
compileSdkVersion 29 compileSdkVersion 31
sourceSets { sourceSets {
main.java.srcDirs += 'src/main/kotlin' main.java.srcDirs += 'src/main/kotlin'

View File

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

View File

@ -1,12 +1,12 @@
buildscript { buildscript {
ext.kotlin_version = '1.3.50' ext.kotlin_version = '1.6.10'
repositories { repositories {
google() google()
jcenter() jcenter()
} }
dependencies { 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" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }

View File

@ -3,4 +3,5 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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 # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: 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: archive:
dependency: transitive dependency: transitive
description: description:
name: archive name: archive
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted 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: async:
dependency: transitive dependency: transitive
description: description:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.6.1" version: "2.8.2"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -22,20 +43,34 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.1.0"
build:
dependency: transitive
description:
name: build
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.0"
characters: characters:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.2.0"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
name: charcode name: charcode
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted 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: clock:
dependency: transitive dependency: transitive
description: description:
@ -49,7 +84,14 @@ packages:
name: collection name: collection
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted 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: crypto:
dependency: transitive dependency: transitive
description: description:
@ -57,20 +99,27 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.1" 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: fake_async:
dependency: transitive dependency: transitive
description: description:
name: fake_async name: fake_async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.3.0"
file: file:
dependency: transitive dependency: transitive
description: description:
name: file name: file
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.1.0" version: "6.1.2"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -87,7 +136,7 @@ packages:
path: ".." path: ".."
relative: true relative: true
source: path source: path
version: "2.0.0" version: "3.0.0"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -104,59 +153,101 @@ packages:
name: gherkin name: gherkin
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted 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: integration_test:
dependency: transitive dependency: transitive
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
logging:
dependency: transitive
description:
name: logging
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted 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: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted 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: path:
dependency: transitive dependency: transitive
description: description:
name: path name: path
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0" version: "1.8.1"
platform: platform:
dependency: transitive dependency: transitive
description: description:
name: platform name: platform
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.0" version: "3.1.0"
process: process:
dependency: transitive dependency: transitive
description: description:
name: process name: process
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted 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: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.99" 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: source_span:
dependency: transitive dependency: transitive
description: description:
name: source_span name: source_span
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.1" version: "1.8.2"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -198,7 +289,7 @@ packages:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.3.0" version: "0.4.9"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -206,20 +297,34 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
uuid:
dependency: transitive
description:
name: uuid
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.6"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
name: vector_math name: vector_math
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.1.2"
vm_service: vm_service:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted 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: webdriver:
dependency: transitive dependency: transitive
description: description:
@ -227,6 +332,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.0" version: "3.0.0"
yaml:
dependency: transitive
description:
name: yaml
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.1"
sdks: sdks:
dart: ">=2.12.3 <3.0.0" dart: ">=2.17.0 <3.0.0"
flutter: ">=2.2.0" flutter: ">=2.2.0"

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,12 @@
buildscript { buildscript {
ext.kotlin_version = '1.3.50' ext.kotlin_version = '1.6.10'
repositories { repositories {
google() google()
jcenter() jcenter()
} }
dependencies { 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" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }

View File

@ -3,4 +3,5 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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 @tag
Feature: Creating todos 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 @debug
Scenario: User can create multiple new todo items Scenario: User can create multiple new todo items
Given I fill the "todo" field with "Buy carrots" Given I fill the "todo" field with "Buy carrots"

View File

@ -1,7 +1,6 @@
@tag @tag
Feature: Swiping Feature: Swiping
@debug
Scenario: User can swipe cards left and right Scenario: User can swipe cards left and right
Given I swipe right by 250 pixels on the "scrollable cards"` Given I swipe right by 250 pixels on the "scrollable cards"`
Then Then I expect the text "Page 2" to be present 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 'steps/when_step_has_timeout.dart';
import 'world/custom_world.dart'; import 'world/custom_world.dart';
FlutterTestConfiguration gherkinTestConfiguration = FlutterTestConfiguration gherkinTestConfiguration = FlutterTestConfiguration(
FlutterTestConfiguration.DEFAULT( tagExpression: '@debug',
[ stepDefinitions: [
thenIExpectTheTodos, thenIExpectTheTodos,
whenAnAnimationIsAwaited, whenAnAnimationIsAwaited,
whenStepHasTimeout, whenStepHasTimeout,
givenTheData givenTheData
], ],
) hooks: [
// ..tagExpression = '@debug' ResetAppHook(),
..hooks = [ ],
ResetAppHook(), reporters: [
] StdoutReporter(MessageLevel.error)
..reporters = [ ..setWriteLineFn(print)
StdoutReporter(MessageLevel.error) ..setWriteFn(print),
..setWriteLineFn(print) ProgressReporter()
..setWriteFn(print), ..setWriteLineFn(print)
ProgressReporter() ..setWriteFn(print),
..setWriteLineFn(print) TestRunSummaryReporter()
..setWriteFn(print), ..setWriteLineFn(print)
TestRunSummaryReporter() ..setWriteFn(print),
..setWriteLineFn(print) ],
..setWriteFn(print), createWorld: (config) => Future.value(CustomWorld()),
JsonReporter( );
writeReport: (_, __) => Future<void>.value(),
),
]
..createWorld = (config) => Future.value(CustomWorld());
Future<void> Function(World) appInitializationFn = (World world) async { Future<void> Function(World) appInitializationFn = (World world) async {
// ensure a new injector instance is created each time // ensure a new injector instance is created each time

View File

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

View File

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

View File

@ -6,8 +6,8 @@ publish_to: 'none'
version: 1.0.0+1 version: 1.0.0+1
environment: environment:
sdk: '>=2.14.0 <3.0.0' sdk: '>=2.17.0 <3.0.0'
flutter: ">=2.5.0" flutter: ">=2.2.0"
dependencies: dependencies:
flutter: 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/wait_until_key_exists_step.dart';
export 'src/flutter/steps/when_tap_the_back_button_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_type_exists_step.dart';
export 'src/flutter/steps/wait_until_key_exists_step.dart';
// Hooks // Hooks
export 'src/flutter/hooks/attach_screenshot_on_failed_step_hook.dart'; export 'src/flutter/hooks/attach_screenshot_on_failed_step_hook.dart';

View File

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

View File

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

View File

@ -1,28 +1,63 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter_gherkin/flutter_gherkin_with_driver.dart'; 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_gherkin/src/flutter/hooks/app_runner_hook.dart';
import 'package:flutter_driver/flutter_driver.dart'; import 'package:flutter_driver/flutter_driver.dart';
import 'package:gherkin/gherkin.dart'; import 'package:gherkin/gherkin.dart';
import 'flutter_test_configuration.dart';
class FlutterDriverTestConfiguration extends FlutterTestConfiguration { class FlutterDriverTestConfiguration extends FlutterTestConfiguration {
String? _observatoryDebuggerUri; 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 /// 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. /// Additional setting on the configuration object can be set on the returned instance.
static FlutterDriverTestConfiguration DEFAULT( static FlutterDriverTestConfiguration DEFAULT(
Iterable<StepDefinitionGeneric<World>> steps, { Iterable<StepDefinitionGeneric<World>> steps, {
String featurePath = 'features/*.*.feature', String featurePath = 'features/*.*.feature',
String targetAppPath = 'test_driver/app.dart', String targetAppPath = 'test_driver/app.dart',
String? targetAppWorkingDirectory,
bool restartAppBetweenScenarios = true,
}) { }) {
return FlutterDriverTestConfiguration() return FlutterDriverTestConfiguration(
..features = [RegExp(featurePath)] features: [RegExp(featurePath)],
..reporters = [ reporters: [
StdoutReporter(MessageLevel.error), StdoutReporter(MessageLevel.error),
ProgressReporter(), ProgressReporter(),
TestRunSummaryReporter(), TestRunSummaryReporter(),
@ -32,81 +67,84 @@ class FlutterDriverTestConfiguration extends FlutterTestConfiguration {
logInfoMessages: false, logInfoMessages: false,
logWarningMessages: false, logWarningMessages: false,
), ),
] ],
..targetAppPath = targetAppPath targetAppPath: targetAppPath,
..stepDefinitions = steps targetAppWorkingDirectory: targetAppWorkingDirectory,
..restartAppBetweenScenarios = true; stepDefinitions: steps,
restartAppBetweenScenarios: restartAppBetweenScenarios,
);
} }
/// restarts the application under test between each scenario. /// restarts the application under test between each scenario.
/// Defaults to true to avoid the application being in an invalid state /// Defaults to true to avoid the application being in an invalid state
/// before each test /// before each test
bool restartAppBetweenScenarios = true; final bool restartAppBetweenScenarios;
/// The target app to run the tests against /// The target app to run the tests against
/// Defaults to "test_driver/app.dart" /// 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) /// 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 /// 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) /// The build flavor to run the tests against (optional)
/// Defaults to null /// Defaults to null
String? buildFlavor; final String? buildFlavor;
/// The default build mode used for running tests is --debug. /// The default build mode used for running tests is --debug.
/// We are exposing the option to run the tests also in --profile mode /// 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 /// If the application should be built prior to running the tests
/// Defaults to true /// Defaults to true
bool build = true; final bool build;
/// The target device id to run the tests against when multiple devices detected /// The target device id to run the tests against when multiple devices detected
/// Defaults to null /// Defaults to null
String? targetDeviceId; final String? targetDeviceId;
/// Will keep the Flutter application running when done testing /// Will keep the Flutter application running when done testing
/// Defaults to false /// Defaults to false
bool keepAppRunningAfterTests = false; final bool keepAppRunningAfterTests;
/// Logs Flutter process output to stdout /// Logs Flutter process output to stdout
/// The Flutter process is use to start and driver the app under test. /// The Flutter process is use to start and driver the app under test.
/// The output may contain build and run information /// The output may contain build and run information
/// Defaults to false /// Defaults to false
bool logFlutterProcessOutput = false; final bool logFlutterProcessOutput;
/// Sets the --verbose flag on the flutter process /// Sets the --verbose flag on the flutter process
/// Defaults to false /// Defaults to false
bool verboseFlutterProcessLogs = false; final bool verboseFlutterProcessLogs;
/// Duration to wait for Flutter to build and start the app on the target device /// 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 /// Slower machine may take longer to build and run a large app
/// Defaults to 90 seconds /// Defaults to 90 seconds
Duration flutterBuildTimeout = const Duration(seconds: 90); final Duration flutterBuildTimeout;
/// Duration to wait before reconnecting the Flutter driver to the app. /// 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 /// On slower machines the app might not be in a state where the driver can successfully connect immediately
/// Defaults to 2 seconds /// 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 /// The maximum times the flutter driver can try and connect to the running app
/// Defaults to 3 /// 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 /// 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=/` /// 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 /// 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 /// 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. /// 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 /// 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. /// 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; void setObservatoryDebuggerUri(String uri) => _observatoryDebuggerUri = uri;
@ -157,20 +195,41 @@ class FlutterDriverTestConfiguration extends FlutterTestConfiguration {
} }
@override @override
void prepare() { TestConfiguration prepare() {
super.prepare(); super.prepare();
_ensureCorrectConfiguration(); _ensureCorrectConfiguration();
final providedCreateWorld = createWorld; 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( Future<FlutterDriver> _attemptDriverConnection(

View File

@ -1,34 +1,50 @@
import 'package:flutter_gherkin/flutter_gherkin_with_driver.dart'; 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/existence_parameter.dart';
import 'package:flutter_gherkin/src/flutter/parameters/swipe_direction_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/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_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:flutter_test/flutter_test.dart';
import 'package:gherkin/gherkin.dart'; import 'package:gherkin/gherkin.dart';
class FlutterTestConfiguration extends TestConfiguration { 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]. /// Enable semantics in a test by creating a [SemanticsHandle].
/// See: [testWidgets] and [WidgetController.ensureSemantics]. /// 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 /// 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. /// Additional setting on the configuration object can be set on the returned instance.
static FlutterTestConfiguration DEFAULT( static FlutterTestConfiguration DEFAULT(
@ -36,49 +52,38 @@ class FlutterTestConfiguration extends TestConfiguration {
String featurePath = 'integration_test/features/*.*.feature', String featurePath = 'integration_test/features/*.*.feature',
String targetAppPath = 'test_driver/integration_test_driver.dart', String targetAppPath = 'test_driver/integration_test_driver.dart',
}) { }) {
return FlutterTestConfiguration() return FlutterTestConfiguration(
..reporters = [ reporters: [
StdoutReporter(MessageLevel.error), StdoutReporter(MessageLevel.error),
ProgressReporter(), ProgressReporter(),
TestRunSummaryReporter(), TestRunSummaryReporter(),
// JsonReporter(path: './report.json'), // JsonReporter(path: './report.json'),
] ],
..stepDefinitions = steps; stepDefinitions: steps,
);
} }
@override FlutterTestConfiguration({
void prepare() { super.features = const <Pattern>[],
customStepParameterDefinitions = super.featureDefaultLanguage = 'en',
List.from(customStepParameterDefinitions ?? Iterable.empty()) super.order = ExecutionOrder.random,
..addAll([ super.defaultTimeout = const Duration(seconds: 10),
ExistenceParameter(), super.featureFileMatcher = const IoFeatureFileAccessor(),
SwipeDirectionParameter(), super.featureFileReader = const IoFeatureFileAccessor(),
]); super.stopAfterTestFailed = false,
stepDefinitions = List.from(stepDefinitions ?? Iterable.empty()) super.tagExpression,
..addAll([ super.hooks,
ThenExpectElementToHaveValue(), super.reporters = const [],
WhenTapBackButtonWidget(), super.createWorld,
WhenTapWidget(), this.semanticsEnabled = true,
WhenTapWidgetWithoutScroll(), this.waitImplicitlyAfterAction = false,
WhenLongPressWidget(), Iterable<CustomParameter<dynamic>>? customStepParameterDefinitions,
WhenLongPressWidgetWithoutScroll(), Iterable<StepDefinitionGeneric<World>>? stepDefinitions,
WhenLongPressWidgetForDuration(), }) : super(
GivenOpenDrawer(), customStepParameterDefinitions:
WhenPauseStep(), List.from(customStepParameterDefinitions ?? Iterable.empty())
WhenFillFieldStep(), ..addAll(_wellKnownParameters),
ThenExpectWidgetToBePresent(), stepDefinitions: List.from(stepDefinitions ?? Iterable.empty())
RestartAppStep(), ..addAll(_wellKnownStepDefinitions),
SiblingContainsTextStep(), );
SwipeOnKeyStep(),
SwipeOnTextStep(),
TapTextWithinWidgetStep(),
TapWidgetOfTypeStep(),
TapWidgetOfTypeWithinStep(),
TapWidgetWithTextStep(),
TextExistsStep(),
TextExistsWithinStep(),
WaitUntilKeyExistsStep(),
WaitUntilTypeExistsStep(),
]);
}
} }

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/configuration/flutter_driver_test_configuration.dart';
import 'package:flutter_gherkin/src/flutter/runners/flutter_run_process_handler.dart'; import 'package:flutter_gherkin/src/flutter/runners/flutter_run_process_handler.dart';
import 'package:flutter_gherkin/src/flutter/world/flutter_driver_world.dart'; import 'package:flutter_gherkin/src/flutter/world/flutter_driver_world.dart';
@ -38,9 +38,9 @@ class FlutterAppRunnerHook extends Hook {
Future<void> onAfterScenario( Future<void> onAfterScenario(
TestConfiguration config, TestConfiguration config,
String scenario, String scenario,
Iterable<Tag> tags, Iterable<Tag> tags, {
{bool? passed,} bool? passed,
) async { }) async {
final flutterConfig = _castConfig(config); final flutterConfig = _castConfig(config);
haveRunFirstScenario = true; haveRunFirstScenario = true;
if (_flutterRunProcessHandler != null && 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 /// 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 /// 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. /// 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 logErrorMessages;
final bool logWarningMessages; final bool logWarningMessages;
final bool logInfoMessages; final bool logInfoMessages;
DriverLogCallback? defaultCallback;
FlutterDriverReporter({ FlutterDriverReporter({
this.logErrorMessages = true, this.logErrorMessages = true,
this.logWarningMessages = true, this.logWarningMessages = true,
@ -25,13 +28,18 @@ class FlutterDriverReporter extends Reporter {
}); });
@override @override
Future<void> onTestRunStarted() async { ReportActionHandler<TestMessage> get test => ReportActionHandler(
driverLog = _driverLogMessageHandler; onStarted: ([_]) async {
} defaultCallback = driverLog;
driverLog = _driverLogMessageHandler;
},
);
@override @override
Future<void> dispose() async { Future<void> dispose() async {
// driverLog = null; if (defaultCallback != null) {
driverLog = defaultCallback!;
}
} }
void _driverLogMessageHandler(String source, String message) { void _driverLogMessageHandler(String source, String message) {

View File

@ -49,8 +49,7 @@ abstract class GherkinIntegrationTestRunner {
} }
Future<void> run() async { Future<void> run() async {
_binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized() _binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
as IntegrationTestWidgetsFlutterBinding;
_binding!.framePolicy = _binding!.framePolicy =
framePolicy ?? LiveTestWidgetsFlutterBindingFramePolicy.benchmarkLive; framePolicy ?? LiveTestWidgetsFlutterBindingFramePolicy.benchmarkLive;
@ -77,10 +76,8 @@ abstract class GherkinIntegrationTestRunner {
} }
void setTestResultData(IntegrationTestWidgetsFlutterBinding binding) { void setTestResultData(IntegrationTestWidgetsFlutterBinding binding) {
if (reporter is SerializableReporter) { final json = (reporter).serialize();
final json = (reporter as SerializableReporter).serialize(); binding.reportData = {'gherkin_reports': json};
binding.reportData = {'gherkin_reports': json};
}
} }
@protected @protected
@ -115,10 +112,7 @@ abstract class GherkinIntegrationTestRunner {
} }
@protected @protected
Future<void> onAfterRunFeature( Future<void> onAfterRunFeature(String name, String path) async {
String name,
String path
) async {
final debugInformation = RunnableDebugInformation(path, 0, name); final debugInformation = RunnableDebugInformation(path, 0, name);
await reporter.test.onFinished.maybeCall( await reporter.test.onFinished.maybeCall(
TestMessage( TestMessage(
@ -130,11 +124,16 @@ abstract class GherkinIntegrationTestRunner {
} }
@protected @protected
void runScenario( void runScenario({
String name, required String name,
Iterable<String>? tags, required Iterable<String>? tags,
List<Future<StepResult> Function(TestDependencies dependencies, bool skip,)> required List<
steps, String path, { Future<StepResult> Function(
TestDependencies dependencies,
bool skip,
)>
steps,
required String path,
Future<void> Function()? onBefore, Future<void> Function()? onBefore,
Future<void> Function()? onAfter, Future<void> Function()? onAfter,
}) { }) {
@ -257,7 +256,15 @@ abstract class GherkinIntegrationTestRunner {
world = world ?? FlutterWidgetTesterWorld(); world = world ?? FlutterWidgetTesterWorld();
world.setAttachmentManager(attachmentManager); 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( return TestDependencies(
world, world,
@ -266,8 +273,13 @@ abstract class GherkinIntegrationTestRunner {
} }
@protected @protected
Future<StepResult> runStep(String step, Iterable<String> multiLineStrings, Future<StepResult> runStep(
dynamic table, TestDependencies dependencies, bool hasToSkip,) async { String step,
Iterable<String> multiLineStrings,
dynamic table,
TestDependencies dependencies,
bool hasToSkip,
) async {
final executable = _executableSteps!.firstWhereOrNull( final executable = _executableSteps!.firstWhereOrNull(
(s) => s.expression.isMatch(step), (s) => s.expression.isMatch(step),
); );
@ -294,8 +306,8 @@ abstract class GherkinIntegrationTestRunner {
StepResult? result; StepResult? result;
if (hasToSkip) { if (hasToSkip) {
result = new StepResult( result = new StepResult(0, StepExecutionResult.skipped,
0, StepExecutionResult.skipped, resultReason: "Previous step(s) failed."); resultReason: "Previous step(s) failed.");
} else { } else {
for (int i = 0; i < this.configuration.stepMaxRetries + 1; i++) { for (int i = 0; i < this.configuration.stepMaxRetries + 1; i++) {
result = await executable.step.run( result = await executable.step.run(
@ -426,7 +438,9 @@ abstract class GherkinIntegrationTestRunner {
name: step, name: step,
context: RunnableDebugInformation('', 0, step), context: RunnableDebugInformation('', 0, step),
result: result, 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/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'; import 'package:gherkin/gherkin.dart';
/// Discovers a widget by its text within the same parent. /// 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/flutter_gherkin.dart';
import 'package:flutter_gherkin/src/flutter/world/flutter_world.dart';
import 'package:gherkin/gherkin.dart'; import 'package:gherkin/gherkin.dart';
import '../parameters/swipe_direction_parameter.dart'; import '../parameters/swipe_direction_parameter.dart';

View File

@ -1,5 +1,4 @@
import 'package:flutter_gherkin/flutter_gherkin.dart'; import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:flutter_gherkin/src/flutter/adapters/app_driver_adapter.dart';
import 'package:gherkin/gherkin.dart'; import 'package:gherkin/gherkin.dart';
/// Taps a widget that contains the text within another widget. /// 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/flutter_gherkin.dart';
import 'package:flutter_gherkin/src/flutter/adapters/app_driver_adapter.dart';
import 'package:gherkin/gherkin.dart'; import 'package:gherkin/gherkin.dart';
/// Taps a widget of type. /// Taps a widget of type.

View File

@ -1,5 +1,4 @@
import 'package:flutter_gherkin/flutter_gherkin.dart'; import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:flutter_gherkin/src/flutter/adapters/app_driver_adapter.dart';
import 'package:gherkin/gherkin.dart'; import 'package:gherkin/gherkin.dart';
/// Taps a widget of type within another widget. /// 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/flutter_gherkin.dart';
import 'package:flutter_gherkin/src/flutter/adapters/app_driver_adapter.dart';
import 'package:gherkin/gherkin.dart'; import 'package:gherkin/gherkin.dart';
/// Taps a widget that contains text. /// Taps a widget that contains text.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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