Skip to content
This repository was archived by the owner on Jun 17, 2025. It is now read-only.

Commit a9c73a4

Browse files
committed
Multiple feature
1 parent 8ffb916 commit a9c73a4

File tree

6 files changed

+87
-118
lines changed

6 files changed

+87
-118
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Changelog
22

33
## [Unreleased]
4+
### Added
5+
- Common Stack Trace frames skip in description and logs, by @HardNorth
6+
- Reporting of Last Error Log in Item description, by @HardNorth and @ArtemOAS
47
### Changed
58
- Client version updated on [5.2.21](https://github.com/reportportal/client-java/releases/tag/5.2.21), by @HardNorth
69

src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java

Lines changed: 41 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.epam.reportportal.service.tree.TestItemTree;
2727
import com.epam.reportportal.utils.*;
2828
import com.epam.reportportal.utils.files.ByteSource;
29+
import com.epam.reportportal.utils.formatting.MarkdownUtils;
2930
import com.epam.reportportal.utils.http.ContentType;
3031
import com.epam.reportportal.utils.properties.SystemAttributesExtractor;
3132
import com.epam.ta.reportportal.ws.model.FinishExecutionRQ;
@@ -61,11 +62,12 @@
6162
import static com.epam.reportportal.cucumber.Utils.*;
6263
import static com.epam.reportportal.cucumber.util.ItemTreeUtils.createKey;
6364
import static com.epam.reportportal.cucumber.util.ItemTreeUtils.retrieveLeaf;
65+
import static com.epam.reportportal.utils.formatting.ExceptionUtils.getStackTrace;
66+
import static com.epam.reportportal.utils.formatting.MarkdownUtils.formatDataTable;
6467
import static java.lang.String.format;
6568
import static java.util.Optional.ofNullable;
6669
import static org.apache.commons.lang3.StringUtils.isBlank;
6770
import static org.apache.commons.lang3.StringUtils.isNotBlank;
68-
import static org.apache.commons.lang3.exception.ExceptionUtils.getStackTrace;
6971

7072
/**
7173
* Abstract Cucumber formatter/reporter for Report Portal
@@ -83,7 +85,6 @@ public abstract class AbstractReporter implements Formatter, Reporter {
8385
private static final String METHOD_OPENING_BRACKET = "(";
8486
private static final String DOCSTRING_DECORATOR = "\n\"\"\"\n";
8587
private static final String ERROR_FORMAT = "Error:\n%s";
86-
private static final String DESCRIPTION_ERROR_FORMAT = "%s\n" + ERROR_FORMAT;
8788

8889
public static final TestItemTree ITEM_TREE = new TestItemTree();
8990
private static volatile ReportPortal REPORT_PORTAL = ReportPortal.builder().build();
@@ -97,11 +98,11 @@ public abstract class AbstractReporter implements Formatter, Reporter {
9798
/**
9899
* This map uses to record the description of the scenario and the step to append the error to the description.
99100
*/
100-
private final Map<String, String> descriptionsMap = new ConcurrentHashMap<>();
101+
private final Map<Maybe<String>, String> descriptionsMap = new ConcurrentHashMap<>();
101102
/**
102103
* This map uses to record errors to append to the description.
103104
*/
104-
private final Map<String, Throwable> errorMap = new ConcurrentHashMap<>();
105+
private final Map<Maybe<String>, Throwable> errorMap = new ConcurrentHashMap<>();
105106

106107
private AtomicBoolean finished = new AtomicBoolean(false);
107108

@@ -273,8 +274,7 @@ protected void beforeScenario(Scenario scenario, String outlineIteration) {
273274
scenarioContext.setId(startScenario(featureContext.getId(), rq));
274275
scenarioContext.setLine(scenario.getLine());
275276
scenarioContext.setFeatureUri(uri);
276-
scenarioContext.getId()
277-
.subscribe(id -> descriptionsMap.put(id, ofNullable(rq.getDescription()).orElse(StringUtils.EMPTY)));
277+
descriptionsMap.put(scenarioContext.getId(), ofNullable(rq.getDescription()).orElse(StringUtils.EMPTY));
278278
if (myLaunch.getParameters().isCallbackReportingEnabled()) {
279279
addToTree(featureContext, scenarioContext);
280280
}
@@ -294,33 +294,34 @@ private void removeFromTree(RunningContext.FeatureContext featureContext, Runnin
294294
*/
295295
@Nonnull
296296
@SuppressWarnings("unused")
297-
protected Maybe<FinishTestItemRQ> buildFinishTestItemRequest(@Nonnull Maybe<String> itemId, @Nullable ItemStatus status) {
298-
return itemId.map(id -> {
299-
FinishTestItemRQ rq = new FinishTestItemRQ();
300-
if (status == ItemStatus.FAILED) {
301-
Optional<String> currentDescription = Optional.ofNullable(descriptionsMap.get(id));
302-
Optional<Throwable> currentError = Optional.ofNullable(errorMap.get(id));
303-
currentDescription.flatMap(description -> currentError
304-
.map(errorMessage -> resolveDescriptionErrorMessage(description, errorMessage)))
305-
.ifPresent(rq::setDescription);
306-
}
307-
ofNullable(status).ifPresent(s -> rq.setStatus(s.name()));
308-
rq.setEndTime(Calendar.getInstance().getTime());
309-
return rq;
310-
});
297+
protected FinishTestItemRQ buildFinishTestItemRequest(@Nonnull Maybe<String> itemId, @Nullable ItemStatus status) {
298+
FinishTestItemRQ rq = new FinishTestItemRQ();
299+
if (status == ItemStatus.FAILED) {
300+
Optional<String> currentDescription = Optional.ofNullable(descriptionsMap.get(itemId));
301+
Optional<Throwable> currentError = Optional.ofNullable(errorMap.get(itemId));
302+
currentDescription.flatMap(description -> currentError.map(errorMessage -> resolveDescriptionErrorMessage(
303+
description,
304+
errorMessage
305+
))).ifPresent(rq::setDescription);
306+
}
307+
ofNullable(status).ifPresent(s -> rq.setStatus(s.name()));
308+
rq.setEndTime(Calendar.getInstance().getTime());
309+
return rq;
311310
}
312311

313312
/**
314313
* Resolve description
314+
*
315315
* @param currentDescription Current description
316-
* @param error Error message
316+
* @param error Error message
317317
* @return Description with error
318318
*/
319319
private String resolveDescriptionErrorMessage(String currentDescription, Throwable error) {
320+
String errorStr = format(ERROR_FORMAT, getStackTrace(error, new Throwable()));
320321
return Optional.ofNullable(currentDescription)
321322
.filter(StringUtils::isNotBlank)
322-
.map(description -> format(DESCRIPTION_ERROR_FORMAT, currentDescription, error))
323-
.orElse(format(ERROR_FORMAT, error));
323+
.map(description -> MarkdownUtils.asTwoParts(currentDescription, errorStr))
324+
.orElse(errorStr);
324325
}
325326

326327
/**
@@ -334,9 +335,9 @@ protected void finishTestItem(@Nullable Maybe<String> itemId, @Nullable ItemStat
334335
LOGGER.error("BUG: Trying to finish unspecified test item.");
335336
return;
336337
}
338+
FinishTestItemRQ finishTestItemRQ = buildFinishTestItemRequest(itemId, status);
337339
//noinspection ReactiveStreamsUnusedPublisher
338-
Maybe<FinishTestItemRQ> finishTestItemRQMaybe = buildFinishTestItemRequest(itemId, status);
339-
finishTestItemRQMaybe.subscribe(finishTestItemRQ -> launch.get().finishTestItem(itemId, finishTestItemRQ));
340+
launch.get().finishTestItem(itemId, finishTestItemRQ);
340341
}
341342

342343
/**
@@ -427,7 +428,8 @@ protected Maybe<String> startStep(@Nonnull Maybe<String> scenarioId, @Nonnull St
427428
}
428429

429430
private void addToTree(@Nonnull RunningContext.ScenarioContext scenarioContext, @Nullable String text, @Nullable Maybe<String> stepId) {
430-
retrieveLeaf(scenarioContext.getFeatureUri(),
431+
retrieveLeaf(
432+
scenarioContext.getFeatureUri(),
431433
scenarioContext.getLine(),
432434
ITEM_TREE
433435
).ifPresent(scenarioLeaf -> scenarioLeaf.getChildItems().put(createKey(text), TestItemTree.createTestItemLeaf(stepId)));
@@ -446,7 +448,7 @@ protected void beforeStep(Step step, Match match) {
446448
context.setCurrentStepId(stepId);
447449
String stepText = step.getName();
448450
if (rq.isHasStats()) {
449-
stepId.subscribe(id -> descriptionsMap.put(id, ofNullable(rq.getDescription()).orElse(StringUtils.EMPTY)));
451+
descriptionsMap.put(stepId, ofNullable(rq.getDescription()).orElse(StringUtils.EMPTY));
450452
}
451453

452454
if (launch.get().getParameters().isCallbackReportingEnabled()) {
@@ -483,8 +485,8 @@ protected StartTestItemRQ buildStartHookRequest(boolean isBefore) {
483485
/**
484486
* Start before/after-hook item on Report Portal
485487
*
486-
* @param parentId parent item id
487-
* @param rq hook start request
488+
* @param parentId parent item id
489+
* @param rq hook start request
488490
* @return hook item id
489491
*/
490492
@Nonnull
@@ -545,14 +547,14 @@ protected void reportResult(@Nonnull Result result, @Nullable String message) {
545547
if (errorMessage != null) {
546548
sendLog(errorMessage, level);
547549
} else if (result.getError() != null) {
548-
sendLog(getStackTrace(result.getError()), level);
550+
sendLog(getStackTrace(result.getError(), new Throwable()), level);
549551
}
550552
RunningContext.ScenarioContext currentScenario = getCurrentScenarioContext();
551553
ItemStatus itemStatus = mapStatus(result.getStatus());
552554
currentScenario.updateStatus(itemStatus);
553555
if (itemStatus == ItemStatus.FAILED) {
554-
currentScenario.getId().subscribe(id -> errorMap.put(id, result.getError()));
555-
currentScenario.getCurrentStepId().subscribe(id -> errorMap.put(id, result.getError()));
556+
errorMap.put(currentScenario.getId(), result.getError());
557+
errorMap.put(currentScenario.getCurrentStepId(), result.getError());
556558
}
557559
}
558560

@@ -619,7 +621,8 @@ private static String getDataType(@Nonnull byte[] data) {
619621
public void embedding(String mimeType, byte[] data) {
620622
String type = ofNullable(mimeType).filter(ContentType::isValidType).orElseGet(() -> getDataType(data));
621623
String attachmentName = ofNullable(type).map(t -> t.substring(0, t.indexOf("/"))).orElse("");
622-
ReportPortal.emitLog(new ReportPortalMessage(ByteSource.wrap(data), type, attachmentName),
624+
ReportPortal.emitLog(
625+
new ReportPortalMessage(ByteSource.wrap(data), type, attachmentName),
623626
"UNKNOWN",
624627
Calendar.getInstance().getTime()
625628
);
@@ -749,7 +752,8 @@ protected TestCaseIdEntry getTestCaseId(@Nonnull Match match, @Nullable String c
749752
if (method == null) {
750753
return getTestCaseId(codeRef, match.getArguments());
751754
}
752-
return TestCaseIdUtils.getTestCaseId(method.getAnnotation(TestCaseId.class),
755+
return TestCaseIdUtils.getTestCaseId(
756+
method.getAnnotation(TestCaseId.class),
753757
method,
754758
codeRef,
755759
(List<Object>) ARGUMENTS_TRANSFORM.apply(match.getArguments())
@@ -913,8 +917,9 @@ protected List<ParameterResource> getParameters(@Nonnull Step step, @Nullable St
913917
.filter(ds -> !ds.isEmpty())
914918
.ifPresent(ds -> params.add(Pair.of("docstring", StringEscapeUtils.escapeHtml4(ds))));
915919
ofNullable(step.getRows()).filter(rows -> !rows.isEmpty())
916-
.ifPresent(rows -> params.add(Pair.of("datatable",
917-
Utils.formatDataTable(rows.stream().map(Row::getCells).collect(Collectors.toList()))
920+
.ifPresent(rows -> params.add(Pair.of(
921+
"datatable",
922+
formatDataTable(rows.stream().map(Row::getCells).collect(Collectors.toList()))
918923
)));
919924
return params.isEmpty() ? Collections.emptyList() : ParameterUtils.getParameters(codeRef, params);
920925
}

src/main/java/com/epam/reportportal/cucumber/Utils.java

Lines changed: 4 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,18 @@
2323
import javax.annotation.Nullable;
2424
import java.lang.reflect.Field;
2525
import java.lang.reflect.Method;
26-
import java.util.*;
26+
import java.util.Collections;
27+
import java.util.HashMap;
28+
import java.util.List;
29+
import java.util.Map;
2730
import java.util.function.Function;
2831
import java.util.stream.Collectors;
29-
import java.util.stream.IntStream;
3032

3133
import static java.util.Optional.ofNullable;
3234

3335
public class Utils {
3436
private static final String STEP_DEFINITION_FIELD_NAME = "stepDefinition";
3537
private static final String METHOD_FIELD_NAME = "method";
36-
public static final String ONE_SPACE = "\u00A0";
37-
private static final String NEW_LINE = "\r\n";
38-
public static final String TABLE_INDENT = "\u00A0\u00A0\u00A0\u00A0";
39-
public static final String TABLE_COLUMN_SEPARATOR = "|";
40-
public static final String TABLE_ROW_SEPARATOR = "-";
4138

4239
public static final Map<String, ItemStatus> STATUS_MAPPING = Collections.unmodifiableMap(new HashMap<String, ItemStatus>() {{
4340
put("passed", ItemStatus.PASSED);
@@ -84,48 +81,4 @@ public static Method retrieveMethod(@Nonnull Match match) throws NoSuchFieldExce
8481
public static final Function<List<Argument>, List<?>> ARGUMENTS_TRANSFORM = arguments -> ofNullable(arguments).map(args -> args.stream()
8582
.map(Argument::getVal)
8683
.collect(Collectors.toList())).orElse(null);
87-
88-
/**
89-
* Converts a table represented as List of Lists to a formatted table string
90-
*
91-
* @param table a table object
92-
* @return string representation of the table
93-
*/
94-
@Nonnull
95-
public static String formatDataTable(@Nonnull final List<List<String>> table) {
96-
StringBuilder result = new StringBuilder();
97-
int tableLength = table.stream().mapToInt(List::size).max().orElse(-1);
98-
List<Iterator<String>> iterList = table.stream().map(List::iterator).collect(Collectors.toList());
99-
List<Integer> colSizes = IntStream.range(0, tableLength)
100-
.mapToObj(n -> iterList.stream().filter(Iterator::hasNext).map(Iterator::next).collect(Collectors.toList()))
101-
.map(col -> col.stream().mapToInt(String::length).max().orElse(0))
102-
.collect(Collectors.toList());
103-
104-
boolean header = true;
105-
for (List<String> row : table) {
106-
result.append(TABLE_INDENT).append(TABLE_COLUMN_SEPARATOR);
107-
for (int i = 0; i < row.size(); i++) {
108-
String cell = row.get(i);
109-
int maxSize = colSizes.get(i) - cell.length() + 2;
110-
int lSpace = maxSize / 2;
111-
int rSpace = maxSize - lSpace;
112-
IntStream.range(0, lSpace).forEach(j -> result.append(ONE_SPACE));
113-
result.append(cell);
114-
IntStream.range(0, rSpace).forEach(j -> result.append(ONE_SPACE));
115-
result.append(TABLE_COLUMN_SEPARATOR);
116-
}
117-
if (header) {
118-
header = false;
119-
result.append(NEW_LINE);
120-
result.append(TABLE_INDENT).append(TABLE_COLUMN_SEPARATOR);
121-
for (int i = 0; i < row.size(); i++) {
122-
int maxSize = colSizes.get(i) + 2;
123-
IntStream.range(0, maxSize).forEach(j -> result.append(TABLE_ROW_SEPARATOR));
124-
result.append(TABLE_COLUMN_SEPARATOR);
125-
}
126-
}
127-
result.append(NEW_LINE);
128-
}
129-
return result.toString().trim();
130-
}
13184
}

src/test/java/com/epam/reportportal/cucumber/FailedTest.java

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.epam.reportportal.service.ReportPortal;
2626
import com.epam.reportportal.service.ReportPortalClient;
2727
import com.epam.reportportal.util.test.CommonUtils;
28+
import com.epam.reportportal.utils.formatting.MarkdownUtils;
2829
import com.epam.ta.reportportal.ws.model.FinishTestItemRQ;
2930
import com.epam.ta.reportportal.ws.model.log.SaveLogRQ;
3031
import cucumber.api.CucumberOptions;
@@ -43,9 +44,8 @@
4344

4445
import static com.epam.reportportal.cucumber.integration.util.TestUtils.filterLogs;
4546
import static org.hamcrest.MatcherAssert.assertThat;
46-
import static org.hamcrest.Matchers.equalTo;
47-
import static org.hamcrest.Matchers.hasSize;
48-
import static org.hamcrest.Matchers.not;
47+
import static org.hamcrest.Matchers.*;
48+
import static org.mockito.Mockito.any;
4949
import static org.mockito.Mockito.*;
5050

5151
/**
@@ -54,22 +54,26 @@
5454
public class FailedTest {
5555

5656
private static final String EXPECTED_ERROR = "java.lang.IllegalStateException: " + FailedSteps.ERROR_MESSAGE;
57-
private static final String ERROR_LOG_TEXT = "Error:\n" + EXPECTED_ERROR;
58-
private static final String DESCRIPTION_ERROR_LOG_TEXT =
59-
"src/test/resources/features/FailedScenario.feature\n"
60-
+ ERROR_LOG_TEXT;
57+
private static final String EXPECTED_STACK_TRACE = EXPECTED_ERROR
58+
+ "\n\tat com.epam.reportportal.cucumber.integration.feature.FailedSteps.i_have_a_failed_step(FailedSteps.java:31)"
59+
+ "\n\tat ✽.Given I have a failed step(src/test/resources/features/FailedScenario.feature:4)\n";
60+
private static final String ERROR_LOG_TEXT = "Error:\n" + EXPECTED_STACK_TRACE;
61+
62+
private static final String SCENARIO_CODE_REFERENCES_WITH_ERROR = MarkdownUtils.asTwoParts("src/test/resources/features/FailedScenario.feature",
63+
ERROR_LOG_TEXT
64+
);
6165

6266
@CucumberOptions(features = "src/test/resources/features/FailedScenario.feature", glue = {
6367
"com.epam.reportportal.cucumber.integration.feature" }, plugin = { "pretty",
6468
"com.epam.reportportal.cucumber.integration.TestScenarioReporter" })
65-
public static class FailedScenarioReporter extends AbstractTestNGCucumberTests {
69+
public static class FailedScenarioReporterTest extends AbstractTestNGCucumberTests {
6670

6771
}
6872

6973
@CucumberOptions(features = "src/test/resources/features/FailedScenario.feature", glue = {
7074
"com.epam.reportportal.cucumber.integration.feature" }, plugin = { "pretty",
7175
"com.epam.reportportal.cucumber.integration.TestStepReporter" })
72-
public static class FailedStepReporter extends AbstractTestNGCucumberTests {
76+
public static class FailedStepReporterTest extends AbstractTestNGCucumberTests {
7377

7478
}
7579

@@ -99,7 +103,7 @@ public void initLaunch() {
99103
@Test
100104
@SuppressWarnings("unchecked")
101105
public void verify_failed_step_reporting_scenario_reporter() {
102-
TestUtils.runTests(FailedScenarioReporter.class);
106+
TestUtils.runTests(FailedScenarioReporterTest.class);
103107

104108
verify(client).startTestItem(any());
105109
verify(client).startTestItem(same(suiteId), any());
@@ -125,7 +129,7 @@ public void verify_failed_step_reporting_scenario_reporter() {
125129
@Test
126130
@SuppressWarnings("unchecked")
127131
public void verify_failed_step_reporting_step_reporter() {
128-
TestUtils.runTests(FailedStepReporter.class);
132+
TestUtils.runTests(FailedStepReporterTest.class);
129133

130134
verify(client).startTestItem(any());
131135
verify(client).startTestItem(same(suiteId), any());
@@ -146,9 +150,8 @@ public void verify_failed_step_reporting_step_reporter() {
146150
}
147151

148152
@Test
149-
@SuppressWarnings("unchecked")
150153
public void verify_failed_nested_step_description_scenario_reporter() {
151-
TestUtils.runTests(FailedScenarioReporter.class);
154+
TestUtils.runTests(FailedScenarioReporterTest.class);
152155

153156
verify(client).startTestItem(any());
154157
verify(client).startTestItem(same(suiteId), any());
@@ -164,13 +167,12 @@ public void verify_failed_nested_step_description_scenario_reporter() {
164167

165168
FinishTestItemRQ step = finishRqs.get(0);
166169
assertThat(step.getDescription(), not(equalTo(ERROR_LOG_TEXT)));
167-
assertThat(step.getDescription(), not(equalTo(DESCRIPTION_ERROR_LOG_TEXT)));
170+
assertThat(step.getDescription(), not(equalTo(SCENARIO_CODE_REFERENCES_WITH_ERROR)));
168171
}
169172

170173
@Test
171-
@SuppressWarnings("unchecked")
172174
public void verify_failed_step_description_step_reporter() {
173-
TestUtils.runTests(FailedStepReporter.class);
175+
TestUtils.runTests(FailedStepReporterTest.class);
174176

175177
verify(client).startTestItem(any());
176178
verify(client).startTestItem(same(suiteId), any());
@@ -185,6 +187,6 @@ public void verify_failed_step_description_step_reporter() {
185187
FinishTestItemRQ step = finishRqs.get(0);
186188
assertThat(step.getDescription(), equalTo(ERROR_LOG_TEXT));
187189
FinishTestItemRQ test = finishRqs.get(1);
188-
assertThat(test.getDescription(), equalTo(DESCRIPTION_ERROR_LOG_TEXT));
190+
assertThat(test.getDescription(), equalTo(SCENARIO_CODE_REFERENCES_WITH_ERROR));
189191
}
190192
}

0 commit comments

Comments
 (0)