From f608606df16a5a5485a5ccaa8f2b4e3bc18af2c5 Mon Sep 17 00:00:00 2001 From: grasshopper7 Date: Wed, 23 Aug 2023 16:30:15 +0530 Subject: [PATCH 1/5] Add video display in tests --- .../aventstack/extentreports/ExtentTest.java | 35 +++- .../extentreports/MediaEntityBuilder.java | 27 ++- .../append/JsonDeserializer.java | 2 +- .../append/MediaTypeAdapter.java | 61 +++++++ .../append/RawEntityConverter.java | 18 +- .../gson/GsonExtentTypeAdapterBuilder.java | 7 +- .../aventstack/extentreports/model/Log.java | 11 +- .../aventstack/extentreports/model/Media.java | 5 + .../extentreports/model/ScreenCapture.java | 16 +- .../aventstack/extentreports/model/Test.java | 12 +- .../aventstack/extentreports/model/Video.java | 24 +++ .../model/service/MediaService.java | 42 ++++- .../model/service/TestService.java | 41 ++++- .../templates/commons/commons-macros.ftl | 31 ++-- .../templates/spark/macros/recurse_nodes.ftl | 2 +- .../templates/spark/partials/bdd-content.ftl | 4 +- .../extentreports/AppenderTest.java | 58 +++++++ .../extentreports/ExtentTestLogTest.java | 49 ++++-- .../extentreports/ExtentTestMediaTest.java | 154 ++++++++++++++++++ .../extentreports/entity/LogEntityTest.java | 15 ++ .../extentreports/entity/TestEntityTest.java | 11 ++ .../entity/service/MediaServiceTest.java | 83 ++++++++++ .../entity/service/TestServiceTest.java | 52 ++++++ .../reporter/SparkMediaTest.java | 58 +++++++ src/test/resources/vid.mp4 | Bin 0 -> 32245 bytes 25 files changed, 751 insertions(+), 67 deletions(-) create mode 100644 src/main/java/com/aventstack/extentreports/append/MediaTypeAdapter.java create mode 100644 src/main/java/com/aventstack/extentreports/model/Video.java create mode 100644 src/test/java/com/aventstack/extentreports/entity/service/MediaServiceTest.java create mode 100644 src/test/java/com/aventstack/extentreports/reporter/SparkMediaTest.java create mode 100644 src/test/resources/vid.mp4 diff --git a/src/main/java/com/aventstack/extentreports/ExtentTest.java b/src/main/java/com/aventstack/extentreports/ExtentTest.java index 61a225e..2fb6485 100644 --- a/src/main/java/com/aventstack/extentreports/ExtentTest.java +++ b/src/main/java/com/aventstack/extentreports/ExtentTest.java @@ -15,6 +15,7 @@ import com.aventstack.extentreports.model.RunResult; import com.aventstack.extentreports.model.ScreenCapture; import com.aventstack.extentreports.model.Test; +import com.aventstack.extentreports.model.Video; import com.aventstack.extentreports.model.service.ExceptionInfoService; import com.aventstack.extentreports.util.Assert; @@ -1110,7 +1111,7 @@ public ExtentTest addScreenCaptureFromPath(String path) { public ExtentTest addScreenCaptureFromBase64String(String base64, String title) { Assert.notEmpty(base64, "ScreenCapture's base64 string must not be null or empty"); if (!base64.startsWith("data:")) - base64 = "data:image/png;base64," + base64; + base64 = ScreenCapture.BASE64_ENCODED + base64; Media m = ScreenCapture.builder().base64(base64).title(title).build(); model.addMedia(m); extent.onMediaAdded(m, model); @@ -1120,4 +1121,36 @@ public ExtentTest addScreenCaptureFromBase64String(String base64, String title) public ExtentTest addScreenCaptureFromBase64String(String base64) { return addScreenCaptureFromBase64String(base64, null); } + + public ExtentTest addVideoFromPath(String path, String title) { + Assert.notEmpty(path, "Video path must not be null or empty"); + Media m = Video.builder().path(path).title(title).build(); + model.addMedia(m); + extent.onMediaAdded(m, model); + return this; + } + + public ExtentTest addVideoFromPath(String path) { + return addVideoFromPath(path, null); + } + + public ExtentTest addVideoFromBase64String(String base64, String title) { + Assert.notEmpty(base64, "Video's base64 string must not be null or empty"); + if (!base64.startsWith("data:")) + base64 = Video.BASE64_ENCODED + base64; + Media m = Video.builder().base64(base64).title(title).build(); + model.addMedia(m); + extent.onMediaAdded(m, model); + return this; + } + + public ExtentTest addVideoFromBase64String(String base64) { + return addVideoFromBase64String(base64, null); + } + + public ExtentTest addMedia(Media m) { + model.addMedia(m); + extent.onMediaAdded(m, model); + return this; + } } \ No newline at end of file diff --git a/src/main/java/com/aventstack/extentreports/MediaEntityBuilder.java b/src/main/java/com/aventstack/extentreports/MediaEntityBuilder.java index 9676170..ed45e88 100644 --- a/src/main/java/com/aventstack/extentreports/MediaEntityBuilder.java +++ b/src/main/java/com/aventstack/extentreports/MediaEntityBuilder.java @@ -2,6 +2,7 @@ import com.aventstack.extentreports.model.Media; import com.aventstack.extentreports.model.ScreenCapture; +import com.aventstack.extentreports.model.Video; import com.aventstack.extentreports.util.Assert; /** @@ -9,7 +10,7 @@ * */ public class MediaEntityBuilder { - private static final String BASE64_ENCODED = "data:image/png;base64,"; + private static ThreadLocal media = new ThreadLocal<>(); private static class MediaBuilderInstance { @@ -43,7 +44,7 @@ public static MediaEntityBuilder createScreenCaptureFromPath(String path) { public static MediaEntityBuilder createScreenCaptureFromBase64String(String base64, String title) { Assert.notEmpty(base64, "ScreenCapture's base64 string must not be null or empty"); if (!base64.startsWith("data:")) - base64 = BASE64_ENCODED + base64; + base64 = ScreenCapture.BASE64_ENCODED + base64; media.set(ScreenCapture.builder().base64(base64).title(title).build()); return getInstance(); } @@ -51,4 +52,26 @@ public static MediaEntityBuilder createScreenCaptureFromBase64String(String base public static MediaEntityBuilder createScreenCaptureFromBase64String(String base64) { return createScreenCaptureFromBase64String(base64, null); } + + public static MediaEntityBuilder createVideoFromPath(String path, String title) { + Assert.notEmpty(path, "Video path must not be null or empty"); + media.set(Video.builder().path(path).title(title).build()); + return getInstance(); + } + + public static MediaEntityBuilder createVideoFromPath(String path) { + return createVideoFromPath(path, null); + } + + public static MediaEntityBuilder createVideoFromBase64String(String base64, String title) { + Assert.notEmpty(base64, "Video's base64 string must not be null or empty"); + if (!base64.startsWith("data:")) + base64 = Video.BASE64_ENCODED + base64; + media.set(Video.builder().base64(base64).title(title).build()); + return getInstance(); + } + + public static MediaEntityBuilder createVideoFromBase64String(String base64) { + return createVideoFromBase64String(base64, null); + } } \ No newline at end of file diff --git a/src/main/java/com/aventstack/extentreports/append/JsonDeserializer.java b/src/main/java/com/aventstack/extentreports/append/JsonDeserializer.java index c2342ad..bc49428 100644 --- a/src/main/java/com/aventstack/extentreports/append/JsonDeserializer.java +++ b/src/main/java/com/aventstack/extentreports/append/JsonDeserializer.java @@ -22,7 +22,7 @@ public JsonDeserializer(final File f) { public List deserialize() throws IOException { Gson gson = GsonExtentTypeAdapterBuilder.builder() .withBddTypeAdapterFactory() - .withScreenCaptureTypeAdapter() + .withMediaTypeAdapter() .build(); String json = new String(Files.readAllBytes(f.toPath())); Type t = new TypeToken>(){}.getType(); diff --git a/src/main/java/com/aventstack/extentreports/append/MediaTypeAdapter.java b/src/main/java/com/aventstack/extentreports/append/MediaTypeAdapter.java new file mode 100644 index 0000000..8cad5bf --- /dev/null +++ b/src/main/java/com/aventstack/extentreports/append/MediaTypeAdapter.java @@ -0,0 +1,61 @@ +package com.aventstack.extentreports.append; + +import java.io.IOException; + +import com.aventstack.extentreports.model.Media; +import com.aventstack.extentreports.model.service.MediaService; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +public class MediaTypeAdapter extends TypeAdapter { + + @Override + public void write(JsonWriter out, Media value) throws IOException { + } + + @Override + public Media read(JsonReader reader) throws IOException { + reader.beginObject(); + String fieldName = null; + int cycle = 0; + + String type = null; + String path = null; + String resolvedPath = null; + String title = null; + String base64 = null; + + while (reader.hasNext()) { + JsonToken token = reader.peek(); + if (token.equals(JsonToken.NAME)) { + fieldName = reader.nextName(); + } + if ("string".equalsIgnoreCase(token.name()) && fieldName.equalsIgnoreCase("type")) { + token = reader.peek(); + type = reader.nextString(); + } + if ("string".equalsIgnoreCase(token.name()) && fieldName.equalsIgnoreCase("path")) { + token = reader.peek(); + path = reader.nextString(); + } + if ("string".equalsIgnoreCase(token.name()) && fieldName.equalsIgnoreCase("resolvedPath")) { + token = reader.peek(); + resolvedPath = reader.nextString(); + } + if ("string".equalsIgnoreCase(token.name()) && fieldName.equalsIgnoreCase("title")) { + token = reader.peek(); + title = reader.nextString(); + } + if ("string".equalsIgnoreCase(token.name()) && fieldName.equalsIgnoreCase("base64")) { + token = reader.peek(); + base64 = reader.nextString(); + } + if (cycle++ > 10) + return MediaService.createMedia(type, path, resolvedPath, title, base64); + } + reader.endObject(); + return MediaService.createMedia(type, path, resolvedPath, title, base64); + } +} diff --git a/src/main/java/com/aventstack/extentreports/append/RawEntityConverter.java b/src/main/java/com/aventstack/extentreports/append/RawEntityConverter.java index 59a6f5e..410e155 100644 --- a/src/main/java/com/aventstack/extentreports/append/RawEntityConverter.java +++ b/src/main/java/com/aventstack/extentreports/append/RawEntityConverter.java @@ -7,10 +7,8 @@ import com.aventstack.extentreports.ExtentReports; import com.aventstack.extentreports.ExtentTest; import com.aventstack.extentreports.GherkinKeyword; -import com.aventstack.extentreports.MediaEntityBuilder; import com.aventstack.extentreports.model.Log; import com.aventstack.extentreports.model.Media; -import com.aventstack.extentreports.model.ScreenCapture; import com.aventstack.extentreports.model.Test; public class RawEntityConverter { @@ -79,24 +77,14 @@ else if (log.hasMedia()) } private void addMedia(Log log, ExtentTest extentTest, Throwable ex) { - Media m = log.getMedia(); - if (m.getPath() != null) { - extentTest.log(log.getStatus(), ex, - MediaEntityBuilder.createScreenCaptureFromPath(m.getPath()).build()); - } else if (((ScreenCapture) m).getBase64() != null) { - extentTest.log(log.getStatus(), ex, - MediaEntityBuilder.createScreenCaptureFromBase64String(((ScreenCapture) m).getBase64()).build()); - } + Media m = log.getMedia(); + extentTest.log(log.getStatus(), ex, m); } private void addMedia(Test test, ExtentTest extentTest) { if (test.getMedia() != null) { for (Media m : test.getMedia()) { - if (m.getPath() != null) { - extentTest.addScreenCaptureFromPath(m.getPath()); - } else if (m instanceof ScreenCapture) { - extentTest.addScreenCaptureFromBase64String(((ScreenCapture) m).getBase64()); - } + extentTest.addMedia(m); } } } diff --git a/src/main/java/com/aventstack/extentreports/gson/GsonExtentTypeAdapterBuilder.java b/src/main/java/com/aventstack/extentreports/gson/GsonExtentTypeAdapterBuilder.java index 5c873d3..b042eb0 100644 --- a/src/main/java/com/aventstack/extentreports/gson/GsonExtentTypeAdapterBuilder.java +++ b/src/main/java/com/aventstack/extentreports/gson/GsonExtentTypeAdapterBuilder.java @@ -1,6 +1,6 @@ package com.aventstack.extentreports.gson; -import com.aventstack.extentreports.append.ScreenCaptureTypeAdapter; +import com.aventstack.extentreports.append.MediaTypeAdapter; import com.aventstack.extentreports.model.Media; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -18,8 +18,9 @@ public Builder withBddTypeAdapterFactory() { return this; } - public Builder withScreenCaptureTypeAdapter() { - builder.registerTypeAdapter(Media.class, new ScreenCaptureTypeAdapter()); + public Builder withMediaTypeAdapter() { + //builder.registerTypeAdapter(Media.class, new ScreenCaptureTypeAdapter()); + builder.registerTypeAdapter(Media.class, new MediaTypeAdapter()); return this; } diff --git a/src/main/java/com/aventstack/extentreports/model/Log.java b/src/main/java/com/aventstack/extentreports/model/Log.java index 04e80d0..01fe623 100644 --- a/src/main/java/com/aventstack/extentreports/model/Log.java +++ b/src/main/java/com/aventstack/extentreports/model/Log.java @@ -41,11 +41,20 @@ public final boolean hasException() { public final void addMedia(Media media) { if (media != null && ((media.getPath() != null || media.getResolvedPath() != null) - || media instanceof ScreenCapture && ((ScreenCapture) media).getBase64() != null)) + || (media instanceof ScreenCapture && ((ScreenCapture) media).getBase64() != null) + || (media instanceof Video && ((Video) media).getBase64() != null))) this.media = media; } public final boolean hasMedia() { return media != null; } + + public final boolean hasScreenCapture() { + return media != null && media instanceof ScreenCapture; + } + + public final boolean hasVideo() { + return media != null && media instanceof Video; + } } diff --git a/src/main/java/com/aventstack/extentreports/model/Media.java b/src/main/java/com/aventstack/extentreports/model/Media.java index 1bdfeb8..9ef6d15 100644 --- a/src/main/java/com/aventstack/extentreports/model/Media.java +++ b/src/main/java/com/aventstack/extentreports/model/Media.java @@ -13,8 +13,13 @@ public class Media implements Serializable, BaseEntity, MetaDataStorable { private static final long serialVersionUID = 5428859443090457608L; + private MediaType type; private String path; private String title; private String resolvedPath; private transient Map infoMap; + + public enum MediaType { + SCREENCAPTURE, VIDEO + } } diff --git a/src/main/java/com/aventstack/extentreports/model/ScreenCapture.java b/src/main/java/com/aventstack/extentreports/model/ScreenCapture.java index 5f441f0..f5d495f 100644 --- a/src/main/java/com/aventstack/extentreports/model/ScreenCapture.java +++ b/src/main/java/com/aventstack/extentreports/model/ScreenCapture.java @@ -10,13 +10,15 @@ @Getter @Setter public class ScreenCapture extends Media implements Serializable { - private static final long serialVersionUID = -3047762572007885369L; + private static final long serialVersionUID = -3047762572007885369L; + + public static final String BASE64_ENCODED = "data:image/png;base64,"; - private String base64; + private String base64; - @Builder - public ScreenCapture(String path, String title, String resolvedPath, String base64) { - super(path, title, resolvedPath, new HashMap()); - this.base64 = base64; - } + @Builder + public ScreenCapture(String path, String title, String resolvedPath, String base64) { + super(MediaType.SCREENCAPTURE, path, title, resolvedPath, new HashMap()); + this.base64 = base64; + } } diff --git a/src/main/java/com/aventstack/extentreports/model/Test.java b/src/main/java/com/aventstack/extentreports/model/Test.java index 80fdc14..b7e1965 100644 --- a/src/main/java/com/aventstack/extentreports/model/Test.java +++ b/src/main/java/com/aventstack/extentreports/model/Test.java @@ -158,7 +158,9 @@ public String getFullName() { public void addMedia(Media m) { if (m != null - && (m.getPath() != null || m.getResolvedPath() != null || ((ScreenCapture) m).getBase64() != null)) + && (m.getPath() != null || m.getResolvedPath() != null || + (m instanceof ScreenCapture && ((ScreenCapture) m).getBase64() != null) || + (m instanceof Video && ((Video) m).getBase64() != null))) media.add(m); end(status); } @@ -166,6 +168,14 @@ public void addMedia(Media m) { public boolean hasScreenCapture() { return !media.isEmpty() && media.stream().anyMatch(x -> x instanceof ScreenCapture); } + + public boolean hasVideo() { + return !media.isEmpty() && media.stream().anyMatch(x -> x instanceof Video); + } + + public boolean hasMedia() { + return !media.isEmpty() && media.stream().anyMatch(x -> x instanceof Media); + } public long timeTaken() { return endTime.getTime() - startTime.getTime(); diff --git a/src/main/java/com/aventstack/extentreports/model/Video.java b/src/main/java/com/aventstack/extentreports/model/Video.java new file mode 100644 index 0000000..8645a98 --- /dev/null +++ b/src/main/java/com/aventstack/extentreports/model/Video.java @@ -0,0 +1,24 @@ +package com.aventstack.extentreports.model; + +import java.io.Serializable; +import java.util.HashMap; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class Video extends Media implements Serializable { + private static final long serialVersionUID = -6874043323709622353L; + + public static final String BASE64_ENCODED = "data:video/mp4;base64,"; + + private String base64; + + @Builder + public Video(String path, String title, String resolvedPath, String base64) { + super(MediaType.VIDEO, path, title, resolvedPath, new HashMap()); + this.base64 = base64; + } +} diff --git a/src/main/java/com/aventstack/extentreports/model/service/MediaService.java b/src/main/java/com/aventstack/extentreports/model/service/MediaService.java index 155a8b3..dd93853 100644 --- a/src/main/java/com/aventstack/extentreports/model/service/MediaService.java +++ b/src/main/java/com/aventstack/extentreports/model/service/MediaService.java @@ -5,11 +5,14 @@ import java.nio.file.Paths; import com.aventstack.extentreports.model.Media; +import com.aventstack.extentreports.model.Media.MediaType; import com.aventstack.extentreports.model.ScreenCapture; +import com.aventstack.extentreports.model.Video; public class MediaService { public static void tryResolveMediaPath(Media media, String[] knownPath) { - if (knownPath == null || (media instanceof ScreenCapture && ((ScreenCapture) media).getBase64() != null)) + if (knownPath == null || (media instanceof ScreenCapture && ((ScreenCapture) media).getBase64() != null) + || (media instanceof Video && ((Video) media).getBase64() != null)) return; String loc = media.getPath(); File f = new File(loc); @@ -38,4 +41,41 @@ public static String getBase64(Media m) { return ((ScreenCapture) m).getBase64(); return null; } + + public static boolean isVideoBase64(Media m) { + return m instanceof Video && ((Video) m).getBase64() != null; + } + + public static String getVideoBase64(Media m) { + if (isVideoBase64(m)) + return ((Video) m).getBase64(); + return null; + } + + public static Media createMedia(String type, String path, String resolvedPath, String title, String base64) { + if (MediaType.valueOf(type) == MediaType.SCREENCAPTURE) { + ScreenCapture sc = ScreenCapture.builder().build(); + if (base64 != null) + sc.setBase64(base64); + updatePaths(sc, path, resolvedPath, title); + return sc; + } + if (MediaType.valueOf(type) == MediaType.VIDEO) { + Video vid = Video.builder().build(); + if (base64 != null) + vid.setBase64(base64); + updatePaths(vid, path, resolvedPath, title); + return vid; + } + return null; + } + + private static void updatePaths(Media media, String path, String resolvedPath, String title) { + if (path != null) + media.setPath(path); + if (resolvedPath != null) + media.setResolvedPath(resolvedPath); + if (title != null) + media.setTitle(title); + } } diff --git a/src/main/java/com/aventstack/extentreports/model/service/TestService.java b/src/main/java/com/aventstack/extentreports/model/service/TestService.java index d0fd61f..1b994e4 100644 --- a/src/main/java/com/aventstack/extentreports/model/service/TestService.java +++ b/src/main/java/com/aventstack/extentreports/model/service/TestService.java @@ -5,20 +5,57 @@ import com.aventstack.extentreports.gherkin.model.IGherkinFormatterModel; import com.aventstack.extentreports.model.Log; +import com.aventstack.extentreports.model.Media; +import com.aventstack.extentreports.model.ScreenCapture; import com.aventstack.extentreports.model.Test; +import com.aventstack.extentreports.model.Video; import com.aventstack.extentreports.util.Assert; public class TestService { public static Boolean testHasScreenCapture(Test test, Boolean deep) { if (deep) { - Boolean hasScreenCapture = !test.getMedia().isEmpty() - || test.getLogs().stream().anyMatch(Log::hasMedia); + Boolean hasScreenCapture = (!test.getMedia().isEmpty() && test.hasScreenCapture()) + || test.getLogs().stream().anyMatch(Log::hasScreenCapture); if (!hasScreenCapture) hasScreenCapture = test.getChildren().stream().anyMatch(x -> testHasScreenCapture(x, deep)); return hasScreenCapture; } return test.hasScreenCapture(); } + + public static Boolean testHasVideo(Test test, Boolean deep) { + if (deep) { + Boolean hasVideo = (!test.getMedia().isEmpty() && test.hasVideo()) + || test.getLogs().stream().anyMatch(Log::hasVideo); + if (!hasVideo) + hasVideo = test.getChildren().stream().anyMatch(x -> testHasVideo(x, deep)); + return hasVideo; + } + return test.hasVideo(); + } + + public static Boolean testHasMedia(Test test, Boolean deep) { + return testHasScreenCapture(test, deep) || testHasVideo(test, deep); + } + + + public static boolean isScreenCapture(Media m) { + return m instanceof ScreenCapture; + } + + public static boolean isVideo(Media m) { + return m instanceof Video; + } + + public static String videoSrc(Media m) { + if (MediaService.isVideoBase64(m)) + return MediaService.getVideoBase64(m); + else if (m.getResolvedPath() != null && !m.getResolvedPath().isEmpty()) + return m.getResolvedPath(); + else if (m.getPath() != null && !m.getPath().isEmpty()) + return m.getPath(); + return ""; + } public static Test createTest(Class type, String name, String description) { Assert.notEmpty(name, "Test name must not be null or empty"); diff --git a/src/main/resources/com/aventstack/extentreports/templates/commons/commons-macros.ftl b/src/main/resources/com/aventstack/extentreports/templates/commons/commons-macros.ftl index a7c89de..55f4f22 100644 --- a/src/main/resources/com/aventstack/extentreports/templates/commons/commons-macros.ftl +++ b/src/main/resources/com/aventstack/extentreports/templates/commons/commons-macros.ftl @@ -29,18 +29,25 @@ <#macro mediaSingle m> - <#if m??>
- <#if m.base64??> - <#if config.thumbnailForBase64()?? && config.thumbnailForBase64()> - - <#else> - base64 img - - <#elseif m.resolvedPath??> - <#elseif m.path??> - - <#if m.title??>
${m.title}
+ + <#if TestService.isVideo(m)> +
- diff --git a/src/main/resources/com/aventstack/extentreports/templates/spark/macros/recurse_nodes.ftl b/src/main/resources/com/aventstack/extentreports/templates/spark/macros/recurse_nodes.ftl index a2cbf3e..21d2adb 100644 --- a/src/main/resources/com/aventstack/extentreports/templates/spark/macros/recurse_nodes.ftl +++ b/src/main/resources/com/aventstack/extentreports/templates/spark/macros/recurse_nodes.ftl @@ -9,7 +9,7 @@
  • ${node.status}
  • ${node.timeTaken()?number_to_time?string("mm:ss:SSS")}
  • -
  • <#if TestService.testHasScreenCapture(node, true)>
  • +
  • <#if TestService.testHasMedia(node, true)>
diff --git a/src/main/resources/com/aventstack/extentreports/templates/spark/partials/bdd-content.ftl b/src/main/resources/com/aventstack/extentreports/templates/spark/partials/bdd-content.ftl index 4fdc85a..bdba8b4 100644 --- a/src/main/resources/com/aventstack/extentreports/templates/spark/partials/bdd-content.ftl +++ b/src/main/resources/com/aventstack/extentreports/templates/spark/partials/bdd-content.ftl @@ -16,7 +16,7 @@