Skip to content

Commit 281de67

Browse files
authored
Merge pull request #48 from reportportal/develop
Release
2 parents f5b67c1 + 89dba6e commit 281de67

File tree

9 files changed

+213
-5
lines changed

9 files changed

+213
-5
lines changed

CHANGELOG.md

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

33
## [Unreleased]
4+
### Added
5+
- Attachment logging, by @HardNorth
46

57
## [5.3.0]
68
### Changed

src/main/java/com/epam/reportportal/karate/ReportPortalHook.java

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.epam.reportportal.utils.MemoizingSupplier;
2727
import com.epam.reportportal.utils.StatusEvaluation;
2828
import com.epam.reportportal.utils.formatting.MarkdownUtils;
29+
import com.epam.reportportal.utils.reflect.Accessible;
2930
import com.epam.ta.reportportal.ws.model.FinishExecutionRQ;
3031
import com.epam.ta.reportportal.ws.model.FinishTestItemRQ;
3132
import com.epam.ta.reportportal.ws.model.StartTestItemRQ;
@@ -44,10 +45,7 @@
4445

4546
import java.time.Instant;
4647
import java.time.temporal.ChronoUnit;
47-
import java.util.Collections;
48-
import java.util.Map;
49-
import java.util.Optional;
50-
import java.util.Set;
48+
import java.util.*;
5149
import java.util.concurrent.ConcurrentHashMap;
5250
import java.util.function.Supplier;
5351

@@ -322,14 +320,47 @@ public void finishBackground(@Nullable StepResult stepResult, @Nonnull ScenarioR
322320
}
323321
}
324322

