1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package dev.aherscu.qa.testrail.reporter;
18
19 import static dev.aherscu.qa.testing.utils.ObjectMapperUtils.*;
20 import static dev.aherscu.qa.testing.utils.UriUtils.*;
21 import static java.text.MessageFormat.*;
22 import static java.util.Collections.*;
23 import static java.util.Objects.*;
24 import static org.apache.commons.io.FileUtils.*;
25
26 import java.io.*;
27 import java.net.*;
28 import java.util.*;
29
30 import org.apache.commons.io.*;
31 import org.apache.commons.io.filefilter.*;
32 import org.testng.xml.*;
33
34 import com.fasterxml.jackson.annotation.*;
35 import com.google.common.collect.*;
36 import com.samskivert.mustache.*;
37 import com.tngtech.jgiven.report.model.*;
38
39 import dev.aherscu.qa.jgiven.reporter.*;
40 import lombok.*;
41 import lombok.experimental.*;
42 import lombok.extern.slf4j.*;
43
44
45
46
47
48
49
50 @SuperBuilder(toBuilder = true)
51 @NoArgsConstructor(force = true)
52 @Slf4j
53 @ToString(callSuper = true)
54 public class TestRailReporter extends QaJGivenPerMethodReporter {
55 @Getter
56 @AllArgsConstructor
57 private enum Status {
58 SUCCESS(1), FAILED(5);
59
60 final int id;
61
62 static Status from(final ExecutionStatus status) {
63 return status == ExecutionStatus.SUCCESS ? SUCCESS : FAILED;
64 }
65 }
66
67 @JsonIgnoreProperties(ignoreUnknown = true)
68 static class AttachScreenshotsResponse {
69 @JsonProperty("attachment_id")
70 String id;
71 }
72
73 @JsonIgnoreProperties(ignoreUnknown = true)
74 static class ResultForCaseResponse {
75 @JsonProperty("id")
76 String id;
77 @JsonProperty("test_id")
78 String testId;
79 }
80
81
82 @SuppressWarnings("hiding")
83 public static final String DEFAULT_TEMPLATE_RESOURCE =
84 "/permethod-reporter.testrail";
85 private final URI testRailUrl;
86 private final String testRailRunId;
87
88 private static Collection<File> listScreenshots(final File directory) {
89 return directory.exists()
90 ? listFiles(directory, new SuffixFileFilter(".png"), null)
91 : emptyList();
92 }
93
94 private static TestRailClient testRailClient(final URI testRailUrl) {
95 val testRailClient = new TestRailClient(testRailUrl.toString());
96 testRailClient.setUser(usernameFrom(testRailUrl));
97 testRailClient.setPassword(passwordFrom(testRailUrl));
98 return testRailClient;
99 }
100
101 @Override
102 protected Mustache.Compiler compiler() {
103 return Mustache.compiler().withEscaper(Escapers.NONE);
104 }
105
106 @Override
107 protected TestRailReportModel reportModel(File targetReportFile) {
108 return TestRailReportModel.builder()
109 .outputDirectory(outputDirectory)
110 .targetReportFile(targetReportFile)
111 .build();
112 }
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128 @Override
129 protected TestRailReporter with(final XmlSuite xmlSuite) {
130 return ((TestRailReporter) super.with(xmlSuite))
131 .toBuilder()
132 .templateResource(templateResourceParamFrom(xmlSuite,
133 DEFAULT_TEMPLATE_RESOURCE))
134 .testRailRunId(
135 requireNonNull(xmlSuite.getParameter("testRailRunId"),
136 "testRailRunId parameter not found in current TestNG XML"))
137 .testRailUrl(URI.create(
138 requireNonNull(xmlSuite.getParameter("testRailUrl"),
139 "testRailUrl parameter not found in current TestNG XML")))
140 .build();
141 }
142
143 @Override
144 protected void reportGenerated(
145 final ScenarioModel scenarioModel,
146 final File reportFile) {
147 super.reportGenerated(scenarioModel, reportFile);
148
149 val testCaseId = readAttributesOf(reportFile)
150
151 .get("dev.aherscu.qa.jgiven.commons.tags.Reference");
152
153 try {
154 val addResultForCaseResponse =
155 addResultForCase(scenarioModel, reportFile, testCaseId);
156 log.debug(
157 "reported result id {} for case {} on run {} to {}/index.php?/tests/view/{}",
158 addResultForCaseResponse.id,
159 testCaseId, testRailRunId, testRailUrl,
160 addResultForCaseResponse.testId);
161
162 listScreenshots(
163 new File(outputDirectory, targetNameFor(scenarioModel)))
164 .forEach(file -> {
165 log.trace("attaching {}", file);
166 val attachScreenshotsResponse =
167 addAttachmentToResult(addResultForCaseResponse.id,
168 file);
169 log.debug("attached {}", attachScreenshotsResponse.id);
170 });
171
172 } catch (final Exception e) {
173 log.error("failed to report case {} on run {} -> {}",
174 testCaseId, testRailRunId, e.toString());
175 }
176 }
177
178 private AttachScreenshotsResponse addAttachmentToResult(
179 final String resultId,
180 final File screenshot) {
181 return fromJson(testRailClient(testRailUrl)
182 .sendPost(format("add_attachment_to_result/{0}",
183 resultId),
184 screenshot.toString())
185 .toString(),
186 AttachScreenshotsResponse.class);
187 }
188
189 private ResultForCaseResponse addResultForCase(
190 final ScenarioModel scenarioModel,
191 final File reportFile, final String testCaseId) throws IOException {
192 try (val fileReader = new FileReader(reportFile)) {
193 return fromJson(testRailClient(testRailUrl)
194 .sendPost(format("add_result_for_case/{0}/{1}",
195 testRailRunId,
196 testCaseId),
197 ImmutableMap.builder()
198 .put("status_id",
199 Status.from(scenarioModel.getExecutionStatus()).id)
200 .put("comment",
201 IOUtils.toString(fileReader))
202 .build())
203 .toString(),
204 ResultForCaseResponse.class);
205 }
206 }
207 }