diff --git a/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java b/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java index d3c0678..8369dc1 100644 --- a/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java +++ b/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java @@ -47,6 +47,7 @@ import gherkin.pickles.PickleTable; import gherkin.pickles.PickleTag; import io.reactivex.Maybe; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,6 +67,7 @@ import static com.epam.reportportal.cucumber.Utils.*; import static com.epam.reportportal.cucumber.util.ItemTreeUtils.createKey; import static com.epam.reportportal.cucumber.util.ItemTreeUtils.retrieveLeaf; +import static java.lang.String.format; import static java.util.Optional.of; import static java.util.Optional.ofNullable; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -89,6 +91,8 @@ public abstract class AbstractReporter implements ConcurrentEventListener { private static final String FILE_PREFIX = "file:"; private static final String HOOK_ = "Hook: "; private static final String DOCSTRING_DECORATOR = "\n\"\"\"\n"; + private static final String ERROR_FORMAT = "Error:\n%s"; + private static final String DESCRIPTION_ERROR_FORMAT = "%s\n" + ERROR_FORMAT; public static final TestItemTree ITEM_TREE = new TestItemTree(); private static volatile ReportPortal REPORT_PORTAL = ReportPortal.builder().build(); @@ -108,6 +112,15 @@ public abstract class AbstractReporter implements ConcurrentEventListener { // End of feature occurs once launch is finished. private final Map featureEndTime = new ConcurrentHashMap<>(); + /** + * This map uses to record the description of the scenario and the step to append the error to the description. + */ + private final Map descriptionsMap = new ConcurrentHashMap<>(); + /** + * This map uses to record errors to append to the description. + */ + private final Map errorMap = new ConcurrentHashMap<>(); + public static ReportPortal getReportPortal() { return REPORT_PORTAL; } @@ -239,10 +252,10 @@ protected void beforeScenario(RunningContext.FeatureContext featureContext, Runn AbstractReporter.COLON_INFIX, scenarioContext.getTestCase().getName() ); - Maybe id = startScenario(featureContext.getFeatureId(), - buildStartScenarioRequest(scenarioContext.getTestCase(), scenarioName, featureContext.getUri(), scenarioContext.getLine()) - ); + StartTestItemRQ startTestItemRQ = buildStartScenarioRequest(scenarioContext.getTestCase(), scenarioName, featureContext.getUri(), scenarioContext.getLine()); + Maybe id = startScenario(featureContext.getFeatureId(), startTestItemRQ); scenarioContext.setId(id); + id.subscribe(scenarioId -> descriptionsMap.put(scenarioId, ofNullable(startTestItemRQ.getDescription()).orElse(StringUtils.EMPTY))); if (launch.get().getParameters().isCallbackReportingEnabled()) { addToTree(featureContext, scenarioContext); } @@ -279,12 +292,34 @@ protected ItemStatus mapItemStatus(@Nullable Result.Type status) { */ @Nonnull @SuppressWarnings("unused") - protected FinishTestItemRQ buildFinishTestItemRequest(@Nonnull Maybe itemId, @Nullable Date finishTime, - @Nullable ItemStatus status) { - FinishTestItemRQ rq = new FinishTestItemRQ(); - ofNullable(status).ifPresent(s -> rq.setStatus(s.name())); - rq.setEndTime(ofNullable(finishTime).orElse(Calendar.getInstance().getTime())); - return rq; + protected Maybe buildFinishTestItemRequest(@Nonnull Maybe itemId, @Nullable Date finishTime, + @Nullable ItemStatus status) { + return itemId.map(id -> { + FinishTestItemRQ rq = new FinishTestItemRQ(); + if (status == ItemStatus.FAILED) { + Optional currentDescription = Optional.ofNullable(descriptionsMap.get(itemId.blockingGet())); + Optional currentError = Optional.ofNullable(errorMap.get(itemId.blockingGet())); + currentDescription.flatMap(description -> currentError + .map(errorMessage -> resolveDescriptionErrorMessage(description, errorMessage))) + .ifPresent(rq::setDescription); + } + ofNullable(status).ifPresent(s -> rq.setStatus(s.name())); + rq.setEndTime(finishTime); + return rq; + }); + } + + /** + * Resolve description + * @param currentDescription Current description + * @param error Error message + * @return Description with error + */ + private String resolveDescriptionErrorMessage(String currentDescription, Throwable error) { + return Optional.ofNullable(currentDescription) + .filter(StringUtils::isNotBlank) + .map(description -> format(DESCRIPTION_ERROR_FORMAT, currentDescription, error)) + .orElse(format(ERROR_FORMAT, error)); } /** @@ -300,10 +335,11 @@ protected Date finishTestItem(Maybe itemId, Result.Type status) { return null; } - FinishTestItemRQ rq = buildFinishTestItemRequest(itemId, null, mapItemStatus(status)); + Date endTime = Calendar.getInstance().getTime(); + Maybe rqMaybe = buildFinishTestItemRequest(itemId, endTime, mapItemStatus(status)); //noinspection ReactiveStreamsUnusedPublisher - launch.get().finishTestItem(itemId, rq); - return rq.getEndTime(); + rqMaybe.subscribe(rq -> launch.get().finishTestItem(itemId, rq)); + return endTime; } /** @@ -330,6 +366,10 @@ protected void afterScenario(TestCaseFinished event) { RunningContext.ScenarioContext context = getCurrentScenarioContext(); String featureUri = context.getFeatureUri(); currentScenarioContextMap.remove(Pair.of(context.getLine(), featureUri)); + if (mapItemStatus(event.result.getStatus()) == ItemStatus.FAILED) { + Optional.ofNullable(event.result.getError()) + .ifPresent(error -> context.getId().subscribe(id -> errorMap.put(id, error))); + } Date endTime = finishTestItem(context.getId(), event.result.getStatus()); featureEndTime.put(featureUri, endTime); currentScenarioContext.remove(); @@ -436,7 +476,9 @@ protected void beforeStep(TestStep testStep) { context.setCurrentStepId(stepId); String stepText = step.getText(); context.setCurrentText(stepText); - + if (rq.isHasStats()) { + stepId.subscribe(id -> descriptionsMap.put(id, ofNullable(rq.getDescription()).orElse(StringUtils.EMPTY))); + } if (launch.get().getParameters().isCallbackReportingEnabled()) { addToTree(context, stepText, stepId); } @@ -450,6 +492,10 @@ protected void beforeStep(TestStep testStep) { protected void afterStep(Result result) { reportResult(result, null); RunningContext.ScenarioContext context = getCurrentScenarioContext(); + if (mapItemStatus(result.getStatus()) == ItemStatus.FAILED){ + Optional.ofNullable(result.getError()) + .ifPresent(error -> context.getCurrentStepId().subscribe(id -> errorMap.put(id, error))); + } finishTestItem(context.getCurrentStepId(), result.getStatus()); context.setCurrentStepId(null); } @@ -620,9 +666,10 @@ protected void finishFeature(Maybe itemId, Date dateTime) { LOGGER.error("BUG: Trying to finish unspecified test item."); return; } - FinishTestItemRQ rq = buildFinishTestItemRequest(itemId, dateTime, null); + Date endTime = ofNullable(dateTime).orElse(Calendar.getInstance().getTime()); + Maybe rqMaybe = buildFinishTestItemRequest(itemId, endTime, null); //noinspection ReactiveStreamsUnusedPublisher - launch.get().finishTestItem(itemId, rq); + rqMaybe.subscribe(rq -> launch.get().finishTestItem(itemId, rq)); } private void removeFromTree(RunningContext.FeatureContext featureContext) { diff --git a/src/test/java/com/epam/reportportal/cucumber/FailedTest.java b/src/test/java/com/epam/reportportal/cucumber/FailedTest.java index b7292c9..5c55032 100644 --- a/src/test/java/com/epam/reportportal/cucumber/FailedTest.java +++ b/src/test/java/com/epam/reportportal/cucumber/FailedTest.java @@ -42,8 +42,13 @@ import static com.epam.reportportal.cucumber.integration.util.TestUtils.filterLogs; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.startsWith; import static org.mockito.Mockito.*; /** @@ -52,6 +57,14 @@ public class FailedTest { private static final String EXPECTED_ERROR = "java.lang.IllegalStateException: " + FailedSteps.ERROR_MESSAGE; + private static final String ERROR_LOG_TEXT = "Error:\n" + EXPECTED_ERROR; + private static final String DESCRIPTION_ERROR_LOG_TEXT = + "file:src/test/resources/features/FailedScenario.feature\n" + + ERROR_LOG_TEXT; + + private static final Pair SCENARIO_CODE_REFERENCES_WITH_ERROR = Pair.of("file:", + "src/test/resources/features/FailedScenario.feature\n" + ERROR_LOG_TEXT + ); @CucumberOptions(features = "src/test/resources/features/FailedScenario.feature", glue = { "com.epam.reportportal.cucumber.integration.feature" }, plugin = { "pretty", @@ -136,4 +149,48 @@ public void verify_failed_step_reporting_step_reporter() { SaveLogRQ expectedError = expectedErrorList.get(0); assertThat(expectedError.getItemUuid(), equalTo(stepId)); } + + @Test + @SuppressWarnings("unchecked") + public void verify_failed_nested_step_description_scenario_reporter() { + TestUtils.runTests(FailedScenarioReporter.class); + + verify(client).startTestItem(any()); + verify(client).startTestItem(same(suiteId), any()); + verify(client).startTestItem(same(testId), any()); + verify(client).startTestItem(same(stepId), any()); + ArgumentCaptor finishCaptor = ArgumentCaptor.forClass(FinishTestItemRQ.class); + verify(client).finishTestItem(same(nestedStepId), finishCaptor.capture()); + verify(client).finishTestItem(same(stepId), any()); + verify(client).finishTestItem(same(testId), finishCaptor.capture()); + + List finishRqs = finishCaptor.getAllValues(); + finishRqs.subList(0, finishRqs.size() - 1).forEach(e -> assertThat(e.getStatus(), equalTo(ItemStatus.FAILED.name()))); + + FinishTestItemRQ step = finishRqs.get(0); + assertThat(step.getDescription(), not(equalTo(ERROR_LOG_TEXT))); + } + + @Test + @SuppressWarnings("unchecked") + public void verify_failed_step_description_step_reporter() { + TestUtils.runTests(FailedStepReporter.class); + + verify(client).startTestItem(any()); + verify(client).startTestItem(same(suiteId), any()); + verify(client).startTestItem(same(testId), any()); + ArgumentCaptor finishCaptor = ArgumentCaptor.forClass(FinishTestItemRQ.class); + verify(client).finishTestItem(same(stepId), finishCaptor.capture()); + verify(client).finishTestItem(same(testId), finishCaptor.capture()); + + List finishRqs = finishCaptor.getAllValues(); + finishRqs.forEach(e -> assertThat(e.getStatus(), equalTo(ItemStatus.FAILED.name()))); + + FinishTestItemRQ step = finishRqs.get(0); + assertThat(step.getDescription(), equalTo(ERROR_LOG_TEXT)); + FinishTestItemRQ test = finishRqs.get(1); + assertThat(test.getDescription(), + allOf(notNullValue(), startsWith(SCENARIO_CODE_REFERENCES_WITH_ERROR.getKey()), endsWith(SCENARIO_CODE_REFERENCES_WITH_ERROR.getValue())) + ); + } }