A Gherkin parsers and runner for Dart and Flutter which is very similar to cucumber

Overview
Awesome Flutter

flutter_gherkin

A fully featured Gherkin parser and test runner. Works with Flutter and Dart 2.

This implementation of the Gherkin tries to follow as closely as possible other implementations of Gherkin and specifically Cucumber in it's various forms.

Available as a Dart package https://pub.dartlang.org/packages/flutter_gherkin

  # Comment
  Feature: Addition

    @tag
    Scenario: 1 + 0
      Given I start with 1
      When I add 0
      Then I end up with 1

    Scenario: 1 + 1
      Given I start with 1
      When I add 1
      Then I end up with 2

Note - Package upgrades

This package will soon have a major release to support null-safety and then another major release to support running tests using the integration_test package and WidgetTester. We will still maintain compatibility for running tests using flutter_driver and do our best so that switching over to using the integration_test package will be seamless. For this to happen we have had to refactor large chunks of the code base so unfortunately there will be some unavoidable breaking changes.

Table of Contents

Getting Started

See https://docs.cucumber.io/gherkin/ for information on the Gherkin syntax and Behaviour Driven Development (BDD).

See example readme for a quick start guide to running the example features and app.

The first step is to create a version of your app that has flutter driver enabled so that it can be automated. A good guide how to do this is show here. However in short, create a folder called test_driver and within that create a file called app.dart and paste in the below code.

import '../lib/main.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_driver/driver_extension.dart';

void main() {
  // This line enables the extension
  enableFlutterDriverExtension();

  // Call the `main()` function of your app or call `runApp` with any widget you
  // are interested in testing.
  runApp(MyApp());
}

All this code does is enable the Flutter driver extension which is required to be able to automate the app and then runs your application.

To get started with BDD in Flutter the first step is to write a feature file and a test scenario within that.

First create a folder called test_driver (this is inline with the current integration test as we will need to use the Flutter driver to automate the app). Within the folder create a folder called features , then create a file called counter.feature .

Feature: Counter
  The counter should be incremented when the button is pressed.

  Scenario: Counter increases when the button is pressed
    Given I expect the "counter" to be "0"
    When I tap the "increment" button 10 times
    Then I expect the "counter" to be "10"

Now we have created a scenario we need to implement the steps within. Steps are just classes that extends from the base step definition class or any of its variations Given , Then , When , And , But .

Granted the example is a little contrived but is serves to illustrate the process.

This library has a couple of built in step definitions for convenience. The first step uses the built in step, however the second step When I tap the "increment" button 10 times is a custom step and has to be implemented. To implement a step we have to create a simple step definition class.

import 'package:flutter_driver/flutter_driver.dart';
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart';

StepDefinitionGeneric TapButtonNTimesStep() {
  return when2<String, int, FlutterWorld>(
    'I tap the {string} button {int} times',
    (key, count, context) async {
      final locator = find.byValueKey(key);
      for (var i = 0; i < count; i += 1) {
        await FlutterDriverUtils.tap(context.world.driver, locator);
      }
    },
  );
}

As you can see the when2 method is invoked specifying two input parameters. The third type FlutterWorld is a special world context object that allow access from the context object to the Flutter driver that allows you to interact with your app. If you did not need a custom world object or strongly typed parameters you can omit the type arguments completely.

The input parameters are retrieved via the pattern regex from well know parameter types {string} and {int} explained below. They are just special syntax to indicate you are expecting a string and an integer at those points in the step text. Therefore, when the step to execute is When I tap the "increment" button 10 times the parameters "increment" and 10 will be passed into the step as the correct types. Note that in the pattern you can use any regex capture group to indicate any input parameter. For example the regex RegExp(r"When I tap the {string} (button|icon) {int} times") indicates 3 parameters and would match to either of the below step text.

When I tap the "increment" button 10 times    // passes 3 parameters "increment", "button" & 10
When I tap the "plus" icon 2 times       // passes 3 parameters "plus", "icon" & 2

It is worth noting that this library does not rely on mirrors (reflection) for many reasons but most prominently for ease of maintenance and to fall inline with the principles of Flutter not allowing reflection. All in all this make for a much easier to understand and maintain code base as well as much easier debugging for the user. The downside is that we have to be slightly more explicit by providing instances of custom code such as step definition, hook, reporters and custom parameters.

Now that we have a testable app, a feature file and a custom step definition we need to create a class that will call this library and actually run the tests. Create a file called app_test.dart and put the below code in.

import 'dart:async';
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart';
import 'hooks/hook_example.dart';
import 'steps/colour_parameter.dart';
import 'steps/given_I_pick_a_colour_step.dart';
import 'steps/tap_button_n_times_step.dart';

Future<void> main() {
  final config = FlutterTestConfiguration()
    ..features = [RegExp('features/*.*.feature')]
    ..reporters = [
      ProgressReporter(),
      TestRunSummaryReporter(),
      JsonReporter(path: './report.json')
    ] // you can include the "StdoutReporter()" without the message level parameter for verbose log information
    ..hooks = [HookExample()]
    ..stepDefinitions = [TapButtonNTimesStep(), GivenIPickAColour()]
    ..customStepParameterDefinitions = [ColourParameter()]
    ..restartAppBetweenScenarios = true
    ..targetAppPath = "test_driver/app.dart";
    // ..tagExpression = "@smoke" // uncomment to see an example of running scenarios based on tag expressions
  return GherkinRunner().execute(config);
}

This code simple creates a configuration object and calls this library which will then promptly parse your feature files and run the tests. The configuration file is important and explained in further detail below. However, all that is happening is a RegExp is provide which specifies the path to one or more feature files, it sets the reporters to the ProgressReporter report which prints the result of scenarios and steps to the standard output (console). The TestRunSummaryReporter prints a summary of the run once all tests have been executed. Finally it specifies the path to the testable app created above test_driver/app.dart . This is important as it instructions the library which app to run the tests against.

Finally to actually run the tests run the below on the command line:

dart test_driver/app_test.dart

To debug tests see Debugging.

Note: You might need to ensure dart is accessible by adding it to your path variable.

Configuration

The configuration is an important piece of the puzzle in this library as it specifies not only what to run but classes to run against in the form of steps, hooks and reporters. Unlike other implementation this library does not rely on reflection so need to be explicitly told classes to use.

The parameters below can be specified in your configuration file:

features

Required

An iterable of Pattern that specify the location(s) of *.feature files to run. See https://api.dart.dev/stable/2.12.4/dart-core/Pattern-class.html

tagExpression

Defaults to null .

An infix boolean expression which defines the features and scenarios to run based of their tags. See Tags.

order

Defaults to ExecutionOrder.random The order by which scenarios will be run. Running an a random order may highlight any inter-test dependencies that should be fixed. Running with ExecutionOrder.sorted processes the feature files in filename order.

stepDefinitions

Defaults to Iterable<StepDefinitionBase> Place instances of any custom step definition classes Given , Then , When , And , But that match to any custom steps defined in your feature files.

import 'dart:async';
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart';
import 'steps/given_I_pick_a_colour_step.dart';
import 'steps/tap_button_n_times_step.dart';

Future<void> main() {
  final config = FlutterTestConfiguration()
    ..features = [RegExp('features/*.*.feature')]
    ..reporters = [StdoutReporter()]
    ..stepDefinitions = [TapButtonNTimesStep(), GivenIPickAColour()]
    ..restartAppBetweenScenarios = true
    ..targetAppPath = "test_driver/app.dart";
  return GherkinRunner().execute(config);
}

defaultLanguage

Defaults to en This specifies the default language the feature files are written in. See https://cucumber.io/docs/gherkin/reference/#overview for supported languages.

Note that this can be overridden in the feature itself by the use of a language block.

# language: de
Funktionalität: Calculator
  Tests the addition of two numbers

  Szenariogrundriss: Add two numbers
    Gegeben sei the numbers <number_one> and <number_two>
    Wenn they are added
    Dann the expected result is <result>

    Beispiele:
      | number_one | number_two | result |
      | 12         | 5          | 17     |
      | 20         | 5          | 25     |
      | 20937      | 1          | 20938  |
      | 20.937     | -1.937     | 19     |
# language: fr
Fonctionnalité: Counter
  The counter should be incremented when the button is pressed.

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

customStepParameterDefinitions

Defaults to CustomParameter<dynamic> .

Place instances of any custom step parameters that you have defined. These will be matched up to steps when scenarios are run and their result passed to the executable step. See Custom Parameters.

import 'dart:async';
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart';
import 'steps/given_I_pick_a_colour_step.dart';
import 'steps/tap_button_n_times_step.dart';
import 'steps/colour_parameter.dart';

Future<void> main() {
  final config = FlutterTestConfiguration()
    ..features = [RegExp('features/*.*.feature')]
    ..reporters = [StdoutReporter()]
    ..stepDefinitions = [TapButtonNTimesStep(), GivenIPickAColour()]
    ..customStepParameterDefinitions = [ColourParameter()]
    ..restartAppBetweenScenarios = true
    ..targetAppPath = "test_driver/app.dart";

  return GherkinRunner().execute(config);
}

hooks

Hooks are custom bits of code that can be run at certain points with the test run such as before or after a scenario. Place instances of any custom Hook class instance in this collection. They will then be run at the defined points with the test run.

attachments

Attachment are pieces of data you can attach to a running scenario. This could be simple bits of textual data or even image like a screenshot. These attachments can then be used by reporters to provide more contextual information. For example when a step fails some contextual information could be attached to the scenario which is then used by a reporter to display why the step failed.

Attachments would typically be attached via a Hook for example onAfterStep .

import 'package:gherkin/gherkin.dart';

class AttachScreenshotOnFailedStepHook extends Hook {
  /// Run after a step has executed
  @override
  Future<void> onAfterStep(World world, String step, StepResult stepResult) async {
    if (stepResult.result == StepExecutionResult.fail) {
      world.attach('Some info.','text/plain');
      world.attach('{"some", "JSON"}}', 'application/json');
    }
  }
}
screenshot

To take a screenshot on a step failing you can used the pre-defined hook AttachScreenshotOnFailedStepHook and include it in the hook configuration of the tests config. This hook will take a screenshot and add it as an attachment to the scenario. If the JsonReporter is being used the screenshot will be embedded in the report which can be used to generate a HTML report which will ultimately display the screenshot under the failed step.

import 'dart:async';
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart';
import 'hooks/hook_example.dart';
import 'steps/colour_parameter.dart';
import 'steps/given_I_pick_a_colour_step.dart';
import 'steps/tap_button_n_times_step.dart';

Future<void> main() {
  final config = FlutterTestConfiguration()
    ..features = [RegExp('features/*.*.feature')]
    ..reporters = [
      ProgressReporter(),
      TestRunSummaryReporter(),
      JsonReporter(path: './report.json')
    ]
    ..hooks = [HookExample(), AttachScreenshotOnFailedStepHook()]
    ..stepDefinitions = [TapButtonNTimesStep(), GivenIPickAColour()]
    ..customStepParameterDefinitions = [ColourParameter()]
    ..restartAppBetweenScenarios = true
    ..targetAppPath = "test_driver/app.dart";

  return GherkinRunner().execute(config);
}

reporters

