io.qameta.allure.cucumber6jvm.AllureCucumber6Jvm Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of allure-cucumber6-jvm Show documentation
Show all versions of allure-cucumber6-jvm Show documentation
Module allure-cucumber6-jvm of Allure Framework.
/*
* Copyright 2019 Qameta Software OÜ
*
* 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;
import io.cucumber.messages.Messages.GherkinDocument.Feature;
import io.cucumber.messages.Messages.GherkinDocument.Feature.Scenario;
import io.cucumber.messages.Messages.GherkinDocument.Feature.Scenario.Examples;
import io.cucumber.messages.Messages.GherkinDocument.Feature.TableRow;
import io.cucumber.plugin.ConcurrentEventListener;
import io.cucumber.plugin.event.DataTableArgument;
import io.cucumber.plugin.event.EmbedEvent;
import io.cucumber.plugin.event.EventHandler;
import io.cucumber.plugin.event.EventPublisher;
import io.cucumber.plugin.event.HookTestStep;
import io.cucumber.plugin.event.HookType;
import io.cucumber.plugin.event.PickleStepTestStep;
import io.cucumber.plugin.event.Result;
import io.cucumber.plugin.event.StepArgument;
import io.cucumber.plugin.event.TestCase;
import io.cucumber.plugin.event.TestCaseFinished;
import io.cucumber.plugin.event.TestCaseStarted;
import io.cucumber.plugin.event.TestSourceRead;
import io.cucumber.plugin.event.TestStepFinished;
import io.cucumber.plugin.event.TestStepStarted;
import io.cucumber.plugin.event.WriteEvent;
import io.qameta.allure.Allure;
import io.qameta.allure.AllureLifecycle;
import io.qameta.allure.cucumber6jvm.testsourcemodel.TestSourcesModelProxy;
import io.qameta.allure.model.FixtureResult;
import io.qameta.allure.model.Parameter;
import io.qameta.allure.model.Status;
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 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.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static io.qameta.allure.util.ResultsUtils.createParameter;
import static io.qameta.allure.util.ResultsUtils.getStatus;
import static io.qameta.allure.util.ResultsUtils.getStatusDetails;
import static io.qameta.allure.util.ResultsUtils.md5;
/**
* Allure plugin for Cucumber JVM 6.0.
*/
@SuppressWarnings({
"ClassDataAbstractionCoupling",
"ClassFanOutComplexity",
"PMD.ExcessiveImports",
"PMD.GodClass",
})
public class AllureCucumber6Jvm implements ConcurrentEventListener {
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;
private final EventHandler stepStartedHandler = this::handleTestStepStarted;
private final EventHandler stepFinishedHandler = this::handleTestStepFinished;
private final EventHandler writeEventHandler = this::handleWriteEvent;
private final EventHandler embedEventHandler = this::handleEmbedEvent;
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().toString();
@SuppressWarnings("unused")
public AllureCucumber6Jvm() {
this(Allure.getLifecycle());
}
public AllureCucumber6Jvm(final AllureLifecycle lifecycle) {
this.lifecycle = lifecycle;
}
/*
Event Handlers
*/
@Override
public void setEventPublisher(final EventPublisher publisher) {
publisher.registerHandlerFor(TestSourceRead.class, featureStartedHandler);
publisher.registerHandlerFor(TestCaseStarted.class, caseStartedHandler);
publisher.registerHandlerFor(TestCaseFinished.class, caseFinishedHandler);
publisher.registerHandlerFor(TestStepStarted.class, stepStartedHandler);
publisher.registerHandlerFor(TestStepFinished.class, stepFinishedHandler);
publisher.registerHandlerFor(WriteEvent.class, writeEventHandler);
publisher.registerHandlerFor(EmbedEvent.class, embedEventHandler);
}
private void handleFeatureStartedHandler(final TestSourceRead event) {
testSources.addTestSourceReadEvent(event.getUri(), 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 Deque tags = new LinkedList<>(currentTestCase.get().getTags());
final Feature feature = currentFeature.get();
final LabelBuilder labelBuilder = new LabelBuilder(feature, currentTestCase.get(), tags);
final String name = currentTestCase.get().getName();
final String featureName = feature.getName();
final TestResult result = new TestResult()
.setUuid(getTestCaseUuid(currentTestCase.get()))
.setHistoryId(getHistoryId(currentTestCase.get()))
.setFullName(featureName + ": " + name)
.setName(name)
.setLabels(labelBuilder.getScenarioLabels())
.setLinks(labelBuilder.getScenarioLinks());
final Scenario scenarioDefinition =
testSources.getScenarioDefinition(
currentFeatureFile.get(),
currentTestCase.get().getLocation().getLine()
);
if (scenarioDefinition.getExamplesCount() > 0) {
result.setParameters(
getExamplesAsParameters(scenarioDefinition, currentTestCase.get())
);
}
final String description = Stream.of(feature.getDescription(), scenarioDefinition.getDescription())
.filter(Objects::nonNull)
.filter(s -> !s.isEmpty())
.collect(Collectors.joining("\n"));
if (!description.isEmpty()) {
result.setDescription(description);
}
final TestResultContainer resultContainer = new TestResultContainer()
.setName(String.format("%s: %s", scenarioDefinition.getKeyword(), scenarioDefinition.getName()))
.setUuid(getTestContainerUuid())
.setChildren(Collections.singletonList(getTestCaseUuid(currentTestCase.get())));
lifecycle.scheduleTestCase(result);
lifecycle.startTestContainer(getTestContainerUuid(), resultContainer);
lifecycle.startTestCase(getTestCaseUuid(currentTestCase.get()));
}
private void handleTestCaseFinished(final TestCaseFinished event) {
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 StepArgument stepArgument = pickleStep.getStep().getArgument();
if (stepArgument instanceof DataTableArgument) {
final DataTableArgument dataTableArgument = (DataTableArgument) stepArgument;
createDataTableAttachment(dataTableArgument);
}
} else if (event.getTestStep() instanceof HookTestStep) {
initHook((HookTestStep) event.getTestStep());
}
}
private void initHook(final HookTestStep hook) {
final FixtureResult hookResult = new FixtureResult()
.setName(hook.getCodeLocation())
.setStart(System.currentTimeMillis());
if (hook.getHookType() == HookType.BEFORE) {
lifecycle.startPrepareFixture(getTestContainerUuid(), getHookStepUuid(hook), hookResult);
} else {
lifecycle.startTearDownFixture(getTestContainerUuid(), getHookStepUuid(hook), hookResult);
}
}
private void handleTestStepFinished(final TestStepFinished event) {
if (event.getTestStep() instanceof HookTestStep) {
handleHookStep(event);
} else {
handlePickleStep(event);
}
}
private void handleWriteEvent(final WriteEvent event) {
lifecycle.addAttachment(
"Text output",
TEXT_PLAIN,
TXT_EXTENSION,
Objects.toString(event.getText()).getBytes(StandardCharsets.UTF_8)
);
}
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();
return md5(testCaseLocation);
}
private String getTestCaseUri(final TestCase testCase) {
final String testCaseUri = testCase.getUri().toString();
if (testCaseUri.startsWith(CUCUMBER_WORKING_DIR)) {
return testCaseUri.substring(CUCUMBER_WORKING_DIR.length());
}
return testCaseUri;
}
private Status translateTestCaseStatus(final Result testCaseResult) {
switch (testCaseResult.getStatus()) {
case FAILED:
return getStatus(testCaseResult.getError())
.orElse(Status.FAILED);
case PASSED:
return Status.PASSED;
case SKIPPED:
case PENDING:
return Status.SKIPPED;
case AMBIGUOUS:
case UNDEFINED:
default:
return null;
}
}
private List getExamplesAsParameters(
final Scenario scenario, final TestCase localCurrentTestCase
) {
final Optional maybeExample =
scenario.getExamplesList().stream()
.filter(example -> example.getTableBodyList().stream()
.anyMatch(row -> row.getLocation().getLine()
== localCurrentTestCase.getLocation().getLine())
)
.findFirst();
if (!maybeExample.isPresent()) {
return Collections.emptyList();
}
final Examples examples = maybeExample.get();
final Optional maybeRow = examples.getTableBodyList().stream()
.filter(example -> example.getLocation().getLine() == localCurrentTestCase.getLocation().getLine())
.findFirst();
if (!maybeRow.isPresent()) {
return Collections.emptyList();
}
final TableRow row = maybeRow.get();
return IntStream.range(0, examples.getTableHeader().getCellsList().size())
.mapToObj(index -> {
final String name = examples.getTableHeader().getCellsList().get(index).getValue();
final String value = row.getCellsList().get(index).getValue();
return createParameter(name, value);
})
.collect(Collectors.toList());
}
private void createDataTableAttachment(final DataTableArgument dataTableArgument) {
final List> rowsInTable = dataTableArgument.cells();
final StringBuilder dataTableCsv = new StringBuilder();
for (List columns : rowsInTable) {
if (!columns.isEmpty()) {
for (int i = 0; i < columns.size(); i++) {
if (i == columns.size() - 1) {
dataTableCsv.append(columns.get(i));
} else {
dataTableCsv.append(columns.get(i));
dataTableCsv.append('\t');
}
}
dataTableCsv.append('\n');
}
}
final String attachmentSource = lifecycle
.prepareAttachment("Data table", "text/tab-separated-values", "csv");
lifecycle.writeAttachment(attachmentSource,
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);
}
lifecycle.updateFixture(uuid, result -> result.setStatus(fixtureResult.getStatus())
.setStatusDetails(fixtureResult.getStatusDetails()));
lifecycle.stopFixture(uuid);
}
private void handlePickleStep(final TestStepFinished event) {
final Status stepStatus = translateTestCaseStatus(event.getResult());
final StatusDetails statusDetails;
if (event.getResult().getStatus() == io.cucumber.plugin.event.Status.UNDEFINED) {
updateTestCaseStatus(Status.PASSED);
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);
}
if (!Status.PASSED.equals(stepStatus) && stepStatus != null) {
forbidTestCaseStatusChange.set(true);
}
final TagParser tagParser = new TagParser(currentFeature.get(), currentTestCase.get());
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));
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy