View Javadoc
1   /*
2    * Copyright 2023 Adrian Herscu
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    *     http://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 dev.aherscu.qa.testrail.reporter;
18  
19  import static dev.aherscu.qa.testing.utils.FileUtilsExtensions.*;
20  import static java.lang.Long.*;
21  import static java.nio.charset.StandardCharsets.*;
22  import static org.apache.commons.io.FilenameUtils.*;
23  
24  import java.io.*;
25  import java.util.*;
26  
27  import com.samskivert.mustache.*;
28  import com.tngtech.jgiven.report.model.*;
29  
30  import dev.aherscu.qa.jgiven.reporter.*;
31  import dev.aherscu.qa.testing.utils.*;
32  import lombok.*;
33  import lombok.experimental.*;
34  import lombok.extern.slf4j.*;
35  
36  /**
37   * Adds screenshot manipulation functionality.
38   *
39   * @see #saveScreenshot
40   */
41  @SuperBuilder(toBuilder = true)
42  @Slf4j
43  public class TestRailReportModel extends QaJGivenReportModel<ScenarioModel> {
44      public final File            outputDirectory;
45  
46      // NOTE: unfortunately cannot attach the screenshot during report generation
47      // That would prevent polluting the disk with screenshot files.
48      // The TestRail add_attachment_to_result API requires an id to make
49      // the attachment. That id is returned by add_result_for_case API, which
50      // implies that the report is fully generated...
51      // Another unfortunate limitation... Cannot add links to these attachments
52      // inside the report, since an attachment_id is made available only after
53      // calling add_attachment_to_result, which implies the report is generated.
54      public final Mustache.Lambda saveScreenshot = this::saveScreenshot;
55  
56      /**
57       * To be called from Mustache template for saving screenshots as separate
58       * PNG files, in a directory matching the name of the test report file.
59       *
60       * <p>
61       * These are later picked up by the {@link TestRailReporter} and attached to
62       * the test result in TestRail.
63       * </p>
64       *
65       * @param frag
66       *            Mustache fragment to process, assumed to be a screenshot in
67       *            Base64 PNG format
68       * @param out
69       *            Mustache output stream; will be written the screenshot file
70       *            name (currently, a hex representation of its contents hash)
71       */
72      @SneakyThrows
73      public void saveScreenshot(final Template.Fragment frag, final Writer out) {
74          val screenshotsDirectory = new File(outputDirectory,
75              removeExtension(targetReportFile.getName()));
76          if (!screenshotsDirectory.exists()) {
77              log.trace("creating screenshots directory {}",
78                  screenshotsDirectory);
79              forceMkdir(screenshotsDirectory);
80          }
81          val screenshotHash = toHexString(frag.execute().hashCode());
82          out.write(screenshotHash);
83          val screenshotFile =
84              new File(screenshotsDirectory, screenshotHash + ".png");
85          log.trace("saving screenshot to {}", screenshotFile);
86          try (
87              val screenshotOutputStream = new FileOutputStream(screenshotFile);
88              val byteArrayInputStream = new ByteArrayInputStream(Base64
89                  .getMimeDecoder()
90                  .decode(frag.execute().getBytes(UTF_8)));
91              val fileOutputStream = ImageUtils.Pipeline
92                  .from(byteArrayInputStream)
93                  .into(screenshotOutputStream, "png")) {
94              // nothing to do here
95          }
96      }
97  }