Skip to content
This repository was archived by the owner on Jun 17, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:
java-version: '8'

- name: Setup git credentials
uses: oleksiyrudenko/gha-git-credentials@v2.1.1
uses: oleksiyrudenko/gha-git-credentials@v2-latest
with:
name: 'reportportal.io'
email: '[email protected]'
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Changelog

## [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

## [5.2.3]
### Changed
Expand Down
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ repositories {
}

dependencies {
api 'com.epam.reportportal:client-java:5.2.13'
api 'com.epam.reportportal:client-java:5.2.21'
api 'info.cukes:gherkin:2.12.2'

implementation 'org.slf4j:slf4j-api:2.0.7'
Expand All @@ -61,7 +61,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 {
Expand Down
74 changes: 63 additions & 11 deletions src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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.properties.SystemAttributesExtractor;
import com.epam.ta.reportportal.ws.model.FinishExecutionRQ;
Expand All @@ -39,6 +40,7 @@
import gherkin.formatter.Reporter;
import gherkin.formatter.model.*;
import io.reactivex.Maybe;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.text.StringEscapeUtils;
import org.slf4j.Logger;
Expand All @@ -51,6 +53,7 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import java.util.stream.Collectors;
Expand All @@ -59,10 +62,12 @@
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 com.epam.reportportal.utils.formatting.MarkdownUtils.formatDataTable;
import static java.lang.String.format;
import static java.util.Optional.ofNullable;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.exception.ExceptionUtils.getStackTrace;

/**
* Abstract Cucumber formatter/reporter for Report Portal
Expand All @@ -79,6 +84,7 @@ public abstract class AbstractReporter implements Formatter, Reporter {
private static final String GET_LOCATION_METHOD_NAME = "getLocation";
private static final String METHOD_OPENING_BRACKET = "(";
private static final String DOCSTRING_DECORATOR = "\n\"\"\"\n";
private static final String ERROR_FORMAT = "Error:\n%s";

public static final TestItemTree ITEM_TREE = new TestItemTree();
private static volatile ReportPortal REPORT_PORTAL = ReportPortal.builder().build();
Expand All @@ -89,6 +95,15 @@ public abstract class AbstractReporter implements Formatter, Reporter {
protected final ThreadLocal<RunningContext.FeatureContext> currentFeatureContext = new ThreadLocal<>();
protected final ThreadLocal<RunningContext.ScenarioContext> currentScenarioContext = new ThreadLocal<>();

/**
* This map uses to record the description of the scenario and the step to append the error to the description.
*/
private final Map<Maybe<String>, String> descriptionsMap = new ConcurrentHashMap<>();
/**
* This map uses to record errors to append to the description.
*/
private final Map<Maybe<String>, Throwable> errorMap = new ConcurrentHashMap<>();

private AtomicBoolean finished = new AtomicBoolean(false);

protected final Supplier<Launch> launch = new MemoizingSupplier<>(new Supplier<Launch>() {
Expand Down Expand Up @@ -259,6 +274,7 @@ protected void beforeScenario(Scenario scenario, String outlineIteration) {
scenarioContext.setId(startScenario(featureContext.getId(), rq));
scenarioContext.setLine(scenario.getLine());
scenarioContext.setFeatureUri(uri);
descriptionsMap.put(scenarioContext.getId(), ofNullable(rq.getDescription()).orElse(StringUtils.EMPTY));
if (myLaunch.getParameters().isCallbackReportingEnabled()) {
addToTree(featureContext, scenarioContext);
}
Expand All @@ -280,11 +296,34 @@ private void removeFromTree(RunningContext.FeatureContext featureContext, Runnin
@SuppressWarnings("unused")
protected FinishTestItemRQ buildFinishTestItemRequest(@Nonnull Maybe<String> itemId, @Nullable ItemStatus status) {
FinishTestItemRQ rq = new FinishTestItemRQ();
if (status == ItemStatus.FAILED) {
Optional<String> currentDescription = Optional.ofNullable(descriptionsMap.get(itemId));
Optional<Throwable> currentError = Optional.ofNullable(errorMap.get(itemId));
currentDescription.flatMap(description -> currentError.map(errorMessage -> resolveDescriptionErrorMessage(
description,
errorMessage
))).ifPresent(rq::setDescription);
}
ofNullable(status).ifPresent(s -> rq.setStatus(s.name()));
rq.setEndTime(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) {
String errorStr = format(ERROR_FORMAT, getStackTrace(error, new Throwable()));
return Optional.ofNullable(currentDescription)
.filter(StringUtils::isNotBlank)
.map(description -> MarkdownUtils.asTwoParts(currentDescription, errorStr))
.orElse(errorStr);
}

/**
* Finish a test item with specified status
*
Expand All @@ -296,8 +335,9 @@ protected void finishTestItem(@Nullable Maybe<String> itemId, @Nullable ItemStat
LOGGER.error("BUG: Trying to finish unspecified test item.");
return;
}
FinishTestItemRQ finishTestItemRQ = buildFinishTestItemRequest(itemId, status);
//noinspection ReactiveStreamsUnusedPublisher
launch.get().finishTestItem(itemId, buildFinishTestItemRequest(itemId, status));
launch.get().finishTestItem(itemId, finishTestItemRQ);
}

/**
Expand Down Expand Up @@ -388,7 +428,8 @@ protected Maybe<String> startStep(@Nonnull Maybe<String> scenarioId, @Nonnull St
}

private void addToTree(@Nonnull RunningContext.ScenarioContext scenarioContext, @Nullable String text, @Nullable Maybe<String> stepId) {
retrieveLeaf(scenarioContext.getFeatureUri(),
retrieveLeaf(
scenarioContext.getFeatureUri(),
scenarioContext.getLine(),
ITEM_TREE
).ifPresent(scenarioLeaf -> scenarioLeaf.getChildItems().put(createKey(text), TestItemTree.createTestItemLeaf(stepId)));
Expand All @@ -406,6 +447,9 @@ protected void beforeStep(Step step, Match match) {
Maybe<String> stepId = startStep(context.getId(), rq);
context.setCurrentStepId(stepId);
String stepText = step.getName();
if (rq.isHasStats()) {
descriptionsMap.put(stepId, ofNullable(rq.getDescription()).orElse(StringUtils.EMPTY));
}

if (launch.get().getParameters().isCallbackReportingEnabled()) {
addToTree(context, stepText, stepId);
Expand Down Expand Up @@ -441,8 +485,8 @@ protected StartTestItemRQ buildStartHookRequest(boolean isBefore) {
/**
* Start before/after-hook item on Report Portal
*
* @param parentId parent item id
* @param rq hook start request
* @param parentId parent item id
* @param rq hook start request
* @return hook item id
*/
@Nonnull
Expand Down Expand Up @@ -503,10 +547,15 @@ protected void reportResult(@Nonnull Result result, @Nullable String message) {
if (errorMessage != null) {
sendLog(errorMessage, level);
} else if (result.getError() != null) {
sendLog(getStackTrace(result.getError()), level);
sendLog(getStackTrace(result.getError(), new Throwable()), level);
}
RunningContext.ScenarioContext currentScenario = getCurrentScenarioContext();
currentScenario.updateStatus(mapStatus(result.getStatus()));
ItemStatus itemStatus = mapStatus(result.getStatus());
currentScenario.updateStatus(itemStatus);
if (itemStatus == ItemStatus.FAILED) {
errorMap.put(currentScenario.getId(), result.getError());
errorMap.put(currentScenario.getCurrentStepId(), result.getError());
}
}

/**
Expand Down Expand Up @@ -572,7 +621,8 @@ private static String getDataType(@Nonnull byte[] data) {
public void embedding(String mimeType, byte[] data) {
String type = ofNullable(mimeType).filter(ContentType::isValidType).orElseGet(() -> getDataType(data));
String attachmentName = 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()
);
Expand Down Expand Up @@ -702,7 +752,8 @@ protected TestCaseIdEntry getTestCaseId(@Nonnull Match match, @Nullable String c
if (method == null) {
return getTestCaseId(codeRef, match.getArguments());
}
return TestCaseIdUtils.getTestCaseId(method.getAnnotation(TestCaseId.class),
return TestCaseIdUtils.getTestCaseId(
method.getAnnotation(TestCaseId.class),
method,
codeRef,
(List<Object>) ARGUMENTS_TRANSFORM.apply(match.getArguments())
Expand Down Expand Up @@ -866,8 +917,9 @@ protected List<ParameterResource> getParameters(@Nonnull Step step, @Nullable St
.filter(ds -> !ds.isEmpty())
.ifPresent(ds -> params.add(Pair.of("docstring", StringEscapeUtils.escapeHtml4(ds))));
ofNullable(step.getRows()).filter(rows -> !rows.isEmpty())
.ifPresent(rows -> params.add(Pair.of("datatable",
Utils.formatDataTable(rows.stream().map(Row::getCells).collect(Collectors.toList()))
.ifPresent(rows -> params.add(Pair.of(
"datatable",
formatDataTable(rows.stream().map(Row::getCells).collect(Collectors.toList()))
)));
return params.isEmpty() ? Collections.emptyList() : ParameterUtils.getParameters(codeRef, params);
}
Expand Down
55 changes: 4 additions & 51 deletions src/main/java/com/epam/reportportal/cucumber/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,18 @@
import javax.annotation.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static java.util.Optional.ofNullable;

public class Utils {
private static final String STEP_DEFINITION_FIELD_NAME = "stepDefinition";
private static final String METHOD_FIELD_NAME = "method";
public static final String ONE_SPACE = "\u00A0";
private static final String NEW_LINE = "\r\n";
public static final String TABLE_INDENT = "\u00A0\u00A0\u00A0\u00A0";
public static final String TABLE_COLUMN_SEPARATOR = "|";
public static final String TABLE_ROW_SEPARATOR = "-";

public static final Map<String, ItemStatus> STATUS_MAPPING = Collections.unmodifiableMap(new HashMap<String, ItemStatus>() {{
put("passed", ItemStatus.PASSED);
Expand Down Expand Up @@ -84,48 +81,4 @@ public static Method retrieveMethod(@Nonnull Match match) throws NoSuchFieldExce
public static final Function<List<Argument>, List<?>> ARGUMENTS_TRANSFORM = arguments -> ofNullable(arguments).map(args -> args.stream()
.map(Argument::getVal)
.collect(Collectors.toList())).orElse(null);

/**
* Converts a table represented as List of Lists to a formatted table string
*
* @param table a table object
* @return string representation of the table
*/
@Nonnull
public static String formatDataTable(@Nonnull final List<List<String>> table) {
StringBuilder result = new StringBuilder();
int tableLength = table.stream().mapToInt(List::size).max().orElse(-1);
List<Iterator<String>> iterList = table.stream().map(List::iterator).collect(Collectors.toList());
List<Integer> colSizes = IntStream.range(0, tableLength)
.mapToObj(n -> iterList.stream().filter(Iterator::hasNext).map(Iterator::next).collect(Collectors.toList()))
.map(col -> col.stream().mapToInt(String::length).max().orElse(0))
.collect(Collectors.toList());

boolean header = true;
for (List<String> row : table) {
result.append(TABLE_INDENT).append(TABLE_COLUMN_SEPARATOR);
for (int i = 0; i < row.size(); i++) {
String cell = row.get(i);
int maxSize = colSizes.get(i) - cell.length() + 2;
int lSpace = maxSize / 2;
int rSpace = maxSize - lSpace;
IntStream.range(0, lSpace).forEach(j -> result.append(ONE_SPACE));
result.append(cell);
IntStream.range(0, rSpace).forEach(j -> result.append(ONE_SPACE));
result.append(TABLE_COLUMN_SEPARATOR);
}
if (header) {
header = false;
result.append(NEW_LINE);
result.append(TABLE_INDENT).append(TABLE_COLUMN_SEPARATOR);
for (int i = 0; i < row.size(); i++) {
int maxSize = colSizes.get(i) + 2;
IntStream.range(0, maxSize).forEach(j -> result.append(TABLE_ROW_SEPARATOR));
result.append(TABLE_COLUMN_SEPARATOR);
}
}
result.append(NEW_LINE);
}
return result.toString().trim();
}
}
Loading