Reporters are classes that are able to report on the status of the test run. This could be a simple as merely logging scenario result to the console. There are a number of built-in reporter:

  • StdoutReporter : Logs all messages from the test run to the standard output (console).
  • ProgressReporter : Logs the progress of the test run marking each step with a scenario as either passed, skipped or failed.
  • JsonReporter - creates a JSON file with the results of the test run which can then be used by 'https://www.npmjs.com/package/cucumber-html-reporter.' to create a HTML report. You can pass in the file path of the json file to be created.

You should provide at least one reporter in the configuration otherwise it'll be hard to know what is going on.

Note: Feel free to PR new reporters!

import 'dart:async';
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'steps/colour_parameter.dart';
import 'steps/given_I_pick_a_colour_step.dart';
import 'steps/tap_button_n_times_step.dart';

Future<void> main() {
  final config = FlutterTestConfiguration()
    ..features = [RegExp('features/*.*.feature')]
    ..reporters = [StdoutReporter()]
    ..stepDefinitions = [TapButtonNTimesStep(), GivenIPickAColour()]
    ..customStepParameterDefinitions = [ColourParameter()]
    ..restartAppBetweenScenarios = true
    ..targetAppPath = "test_driver/app.dart";

  return GherkinRunner().execute(config);
}

createWorld

Defaults to null .

While it is not recommended so share state between steps within the same scenario we all in fact live in the real world and thus at time may need to share certain information such as login credentials etc for future steps to use. The world context object is created once per scenario and then destroyed at the end of each scenario. This configuration property allows you to specify a custom World class to create which can then be accessed in your step classes.

import 'dart:async';
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'steps/given_I_pick_a_colour_step.dart';
import 'steps/tap_button_n_times_step.dart';

Future<void> main() {
  final config = FlutterTestConfiguration()
    ..features = [RegExp('features/*.*.feature')]
    ..reporters = [StdoutReporter()]
    ..stepDefinitions = [TapButtonNTimesStep(), GivenIPickAColour()]
    ..createWorld = (TestConfiguration config) async => await createMyWorldInstance(config)
    ..restartAppBetweenScenarios = true
    ..targetAppPath = "test_driver/app.dart";
    
  return GherkinRunner().execute(config);
}

logFlutterProcessOutput

Defaults to false If true the output from the flutter process is logged to the stdout / stderr streams. Useful when debugging app build or start failures

flutterBuildTimeout

Defaults to 90 seconds Specifies the period of time to wait for the Flutter build to complete and the app to be installed and in a state to be tested. Slower machines may need longer than the default 90 seconds to complete this process.

onBeforeFlutterDriverConnect

An async method that is called before an attempt by Flutter driver to connect to the app under test

onAfterFlutterDriverConnect

An async method that is called after a successful attempt by Flutter driver to connect to the app under test

flutterDriverMaxConnectionAttempts

Defaults to 3 Specifies the number of Flutter driver connection attempts to a running app before the test is aborted

flutterDriverReconnectionDelay

Defaults to 2 seconds Specifies the amount of time to wait after a failed Flutter driver connection attempt to the running app

Flutter specific configuration options

The FlutterTestConfiguration will automatically create some default Flutter options such as well know step definitions, the Flutter world context object which provides access to a Flutter driver instance as well as the ability to restart you application under test between scenarios. Most of the time you should use this configuration object if you are testing Flutter applications.

restartAppBetweenScenarios

Defaults to true .

To avoid tests starting on an app changed by a previous test it is suggested that the Flutter application under test be restarted between each scenario. While this will increase the execution time slightly it will limit tests failing because they run against an app changed by a previous test. Note in more complex application it may also be necessary to use the AfterScenario hook to reset the application to a base state a test can run on. Logging out for example if restarting an application will present a lock screen etc. This now performs a hot reload of the application which resets the state and drastically reduces the time to run the tests.

targetAppPath

Defaults to lib/test_driver/app.dart This should point to the testable application that enables the Flutter driver extensions and thus is able to be automated. This application wil be started when the test run is started and restarted if the restartAppBetweenScenarios configuration property is set to true.

build

Defaults to true This optional argument lets you specify if the target application should be built prior to running the first test. This defaults to true

keepAppRunningAfterTests

Defaults to false This optional argument will keep the Flutter application running when done testing. This defaults to false

buildFlavor

Defaults to empty string

This optional argument lets you specify which flutter flavor you want to test against. Flutter's flavor has similar concept with Android Build Variants or iOS Scheme Configuration . This flavoring flutter documentation has complete guide on both flutter and android/ios side.

buildMode

Defaults to BuildMode.Debug

This optional argument lets you specify which build mode you prefer while compiling your app. Flutter Gherkin supports --debug and --profile modes. Check Flutter's build modes documentation for more details.

dartDefineArgs

Defaults to []

--dart-define args to pass into the build parameters. Include the name and value for each. For example, --dart-define=MY_VAR="true" becomes ['MY_VAR="true"']

targetDeviceId

Defaults to empty string

This optional argument lets you specify device target id as flutter run --device-id command. To show list of connected devices, run flutter devices . If you only have one device connected, no need to provide this argument.

runningAppProtocolEndpointUri

An observatory url that the test runner can connect to instead of creating a new running instance of the target application The 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

Features Files

Steps Definitions

Step definitions are the coded representation of a textual step in a feature file. Each step starts with either Given , Then , When , And or But . It is worth noting that all steps are actually the same but semantically different. The keyword is not taken into account when matching a step. Therefore the two below steps are actually treated the same and will result in the same step definition being invoked.

Note: Step definitions (in this implementation) are allowed up to 5 input parameters. If you find yourself needing more than this you might want to consider making your step more isolated or using a Table parameter.

Given there are 6 kangaroos
Then there are 6 kangaroos

However, the domain language you choose will influence what keyword works best in each context. For more information https://docs.cucumber.io/gherkin/reference/#steps.

Given

Given steps are used to describe the initial state of a system. The execution of a Given step will usually put the system into well defined state.

To implement a Given step you can inherit from the Given class.

Given Bob has logged in

Would be implemented like so:

import 'package:gherkin/gherkin.dart';

StepDefinitionGeneric GivenWellKnownUserIsLoggedIn() {
  return given1(
    RegExp(r'(Bob|Mary|Emma|Jon) has logged in'),
    (wellKnownUsername, context) async {
      // implement your code
    },
  );
}

If you need to have more than one Given in a block it is often best to use the additional keywords And or But .

Given Bob has logged in
And opened the dashboard

Then

Then steps are used to describe an expected outcome, or result. They would typically have an assertion in which can pass or fail.

Then I expect 10 apples

Would be implemented like so:

import 'package:gherkin/gherkin.dart';

StepDefinitionGeneric ThenExpectAppleCount() {
  return then1(
    'I expect {int} apple(s)',
    (count, context) async {
      // example code
      final actualCount = await _getActualCount();
      context.expectMatch(actualCount, count);
    },
  );
}

Expects Assertions

Caveat: The expect library currently only works within the library's own test function blocks; so using it with a Then step will cause an error. Therefore, the expectMatch or expectA or this.expect or context.expect methods have been added which mimic the underlying functionality of except in that they assert that the give is true. The Matcher within Dart's test library still work and can be used as expected.

Step Timeout

By default a step will timeout if it exceed the defaultTimeout parameter in the configuration file. In some cases you want have a step that is longer or shorter running and in the case you can optionally proved a custom timeout to that step. To do this pass in a Duration object in the step's call to super .

For example, the below sets the step's timeout to 10 seconds.

import 'package:flutter_driver/flutter_driver.dart';
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 = find.byValueKey(key);
      for (var i = 0; i < count; i += 1) {
        await FlutterDriverUtils.tap(context.world.driver, locator);
      }
    },
  );
}

Multiline Strings

Multiline strings can follow a step and will be give to the step it proceeds as the final argument. To denote a multiline string the pre and postfix can either be third double or single quotes """ ... """ or ''' ... ''' .

For example:

Given I provide the following "review" comment
"""
Some long review comment.
That can span multiple lines

Skip lines

Maybe even include some numbers
1
2
3
"""

The matching step definition would then be:

import 'package:gherkin/gherkin.dart';

StepDefinitionGeneric GivenTheMultiLineComment() {
  return given1(
    'I provide the following {string} comment',
    (comment, context) async {
      // implement step
    },
  );
}

Data tables

import 'package:gherkin/gherkin.dart';

/// This step expects a multiline string proceeding it
///
/// For example:
///
/// `When 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));
      }

      // or get the table as a map (column values keyed by the header)
      final columns = dataTable.asMap();
      final personOne = columns.elementAt(0);
      final personOneName = personOne["Firstname"];
      print('Name of first user: `$personOneName` ');
    },
  );
}

Well known step parameters

In addition to being able to define a step's own parameters (by using regex capturing groups) there are some well known parameter types you can include that will automatically match and convert the parameter into the correct type before passing it to you step definition. (see https://docs.cucumber.io/cucumber/cucumber-expressions/#parameter-types).

In most scenarios theses parameters will be enough for you to write quite advanced step definitions.

Parameter Name Description Aliases Type Example
{word} Matches a single word surrounded by a quotes {word}, {Word} String Given I eat a {word} would match Given I eat a "worm"
{string} Matches one more words surrounded by a quotes {string}, {String} String Given I eat a {string} would match Given I eat a "can of worms"
{int} Matches an integer {int}, {Int} int Given I see {int} worm(s) would match Given I see 6 worms
{num} Matches an number {num}, {Num}, {float}, {Float} num Given I see {num} worm(s) would match Given I see 0.75 worms

Note that you can combine there well known parameters in any step. For example Given I {word} {int} worm(s) would match Given I "see" 6 worms and also match Given I "eat" 1 worm

Pluralization

As the aim of a feature is to convey human readable tests it is often desirable to optionally have some word pluralized so you can use the special pluralization syntax to do simple pluralization of some words in your step definition. For example:

The step string Given I see {int} worm(s) has the pluralization syntax on the word "worm" and thus would be matched to both Given I see 1 worm and Given I see 4 worms .

Custom Parameters

While the well know step parameter will be sufficient in most cases there are time when you would want to defined a custom parameter that might be used across more than or step definition or convert into a custom type.

The below custom parameter defines a regex that matches the words "red", "green" or "blue". The matches word is passed into the function which is then able to convert the string into a Color object. The name of the custom parameter is used to identity the parameter within the step text. In the below example the word "colour" is used. This is combined with the pre / post prefixes (which default to "{" and "}") to match to the custom parameter.

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":
              return Colour.blue;
          }
        });
}

The step definition would then use this custom parameter like so:

import 'package:gherkin/gherkin.dart';
import 'colour_parameter.dart';

StepDefinitionGeneric GivenIAddTheUsers() {
  return given1<Colour>(
    'I pick the colour {colour}',
    (colour, _) async {
      print("The picked colour was: '$colour'");
    },
  );
}

This customer parameter would be used like this: Given I pick the colour red . When the step is invoked the word "red" would matched and passed to the custom parameter to convert it into a Colour enum which is then finally passed to the step definition code as a Colour object.

World Context (per test scenario shared state)

Assertions

Tags

Tags are a great way of organizing your features and marking them with filterable information. Tags can be uses to filter the scenarios that are run. For instance you might have a set of smoke tests to run on every check-in as the full test suite is only ran once a day. You could also use an @ignore or @todo tag to ignore certain scenarios that might not be ready to run yet.

