feat(integration_test): first pass of code generation and gherkin tests running using the integration_test library

This commit is contained in:
Jon Samwell 2021-01-07 17:30:59 +11:00
parent e4af918cf9
commit 6d474d483a
181 changed files with 3814 additions and 791 deletions

2
.flutter-plugins Normal file
View File

@ -0,0 +1,2 @@
# This is a generated file; do not edit or check into version control.
integration_test=C:\\Google\\flutter\\.pub-cache\\hosted\\pub.dartlang.org\\integration_test-1.0.1\\

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\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\integration_test-1.0.1\\\\","dependencies":[]}],"android":[{"name":"integration_test","path":"C:\\\\Google\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\integration_test-1.0.1\\\\","dependencies":[]}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"integration_test","dependencies":[]}],"date_created":"2021-01-07 14:33:49.048225","version":"1.22.5"}
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"integration_test","path":"C:\\\\Google\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\integration_test-1.0.1\\\\","dependencies":[]}],"android":[{"name":"integration_test","path":"C:\\\\Google\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\integration_test-1.0.1\\\\","dependencies":[]}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"integration_test","dependencies":[]}],"date_created":"2021-01-07 17:30:41.008680","version":"1.22.5"}

10
.vscode/launch.json vendored
View File

@ -15,6 +15,16 @@
"cwd": "example/test_driver",
"request": "launch",
"type": "dart",
},
{
"name": "Debug integration_test",
"program": "test_driver/integration_test_driver.dart",
"cwd": "example_with_integration_test/",
"request": "launch",
"type": "dart",
"args": [
"--target=integration_test/gherkin_suite_test.dart",
],
}
]
}

View File

