From 07d589b410afb1a641ea311545032d82bb2c76c5 Mon Sep 17 00:00:00 2001 From: Artem_Oliinyk Date: Thu, 14 Mar 2024 21:54:50 +0100 Subject: [PATCH 1/3] [Cucumber_4] Implemented logic that appends an error to the scenario and step description. --- .../cucumber/AbstractReporter.java | 55 +++++++++++++++---- .../reportportal/cucumber/FailedTest.java | 48 ++++++++++++++++ 2 files changed, 91 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java b/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java index fb50a43..fc36dd2 100644 --- a/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java +++ b/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java @@ -47,6 +47,8 @@ import gherkin.pickles.PickleTag; import io.reactivex.Maybe; import okhttp3.MediaType; + +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,6 +68,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 +92,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 +113,11 @@ 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<>(); + public static ReportPortal getReportPortal() { return REPORT_PORTAL; } @@ -239,10 +249,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); + descriptionsMap.put(scenarioContext.getId().blockingGet(), ofNullable(startTestItemRQ.getDescription()).orElse(StringUtils.EMPTY)); if (launch.get().getParameters().isCallbackReportingEnabled()) { addToTree(featureContext, scenarioContext); } @@ -280,13 +290,32 @@ protected ItemStatus mapItemStatus(@Nullable Result.Type status) { @Nonnull @SuppressWarnings("unused") protected FinishTestItemRQ buildFinishTestItemRequest(@Nonnull Maybe itemId, @Nullable Date finishTime, - @Nullable ItemStatus status) { + @Nullable ItemStatus status, @Nullable Throwable error) { FinishTestItemRQ rq = new FinishTestItemRQ(); + if (status == ItemStatus.FAILED) { + Optional currentDescription = Optional.ofNullable(descriptionsMap.get(itemId.blockingGet())); + currentDescription.flatMap(description -> Optional.ofNullable(error) + .map(errorMessage -> resolveDescriptionErrorMessage(description, errorMessage))) + .ifPresent(rq::setDescription); + } ofNullable(status).ifPresent(s -> rq.setStatus(s.name())); rq.setEndTime(ofNullable(finishTime).orElse(Calendar.getInstance().getTime())); 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)); + } + /** * Finish a test item with specified status * @@ -294,13 +323,13 @@ protected FinishTestItemRQ buildFinishTestItemRequest(@Nonnull Maybe ite * @param status the status of the item * @return a date and time object of the finish event */ - protected Date finishTestItem(Maybe itemId, Result.Type status) { + protected Date finishTestItem(Maybe itemId, Result.Type status, @Nullable Throwable error) { if (itemId == null) { LOGGER.error("BUG: Trying to finish unspecified test item."); return null; } - FinishTestItemRQ rq = buildFinishTestItemRequest(itemId, null, mapItemStatus(status)); + FinishTestItemRQ rq = buildFinishTestItemRequest(itemId, null, mapItemStatus(status), error); //noinspection ReactiveStreamsUnusedPublisher launch.get().finishTestItem(itemId, rq); return rq.getEndTime(); @@ -312,7 +341,7 @@ protected Date finishTestItem(Maybe itemId, Result.Type status) { * @param itemId an ID of the item */ protected void finishTestItem(Maybe itemId) { - finishTestItem(itemId, null); + finishTestItem(itemId, null, null); } private void removeFromTree(RunningContext.FeatureContext featureContext, RunningContext.ScenarioContext scenarioContext) { @@ -330,7 +359,7 @@ protected void afterScenario(TestCaseFinished event) { RunningContext.ScenarioContext context = getCurrentScenarioContext(); String featureUri = context.getFeatureUri(); currentScenarioContextMap.remove(Pair.of(context.getLine(), featureUri)); - Date endTime = finishTestItem(context.getId(), event.result.getStatus()); + Date endTime = finishTestItem(context.getId(), event.result.getStatus(), event.result.getError()); featureEndTime.put(featureUri, endTime); currentScenarioContext.remove(); removeFromTree(currentFeatureContextMap.get(context.getFeatureUri()), context); @@ -436,7 +465,9 @@ protected void beforeStep(TestStep testStep) { context.setCurrentStepId(stepId); String stepText = step.getText(); context.setCurrentText(stepText); - + if (rq.isHasStats()) { + descriptionsMap.put(stepId.blockingGet(), ofNullable(rq.getDescription()).orElse(StringUtils.EMPTY)); + } if (launch.get().getParameters().isCallbackReportingEnabled()) { addToTree(context, stepText, stepId); } @@ -450,7 +481,7 @@ protected void beforeStep(TestStep testStep) { protected void afterStep(Result result) { reportResult(result, null); RunningContext.ScenarioContext context = getCurrentScenarioContext(); - finishTestItem(context.getCurrentStepId(), result.getStatus()); + finishTestItem(context.getCurrentStepId(), result.getStatus(), result.getError()); context.setCurrentStepId(null); } @@ -501,7 +532,7 @@ protected void beforeHooks(HookType hookType) { */ protected void afterHooks(HookType hookType) { RunningContext.ScenarioContext context = getCurrentScenarioContext(); - finishTestItem(context.getHookStepId(), context.getHookStatus()); + finishTestItem(context.getHookStepId(), context.getHookStatus(), null); context.setHookStepId(null); if (hookType == HookType.AfterStep) { removeFromTree(context, context.getCurrentText()); @@ -628,7 +659,7 @@ protected void finishFeature(Maybe itemId, Date dateTime) { LOGGER.error("BUG: Trying to finish unspecified test item."); return; } - FinishTestItemRQ rq = buildFinishTestItemRequest(itemId, dateTime, null); + FinishTestItemRQ rq = buildFinishTestItemRequest(itemId, dateTime, null, null); //noinspection ReactiveStreamsUnusedPublisher launch.get().finishTestItem(itemId, rq); } diff --git a/src/test/java/com/epam/reportportal/cucumber/FailedTest.java b/src/test/java/com/epam/reportportal/cucumber/FailedTest.java index b7292c9..49ff98f 100644 --- a/src/test/java/com/epam/reportportal/cucumber/FailedTest.java +++ b/src/test/java/com/epam/reportportal/cucumber/FailedTest.java @@ -44,6 +44,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.not; import static org.mockito.Mockito.*; /** @@ -52,6 +53,10 @@ 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; @CucumberOptions(features = "src/test/resources/features/FailedScenario.feature", glue = { "com.epam.reportportal.cucumber.integration.feature" }, plugin = { "pretty", @@ -136,4 +141,47 @@ 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))); + assertThat(step.getDescription(), not(equalTo(DESCRIPTION_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(), equalTo(DESCRIPTION_ERROR_LOG_TEXT)); + } } From b9c273d2bcc94cad5b5370f3f1cf7b87945ccbb1 Mon Sep 17 00:00:00 2001 From: Artem_Oliinyk Date: Fri, 12 Apr 2024 16:44:20 +0200 Subject: [PATCH 2/3] [Cucumber_4] Fixed issues mentioned in PR related to last error log. --- .../cucumber/AbstractReporter.java | 65 ++++++++++++------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java b/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java index 13287cd..8369dc1 100644 --- a/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java +++ b/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java @@ -116,6 +116,10 @@ public abstract class AbstractReporter implements ConcurrentEventListener { * 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; @@ -251,7 +255,7 @@ protected void beforeScenario(RunningContext.FeatureContext featureContext, Runn StartTestItemRQ startTestItemRQ = buildStartScenarioRequest(scenarioContext.getTestCase(), scenarioName, featureContext.getUri(), scenarioContext.getLine()); Maybe id = startScenario(featureContext.getFeatureId(), startTestItemRQ); scenarioContext.setId(id); - descriptionsMap.put(scenarioContext.getId().blockingGet(), ofNullable(startTestItemRQ.getDescription()).orElse(StringUtils.EMPTY)); + id.subscribe(scenarioId -> descriptionsMap.put(scenarioId, ofNullable(startTestItemRQ.getDescription()).orElse(StringUtils.EMPTY))); if (launch.get().getParameters().isCallbackReportingEnabled()) { addToTree(featureContext, scenarioContext); } @@ -288,18 +292,21 @@ protected ItemStatus mapItemStatus(@Nullable Result.Type status) { */ @Nonnull @SuppressWarnings("unused") - protected FinishTestItemRQ buildFinishTestItemRequest(@Nonnull Maybe itemId, @Nullable Date finishTime, - @Nullable ItemStatus status, @Nullable Throwable error) { - FinishTestItemRQ rq = new FinishTestItemRQ(); - if (status == ItemStatus.FAILED) { - Optional currentDescription = Optional.ofNullable(descriptionsMap.get(itemId.blockingGet())); - currentDescription.flatMap(description -> Optional.ofNullable(error) - .map(errorMessage -> resolveDescriptionErrorMessage(description, errorMessage))) - .ifPresent(rq::setDescription); - } - 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; + }); } /** @@ -322,16 +329,17 @@ private String resolveDescriptionErrorMessage(String currentDescription, Throwab * @param status the status of the item * @return a date and time object of the finish event */ - protected Date finishTestItem(Maybe itemId, Result.Type status, @Nullable Throwable error) { + protected Date finishTestItem(Maybe itemId, Result.Type status) { if (itemId == null) { LOGGER.error("BUG: Trying to finish unspecified test item."); return null; } - FinishTestItemRQ rq = buildFinishTestItemRequest(itemId, null, mapItemStatus(status), error); + 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; } /** @@ -340,7 +348,7 @@ protected Date finishTestItem(Maybe itemId, Result.Type status, @Nullabl * @param itemId an ID of the item */ protected void finishTestItem(Maybe itemId) { - finishTestItem(itemId, null, null); + finishTestItem(itemId, null); } private void removeFromTree(RunningContext.FeatureContext featureContext, RunningContext.ScenarioContext scenarioContext) { @@ -358,7 +366,11 @@ protected void afterScenario(TestCaseFinished event) { RunningContext.ScenarioContext context = getCurrentScenarioContext(); String featureUri = context.getFeatureUri(); currentScenarioContextMap.remove(Pair.of(context.getLine(), featureUri)); - Date endTime = finishTestItem(context.getId(), event.result.getStatus(), event.result.getError()); + 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(); removeFromTree(currentFeatureContextMap.get(context.getFeatureUri()), context); @@ -465,7 +477,7 @@ protected void beforeStep(TestStep testStep) { String stepText = step.getText(); context.setCurrentText(stepText); if (rq.isHasStats()) { - descriptionsMap.put(stepId.blockingGet(), ofNullable(rq.getDescription()).orElse(StringUtils.EMPTY)); + stepId.subscribe(id -> descriptionsMap.put(id, ofNullable(rq.getDescription()).orElse(StringUtils.EMPTY))); } if (launch.get().getParameters().isCallbackReportingEnabled()) { addToTree(context, stepText, stepId); @@ -480,7 +492,11 @@ protected void beforeStep(TestStep testStep) { protected void afterStep(Result result) { reportResult(result, null); RunningContext.ScenarioContext context = getCurrentScenarioContext(); - finishTestItem(context.getCurrentStepId(), result.getStatus(), result.getError()); + 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); } @@ -531,7 +547,7 @@ protected void beforeHooks(HookType hookType) { */ protected void afterHooks(HookType hookType) { RunningContext.ScenarioContext context = getCurrentScenarioContext(); - finishTestItem(context.getHookStepId(), context.getHookStatus(), null); + finishTestItem(context.getHookStepId(), context.getHookStatus()); context.setHookStepId(null); if (hookType == HookType.AfterStep) { removeFromTree(context, context.getCurrentText()); @@ -650,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, 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) { From 8de54050023b116dcc793e0e769e7e1ad4f06d0a Mon Sep 17 00:00:00 2001 From: Artem_Oliinyk Date: Tue, 30 Apr 2024 23:55:22 +0200 Subject: [PATCH 3/3] [Cucumber_4] Unit tests were fixed. --- .../com/epam/reportportal/cucumber/FailedTest.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/epam/reportportal/cucumber/FailedTest.java b/src/test/java/com/epam/reportportal/cucumber/FailedTest.java index 49ff98f..5c55032 100644 --- a/src/test/java/com/epam/reportportal/cucumber/FailedTest.java +++ b/src/test/java/com/epam/reportportal/cucumber/FailedTest.java @@ -42,9 +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.*; /** @@ -58,6 +62,10 @@ public class FailedTest { "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", "com.epam.reportportal.cucumber.integration.TestScenarioReporter" }) @@ -161,7 +169,6 @@ public void verify_failed_nested_step_description_scenario_reporter() { FinishTestItemRQ step = finishRqs.get(0); assertThat(step.getDescription(), not(equalTo(ERROR_LOG_TEXT))); - assertThat(step.getDescription(), not(equalTo(DESCRIPTION_ERROR_LOG_TEXT))); } @Test @@ -182,6 +189,8 @@ public void verify_failed_step_description_step_reporter() { FinishTestItemRQ step = finishRqs.get(0); assertThat(step.getDescription(), equalTo(ERROR_LOG_TEXT)); FinishTestItemRQ test = finishRqs.get(1); - assertThat(test.getDescription(), equalTo(DESCRIPTION_ERROR_LOG_TEXT)); + assertThat(test.getDescription(), + allOf(notNullValue(), startsWith(SCENARIO_CODE_REFERENCES_WITH_ERROR.getKey()), endsWith(SCENARIO_CODE_REFERENCES_WITH_ERROR.getValue())) + ); } }