Cucumber JUnit Platform Engine
==============================
Use the JUnit (6) Platform to execute Cucumber scenarios.
Add the `cucumber-junit-platform-engine` dependency to your `pom.xml` and use
the [`cucumber-bom`](../cucumber-bom/README.md) for dependency management:
```xml
io.cucumber
cucumber-junit-platform-engine
test
```
This will allow IntelliJ IDEA, Eclipse, Maven, Gradle, etc, to discover, select
and execute Cucumber scenarios.
## Running Cucumber
The JUnit Platform provides a single interface for tools and IDE's to discover,
select and execute tests from different test engines. Conceptually this looks
like this:
```mermaid
erDiagram
"IDE" ||--|{ "JUnit Platform" : "requests discovery and execution"
"Maven, Gradle, or SBT" ||--|{ "JUnit Platform" : "requests discovery and execution"
"Console Launcher" ||--|{ "JUnit Platform" : "requests discovery and execution"
"JUnit Platform" ||--|{ "Cucumber Test Engine": "forwards request"
"JUnit Platform" ||--|{ "Jupiter Test Engine": "forwards request"
"Cucumber Test Engine" ||--|{ "Feature Files": "discovers and executes"
"Jupiter Test Engine" ||--|{ "Test Classes": "discovers and executes"
```
In practice, integration is still limited so we discuss the solutions and issues
per platform below.
### Running Cucumber with Gradle
Gradle [supports the discovery of resource based tests](https://docs.gradle.org/current/userguide/java_testing.html#sec:non-class-based-testing).
A minimal setup might look like:
```kotlin
tasks.named("test") {
useJUnitPlatform()
testDefinitionDirs.from("src/test/features")
}
```
#### IDEA Workarounds
When running features through IDEA, the Cucumber CLI is used. The CLI looks for
configuration properties in `cucumber.properties` while JUnit looks for
`junit-platform.properties`. To avoid duplication you can use Gradle read the
`cucumber.properties` and pass these to the JUnit Platform through system
properties.
```kotlin
tasks.named("test") {
useJUnitPlatform {
System.getProperty("cucumber.features")?.let { includeEngines("cucumber") }
}
// Tell Cucumber where to find the feature files.
testDefinitionDirs.from("src/test/features")
// Use properties from cucumber.properties for consistent behavior between
// Gradle and the CLI (used by IDEA).
systemProperties("src/test/resources/cucumber.properties")
}
fun Test.systemProperties(path: String) {
with(file(path).inputStream()) {
val props = Properties();
props.load(this)
props.stringPropertyNames().forEach { systemProperty(it, props.getProperty(it)) }
}
}
```
### Running Cucumber with Maven Surefire or SBT
Maven Surefire and SBT do not yet support discovery of resource based tests
[maven-surefire/#2065](https://github.com/apache/maven-surefire/issues/2065), [stb-jupiter-interface/#142](https://github.com/sbt/sbt-jupiter-interface/issues/142)).
As a workaround, you can either use:
* the [JUnit Platform Suite Engine](https://docs.junit.org/current/advanced-topics/junit-platform-suite-engine.html);
* the [JUnit Platform Console Launcher](https://docs.junit.org/current/running-tests/console-launcher.html) or;
* the [Cucable](https://github.com/trivago/cucable-plugin) plugin for Maven.
#### Use the JUnit Platform Suite Engine
The JUnit Platform Suite Engine can be used to run Cucumber. See
[Suites with different configurations](#suites-with-different-configurations)
for a brief how to.
##### Maven Surefire workarounds
Because Surefire provide the results in a ` - `
format, only scenario names or example numbers are reported. This
can make for hard to read reports.
To improve the readability of the reports use the
`cucumber.junit-platform.naming-strategy` configuration parameter. This will
include the feature name, scenario name, example number, etc. in the report.
For `3.5.2` and below use:
```xml
org.apache.maven.plugins
maven-surefire-plugin
3.5.2
cucumber.junit-platform.naming-strategy=surefire
```
For `3.5.4` and above use:
```xml
org.apache.maven.plugins
maven-surefire-plugin
3.5.4
cucumber.junit-platform.naming-strategy=long
```
##### IDEA workarounds
When running features through IDEA, the Cucumber CLI is used. The CLI looks for
configuration properties in `cucumber.properties` while JUnit looks for
`junit-platform.properties`. To avoid duplication you can use the
`@ConfigurationParametersResource` annotation to include `cucumber.properties`
into a Suite.
```java
@Suite
@IncludeEngines("cucumber")
@SelectPackages("com.example")
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "com.example")
@ConfigurationParametersResource("cucumber.properties")
class RunCucumberTest {
}
```
##### SBT workarounds
The `sbt-jupiter-interface` assumes that all tests directly under a test engine
have a class source. This is not the case for Cucumber. By running Cucumber
indirectly through the JUnit Platform Suite Engine and disabling discovery when
run directly as a "root engine" this problem is avoided.
Add to `junit-platform.properties`:
```
cucumber.junit-platform.discovery.as-root-engine=false
```
#### Use the JUnit Console Launcher ###
You can integrate the JUnit Platform Console Launcher in your build by using
either the Maven Antrun plugin or the Gradle JavaExec task.
##### Use the Maven Antrun plugin ####
Add the following to your `pom.xml`:
```xml
....
org.junit.platform
junit-platform-console
${junit-platform.version}
test
org.apache.maven.plugins
maven-antrun-plugin
CLI-test
integration-test
run
```
### Running a single scenario or feature from the CLI
To select a single scenario or feature the `cucumber.features` property can be
used. Because this property will cause Cucumber to ignore any other selectors
from JUnit, it is prudent to execute only the Cucumber engine.
#### Maven
To select the scenario on line 10 of the `example.feature` file use:
```shell
mvn test -Dsurefire.includeJUnit5Engines=cucumber -Dcucumber.plugin=pretty -Dcucumber.features=path/to/example.feature:10
```
#### Gradle
First update `build.gradle.kts` to pass system properties to the test task.
```kotlin
tasks.named("test") {
useJUnitPlatform {
// When running an individual scenario, assume we only want to run
// Cucumber
System.getProperty("cucumber.features")?.let { includeEngines("cucumber") }
}
testDefinitionDirs.from("src/test/features")
// Pass selected system properties to Cucumber
System.getProperty("cucumber.features")?.let { systemProperty("cucumber.features", it) }
System.getProperty("cucumber.filter.tags")?.let { systemProperty("cucumber.filter.tags", it) }
System.getProperty("cucumber.filter.name")?.let { systemProperty("cucumber.filter.name", it) }
System.getProperty("cucumber.plugin")?.let { systemProperty("cucumber.plugin", it) }
}
```
Then to select the scenario on line 10 of the `example.feature` file use:
```shell
gradle test --rerun-tasks --info -Dcucumber.plugin=pretty -Dcucumber.features=path/to/example.feature:10
```
## Suites with different configurations
The JUnit Platform Suite Engine can be used to run Cucumber multiple times with
different configurations. Conceptually this looks like this:
```mermaid
erDiagram
"IDE" ||--|{ "JUnit Platform" : "requests discovery and execution"
"Maven or Gradle" ||--|{ "JUnit Platform" : "requests discovery and execution"
"Console Launcher" ||--|{ "JUnit Platform" : "requests discovery and execution"
"JUnit Platform" ||--|{ "Suite Test Engine": "forwards request"
"Suite Test Engine" ||--|{ "@Suite annotated class A" : "discovers and executes"
"Suite Test Engine" ||--|{ "@Suite annotated class B" : "discovers and executes"
"@Suite annotated class A" ||--|{ "JUnit Platform (A)" : "requests discovery and execution"
"@Suite annotated class B" ||--|{ "JUnit Platform (B)" : "requests discovery and execution"
"JUnit Platform (A)" ||--|{ "Cucumber Test Engine (A)": "forwards request"
"JUnit Platform (B)" ||--|{ "Cucumber Test Engine (B)": "forwards request"
"Cucumber Test Engine (A)" ||--|{ "Feature Files (A)": "discovers and executes"
"Cucumber Test Engine (B)" ||--|{ "Feature Files (B)": "discovers and executes"
```
To use, add the `junit-platform-suite` dependency and use
the [`junit-bom`](https://docs.junit.org/current/running-tests/build-support.html#maven) for dependency management:
```xml
org.junit.platform
junit-platform-suite
test
```
Then define suites as needed using the annotation from the
[`org.junit.platform.suite.api`](https://junit.org/junit5/docs/current/api/org.junit.platform.suite.api/org/junit/platform/suite/api/package-summary.html)
package:
```java
package com.example;
import org.junit.platform.suite.api.ConfigurationParameter;
import org.junit.platform.suite.api.IncludeEngines;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.Suite;
import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;
@Suite
@IncludeEngines("cucumber")
@SelectPackages("com.example")
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "com.example")
class RunCucumberTest {
}
```
## Parallel execution ##
By default, Cucumber runs tests sequentially in a single thread. Running tests
in parallel is available as an opt-in feature. To enable parallel execution, set
the `cucumber.execution.parallel.enabled` configuration parameter to `true`,
e.g., in `junit-platform.properties`.
To control properties such as the desired parallelism and maximum parallelism,
Cucumber supports JUnit 6 `ParallelExecutionConfigurationStrategy`. Cucumber
provides two implementations: `dynamic` and `fixed` that can be set through
`cucumber.execution.parallel.config.strategy`. You may also implement a `custom`
strategy.
* `dynamic`: Computes the desired parallelism as `` *
`cucumber.execution.parallel.config.dynamic.factor`.
* `fixed`: Set `cucumber.execution.parallel.config.fixed.parallelism` to the
desired parallelism and `cucumber.execution.parallel.config.fixed.max-pool-size`
to the maximum pool size of the underlying ForkJoin pool.
* `custom`: Specify a custom `ParallelExecutionConfigurationStrategy`
implementation through `cucumber.execution.parallel.config.custom.class`.
If no strategy is specified Cucumber will use the `dynamic` strategy with a
factor of `1`.
Note: While `.fixed.max-pool-size` effectively limits the maximum number of
concurrent threads, Cucumber does not guarantee that the number of concurrently
executing scenarios will not exceed this. See [junit5/#3108](https://github.com/junit-team/junit5/issues/3108)
for details.
### Exclusive Resources ###
To avoid flaky tests when multiple scenarios manipulate the same resource, tests
can be [synchronized][junit5-user-guide-synchronization] on that resource.
[junit5-user-guide-synchronization]: https://docs.junit.org/current/writing-tests/parallel-execution.html#synchronization
To synchronize a scenario on a specific resource, the scenario must be tagged
and this tag mapped to a lock for the specific resource. A resource is
identified by an arbitrary string and can be either locked with a
read-write-lock, or a read-lock.
For example, the following tags:
```gherkin
Feature: Exclusive resources
@reads-and-writes-system-properties
Scenario: first example
Given this reads and writes system properties
When it is executed
Then it will not be executed concurrently with the second example
@reads-system-properties
Scenario: second example
Given this reads system properties
When it is executed
Then it will not be executed concurrently with the first example
```
with this configuration:
```properties
cucumber.execution.exclusive-resources.reads-and-writes-system-properties.read-write=java.lang.System.properties
cucumber.execution.exclusive-resources.reads-system-properties.read=java.lang.System.properties
```
when executing the first scenario tagged with
`@reads-and-writes-system-properties` will lock the `java.lang.System.properties`
resource with a read-write lock and will not be concurrently executed with the
second scenario that locks the same resource with a read lock.
Note: The `@` from the tag is not included in the property name.
Note: For canonical resource names see [junit5/Resources.java][resources-java]
[resources-java]: https://github.com/junit-team/junit5/blob/main/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Resources.java
### Running tests in isolation
To ensure that a scenario runs while no other scenarios are running the global
resource [`org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY`][global-key]
can be used.
[global-key]: https://github.com/junit-team/junit5/blob/main/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ExclusiveResource.java#L47
```gherkin
Feature: Isolated scenarios
@isolated
Scenario: isolated example
Given this scenario runs isolated
When it is executed
Then it will not be executed concurrently with the second or third example
Scenario: second example
When it is executed
Then it will not be executed concurrently with the isolated example
And it will be executed concurrently with the third example
Scenario: third example
When it is executed
Then it will not be executed concurrently with the isolated example
And it will be executed concurrently with the second example
```
with this configuration:
```properties
cucumber.execution.exclusive-resources.isolated.read-write=org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY
```
### Executing features in parallel
By default, when parallel execution is enabled, scenarios and examples are
executed in parallel. Due to limitations, JUnit 4 could only execute features in
parallel. This behaviour can be restored by setting the configuration parameter
`cucumber.execution.execution-mode.feature` to `same_thread`.
## Configuration Options ##
Cucumber receives its configuration from the JUnit Platform. To see how these can be supplied; see the JUnit
documentation
[4.5. Configuration Parameters](https://docs.junit.org/current/running-tests/configuration-parameters.html). For
documentation on Cucumber properties, see [Constants](src/main/java/io/cucumber/junit/platform/engine/Constants.java).
```
cucumber.ansi-colors.disabled= # true or false.
# default: false
cucumber.filter.name= # a regular expression.
# only scenarios with matching names are executed.
# combined with cucumber.filter.tags using "and" semantics.
# example: ^Hello (World|Cucumber)$
# note: To ensure consistent reports between Cucumber and
# JUnit 6 prefer using JUnit 6s discovery request filters
# or JUnit 6 tag expressions instead.
cucumber.features= # comma separated paths to feature files.
# example: path/to/example.feature, path/to/other.feature
# note: When used any discovery selectors from the JUnit
# Platform will be ignored. This may lead to multiple
# executions of Cucumber. For example when used in
# combination with the JUnit Platform Suite Engine.
# When using Cucumber through the JUnit Platform
# Launcher API or the JUnit Platform Suite Engine, it is
# recommended to use JUnit's DiscoverySelectors or
# Junit Platform Suite annotations.
cucumber.filter.tags= # a cucumber tag expression.
# only scenarios with matching tags are executed.
# combined with cucumber.filter.name using "and" semantics.
# example: @Cucumber and not (@Gherkin or @Zucchini)
# note: To ensure consistent reports between Cucumber and
# JUnit 6 prefer using JUnit 6s discovery request filters
# or JUnit 6 tag expressions instead.
cucumber.glue= # comma separated package names.
# example: com.example.glue
cucumber.glue.classes= # comma separated class names.
# example: com.example.StepDefinitionsA, com.example.StepDefinitionsB
# note: classes that are explicitly included are not
# filtered by either the included or excluded class name
# patterns
cucumber.glue.included-class-name-pattern= # pattern for included glue classes
# example: .*StepDefinitions?|.*Hooks?
cucumber.glue.excluded-class-name-pattern= # pattern for excluded glue classes
# example: .*UnwantedStepDefinitions?|.*UnwantedHooks?
cucumber.glue.hint.enabled= # true or false
# default: true
# enable displaying glue hint in case of inneficient configuration.
cucumber.glue.hint.threshold= # threshold value as an ISO-8601 duration string
# default: PT0.1S
# if the expected gain is higher than this value, the glue hint is displayed.
cucumber.junit-platform.discovery.as-root-engine # true or false
# default: true
# enable discovery when used as a root engine.
# note: Workaround for SBT issues.
cucumber.junit-platform.naming-strategy= # long, short or surefire.
# default: short
# long: include parent descriptor names in test descriptor.
# surefire: Workaround to make test names appear nicely
# with Surefire < 3.5.3. For 3.5.4 and above use the long
# strategy.
cucumber.junit-platform.naming-strategy.short.example-name= # number, number-and-pickle-if-parameterized or pickle.
# default: number-and-pickle-if-parameterized
# Use example number and/or pickle name for examples when
# short naming strategy is used
cucumber.junit-platform.naming-strategy.long.example-name= # number, number-and-pickle-if-parameterized or pickle.
# default: number-and-pickle-if-parameterized
# Use example number and/or pickle name for examples when
# long naming strategy is used
cucumber.junit-platform.naming-strategy.surefire.example-name= # number or pickle.
# default: number-and-pickle-if-parameterized
# Use example number or pickle name for examples when
# surefire naming strategy is used
cucumber.plugin= # comma separated plugin strings.
# example: pretty, json:path/to/report.json
# example: com.example.MyCustomPlugin:path/to/report.xml
cucumber.uuid-generator # uuid generator class name of a registered service provider.
# default: io.cucumber.core.eventbus.RandomUuidGenerator
# example: com.example.MyUuidGenerator
cucumber.object-factory= # object factory class name.
# example: com.example.MyObjectFactory
cucumber.publish.enabled # true or false.
# default: false
# enable publishing of test results
cucumber.publish.quiet # true or false.
# default: false
# suppress publish banner after test execution.
cucumber.publish.token # any string value.
# publish authenticated test results.
cucumber.snippet-type= # underscore or camelcase.
# default: underscore
cucumber.execution.dry-run= # true or false.
# default: false
cucumber.execution.execution-mode.feature= # same_thread or concurrent
# default: concurrent
# same_thread - executes scenarios sequentially in the
# same thread as the parent feature
# concurrent - executes scenarios concurrently on any
# available thread
cucumber.execution.order= # lexical, reverse or random
# default: lexical
# lexical - executes features in lexical uri order, scenarios and examples from top to bottom
# reverse - as lexical, but with the elements of each container reversed
# random - executes scenarios and examples in a random order within their parent container
cucumber.execution.order.random.seed= # any long
# example: 20090120
# enables deterministic random execution
cucumber.execution.parallel.enabled= # true or false.
# default: false
cucumber.execution.parallel.config.strategy= # dynamic, fixed or custom.
# default: dynamic
cucumber.execution.parallel.config.fixed.parallelism= # positive integer.
# example: 4
cucumber.execution.parallel.config.fixed.max-pool-size= # positive integer.
# example: 4
cucumber.execution.parallel.config.dynamic.factor= # positive double.
# default: 1.0
cucumber.execution.parallel.config.custom.class= # class name.
# example: com.example.MyCustomParallelStrategy
cucumber.execution.parallel.config.executor-service # FORK_JOIN_POOL, WORKER_THREAD_POOL
# default: depends on JUnit version
cucumber.execution.exclusive-resources..read-write= # a comma separated list of strings
# example: resource-a, resource-b.
cucumber.execution.exclusive-resources..read= # a comma separated list of strings
# example: resource-a, resource-b
```
## Supported Discovery Selectors and Filters ##
The JUnit Platform [introduced a test discovery mechanism](https://docs.junit.org/current/advanced-topics/launcher-api.html)
as a dedicated feature of the platform itself. This allows IDEs and build tools
to identify tests. Supported `DiscoverySelector`s are:
* `ClasspathRootSelector`
* `ClasspathResourceSelector`
* `ClassSelector`
* `PackageSelector`
* `FileSelector`
* `DirectorySelector`
* `UriSelector`
* `UniqueIdSelector`
The only supported `DiscoveryFilter` is the `PackageNameFilter` and only when
features are selected from the classpath.
### Selecting individual scenarios, rules and examples ###
The `FileSelector` and `ClasspathResourceSelector` support a `FilePosition`.
* `DiscoverySelectors.selectClasspathResource("rule.feature", FilePosition.from(5))`
* `DiscoverySelectors.selectFile("rule.feature", FilePosition.from(5))`
The `UriSelector` supports URI's with a `line` query parameter:
- `classpath:/com/example/example.feature?line=20`
- `file:/path/to/com/example/example.feature?line=20`
Any `TestDescriptor` that matches the line *and* its descendants will be included in the discovery result. For example,
selecting a `Rule` will execute all scenarios contained within the Rule.
## Tags ##
Cucumber tags are mapped to JUnit tags. Note that the `@` symbol is not part of
the JUnit tag. So the scenarios below are tagged with `Smoke` and `Sanity`.
```gherkin
@Smoke
@Ignore
Scenario: A tagged scenario
Given I tag a scenario
When I select tests with that tag for execution
Then my tagged scenario is executed
@Sanity
Scenario: Another tagged scenario
Given I tag a scenario
When I select tests with that tag for execution
Then my tagged scenario is executed
```
When using Maven, tags can be provided from the CLI using the `groups` and `excludedGroups` parameters. These take a
[JUnit5 Tag Expression](https://docs.junit.org/current/running-tests/tags.html#expressions). The example
below will execute `Another tagged scenario`.
```
mvn verify -DexcludedGroups="Ignore" -Dgroups="Smoke | Sanity"
```
For more information on how to select tags, see the relevant documentation:
* [JUnit 6 Suite: @Include Tags](https://docs.junit.org/current/api/org.junit.platform.suite.api/org/junit/platform/suite/api/IncludeTags.html)
* [JUnit 6 Suite: @Exclude Tags](https://docs.junit.org/current/api/org.junit.platform.suite.api/org/junit/platform/suite/api/ExcludeTags.html)
* [JUnit 6 Console Launcher: Options](https://docs.junit.org/current/running-tests/console-launcher.html#options)
* [JUnit 6 Tag Expression](https://docs.junit.org/current/running-tests/tags.html#expressions)
* [Maven: Filtering by Tags](https://maven.apache.org/surefire/maven-surefire-plugin/examples/junit-platform.html)
* [Gradle: Test Grouping](https://docs.gradle.org/current/userguide/java_testing.html#test_grouping)
### @Disabled
It is possible to recreate JUnit Jupiter's `@Disabled` functionality by
setting the `cucumber.filter.tags=not @Disabled` property1. Any scenarios
tagged with `@Disabled` will be skipped. See [Configuration Options](#configuration-options)
for more information.
1. Do note that this is a [Cucumber Tag Expression](https://cucumber.io/docs/cucumber/api/#tags) rather than a JUnit5
tag expression.
## Aborting Tests
Cucumber supports [OpenTest4Js](https://github.com/ota4j-team/opentest4j)
`TestAbortedException`. This makes it possible to use JUnit Jupiter's
`Assumptions` to abort rather than fail a scenario.
```java
package com.example;
import io.cucumber.java.Before;
import org.junit.jupiter.api.Assumptions;
import java.util.List;
class RpnCalculatorSteps {
@Before
void before() {
boolean condition = // decide if tests should abort
Assumptions.assumeTrue(condition, "Condition not met");
}
}
```
## Rerunning Failed Scenarios ##
Failed scenarios can be rerun with either a rerun file, Maven, Gradle or the
JUnit Platform Launcher API.
### Using a Rerun file
The JUnit Platform Engine supports rerun files. Rerun files must have the
`*.txt` suffix. To create a rerun file, enable the `rerun` plugin:
```java
@Suite
@IncludeEngines("cucumber")
@SelectPackages("com.example")
// Writes the failed tests to rerun.txt
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "rerun:target/rerun.txt")
class RunCucumber {
}
```
After the `RunCucumberTest` has executed and produced a rerun file, this file
can be selected for execution:
```java
@Suite(failIfNoTests = false) // Allows the suite have no tests to rerun if all tests in RunCucumber passed
@IncludeEngines("cucumber")
@SelectFile("target/rerun.txt") // Selects the rerun file, must end with .txt
class RerunRunCucumber {
}
```
Because the JUnit platform creates a test plan before any tests are executed,
the `RunCucumber` and `RerunRunCucumber` must be in separate test executions.
If they are in the same execution, `RerunRunCucumber` will not find any tests.
With Maven Surefire you could configure multiple executions as follows:
```xml
org.apache.maven.plugins
maven-surefire-plugin
...
run-cucumber
test
test
**/RunCucumber.java
true
rerun-cucumber
test
test
**/RerunCucumber.java
```
### Using Maven
When running Cucumber through the [JUnit Platform Suite Engine](use-the-jUnit-platform-suite-engine)
use [`rerunFailingTestsCount`](https://maven.apache.org/surefire/maven-surefire-plugin/examples/rerun-failing-tests.html).
Note: any files written by Cucumber will be overwritten during the rerun.
```xml
org.apache.maven.plugins
maven-surefire-plugin
3.5.4
2
cucumber.junit-platform.naming-strategy=long
```
### Using Gradle.
Gradle support for JUnit 6 is rather limited
[gradle#4773](https://github.com/gradle/gradle/issues/4773),
[junit5#2849](https://github.com/junit-team/junit5/issues/2849).
As a workaround you can the [Gradle Cucumber-Companion](https://github.com/gradle/cucumber-companion)
plugin in combination with [Gradle Test Retry](https://github.com/gradle/test-retry-gradle-plugin)
plugin.
Note: any files written by Cucumber will be overwritten while retrying.
### Using the JUnit Platform Launcher API
The [JUnit Platform Launcher API](https://docs.junit.org/current/advanced-topics/launcher-api.html) provides a method to programmatically run and
re-run tests. For example:
```java
package com.example;
import org.junit.platform.engine.discovery.DiscoverySelectors;
import org.junit.platform.engine.discovery.UniqueIdSelector;
import org.junit.platform.launcher.Launcher;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
import org.junit.platform.launcher.TestIdentifier;
import org.junit.platform.launcher.core.LauncherFactory;
import org.junit.platform.launcher.listeners.SummaryGeneratingListener;
import org.junit.platform.launcher.listeners.TestExecutionSummary;
import org.junit.platform.launcher.listeners.TestExecutionSummary.Failure;
import java.util.List;
import java.util.stream.Collectors;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectDirectory;
import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request;
public class RunCucumber {
public static void main(String[] args) {
LauncherDiscoveryRequest request = request()
.selectors(
selectDirectory("path/to/features")
)
.build();
Launcher launcher = LauncherFactory.create();
SummaryGeneratingListener listener = new SummaryGeneratingListener();
launcher.registerTestExecutionListeners(listener);
launcher.execute(request);
TestExecutionSummary summary = listener.getSummary();
// Do something with summary
List failures = summary.getFailures().stream()
.map(Failure::getTestIdentifier)
.filter(TestIdentifier::isTest)
.map(TestIdentifier::getUniqueId)
.map(DiscoverySelectors::selectUniqueId)
.collect(Collectors.toList());
LauncherDiscoveryRequest rerunRequest = request()
.selectors(failures)
.build();
launcher.execute(rerunRequest);
TestExecutionSummary rerunSummary = listener.getSummary();
// Do something with rerunSummary
}
}
```