@ -1,5 +1,6 @@
{
"cSpell.words": [
"Errored",
"Serializable",
"scrollable",
"writeln"

View File

@ -1,3 +1,88 @@
## [1.2.0] - 07/01/2021
# BREAKING CHANGES
In order to progress this library and add support for the new integration_test package various things have had to be changed to enable this will still supporting Flutter Driver. The big of which is removing Flutter Driver instance from the `FlutterWorld` instance in favour of an adapter approach whereby driving of the app (whether that is via `flutter_driver` or `WidgetTester`) becomes agnostic see `https://github.com/jonsamwell/flutter_gherkin/blob/f1fb2d4a632362629f5d1a196a0c055f858ad1d7/lib/src/flutter/adapters/app_driver_adapter.dart`.
- `FlutterDriverUtils` has been removed, use `world.appDriver` instead. You can still access the raw driver if needed via `world.appDriver.rawDriver`
- If you are using a custom world object and still want to use Flutter Driver it will need to extend `FlutterDriverWorld` instead of `FlutterWorld`
The change to use the `integration_test` package is a fundamentally different approach. Where using the `flutter_driver` implementation your app is launch in a different process and then controlled by remote RPC calls from the flutter driver again in a different process. Using the new `integration_test` package your tests surround your app and become the app themselves. This removes the need for RPC communication from an external process into the app as well as giving you access to the internal state of your app. This is an altogether better approach, one that is quicker, more maintainable scalable to device testing labs. However, it brings with it, its own set of challenges when trying to make this library work with it. Traditionally this library has evaluated the Gherkin feature files at run time, then used that evaluation to invoke actions against the app under test. However, as the tests need to surround the app in the `integration_test` view of the world the Gherkin tests need to be generated at development time so they can be complied in to a test app. Much like `json_serializable` creates classes that are able to work with json data.
### Steps to get going
1. Add the following `dev_dependencies` to your app's `pubspec.yaml` file
- integration_test
- build_runner
- flutter_gherkin
2. Add the following `build.yaml` to the root of your project. This file allows the dart code generator to target files outside of your application's `lib` folder
```
targets:
$default:
sources:
- lib/**
- pubspec.*
- $package$
# Allows the code generator to target files outside of the lib folder
- integration_test/**.dart
```
3. Add the following file (and folder) `example_with_integration_test\test_driver\integration_test_driver.dart`. This file is the entry point to run your tests. See `https://flutter.dev/docs/testing/integration-tests` for more information.
```
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() => integrationDriver(timeout: const Duration(minutes: 90));
```
4. Create a folder call `integration_test` this will eventually contain all your Gherkin feature files and the generated test files.
5. Add the following file (and folder) `integration_test\feature\counter.feature` with the following below contents. This is a basic feature file that will be transform in to a test file that can run a test against the sample app.
```
Feature: Counter
Scenario: User can increment the counter
Given I expect the "counter" to be "0"
When I tap the "increment" button
Then I expect the "counter" to be "1"
```
6. Add the following file (and folder) `integration_test\gherkin_suite_test.dart`. Notice the attribute `@GherkinTestSuite()` this indicates to the code generator to create a partial file for this file with the generated Gherkin tests in `part 'gherkin_suite_test.g.dart';`. Don't worry about the initial errors as this will disappear when the tests are generated.
```dart
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:gherkin/gherkin.dart';
// The application under test.
import 'package:example_with_integration_test/main.dart' as app;
part 'gherkin_suite_test.g.dart';
@GherkinTestSuite()
void main() {
executeTestSuite(
FlutterTestConfiguration.DEFAULT([]),
app.main,
);
}
```
7. We now need to generate the test by running the builder command from the command line in the root of your project. Much like `json_serializable` this will create a `.g.dart` part file that will contain the Gherkin tests in code format which are able to via using the `integration_test` package.
```
flutter pub run build_runner build
```
8. The errors in the `integration_test\gherkin_suite_test.dart` file should have not gone away and it you look in `integration_test\gherkin_suite_test.g.dart` you will see the coded version of the Gherkin tests described in the feature file `integration_test\feature\counter.feature`.
9. We can now run the test using the below command from the root of your project.
```
flutter drive --driver=test_driver/integration_test_driver.dart --target=integration_test/gherkin_suite_test.dart
```
10. You can debug the tests by adding a breakpoint to line 12 in `integration_test\gherkin_suite_test.dart` and adding the below to your `.vscode\launch.json` file:
```json
{
"name": "Debug integration_test",
"program": "test_driver/integration_test_driver.dart",
"cwd": "example_with_integration_test/",
"request": "launch",
"type": "dart",
"args": [
"--target=integration_test/gherkin_suite_test.dart",
],
}
```
11. Custom world need to extend `FlutterWorld` note `FlutterDriverWorld`.
## [1.1.9] - 24/11/2020
* Fixes #93 & #92 - Error waiting for no transient callbacks from Flutter driver
* Added option to leave Flutter app under test running when the tests finish see `keepAppRunningAfterTests` configuration property

10
build.yaml Normal file
View File

@ -0,0 +1,10 @@
# Read about `build.yaml` at https://pub.dev/packages/build_config
builders:
gherkin_test_suite:
import: "package:flutter_gherkin/src/flutter/code_generation/builders/gherkin_test_suite_builder.dart"
builder_factories: ["gherkinTestSuiteBuilder"]
build_extensions: {".dart": ["gherkin_tests.g.part"]}
auto_apply: dependents
build_to: cache
applies_builders: ["source_gen|combining_builder"]

71
example/.gitignore vendored
View File

@ -1,71 +0,0 @@
# Miscellaneous
*.class
*.lock
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# Visual Studio Code related
.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.packages
.pub-cache/
.pub/
build/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages

View File

@ -1,22 +0,0 @@
# Running the example
To run this example:
1. Ensure dart is accessible in the command line (on your path variable)
2. Ensure an emulator or device is connected
3. In a command prompt (from the root of this library):
```bash
cd example/test_driver
dart app_test.dart
```
This will run the features files found in the folder `test_driver/features` against this example app.
## Debugging the example
To debug this example and step through the library code.
1. Set a break point in `test_driver/app_test.dart`
2. Set `exitAfterTestRun` on the configuration to false to ensure exiting cleanly as the IDE will handle exiting
3. If you are in VsCode you will simply be able to select `Debug example` from the dropdown in the `debugging tab` as the `launch.json` has been configured.
- otherwise you will need to run a debugging session against `test_driver/app_test.dart`.

View File

@ -1,39 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example">
<!-- The INTERNET permission is required for development. Specifically,
flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="example"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- This keeps the window background of the activity showing
until Flutter renders its first frame. It can be removed if
there is no splash screen (such as the default splash screen
defined in @style/LaunchTheme). -->
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -1,13 +0,0 @@
package com.example.example;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
</resources>

View File

@ -1,15 +0,0 @@
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}

View File

@ -1,15 +0,0 @@
#!/bin/sh
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=C:\Google\flutter"
export "FLUTTER_APPLICATION_PATH=C:\github\flutter_gherkin\example"
export "FLUTTER_TARGET=lib\main.dart"
export "FLUTTER_BUILD_DIR=build"
export "SYMROOT=${SOURCE_ROOT}/../build\ios"
export "OTHER_LDFLAGS=$(inherited) -framework Flutter"
export "FLUTTER_FRAMEWORK_DIR=C:\Google\flutter\bin\cache\artifacts\engine\ios"
export "FLUTTER_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1"
export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=false"
export "TREE_SHAKE_ICONS=false"
export "PACKAGE_CONFIG=.packages"

View File

@ -1,6 +0,0 @@
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
@interface AppDelegate : FlutterAppDelegate
@end

View File

@ -1,13 +0,0 @@
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,9 +0,0 @@
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char* argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

View File

@ -1,153 +0,0 @@
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Counter App',
home: MyHomePage(title: 'Counter App Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
bool hasLongPressedText = false;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: Drawer(
key: const Key('drawer'),
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: const Text('Drawer Header'),
decoration: BoxDecoration(
color: Colors.blue,
),
),
ListTile(
title: const Text('Item 1'),
onTap: () {
// Update the state of the app
// ...
// Then close the drawer
Navigator.pop(context);
},
),
ListTile(
title: const Text('Item 2'),
onTap: () {
// Update the state of the app
// ...
// Then close the drawer
Navigator.pop(context);
},
),
],
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
// Provide a Key to this specific Text Widget. This allows us
// to identify this specific Widget from inside our test suite and
// read the text.
key: const Key('counter'),
style: Theme.of(context).textTheme.headline4,
),
FlatButton(
key: Key('openPage2'),
child: Text('Open page 2'),
onLongPress: () {
Future.delayed(
Duration(seconds: 12),
() => Navigator.push(
context,
MaterialPageRoute(builder: (context) => PageTwo()),
));
},
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => PageTwo()),
);
},
),
GestureDetector(
onLongPress: () {
setState(() {
hasLongPressedText = true;
});
},
child: Container(
color:
hasLongPressedText ? Colors.blueGrey : Colors.transparent,
child: Text(
hasLongPressedText
? 'Text has been long pressed!'
: 'Text that has not been long pressed',
key: const Key('longPressText'),
),
),
),
],
),
),
floatingActionButton: FloatingActionButton(
// Provide a Key to this the button. This allows us to find this
// specific button and tap it inside the test suite.
key: const Key('increment'),
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
class PageTwo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
key: Key('pageTwo'),
appBar: AppBar(
automaticallyImplyLeading: true,
title: Text('Page 2'),
),
body: SafeArea(
child: Center(
child: Text('Contents of page 2'),
),
),
);
}
}

