From bade84f0892051b185096ad28669f2dc89932e30 Mon Sep 17 00:00:00 2001 From: Artem_Oliinyk Date: Thu, 14 Mar 2024 20:58:00 +0100 Subject: [PATCH 1/8] [Cucumber_6] Implemented logic that appends an error to the scenario and step description. --- .../cucumber/AbstractReporter.java | 59 +++++++++++++++---- .../reportportal/cucumber/FailedTest.java | 48 +++++++++++++++ 2 files changed, 94 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java b/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java index 854eec7..e940a85 100644 --- a/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java +++ b/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java @@ -41,6 +41,8 @@ import io.cucumber.plugin.event.*; 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; @@ -59,6 +61,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.ofNullable; import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.exception.ExceptionUtils.getStackTrace; @@ -85,6 +88,8 @@ public abstract class AbstractReporter implements ConcurrentEventListener { protected static final String METHOD_OPENING_BRACKET = "("; protected static final String HOOK_ = "Hook: "; protected 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; private final Map featureContextMap = new ConcurrentHashMap<>(); private final TestItemTree itemTree = new TestItemTree(); @@ -95,6 +100,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<>(); + /** * A method for creation a Start Launch request which will be sent to Report Portal. You can customize it by overriding the method. * @@ -320,7 +330,7 @@ protected void afterScenario(TestCaseFinished event) { TestCase testCase = event.getTestCase(); execute(testCase, (f, s) -> { URI featureUri = f.getUri(); - Date endTime = finishTestItem(s.getId(), mapItemStatus(event.getResult().getStatus())); + Date endTime = finishTestItem(s.getId(), mapItemStatus(event.getResult().getStatus()), null, event.getResult().getError()); featureEndTime.put(featureUri, endTime); removeFromTree(f.getFeature(), testCase); }); @@ -416,6 +426,9 @@ protected void beforeStep(@Nonnull TestCase testCase, @Nonnull TestStep testStep String stepPrefix = step.getStep().getLocation().getLine() < s.getLine() ? BACKGROUND_PREFIX : null; StartTestItemRQ rq = buildStartStepRequest(testStep, stepPrefix, step.getStep().getKeyword()); Maybe stepId = startStep(s.getId(), rq); + if (rq.isHasStats()) { + descriptionsMap.put(stepId.blockingGet(), ofNullable(rq.getDescription()).orElse(StringUtils.EMPTY)); + } s.setStepId(stepId); String stepText = step.getStep().getText(); if (getLaunch().getParameters().isCallbackReportingEnabled()) { @@ -436,7 +449,7 @@ protected void beforeStep(@Nonnull TestCase testCase, @Nonnull TestStep testStep protected void afterStep(@Nonnull TestCase testCase, @Nonnull TestStep testStep, @Nonnull Result result) { execute(testCase, (f, s) -> { reportResult(result, null); - finishTestItem(s.getStepId(), mapItemStatus(result.getStatus())); + finishTestItem(s.getStepId(), mapItemStatus(result.getStatus()), null, result.getError()); s.setStepId(Maybe.empty()); }); } @@ -684,7 +697,9 @@ protected void beforeScenario(@Nonnull Feature feature, @Nonnull TestCase scenar // If it's a ScenarioOutline use Example's line number as code reference to detach one Test Item from another int codeLine = s.getExample().map(e -> e.getLocation().getLine()).orElse(s.getLine()); - s.setId(startScenario(rootId, buildStartScenarioRequest(scenario, scenarioName, s.getUri(), codeLine))); + StartTestItemRQ startTestItemRQ = buildStartScenarioRequest(scenario, scenarioName, s.getUri(), codeLine); + s.setId(startScenario(rootId, startTestItemRQ)); + descriptionsMap.put(s.getId().blockingGet(), ofNullable(startTestItemRQ.getDescription()).orElse(StringUtils.EMPTY)); if (getLaunch().getParameters().isCallbackReportingEnabled()) { addToTree(feature, scenario, s.getId()); } @@ -839,8 +854,8 @@ private void removeFromTree(Feature feature) { protected void handleEndOfFeature() { featureContextMap.values().forEach(f -> { Date featureCompletionDateTime = featureEndTime.get(f.getUri()); - f.getCurrentRule().ifPresent(r -> finishTestItem(r.getId(), null, featureCompletionDateTime)); - finishTestItem(f.getId(), null, featureCompletionDateTime); + f.getCurrentRule().ifPresent(r -> finishTestItem(r.getId(), null, featureCompletionDateTime, null)); + finishTestItem(f.getId(), null, featureCompletionDateTime, null); removeFromTree(f.getFeature()); }); featureContextMap.clear(); @@ -877,13 +892,32 @@ protected void handleTestStepFinished(@Nonnull TestStepFinished event) { @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 * @@ -892,12 +926,13 @@ protected FinishTestItemRQ buildFinishTestItemRequest(@Nonnull Maybe ite * @param dateTime a date and time object to use as feature end time * @return a date and time object of the finish event */ - protected Date finishTestItem(@Nullable Maybe itemId, @Nullable ItemStatus status, @Nullable Date dateTime) { + protected Date finishTestItem(@Nullable Maybe itemId, @Nullable ItemStatus status, @Nullable Date dateTime, + @Nullable Throwable error) { if (itemId == null) { LOGGER.error("BUG: Trying to finish unspecified test item."); return null; } - FinishTestItemRQ rq = buildFinishTestItemRequest(itemId, dateTime, status); + FinishTestItemRQ rq = buildFinishTestItemRequest(itemId, dateTime, status, error); //noinspection ReactiveStreamsUnusedPublisher getLaunch().finishTestItem(itemId, rq); return rq.getEndTime(); @@ -915,7 +950,7 @@ protected ItemStatus mapItemStatus(@Nullable Status status) { return null; } else { if (STATUS_MAPPING.get(status) == null) { - LOGGER.error(String.format("Unable to find direct mapping between Cucumber and ReportPortal for TestItem with status: '%s'.", + LOGGER.error(format("Unable to find direct mapping between Cucumber and ReportPortal for TestItem with status: '%s'.", status )); return ItemStatus.SKIPPED; @@ -929,11 +964,9 @@ protected ItemStatus mapItemStatus(@Nullable Status status) { * * @param itemId an ID of the item * @param status the status of the item - * @return a date and time object of the finish event */ - @Nullable - protected Date finishTestItem(@Nullable Maybe itemId, @Nullable ItemStatus status) { - return finishTestItem(itemId, status, null); + protected void finishTestItem(@Nullable Maybe itemId, @Nullable ItemStatus status) { + finishTestItem(itemId, status, null, null); } /** diff --git a/src/test/java/com/epam/reportportal/cucumber/FailedTest.java b/src/test/java/com/epam/reportportal/cucumber/FailedTest.java index b7292c9..0824de4 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:///C:/Projects/agent-java-cucumber6/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 81dab68092d10663a535b3e3d7559b2d3aa9fa9c Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 12 Apr 2024 11:57:32 +0300 Subject: [PATCH 2/8] Action version update --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cb9f290..faac8e6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,7 +52,7 @@ jobs: java-version: '11' - name: Setup git credentials - uses: oleksiyrudenko/gha-git-credentials@v2.1.1 + uses: oleksiyrudenko/gha-git-credentials@v2-latest with: name: 'reportportal.io' email: 'support@reportportal.io' From 374bda76cfe4deb8db0cc3280224674fd18465e6 Mon Sep 17 00:00:00 2001 From: Artem_Oliinyk Date: Fri, 12 Apr 2024 13:08:24 +0200 Subject: [PATCH 3/8] [Cucumber_6] Fixed issues mentioned in PR related to last error log. --- .../cucumber/AbstractReporter.java | 65 ++++++++++++------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java b/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java index f203088..028e031 100644 --- a/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java +++ b/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java @@ -103,6 +103,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<>(); /** * A method for creation a Start Launch request which will be sent to Report Portal. You can customize it by overriding the method. @@ -329,8 +333,12 @@ protected void afterScenario(TestCaseFinished event) { TestCase testCase = event.getTestCase(); execute(testCase, (f, s) -> { URI featureUri = f.getUri(); - Date endTime = finishTestItem(s.getId(), mapItemStatus(event.getResult().getStatus()), null, event.getResult().getError()); - featureEndTime.put(featureUri, endTime); + if (mapItemStatus(event.getResult().getStatus()) == ItemStatus.FAILED) { + Optional.ofNullable(event.getResult().getError()) + .ifPresent(error -> s.getId().subscribe(id -> errorMap.put(id, error))); + } + Date endTime = finishTestItem(s.getId(), mapItemStatus(event.getResult().getStatus()), null); + featureEndTime.put(featureUri, endTime); removeFromTree(f.getFeature(), testCase); }); } @@ -426,7 +434,7 @@ protected void beforeStep(@Nonnull TestCase testCase, @Nonnull TestStep testStep StartTestItemRQ rq = buildStartStepRequest(testStep, stepPrefix, step.getStep().getKeyword()); Maybe stepId = startStep(s.getId(), rq); 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))); } s.setStepId(stepId); String stepText = step.getStep().getText(); @@ -448,7 +456,11 @@ protected void beforeStep(@Nonnull TestCase testCase, @Nonnull TestStep testStep protected void afterStep(@Nonnull TestCase testCase, @Nonnull TestStep testStep, @Nonnull Result result) { execute(testCase, (f, s) -> { reportResult(result, null); - finishTestItem(s.getStepId(), mapItemStatus(result.getStatus()), null, result.getError()); + if (mapItemStatus(result.getStatus()) == ItemStatus.FAILED) { + Optional.ofNullable(result.getError()) + .ifPresent(error -> s.getStepId().subscribe(id -> errorMap.put(id, error))); + } + finishTestItem(s.getStepId(), mapItemStatus(result.getStatus()), null); s.setStepId(Maybe.empty()); }); } @@ -690,7 +702,7 @@ protected void beforeScenario(@Nonnull Feature feature, @Nonnull TestCase scenar int codeLine = s.getExample().map(e -> e.getLocation().getLine()).orElse(s.getLine()); StartTestItemRQ startTestItemRQ = buildStartScenarioRequest(scenario, scenarioName, s.getUri(), codeLine); s.setId(startScenario(rootId, startTestItemRQ)); - descriptionsMap.put(s.getId().blockingGet(), ofNullable(startTestItemRQ.getDescription()).orElse(StringUtils.EMPTY)); + s.getId().subscribe(id -> descriptionsMap.put(id, ofNullable(startTestItemRQ.getDescription()).orElse(StringUtils.EMPTY))); if (getLaunch().getParameters().isCallbackReportingEnabled()) { addToTree(feature, scenario, s.getId()); } @@ -845,8 +857,8 @@ private void removeFromTree(Feature feature) { protected void handleEndOfFeature() { featureContextMap.values().forEach(f -> { Date featureCompletionDateTime = featureEndTime.get(f.getUri()); - f.getCurrentRule().ifPresent(r -> finishTestItem(r.getId(), null, featureCompletionDateTime, null)); - finishTestItem(f.getId(), null, featureCompletionDateTime, null); + f.getCurrentRule().ifPresent(r -> finishTestItem(r.getId(), null, featureCompletionDateTime)); + finishTestItem(f.getId(), null, featureCompletionDateTime); removeFromTree(f.getFeature()); }); featureContextMap.clear(); @@ -882,18 +894,21 @@ protected void handleTestStepFinished(@Nonnull TestStepFinished event) { */ @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(id)); + Optional currentError = Optional.ofNullable(errorMap.get(id)); + 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; + }); } /** @@ -917,16 +932,16 @@ private String resolveDescriptionErrorMessage(String currentDescription, Throwab * @param dateTime a date and time object to use as feature end time * @return a date and time object of the finish event */ - protected Date finishTestItem(@Nullable Maybe itemId, @Nullable ItemStatus status, @Nullable Date dateTime, - @Nullable Throwable error) { + protected Date finishTestItem(@Nullable Maybe itemId, @Nullable ItemStatus status, @Nullable Date dateTime) { if (itemId == null) { LOGGER.error("BUG: Trying to finish unspecified test item."); return null; } - FinishTestItemRQ rq = buildFinishTestItemRequest(itemId, dateTime, status, error); + Date endTime = ofNullable(dateTime).orElse(Calendar.getInstance().getTime()); + Maybe rq = buildFinishTestItemRequest(itemId, endTime, status); //noinspection ReactiveStreamsUnusedPublisher - getLaunch().finishTestItem(itemId, rq); - return rq.getEndTime(); + rq.subscribe(finishTestItem -> getLaunch().finishTestItem(itemId, finishTestItem)); + return endTime; } /** @@ -957,7 +972,7 @@ protected ItemStatus mapItemStatus(@Nullable Status status) { * @param status the status of the item */ protected void finishTestItem(@Nullable Maybe itemId, @Nullable ItemStatus status) { - finishTestItem(itemId, status, null, null); + finishTestItem(itemId, status, null); } /** From 243b93d2af5f4df2b7d24c26d99b6d5d98697dfb Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 18 Apr 2024 18:08:25 +0300 Subject: [PATCH 4/8] Client version update --- CHANGELOG.md | 2 ++ build.gradle | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f601fb8..44b795f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## [Unreleased] +### Changed +- Client version updated on [5.2.14](https://github.com/reportportal/client-java/releases/tag/5.2.14), by @HardNorth ## [5.3.2] ### Changed diff --git a/build.gradle b/build.gradle index 181f0c8..ccf4368 100644 --- a/build.gradle +++ b/build.gradle @@ -39,7 +39,7 @@ repositories { } dependencies { - api 'com.epam.reportportal:client-java:5.2.13' + api 'com.epam.reportportal:client-java:5.2.14' implementation "io.cucumber:cucumber-gherkin:${project.cucumber_version}" implementation 'org.slf4j:slf4j-api:2.0.7' From 7503ddb1e9f1eabb3c45385358df0df3ea2ba59d Mon Sep 17 00:00:00 2001 From: Artem_Oliinyk Date: Tue, 30 Apr 2024 23:44:51 +0200 Subject: [PATCH 5/8] [Cucumber_6] Unit tests were fixed --- .../epam/reportportal/cucumber/FailedTest.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/epam/reportportal/cucumber/FailedTest.java b/src/test/java/com/epam/reportportal/cucumber/FailedTest.java index 0824de4..67a836e 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.*; /** @@ -54,9 +58,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:///C:/Projects/agent-java-cucumber6/src/test/resources/features/FailedScenario.feature\n" - + ERROR_LOG_TEXT; + + private static final Pair SCENARIO_CODE_REFERENCES_WITH_ERROR = Pair.of("file:///", + "/agent-java-cucumber6/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", @@ -161,7 +166,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 +186,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())) + ); } } From fe960b3beb13259ae581319e1b059eaf771a8a28 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 14 Nov 2024 11:08:24 +0300 Subject: [PATCH 6/8] Client version update --- CHANGELOG.md | 5 ++++- build.gradle | 4 ++-- gradle.properties | 2 +- .../com/epam/reportportal/cucumber/AbstractReporter.java | 6 +++--- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44b795f..734f2fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ # Changelog ## [Unreleased] +### Added +- Common Stack Trace frames skip in description and logs, by @HardNorth ### Changed -- Client version updated on [5.2.14](https://github.com/reportportal/client-java/releases/tag/5.2.14), by @HardNorth +- Client version updated on [5.2.20](https://github.com/reportportal/client-java/releases/tag/5.2.20), by @HardNorth +- Cucumber version updated on 7.20.1, by @HardNorth ## [5.3.2] ### Changed diff --git a/build.gradle b/build.gradle index ccf4368..3379948 100644 --- a/build.gradle +++ b/build.gradle @@ -39,7 +39,7 @@ repositories { } dependencies { - api 'com.epam.reportportal:client-java:5.2.14' + api 'com.epam.reportportal:client-java:5.2.20' implementation "io.cucumber:cucumber-gherkin:${project.cucumber_version}" implementation 'org.slf4j:slf4j-api:2.0.7' @@ -60,7 +60,7 @@ dependencies { testImplementation "org.junit.jupiter:junit-jupiter-api:${project.junit_version}" testImplementation "org.junit.jupiter:junit-jupiter-params:${project.junit_version}" testImplementation "org.junit.jupiter:junit-jupiter-engine:${project.junit_version}" - testImplementation 'org.apache.commons:commons-io:1.3.2' + testImplementation 'commons-io:commons-io:2.16.1' } test { diff --git a/gradle.properties b/gradle.properties index 0bf12d5..8765eeb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ version=5.3.3-SNAPSHOT description=EPAM Report portal. Cucumber JVM version [6.0.0; ) adapter -cucumber_version=7.13.0 +cucumber_version=7.20.1 junit_version=5.6.3 junit_runner_version=1.6.3 scripts_url=https://raw.githubusercontent.com/reportportal/gradle-scripts diff --git a/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java b/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java index e3519c4..4040c40 100644 --- a/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java +++ b/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java @@ -27,8 +27,8 @@ import com.epam.reportportal.service.tree.TestItemTree; import com.epam.reportportal.utils.*; import com.epam.reportportal.utils.files.ByteSource; +import com.epam.reportportal.utils.formatting.MarkdownUtils; import com.epam.reportportal.utils.http.ContentType; -import com.epam.reportportal.utils.markdown.MarkdownUtils; import com.epam.reportportal.utils.properties.SystemAttributesExtractor; import com.epam.reportportal.utils.reflect.Accessible; import com.epam.ta.reportportal.ws.model.FinishExecutionRQ; @@ -59,9 +59,9 @@ 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 com.epam.reportportal.utils.formatting.ExceptionUtils.getStackTrace; import static java.util.Optional.ofNullable; import static org.apache.commons.lang3.StringUtils.isNotBlank; -import static org.apache.commons.lang3.exception.ExceptionUtils.getStackTrace; /** * Abstract Cucumber 5.x formatter for Report Portal @@ -559,7 +559,7 @@ protected void reportResult(@Nonnull Result result, @Nullable String message) { sendLog(message, level); } if (result.getError() != null) { - sendLog(getStackTrace(result.getError()), level); + sendLog(getStackTrace(result.getError(), new Throwable()), level); } } From 19fcafacee4d87eaec7f1ae9a355e85ea49c1414 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 15 Nov 2024 13:22:00 +0300 Subject: [PATCH 7/8] Client version update --- CHANGELOG.md | 2 +- build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 734f2fb..5fc39e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Added - Common Stack Trace frames skip in description and logs, by @HardNorth ### Changed -- Client version updated on [5.2.20](https://github.com/reportportal/client-java/releases/tag/5.2.20), by @HardNorth +- Client version updated on [5.2.21](https://github.com/reportportal/client-java/releases/tag/5.2.21), by @HardNorth - Cucumber version updated on 7.20.1, by @HardNorth ## [5.3.2] diff --git a/build.gradle b/build.gradle index 3379948..ddb1d74 100644 --- a/build.gradle +++ b/build.gradle @@ -39,7 +39,7 @@ repositories { } dependencies { - api 'com.epam.reportportal:client-java:5.2.20' + api 'com.epam.reportportal:client-java:5.2.21' implementation "io.cucumber:cucumber-gherkin:${project.cucumber_version}" implementation 'org.slf4j:slf4j-api:2.0.7' From 8381ecbe3a086958b12d9b60a7c39b2fa4ddf684 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 15 Nov 2024 14:37:37 +0300 Subject: [PATCH 8/8] Reporting of Last Error Log in Item description --- CHANGELOG.md | 1 + .../cucumber/AbstractReporter.java | 276 ++++++++++-------- .../reportportal/cucumber/FailedTest.java | 41 ++- .../cucumber/FeatureDescriptionTest.java | 26 +- 4 files changed, 186 insertions(+), 158 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fc39e5..5fdc089 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## [Unreleased] ### Added - Common Stack Trace frames skip in description and logs, by @HardNorth +- Reporting of Last Error Log in Item description, by @HardNorth and @ArtemOAS ### Changed - Client version updated on [5.2.21](https://github.com/reportportal/client-java/releases/tag/5.2.21), by @HardNorth - Cucumber version updated on 7.20.1, by @HardNorth diff --git a/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java b/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java index be949bb..8881904 100644 --- a/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java +++ b/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java @@ -60,8 +60,8 @@ 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 com.epam.reportportal.utils.formatting.ExceptionUtils.getStackTrace; +import static java.lang.String.format; import static java.util.Optional.ofNullable; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -88,7 +88,6 @@ public abstract class AbstractReporter implements ConcurrentEventListener { protected static final String HOOK_ = "Hook: "; protected 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; private final Map featureContextMap = new ConcurrentHashMap<>(); private final TestItemTree itemTree = new TestItemTree(); @@ -102,11 +101,11 @@ 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<>(); + private final Map, String> descriptionsMap = new ConcurrentHashMap<>(); /** * This map uses to record errors to append to the description. */ - private final Map errorMap = new ConcurrentHashMap<>(); + private final Map, Throwable> errorMap = new ConcurrentHashMap<>(); /** * A method for creation a Start Launch request which will be sent to Report Portal. You can customize it by overriding the method. @@ -308,14 +307,16 @@ private interface ScenarioContextAware { private void execute(@Nonnull TestCase testCase, @Nonnull ScenarioContextAware context) { URI uri = testCase.getUri(); int line = testCase.getLocation().getLine(); - execute(uri, f -> { - Optional scenario = f.getScenario(line); - if (scenario.isPresent()) { - context.executeWithContext(f, scenario.get()); - } else { - LOGGER.warn("Unable to locate corresponding Feature or Scenario context for URI: " + uri + "; line: " + line); - } - }); + execute( + uri, f -> { + Optional scenario = f.getScenario(line); + if (scenario.isPresent()) { + context.executeWithContext(f, scenario.get()); + } else { + LOGGER.warn("Unable to locate corresponding Feature or Scenario context for URI: " + uri + "; line: " + line); + } + } + ); } private void removeFromTree(Feature featureContext, TestCase scenarioContext) { @@ -331,16 +332,17 @@ private void removeFromTree(Feature featureContext, TestCase scenarioContext) { */ protected void afterScenario(TestCaseFinished event) { TestCase testCase = event.getTestCase(); - execute(testCase, (f, s) -> { - URI featureUri = f.getUri(); - if (mapItemStatus(event.getResult().getStatus()) == ItemStatus.FAILED) { - Optional.ofNullable(event.getResult().getError()) - .ifPresent(error -> s.getId().subscribe(id -> errorMap.put(id, error))); - } - Date endTime = finishTestItem(s.getId(), mapItemStatus(event.getResult().getStatus()), null); - featureEndTime.put(featureUri, endTime); - removeFromTree(f.getFeature(), testCase); - }); + execute( + testCase, (f, s) -> { + URI featureUri = f.getUri(); + if (mapItemStatus(event.getResult().getStatus()) == ItemStatus.FAILED) { + Optional.ofNullable(event.getResult().getError()).ifPresent(error -> errorMap.put(s.getId(), error)); + } + Date endTime = finishTestItem(s.getId(), mapItemStatus(event.getResult().getStatus()), null); + featureEndTime.put(featureUri, endTime); + removeFromTree(f.getFeature(), testCase); + } + ); } /** @@ -372,7 +374,8 @@ protected TestCaseIdEntry getTestCaseId(@Nonnull TestStep testStep, @Nullable St Pair splitCodeRef = parseJavaCodeRef(codeRef); Optional> testStepClass = getStepClass(splitCodeRef.getKey(), codeRef); return testStepClass.flatMap(c -> getStepMethod(c, splitCodeRef.getValue())) - .map(m -> TestCaseIdUtils.getTestCaseId(m.getAnnotation(TestCaseId.class), + .map(m -> TestCaseIdUtils.getTestCaseId( + m.getAnnotation(TestCaseId.class), m, codeRef, (List) ARGUMENTS_TRANSFORM.apply(arguments) @@ -427,22 +430,24 @@ private void addToTree(@Nonnull TestCase scenario, @Nullable String text, @Nulla * @param testStep a cucumber step object */ protected void beforeStep(@Nonnull TestCase testCase, @Nonnull TestStep testStep) { - execute(testCase, (f, s) -> { - if (testStep instanceof PickleStepTestStep) { - PickleStepTestStep step = (PickleStepTestStep) testStep; - String stepPrefix = step.getStep().getLocation().getLine() < s.getLine() ? BACKGROUND_PREFIX : null; - StartTestItemRQ rq = buildStartStepRequest(testStep, stepPrefix, step.getStep().getKeyword()); - Maybe stepId = startStep(s.getId(), rq); - if (rq.isHasStats()) { - stepId.subscribe(id -> descriptionsMap.put(id, ofNullable(rq.getDescription()).orElse(StringUtils.EMPTY))); - } - s.setStepId(stepId); - String stepText = step.getStep().getText(); - if (getLaunch().getParameters().isCallbackReportingEnabled()) { - addToTree(testCase, stepText, stepId); + execute( + testCase, (f, s) -> { + if (testStep instanceof PickleStepTestStep) { + PickleStepTestStep step = (PickleStepTestStep) testStep; + String stepPrefix = step.getStep().getLocation().getLine() < s.getLine() ? BACKGROUND_PREFIX : null; + StartTestItemRQ rq = buildStartStepRequest(testStep, stepPrefix, step.getStep().getKeyword()); + Maybe stepId = startStep(s.getId(), rq); + if (rq.isHasStats()) { + descriptionsMap.put(stepId, ofNullable(rq.getDescription()).orElse(StringUtils.EMPTY)); + } + s.setStepId(stepId); + String stepText = step.getStep().getText(); + if (getLaunch().getParameters().isCallbackReportingEnabled()) { + addToTree(testCase, stepText, stepId); + } + } } - } - }); + ); } /** @@ -454,15 +459,16 @@ protected void beforeStep(@Nonnull TestCase testCase, @Nonnull TestStep testStep */ @SuppressWarnings("unused") protected void afterStep(@Nonnull TestCase testCase, @Nonnull TestStep testStep, @Nonnull Result result) { - execute(testCase, (f, s) -> { - reportResult(result, null); - if (mapItemStatus(result.getStatus()) == ItemStatus.FAILED) { - Optional.ofNullable(result.getError()) - .ifPresent(error -> s.getStepId().subscribe(id -> errorMap.put(id, error))); - } - finishTestItem(s.getStepId(), mapItemStatus(result.getStatus()), null); - s.setStepId(Maybe.empty()); - }); + execute( + testCase, (f, s) -> { + reportResult(result, null); + if (mapItemStatus(result.getStatus()) == ItemStatus.FAILED) { + Optional.ofNullable(result.getError()).ifPresent(error -> errorMap.put(s.getStepId(), error)); + } + finishTestItem(s.getStepId(), mapItemStatus(result.getStatus()), null); + s.setStepId(Maybe.empty()); + } + ); } /** @@ -524,10 +530,12 @@ protected Maybe startHook(@Nonnull Maybe parentId, @Nonnull Star * @param testStep Cucumber's TestStep object */ protected void beforeHooks(@Nonnull TestCase testCase, @Nonnull HookTestStep testStep) { - execute(testCase, (f, s) -> { - StartTestItemRQ rq = buildStartHookRequest(testCase, testStep); - s.setHookId(startHook(s.getId(), rq)); - }); + execute( + testCase, (f, s) -> { + StartTestItemRQ rq = buildStartHookRequest(testCase, testStep); + s.setHookId(startHook(s.getId(), rq)); + } + ); } private void removeFromTree(TestCase testCase, String text) { @@ -543,16 +551,18 @@ private void removeFromTree(TestCase testCase, String text) { * @param result a cucumber result object */ protected void afterHooks(@Nonnull TestCase testCase, @Nonnull HookTestStep step, Result result) { - execute(testCase, (f, s) -> { - reportResult(result, (isBefore(step) ? "Before" : "After") + " hook: " + step.getCodeLocation()); - finishTestItem(s.getHookId(), mapItemStatus(result.getStatus())); - s.setHookId(Maybe.empty()); - if (step.getHookType() == HookType.AFTER_STEP) { - if (step instanceof PickleStepTestStep) { - removeFromTree(testCase, ((PickleStepTestStep) step).getStep().getText()); + execute( + testCase, (f, s) -> { + reportResult(result, (isBefore(step) ? "Before" : "After") + " hook: " + step.getCodeLocation()); + finishTestItem(s.getHookId(), mapItemStatus(result.getStatus())); + s.setHookId(Maybe.empty()); + if (step.getHookType() == HookType.AFTER_STEP) { + if (step instanceof PickleStepTestStep) { + removeFromTree(testCase, ((PickleStepTestStep) step).getStep().getText()); + } + } } - } - }); + ); } /** @@ -608,7 +618,8 @@ protected void embedding(@Nullable String name, @Nullable String mimeType, @Nonn String type = ofNullable(mimeType).filter(ContentType::isValidType).orElseGet(() -> getDataType(data, name)); String attachmentName = ofNullable(name).filter(m -> !m.isEmpty()) .orElseGet(() -> ofNullable(type).map(t -> t.substring(0, t.indexOf("/"))).orElse("")); - ReportPortal.emitLog(new ReportPortalMessage(ByteSource.wrap(data), type, attachmentName), + ReportPortal.emitLog( + new ReportPortalMessage(ByteSource.wrap(data), type, attachmentName), "UNKNOWN", Calendar.getInstance().getTime() ); @@ -679,34 +690,42 @@ protected Maybe startRule(@Nonnull Maybe featureId, @Nonnull Sta */ protected void beforeScenario(@Nonnull Feature feature, @Nonnull TestCase scenario) { String scenarioName = Utils.buildName(scenario.getKeyword(), AbstractReporter.COLON_INFIX, scenario.getName()); - execute(scenario, (f, s) -> { - Optional rule = s.getRule(); - Optional currentRule = f.getCurrentRule(); - if (!currentRule.equals(rule)) { - if (currentRule.isEmpty()) { - rule.ifPresent(r -> { - r.setId(startRule(f.getId(), buildStartRuleRequest(r.getRule(), getCodeRef(feature.getUri(), r.getLine())))); - f.setCurrentRule(r); - }); - } else { - finishTestItem(currentRule.get().getId()); - rule.ifPresent(r -> { - r.setId(startRule(f.getId(), buildStartRuleRequest(r.getRule(), getCodeRef(feature.getUri(), r.getLine())))); - f.setCurrentRule(r); - }); + execute( + scenario, (f, s) -> { + Optional rule = s.getRule(); + Optional currentRule = f.getCurrentRule(); + if (!currentRule.equals(rule)) { + if (currentRule.isEmpty()) { + rule.ifPresent(r -> { + r.setId(startRule( + f.getId(), + buildStartRuleRequest(r.getRule(), getCodeRef(feature.getUri(), r.getLine())) + )); + f.setCurrentRule(r); + }); + } else { + finishTestItem(currentRule.get().getId()); + rule.ifPresent(r -> { + r.setId(startRule( + f.getId(), + buildStartRuleRequest(r.getRule(), getCodeRef(feature.getUri(), r.getLine())) + )); + f.setCurrentRule(r); + }); + } + } + Maybe rootId = rule.map(RuleContext::getId).orElseGet(f::getId); + + // If it's a ScenarioOutline use Example's line number as code reference to detach one Test Item from another + int codeLine = s.getExample().map(e -> e.getLocation().getLine()).orElse(s.getLine()); + StartTestItemRQ startTestItemRQ = buildStartScenarioRequest(scenario, scenarioName, s.getUri(), codeLine); + s.setId(startScenario(rootId, startTestItemRQ)); + descriptionsMap.put(s.getId(), ofNullable(startTestItemRQ.getDescription()).orElse(StringUtils.EMPTY)); + if (getLaunch().getParameters().isCallbackReportingEnabled()) { + addToTree(feature, scenario, s.getId()); + } } - } - Maybe rootId = rule.map(RuleContext::getId).orElseGet(f::getId); - - // If it's a ScenarioOutline use Example's line number as code reference to detach one Test Item from another - int codeLine = s.getExample().map(e -> e.getLocation().getLine()).orElse(s.getLine()); - StartTestItemRQ startTestItemRQ = buildStartScenarioRequest(scenario, scenarioName, s.getUri(), codeLine); - s.setId(startScenario(rootId, startTestItemRQ)); - s.getId().subscribe(id -> descriptionsMap.put(id, ofNullable(startTestItemRQ.getDescription()).orElse(StringUtils.EMPTY))); - if (getLaunch().getParameters().isCallbackReportingEnabled()) { - addToTree(feature, scenario, s.getId()); - } - }); + ); } /** @@ -754,21 +773,25 @@ private void addToTree(Feature feature, Maybe featureId) { protected void handleStartOfTestCase(@Nonnull TestCaseStarted event) { TestCase testCase = event.getTestCase(); URI uri = testCase.getUri(); - execute(uri, f -> { - //noinspection ReactiveStreamsUnusedPublisher - if (f.getId().equals(Maybe.empty())) { - getRootItemId(); // trigger root item creation - StartTestItemRQ featureRq = buildStartFeatureRequest(f.getFeature(), uri); - f.setId(startFeature(featureRq)); - if (getLaunch().getParameters().isCallbackReportingEnabled()) { - addToTree(f.getFeature(), f.getId()); + execute( + uri, f -> { + //noinspection ReactiveStreamsUnusedPublisher + if (f.getId().equals(Maybe.empty())) { + getRootItemId(); // trigger root item creation + StartTestItemRQ featureRq = buildStartFeatureRequest(f.getFeature(), uri); + f.setId(startFeature(featureRq)); + if (getLaunch().getParameters().isCallbackReportingEnabled()) { + addToTree(f.getFeature(), f.getId()); + } + } } - } - }); - execute(testCase, (f, s) -> { - s.setTestCase(testCase); - beforeScenario(f.getFeature(), testCase); - }); + ); + execute( + testCase, (f, s) -> { + s.setTestCase(testCase); + beforeScenario(f.getFeature(), testCase); + } + ); } protected void handleSourceEvents(TestSourceParsed parseEvent) { @@ -893,35 +916,35 @@ protected void handleTestStepFinished(@Nonnull TestStepFinished event) { * @return finish request */ @Nonnull - @SuppressWarnings("unused") - 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(id)); - Optional currentError = Optional.ofNullable(errorMap.get(id)); - 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; - }); + protected FinishTestItemRQ buildFinishTestItemRequest(@Nonnull Maybe itemId, @Nullable Date finishTime, + @Nullable ItemStatus status) { + FinishTestItemRQ rq = new FinishTestItemRQ(); + if (status == ItemStatus.FAILED) { + Optional currentDescription = Optional.ofNullable(descriptionsMap.remove(itemId)); + Optional currentError = Optional.ofNullable(errorMap.remove(itemId)); + 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 + * @param error Error message * @return Description with error */ private String resolveDescriptionErrorMessage(String currentDescription, Throwable error) { + String errorStr = format(ERROR_FORMAT, getStackTrace(error, new Throwable())); return Optional.ofNullable(currentDescription) .filter(StringUtils::isNotBlank) - .map(description -> format(DESCRIPTION_ERROR_FORMAT, currentDescription, error)) - .orElse(format(ERROR_FORMAT, error)); + .map(description -> MarkdownUtils.asTwoParts(currentDescription, errorStr)) + .orElse(errorStr); } /** @@ -937,11 +960,11 @@ protected Date finishTestItem(@Nullable Maybe itemId, @Nullable ItemStat LOGGER.error("BUG: Trying to finish unspecified test item."); return null; } - Date endTime = ofNullable(dateTime).orElse(Calendar.getInstance().getTime()); - Maybe rq = buildFinishTestItemRequest(itemId, endTime, status); + Date endTime = ofNullable(dateTime).orElse(Calendar.getInstance().getTime()); + FinishTestItemRQ rq = buildFinishTestItemRequest(itemId, endTime, status); //noinspection ReactiveStreamsUnusedPublisher - rq.subscribe(finishTestItem -> getLaunch().finishTestItem(itemId, finishTestItem)); - return endTime; + getLaunch().finishTestItem(itemId, rq); + return endTime; } /** @@ -956,7 +979,8 @@ protected ItemStatus mapItemStatus(@Nullable Status status) { return null; } else { if (STATUS_MAPPING.get(status) == null) { - LOGGER.error(format("Unable to find direct mapping between Cucumber and ReportPortal for TestItem with status: '%s'.", + LOGGER.error(format( + "Unable to find direct mapping between Cucumber and ReportPortal for TestItem with status: '%s'.", status )); return ItemStatus.SKIPPED; diff --git a/src/test/java/com/epam/reportportal/cucumber/FailedTest.java b/src/test/java/com/epam/reportportal/cucumber/FailedTest.java index 67a836e..70b10b5 100644 --- a/src/test/java/com/epam/reportportal/cucumber/FailedTest.java +++ b/src/test/java/com/epam/reportportal/cucumber/FailedTest.java @@ -25,6 +25,7 @@ import com.epam.reportportal.service.ReportPortal; import com.epam.reportportal.service.ReportPortalClient; import com.epam.reportportal.util.test.CommonUtils; +import com.epam.reportportal.utils.formatting.MarkdownUtils; import com.epam.ta.reportportal.ws.model.FinishTestItemRQ; import com.epam.ta.reportportal.ws.model.log.SaveLogRQ; import io.cucumber.testng.AbstractTestNGCucumberTests; @@ -42,13 +43,8 @@ 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.hamcrest.Matchers.*; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.*; /** @@ -57,23 +53,28 @@ 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 Pair SCENARIO_CODE_REFERENCES_WITH_ERROR = Pair.of("file:///", - "/agent-java-cucumber6/src/test/resources/features/FailedScenario.feature\n" + ERROR_LOG_TEXT + private static final String EXPECTED_STACK_TRACE = EXPECTED_ERROR + + "\n\tat com.epam.reportportal.cucumber.integration.feature.FailedSteps.i_have_a_failed_step(FailedSteps.java:31)" + + "\n\tat ✽.I have a failed step(file://" + System.getProperty("user.dir") + + "/src/test/resources/features/FailedScenario.feature:4)\n"; + private static final String ERROR_LOG_TEXT = "Error:\n" + EXPECTED_STACK_TRACE; + + private static final String SCENARIO_CODE_REFERENCES_WITH_ERROR = MarkdownUtils.asTwoParts( + "file://" + System.getProperty("user.dir") + "/src/test/resources/features/FailedScenario.feature", + 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" }) - public static class FailedScenarioReporter extends AbstractTestNGCucumberTests { + public static class FailedScenarioReporterTest extends AbstractTestNGCucumberTests { } @CucumberOptions(features = "src/test/resources/features/FailedScenario.feature", glue = { "com.epam.reportportal.cucumber.integration.feature" }, plugin = { "pretty", "com.epam.reportportal.cucumber.integration.TestStepReporter" }) - public static class FailedStepReporter extends AbstractTestNGCucumberTests { + public static class FailedStepReporterTest extends AbstractTestNGCucumberTests { } @@ -101,7 +102,7 @@ public void initLaunch() { @Test @SuppressWarnings("unchecked") public void verify_failed_step_reporting_scenario_reporter() { - TestUtils.runTests(FailedScenarioReporter.class); + TestUtils.runTests(FailedScenarioReporterTest.class); verify(client).startTestItem(any()); verify(client).startTestItem(same(suiteId), any()); @@ -127,7 +128,7 @@ public void verify_failed_step_reporting_scenario_reporter() { @Test @SuppressWarnings("unchecked") public void verify_failed_step_reporting_step_reporter() { - TestUtils.runTests(FailedStepReporter.class); + TestUtils.runTests(FailedStepReporterTest.class); verify(client).startTestItem(any()); verify(client).startTestItem(same(suiteId), any()); @@ -148,9 +149,8 @@ public void verify_failed_step_reporting_step_reporter() { } @Test - @SuppressWarnings("unchecked") public void verify_failed_nested_step_description_scenario_reporter() { - TestUtils.runTests(FailedScenarioReporter.class); + TestUtils.runTests(FailedScenarioReporterTest.class); verify(client).startTestItem(any()); verify(client).startTestItem(same(suiteId), any()); @@ -169,9 +169,8 @@ public void verify_failed_nested_step_description_scenario_reporter() { } @Test - @SuppressWarnings("unchecked") public void verify_failed_step_description_step_reporter() { - TestUtils.runTests(FailedStepReporter.class); + TestUtils.runTests(FailedStepReporterTest.class); verify(client).startTestItem(any()); verify(client).startTestItem(same(suiteId), any()); @@ -186,8 +185,6 @@ 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(), - allOf(notNullValue(), startsWith(SCENARIO_CODE_REFERENCES_WITH_ERROR.getKey()), endsWith(SCENARIO_CODE_REFERENCES_WITH_ERROR.getValue())) - ); + assertThat(test.getDescription(), equalTo(SCENARIO_CODE_REFERENCES_WITH_ERROR)); } } diff --git a/src/test/java/com/epam/reportportal/cucumber/FeatureDescriptionTest.java b/src/test/java/com/epam/reportportal/cucumber/FeatureDescriptionTest.java index 0360dc8..ca2731d 100644 --- a/src/test/java/com/epam/reportportal/cucumber/FeatureDescriptionTest.java +++ b/src/test/java/com/epam/reportportal/cucumber/FeatureDescriptionTest.java @@ -52,14 +52,14 @@ public class FeatureDescriptionTest { @CucumberOptions(features = "src/test/resources/features/belly.feature", glue = { "com.epam.reportportal.cucumber.integration.feature" }, plugin = { "pretty", "com.epam.reportportal.cucumber.integration.TestScenarioReporter" }) - public static class BellyScenarioReporter extends AbstractTestNGCucumberTests { + public static class BellyScenarioReporterTest extends AbstractTestNGCucumberTests { } @CucumberOptions(features = "src/test/resources/features/belly.feature", glue = { "com.epam.reportportal.cucumber.integration.feature" }, plugin = { "pretty", "com.epam.reportportal.cucumber.integration.TestStepReporter" }) - public static class BellyStepReporter extends AbstractTestNGCucumberTests { + public static class BellyStepReporterTest extends AbstractTestNGCucumberTests { } @@ -82,17 +82,19 @@ public void initLaunch() { TestStepReporter.RP.set(reportPortal); } - private static final Pair FEATURE_CODE_REFERENCES = Pair.of("file:///", + private static final Pair FEATURE_CODE_REFERENCES = Pair.of( + "file:///", "/agent-java-cucumber6/src/test/resources/features/belly.feature" ); - private static final Pair SCENARIO_CODE_REFERENCES = Pair.of("file:///", + private static final Pair SCENARIO_CODE_REFERENCES = Pair.of( + "file:///", "/agent-java-cucumber6/src/test/resources/features/belly.feature" ); @Test public void verify_code_reference_scenario_reporter() { - TestUtils.runTests(BellyScenarioReporter.class); + TestUtils.runTests(BellyScenarioReporterTest.class); verify(client, times(1)).startTestItem(any()); ArgumentCaptor captor = ArgumentCaptor.forClass(StartTestItemRQ.class); @@ -104,17 +106,19 @@ public void verify_code_reference_scenario_reporter() { StartTestItemRQ feature = items.get(0); StartTestItemRQ scenario = items.get(1); - assertThat(feature.getDescription(), + assertThat( + feature.getDescription(), allOf(notNullValue(), startsWith(FEATURE_CODE_REFERENCES.getKey()), endsWith(FEATURE_CODE_REFERENCES.getValue())) ); - assertThat(scenario.getDescription(), + assertThat( + scenario.getDescription(), allOf(notNullValue(), startsWith(SCENARIO_CODE_REFERENCES.getKey()), endsWith(SCENARIO_CODE_REFERENCES.getValue())) ); } @Test public void verify_code_reference_step_reporter() { - TestUtils.runTests(BellyStepReporter.class); + TestUtils.runTests(BellyStepReporterTest.class); ArgumentCaptor captor = ArgumentCaptor.forClass(StartTestItemRQ.class); verify(client, times(1)).startTestItem(captor.capture()); @@ -125,10 +129,12 @@ public void verify_code_reference_step_reporter() { StartTestItemRQ feature = items.get(0); StartTestItemRQ scenario = items.get(1); - assertThat(feature.getDescription(), + assertThat( + feature.getDescription(), allOf(notNullValue(), startsWith(FEATURE_CODE_REFERENCES.getKey()), endsWith(FEATURE_CODE_REFERENCES.getValue())) ); - assertThat(scenario.getDescription(), + assertThat( + scenario.getDescription(), allOf(notNullValue(), startsWith(SCENARIO_CODE_REFERENCES.getKey()), endsWith(SCENARIO_CODE_REFERENCES.getValue())) ); }