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 } } ```