View File

@ -1,74 +0,0 @@
name: example
description: A new Flutter project.
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# Read more about versioning at semver.org.
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_driver:
sdk: flutter
flutter_gherkin:
path: ../
path:
glob:
# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.io/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.io/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.io/custom-fonts/#from-packages

File diff suppressed because one or more lines are too long

View File

@ -1,8 +0,0 @@
Feature: Startup
Scenario: counter should reset when app is restarted
Given I expect the "counter" to be "0"
When I tap the "increment" button
Then I expect the "counter" to be "1"
When I restart the app
Then I expect the "counter" to be "0"

View File

@ -1,13 +0,0 @@
Feature: Navigation
@debug
Scenario: User can navigate back from page two
Given I expect the "counter" to be "0"
When I tap the "increment" button
Then I expect the "counter" to be "1"
Given I tap the label that contains the text "Open page 2"
Then I expect the text "Contents of page 2" to be present
Given I tap the back button
Then I expect the "counter" to be "1"

View File

@ -1,14 +0,0 @@
Feature: Startup
Scenario: should increment counter
Given I expect the "counter" to be "0"
When I tap the "increment" button
And I tap the "increment" button
Then I expect the "counter" to be "2"
Scenario: counter should reset when app is restarted
Given I expect the "counter" to be "0"
When I tap the "increment" button
Then I expect the "counter" to be "1"
When I restart the app
Then I expect the "counter" to be "0"

