From 5de7ff48ef1591e8a645185db4ce18d1e612e51d Mon Sep 17 00:00:00 2001 From: Dmitry Baev Date: Mon, 4 Mar 2024 16:08:50 +0000 Subject: [PATCH] backport change to cucumber 6 --- .../cucumber6jvm/AllureCucumber6Jvm.java | 304 +++++++++--------- .../cucumber6jvm/AllureCucumber6JvmTest.java | 225 ++++++++----- .../cucumber6jvm/samples/RuntimeApiSteps.java | 83 +++++ .../resources/features/runtimeapi.feature | 10 + 4 files changed, 390 insertions(+), 232 deletions(-) create mode 100644 allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/RuntimeApiSteps.java create mode 100644 allure-cucumber6-jvm/src/test/resources/features/runtimeapi.feature diff --git a/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/AllureCucumber6Jvm.java b/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/AllureCucumber6Jvm.java index fba287315..bb0e513bf 100644 --- a/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/AllureCucumber6Jvm.java +++ b/allure-cucumber6-jvm/src/main/java/io/qameta/allure/cucumber6jvm/AllureCucumber6Jvm.java @@ -28,6 +28,7 @@ import io.cucumber.plugin.event.HookType; import io.cucumber.plugin.event.PickleStepTestStep; import io.cucumber.plugin.event.Result; +import io.cucumber.plugin.event.Step; import io.cucumber.plugin.event.StepArgument; import io.cucumber.plugin.event.TestCase; import io.cucumber.plugin.event.TestCaseFinished; @@ -48,13 +49,13 @@ import io.qameta.allure.model.TestResultContainer; import java.io.ByteArrayInputStream; -import java.net.URI; import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.util.Collections; import java.util.Deque; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.UUID; @@ -78,17 +79,12 @@ }) public class AllureCucumber6Jvm implements ConcurrentEventListener { + private static final String COLON = ":"; + private final AllureLifecycle lifecycle; - private final ConcurrentHashMap scenarioUuids = new ConcurrentHashMap<>(); private final TestSourcesModelProxy testSources = new TestSourcesModelProxy(); - private final ThreadLocal currentFeature = new InheritableThreadLocal<>(); - private final ThreadLocal currentFeatureFile = new InheritableThreadLocal<>(); - private final ThreadLocal currentTestCase = new InheritableThreadLocal<>(); - private final ThreadLocal currentContainer = new InheritableThreadLocal<>(); - private final ThreadLocal forbidTestCaseStatusChange = new InheritableThreadLocal<>(); - private final EventHandler featureStartedHandler = this::handleFeatureStartedHandler; private final EventHandler caseStartedHandler = this::handleTestCaseStarted; private final EventHandler caseFinishedHandler = this::handleTestCaseFinished; @@ -97,6 +93,8 @@ public class AllureCucumber6Jvm implements ConcurrentEventListener { private final EventHandler writeEventHandler = this::handleWriteEvent; private final EventHandler embedEventHandler = this::handleEmbedEvent; + private final Map hookStepContainerUuid = new ConcurrentHashMap<>(); + private static final String TXT_EXTENSION = ".txt"; private static final String TEXT_PLAIN = "text/plain"; private static final String CUCUMBER_WORKING_DIR = Paths.get("").toUri().getSchemeSpecificPart(); @@ -110,9 +108,6 @@ public AllureCucumber6Jvm(final AllureLifecycle lifecycle) { this.lifecycle = lifecycle; } - /* - Event Handlers - */ @Override public void setEventPublisher(final EventPublisher publisher) { publisher.registerHandlerFor(TestSourceRead.class, featureStartedHandler); @@ -132,29 +127,28 @@ private void handleFeatureStartedHandler(final TestSourceRead event) { } private void handleTestCaseStarted(final TestCaseStarted event) { - currentFeatureFile.set(event.getTestCase().getUri()); - currentFeature.set(testSources.getFeature(currentFeatureFile.get())); - currentTestCase.set(event.getTestCase()); - currentContainer.set(UUID.randomUUID().toString()); - forbidTestCaseStatusChange.set(false); + final TestCase testCase = event.getTestCase(); + final Feature feature = testSources.getFeature(testCase.getUri()); - final TestCase testCase = currentTestCase.get(); final Deque tags = new LinkedList<>(testCase.getTags()); - - final Feature feature = currentFeature.get(); final LabelBuilder labelBuilder = new LabelBuilder(feature, testCase, tags); final String name = testCase.getName(); + + // the same way full name is generated for // org.junit.platform.engine.support.descriptor.ClasspathResourceSource // to support io.qameta.allure.junitplatform.AllurePostDiscoveryFilter final String fullName = String.format("%s:%d", - getTestCaseUri(event.getTestCase()), - event.getTestCase().getLocation().getLine() + getTestCaseUri(testCase), + testCase.getLocation().getLine() ); + final String testCaseUuid = testCase.getId().toString(); + final TestResult result = new TestResult() - .setUuid(getTestCaseUuid(testCase)) + .setUuid(testCaseUuid) + .setTestCaseId(getTestCaseId(testCase)) .setHistoryId(getHistoryId(testCase)) .setFullName(fullName) .setName(name) @@ -163,11 +157,11 @@ private void handleTestCaseStarted(final TestCaseStarted event) { final Scenario scenarioDefinition = testSources.getScenarioDefinition( - currentFeatureFile.get(), + testCase.getUri(), testCase.getLocation().getLine() ); - if (scenarioDefinition.getExamplesCount() > 0) { + if (scenarioDefinition.getExamplesList() != null) { result.setParameters( getExamplesAsParameters(scenarioDefinition, testCase) ); @@ -182,75 +176,121 @@ private void handleTestCaseStarted(final TestCaseStarted event) { result.setDescription(description); } - final TestResultContainer resultContainer = new TestResultContainer() - .setName(String.format("%s: %s", scenarioDefinition.getKeyword(), scenarioDefinition.getName())) - .setUuid(getTestContainerUuid()) - .setChildren(Collections.singletonList(getTestCaseUuid(testCase))); - lifecycle.scheduleTestCase(result); - lifecycle.startTestContainer(getTestContainerUuid(), resultContainer); - lifecycle.startTestCase(getTestCaseUuid(testCase)); + lifecycle.startTestCase(testCaseUuid); } private void handleTestCaseFinished(final TestCaseFinished event) { + final TestCase testCase = event.getTestCase(); + final Feature feature = testSources.getFeature(testCase.getUri()); + final String uuid = testCase.getId().toString(); + final Result result = event.getResult(); + final Status status = translateTestCaseStatus(result); + final StatusDetails statusDetails = getStatusDetails(result.getError()) + .orElseGet(StatusDetails::new); + + final TagParser tagParser = new TagParser(feature, testCase); + statusDetails + .setFlaky(tagParser.isFlaky()) + .setMuted(tagParser.isMuted()) + .setKnown(tagParser.isKnown()); + + lifecycle.updateTestCase(uuid, testResult -> testResult + .setStatus(status) + .setStatusDetails(statusDetails) + ); - final String uuid = getTestCaseUuid(event.getTestCase()); - final Optional details = getStatusDetails(event.getResult().getError()); - details.ifPresent(statusDetails -> lifecycle.updateTestCase( - uuid, - testResult -> testResult.setStatusDetails(statusDetails) - )); lifecycle.stopTestCase(uuid); - lifecycle.stopTestContainer(getTestContainerUuid()); lifecycle.writeTestCase(uuid); - lifecycle.writeTestContainer(getTestContainerUuid()); } private void handleTestStepStarted(final TestStepStarted event) { - if (event.getTestStep() instanceof PickleStepTestStep) { - final PickleStepTestStep pickleStep = (PickleStepTestStep) event.getTestStep(); - final String stepKeyword = Optional.ofNullable( - testSources.getKeywordFromSource(currentFeatureFile.get(), pickleStep.getStep().getLine()) - ).orElse("UNDEFINED"); - - final StepResult stepResult = new StepResult() - .setName(String.format("%s %s", stepKeyword, pickleStep.getStep().getText())) - .setStart(System.currentTimeMillis()); - - lifecycle.startStep(getTestCaseUuid(currentTestCase.get()), getStepUuid(pickleStep), stepResult); + final TestCase testCase = event.getTestCase(); + if (event.getTestStep() instanceof HookTestStep) { + final HookTestStep hook = (HookTestStep) event.getTestStep(); - final StepArgument stepArgument = pickleStep.getStep().getArgument(); - if (stepArgument instanceof DataTableArgument) { - final DataTableArgument dataTableArgument = (DataTableArgument) stepArgument; - createDataTableAttachment(dataTableArgument); + if (isFixtureHook(hook)) { + handleStartFixtureHook(testCase, hook); + } else { + handleStartStepHook(testCase, hook); } - } else if (event.getTestStep() instanceof HookTestStep) { - initHook((HookTestStep) event.getTestStep()); + } else if (event.getTestStep() instanceof PickleStepTestStep) { + handleStartPickleStep(testCase, (PickleStepTestStep) event.getTestStep()); } } - private void initHook(final HookTestStep hook) { + private void handleStartPickleStep(final TestCase testCase, + final PickleStepTestStep pickleStep) { + final String uuid = testCase.getId().toString(); + final Step step = pickleStep.getStep(); - final FixtureResult hookResult = new FixtureResult() + final StepResult stepResult = new StepResult() + .setName(step.getKeyword() + step.getText()) + .setStart(System.currentTimeMillis()); + + lifecycle.setCurrentTestCase(uuid); + lifecycle.startStep(uuid, pickleStep.getId().toString(), stepResult); + + final StepArgument stepArgument = step.getArgument(); + if (stepArgument instanceof DataTableArgument) { + final DataTableArgument dataTableArgument = (DataTableArgument) stepArgument; + createDataTableAttachment(dataTableArgument); + } + } + + private void handleStartStepHook(final TestCase testCase, + final HookTestStep hook) { + final String uuid = testCase.getId().toString(); + final StepResult stepResult = new StepResult() .setName(hook.getCodeLocation()) .setStart(System.currentTimeMillis()); + lifecycle.setCurrentTestCase(uuid); + lifecycle.startStep(uuid, hook.getId().toString(), stepResult); + } + + private void handleStartFixtureHook(final TestCase testCase, + final HookTestStep hook) { + final String uuid = testCase.getId().toString(); + + final UUID hookId = hook.getId(); + final String containerUuid = hookStepContainerUuid + .computeIfAbsent(hookId, unused -> UUID.randomUUID().toString()); + + lifecycle.startTestContainer(new TestResultContainer() + .setUuid(containerUuid) + .setChildren(Collections.singletonList(uuid)) + ); + + final FixtureResult hookResult = new FixtureResult() + .setName(hook.getCodeLocation()); + + final String fixtureUuid = hookId.toString(); if (hook.getHookType() == HookType.BEFORE) { - lifecycle.startPrepareFixture(getTestContainerUuid(), getHookStepUuid(hook), hookResult); + lifecycle.startPrepareFixture(containerUuid, fixtureUuid, hookResult); } else { - lifecycle.startTearDownFixture(getTestContainerUuid(), getHookStepUuid(hook), hookResult); + lifecycle.startTearDownFixture(containerUuid, fixtureUuid, hookResult); } - } private void handleTestStepFinished(final TestStepFinished event) { if (event.getTestStep() instanceof HookTestStep) { - handleHookStep(event); - } else { - handlePickleStep(event); + final HookTestStep hook = (HookTestStep) event.getTestStep(); + if (isFixtureHook(hook)) { + handleStopHookStep(event.getResult(), hook); + } else { + handleStopStep(event.getTestCase(), event.getResult(), hook.getId()); + } + } else if (event.getTestStep() instanceof PickleStepTestStep) { + final PickleStepTestStep pickleStep = (PickleStepTestStep) event.getTestStep(); + handleStopStep(event.getTestCase(), event.getResult(), pickleStep.getId()); } } + private static boolean isFixtureHook(final HookTestStep hook) { + return hook.getHookType() == HookType.BEFORE || hook.getHookType() == HookType.AFTER; + } + private void handleWriteEvent(final WriteEvent event) { lifecycle.addAttachment( "Text output", @@ -264,33 +304,16 @@ private void handleEmbedEvent(final EmbedEvent event) { lifecycle.addAttachment(event.name, event.getMediaType(), null, new ByteArrayInputStream(event.getData())); } - /* - Utility Methods - */ - - private String getTestContainerUuid() { - return currentContainer.get(); - } - - private String getTestCaseUuid(final TestCase testCase) { - return scenarioUuids.computeIfAbsent(getHistoryId(testCase), it -> UUID.randomUUID().toString()); - } - - private String getStepUuid(final PickleStepTestStep step) { - return currentFeature.get().getName() + getTestCaseUuid(currentTestCase.get()) - + step.getStep().getText() + step.getStep().getLine(); - } - - private String getHookStepUuid(final HookTestStep step) { - return currentFeature.get().getName() + getTestCaseUuid(currentTestCase.get()) - + step.getHookType().toString() + step.getCodeLocation(); - } - private String getHistoryId(final TestCase testCase) { - final String testCaseLocation = getTestCaseUri(testCase) + ":" + testCase.getLocation().getLine(); + final String testCaseLocation = getTestCaseUri(testCase) + COLON + testCase.getLocation().getLine(); return md5(testCaseLocation); } + private String getTestCaseId(final TestCase testCase) { + final String testCaseId = getTestCaseUri(testCase) + COLON + testCase.getName(); + return md5(testCaseId); + } + private String getTestCaseUri(final TestCase testCase) { final String testCaseUri = testCase.getUri().getSchemeSpecificPart(); if (testCaseUri.startsWith(CUCUMBER_WORKING_DIR)) { @@ -317,8 +340,8 @@ private Status translateTestCaseStatus(final Result testCaseResult) { } private List getExamplesAsParameters( - final Scenario scenario, final TestCase localCurrentTestCase - ) { + final Scenario scenario, + final TestCase localCurrentTestCase) { final Optional maybeExample = scenario.getExamplesList().stream() .filter(example -> example.getTableBodyList().stream() @@ -374,83 +397,56 @@ private void createDataTableAttachment(final DataTableArgument dataTableArgument new ByteArrayInputStream(dataTableCsv.toString().getBytes(StandardCharsets.UTF_8))); } - private void handleHookStep(final TestStepFinished event) { - final HookTestStep hookStep = (HookTestStep) event.getTestStep(); - final String uuid = getHookStepUuid(hookStep); - final FixtureResult fixtureResult = new FixtureResult().setStatus(translateTestCaseStatus(event.getResult())); - - if (!Status.PASSED.equals(fixtureResult.getStatus())) { - final TestResult testResult = new TestResult().setStatus(translateTestCaseStatus(event.getResult())); - final StatusDetails statusDetails = getStatusDetails(event.getResult().getError()) - .orElseGet(StatusDetails::new); - - final String errorMessage = event.getResult().getError() == null - ? hookStep.getHookType().name() + " is failed." - : hookStep.getHookType().name() - + " is failed: " - + event.getResult().getError().getLocalizedMessage(); - statusDetails.setMessage(errorMessage); - - if (hookStep.getHookType() == HookType.BEFORE) { - final TagParser tagParser = new TagParser(currentFeature.get(), currentTestCase.get()); - statusDetails - .setFlaky(tagParser.isFlaky()) - .setMuted(tagParser.isMuted()) - .setKnown(tagParser.isKnown()); - testResult.setStatus(Status.SKIPPED); - updateTestCaseStatus(testResult.getStatus()); - forbidTestCaseStatusChange.set(true); - } else { - testResult.setStatus(Status.BROKEN); - updateTestCaseStatus(testResult.getStatus()); - } - fixtureResult.setStatusDetails(statusDetails); + private void handleStopHookStep(final Result eventResult, + final HookTestStep hook) { + final String containerUuid = hookStepContainerUuid.get(hook.getId()); + if (Objects.isNull(containerUuid)) { + // maybe throw an exception? + return; } - lifecycle.updateFixture(uuid, result -> result.setStatus(fixtureResult.getStatus()) - .setStatusDetails(fixtureResult.getStatusDetails())); + final String uuid = hook.getId().toString(); + + final Status status = translateTestCaseStatus(eventResult); + final StatusDetails statusDetails = getStatusDetails(eventResult.getError()) + .orElseGet(StatusDetails::new); + + lifecycle.updateFixture(uuid, result -> result + .setStatus(status) + .setStatusDetails(statusDetails) + ); lifecycle.stopFixture(uuid); - } - private void handlePickleStep(final TestStepFinished event) { + lifecycle.stopTestContainer(containerUuid); + lifecycle.writeTestContainer(containerUuid); + } - final Status stepStatus = translateTestCaseStatus(event.getResult()); - final StatusDetails statusDetails; - if (event.getResult().getStatus() == io.cucumber.plugin.event.Status.UNDEFINED) { - updateTestCaseStatus(Status.PASSED); + private void handleStopStep(final TestCase testCase, + final Result eventResult, + final UUID stepId) { + final Feature feature = testSources.getFeature(testCase.getUri()); - statusDetails = - getStatusDetails(new IllegalStateException("Undefined Step. Please add step definition")) - .orElse(new StatusDetails()); - lifecycle.updateTestCase(getTestCaseUuid(currentTestCase.get()), scenarioResult -> - scenarioResult - .setStatusDetails(statusDetails)); - } else { - statusDetails = - getStatusDetails(event.getResult().getError()) - .orElse(new StatusDetails()); - updateTestCaseStatus(stepStatus); - } + final Status stepStatus = translateTestCaseStatus(eventResult); - if (!Status.PASSED.equals(stepStatus) && stepStatus != null) { - forbidTestCaseStatusChange.set(true); - } + final StatusDetails statusDetails + = eventResult.getStatus() == io.cucumber.plugin.event.Status.UNDEFINED + ? new StatusDetails().setMessage("Undefined Step. Please add step definition") + : getStatusDetails(eventResult.getError()) + .orElse(new StatusDetails()); - final TagParser tagParser = new TagParser(currentFeature.get(), currentTestCase.get()); + final TagParser tagParser = new TagParser(feature, testCase); statusDetails .setFlaky(tagParser.isFlaky()) .setMuted(tagParser.isMuted()) .setKnown(tagParser.isKnown()); - lifecycle.updateStep(getStepUuid((PickleStepTestStep) event.getTestStep()), - stepResult -> stepResult.setStatus(stepStatus).setStatusDetails(statusDetails)); - lifecycle.stopStep(getStepUuid((PickleStepTestStep) event.getTestStep())); - } - - private void updateTestCaseStatus(final Status status) { - if (!forbidTestCaseStatusChange.get()) { - lifecycle.updateTestCase(getTestCaseUuid(currentTestCase.get()), - result -> result.setStatus(status)); - } + final String stepUuid = stepId.toString(); + lifecycle.updateStep( + stepUuid, + stepResult -> stepResult + .setStatus(stepStatus) + .setStatusDetails(statusDetails) + ); + lifecycle.stopStep(stepUuid); } } diff --git a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/AllureCucumber6JvmTest.java b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/AllureCucumber6JvmTest.java index e742aeb02..8cfbd31bf 100644 --- a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/AllureCucumber6JvmTest.java +++ b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/AllureCucumber6JvmTest.java @@ -37,6 +37,7 @@ import io.qameta.allure.model.StatusDetails; import io.qameta.allure.model.StepResult; import io.qameta.allure.model.TestResult; +import io.qameta.allure.model.TestResultContainer; import io.qameta.allure.test.AllureFeatures; import io.qameta.allure.test.AllureResults; import io.qameta.allure.test.RunUtils; @@ -59,6 +60,7 @@ import static io.qameta.allure.util.ResultsUtils.PACKAGE_LABEL_NAME; import static io.qameta.allure.util.ResultsUtils.SUITE_LABEL_NAME; import static io.qameta.allure.util.ResultsUtils.TEST_CLASS_LABEL_NAME; +import static io.qameta.allure.util.ResultsUtils.md5; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE; @@ -87,8 +89,10 @@ void shouldSetStatus() { final List testResults = results.getTestResults(); assertThat(testResults) - .extracting(TestResult::getStatus) - .containsExactlyInAnyOrder(Status.PASSED); + .extracting(TestResult::getName, TestResult::getStatus) + .containsExactlyInAnyOrder( + tuple("Add a to b", Status.PASSED) + ); } @AllureFeatures.FailedTests @@ -282,10 +286,10 @@ void shouldAddBackgroundSteps() { .flatExtracting(TestResult::getSteps) .extracting(StepResult::getName) .containsExactly( - "Given cat is sad", - "And cat is murmur", - "When Pet the cat", - "Then Cat is happy" + "Given cat is sad", + "And cat is murmur", + "When Pet the cat", + "Then Cat is happy" ); } @@ -447,15 +451,17 @@ void shouldProcessUndefinedSteps() { final List testResults = results.getTestResults(); assertThat(testResults) - .extracting(TestResult::getStatus) - .containsExactlyInAnyOrder(Status.SKIPPED); + .extracting(TestResult::getName, TestResult::getStatus) + .containsExactlyInAnyOrder( + tuple("Step is not defined", null) + ); assertThat(testResults.get(0).getSteps()) .extracting(StepResult::getName, StepResult::getStatus) .containsExactlyInAnyOrder( - tuple("Given a is 5", Status.PASSED), - tuple("When step is undefined", null), - tuple("Then b is 10", Status.SKIPPED) + tuple("Given a is 5", Status.PASSED), + tuple("When step is undefined", null), + tuple("Then b is 10", Status.SKIPPED) ); } @@ -473,9 +479,9 @@ void shouldProcessPendingExceptionsInSteps() { assertThat(testResults.get(0).getSteps()) .extracting(StepResult::getName, StepResult::getStatus) .containsExactlyInAnyOrder( - tuple("Given a is 5", Status.PASSED), - tuple("When step is yet to be implemented", Status.SKIPPED), - tuple("Then b is 10", Status.SKIPPED) + tuple("Given a is 5", Status.PASSED), + tuple("When step is yet to be implemented", Status.SKIPPED), + tuple("Then b is 10", Status.SKIPPED) ); } @@ -494,10 +500,10 @@ void shouldSupportDryRunForSimpleFeatures() { assertThat(testResults.get(0).getSteps()) .extracting(StepResult::getName, StepResult::getStatus) .containsExactlyInAnyOrder( - tuple("Given a is 5", Status.PASSED), - tuple("And b is 10", Status.PASSED), - tuple("When I add a to b", Status.PASSED), - tuple("Then result is 15", Status.PASSED) + tuple("Given a is 5", Status.PASSED), + tuple("And b is 10", Status.PASSED), + tuple("When I add a to b", Status.PASSED), + tuple("Then result is 15", Status.PASSED) ); } @@ -508,32 +514,29 @@ void shouldSupportDryRunForHooks() { final AllureResults results = runFeature("features/hooks.feature", "--dry-run", "-t", "@WithHooks or @BeforeHookWithException or @AfterHookWithException"); - final List testResults = results.getTestResults(); - assertThat(testResults) - .extracting(TestResult::getName, TestResult::getStatus) - .startsWith( - tuple("Simple scenario with Before and After hooks", Status.PASSED) - ); + final TestResult tr1 = results.getTestResultByName("Simple scenario with Before and After hooks"); - assertThat(results.getTestResultContainers().get(0).getBefores()) + assertThat(results.getTestResultContainersForTestResult(tr1)) + .flatExtracting(TestResultContainer::getBefores) .extracting(FixtureResult::getName, FixtureResult::getStatus) .containsExactlyInAnyOrder( tuple("io.qameta.allure.cucumber6jvm.samples.HookSteps.beforeHook()", Status.PASSED) ); - assertThat(results.getTestResultContainers().get(0).getAfters()) + assertThat(results.getTestResultContainersForTestResult(tr1)) + .flatExtracting(TestResultContainer::getAfters) .extracting(FixtureResult::getName, FixtureResult::getStatus) .containsExactlyInAnyOrder( tuple("io.qameta.allure.cucumber6jvm.samples.HookSteps.afterHook()", Status.PASSED) ); - assertThat(testResults.get(0).getSteps()) + assertThat(tr1.getSteps()) .extracting(StepResult::getName, StepResult::getStatus) .containsExactlyInAnyOrder( - tuple("Given a is 7", Status.PASSED), - tuple("And b is 8", Status.PASSED), - tuple("When I add a to b", Status.PASSED), - tuple("Then result is 15", Status.PASSED) + tuple("Given a is 7", Status.PASSED), + tuple("And b is 8", Status.PASSED), + tuple("When I add a to b", Status.PASSED), + tuple("Then result is 15", Status.PASSED) ); } @@ -568,6 +571,42 @@ void shouldPersistDifferentHistoryIdComparedToTheSameTestCaseInDifferentLocation .isNotEqualTo(results2.getTestResults().get(0).getHistoryId()); } + @AllureFeatures.History + @Test + void shouldSetTestCaseIdForScenarios() { + final AllureResults results = runFeature("features/simple.feature"); + + final List testResults = results.getTestResults(); + assertThat(testResults) + .extracting(TestResult::getName, TestResult::getTestCaseId) + .containsExactlyInAnyOrder( + tuple( + "Add a to b", + md5("src/test/resources/features/simple.feature:Add a to b") + ) + ); + } + + @AllureFeatures.History + @Test + void shouldSetTestCaseIdForExamples() { + final AllureResults results = runFeature("features/examples.feature", "--threads", "2"); + + final List testResults = results.getTestResults(); + assertThat(testResults) + .extracting(TestResult::getName, TestResult::getTestCaseId) + .containsExactlyInAnyOrder( + tuple( + "Scenario with Positive Examples", + md5("src/test/resources/features/examples.feature:Scenario with Positive Examples") + ), + tuple( + "Scenario with Positive Examples", + md5("src/test/resources/features/examples.feature:Scenario with Positive Examples") + ) + ); + } + @AllureFeatures.Parallel @Test void shouldProcessScenariosInParallelMode() { @@ -579,30 +618,33 @@ void shouldProcessScenariosInParallelMode() { .hasSize(3); assertThat(testResults) - .extracting(testResult -> testResult.getSteps().stream().map(StepResult::getName).collect(Collectors.toList())) + .flatExtracting(TestResult::getSteps) + .extracting(StepResult::getName) .containsSubsequence( - Arrays.asList("Given a is 1", - "And b is 3", - "When I add a to b", - "Then result is 4") + "Given a is 1", + "And b is 3", + "When I add a to b", + "Then result is 4" ); assertThat(testResults) - .extracting(testResult -> testResult.getSteps().stream().map(StepResult::getName).collect(Collectors.toList())) + .flatExtracting(TestResult::getSteps) + .extracting(StepResult::getName) .containsSubsequence( - Arrays.asList("Given a is 2", - "And b is 4", - "When I add a to b", - "Then result is 6") + "Given a is 2", + "And b is 4", + "When I add a to b", + "Then result is 6" ); assertThat(testResults) - .extracting(testResult -> testResult.getSteps().stream().map(StepResult::getName).collect(Collectors.toList())) + .flatExtracting(TestResult::getSteps) + .extracting(StepResult::getName) .containsSubsequence( - Arrays.asList("Given a is 7", - "And b is 8", - "When I add a to b", - "Then result is 15") + "Given a is 7", + "And b is 8", + "When I add a to b", + "Then result is 15" ); } @@ -613,64 +655,61 @@ void shouldDisplayHooksAsStages() { final AllureResults results = runFeature("features/hooks.feature", "-t", "@WithHooks or @BeforeHookWithException or @AfterHookWithException"); - final List testResults = results.getTestResults(); - assertThat(testResults) - .extracting(TestResult::getName, TestResult::getStatus) - .containsExactlyInAnyOrder( - tuple("Simple scenario with Before and After hooks", Status.PASSED), - tuple("Simple scenario with Before hook with Exception", Status.SKIPPED), - tuple("Simple scenario with After hook with Exception", Status.BROKEN) - ); + final TestResult tr1 = results.getTestResultByName("Simple scenario with Before and After hooks"); + final TestResult tr2 = results.getTestResultByName("Simple scenario with Before hook with Exception"); + final TestResult tr3 = results.getTestResultByName("Simple scenario with After hook with Exception"); - assertThat(testResults.get(0).getSteps()) + assertThat(tr1.getSteps()) .extracting(StepResult::getName, StepResult::getStatus) .containsExactlyInAnyOrder( - tuple("Given a is 7", Status.PASSED), - tuple("And b is 8", Status.PASSED), - tuple("When I add a to b", Status.PASSED), - tuple("Then result is 15", Status.PASSED) + tuple("Given a is 7", Status.PASSED), + tuple("And b is 8", Status.PASSED), + tuple("When I add a to b", Status.PASSED), + tuple("Then result is 15", Status.PASSED) ); - - assertThat(results.getTestResultContainers().get(0).getBefores()) + assertThat(results.getTestResultContainersForTestResult(tr1)) + .flatExtracting(TestResultContainer::getBefores) .extracting(FixtureResult::getName, FixtureResult::getStatus) .containsExactlyInAnyOrder( tuple("io.qameta.allure.cucumber6jvm.samples.HookSteps.beforeHook()", Status.PASSED) ); - assertThat(results.getTestResultContainers().get(0).getAfters()) + assertThat(results.getTestResultContainersForTestResult(tr1)) + .flatExtracting(TestResultContainer::getAfters) .extracting(FixtureResult::getName, FixtureResult::getStatus) .containsExactlyInAnyOrder( tuple("io.qameta.allure.cucumber6jvm.samples.HookSteps.afterHook()", Status.PASSED) ); - assertThat(testResults.get(1).getSteps()) + assertThat(tr2.getSteps()) .extracting(StepResult::getName, StepResult::getStatus) .containsExactlyInAnyOrder( - tuple("Given a is 7", Status.SKIPPED), - tuple("And b is 8", Status.SKIPPED), - tuple("When I add a to b", Status.SKIPPED), - tuple("Then result is 15", Status.SKIPPED) + tuple("Given a is 7", Status.SKIPPED), + tuple("And b is 8", Status.SKIPPED), + tuple("When I add a to b", Status.SKIPPED), + tuple("Then result is 15", Status.SKIPPED) ); - assertThat(results.getTestResultContainers().get(1).getBefores()) + assertThat(results.getTestResultContainersForTestResult(tr2)) + .flatExtracting(TestResultContainer::getBefores) .extracting(FixtureResult::getName, FixtureResult::getStatus) .containsExactlyInAnyOrder( tuple("io.qameta.allure.cucumber6jvm.samples.HookSteps.beforeHookWithException()", Status.FAILED) ); - - assertThat(testResults.get(2).getSteps()) + assertThat(tr3.getSteps()) .extracting(StepResult::getName, StepResult::getStatus) .containsExactlyInAnyOrder( - tuple("Given a is 7", Status.PASSED), - tuple("And b is 8", Status.PASSED), - tuple("When I add a to b", Status.PASSED), - tuple("Then result is 15", Status.PASSED) + tuple("Given a is 7", Status.PASSED), + tuple("And b is 8", Status.PASSED), + tuple("When I add a to b", Status.PASSED), + tuple("Then result is 15", Status.PASSED) ); - assertThat(results.getTestResultContainers().get(2).getAfters()) + assertThat(results.getTestResultContainersForTestResult(tr3)) + .flatExtracting(TestResultContainer::getAfters) .extracting(FixtureResult::getName, FixtureResult::getStatus) .containsExactlyInAnyOrder( tuple("io.qameta.allure.cucumber6jvm.samples.HookSteps.afterHookWithException()", Status.FAILED) @@ -687,14 +726,14 @@ void shouldHandleAmbigiousStepsExceptions() { assertThat(testResults) .extracting(TestResult::getName, TestResult::getStatus) .containsExactlyInAnyOrder( - tuple("Simple scenario with ambigious steps", Status.SKIPPED) + tuple("Simple scenario with ambigious steps", null) ); assertThat(testResults.get(0).getSteps()) .extracting(StepResult::getName, StepResult::getStatus) .containsExactly( - tuple("When ambigious step present", null), - tuple("Then something bad should happen", Status.SKIPPED) + tuple("When ambigious step present", null), + tuple("Then something bad should happen", Status.SKIPPED) ); } @@ -714,6 +753,36 @@ void shouldSupportProvidedLabels() { ); } + @Test + void shouldSupportRuntimeApiInStepsWhenHooksAreUsed() { + final AllureResults results = runFeature("features/runtimeapi.feature"); + + final List testResults = results.getTestResults(); + + assertThat(testResults) + .hasSize(1) + .flatExtracting(TestResult::getSteps) + .extracting(StepResult::getName) + .containsExactly( + "When step 1", + "When step 2", + "And step 3", + "Then step 4", + "And step 5" + ); + + assertThat(testResults) + .flatExtracting(TestResult::getLinks) + .extracting(Link::getName, Link::getUrl) + .containsExactly( + tuple("step1", "https://example.org/step1"), + tuple("step2", "https://example.org/step2"), + tuple("step3", "https://example.org/step3"), + tuple("step4", "https://example.org/step4"), + tuple("step5", "https://example.org/step5") + ); + } + @SystemProperty(name = "cucumber.junit-platform.naming-strategy", value = "long") @Step private AllureResults runFeature(final String featureResource, diff --git a/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/RuntimeApiSteps.java b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/RuntimeApiSteps.java new file mode 100644 index 000000000..c769404ef --- /dev/null +++ b/allure-cucumber6-jvm/src/test/java/io/qameta/allure/cucumber6jvm/samples/RuntimeApiSteps.java @@ -0,0 +1,83 @@ +/* + * Copyright 2016-2024 Qameta Software Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.qameta.allure.cucumber6jvm.samples; + +import io.cucumber.java.Before; +import io.cucumber.java.en.And; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import io.qameta.allure.Allure; + +/** + * @author charlie (Dmitry Baev). + */ +public class RuntimeApiSteps { + + @Before("@beforeScenario") + public void beforeScenario(){ + // nothing + } + + @Before("@beforeFeature") + public void beforeFeature(){ + // nothing + } + + @When("^step 1$") + public void step1() { + Allure.step("step1 nested"); + Allure.link("step1", "https://example.org/step1"); + Allure.getLifecycle().getCurrentTestCase().ifPresent(uuid -> { + System.out.println("step1: " + uuid); + }); + } + + @When("^step 2$") + public void step2() { + Allure.step("step2 nested"); + Allure.link("step2", "https://example.org/step2"); + Allure.getLifecycle().getCurrentTestCase().ifPresent(uuid -> { + System.out.println("step2: " + uuid); + }); + } + + @And("^step 3$") + public void step3() { + Allure.step("step3 nested"); + Allure.link("step3", "https://example.org/step3"); + Allure.getLifecycle().getCurrentTestCase().ifPresent(uuid -> { + System.out.println("step3: " + uuid); + }); + } + + @Then("^step 4$") + public void step4() { + Allure.step("step4 nested"); + Allure.link("step4", "https://example.org/step4"); + Allure.getLifecycle().getCurrentTestCase().ifPresent(uuid -> { + System.out.println("step4: " + uuid); + }); + } + + @And("^step 5$") + public void step5() { + Allure.step("step5 nested"); + Allure.link("step5", "https://example.org/step5"); + Allure.getLifecycle().getCurrentTestCase().ifPresent(uuid -> { + System.out.println("step5: " + uuid); + }); + } +} diff --git a/allure-cucumber6-jvm/src/test/resources/features/runtimeapi.feature b/allure-cucumber6-jvm/src/test/resources/features/runtimeapi.feature new file mode 100644 index 000000000..ec43eccb7 --- /dev/null +++ b/allure-cucumber6-jvm/src/test/resources/features/runtimeapi.feature @@ -0,0 +1,10 @@ +@beforeFeature +Feature: Should support runtime API in all steps + + @beforeScenario + Scenario: Scenario with Runtime API usage + When step 1 + When step 2 + And step 3 + Then step 4 + And step 5