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