You can filter the scenarios by providing a tag expression to your configuration file. Tag expression are simple infix expressions such as:

@smoke @smoke and @perf @billing or @onboarding @smoke and not @ignore You can even us brackets to ensure the order of precedence

@smoke and not (@ignore or @todo) You can use the usual boolean statement "and", "or", "not"

Also see https://docs.cucumber.io/cucumber/api/#tags

Languages

In order to allow features to be written in a number of languages, you can now write the keywords in languages other than English. To improve readability and flow, some languages may have more than one translation for any given keyword. See https://cucumber.io/docs/gherkin/reference/#overview for a list of supported languages.

You can set the default language of feature files in your project via the configuration setting see defaultLanguage

For example these two features are the same the keywords are just written in different languages. Note the # language: de on the second feature. English is the default language.

Feature: Calculator
  Tests the addition of two numbers

  Scenario Outline: Add two numbers
    Given the numbers <number_one> and <number_two>
    When they are added
    Then the expected result is <result>

    Examples:
      | number_one | number_two | result |
      | 12         | 5          | 17     |
      | 20         | 5          | 25     |
      | 20937      | 1          | 20938  |
      | 20.937     | -1.937     | 19     |

# language: de
Funktionalität: Calculator
  Tests the addition of two numbers

  Szenariogrundriss: Add two numbers
    Gegeben sei the numbers <number_one> and <number_two>
    Wenn they are added
    Dann the expected result is <result>

    Beispiele:
      | number_one | number_two | result |
      | 12         | 5          | 17     |
      | 20         | 5          | 25     |
      | 20937      | 1          | 20938  |
      | 20.937     | -1.937     | 19     |

Please note the language data is take and attributed to the cucumber project https://github.com/cucumber/cucumber/blob/master/gherkin/gherkin-languages.json

Hooks

A hook is a point in the execution that custom code can be run. Hooks can be run at the below points in the test run.

  • Before any tests run
  • After all the tests have run
  • Before each scenario
  • After each scenario

To create a hook is easy. Just inherit from Hook and override the method(s) that signifies the point in the process you want to run code at. Note that not all methods need to be override, just the points at which you want to run custom code.

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) async {
    print("running hook before scenario '$scenario'");
  }

  /// Run after a scenario has executed
  @override
  Future<void> onAfterScenario(
      TestConfiguration config, String scenario) async {
    print("running hook after scenario '$scenario'");
  }
}

Finally ensure the hook is added to the hook collection in your configuration file.

import 'dart:async';
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart';
import 'hooks/hook_example.dart';
import 'steps/given_I_pick_a_colour_step.dart';
import 'steps/tap_button_n_times_step.dart';

Future<void> main() {
  final config = FlutterTestConfiguration()
    ..features = [RegExp('features/*.*.feature')]
    ..reporters = [ProgressReporter()]
    ..hooks = [HookExample()]
    ..stepDefinitions = [TapButtonNTimesStep(), GivenIPickAColour()]
    ..restartAppBetweenScenarios = true
    ..targetAppPath = "test_driver/app.dart";
  return GherkinRunner().execute(config);
}

Reporting

A reporter is a class that is able to report on the progress of the test run. In it simplest form it could just print messages to the console or be used to tell a build server such as TeamCity of the progress of the test run. The library has a number of built in reporters.

  • StdoutReporter - prints all messages from the test run to the console.
  • ProgressReporter - prints the result of each scenario and step to the console - colours the output.
  • TestRunSummaryReporter - prints the results and duration of the test run once the run has completed - colours the output.
  • JsonReporter - creates a JSON file with the results of the test run which can then be used by 'https://www.npmjs.com/package/cucumber-html-reporter.' to create a HTML report. You can pass in the file path of the json file to be created.
  • FlutterDriverReporter - prints the output from Flutter Driver. Flutter driver logs all messages to the stderr stream by default so most CI servers would mark the process as failed if anything is logged to the stderr stream (even if the Flutter driver logs are only info messages). This reporter ensures the log messages are output to the most appropriate stream depending on their log level.

You can create your own custom reporter by inheriting from the base Reporter class and overriding the one or many of the methods to direct the output message. The Reporter defines the following methods that can be overridden. All methods must return a Future<void> and can be async.

  • onTestRunStarted
  • onTestRunFinished
  • onFeatureStarted
  • onFeatureFinished
  • onScenarioStarted
  • onScenarioFinished
  • onStepStarted
  • onStepFinished
  • onException
  • message
  • dispose Once you have created your custom reporter don't forget to add it to the reporters configuration file property.

Note: PR's of new reporters are always welcome.

Flutter

Restarting the app before each test

By default to ensure your app is in a consistent state at the start of each test the app is shut-down and restarted. This behaviour can be turned off by setting the restartAppBetweenScenarios flag in your configuration object. Although in more complex scenarios you might want to handle the app reset behaviour yourself; possibly via hooks.

You might additionally want to do some clean-up of your app after each test by implementing an onAfterScenario hook.

Flutter World

Pre-defined Steps

For convenience the library defines a number of pre-defined steps so you can get going much quicker without having to implement lots of step classes. The pre-defined steps are:

Step Text Description Examples
I tap the {string} [button|element|label|icon|field|text|widget] Taps the element with the provided key ( given by the first input parameter) When I tap the "login" button , Then I tap the "save" icon
I fill the {string} field with {string} Fills the element with the provided key with the given value (given by the second input parameter) When I fill the "email" field with "[email protected]"
I expect the {string} to be {string} Asserts that the element with the given key has the given string value Then I expect the "cost" to be "£10.95"
I (open|close) the drawer Opens or closes the application default drawer When I open the drawer , And I close the drawer
I expect the [button|element|label|icon|field|text|widget] {string} to be present within {int} second(s) Expects a widget with the given key to be present within n seconds Then I expect the widget 'notification' to be present within 10 seconds , Then I expect the icon 'notification' to be present within 1 second
I pause for {int} seconds Pauses the test execution for the given seconds. Only use in debug scenarios or to inspect the state of the app Then I pause for 20 seconds
I restart the app Restarts the app under test Then I restart the app
I tap the back button Taps the page default back button widget Then I tap the back button
I expect a {string} that contains the text {string} to also contain the text {string} Discovers a sibling based on its parent widget type and asserts that the both text string exist within the parent. Then I expect a "Row" that contains the text "X" to also contain the text "Y"
I swipe [down|left|right|up] by {int} pixels on the {string} Swipes in a cardinal direction on a widget discovered by its key. Then I swipe up by 800 pixels on the "login_screen" , Then I swipe left by 200 pixels on the "dismissible_list_item"
I swipe [down|left|right|up] by {int} pixels on the on the [button|element|label|field|text|widget|dialog|popup] that contains the text {string} Swipes in a cardinal direction on a widget discovered by its test. Then I swipe left by 400 pixels on the widget that contains the text "Dismiss Me"
I tap the [button|element|label|field|text|widget] that contains the text {string} within the {string} Taps a widget that contains the text within another widget. If the text is not visible, the ancestor will be scrolled. Then I tap the label that contains the text "Logout" within the "user_settings_list"
I tap the [button|element|label|icon|field|text|widget] of type {string} Taps a widget of type. Then I tap the element of type "MaterialButton" , Then I tap the label of type "ListTile" , Then I tap the field of type "TextField"
I tap the [button|element|label|icon|field|text|widget] of type {string} within the {string} Taps a widget of type within another widget. Then I tap the element of type "MaterialButton" within the "user_settings_list"
I tap the [button|element|label|icon|field|text|widget] that contains the text {string} Taps a widget that contains text. Then I tap the label that contains the text "Logout" , Then I tap the button that contains the text "Sign up" , Then I tap the widget that contains the text "My User Profile"
I expect the text {string} to be [present|absent] Asserts the existence of text on the screen. Then I expect the text "Logout" to be present , But I expect the text "Signup" to be absent
I expect the text {string} to be [present|absent] within the {string} Asserts the existence of text within a parent widget. Then I expect the text "Logout" to be present within the "user_settings_list" , But I expect the text "Signup" to be absent within the "login_screen"
I wait until the {string} is [present\absent] Delays until a widget is present or absent. Then I wait until the "login_loading_indicator" is absent , And I wait until the "login_screen" is present
I wait until the [button|element|label|icon|field|text|widget] of type {string} is [present\absent] Waits until a widget type is present or absent. Then I wait until the element of type "ProgressIndicator" is absent , And I wait until the button of type "MaterialButton" is present
I long press the {string} [button|element|label|icon|field|text|widget] Scrolls into view and long presses the widget for 500 milliseconds. When I long press "controlKey" button
I long press the {string} [button|element|label|icon|field|text|widget] without scrolling it into view Long presses the widget for 500 milliseconds. When I long press "controlKey" button without scrolling it into view
I long press the {string} [button|element|label|icon|field|text|widget] for {int} milliseconds Scrolls into view and long presses the widget for the give number of milliseconds. When I long press "controlKey" button without scrolling it into view for 1500 milliseconds

Flutter Driver Utilities

For convenience the library provides a static FlutterDriverUtils class that abstracts away some common Flutter driver functionality like tapping a button, getting and entering text, checking if an element is present or absent, waiting for a condition to become true. See lib/src/flutter/utils/driver_utils.dart.

Debugging

In VSCode simply add add this block to your launch.json file (if you testable app is called app_test.dart and within the test_driver folder, if not replace that with the correct file path). Don't forget to put a break point somewhere!

{
  "name": "Debug Features Tests",
  "request": "launch",
  "type": "dart",
  "program": "test_driver/app_test.dart",
  "flutterMode": "debug"
}

After which the file will most likely look like this

{
  // Use IntelliSense to learn about possible attributes.
  // Hover to view descriptions of existing attributes.
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Flutter",
      "request": "launch",
      "type": "dart"
    },
    {
      "name": "Debug Features Tests",
      "request": "launch",
      "type": "dart",
      "program": "test_driver/app_test.dart",
      "flutterMode": "debug"
    }
  ]
}

Debugging the app under test

Setting the configuration property runningAppProtocolEndpointUri to the service protocol endpoint (found in stdout when an app has --verbose logging turned on) will ensure that the existing app is connected to rather than starting a new instance of the app.

NOTE: ensure the app you are trying to connect to calls enableFlutterDriverExtension() when it starts up otherwise the Flutter Driver will not be able to connect to it.

Also ensure that the --verbose flag is set when starting the app to test, this will then log the service protocol endpoint out to the console which is the uri you will need to set this property to. It usually takes the form of Connecting to service protocol: http://127.0.0.1:51540/EM72VtRsUV0=/ so set the runningAppProtocolEndpointUri to http://127.0.0.1:51540/EM72VtRsUV0=/ and then start the tests.

Interactive debugging

One way to configure your test environment is to run the app under test in a separate terminal and run the gherkin in a different terminal. With this approach you can hot reload the app by entering R in the app terminal and run the steps repeatedly in the other terminal with out incurring the cost of the app start up.

For the app under test, in this case lib/main_test.dart, it should look similar to this:

import 'package:flutter/material.dart';
import 'package:flutter_driver/driver_extension.dart';
void main() {
  enableFlutterDriverExtension();
runApp();

When you start this from the terminal, run like this:

flutter run -t lib/main_test.dart --verbose

As stated above, with the --verbose flag, you will want to find the service protocol endpoint. You should see similar output as this:

.....
Connecting to service protocol: http://127.0.0.1:61658/RtsPT2zp_qs=/
.....
Flutter run key commands.
[ +2 ms] r Hot reload. 🔥🔥🔥
[ +1 ms] R Hot restart.
[ ] h Repeat this help message.
[ ] d Detach (terminate "flutter run" but leave application running).
[ ] c Clear the screen
[ ] q Quit (terminate the application on the device).
[ ] An Observatory debugger and profiler on iPhone 8 Plus is available at: http://127.0.0.1:61660/xgrsw_qQ9sI=/
[ ] Running with unsound null safety
[ ] For more information see https://dart.dev/null-safety/unsound-null-safety

To run the gherkin tests, first update the test_driver/app_test.dart to something similar to this:

import 'dart:async';
import 'dart:io';
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart';
Future<void> main(List<String> args) async {
if (args.isEmpty) {
  print('please pass in the uri');
  exit(1);
}
final Iterable<StepDefinitionGeneric<World>> steps = [];
final config = FlutterTestConfiguration.DEFAULT(
  steps,
  featurePath: 'features//**.feature',
  targetAppPath: 'test_driver/app.dart',
)
  ..restartAppBetweenScenarios = false
  ..targetAppWorkingDirectory = '../'
  ..runningAppProtocolEndpointUri = args[0]; 
  return GherkinRunner().execute(config);
}

Start a new terminal and navigate to the test_driver directory.

Notice the app_test.dart expects a parameter. This is to ease the changing uri which will occur each time the app under test is started. If you use the R command, the uri does not change.

You can copy the uri from the terminal window of the app under test.

Run the command dart app_test.dart <uri>. As an example, the app under test has this line: Connecting to service protocol: http://127.0.0.1:61658/RtsPT2zp_qs=/ so you would copy http://127.0.0.1:61658/RtsPT2zp_qs=/ and paste it as such:

dart app_test.dart http://127.0.0.1:59862/luEyFXvK9Qc=/.

As you make changes in the app under test, just R (reload). In the test window you can rerun the tests and update the Scenarios quickly and easily.

Comments
  • Guarded function conflict resolve

    Guarded function conflict resolve

    Hi @jonsamwell I have the same issue for this problem (https://github.com/jonsamwell/flutter_gherkin/issues/198) After your comment I started look for solution how to fix it And I found solution witch work great for me and I want to share it with you maybe this can be helpful for your package. Without the pumpAndSettle() some steps don't work as well. But in the most cases we can work well without this method. And I skip it from runner to minimize risk to get the conflicts.

    opened by veesmana 21
  • Screenshoot can't be work at Hooks

    Screenshoot can't be work at Hooks

    hi, i try to take a screenshoot if any failed or pass step.

    but there isn't image receive in the folder

    this is my code

    test_driver/app_test.dart

    import 'dart:async';
    import 'package:flutter_gherkin/flutter_gherkin.dart';
    import 'package:gherkin/gherkin.dart';
    import 'package:glob/glob.dart';
    import 'steps/initial_state_of_app.dart';
    
    Future<void> main() {
      final config = FlutterTestConfiguration()
        ..features = [Glob(r"test_driver/features/**.feature")]
        ..reporters = [
          ProgressReporter(),
          TestRunSummaryReporter(),
          JsonReporter(path: './report.json')
        ] // you can include the "StdoutReporter()" without the message level parameter for verbose log information
        ..hooks = [
          AttachScreenshotOnFailedStepHook()
        ]
        ..stepDefinitions = [
          InitialStateOfApp()
        //, ISeeValue(), ClickSubtract()
        ]
        ..restartAppBetweenScenarios = true
        ..targetAppPath = "test_driver/bdd.dart";
        // ..tagExpression = "@smoke" // uncomment to see an example of running scenarios based on tag expressions
        // ..exitAfterTestRun = true; // set to false if debugging to exit cleanly
      return GherkinRunner().execute(config);
    }
    

    test_driver/hooks/attach_screenshot_on_failed_step_hook.dart

    import 'dart:convert';
    
    import 'package:gherkin/gherkin.dart';
    import 'package:meta/meta.dart';
    
    class AttachScreenshotOnFailedStepHook extends Hook {
      @override
      Future<void> onAfterStep(
        World world,
        String step,
        StepResult stepResult,
      ) async {
        if (stepResult.result == StepExecutionResult.fail ||
            stepResult.result == StepExecutionResult.error ||
            stepResult.result == StepExecutionResult.timeout) {
          try {
            final screenshotData = await takeScreenshot(world);
            world.attach(screenshotData, 'image/png', step);
          } catch (e, st) {
            world.attach('Failed to take screenshot\n$e\n$st', 'text/plain', step);
          }
        }else{
          final screenshotData = await takeScreenshot(world);
          world.attach(screenshotData, 'image/png', step);
        }
      }
    
      @protected
      Future<String> takeScreenshot(World world) async {
        final bytes = await (world as FlutterWorld).driver!.screenshot();
    
        return base64Encode(bytes);
      }
    }
    
    

    for the complete code here https://github.com/fadhlimaulidri/flutter_apps/tree/cucumber_version_1.0.1/test_driver

    to run flutter drive --target=test_driver/bdd.dart

    opened by fadhlimaulidri 19
  • Issue after upgrading to latest Flutter stable release (1.17.0)

    Issue after upgrading to latest Flutter stable release (1.17.0)

    It looks like there is something wrong after updating to Flutter 1.17.0 (see below error). I tried updating to flutter_gherkin: ^1.1.8-rc.2, but I still get the below error.

    Flutter run key commands.
    r Hot reload.
    R Hot restart.
    h Repeat this help message.
    d Detach (terminate "flutter run" but leave application running).
    c Clear the screen
    q Quit (terminate the application on the device).
    An Observatory debugger and profiler on AOSP on IA Emulator is available at: http://127.0.0.1:54972/d5TT3rM9KFU=/
    VMServiceFlutterDriver Connecting to Flutter application at http://127.0.0.1:54972/d5TT3rM9KFU=/
    VMServiceFlutterDriver Isolate found with number: 2621319811561611
    VMServiceFlutterDriver Isolate is not paused. Assuming application is ready.
    JSON-RPC error -32601 (method not found): Method not found
    DriverError: Failed to fulfill GetHealth due to remote error
    Original error: JSON-RPC error -32601 (method not found): Method not found
    Original stack trace:
    package:json_rpc_2/src/client.dart 110:64                                   Client.sendRequest
    package:json_rpc_2/src/peer.dart 79:15                                      Peer.sendRequest
    package:vm_service_client/src/scope.dart 64:23                              Scope.sendRequestRaw
    package:vm_service_client/src/isolate.dart 361:19                           VMIsolateRef.invokeExtension
    package:flutter_driver/src/driver/vmservice_driver.dart 323:63              VMServiceFlutterDriver.sendCommand
    package:flutter_driver/src/driver/driver.dart 174:34                        FlutterDriver.checkHealth
    package:flutter_driver/src/driver/vmservice_driver.dart 217:29              VMServiceFlutterDriver.connect.checkHealth
    package:flutter_driver/src/driver/vmservice_driver.dart 232:44              VMServiceFlutterDriver.connect
    ===== asynchronous gap ===========================
    package:flutter_driver/src/driver/driver.dart 148:35                        FlutterDriver.connect
    package:flutter_gherkin/src/flutter/flutter_test_configuration.dart 118:34  FlutterTestConfiguration._attemptDriverConnection
    package:flutter_gherkin/src/flutter/flutter_test_configuration.dart 75:18   FlutterTestConfiguration.createFlutterDriver
    package:flutter_gherkin/src/flutter/flutter_test_configuration.dart 81:26   FlutterTestConfiguration.createFlutterWorld
    package:flutter_gherkin/src/flutter/flutter_test_configuration.dart 95:20   FlutterTestConfiguration.prepare.<fn>
    package:gherkin/src/feature_file_runner.dart 167:42                         FeatureFileRunner._runScenario
    ===== asynchronous gap ===========================
    package:gherkin/src/feature_file_runner.dart 118:30                         FeatureFileRunner._runScenarioInZone.<fn>
    dart:async/zone.dart 1184:13                                                _rootRun
    dart:async/zone.dart 1077:19                                                _CustomZone.run
    dart:async/zone.dart 1619:10                                                _runZoned
    dart:async/zone.dart 1608:12                                                runZonedGuarded
    dart:async/zone.dart 1536:12                                                runZoned
    package:gherkin/src/feature_file_runner.dart 116:5                          FeatureFileRunner._runScenarioInZone
    package:gherkin/src/feature_file_runner.dart 63:21                          FeatureFileRunner._runFeature
    ===== asynchronous gap ===========================
    package:gherkin/src/feature_file_runner.dart 36:38                          FeatureFileRunner.run
    package:gherkin/src/test_runner.dart 77:45                                  GherkinRunner.execute
    ===== asynchronous gap ===========================
    tests\all_test.dart 8:26                                                    main
    dart:isolate-patch/isolate_patch.dart 301:19                                _startIsolate.<fn>
    dart:isolate-patch/isolate_patch.dart 168:12                                _RawReceivePortImpl._handleMessage
    
    
    JSON-RPC error -32601 (method not found): Method not found
    Unhandled exception:
    Bad state: No element
    #0      List.last (dart:core-patch/growable_array.dart:224:5)
    #1      JsonFeature.currentScenario (package:gherkin/src/reporters/json/json_feature.dart:33:22)
    #2      JsonReporter.onException (package:gherkin/src/reporters/json/json_reporter.dart:40:10)
    #3      AggregatedReporter.onException.<anonymous closure> (package:gherkin/src/reporters/aggregated_reporter.dart:58:30)
    #4      AggregatedReporter._invokeReporters (package:gherkin/src/reporters/aggregated_reporter.dart:70:21)
    <asynchronous suspension>
    #5      AggregatedReporter.onException (package:gherkin/src/reporters/aggregated_reporter.dart:57:11)
    #6      FeatureFileRunner._runScenarioInZone.<anonymous closure> (package:gherkin/src/feature_file_runner.dart:131:23)
    #7      _rootRunBinary (dart:async/zone.dart:1208:13)
    #8      _RootZone.runBinary (dart:async/zone.dart:1445:12)
    #9      runZonedGuarded.<anonymous closure> (dart:async/zone.dart:1591:19)
    #10     _CustomZone.handleUncaughtError (dart:async/zone.dart:1059:19)
    #11     Future._propagateToListeners (dart:async/future_impl.dart:598:16)
    #12     Future._completeError (dart:async/future_impl.dart:534:5)
    #13     Future.timeout.<anonymous closure> (dart:async/future_impl.dart:785:16)
    #14     _rootRunBinary (dart:async/zone.dart:1204:38)
    #15     _CustomZone.runBinary (dart:async/zone.dart:1093:19)
    #16     _FutureListener.handleError (dart:async/future_impl.dart:155:20)
    #17     Future._propagateToListeners.handleError (dart:async/future_impl.dart:694:47)
    #18     Future._propagateToListeners (dart:async/future_impl.dart:715:24)
    #19     Future._propagateToListeners (dart:async/future_impl.dart:609:9)
    #20     Future._completeError (dart:async/future_impl.dart:534:5)
    #21     _AsyncAwaitCompleter.completeError (dart:async-patch/async_patch.dart:43:15)
    #22     Scope.sendRequestRaw (package:vm_service_client/src/scope.dart)
    <asynchronous suspension>
    #23     VMIsolateRef.invokeExtension (package:vm_service_client/src/isolate.dart:361:19)
    #24     VMServiceFlutterDriver.sendCommand (package:flutter_driver/src/driver/vmservice_driver.dart:323:63)
    #25     FlutterDriver.checkHealth (package:flutter_driver/src/driver/driver.dart:174:34)
    #26     VMServiceFlutterDriver.connect.checkHealth (package:flutter_driver/src/driver/vmservice_driver.dart:217:29)
    #27     VMServiceFlutterDriver.connect (package:flutter_driver/src/driver/vmservice_driver.dart:232:44)
    <asynchronous suspension>
    #28     FlutterDriver.connect (package:flutter_driver/src/driver/driver.dart:148:35)
    #29     FlutterTestConfiguration._attemptDriverConnection (package:flutter_gherkin/src/flutter/flutter_test_configuration.dart:118:34)
    #30     FlutterTestConfiguration.createFlutterDriver (package:flutter_gherkin/src/flutter/flutter_test_configuration.dart:75:18)
    #31     FlutterTestConfiguration.createFlutterWorld (package:flutter_gherkin/src/flutter/flutter_test_configuration.dart:81:26)
    #32     FlutterTestConfiguration.prepare.<anonymous closure> (package:flutter_gherkin/src/flutter/flutter_test_configuration.dart:95:20)
    #33     FeatureFileRunner._runScenario (package:gherkin/src/feature_file_runner.dart:167:42)
    <asynchronous suspension>
    #34     FeatureFileRunner._runScenarioInZone.<anonymous closure> (package:gherkin/src/feature_file_runner.dart:118:30)
    #35     _rootRun (dart:async/zone.dart:1184:13)
    #36     _CustomZone.run (dart:async/zone.dart:1077:19)
    #37     _runZoned (dart:async/zone.dart:1619:10)
    #38     runZonedGuarded (dart:async/zone.dart:1608:12)
    #39     runZoned (dart:async/zone.dart:1536:12)
    #40     FeatureFileRunner._runScenarioInZone (package:gherkin/src/feature_file_runner.dart:116:5)
    #41     FeatureFileRunner._runFeature (package:gherkin/src/feature_file_runner.dart:63:21)
    <asynchronous suspension>
    #42     FeatureFileRunner.run (package:gherkin/src/feature_file_runner.dart:36:38)
    #43     GherkinRunner.execute (package:gherkin/src/test_runner.dart:77:45)
    <asynchronous suspension>
    #44     main
    #45     _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:301:19)
    #46     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)
    
    opened by jonjon1123 18
  • Writing step definitions using annotations

    Writing step definitions using annotations

    Hi @jonsamwell, thanks for writing this library. I'm a relative Flutter noob, but have been working with Cucumber in a few other languages. In most of those, a step definition is represented by a method with an annotation containing the RegEx part and some code generation / macro magic searches for such annotations, parses the method signature, aligns it with the RegEx and generates the required code. Afais Dart's source_gen should be capable of doing sth. like this. Have you thought about this and do you think it would work?

    question 
    opened by nightscape 18
  • Reporters not working properly with integration_test

    Reporters not working properly with integration_test

    We try to switch to version 3 of the package to use the integration_test package. But it seems that the reporters are not working properly.

    StdoutReporter, ProgressReporter and TestRunSummaryReporter don't show any color when logged by the print function. Also, they don't display any message when leaving the stdout function as default, and neither does the developer.log function.

    The JsonReporter can't generate the JSON file, the path "./reporter.json" is apparently not relative, and tries to generate it in a read-only folder.

    We're trying this on a Mac M1.

    opened by Nolat 17
  • Fail in final of test

    Fail in final of test

    Starting Flutter app under test 'test_driver/app.dart', this might take a few moments before run hook VMServiceFlutterDriver Connecting to Flutter application at http://127.0.0.1:38445/ScaIiPjZCT0=/ VMServiceFlutterDriver Isolate found with number: 3682545693255519 VMServiceFlutterDriver Isolate is not paused. Assuming application is ready. VMServiceFlutterDriver Connected to Flutter application. running hook before scenario 'counter should reset when app is restarted' Running scenario: counter should reset when app is restarted # ./features/app_restart.feature:2  √ Given I expect the "counter" to be "0" # ./features/app_restart.feature:3 took 108ms  √ When I tap the "increment" button # ./features/app_restart.feature:4 took 472ms  √ Then I expect the "counter" to be "1" # ./features/app_restart.feature:5 took 118ms VMServiceFlutterDriver Connecting to Flutter application at http://127.0.0.1:38445/ScaIiPjZCT0=/ VMServiceFlutterDriver Isolate found with number: 778646431923143 VMServiceFlutterDriver Isolate is not paused. Assuming application is ready. VMServiceFlutterDriver Connected to Flutter application.  √ When I restart the app # ./features/app_restart.feature:6 took 3344ms  √ Then I expect the "counter" to be "0" # ./features/app_restart.feature:7 took 146ms PASSED: Scenario counter should reset when app is restarted # ./features/app_restart.feature:2 Restarting Flutter app under test running hook after scenario 'counter should reset when app is restarted' VMServiceFlutterDriver Connecting to Flutter application at http://127.0.0.1:38445/ScaIiPjZCT0=/ Error waiting for no transient callbacks from Flutter driver:

    Failed assertion: boolean expression must not be null

    #0 VMServiceFlutterDriver.sendCommand (package:flutter_driver/src/driver/vmservice_driver.dart:351:29) #1 FlutterDriver.waitUntilNoTransientCallbacks (package:flutter_driver/src/driver/driver.dart:230:11) #2 FlutterWorld._closeDriver (package:flutter_gherkin/src/flutter/flutter_world.dart:49:14) #3 FlutterWorld.dispose (package:flutter_gherkin/src/flutter/flutter_world.dart:40:11) #4 FeatureFileRunner._runScenario (package:gherkin/src/feature_file_runner.dart:276:16) #5 FeatureFileRunner._runScenarioInZone. (package:gherkin/src/feature_file_runner.dart:142:30) #6 _rootRun (dart:async/zone.dart:1190:13) #7 _CustomZone.run (dart:async/zone.dart:1093:19) #8 _runZoned (dart:async/zone.dart:1630:10) #9 runZonedGuarded (dart:async/zone.dart:1618:12) #10 runZoned (dart:async/zone.dart:1547:12) #11 FeatureFileRunner._runScenarioInZone (package:gherkin/src/feature_file_runner.dart:140:5) #12 FeatureFileRunner._runFeature (package:gherkin/src/feature_file_runner.dart:74:21) #13 FeatureFileRunner.run (package:gherkin/src/feature_file_runner.dart:41:38) #14 GherkinRunner.execute (package:gherkin/src/test_runner.dart:86:45) #15 main (file:///home/lukasgarcya/VisualStudioCodeProjects/flutter_gherkin/example/test_driver/app_test.dart:40:26) #16 _startIsolate. (dart:isolate-patch/isolate_patch.dart:301:19) #17 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)

    VMServiceFlutterDriver Isolate found with number: 483803094991227 VMServiceFlutterDriver Isolate is not paused. Assuming application is ready. VMServiceFlutterDriver Connected to Flutter application. running hook before scenario 'User can navigate back from page two' Running scenario: User can navigate back from page two # ./features/back_navigation.feature:3  √ Given I expect the "counter" to be "0" # ./features/back_navigation.feature:4 took 123ms  √ When I tap the "increment" button # ./features/back_navigation.feature:5 took 394ms  √ Then I expect the "counter" to be "1" # ./features/back_navigation.feature:6 took 63ms  √ Given I tap the label that contains the text "Open page 2" # ./features/back_navigation.feature:8 took 396ms  √ Then I expect the text "Contents of page 2" to be present # ./features/back_navigation.feature:9 took 67ms  √ Given I tap the back button # ./features/back_navigation.feature:11 took 384ms  √ Then I expect the "counter" to be "1" # ./features/back_navigation.feature:12 took 98ms PASSED: Scenario User can navigate back from page two # ./features/back_navigation.feature:3 Restarting Flutter app under test running hook after scenario 'User can navigate back from page two' VMServiceFlutterDriver Connecting to Flutter application at http://127.0.0.1:38445/ScaIiPjZCT0=/ Error waiting for no transient callbacks from Flutter driver:

    Failed assertion: boolean expression must not be null

    opened by lukasgarcya 17
  • Error returned when all scenarios are passing

    Error returned when all scenarios are passing

    I'm unable to get a successful result when running the tests. When I run the tests with this command flutter --no-color test --machine test_driver/app_test.dart I get the following output:

    {"suite":{"id":0,"platform":"vm","path":"/Users/denisbrandi/Documents/workspace/Tide/universal-app-android/test_driver/app_test.dart"},"type":"suite","time":0}
    {"test":{"id":1,"name":"loading /Users/denisbrandi/Documents/workspace/Tide/universal-app-android/test_driver/app_test.dart","suiteID":0,"groupIDs":[],"metadata":{"skip":false,"skipReason":null},"line":null,"column":null,"url":null},"type":"testStart","time":2}
    {"count":1,"type":"allSuites","time":5}
    Shell: Found feature file './test_driver/account/acceptance/features/get_accounts.feature'
    Shell: Parsing feature file: './test_driver/account/acceptance/features/get_accounts.feature'
    Shell: Found feature file './test_driver/authentication/acceptance/features/logout.feature'
    Shell: Parsing feature file: './test_driver/authentication/acceptance/features/logout.feature'
    Shell: Found feature file './test_driver/authentication/acceptance/features/recover_account.feature'
    Shell: Parsing feature file: './test_driver/authentication/acceptance/features/recover_account.feature'
    Shell: Found 3 feature file(s) to run
    Shell: Executing features in random order
    Shell: Starting Flutter app under test 'test_driver/app.dart', this might take a few moments
    Shell: Attempting to running feature '[Authentication] Logout' # ./test_driver/authentication/acceptance/features/logout.feature:1
    Shell: Creating new world for scenario 'Successful logout' # ./test_driver/authentication/acceptance/features/logout.feature:3
    Shell: VMServiceFlutterDriver Connecting to Flutter application at http://127.0.0.1:56002/hZCYQQHYtPE=/
    Shell: VMServiceFlutterDriver Isolate found with number: 3832523638888479
    Shell: VMServiceFlutterDriver Isolate is not paused. Assuming application is ready.
    Shell: VMServiceFlutterDriver Connected to Flutter application.
    Shell: Running scenario: Successful logout # ./test_driver/authentication/acceptance/features/logout.feature:3
    Shell: Attempting to run step 'Given user logs out' # ./test_driver/authentication/acceptance/features/logout.feature:4
    Shell:    √ Given user logs out # ./test_driver/authentication/acceptance/features/logout.feature:4 took 4ms
    Shell: Attempting to run step 'When logout is successful' # ./test_driver/authentication/acceptance/features/logout.feature:5
    Shell:    √ When logout is successful # ./test_driver/authentication/acceptance/features/logout.feature:5 took 7ms
    Shell: Attempting to run step 'Then user info is removed' # ./test_driver/authentication/acceptance/features/logout.feature:6
    Shell:    √ Then user info is removed # ./test_driver/authentication/acceptance/features/logout.feature:6 took 1ms
    Shell: PASSED: Scenario Successful logout # ./test_driver/authentication/acceptance/features/logout.feature:3
    Shell: Creating new world for scenario 'Unsuccessful logout' # ./test_driver/authentication/acceptance/features/logout.feature:8
    Shell: VMServiceFlutterDriver Connecting to Flutter application at http://127.0.0.1:56002/hZCYQQHYtPE=/
    Shell: VMServiceFlutterDriver Isolate found with number: 3832523638888479
    Shell: VMServiceFlutterDriver Isolate is not paused. Assuming application is ready.
    Shell: VMServiceFlutterDriver Connected to Flutter application.
    Shell: Running scenario: Unsuccessful logout # ./test_driver/authentication/acceptance/features/logout.feature:8
    Shell: Attempting to run step 'Given user logs out' # ./test_driver/authentication/acceptance/features/logout.feature:9
    Shell:    √ Given user logs out # ./test_driver/authentication/acceptance/features/logout.feature:9 took 0ms
    Shell: Attempting to run step 'When logout is not successful' # ./test_driver/authentication/acceptance/features/logout.feature:10
    Shell:    √ When logout is not successful # ./test_driver/authentication/acceptance/features/logout.feature:10 took 1ms
    Shell: Attempting to run step 'Then user info is removed' # ./test_driver/authentication/acceptance/features/logout.feature:11
    Shell:    √ Then user info is removed # ./test_driver/authentication/acceptance/features/logout.feature:11 took 0ms
    Shell: PASSED: Scenario Unsuccessful logout # ./test_driver/authentication/acceptance/features/logout.feature:8
    Shell: Finished running feature '[Authentication] Logout' # ./test_driver/authentication/acceptance/features/logout.feature:1
    Shell: Attempting to running feature '[Authentication] Recover Account' # ./test_driver/authentication/acceptance/features/recover_account.feature:1
    Shell: Creating new world for scenario 'Account recovered' # ./test_driver/authentication/acceptance/features/recover_account.feature:3
    Shell: VMServiceFlutterDriver Connecting to Flutter application at http://127.0.0.1:56002/hZCYQQHYtPE=/
    Shell: VMServiceFlutterDriver Isolate found with number: 3832523638888479
    Shell: VMServiceFlutterDriver Isolate is not paused. Assuming application is ready.
    Shell: VMServiceFlutterDriver Connected to Flutter application.
    Shell: Running scenario: Account recovered # ./test_driver/authentication/acceptance/features/recover_account.feature:3
    Shell: Attempting to run step 'Given A grant code is exchanged' # ./test_driver/authentication/acceptance/features/recover_account.feature:4
    Shell:    √ Given A grant code is exchanged # ./test_driver/authentication/acceptance/features/recover_account.feature:4 took 0ms
    Shell: Attempting to run step 'When A successful token response is received' # ./test_driver/authentication/acceptance/features/recover_account.feature:5
    Shell:    √ When A successful token response is received # ./test_driver/authentication/acceptance/features/recover_account.feature:5 took 42ms
    Shell: Attempting to run step 'Then User is logged' # ./test_driver/authentication/acceptance/features/recover_account.feature:6
    Shell:    √ Then User is logged # ./test_driver/authentication/acceptance/features/recover_account.feature:6 took 1ms
    Shell: Attempting to run step 'And User info is retrieved' # ./test_driver/authentication/acceptance/features/recover_account.feature:7
    Shell:    √ And User info is retrieved # ./test_driver/authentication/acceptance/features/recover_account.feature:7 took 0ms
    Shell: PASSED: Scenario Account recovered # ./test_driver/authentication/acceptance/features/recover_account.feature:3
    Shell: Creating new world for scenario 'Account not recovered' # ./test_driver/authentication/acceptance/features/recover_account.feature:9
    Shell: VMServiceFlutterDriver Connecting to Flutter application at http://127.0.0.1:56002/hZCYQQHYtPE=/
    Shell: VMServiceFlutterDriver Isolate found with number: 3832523638888479
    Shell: VMServiceFlutterDriver Isolate is not paused. Assuming application is ready.
    Shell: VMServiceFlutterDriver Connected to Flutter application.
    Shell: Running scenario: Account not recovered # ./test_driver/authentication/acceptance/features/recover_account.feature:9
    Shell: Attempting to run step 'Given A grant code is exchanged' # ./test_driver/authentication/acceptance/features/recover_account.feature:10
    Shell:    √ Given A grant code is exchanged # ./test_driver/authentication/acceptance/features/recover_account.feature:10 took 0ms
    Shell: Attempting to run step 'When A token error response is received' # ./test_driver/authentication/acceptance/features/recover_account.feature:11
    Shell:    √ When A token error response is received # ./test_driver/authentication/acceptance/features/recover_account.feature:11 took 3ms
    Shell: Attempting to run step 'Then User is not logged' # ./test_driver/authentication/acceptance/features/recover_account.feature:12
    Shell:    √ Then User is not logged # ./test_driver/authentication/acceptance/features/recover_account.feature:12 took 0ms
    Shell: PASSED: Scenario Account not recovered # ./test_driver/authentication/acceptance/features/recover_account.feature:9
    Shell: Finished running feature '[Authentication] Recover Account' # ./test_driver/authentication/acceptance/features/recover_account.feature:1
    Shell: Attempting to running feature '[Accounts] Get the account list for a user' # ./test_driver/account/acceptance/features/get_accounts.feature:1
    Shell: Creating new world for scenario 'Get the account list info' # ./test_driver/account/acceptance/features/get_accounts.feature:3
    Shell: VMServiceFlutterDriver Connecting to Flutter application at http://127.0.0.1:56002/hZCYQQHYtPE=/
    Shell: VMServiceFlutterDriver Isolate found with number: 3832523638888479
    Shell: VMServiceFlutterDriver Isolate is not paused. Assuming application is ready.
    Shell: VMServiceFlutterDriver Connected to Flutter application.
    Shell: Running scenario: Get the account list info # ./test_driver/account/acceptance/features/get_accounts.feature:3
    Shell: Attempting to run step 'Given a user requests the account list' # ./test_driver/account/acceptance/features/get_accounts.feature:4
    Shell:    √ Given a user requests the account list # ./test_driver/account/acceptance/features/get_accounts.feature:4 took 0ms
    Shell: Attempting to run step 'When the account list is retrieved' # ./test_driver/account/acceptance/features/get_accounts.feature:5
    Shell:    √ When the account list is retrieved # ./test_driver/account/acceptance/features/get_accounts.feature:5 took 7ms
    Shell: Attempting to run step 'Then the account list is returned' # ./test_driver/account/acceptance/features/get_accounts.feature:6
    Shell:    √ Then the account list is returned # ./test_driver/account/acceptance/features/get_accounts.feature:6 took 0ms
    Shell: PASSED: Scenario Get the account list info # ./test_driver/account/acceptance/features/get_accounts.feature:3
    Shell: Creating new world for scenario 'Failed to get account list info' # ./test_driver/account/acceptance/features/get_accounts.feature:8
    Shell: VMServiceFlutterDriver Connecting to Flutter application at http://127.0.0.1:56002/hZCYQQHYtPE=/
    Shell: VMServiceFlutterDriver Isolate found with number: 3832523638888479
    Shell: VMServiceFlutterDriver Isolate is not paused. Assuming application is ready.
    Shell: VMServiceFlutterDriver Connected to Flutter application.
    Shell: Running scenario: Failed to get account list info # ./test_driver/account/acceptance/features/get_accounts.feature:8
    Shell: Attempting to run step 'Given a user requests the account list' # ./test_driver/account/acceptance/features/get_accounts.feature:9
    Shell:    √ Given a user requests the account list # ./test_driver/account/acceptance/features/get_accounts.feature:9 took 0ms
    Shell: Attempting to run step 'When an account list error is retrieved' # ./test_driver/account/acceptance/features/get_accounts.feature:10
    Shell:    √ When an account list error is retrieved # ./test_driver/account/acceptance/features/get_accounts.feature:10 took 3ms
    Shell: Attempting to run step 'Then the error is returned' # ./test_driver/account/acceptance/features/get_accounts.feature:11
    Shell:    √ Then the error is returned # ./test_driver/account/acceptance/features/get_accounts.feature:11 took 0ms
    Shell: PASSED: Scenario Failed to get account list info # ./test_driver/account/acceptance/features/get_accounts.feature:8
    Shell: Finished running feature '[Accounts] Get the account list for a user' # ./test_driver/account/acceptance/features/get_accounts.feature:1
    Shell: 6 scenarios (6 passed)
    Shell: 19 steps (19 passed)
    Shell: 0:00:01.604000
    Shell: Terminating Flutter app under test
    {"testID":1,"result":"success","skipped":false,"hidden":true,"type":"testDone","time":76324}
    {"success":true,"type":"done","time":76376}
    

    But it is returned as an error by both my git-hooks and my CI.

    When I run from Intellij, I get this error instead

    Screenshot 2020-07-08 at 19 03 36

    How can I fix this issue? Thanks in advance!

    opened by DenisBronx 17
  • Different feature folder

    Different feature folder

    When setting the features or the feature path (or both), it still imports all features under the features folder. I want the structure features/casex and features/casey. When I enter the path features/casey it still includes the features of the casex with it.

    opened by AFASbart 16
  • Exception: Guarded function conflict error during tap in latest RC 3.0.0-rc.9

    Exception: Guarded function conflict error during tap in latest RC 3.0.0-rc.9

    @jonsamwell Getting error while taping on element in android , Below function gives error: Future selectClientDetails(String contactName) async { await appDriver.tap(clientDropdown); } In above function appDriver is object of AppDriverAdapter.

    Error below:

    I/flutter ( 7983): × Then User selects client "[[email protected]](mailto:[email protected])" # :0 took 3ms I/flutter ( 7983): Exception: Guarded function conflict. I/flutter ( 7983): You must use "await" with all Future-returning test APIs. I/flutter ( 7983): The guarded method "pumpAndSettle" from class WidgetTester was called from package:flutter_gherkin/src/flutter/adapters/widget_tester_app_driver_adapter.dart on line 21. I/flutter ( 7983): Then, the "selectClientDetails" method from class InvoiceDetailsPage was called from file:///Users/ak/repo/integration_test/gherkin/steps/invoice_steps/invoice_details_page_steps.dart on line 10. I/flutter ( 7983): The first method (WidgetTester.pumpAndSettle) had not yet finished executing at the time that the second method (InvoiceDetailsPage.selectClientDetails) was called. Since both are guarded, and the second was not a nested call inside the first, the first must complete its execution before the second can be called. Typically, this is achieved by putting an "await" statement in front of the call to the first. I/flutter ( 7983): I/flutter ( 7983): When the first method (WidgetTester.

    opened by amitkumar-qa 14
  • Incorrect feature name in HTML reports

    Incorrect feature name in HTML reports

    In the HTML report, correct feature name is not publishing. For each feature, the first scenario name is getting published for feature name in the report.

    I am using 3.0.0-rc.3 with integration_test support.

    opened by umangdivyanshu 14
  • BDD is not working in code magic flutter

    BDD is not working in code magic flutter

    I have written integration test cases with flutter gherkins(BDD) in my flutter project, But I am getting errors with test failed results. My concern is whether code magic will work with BDD or not?

    opened by naswin35 13
  • fix: remove bracket on gherkin generator

    fix: remove bracket on gherkin generator

    The generator is currently not working, as the bracket was not removed in my proposes from here: https://github.com/jonsamwell/flutter_gherkin/pull/279#discussion_r1055775542

    CC @AFASbart

    opened by golane-august 0
  • [Bug] onBeforeRunFeature not being called in a specific case

    [Bug] onBeforeRunFeature not being called in a specific case

    Version: 3.0.0-rc.17

    Hello,

    First of all, I would like to thank you for the package.

    The issue is related to calling reporters before running a feature using the following method: https://github.com/jonsamwell/flutter_gherkin/blob/7c8be5f60e987e858d36502cc97af9a4ec7a5c66/lib/src/flutter/runners/gherkin_integration_test_runner.dart#L126

    This method is called before running the first scenario, which is equivalent to calling it before the feature generally speaking. But, what if the first scenario was ignored using a tag expression ? In this case, the method onBeforeRunFeature will not be called.

    The example using the integration_test package illustrates the issue: For the first scenario, onBeforeRunFeature is called https://github.com/jonsamwell/flutter_gherkin/blob/7c8be5f60e987e858d36502cc97af9a4ec7a5c66/example_with_integration_test/integration_test/gherkin_suite_test.g.dart#L93 But, for the second scenario, the method onBeforeRunFeature is not passed as an argument to it. As a consequence, onBeforeRunFeature cannot be called in any case.

    It would be great to use another mechanism to detect the first scenario to run for a feature.

    Thank you !

    opened by youssef-t 3
  • [Question] How to send tags in the run command as parameter?

    [Question] How to send tags in the run command as parameter?

    Hello,

    We are trying to run the test command with multiple tags as parameters in order to override the “tagExpression” configuration in gherkin_suite_test.dart. Is that possible?

    We are using the 3.0.0_rc.17 version and using the following command to run the tests flutter drive --driver=test_driver/integration_test_driver.dart --target=integration_test/gherkin_suite_test.dart --flavor pre

    Thanks in advance

    opened by joaofyz 5
  • fix(web): wrong pass on integration tests

    fix(web): wrong pass on integration tests

    Plz inform, if have to split in multiple smaller PRs.

    See: #275

    I think the main problem is, that the failures are caught inside the gherkin_integration_test_runner and aren't reported to the integration_test package. So it passes. Why it only happens on web, I don't really know.

    But for all of the platforms. A scenario should never pass, if one step fails. So I would recommend to simply apply it everywhere, as this should be the final "ultimate" test, even if it does not show the exact step, where it fails. At least the CI never passes a scenario, which has not passed all of its steps, whatever is happening. Of course one should determine the source of the failure.

    opened by golane-august 0
  • Call tests via `flutter test` instead of `flutter drive` possible?

    Call tests via `flutter test` instead of `flutter drive` possible?

    Can you use integration_test package instead of driver package to test the app?

    Use:

    flutter test integration_test/gherkin_suite_test.dart
    

    Instead of:

    flutter drive --driver=test_driver/integration_test_driver.dart --target=integration_test/gherkin_suite_test.dart
    

    But I get the error with flutter test:

    flutter test integration_test/gherkin_suite_test.dart
    00:00 +0: loading /Users/august/Coding/git/jonsamwell/flutter_gherkin/example_with_integration_test/integration_test/gherkin_suite_test.dart                                                                                                                                                                           R00:11 +0: loading /Users/august/Coding/git/jonsamwell/flutter_gherkin/example_with_integration_test/integration_test/gherkin_suite_test.dart                                                                                                                                                                      11.7s
    ✓  Built build/app/outputs/flutter-apk/app-debug.apk.
    00:12 +0: loading /Users/august/Coding/git/jonsamwell/flutter_gherkin/example_with_integration_test/integration_test/gherkin_suite_test.dart                                                                                                                                                                           I00:13 +0: loading /Users/august/Coding/git/jonsamwell/flutter_gherkin/example_with_integration_test/integration_test/gherkin_suite_test.dart                                                                                                                                                                      656ms
    00:15 +0 -1: loading /Users/august/Coding/git/jonsamwell/flutter_gherkin/example_with_integration_test/integration_test/gherkin_suite_test.dart [E]                                                                                                                                                                    
      Failed to load "/Users/august/Coding/git/jonsamwell/flutter_gherkin/example_with_integration_test/integration_test/gherkin_suite_test.dart": Bad state: Can't call group() once tests have begun running.
      package:test_api                                                                                                                  Declarer.group
      package:flutter_test/src/test_compat.dart 189:13                                                                                  group
      package:flutter_gherkin/src/flutter/runners/gherkin_integration_test_runner.dart 117:5                                            GherkinIntegrationTestRunner.runFeature
      Users/august/Coding/git/jonsamwell/flutter_gherkin/example_with_integration_test/integration_test/gherkin_suite_test.g.dart 34:5  _CustomGherkinIntegrationTestRunner.testFeature0
      Users/august/Coding/git/jonsamwell/flutter_gherkin/example_with_integration_test/integration_test/gherkin_suite_test.g.dart 26:5  _CustomGherkinIntegrationTestRunner.onRun
      package:flutter_gherkin/src/flutter/runners/gherkin_integration_test_runner.dart 94:5                                             GherkinIntegrationTestRunner.run
      
    00:16 +0 -1: (tearDownAll) - did not complete [E]                                                                                                                                                                                                                                                                      
    00:16 +0 -1: Some tests failed.                         
    

    I used the example in integration_test__package_support: https://github.com/jonsamwell/flutter_gherkin/tree/integration_test__package_support/example_with_integration_test

    I also tested flutter run with tagExpression @failure-expected, but then the result doesn't match the failure:

    flutter run integration_test/gherkin_suite_test.dart
    ...
    I/flutter (16379): FAILED: Scenario Failed expect() should be added to json report # ./integration_test/features/failure.feature:0
    ...
    I/flutter (16379): 00:03 +3: All tests passed!
    
    opened by golane-august 3
  • Getting code generation to work with build_runner watch

    Getting code generation to work with build_runner watch

    Hi, First of all, great work with this package!

    I'm using 3.0.0-rc17 For other libraries using code generation (such as json_serializable), I'm used to keep a flutter pub run build_runner watch running somewhere in the background to update detect changes and update files automatically. However, while running watch, the generated code is updated only when I change the .dart file containing the @GherkinTestSuite, and never when I update .feature files. Is there any way to trigger an incremental rebuild when the .feature files are updated?

    opened by ClemsRocket 0
Releases(3.0.0-rc.17)
  • 3.0.0-rc.17(Jul 25, 2022)

    [3.0.0-rc.17] - 25/07/2022

    • Fix #257 - fixed issue when generating a step with a '$' sign in
    • Fix #256 - Ensure all exceptions generated when running a step are logged
    • Fix #253 - Ensure features with descriptions that span more than one line are parsed correctly
    • Fix #252 - Ensure all async code is awaited
    • When taking a screenshot on the web use the render element rather than relying on native code that does not work
    Source code(tar.gz)
    Source code(zip)
  • 3.0.0-rc.16(Jul 1, 2022)

    [3.0.0-rc.16] - 01/07/2022

    • Fix #231 - using local coordinate system when taking a screenshot on Android (thanks to @youssef-t for the solution)
    • Fix #216 - ensure step exceptions and expect failure results are added as errors to the json report
    • Scenarios can now have descriptions which also appear in the json reporter output

    NOTE: Due to the above changes generated files will need to be re-generated

    flutter pub run build_runner clean
    flutter pub run build_runner build --delete-conflicting-outputs
    
    Source code(tar.gz)
    Source code(zip)
  • 3.0.0-rc.15(Jun 29, 2022)

    [3.0.0-rc.15] - 28/06/2022

    • Exposed frameBindingPolicy on the test runner when running tests which can affect how frames are painted and the speed of the test run, I've removed the default value which might be responsible for #231
    Source code(tar.gz)
    Source code(zip)
  • 3.0.0-rc.14(Jun 27, 2022)

  • 3.0.0-rc.13(Jun 27, 2022)

    [3.0.0-rc.13] - 27/06/2022

    • Fix #235 - fix issue taking a screenshot on an Android device
    • Resolved #170: Added example code to ensure json report is save to disk even when the test run fails. Also added script to generate a HTML report from a JSON report
    Source code(tar.gz)
    Source code(zip)
  • 3.0.0-rc.12(Jun 24, 2022)

  • 3.0.0-rc.11(Jun 24, 2022)

    [3.0.0-rc.11] - 24/06/2022

    • Fix #231 - Removed the use of explicitly calling pumpAndSettle in the pre-defined steps in favour of the implicit pumpAndSettle calls used in the WidgetTesterAppDriverAdapter.
    • Added ability to add a appLifecyclePumpHandler to override the default handler that determines how the app is pumped during lifecycle events. Useful if your app has a long splash screen etc. Parameter is on executeTestSuite.
    • Added ability to ensure feature paths are relative when generating reports useAbsolutePaths on the GherkinTestSuite attribute
    • BREAKING CHANGE: The parameters on executeTestSuite are now keyed to allow for the above changes
    Source code(tar.gz)
    Source code(zip)
  • v3.0.0-rc.10(Jun 24, 2022)

    A lot of new changes in this update.

    • Test scenario's can be retried, in the configuration it is possible to specify the amount of step retries. (#218).
    • It is now possible to the see if the scenario has passed in the JSON report and in the afterScenario hook (#201).
    • When a test fails, it does not automatically fail in the test framework. The other steps will be skipped and show up in the JSON report. This will now not cause any app inconsistencies with error pop-ups (#182).
    • Steps that have been skipped, are now also being recorded through the reporters and hooks (#179).
    • Documentation on the feature folder location has been improved (#174).
    Source code(tar.gz)
    Source code(zip)
  • v3.0.0-rc.9(Nov 23, 2021)

  • 3.0.0-rc.6(Oct 27, 2021)

    [3.0.0-rc.6] - 27/10/2021

    • BREAKING CHANGE: Made appMainFunction return a Future<void> so it can be async
    • Fix: #159: Swipe step not working
    • Ensure Hook.onBeforeRun is called before the run starts
    • Set Frame policy- defaults to LiveTestWidgetsFlutterBindingFramePolicy.benchmarkLive to slightly improve performance
    Source code(tar.gz)
    Source code(zip)
  • 3.0.0-rc.3(Sep 15, 2021)

    [3.0.0-rc.3] - 21/06/2021

    • POSSIBLE BREAKING CHANGE: Removed tap call before enterText is invoked in WidgetTesterAppDriverAdapter this was due to the fact that it opens the on-screen keyboard which is not closed after the text is entered so it could be blocking further controls from view.
    • Fix: #150: Better handling of JSON strings in multiline string segments in feature files
    • Fix: #141 & #128: Added example of how to generate html report from json report output and fixed all scenarios ending up in the last feature section of the json report
    Source code(tar.gz)
    Source code(zip)
  • 3.0.0-rc.2(Jun 20, 2021)

    [3.0.0-rc.2] - 21/06/2021

    • Fixed late initialization error when invoking hooks
    • Updated float parameter parser so an exception is not thrown during parsing
    Source code(tar.gz)
    Source code(zip)
  • 3.0.0-rc.5(Sep 16, 2021)

  • 3.0.0-rc.4(Sep 15, 2021)

  • 2.0.0(May 25, 2021)

  • 1.2.0(May 3, 2021)

    [1.2.0] - 03/05/2021

    • Upgraded to the null-safety version of dart_gherkin, as such there are some breaking changes to be aware of (see https://github.com/jonsamwell/dart_gherkin/blob/master/CHANGELOG.md for the full list):

      • BREAKING CHANGE: Table has been renamed to GherkinTable to avoid naming clashes
      • BREAKING CHANGE: exitAfterTestRun configuration option has been removed as it depends on importing dart:io which is not available under certain environments (dartjs for example).
      • BREAKING CHANGE: Reporter->onException() exception parameter is now an object rather than an exception
      • POSSIBLE BREAKING CHANGE: Feature file discovery has been refactored to abstract it from the external Glob dependency. It now support the three native dart Patterns (String, RegExp & Glob). There is potential here for your patterns to not work anymore due as the default IoFeatureFileAccessor assumes the current directory is the working directory to search from. For the most part this simple regex is probably enough to get you going.
      RegExp('features/*.*.feature')
      
    • Allow dart-define to be passed to the Flutter build (thanks @Pholey)

    Source code(tar.gz)
    Source code(zip)
  • 1.1.9(Nov 24, 2020)

    [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
    • Added the ability to have multiple example blocks with tags per scenario outline
    Source code(tar.gz)
    Source code(zip)
  • 1.1.8+9(Sep 20, 2020)

    [1.1.8+9] - 20/09/2020

    • Fixes #84 - pre-defined present within N seconds is limited by system timeout (thanks @doubleo2)
    • Added build mode to enable profile build and performance profiling (thanks @lsuhov)
    • Updated to latest dart_gherkin library which fixes access to the default step timeout see #81
    Source code(tar.gz)
    Source code(zip)
  • 1.1.8+8(Aug 11, 2020)

    [1.1.8+8] - 11/08/2020

    • Added well know steps and a driver helper method to long press a widget (fixed issue and documentation)
    When I long press "controlKey" button
    
    When I long press "controlKey" icon for 1500 milliseconds
    
    Source code(tar.gz)
    Source code(zip)
  • 1.1.8+7(Aug 11, 2020)

    [1.1.8+7] - 11/08/2020

    • Added well know steps and a driver helper method to long press a widget
    When I long press "controlKey" button
    
    When I long press "controlKey" icon for 1500 milliseconds
    
    Source code(tar.gz)
    Source code(zip)
  • 1.1.8+6(Aug 4, 2020)

    [1.1.8+6] - 05/08/2020

    • Upgraded to latest Gherkin library version which fixes issues with non-alpha-numeric characters in multiline strings and comments https://github.com/jonsamwell/dart_gherkin/issues/14 https://github.com/jonsamwell/dart_gherkin/issues/15 https://github.com/jonsamwell/dart_gherkin/issues/16
    Source code(tar.gz)
    Source code(zip)
  • 1.1.8+5(Aug 3, 2020)

  • 1.1.8+4(Jul 26, 2020)

  • 1.1.8+3(Jul 19, 2020)

    • Updated Gherkin library version to allow for function step implementations; updated docs to match.
    • Add steps SiblingContainsText, SwipeOnKey, SwipeOnText, TapTextWithinWidget, TapWidgetOfType, TapWidgetOfTypeWithin, TapWidgetWithText, TextExists, TextExistsWithin, WaitUntilKeyExists, and WaitUntilTypeExists . Thanks to @tshedor for the PR!
    Source code(tar.gz)
    Source code(zip)
  • 1.1.8+2(May 11, 2020)

    [1.1.8+2] - 11/05/2020

    • Fixed issue where the connection attempt of Flutter driver would not retry before throwing a connection error. This was causing an error on some machines trying to connect to an Android emulator (x86 & x86_64) that runs the googleapis (see https://github.com/flutter/flutter/issues/42433)
    • Added a before onBeforeFlutterDriverConnect and after onAfterFlutterDriverConnect Flutter driver connection method property to the test configuration FlutterTestConfiguration to enable custom logic before and after a driver connection attempt.
    Source code(tar.gz)
    Source code(zip)
  • 1.1.8+1(May 10, 2020)

    [1.1.8+1] - 10/05/2020

    • Updated Gherkin library version to sort issue with JSON reporter throwing error when an exception is logged before any feature have run
    Source code(tar.gz)
    Source code(zip)
  • 1.1.8(May 7, 2020)

    [1.1.8] - 08/05/2020

    • Updated library to work with the new way the Flutter stable branch manages logging for Flutter driver
    • Added the ability to test against an already running app; enabling you to debug a running application while it has tests executed against it. Setting the configuration property runningAppProtocolEndpointUri to the service protocol endpoint (found in stdout when an app has --verbose logging turned on) will ensure that the existing app is connected to rather than starting a new instance of the app. NOTE: ensure the app you are trying to connect to calls enableFlutterDriverExtension() when it starts up otherwise the Flutter Driver will not be able to connect to it.
    • BREAKING CHANGE Fixed spelling mistake of targetAppWorkingDirectory & flutterDriverMaxConnectionAttempts in FlutterTestConfiguration
    • BREAKING CHANGE reverse order of driver and finder in FlutterDriverUtils#isPresent. This makes this method's arguments more consistent with all other instance methods in the class by including driver first.
    • expect the presence of ThenExpectWidgetToBePresent. If the widget was not present, the method would simply timeout and not report an error for the step.
    Source code(tar.gz)
    Source code(zip)
  • 1.1.8-rc.2(Mar 18, 2020)

  • v1.1.7+6(Mar 5, 2020)

    • Updated to latest Gherkin library (see https://github.com/jonsamwell/dart_gherkin/blob/master/CHANGELOG.md#117---04032020) - this includes a breaking change to the Hook interface that will need to be updated if any of the Scenario level methods are implemented
    • Ensured the well known step I tap the ".." button scroll the element into view first
    Source code(tar.gz)
    Source code(zip)
  • v1.1.7+5(Mar 5, 2020)

    Updated to latest Gherkin library (see https://github.com/jonsamwell/dart_gherkin/blob/master/CHANGELOG.md#1164---03022020)

    • Fixed issue with empty cells in scenario table parameters
    • Fixed issue with a leading comment in feature files
    Source code(tar.gz)
    Source code(zip)
Owner
Jon Samwell
Full-stack developer (frontend & Flutter specialist) with key interests in user experience, security, and end-to-end testing
Jon Samwell
A Very Good Dart CLI Template created by the Very Good Ventures Team 🦄

Very Good Dart CLI Developed with ?? by Very Good Ventures ?? A Dart CLI template created by the Very Good Ventures Team. Generated by the Very Good C

Very Good Open Source 26 Dec 15, 2022
A simple screen that is shown when your app gets crashed instead of the normal crash dialog. It's very similar to the one in Flutter.

Red Screen Of Death What A simple screen that is shown when your app gets crashed instead of the normal crash dialog. It's very similar to the one in

Ahmad Melegy 178 Dec 9, 2022
A Very Good Flutter Federated Plugin created by the Very Good Ventures Team 🦄

Very Good Flutter Plugin Developed with ?? by Very Good Ventures ?? A Very Good Flutter Plugin created by the Very Good Ventures Team. Getting Started

Very Good Open Source 14 Oct 19, 2022
A Very Good Flutter Package Template created by the Very Good Ventures Team 🦄

Very Good Flutter Package Developed with ?? by Very Good Ventures ?? A Very Good Flutter package created by Very Good Ventures ?? . Getting Started ??

Very Good Open Source 32 Dec 13, 2022
Win32 runner - Run a Flutter app without needing a lick of C/C++ code. Just Dart

Experimental package for running Flutter apps from a Dart runner, instead of the

Tim Sneath 51 Sep 25, 2022
Let's deliver Flutter app using Fastlane and self-hosted runner.

Flutter CICD - Let's deliver app using Fastlane and Github selfhosted runner About This project shows how to create a minimal configuration for buildi

Codigee 3 Nov 17, 2022
A new flutter package project which contains lots of beautiful alert dialog that will help you lot to create beautiful awesome alert box very quickly and easily.

A new flutter package project which contains lots of beautiful alert dialog that will help you lot to create beautiful awesome alert box very quickly and easily.

Karan Soni 8 Jan 8, 2022
Find The Latest trending and upcoming movies and tv shows with MovieDB app. The app contains all info about movies and tv shows. find similar movies or shows, Browse all genres, video trailers, backdrops, logos, and posters.

MovieDB App Features. Dynamic Theming Search Functionality Onboarding-Screen Select favourite movie Home Screen Tranding movie Movies different catego

Ansh rathod 80 Dec 12, 2022
A Flutter widget to show an icon collection to pick. This widget extend TextField and has a similar behavior as TextFormField

icon_picker A Flutter widget to show an icon collection to pick. This widget extend TextField and has a similar behavior as TextFormField Usage In the

m3uzz Soluções em TI 11 Sep 27, 2022
TheMathU Similarity Index App will accept a mathematical problem as user input and return a list of similar problems that have memorandums.

Technologies MathU Similarity Index - Segmentation Cult The MathU Similarity Index App accepts a mathematical problem as user input and returns a list

COS 301 - 2022 7 Nov 2, 2022
Simple WebRTC for flutter (similar to the simple-peer project)

Simple WebRTC. Wraps flutter_webrtc similar to simple-peer IMPORTANT: Right now this library only supports data channels (and not media). Contribution

Simon Bengtsson 6 Nov 24, 2022
(RPG maker) Create RPG-style or similar games more simply with Flame.

Bonfire Build RPG games and similar with the power of FlameEngine! Bonfire is ideal for building games from the following perspectives: Test our onlin

Rafael Almeida Barbosa 787 Jan 7, 2023
A simple package for working with multithreading, using an interface similar to Task in C#.

A simple package for working with multithreading, using an interface similar to Task in C#.

Gleb Batykov 11 Oct 24, 2022
Apply values per media breakpoints. Breakpoints are similar to the breakpoints used in bootstrap css framework.

Apply values per media breakpoints. Breakpoints are similar to the breakpoints used in bootstrap css framework.

Glenford Williams 4 Mar 26, 2021
A platform similar to iFood that makes it easier for the consumers to find a list of generators with solar plates system available for rent

iEnergy App A platform similar to iFood that makes it easier for the consumers to find a list of generators with solar plates system available for ren

André Diogo 1 Jun 7, 2022
A Very Flexible Widget that can Implement Material Sheets on all Directions, both modal and persistent, and consequently a Material Navigation Drawer

Flutter_MaterialSheetAndNavigationDrawer If this project helped you reduce developement time or you just want to help me continue making useful tools

Bryan Cancel 30 Dec 4, 2021
A very basic manga reader made using flutter and the Mangadex API

Fludex A very basic manga reader made using flutter and mangadex API. It uses the mangadex_library package. A few things to remember The app only supp

null 11 Oct 27, 2022