323+
/**
324+
* Embed an attachment to ReportPortal.
325+
*
326+
* @param itemId item ID future
327+
* @param embed Karate's Embed object
328+
*/
329+
protected void embedAttachment(@Nonnull Maybe<String> itemId, @Nonnull Embed embed) {
330+
ReportPortalUtils.embedAttachment(itemId, embed);
331+
}
332+
333+
/**
334+
* Embed an attachment to ReportPortal.
335+
*
336+
* @param itemId item ID future
337+
* @param embeddedEntities a list of Karate's Embed object
338+
*/
339+
protected void embedAttachments(@Nonnull Maybe<String> itemId, @Nullable List<Embed> embeddedEntities) {
340+
ofNullable(embeddedEntities).ifPresent(embeds -> embeds.forEach(embed -> embedAttachment(itemId, embed)));
341+
}
342+
325343
@Override
326344
public void afterScenario(ScenarioRuntime sr) {
327345
Maybe<String> scenarioId = scenarioIdMap.get(sr.scenario.getUniqueId());
346+
finishBackground(null, sr);
347+
328348
if (scenarioId == null) {
329349
LOGGER.error("ERROR: Trying to finish unspecified scenario.");
350+
return;
351+
}
352+
353+
try {
354+
@SuppressWarnings("unchecked")
355+
List<Embed> embeddedEntities = (List<Embed>) new Accessible(sr).field("embeds").getValue();
356+
embedAttachments(scenarioId, embeddedEntities);
357+
} catch (Exception e) {
358+
LOGGER.warn(
359+
"Unable to retrieve scenario embeddings; attachments (such as screenshots or logs) will not be reported for this" //
360+
+ " scenario. Test execution and reporting will continue. Exception details:", e
361+
);
330362
}
331363

332-
finishBackground(null, sr);
333364
FinishTestItemRQ rq = buildFinishScenarioRq(sr);
334365
//noinspection ReactiveStreamsUnusedPublisher
335366
launch.get().finishTestItem(scenarioId, rq);
@@ -415,6 +446,9 @@ public void sendStepResults(StepResult stepResult, ScenarioRuntime sr) {
415446
Maybe<String> stepId = stepIdMap.get(sr.scenario.getUniqueId());
416447
Step step = stepResult.getStep();
417448
Result result = stepResult.getResult();
449+
450+
ofNullable(stepResult.getEmbeds()).ifPresent(embeds -> embeds.forEach(embed -> embedAttachment(stepId, embed)));
451+
418452
if (result.isFailed()) {
419453
String fullErrorMessage = step.getPrefix() + " " + step.getText();
420454
String errorMessage = result.getErrorMessage();

src/main/java/com/epam/reportportal/karate/ReportPortalPublisher.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,26 @@ protected void sendLog(Maybe<String> itemId, String message, LogLevel level) {
400400
ReportPortalUtils.sendLog(itemId, message, level);
401401
}
402402

403+
/**
404+
* Embed an attachment to ReportPortal.
405+
*
406+
* @param itemId item ID future
407+
* @param embed Karate's Embed object
408+
*/
409+
protected void embedAttachment(Maybe<String> itemId, Embed embed) {
410+
ReportPortalUtils.embedAttachment(itemId, embed);
411+
}
412+
413+
/**
414+
* Embed an attachment to ReportPortal.
415+
*
416+
* @param itemId item ID future
417+
* @param embeddedEntities a list of Karate's Embed object
418+
*/
419+
protected void embedAttachments(@Nonnull Maybe<String> itemId, @Nullable List<Embed> embeddedEntities) {
420+
ofNullable(embeddedEntities).ifPresent(embeds -> embeds.forEach(embed -> embedAttachment(itemId, embed)));
421+
}
422+
403423
/**
404424
* Send Step execution results to ReportPortal.
405425
*
@@ -412,6 +432,9 @@ public void sendStepResults(StepResult stepResult) {
412432
if (isNotBlank(stepLog)) {
413433
sendLog(stepId, stepLog, LogLevel.DEBUG);
414434
}
435+
436+
embedAttachments(stepId, stepResult.getEmbeds());
437+
415438
if (result.isFailed()) {
416439
String fullErrorMessage = step.getPrefix() + " " + step.getText();
417440
String errorMessage = result.getErrorMessage();

src/main/java/com/epam/reportportal/karate/ReportPortalUtils.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,30 @@ public static void sendLog(Maybe<String> itemId, String message, LogLevel level)
465465
sendLog(itemId, message, level, Instant.now());
466466
}
467467

468+
/**
469+
* Embed an attachment to ReportPortal.
470+
*
471+
* @param itemId item ID future
472+
* @param embed Karate's Embed object
473+
*/
474+
public static void embedAttachment(@Nonnull Maybe<String> itemId, @Nonnull Embed embed) {
475+
ReportPortal.emitLog(itemId, id -> {
476+
SaveLogRQ rq = new SaveLogRQ();
477+
rq.setItemUuid(id);
478+
rq.setLevel(LogLevel.INFO.name());
479+
rq.setLogTime(Instant.now());
480+
rq.setMessage("Attachment: " + embed.getResourceType().contentType);
481+
482+
SaveLogRQ.File file = new SaveLogRQ.File();
483+
file.setName(embed.getFile().getName());
484+
file.setContent(embed.getBytes());
485+
file.setContentType(embed.getResourceType().contentType);
486+
rq.setFile(file);
487+
488+
return rq;
489+
});
490+
}
491+
468492
/**
469493
* Finish sending Launch data to ReportPortal.
470494
*
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright 2025 EPAM Systems
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.epam.reportportal.karate.logging;
18+
19+
import com.epam.reportportal.karate.utils.TestUtils;
20+
import com.epam.reportportal.listeners.LogLevel;
21+
import com.epam.reportportal.service.ReportPortal;
22+
import com.epam.reportportal.service.ReportPortalClient;
23+
import com.epam.reportportal.util.test.CommonUtils;
24+
import com.epam.ta.reportportal.ws.model.log.SaveLogRQ;
25+
import com.intuit.karate.Results;
26+
import okhttp3.MultipartBody;
27+
import org.apache.commons.lang3.tuple.Pair;
28+
import org.junit.jupiter.api.BeforeEach;
29+
import org.junit.jupiter.params.ParameterizedTest;
30+
import org.junit.jupiter.params.provider.ValueSource;
31+
import org.mockito.ArgumentCaptor;
32+
33+
import java.util.List;
34+
import java.util.stream.Collectors;
35+
import java.util.stream.Stream;
36+
37+
import static com.epam.reportportal.karate.utils.TestUtils.*;
38+
import static org.hamcrest.MatcherAssert.assertThat;
39+
import static org.hamcrest.Matchers.*;
40+
import static org.mockito.Mockito.*;
41+
42+
public class EmbedLoggingTest {
43+
private static final String TEST_FEATURE = "classpath:feature/embed.feature";
44+
private final String launchUuid = CommonUtils.namedId("launch_");
45+
private final String featureId = CommonUtils.namedId("feature_");
46+
private final String scenarioId = CommonUtils.namedId("scenario_");
47+
private final List<String> stepIds = Stream.generate(() -> CommonUtils.namedId("step_")).limit(2).collect(Collectors.toList());
48+
49+
private final ReportPortalClient client = mock(ReportPortalClient.class);
50+
private final ReportPortal rp = ReportPortal.create(client, standardParameters(), testExecutor());
51+
52+
@BeforeEach
53+
public void setupMock() {
54+
mockLaunch(client, launchUuid, featureId, scenarioId, stepIds);
55+
mockBatchLogging(client);
56+
}
57+
58+
@ParameterizedTest
59+
@ValueSource(booleans = { true, false })
60+
@SuppressWarnings({ "unchecked", "rawtypes" })
61+
public void test_embed_image_attachment(boolean report) {
62+
Results results;
63+
if (report) {
64+
results = TestUtils.runAsReport(rp, TEST_FEATURE);
65+
} else {
66+
results = TestUtils.runAsHook(rp, TEST_FEATURE);
67+
}
68+
assertThat(results.getFailCount(), equalTo(0));
69+
70+
ArgumentCaptor<List> logCaptor = ArgumentCaptor.forClass(List.class);
71+
verify(client).log(logCaptor.capture());
72+
73+
List<SaveLogRQ> logs = logCaptor.getAllValues()
74+
.stream()
75+
.flatMap(rq -> extractJsonParts((List<MultipartBody.Part>) rq).stream())
76+
.collect(Collectors.toList());
77+
assertThat(logs, hasSize(2));
78+
79+
List<SaveLogRQ> attachmentLogs = logs.stream()
80+
.filter(log -> log.getFile() != null)
81+
.filter(log -> log.getMessage() != null && log.getMessage().startsWith("Attachment: "))
82+
.collect(Collectors.toList());
83+
84+
assertThat("Should have one attachment log message", attachmentLogs, hasSize(1));
85+
86+
// Verify the attachment log properties
87+
SaveLogRQ attachmentLog = attachmentLogs.get(0);
88+
assertThat("Attachment log should have INFO level", attachmentLog.getLevel(), equalTo(LogLevel.INFO.name()));
89+
assertThat("Attachment log should have item UUID", attachmentLog.getItemUuid(), notNullValue());
90+
assertThat("Attachment log should have log time", attachmentLog.getLogTime(), notNullValue());
91+
assertThat("Attachment message should contain image/png", attachmentLog.getMessage(), equalTo("Attachment: image/png"));
92+
93+
List<Pair<String, byte[]>> attachments = logCaptor.getAllValues()
94+
.stream()
95+
.flatMap(rq -> extractBinaryParts((List<MultipartBody.Part>) rq).stream())
96+
.collect(Collectors.toList());
97+
assertThat(attachments, hasSize(1));
98+
assertThat(attachments.get(0).getKey(), equalTo("image/png"));
99+
assertThat(attachments.get(0).getValue().length, greaterThan(0));
100+
}
101+
}
102+

src/test/java/com/epam/reportportal/karate/utils/TestUtils.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import io.reactivex.Maybe;
3636
import jakarta.annotation.Nonnull;
3737
import jakarta.annotation.Nullable;
38+
import okhttp3.MediaType;
3839
import okhttp3.MultipartBody;
3940
import okio.Buffer;
4041
import org.apache.commons.lang3.tuple.Pair;
@@ -232,4 +233,21 @@ public static List<SaveLogRQ> extractJsonParts(List<MultipartBody.Part> parts) {
232233
.flatMap(Collection::stream)
233234
.collect(Collectors.toList());
234235
}
236+
237+
public static List<Pair<String, byte[]>> extractBinaryParts(List<MultipartBody.Part> parts) {
238+
return parts.stream()
239+
.filter(p -> ofNullable(p.headers()).map(headers -> headers.get("Content-Disposition"))
240+
.map(h -> h.contains(Constants.LOG_REQUEST_BINARY_PART))
241+
.orElse(false))
242+
.map(MultipartBody.Part::body)
243+
.map(b -> {
244+
Buffer buf = new Buffer();
245+
try {
246+
b.writeTo(buf);
247+
} catch (IOException ignore) {
248+
}
249+
return Pair.of(ofNullable(b.contentType()).map(MediaType::toString).orElse(null), buf.readByteArray());
250+
})
251+
.collect(Collectors.toList());
252+
}
235253
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Feature: Demonstrate image attachment
2+
3+
Scenario: I attach image to report
4+
When def bytes = karate.read('classpath:pug/lucky.png')
5+
Then karate.embed(bytes, 'image/png')

src/test/resources/pug/lucky.png

2.22 MB
Loading

src/test/resources/pug/unlucky.png

2.15 MB
Loading

0 commit comments

Comments
 (0)