View File

@ -1,8 +0,0 @@
Feature: Counter
The counter should be incremented when the button is pressed.
Scenario: Counter increases when the button is pressed
Given I pick the colour red
Given I expect the "counter" to be "0"
When I tap the "increment" button 10 times
Then I expect the "counter" to be "10"

View File

@ -1,9 +0,0 @@
# language: fr
Fonctionnalité: Counter
The counter should be incremented when the button is pressed.
Scénario: Counter increases when the button is pressed
Etant donné que I pick the colour red
Et I expect the "counter" to be "0"
Quand I tap the "increment" button 10 times
Alors I expect the "counter" to be "10"

View File

@ -1,16 +0,0 @@
Feature: Counter
The counter should be incremented when the button is pressed.
@scenario_outline
Scenario Outline: Counter increases when the button is pressed
Given I pick the colour red
Given I expect the "counter" to be "0"
When I tap the "increment" button <tap_amount> times
Then I expect the "counter" to be "<tap_amount>"
Examples:
| tap_amount |
| 1 |
| 2 |
| 5 |
| 10 |

View File

@ -1,6 +0,0 @@
Feature: Delayed navigation
Scenario: User can navigate to page two. Eventually
Given I expect the "counter" to be "0"
When I long press the "openPage2" button
Then I expect the widget "pageTwo" to be present within 15 seconds

View File

@ -1,7 +0,0 @@
Feature: Custom Parameter Example
This test just logs the colour defined in the step
Scenario: Custom colour parameter
Given I pick the colour red
Given I pick the colour green
Given I pick the colour blue

View File

@ -1,5 +0,0 @@
Feature: Drawer
Scenario: should open the drawer
Given I open the drawer
Given I close the drawer

View File

@ -1,6 +0,0 @@
Feature: Interaction
Scenario: Widget can be long pressed
Given I expect the "longPressText" to be "Text that has not been long pressed"
When I long press the "longPressText" text
Then I expect the "longPressText" to be "Text has been long pressed!"

View File

@ -1,8 +0,0 @@
Feature: Counter
The counter should be incremented when the button is pressed.
@perf
Scenario: Counter increases when the button is pressed
Given I expect the "counter" to be "0"
When I tap the "increment" button 20 times
Then I expect the "counter" to be "20"

View File

