diff --git a/pom.xml b/pom.xml
index be5f279d12..e4d85ca92d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -48,6 +48,7 @@
2.2
2.0.16
7.20.1
+ 6.1.1
2.11.0
5.11.1
3.3.3
diff --git a/serenity-cucumber/pom.xml b/serenity-cucumber/pom.xml
index f6f101c44e..a0734ba3b1 100644
--- a/serenity-cucumber/pom.xml
+++ b/serenity-cucumber/pom.xml
@@ -212,5 +212,11 @@
${spring.version}
test
+
+ net.serenity-bdd
+ serenity-junit5
+ ${project.version}
+ compile
+
diff --git a/serenity-cucumber/src/main/java/io/cucumber/junit/CucumberSerenityBaseRunner.java b/serenity-cucumber/src/main/java/io/cucumber/junit/CucumberSerenityBaseRunner.java
index eafe9aa5d2..1a9d604499 100644
--- a/serenity-cucumber/src/main/java/io/cucumber/junit/CucumberSerenityBaseRunner.java
+++ b/serenity-cucumber/src/main/java/io/cucumber/junit/CucumberSerenityBaseRunner.java
@@ -1,14 +1,5 @@
package io.cucumber.junit;
-import static io.cucumber.core.runtime.SynchronizedEventBus.synchronize;
-import static io.cucumber.junit.FileNameCompatibleNames.uniqueSuffix;
-import static java.util.Arrays.stream;
-import static java.util.stream.Collectors.groupingBy;
-import static java.util.stream.Collectors.toList;
-import static net.thucydides.model.ThucydidesSystemProperty.SERENITY_BATCH_COUNT;
-import static net.thucydides.model.ThucydidesSystemProperty.SERENITY_BATCH_NUMBER;
-import static net.thucydides.model.ThucydidesSystemProperty.SERENITY_FORK_COUNT;
-import static net.thucydides.model.ThucydidesSystemProperty.SERENITY_FORK_NUMBER;
import io.cucumber.core.eventbus.EventBus;
import io.cucumber.core.feature.FeatureParser;
import io.cucumber.core.filter.Filters;
@@ -20,25 +11,17 @@
import io.cucumber.core.resource.ClassLoaders;
import io.cucumber.core.runtime.*;
import io.cucumber.plugin.Plugin;
-import io.cucumber.tagexpressions.Expression;
-import java.net.URI;
-import java.time.Clock;
-import java.util.*;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Function;
-import java.util.function.Predicate;
-import java.util.function.Supplier;
-import net.serenitybdd.cucumber.SerenityOptions;
import net.serenitybdd.cucumber.suiteslicing.CucumberSuiteSlicer;
import net.serenitybdd.cucumber.suiteslicing.ScenarioFilter;
import net.serenitybdd.cucumber.suiteslicing.TestStatistics;
import net.serenitybdd.cucumber.suiteslicing.WeightedCucumberScenarios;
+import io.cucumber.tagexpressions.Expression;
+import net.serenitybdd.cucumber.SerenityOptions;
import net.serenitybdd.cucumber.util.PathUtils;
import net.serenitybdd.cucumber.util.Splitter;
+import net.thucydides.core.steps.StepEventBus;
import net.thucydides.model.ThucydidesSystemProperty;
import net.thucydides.model.environment.SystemEnvironmentVariables;
-import net.thucydides.core.steps.StepEventBus;
-import net.thucydides.model.requirements.reports.MultipleSourceRequirmentsOutcomeFactory;
import net.thucydides.model.util.EnvironmentVariables;
import org.junit.runner.Description;
import org.junit.runner.manipulation.NoTestsRemainException;
@@ -50,6 +33,21 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.net.URI;
+import java.time.Clock;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+import static io.cucumber.core.runtime.SynchronizedEventBus.synchronize;
+import static io.cucumber.junit.FileNameCompatibleNames.uniqueSuffix;
+import static java.util.Arrays.stream;
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.toList;
+import static net.thucydides.model.ThucydidesSystemProperty.*;
+
public class CucumberSerenityBaseRunner extends ParentRunner> {
private static final Logger LOGGER = LoggerFactory.getLogger(CucumberSerenityBaseRunner.class);
diff --git a/serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/SlicedTestRunner.java b/serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/SlicedTestRunner.java
deleted file mode 100644
index 14ac479b76..0000000000
--- a/serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/SlicedTestRunner.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package net.serenitybdd.cucumber.suiteslicing;
-
-import io.cucumber.junit.CucumberOptions;
-import net.serenitybdd.cucumber.CucumberWithSerenity;
-import org.junit.Ignore;
-import org.junit.runner.RunWith;
-
-
-
-@Ignore
-@RunWith(CucumberWithSerenity.class)
-@CucumberOptions(glue = "net.serenitybdd.cucumber.smoketests", features="classpath:smoketests")
-public class SlicedTestRunner {
-
-/*
-
-Experimental test runner where parameters can changed in order to run specific portions of the test suite. For instance create a run configuration and paste the following into the VM Options:
-
--Dserenity.batch.count=3 -Dserenity.batch.number=2 -Dserenity.fork.number=1 -Dserenity.fork.count=2 -Dserenity.test.statistics.dir=/statistics
-
-And you should see the following logged in the console:
-
-16:49:18.551 [main] INFO n.s.cucumber.CucumberWithSerenity - Running slice 2 of 3 using fork 1 of 2 from feature paths [classpath:smoketests]
-
-The Test output should show some features selected and some scenarios run and some not run. This is expected!
-
-*/
-
-}
diff --git a/serenity-junit5/pom.xml b/serenity-junit5/pom.xml
index 940e9d1227..2e92e61b53 100644
--- a/serenity-junit5/pom.xml
+++ b/serenity-junit5/pom.xml
@@ -39,8 +39,18 @@
${project.version}
compile
-
+
+ io.cucumber
+ cucumber-junit-platform-engine
+ compile
+
+
+ io.cucumber
+ tag-expressions
+ ${cucumber.tag-expressions.version}
+ compile
+
org.junit.jupiter
junit-jupiter-api
@@ -82,7 +92,7 @@
junit
junit
- test
+ compile
org.springframework
@@ -108,5 +118,16 @@
${assertj.version}
test
+
+ org.apache.commons
+ commons-csv
+ ${commons.csv.version}
+ compile
+
+
+ org.junit.platform
+ junit-platform-suite-api
+ test
+
diff --git a/serenity-cucumber/src/main/java/io/cucumber/gherkin/CucumberScenarioLoader.java b/serenity-junit5/src/main/java/io/cucumber/gherkin/CucumberScenarioLoader.java
similarity index 92%
rename from serenity-cucumber/src/main/java/io/cucumber/gherkin/CucumberScenarioLoader.java
rename to serenity-junit5/src/main/java/io/cucumber/gherkin/CucumberScenarioLoader.java
index 04184675b7..54f5f6b494 100644
--- a/serenity-cucumber/src/main/java/io/cucumber/gherkin/CucumberScenarioLoader.java
+++ b/serenity-junit5/src/main/java/io/cucumber/gherkin/CucumberScenarioLoader.java
@@ -5,10 +5,10 @@
import io.cucumber.core.feature.Options;
import io.cucumber.core.runtime.FeaturePathFeatureSupplier;
import io.cucumber.messages.types.*;
+import net.serenitybdd.cucumber.utils.PathUtils;
import net.serenitybdd.cucumber.suiteslicing.TestStatistics;
import net.serenitybdd.cucumber.suiteslicing.WeightedCucumberScenario;
import net.serenitybdd.cucumber.suiteslicing.WeightedCucumberScenarios;
-import net.serenitybdd.cucumber.util.PathUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -85,12 +85,12 @@ private Function> getScenarios() {
.filter(child -> child.getScenario() != null && child.getScenario().isPresent())
.map(FeatureChild::getScenario)
.map(scenarioDefinition -> new WeightedCucumberScenario(
- PathUtils.getAsFile(mapsForFeatures.get(cucumberFeature)).getName(),
- cucumberFeature.getName(),
- scenarioDefinition.get().getName(),
- scenarioWeightFor(cucumberFeature, scenarioDefinition.get()),
- tagsFor(cucumberFeature, scenarioDefinition.get()),
- scenarioCountFor(scenarioDefinition.get())))
+ PathUtils.getAsFile(mapsForFeatures.get(cucumberFeature)).getName(),
+ cucumberFeature.getName(),
+ scenarioDefinition.get().getName(),
+ scenarioWeightFor(cucumberFeature, scenarioDefinition.get()),
+ tagsFor(cucumberFeature, scenarioDefinition.get()),
+ scenarioCountFor(scenarioDefinition.get())))
.collect(toList());
} catch (Throwable e) {
throw new IllegalStateException(String.format("Could not extract scenarios from %s", mapsForFeatures.get(cucumberFeature)), e);
diff --git a/serenity-cucumber/src/main/java/io/cucumber/gherkin/IncrementingIdGenerator.java b/serenity-junit5/src/main/java/io/cucumber/gherkin/IncrementingIdGenerator.java
similarity index 100%
rename from serenity-cucumber/src/main/java/io/cucumber/gherkin/IncrementingIdGenerator.java
rename to serenity-junit5/src/main/java/io/cucumber/gherkin/IncrementingIdGenerator.java
diff --git a/serenity-cucumber/src/main/java/io/cucumber/gherkin/ScenarioLineCountStatistics.java b/serenity-junit5/src/main/java/io/cucumber/gherkin/ScenarioLineCountStatistics.java
similarity index 100%
rename from serenity-cucumber/src/main/java/io/cucumber/gherkin/ScenarioLineCountStatistics.java
rename to serenity-junit5/src/main/java/io/cucumber/gherkin/ScenarioLineCountStatistics.java
diff --git a/serenity-junit5/src/main/java/io/cucumber/junit/platform/engine/CucumberBatchTestEngine.java b/serenity-junit5/src/main/java/io/cucumber/junit/platform/engine/CucumberBatchTestEngine.java
new file mode 100644
index 0000000000..5c9fd35954
--- /dev/null
+++ b/serenity-junit5/src/main/java/io/cucumber/junit/platform/engine/CucumberBatchTestEngine.java
@@ -0,0 +1,154 @@
+package io.cucumber.junit.platform.engine;
+
+import net.thucydides.model.environment.SystemEnvironmentVariables;
+import net.thucydides.model.util.EnvironmentVariables;
+
+import org.junit.jupiter.engine.config.DefaultJupiterConfiguration;
+import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor;
+import org.junit.jupiter.engine.discovery.DiscoverySelectorResolver;
+import org.junit.platform.engine.ConfigurationParameters;
+import org.junit.platform.engine.EngineDiscoveryRequest;
+import org.junit.platform.engine.ExecutionRequest;
+import org.junit.platform.engine.TestDescriptor;
+import org.junit.platform.engine.UniqueId;
+import org.junit.platform.engine.support.config.PrefixedConfigurationParameters;
+import org.junit.platform.engine.support.hierarchical.ForkJoinPoolHierarchicalTestExecutorService;
+import org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine;
+import org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import static io.cucumber.core.options.Constants.FILTER_TAGS_PROPERTY_NAME;
+import static io.cucumber.junit.platform.engine.Constants.PARALLEL_CONFIG_PREFIX;
+import static io.cucumber.junit.platform.engine.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME;
+import static net.thucydides.model.ThucydidesSystemProperty.SERENITY_BATCH_COUNT;
+import static net.thucydides.model.ThucydidesSystemProperty.SERENITY_BATCH_NUMBER;
+import static net.thucydides.model.ThucydidesSystemProperty.SERENITY_FORK_COUNT;
+import static net.thucydides.model.ThucydidesSystemProperty.SERENITY_FORK_NUMBER;
+
+
+public final class CucumberBatchTestEngine extends HierarchicalTestEngine {
+
+ static final Logger LOGGER = LoggerFactory.getLogger(CucumberBatchTestEngine.class);
+
+ @Override
+ public String getId() {
+ return "cucumber-batch";
+ }
+
+ @Override
+ public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) {
+ CucumberEngineDescriptor engineDescriptor = new CucumberEngineDescriptor(uniqueId);
+ DefaultJupiterConfiguration jupiterConfiguration = new DefaultJupiterConfiguration(null);
+ JupiterEngineDescriptor dd = new JupiterEngineDescriptor(uniqueId, jupiterConfiguration);
+ new DiscoverySelectorResolver().resolveSelectors(discoveryRequest, dd);
+ return engineDescriptor;
+ }
+
+ @Override
+ protected HierarchicalTestExecutorService createExecutorService(ExecutionRequest request) {
+ ConfigurationParameters config = request.getConfigurationParameters();
+ if (config.getBoolean(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME).orElse(false)) {
+ return new ForkJoinPoolHierarchicalTestExecutorService(
+ new PrefixedConfigurationParameters(config, PARALLEL_CONFIG_PREFIX));
+ }
+
+ if (!request.getRootTestDescriptor().getChildren().isEmpty()) {
+ processRequestIfBatched(request);
+ }
+
+ return super.createExecutorService(request);
+ }
+
+ static void processRequestIfBatched(ExecutionRequest request) {
+ //populate list
+ String tagFilter = request.getConfigurationParameters().get(FILTER_TAGS_PROPERTY_NAME)
+ .orElse(System.getProperty(FILTER_TAGS_PROPERTY_NAME));
+ List scenarioList = request.getRootTestDescriptor().getChildren().stream()
+ .map(TestDescriptor::getChildren)
+ .flatMap(Set::stream)
+ .map(WeightedTest::new)
+ .collect(Collectors.toList());
+ int total = scenarioList.size();
+ List tagFilteredScenarioList = scenarioList.stream()
+ .filter(scenario -> scenario.isTagMatchingFilter(tagFilter))
+ .collect(Collectors.toList());
+ LOGGER.info("Found {} scenarios in classpath, {} match(es) tag filter {}", total, tagFilteredScenarioList.size(), tagFilter);
+
+ EnvironmentVariables envs = SystemEnvironmentVariables.currentEnvironmentVariables();
+ int batchCount = envs.getPropertyAsInteger(SERENITY_BATCH_COUNT, 1);
+ int batchNumber = envs.getPropertyAsInteger(SERENITY_BATCH_NUMBER, 1);
+ int forkCount = envs.getPropertyAsInteger(SERENITY_FORK_COUNT, 1);
+ int forkNumber = envs.getPropertyAsInteger(SERENITY_FORK_NUMBER, 1);
+
+ LOGGER.info("Parameters: \n{}", request.getConfigurationParameters());
+ LOGGER.info("Running partitioning for batch {} of {} and fork {} of {}", batchNumber,
+ batchCount, forkNumber, forkCount);
+
+ List batch = getPartition(tagFilteredScenarioList, batchCount, batchNumber);
+ List testToRun = getPartition(batch, forkCount, forkNumber);
+
+ //prune and keep only test to run
+ scenarioList.removeAll(testToRun);
+ scenarioList.forEach(WeightedTest::removeFromHierarchy);
+
+ LOGGER.info("Running {} of {} scenarios", testToRun.size(), total);
+ LOGGER.info("Test to run: {}", testToRun);
+ LOGGER.info("Root test descriptor has {} feature(s)",
+ request.getRootTestDescriptor().getChildren().size());
+ }
+
+ @Override
+ protected CucumberEngineExecutionContext createExecutionContext(ExecutionRequest request) {
+ return new CucumberEngineExecutionContext(request.getConfigurationParameters());
+ }
+
+ static List getPartition(List list, int partitions, int index) {
+ if (partitions == 1 && index == 1) {
+ return new ArrayList<>(list);
+ }
+ return getPartitionedTests(list, partitions).get(index - 1);
+ }
+
+ static List> getPartitionedTests(List list, int partitions) {
+ List> result = Stream.generate(ArrayList::new)
+ .limit(partitions)
+ .collect(Collectors.toList());
+
+ //sort all scenarios from large to small
+ list.sort(Comparator.comparing(WeightedTest::getWeight).reversed());
+ int[] weights = new int[partitions];
+
+ for (WeightedTest test : list) {
+ int minPartition = getMinPartition(weights);
+ result.get(minPartition).add(test);
+ weights[minPartition] += test.getWeight();
+ }
+
+ for (int i = 0; i < result.size(); i++) {
+ LOGGER.info("{} of {}, weight = {}", i + 1, partitions,
+ result.get(i).stream().mapToInt(WeightedTest::getWeight).sum());
+ LOGGER.info(print(result.get(i)));
+ }
+ return result;
+ }
+
+ private static String print(List list) {
+ return list.stream().map(WeightedTest::toString).collect(Collectors.joining("\n"));
+ }
+
+ private static int getMinPartition(int[] weights) {
+ return IntStream.range(0, weights.length)
+ .boxed()
+ .min(Comparator.comparingInt(i -> weights[i]))
+ .orElse(-1);
+ }
+}
diff --git a/serenity-junit5/src/main/java/io/cucumber/junit/platform/engine/TestWeightCalculator.java b/serenity-junit5/src/main/java/io/cucumber/junit/platform/engine/TestWeightCalculator.java
new file mode 100644
index 0000000000..a13114c166
--- /dev/null
+++ b/serenity-junit5/src/main/java/io/cucumber/junit/platform/engine/TestWeightCalculator.java
@@ -0,0 +1,36 @@
+package io.cucumber.junit.platform.engine;
+
+import java.math.BigDecimal;
+import java.net.URI;
+import java.util.List;
+import net.thucydides.model.environment.SystemEnvironmentVariables;
+import org.junit.platform.engine.TestDescriptor;
+import org.junit.platform.engine.support.descriptor.ClasspathResourceSource;
+
+import net.serenitybdd.cucumber.suiteslicing.TestStatistics;
+
+
+class TestWeightCalculator {
+
+ private static TestStatistics statistics;
+
+ static int calculateWeight(TestDescriptor descriptor) {
+ return getEstimatedTestDuration(descriptor).intValue();
+ }
+
+ private static BigDecimal getEstimatedTestDuration(TestDescriptor descriptor) {
+ if (statistics == null) {
+ statistics = TestStatistics.from(SystemEnvironmentVariables.currentEnvironmentVariables(),
+ List.of(URI.create("classpath:" + getTopFeatureDirectory(descriptor))));
+ }
+ String featureName = descriptor.getParent().map(TestDescriptor::getDisplayName).orElseThrow();
+ String scenarioName = descriptor.getDisplayName();
+ return statistics.scenarioWeightFor(featureName, scenarioName);
+ }
+
+ private static String getTopFeatureDirectory(TestDescriptor descriptor) {
+ ClasspathResourceSource resource = (ClasspathResourceSource) descriptor.getSource().orElseThrow();
+ return resource.getClasspathResourceName().split("/")[0];
+ }
+}
+
diff --git a/serenity-junit5/src/main/java/io/cucumber/junit/platform/engine/WeightedTest.java b/serenity-junit5/src/main/java/io/cucumber/junit/platform/engine/WeightedTest.java
new file mode 100644
index 0000000000..ddcf18e087
--- /dev/null
+++ b/serenity-junit5/src/main/java/io/cucumber/junit/platform/engine/WeightedTest.java
@@ -0,0 +1,84 @@
+package io.cucumber.junit.platform.engine;
+
+import io.cucumber.tagexpressions.Expression;
+import io.cucumber.tagexpressions.TagExpressionParser;
+import org.junit.platform.engine.TestDescriptor;
+import org.junit.platform.engine.support.descriptor.ClasspathResourceSource;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static io.cucumber.junit.platform.engine.TestWeightCalculator.calculateWeight;
+
+public class WeightedTest {
+ private final TestDescriptor scenario;
+ private final int weight;
+ private final List tags;
+
+ WeightedTest(TestDescriptor scenario) {
+ this(scenario, calculateWeight(scenario));
+ }
+
+ public WeightedTest(TestDescriptor scenario, int weight) {
+ this.scenario = scenario;
+ this.weight = weight;
+ this.tags = parseTags(scenario);
+ }
+
+ List getTags() {
+ return tags;
+ }
+
+ String getSourceFile() {
+ return this.scenario.getSource()
+ .map(ClasspathResourceSource.class::cast)
+ .map(ClasspathResourceSource::getClasspathResourceName)
+ .orElse(null);
+ }
+
+ String getDisplayName() {
+ return this.scenario.getDisplayName();
+ }
+
+ int getWeight() {
+ return weight;
+ }
+
+ void removeFromHierarchy() {
+ Optional featureOpt = scenario.getParent();
+ scenario.removeFromHierarchy();
+ featureOpt
+ .filter(feature -> feature.getChildren().isEmpty())
+ .ifPresent(TestDescriptor::prune);
+ }
+
+ boolean isTagMatchingFilter(String tagFilter) {
+ if (isNullOrEmpty(tagFilter)) {
+ return true;
+ }
+
+ Expression expression = TagExpressionParser.parse(tagFilter);
+ return expression.evaluate(tags);
+ }
+
+ private List parseTags(TestDescriptor test) {
+ if (test.isTest()) {
+ return test.getTags().stream()
+ .map(tag -> "@" + tag)
+ .collect(Collectors.toList());
+ } else {
+ return test.getChildren().stream()
+ .map(this::parseTags)
+ .flatMap(List::stream)
+ .distinct()
+ .collect(Collectors.toList());
+ }
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s:%s(%s - %d)", getSourceFile(), getDisplayName(), tags, weight);
+ }
+}
diff --git a/serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/CucumberScenarioVisualiser.java b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/CucumberScenarioVisualiser.java
similarity index 98%
rename from serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/CucumberScenarioVisualiser.java
rename to serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/CucumberScenarioVisualiser.java
index 0e3acde122..2af3fa90d5 100644
--- a/serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/CucumberScenarioVisualiser.java
+++ b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/CucumberScenarioVisualiser.java
@@ -4,7 +4,7 @@
import com.google.gson.GsonBuilder;
import io.cucumber.gherkin.CucumberScenarioLoader;
import net.serenitybdd.model.environment.ConfiguredEnvironment;
-import net.serenitybdd.cucumber.util.PathUtils;
+import net.serenitybdd.cucumber.utils.PathUtils;
import net.thucydides.model.util.EnvironmentVariables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/CucumberSuiteSlicer.java b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/CucumberSuiteSlicer.java
similarity index 95%
rename from serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/CucumberSuiteSlicer.java
rename to serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/CucumberSuiteSlicer.java
index 235eab8bb3..82fe782457 100644
--- a/serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/CucumberSuiteSlicer.java
+++ b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/CucumberSuiteSlicer.java
@@ -1,7 +1,7 @@
package net.serenitybdd.cucumber.suiteslicing;
import io.cucumber.gherkin.CucumberScenarioLoader;
-import net.serenitybdd.cucumber.util.TagParser;
+import net.serenitybdd.cucumber.utils.TagParser;
import java.net.URI;
import java.util.List;
@@ -28,4 +28,4 @@ public WeightedCucumberScenarios scenarios(int batchNumber, int batchCount, int
private Predicate forSuppliedTags(List tagFilters) {
return cucumberScenario -> TagParser.parseFromTagFilters(tagFilters).evaluate(newArrayList(cucumberScenario.tags));
}
-}
\ No newline at end of file
+}
diff --git a/serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/MultiRunTestStatistics.java b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/MultiRunTestStatistics.java
similarity index 98%
rename from serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/MultiRunTestStatistics.java
rename to serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/MultiRunTestStatistics.java
index 2f3fc90f8e..7bd5178b7c 100644
--- a/serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/MultiRunTestStatistics.java
+++ b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/MultiRunTestStatistics.java
@@ -1,7 +1,7 @@
package net.serenitybdd.cucumber.suiteslicing;
-import net.serenitybdd.cucumber.util.BigDecimalAverageCollector;
+import net.serenitybdd.cucumber.utils.BigDecimalAverageCollector;
import net.thucydides.model.util.Inflector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/ScenarioFilter.java b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/ScenarioFilter.java
similarity index 100%
rename from serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/ScenarioFilter.java
rename to serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/ScenarioFilter.java
diff --git a/serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/SerenityCSVHeader.java b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/SerenityCSVHeader.java
similarity index 100%
rename from serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/SerenityCSVHeader.java
rename to serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/SerenityCSVHeader.java
diff --git a/serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/SerenityTags.java b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/SerenityTags.java
similarity index 100%
rename from serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/SerenityTags.java
rename to serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/SerenityTags.java
diff --git a/serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/SingleRunTestStatistics.java b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/SingleRunTestStatistics.java
similarity index 100%
rename from serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/SingleRunTestStatistics.java
rename to serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/SingleRunTestStatistics.java
diff --git a/serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/SliceBuilder.java b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/SliceBuilder.java
similarity index 100%
rename from serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/SliceBuilder.java
rename to serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/SliceBuilder.java
diff --git a/serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/TestScenarioResult.java b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/TestScenarioResult.java
similarity index 100%
rename from serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/TestScenarioResult.java
rename to serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/TestScenarioResult.java
diff --git a/serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/TestScenarioResults.java b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/TestScenarioResults.java
similarity index 96%
rename from serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/TestScenarioResults.java
rename to serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/TestScenarioResults.java
index e672aac131..38a9534fff 100644
--- a/serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/TestScenarioResults.java
+++ b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/TestScenarioResults.java
@@ -1,6 +1,6 @@
package net.serenitybdd.cucumber.suiteslicing;
-import net.serenitybdd.cucumber.util.BigDecimalAverageCollector;
+import net.serenitybdd.cucumber.utils.BigDecimalAverageCollector;
import java.math.BigDecimal;
import java.util.List;
diff --git a/serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/TestStatistics.java b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/TestStatistics.java
similarity index 100%
rename from serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/TestStatistics.java
rename to serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/TestStatistics.java
diff --git a/serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/VisualisableCucumberScenarios.java b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/VisualisableCucumberScenarios.java
similarity index 100%
rename from serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/VisualisableCucumberScenarios.java
rename to serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/VisualisableCucumberScenarios.java
diff --git a/serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/WeightedCucumberScenario.java b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/WeightedCucumberScenario.java
similarity index 100%
rename from serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/WeightedCucumberScenario.java
rename to serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/WeightedCucumberScenario.java
diff --git a/serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/WeightedCucumberScenarios.java b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/WeightedCucumberScenarios.java
similarity index 100%
rename from serenity-cucumber/src/main/java/net/serenitybdd/cucumber/suiteslicing/WeightedCucumberScenarios.java
rename to serenity-junit5/src/main/java/net/serenitybdd/cucumber/suiteslicing/WeightedCucumberScenarios.java
diff --git a/serenity-cucumber/src/main/java/net/serenitybdd/cucumber/util/BigDecimalAverageCollector.java b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/utils/BigDecimalAverageCollector.java
similarity index 98%
rename from serenity-cucumber/src/main/java/net/serenitybdd/cucumber/util/BigDecimalAverageCollector.java
rename to serenity-junit5/src/main/java/net/serenitybdd/cucumber/utils/BigDecimalAverageCollector.java
index 0a4dd4ef4a..86bb95df7c 100644
--- a/serenity-cucumber/src/main/java/net/serenitybdd/cucumber/util/BigDecimalAverageCollector.java
+++ b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/utils/BigDecimalAverageCollector.java
@@ -1,4 +1,4 @@
-package net.serenitybdd.cucumber.util;
+package net.serenitybdd.cucumber.utils;
import java.math.BigDecimal;
import java.util.Collections;
@@ -79,4 +79,4 @@ void add(BigDecimal successRate) {
sum = sum.add(successRate);
}
}
-}
\ No newline at end of file
+}
diff --git a/serenity-junit5/src/main/java/net/serenitybdd/cucumber/utils/PathUtils.java b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/utils/PathUtils.java
new file mode 100644
index 0000000000..3dc98bb218
--- /dev/null
+++ b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/utils/PathUtils.java
@@ -0,0 +1,40 @@
+package net.serenitybdd.cucumber.utils;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.util.Objects;
+
+import io.cucumber.core.resource.ClasspathSupport;
+
+public class PathUtils {
+
+ private PathUtils() {
+ }
+
+ public static File getAsFile(URI cucumberFeatureUri) {
+ Objects.requireNonNull(cucumberFeatureUri, "cucumber feature URI cannot be null");
+ String featureFilePath;
+ switch (cucumberFeatureUri.getScheme()) {
+ case "file": {
+ try {
+ featureFilePath = cucumberFeatureUri.toURL().getPath();
+ break;
+ } catch (MalformedURLException e) {
+ throw new IllegalArgumentException("Cannot convert cucumber feature URI to URL", e);
+ }
+ }
+ case "classpath": {
+ featureFilePath = ClasspathSupport.resourceName(cucumberFeatureUri);
+ break;
+ }
+ default:
+ throw new IllegalArgumentException("Cannot get cucumber feature file from URI");
+ }
+ return new File(featureFilePath);
+ }
+
+ public static File getAsFile(String cucumberFeatureUri) {
+ return getAsFile(URI.create(cucumberFeatureUri));
+ }
+}
diff --git a/serenity-junit5/src/main/java/net/serenitybdd/cucumber/utils/Splitter.java b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/utils/Splitter.java
new file mode 100644
index 0000000000..c7cf7f9474
--- /dev/null
+++ b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/utils/Splitter.java
@@ -0,0 +1,62 @@
+package net.serenitybdd.cucumber.utils;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class Splitter {
+ private String separator;
+ private boolean omitEmptyStrings = false;
+ private boolean trimResults = false;
+ private String trimmable = null;
+
+ public Splitter(String separator) {
+ this.separator = separator;
+ }
+
+ public static Splitter on(String separator) {
+ return new Splitter(separator);
+ }
+
+ public Splitter omitEmptyStrings() {
+ omitEmptyStrings = true;
+ return this;
+ }
+
+ public Splitter trimResults() {
+ this.trimResults = true;
+ return this;
+ }
+
+ public Splitter trimResults(String trimmable) {
+ this.trimResults = true;
+ this.trimmable = trimmable;
+ return this;
+ }
+
+ public List splitToList(String value) {
+ String[] separatedElements = StringUtils.split(value, separator);
+ List result = Arrays.asList(separatedElements);
+
+ if (omitEmptyStrings) {
+ result = result.stream()
+ .filter(element -> !element.trim().equals(""))
+ .collect(Collectors.toList());
+ }
+
+ if (trimResults) {
+ result = result.stream()
+ .map(v -> StringUtils.strip(v, trimmable))
+ .collect(Collectors.toList());
+ }
+
+ return result;
+ }
+
+ public static Splitter on(char separator) {
+ return on(Character.toString(separator));
+ }
+
+}
diff --git a/serenity-junit5/src/main/java/net/serenitybdd/cucumber/utils/StepDefinitionAnnotationReader.java b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/utils/StepDefinitionAnnotationReader.java
new file mode 100644
index 0000000000..37b37601a2
--- /dev/null
+++ b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/utils/StepDefinitionAnnotationReader.java
@@ -0,0 +1,128 @@
+package net.serenitybdd.cucumber.utils;
+
+import net.serenitybdd.annotations.Screenshots;
+import net.thucydides.model.domain.TakeScreenshots;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static java.util.Arrays.stream;
+
+public class StepDefinitionAnnotationReader {
+ private String stepDefinitionPath;
+ private TakeScreenshots screenshotDefaultLevel = TakeScreenshots.UNDEFINED;
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(StepDefinitionAnnotationReader.class);
+
+ public StepDefinitionAnnotationReader(String stepDefinitionPath) {
+ this.stepDefinitionPath = stepDefinitionPath;
+ }
+
+ public StepDefinitionAnnotationReader(String stepDefinitionPath, TakeScreenshots screenshotDefaultLevel) {
+ this.screenshotDefaultLevel = screenshotDefaultLevel;
+ this.stepDefinitionPath = stepDefinitionPath;
+ }
+
+ public static StepDefinitionAnnotationReader forStepDefinition(String stepDefinitionPath) {
+ return new StepDefinitionAnnotationReader(stepDefinitionPath);
+ }
+
+ public static Builder withScreenshotLevel(TakeScreenshots screenshotLevel) {
+ return new Builder(screenshotLevel);
+ }
+
+ public TakeScreenshots getScreenshotPreferences() {
+ if (stepDefinitionPath == null) {
+ return TakeScreenshots.UNDEFINED;
+ }
+ List stepDefinitionAnnotations = annotationsIn(className(), methodName());
+ return stepDefinitionAnnotations.stream()
+ .filter(annotation -> annotation instanceof Screenshots)
+ .map(annotation -> asEnum((Screenshots) annotation))
+ .findFirst()
+ .orElse(screenshotDefaultLevel);
+ }
+
+ private TakeScreenshots asEnum(Screenshots screenshotAnnotation) {
+ if (screenshotAnnotation.disabled()) {
+ return TakeScreenshots.DISABLED;
+ } else if (screenshotAnnotation.afterEachStep()) {
+ return TakeScreenshots.AFTER_EACH_STEP;
+ } else if (screenshotAnnotation.beforeAndAfterEachStep()) {
+ return TakeScreenshots.BEFORE_AND_AFTER_EACH_STEP;
+ } else if (screenshotAnnotation.forEachAction()) {
+ return TakeScreenshots.FOR_EACH_ACTION;
+ } else if (screenshotAnnotation.onlyOnFailures()) {
+ return TakeScreenshots.FOR_FAILURES;
+ } else {
+ return TakeScreenshots.UNDEFINED;
+ }
+ }
+
+ private String className() {
+ Matcher matcher = Pattern.compile("^(\\w*\\s)").matcher(stepDefinitionPath);
+ if (matcher.lookingAt()) {
+ stepDefinitionPath = matcher.replaceFirst("");
+ }
+ int lastOpeningParentheses;
+ if (stepDefinitionPath.contains("(")) {
+ lastOpeningParentheses = stepDefinitionPath.lastIndexOf("(");
+ } else {
+ lastOpeningParentheses = stepDefinitionPath.toCharArray().length;
+ }
+ String qualifiedMethodName = stepDefinitionPath.substring(0, lastOpeningParentheses);
+ int endOfClassName = qualifiedMethodName.lastIndexOf(".");
+ return stepDefinitionPath.substring(0, endOfClassName);
+ }
+
+ private String methodName() {
+ int lastOpeningParentheses;
+ if (stepDefinitionPath.contains("(")) {
+ lastOpeningParentheses = stepDefinitionPath.lastIndexOf("(");
+ } else {
+ lastOpeningParentheses = stepDefinitionPath.toCharArray().length;
+ }
+ String qualifiedMethodName = stepDefinitionPath.substring(0, lastOpeningParentheses);
+ int startOfMethodName = qualifiedMethodName.lastIndexOf(".") + 1;
+ return stepDefinitionPath.substring(startOfMethodName, lastOpeningParentheses);
+ }
+
+ private List annotationsIn(String className, String methodName) {
+ try {
+ Optional matchingMethod
+ = stream(Class.forName(className).getMethods())
+ .filter(method -> method.getName().equals(methodName))
+ .findFirst();
+
+ return matchingMethod
+ .map(method -> Arrays.asList(method.getAnnotations()))
+ .orElseGet(ArrayList::new);
+
+ } catch (ClassNotFoundException e) {
+ LOGGER.warn("Could not analyse step definition method " + className + "." + methodName);
+ }
+ return new ArrayList<>();
+ }
+
+ public static class Builder {
+
+ private final TakeScreenshots screenshotLevel;
+
+ public Builder(TakeScreenshots screenshotLevel) {
+ this.screenshotLevel = screenshotLevel;
+ }
+
+ public StepDefinitionAnnotationReader forStepDefinition(String stepDefinitionPath) {
+ return new StepDefinitionAnnotationReader(stepDefinitionPath, screenshotLevel);
+ }
+ }
+}
diff --git a/serenity-junit5/src/main/java/net/serenitybdd/cucumber/utils/TagParser.java b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/utils/TagParser.java
new file mode 100644
index 0000000000..937f8bd7f0
--- /dev/null
+++ b/serenity-junit5/src/main/java/net/serenitybdd/cucumber/utils/TagParser.java
@@ -0,0 +1,49 @@
+package net.serenitybdd.cucumber.utils;
+
+import net.thucydides.model.ThucydidesSystemProperty;
+import net.thucydides.model.util.EnvironmentVariables;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Stream;
+
+import io.cucumber.tagexpressions.Expression;
+import io.cucumber.tagexpressions.TagExpressionParser;
+
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toList;
+
+public class TagParser {
+
+ public static Expression parseFromTagFilters(List stringList) {
+ String combinedExpression = stringList.isEmpty() ? "" : stringList.stream()
+ .filter(StringUtils::isNotEmpty)
+ .map(tagExpression -> tagExpression.replace("~", "not "))
+ .collect(joining(") and (", "(", ")"));
+
+ return TagExpressionParser.parse(combinedExpression);
+ }
+
+ public static Collection additionalTagsSuppliedFrom(EnvironmentVariables environmentVariables, List existingTags) {
+ String tagsExpression = ThucydidesSystemProperty.TAGS.from(environmentVariables, "");
+ return Stream.of(StringUtils.split(tagsExpression, ","))
+ .map(TagParser::toCucumberTag)
+ .filter(tag -> !existingTags.contains(tag)).collect(toList());
+ }
+
+ private static String toCucumberTag(String from) {
+ String tag = from.trim().replaceAll(":", "=");
+ if (tag.startsWith("~@") || tag.startsWith("@")) {
+ return tag;
+ }
+ if (tag.startsWith("~")) {
+ return "~@" + tag.substring(1);
+ }
+
+ return "@" + tag;
+ }
+
+
+}
diff --git a/serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/CucumberScenarioLoaderTest.java b/serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/CucumberScenarioLoaderTest.java
similarity index 97%
rename from serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/CucumberScenarioLoaderTest.java
rename to serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/CucumberScenarioLoaderTest.java
index 0d9752a7e8..e394e1de2f 100644
--- a/serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/CucumberScenarioLoaderTest.java
+++ b/serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/CucumberScenarioLoaderTest.java
@@ -1,6 +1,8 @@
package net.serenitybdd.cucumber.suiteslicing;
import io.cucumber.gherkin.CucumberScenarioLoader;
+
+import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
@@ -79,7 +81,7 @@ public void scenariosOfAllSlicesBetweenAllJvmShouldBeTheSame() throws URISyntaxE
.sliceInto(sliceCount);
for (int i = 0; i < jvm1Slices.size(); i++) {
- assertThat(jvm1Slices.get(i).scenarios, contains(buildMatchingCucumberScenario(jvm2Slices.get(i))));
+ assertThat(jvm1Slices.get(i).scenarios, Matchers.contains(buildMatchingCucumberScenario(jvm2Slices.get(i))));
}
}
diff --git a/serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/CucumberSliceVisualiserTest.java b/serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/CucumberSliceVisualiserTest.java
similarity index 100%
rename from serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/CucumberSliceVisualiserTest.java
rename to serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/CucumberSliceVisualiserTest.java
diff --git a/serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/CucumberSuiteSlicerTest.java b/serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/CucumberSuiteSlicerTest.java
similarity index 84%
rename from serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/CucumberSuiteSlicerTest.java
rename to serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/CucumberSuiteSlicerTest.java
index 331b6ca120..d079178b0c 100644
--- a/serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/CucumberSuiteSlicerTest.java
+++ b/serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/CucumberSuiteSlicerTest.java
@@ -1,6 +1,7 @@
package net.serenitybdd.cucumber.suiteslicing;
+import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
@@ -35,20 +36,20 @@ public void setup() throws Exception {
@Test
public void shouldReturnOnlyScenariosWithSpecifiedTags() {
- assertThat(cucumberSuiteSlicer.scenarios(1, 1, 1, 1, asList("@shouldPass")).scenarios, contains(expectedScenario1));
+ assertThat(cucumberSuiteSlicer.scenarios(1, 1, 1, 1, asList("@shouldPass")).scenarios, Matchers.contains(expectedScenario1));
}
@Test
public void noSuppliedTagsMeansReturnAllScenarios() {
- assertThat(cucumberSuiteSlicer.scenarios(1, 1, 1, 1, asList()).scenarios, contains(expectedScenario1, expectedScenario2));
+ assertThat(cucumberSuiteSlicer.scenarios(1, 1, 1, 1, asList()).scenarios, Matchers.contains(expectedScenario1, expectedScenario2));
}
@Test
public void shouldSupportNotInTheTagExpression() {
- assertThat(cucumberSuiteSlicer.scenarios(1, 1, 1, 1, asList("not @shouldPass")).scenarios, contains(expectedScenario2));
+ assertThat(cucumberSuiteSlicer.scenarios(1, 1, 1, 1, asList("not @shouldPass")).scenarios, Matchers.contains(expectedScenario2));
}
@Test
public void shouldSupportOldExclusionSyntaxInTheTagExpression() {
- assertThat(cucumberSuiteSlicer.scenarios(1, 1, 1, 1, asList("~@shouldPass")).scenarios, contains(expectedScenario2));
+ assertThat(cucumberSuiteSlicer.scenarios(1, 1, 1, 1, asList("~@shouldPass")).scenarios, Matchers.contains(expectedScenario2));
}
}
diff --git a/serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/DummyStatsOfWeightingOne.java b/serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/DummyStatsOfWeightingOne.java
similarity index 100%
rename from serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/DummyStatsOfWeightingOne.java
rename to serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/DummyStatsOfWeightingOne.java
diff --git a/serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/MatchingCucumberScenario.java b/serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/MatchingCucumberScenario.java
similarity index 100%
rename from serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/MatchingCucumberScenario.java
rename to serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/MatchingCucumberScenario.java
diff --git a/serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/MultiRunTestStatisticsTest.java b/serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/MultiRunTestStatisticsTest.java
similarity index 99%
rename from serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/MultiRunTestStatisticsTest.java
rename to serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/MultiRunTestStatisticsTest.java
index 2d37b27d2f..41a361e38d 100644
--- a/serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/MultiRunTestStatisticsTest.java
+++ b/serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/MultiRunTestStatisticsTest.java
@@ -33,4 +33,4 @@ public void scenarioWeightForShouldReturnAverageAllDurationsForUnknownScenario()
assertThat(statistics.scenarioWeightFor("Yo", "I don't exist matey"), is(new BigDecimal("5.53")));
}
-}
\ No newline at end of file
+}
diff --git a/serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/ScenarioLineCountStatisticsTest.java b/serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/ScenarioLineCountStatisticsTest.java
similarity index 99%
rename from serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/ScenarioLineCountStatisticsTest.java
rename to serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/ScenarioLineCountStatisticsTest.java
index d4a1bbedc8..efde1655da 100644
--- a/serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/ScenarioLineCountStatisticsTest.java
+++ b/serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/ScenarioLineCountStatisticsTest.java
@@ -55,4 +55,4 @@ public void scenarioWeightForScenarioWithBackgroundAndScenario() {
assertThat(stats.scenarioWeightFor("Locate a customer by personal details and Reg Number", "Locating a customer using a unique criterion"), is(new BigDecimal("4")));
}
-}
\ No newline at end of file
+}
diff --git a/serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/SingleRunTestStatisticsTest.java b/serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/SingleRunTestStatisticsTest.java
similarity index 99%
rename from serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/SingleRunTestStatisticsTest.java
rename to serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/SingleRunTestStatisticsTest.java
index d6515502d1..b5e9553e50 100644
--- a/serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/SingleRunTestStatisticsTest.java
+++ b/serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/SingleRunTestStatisticsTest.java
@@ -33,4 +33,4 @@ public void scenarioWeightForShouldReturnAverageDurationForUnknownScenario() thr
statistics.scenarioWeightFor("Yo", "I don't exist matey");
}
-}
\ No newline at end of file
+}
diff --git a/serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/SlicedTestRunner.java b/serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/SlicedTestRunner.java
new file mode 100644
index 0000000000..5c018bc560
--- /dev/null
+++ b/serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/SlicedTestRunner.java
@@ -0,0 +1,43 @@
+package net.serenitybdd.cucumber.suiteslicing;
+
+import io.cucumber.junit.platform.engine.Constants;
+import io.cucumber.plugin.ConcurrentEventListener;
+import io.cucumber.plugin.event.EventPublisher;
+import io.cucumber.plugin.event.TestRunFinished;
+import io.cucumber.plugin.event.TestRunStarted;
+import org.junit.platform.suite.api.*;
+
+
+@Suite(failIfNoTests = false)
+@IncludeEngines("cucumber-batch")
+@SelectClasspathResources({@SelectClasspathResource("path-1"), @SelectClasspathResource("path-2")})
+@ConfigurationParameters({
+ @ConfigurationParameter(key = Constants.EXECUTION_DRY_RUN_PROPERTY_NAME, value = "false"),
+ @ConfigurationParameter(key = Constants.GLUE_PROPERTY_NAME, value = "com.example.support,com.example.steps,com.example.hooks,com.example.types"),
+ @ConfigurationParameter(key = Constants.OBJECT_FACTORY_PROPERTY_NAME, value = "cucumber.runtime.SerenityObjectFactory"),
+ @ConfigurationParameter(key = Constants.PLUGIN_PUBLISH_QUIET_PROPERTY_NAME, value = "true"),
+ @ConfigurationParameter(key = Constants.PLUGIN_PUBLISH_ENABLED_PROPERTY_NAME, value = "false"),
+ @ConfigurationParameter(key = Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, value = "false"),
+ @ConfigurationParameter(key = Constants.PLUGIN_PROPERTY_NAME, value = "SlicedTestRunner,io.cucumber.core.plugin.SerenityReporter,pretty"),
+ @ConfigurationParameter(key = Constants.FILTER_TAGS_PROPERTY_NAME, value = "@my-tag")})
+public class SlicedTestRunner implements ConcurrentEventListener {
+
+
+ @Override
+ public void setEventPublisher(EventPublisher eventPublisher) {
+ eventPublisher.registerHandlerFor(TestRunStarted.class, event -> {
+ setUp();
+ });
+ eventPublisher.registerHandlerFor(TestRunFinished.class, event -> {
+ tearDown();
+ });
+ }
+
+ public static void setUp() {
+ // setup code here
+ }
+
+ public static void tearDown() {
+ // tearDown code here
+ }
+}
diff --git a/serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/WeightedCucumberScenariosTest.java b/serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/WeightedCucumberScenariosTest.java
similarity index 93%
rename from serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/WeightedCucumberScenariosTest.java
rename to serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/WeightedCucumberScenariosTest.java
index 9b198176f4..9f82e9e962 100644
--- a/serenity-cucumber/src/test/java/net/serenitybdd/cucumber/suiteslicing/WeightedCucumberScenariosTest.java
+++ b/serenity-junit5/src/test/java/net/serenitybdd/cucumber/suiteslicing/WeightedCucumberScenariosTest.java
@@ -1,5 +1,6 @@
package net.serenitybdd.cucumber.suiteslicing;
+import org.hamcrest.core.Is;
import org.junit.Test;
import java.math.BigDecimal;
@@ -21,7 +22,7 @@ public void slicingMoreThinlyThanTheNumberOfScenariosShouldResultInSomeEmptySlic
List weightedCucumberScenarios = oneScenario.sliceInto(100);
assertThat(weightedCucumberScenarios, hasSize(100));
assertThat(weightedCucumberScenarios.get(0).scenarios, hasSize(1));
- assertThat(weightedCucumberScenarios.get(0).scenarios.get(0), is(weightedCucumberScenario));
+ assertThat(weightedCucumberScenarios.get(0).scenarios.get(0), Is.is(weightedCucumberScenario));
assertThat(weightedCucumberScenarios.get(1).scenarios, hasSize(0));
assertThat(weightedCucumberScenarios.get(99).scenarios, hasSize(0));
}
@@ -31,7 +32,7 @@ public void slicingASliceIntoOneSliceOfOneShouldBeTheSameAsAllScenarios() {
List scenarios = Collections.singletonList(new WeightedCucumberScenario("test.feature", "featurename", "scenarioname", BigDecimal.ONE, emptySet(), 0));
WeightedCucumberScenarios oneScenario = new WeightedCucumberScenarios(scenarios);
WeightedCucumberScenarios fork1 = oneScenario.slice(1).of(1);
- assertThat(oneScenario, is(fork1));
+ assertThat(oneScenario, Is.is(fork1));
}
-}
\ No newline at end of file
+}
diff --git a/serenity-junit5/src/test/resources/samples/failing_scenario.feature b/serenity-junit5/src/test/resources/samples/failing_scenario.feature
new file mode 100644
index 0000000000..7aa224ceb0
--- /dev/null
+++ b/serenity-junit5/src/test/resources/samples/failing_scenario.feature
@@ -0,0 +1,9 @@
+Feature: A simple feature that fails
+
+ @shouldFail
+ Scenario: A simple failing scenario
+ Given I want to purchase 2 widgets
+ And a widget costs $5
+ When I buy the widgets
+ Then I should be billed $20
+ And I want to purchase 5 widgets
\ No newline at end of file
diff --git a/serenity-junit5/src/test/resources/samples/failing_scenario_outline.feature b/serenity-junit5/src/test/resources/samples/failing_scenario_outline.feature
new file mode 100644
index 0000000000..32720ea269
--- /dev/null
+++ b/serenity-junit5/src/test/resources/samples/failing_scenario_outline.feature
@@ -0,0 +1,12 @@
+Feature: A simple feature that fails
+
+ @shouldFail
+ Scenario Outline: A simple failing scenario outline
+ Given I want to purchase widgets
+ And at a cost of
+ When I buy the widgets
+ Then I should be billed $10
+ Examples:
+ | count | cost | total
+ | 1 | 5 | 20
+ | 2 | 5 | 10
diff --git a/serenity-junit5/src/test/resources/samples/feature_pending_tag.feature b/serenity-junit5/src/test/resources/samples/feature_pending_tag.feature
new file mode 100644
index 0000000000..b16b052362
--- /dev/null
+++ b/serenity-junit5/src/test/resources/samples/feature_pending_tag.feature
@@ -0,0 +1,20 @@
+@pending
+Feature: A feature with pending scenarios
+
+ Scenario: Simple scenario 1
+ Given I want to purchase 2 widgets
+ And a widget costs $5
+ When I buy the widgets
+ Then I should be billed $10
+
+ Scenario: Simple scenario 2
+ Given I want to purchase 4 widgets
+ And a widget costs $3
+ When I buy the widgets
+ Then I should be billed $12
+
+ Scenario: Simple scenario 3
+ Given I want to purchase 5 widgets
+ And a widget costs $3
+ When I buy the widgets
+ Then I should be billed $15
\ No newline at end of file
diff --git a/serenity-junit5/src/test/resources/samples/jira_issue.feature b/serenity-junit5/src/test/resources/samples/jira_issue.feature
new file mode 100644
index 0000000000..5cd8aa5ee6
--- /dev/null
+++ b/serenity-junit5/src/test/resources/samples/jira_issue.feature
@@ -0,0 +1,13 @@
+@foo
+@issues:ISSUE-123,ISSUE-789
+Feature: Basic Arithmetic
+ Calculing additions
+
+ Background: A Calculator
+ Given a calculator I just turned on
+
+ @issues:ISSUE-456,ISSUE-001
+ Scenario: Addition
+ # Try to change one of the values below to provoke a failure
+ When I add 4 and 5
+ Then the result is 9
diff --git a/serenity-junit5/src/test/resources/samples/multiple_jira_issues.feature b/serenity-junit5/src/test/resources/samples/multiple_jira_issues.feature
new file mode 100644
index 0000000000..1ffce3960c
--- /dev/null
+++ b/serenity-junit5/src/test/resources/samples/multiple_jira_issues.feature
@@ -0,0 +1,18 @@
+@foo
+@issues:ISSUE-123,ISSUE-789
+Feature: Basic Arithmetic
+ Calculing additions
+
+ Background: A Calculator
+ Given a calculator I just turned on
+
+ @issues:ISSUE-456,ISSUE-001
+ Scenario: Addition
+ # Try to change one of the values below to provoke a failure
+ When I add 4 and 5
+ Then the result is 9
+
+ Scenario: Another Addition
+ # Try to change one of the values below to provoke a failure
+ When I add 4 and 7
+ Then the result is 11
diff --git a/serenity-junit5/src/test/resources/samples/multiple_scenarios.feature b/serenity-junit5/src/test/resources/samples/multiple_scenarios.feature
new file mode 100644
index 0000000000..a5700b84e3
--- /dev/null
+++ b/serenity-junit5/src/test/resources/samples/multiple_scenarios.feature
@@ -0,0 +1,15 @@
+Feature: A feature with multiple scenarios
+
+ @shouldFail
+ Scenario: Simple scenario 1
+ Given I want to purchase 2 widgets
+ And a widget costs $5
+ When I buy the widgets
+ Then I should be billed $50
+
+ @shouldPass
+ Scenario: Simple scenario 2
+ Given I want to purchase 4 widgets
+ And a widget costs $3
+ When I buy the widgets
+ Then I should be billed $12
\ No newline at end of file
diff --git a/serenity-junit5/src/test/resources/samples/scenario_with_table_in_background_steps.feature b/serenity-junit5/src/test/resources/samples/scenario_with_table_in_background_steps.feature
new file mode 100644
index 0000000000..75307987e1
--- /dev/null
+++ b/serenity-junit5/src/test/resources/samples/scenario_with_table_in_background_steps.feature
@@ -0,0 +1,20 @@
+Feature: Locate a customer by personal details and Reg Number
+ In order to find information relevant to a specific customer
+ As a financial advisor
+ I want to be able to locate a customer by personal details, registration number
+
+ Background:
+ # Set up the customer in the test data, or ensure that this data is available
+ Given the following customers exist:
+ | Name | DOB | Mobile Phone | Home Phone | Work Phone | Address Line 1 | Address Line 2 |
+ | SEAN PAUL | 30/05/1978 | 860123334 | 1234567899 | 16422132 | ONE BBI ACC | BEACON SOUTH |
+ | TONY SMITH | 10/10/1975 | 86123335 | 11255555 | 16422132 | 1 MAIN STREET | BANKCENTRE |
+ | PETE FORD | 12/03/1970 | 865555551 | 15555551 | 15555551 | Q6B HILL ST | BLACKROCK |
+ | JOHN B JOVI | 22/08/1957 | 871274762 | | 16422132 | BLAKBURN | TALLAGHT |
+ | JOHN ANFIELD | 20/05/1970 | 876565656 | 015555551 | 214555555 | DUBLIN | DUBLIN |
+ And I am logged into the OneView app
+
+ @layer:ui
+ Scenario: Locating a customer using a unique criterion
+ When I locate a customer with a Reg Number of 80862061
+ Then I should see the customer profile for TONY SMITH
diff --git a/serenity-junit5/src/test/resources/samples/simple_scenario.feature b/serenity-junit5/src/test/resources/samples/simple_scenario.feature
new file mode 100644
index 0000000000..0c0aeb776a
--- /dev/null
+++ b/serenity-junit5/src/test/resources/samples/simple_scenario.feature
@@ -0,0 +1,15 @@
+Feature: A simple feature
+ @shouldPass
+ Scenario: A simple scenario
+ Given I want to purchase 2 widgets
+ And a widget costs $5
+ When I buy the widgets
+ Then I should be billed $10
+
+ Scenario: A simple scenario 2
+ Given I want to purchase 2 widgets
+ And a widget costs $5
+ When I buy the widgets
+ Then I should be billed $10
+
+
diff --git a/serenity-junit5/src/test/resources/samples/simple_scenario_with_a_long_name.feature b/serenity-junit5/src/test/resources/samples/simple_scenario_with_a_long_name.feature
new file mode 100644
index 0000000000..f2404ae098
--- /dev/null
+++ b/serenity-junit5/src/test/resources/samples/simple_scenario_with_a_long_name.feature
@@ -0,0 +1,8 @@
+Feature: A simple feature showing how features can have long names
+ @shouldPass
+ Scenario: A simple scenario
+ Given I want to purchase 2 widgets
+ And a widget costs $5
+ When I buy the widgets
+ Then I should be billed $10
+
diff --git a/serenity-junit5/src/test/resources/samples/simple_scenario_with_lambda_steps.feature b/serenity-junit5/src/test/resources/samples/simple_scenario_with_lambda_steps.feature
new file mode 100644
index 0000000000..e662d706e9
--- /dev/null
+++ b/serenity-junit5/src/test/resources/samples/simple_scenario_with_lambda_steps.feature
@@ -0,0 +1,7 @@
+Feature: A simple feature with lambda steps
+ @shouldPass
+ Scenario: A simple scenario
+ Given I want to use a lambda step
+ When I run the test
+ Then I should see the output
+
diff --git a/serenity-junit5/src/test/resources/samples/simple_scenario_with_narrative_text.feature b/serenity-junit5/src/test/resources/samples/simple_scenario_with_narrative_text.feature
new file mode 100644
index 0000000000..30aa9f7fde
--- /dev/null
+++ b/serenity-junit5/src/test/resources/samples/simple_scenario_with_narrative_text.feature
@@ -0,0 +1,11 @@
+Feature: A simple feature
+ This is about selling widgets
+ @shouldPass
+ Scenario: A simple scenario
+ A description of this scenario
+ It goes for two lines
+ Given I want to purchase 2 widgets
+ And a widget costs $5
+ When I buy the widgets
+ Then I should be billed $10
+
diff --git a/serenity-junit5/src/test/resources/samples/simple_scenario_with_rules.feature b/serenity-junit5/src/test/resources/samples/simple_scenario_with_rules.feature
new file mode 100644
index 0000000000..dd7ce807bd
--- /dev/null
+++ b/serenity-junit5/src/test/resources/samples/simple_scenario_with_rules.feature
@@ -0,0 +1,18 @@
+Feature: A simple feature
+
+ Rule: This is a simple rule
+ Simple first rule description
+ Scenario: A simple scenario
+ Given I want to purchase 2 widgets
+ And a widget costs $5
+ When I buy the widget
+ Then I should be billed $10
+
+ Rule: This is a simple second rule
+ Simple second rule description
+ Scenario: A simple second scenario
+ Given I want to purchase 2 widgets
+ And a widget costs $5
+ When I buy the widgets
+ Then I should be billed $10
+
diff --git a/serenity-junit5/src/test/resources/samples/simple_scenario_with_tags.feature b/serenity-junit5/src/test/resources/samples/simple_scenario_with_tags.feature
new file mode 100644
index 0000000000..a3cca5f2c8
--- /dev/null
+++ b/serenity-junit5/src/test/resources/samples/simple_scenario_with_tags.feature
@@ -0,0 +1,12 @@
+@flavor:strawberry
+Feature: A simple feature with tags
+ This is about selling widgets
+ @shouldPass
+ @color:red
+ @in-progress
+ Scenario: A simple scenario with tags
+ Given I want to purchase 2 widgets
+ And a widget costs $5
+ When I buy the widgets
+ Then I should be billed $10
+
diff --git a/serenity-junit5/src/test/resources/samples/simple_table_based_scenario.feature b/serenity-junit5/src/test/resources/samples/simple_table_based_scenario.feature
new file mode 100644
index 0000000000..a12d45fde1
--- /dev/null
+++ b/serenity-junit5/src/test/resources/samples/simple_table_based_scenario.feature
@@ -0,0 +1,34 @@
+Feature: Buying things - with tables
+
+ Background: I already have some cash
+ Given I have $100
+
+ @shouldPass
+ Scenario Outline: Buying lots of widgets
+ Given I want to purchase widgets
+ And a widget costs $
+ When I buy the widgets
+ Then I should be billed $
+ Examples:
+ | amount | cost | total |
+ | 0 | 10 | 0 |
+ | 1 | 10 | 10 |
+ | 2 | 10 | 20 |
+ | 3 | 10 | 30 |
+ | 4 | 0 | 0 |
+
+ Examples:
+ | amount | cost | total |
+ | 50 | 10 | 500 |
+ | 60 | 10 | 600 |
+
+ Scenario Outline: Buying more widgets
+ Given I want to purchase widgets
+ And a widget costs $
+ When I buy the widgets
+ Then I should be billed $
+ Examples:
+ | amount | cost | total |
+ | 6 | 10 | 0 |
+ | 8 | 10 | 80 |
+ | 10 | 10 | 100 |
diff --git a/serenity-junit5/src/test/resources/samples/tagged_example_tables.feature b/serenity-junit5/src/test/resources/samples/tagged_example_tables.feature
new file mode 100644
index 0000000000..d41ded28a5
--- /dev/null
+++ b/serenity-junit5/src/test/resources/samples/tagged_example_tables.feature
@@ -0,0 +1,20 @@
+Feature: Tagged Tables
+
+ Scenario Outline: This scenario should have two tables
+
+ Given the number and the number
+ When plus
+ Then the result is equals to
+
+ @small
+ Examples: Small numbers
+ | a | b | c |
+ | 1 | 4 | 5 |
+ | 2 | 7 | 9 |
+ | 3 | 6 | 9 |
+
+ @big
+ Examples: Big numbers
+ | a | b | c |
+ | 11 | 4 | 15 |
+ | 12 | 7 | 19 |
\ No newline at end of file
diff --git a/serenity-cucumber/src/test/resources/statistics/smoke-test-results-run-1.csv b/serenity-junit5/src/test/resources/statistics/smoke-test-results-run-1.csv
similarity index 100%
rename from serenity-cucumber/src/test/resources/statistics/smoke-test-results-run-1.csv
rename to serenity-junit5/src/test/resources/statistics/smoke-test-results-run-1.csv
diff --git a/serenity-cucumber/src/test/resources/statistics/smoke-test-results-run-2.csv b/serenity-junit5/src/test/resources/statistics/smoke-test-results-run-2.csv
similarity index 100%
rename from serenity-cucumber/src/test/resources/statistics/smoke-test-results-run-2.csv
rename to serenity-junit5/src/test/resources/statistics/smoke-test-results-run-2.csv