@ -1,40 +0,0 @@
import 'package:gherkin/gherkin.dart';
class HookExample extends Hook {
/// The priority to assign to this hook.
/// Higher priority gets run first so a priority of 10 is run before a priority of 2
@override
int get priority => 1;
/// Run before any scenario in a test run have executed
@override
Future<void> onBeforeRun(TestConfiguration config) async {
print('before run hook');
}
/// Run after all scenarios in a test run have completed
@override
Future<void> onAfterRun(TestConfiguration config) async {
print('after run hook');
}
/// Run before a scenario and it steps are executed
@override
Future<void> onBeforeScenario(
TestConfiguration config,
String scenario,
Iterable<Tag> tags,
) async {
print("running hook before scenario '$scenario'");
}
/// Run after a scenario has executed
@override
Future<void> onAfterScenario(
TestConfiguration config,
String scenario,
Iterable<Tag> tags,
) async {
print("running hook after scenario '$scenario'");
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,18 +0,0 @@
import 'package:gherkin/gherkin.dart';
enum Colour { red, green, blue }
class ColourParameter extends CustomParameter<Colour> {
ColourParameter()
: super('colour', RegExp(r'(red|green|blue)', caseSensitive: true), (c) {
switch (c.toLowerCase()) {
case 'red':
return Colour.red;
case 'green':
return Colour.green;
case 'blue':
default:
return Colour.blue;
}
});
}

View File

@ -1,22 +0,0 @@
import 'package:gherkin/gherkin.dart';
/// This step expects a data table
///
/// For example:
///
/// `Given I add the users`
/// | Firstname | Surname | Age | Gender |
/// | Woody | Johnson | 28 | Male |
/// | Edith | Summers | 23 | Female |
/// | Megan | Hill | 83 | Female |
StepDefinitionGeneric WhenIAddTheUsers() {
return when1(
'I add the users',
(Table dataTable, context) async {
for (var row in dataTable.rows) {
// do something with row
row.columns.forEach((columnValue) => print(columnValue));
}
},
);
}

View File

@ -1,11 +0,0 @@
import 'package:gherkin/gherkin.dart';
import 'colour_parameter.dart';
StepDefinitionGeneric GivenIPickAColour() {
return given1(
'I pick the colour {colour}',
(Colour colour, _) async {
print("The picked colour was: '$colour'");
},
);
}

View File

@ -1,18 +0,0 @@
import 'package:gherkin/gherkin.dart';
/// This step expects a multi-line string proceeding it
///
/// For example:
///
/// `Given I provide the following "review" comment`
/// """
/// Some comment
/// """
StepDefinitionGeneric GivenMultiLineString() {
return given2(
'I provide the following {string} comment',
(commentType, comment, _) async {
// implement step
},
);
}

View File

@ -1,14 +0,0 @@
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart';
StepDefinitionGeneric TapButtonNTimesStep() {
return given2<String, int, FlutterWorld>(
'I tap the {string} button {int} times',
(key, count, context) async {
final locator = context.world.appDriver.findBy(key, FindType.key);
for (var i = 0; i < count; i += 1) {
await context.world.appDriver.tap(locator);
}
},
);
}

41
example_with_flutter_driver/.gitignore vendored Normal file
View File

@ -0,0 +1,41 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json

View File

@ -4,5 +4,7 @@
# This file should be version controlled and should not be manually edited.
version:
revision: f37c235c32fc15babe6dc7b7bc2ee4387e5ecf92
channel: beta
revision: 78910062997c3a836feee883712c241a5fd22983
channel: stable
project_type: app

View File

@ -0,0 +1,16 @@
# example_with_flutter_driver
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View File

@ -0,0 +1,11 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties

View File

@ -22,10 +22,15 @@ if (flutterVersionName == null) {
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 28
compileSdkVersion 29
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
disable 'InvalidPackage'
@ -33,12 +38,11 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.example"
applicationId "com.example.example_with_flutter_driver"
minSdkVersion 16
targetSdkVersion 28
targetSdkVersion 29
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
@ -48,19 +52,6 @@ android {
signingConfig signingConfigs.debug
}
}
/// uncomment to see example of using flavors
// flavorDimensions "env"
// productFlavors {
// staging {
// dimension "env"
// applicationIdSuffix ".dev"
// versionNameSuffix "-dev"
// }
// production {
// dimension "env"
// }
// }
}
flutter {
@ -68,7 +59,5 @@ flutter {
}
dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example_with_flutter_driver">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1,47 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example_with_flutter_driver">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="example_with_flutter_driver"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>

View File

@ -0,0 +1,6 @@
package com.example.example_with_flutter_driver
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@android:color/white</item>
</style>
</resources>

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example_with_flutter_driver">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

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

View File

@ -1,4 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true
android.enableR8=true

View File

@ -1,6 +1,6 @@
#Wed Dec 18 20:00:38 AEDT 2019
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip

View File

@ -0,0 +1,11 @@
include ':app'
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"

View File

@ -0,0 +1,32 @@
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View File

@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>

View File

@ -8,15 +8,8 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
@ -29,8 +22,6 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@ -40,17 +31,13 @@
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
@ -62,8 +49,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
);