diff --git a/core/src/main/java/io/cucumber/core/api/Scenario.java b/core/src/main/java/io/cucumber/core/api/Scenario.java index a20cea881b..01687bc75e 100644 --- a/core/src/main/java/io/cucumber/core/api/Scenario.java +++ b/core/src/main/java/io/cucumber/core/api/Scenario.java @@ -34,9 +34,21 @@ public interface Scenario { * * @param data what to embed, for example an image. * @param mimeType what is the data? + * + * @deprecated use {@link Scenario#embed(byte[], String, String)} instead. */ + @Deprecated void embed(byte[] data, String mimeType); + /** + * Like {@link Scenario#embed(byte[], String)}, but with name for the embedding. + * + * @param data what to embed, for example an image. + * @param mimeType what is the data? + * @param name embedding name + */ + void embed(byte[] data, String mimeType, String name); + /** * Outputs some text into the report. * diff --git a/core/src/main/java/io/cucumber/core/event/EmbedEvent.java b/core/src/main/java/io/cucumber/core/event/EmbedEvent.java index e3ee749e47..0dfd4e1d3d 100644 --- a/core/src/main/java/io/cucumber/core/event/EmbedEvent.java +++ b/core/src/main/java/io/cucumber/core/event/EmbedEvent.java @@ -9,11 +9,20 @@ public final class EmbedEvent extends TestCaseEvent { private final byte[] data; private final String mimeType; + public final String name; public EmbedEvent(Instant timeInstant, TestCase testCase, byte[] data, String mimeType) { super(timeInstant, testCase); this.data = Objects.requireNonNull(data); this.mimeType = Objects.requireNonNull(mimeType); + this.name = null; + } + + public EmbedEvent(Instant timeInstant, TestCase testCase, byte[] data, String mimeType, String name) { + super(timeInstant, testCase); + this.data = data; + this.mimeType = mimeType; + this.name = name; } public byte[] getData() { @@ -23,4 +32,8 @@ public byte[] getData() { public String getMimeType() { return mimeType; } + + public String getName() { + return name; + } } diff --git a/core/src/main/java/io/cucumber/core/plugin/HTMLFormatter.java b/core/src/main/java/io/cucumber/core/plugin/HTMLFormatter.java index 90eef92fe0..8f7c599cc3 100644 --- a/core/src/main/java/io/cucumber/core/plugin/HTMLFormatter.java +++ b/core/src/main/java/io/cucumber/core/plugin/HTMLFormatter.java @@ -212,14 +212,14 @@ private void handleEmbed(EmbedEvent event) { String mimeType = event.getMimeType(); if (mimeType.startsWith("text/")) { // just pass straight to the plugin to output in the html - jsFunctionCall("embedding", mimeType, new String(event.getData())); + jsFunctionCall("embedding", mimeType, new String(event.getData()), event.getName()); } else { // Creating a file instead of using data urls to not clutter the js file String extension = MIME_TYPES_EXTENSIONS.get(mimeType); if (extension != null) { StringBuilder fileName = new StringBuilder("embedded").append(embeddedIndex++).append(".").append(extension); writeBytesToURL(event.getData(), toUrl(fileName.toString())); - jsFunctionCall("embedding", mimeType, fileName); + jsFunctionCall("embedding", mimeType, fileName, event.getName()); } } } diff --git a/core/src/main/java/io/cucumber/core/plugin/JSONFormatter.java b/core/src/main/java/io/cucumber/core/plugin/JSONFormatter.java index 1bcad06a24..da5eb2a4bf 100644 --- a/core/src/main/java/io/cucumber/core/plugin/JSONFormatter.java +++ b/core/src/main/java/io/cucumber/core/plugin/JSONFormatter.java @@ -165,7 +165,7 @@ private void handleWrite(WriteEvent event) { } private void handleEmbed(EmbedEvent event) { - addEmbeddingToHookMap(event.getData(), event.getMimeType()); + addEmbeddingToHookMap(event.getData(), event.getMimeType(), event.getName()); } private void handleTestStepFinished(TestStepFinished event) { @@ -339,18 +339,21 @@ private void addOutputToHookMap(String text) { ((List)currentStepOrHookMap.get("output")).add(text); } - private void addEmbeddingToHookMap(byte[] data, String mimeType) { + private void addEmbeddingToHookMap(byte[] data, String mimeType, String name) { if (!currentStepOrHookMap.containsKey("embeddings")) { currentStepOrHookMap.put("embeddings", new ArrayList>()); } - Map embedMap = createEmbeddingMap(data, mimeType); + Map embedMap = createEmbeddingMap(data, mimeType, name); ((List>)currentStepOrHookMap.get("embeddings")).add(embedMap); } - private Map createEmbeddingMap(byte[] data, String mimeType) { + private Map createEmbeddingMap(byte[] data, String mimeType, String name) { Map embedMap = new HashMap<>(); embedMap.put("mime_type", mimeType); embedMap.put("data", Base64.encodeBytes(data)); + if (name != null) { + embedMap.put("name", name); + } return embedMap; } diff --git a/core/src/main/java/io/cucumber/core/runner/Scenario.java b/core/src/main/java/io/cucumber/core/runner/Scenario.java index 706e332a48..22c7ea82eb 100644 --- a/core/src/main/java/io/cucumber/core/runner/Scenario.java +++ b/core/src/main/java/io/cucumber/core/runner/Scenario.java @@ -48,16 +48,17 @@ public boolean isFailed() { @Override public void embed(byte[] data, String mimeType) { - if (bus != null) { - bus.send(new EmbedEvent(bus.getInstant(), testCase, data, mimeType)); - } + bus.send(new EmbedEvent(bus.getInstant(), testCase, data, mimeType)); + } + + @Override + public void embed(byte[] data, String mimeType, String name) { + bus.send(new EmbedEvent(bus.getInstant(), testCase, data, mimeType, name)); } @Override public void write(String text) { - if (bus != null) { - bus.send(new WriteEvent(bus.getInstant(), testCase, text)); - } + bus.send(new WriteEvent(bus.getInstant(), testCase, text)); } @Override diff --git a/core/src/main/resources/io/cucumber/core/plugin/html/formatter.js b/core/src/main/resources/io/cucumber/core/plugin/html/formatter.js index 5cfeabd5de..17c4c24ed4 100644 --- a/core/src/main/resources/io/cucumber/core/plugin/html/formatter.js +++ b/core/src/main/resources/io/cucumber/core/plugin/html/formatter.js @@ -93,21 +93,27 @@ CucumberHTML.DOMFormatter = function(rootNode) { } }; - this.embedding = function(mimeType, data) { + this.embedding = function(mimeType, data, name) { + var nameHtml; + if (!name) { + nameHtml = ""; + } else { + nameHtml = "

" + name + "

"; + } if (currentStepIndex == 1) { this.dummyStep(); } if (mimeType.match(/^image\//)) { - currentStep.append(''); + currentStep.append(nameHtml + ''); } else if (mimeType.match(/^video\//)) { - currentStep.append(''); + currentStep.append(nameHtml + ''); } else if (mimeType.match(/^text\//)) { - this.write(data); + this.write(nameHtml + data); } }; diff --git a/core/src/test/java/io/cucumber/core/plugin/HTMLFormatterTest.java b/core/src/test/java/io/cucumber/core/plugin/HTMLFormatterTest.java index d3d886a86b..680348374d 100644 --- a/core/src/test/java/io/cucumber/core/plugin/HTMLFormatterTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/HTMLFormatterTest.java @@ -93,11 +93,11 @@ public void writes_valid_report_js() throws Throwable { "formatter.result({\n" + " \"status\": \"passed\"\n" + "});\n", - "formatter.embedding(\"image/png\", \"embedded0.png\");\n", + "formatter.embedding(\"image/png\", \"embedded0.png\", \"Fake image\");\n", "formatter.after({\n" + " \"status\": \"passed\"\n" + "});\n", - "formatter.embedding(\"text/plain\", \"dodgy stack trace here\");\n", + "formatter.embedding(\"text/plain\", \"dodgy stack trace here\", null);\n", "formatter.after({\n" + " \"status\": \"passed\"\n" + "});\n"), @@ -114,8 +114,8 @@ public void includes_uri() throws Throwable { public void included_embedding() throws Throwable { writeReport(); String reportJs = readReportJs(); - assertContains("formatter.embedding(\"image/png\", \"embedded0.png\");", reportJs); - assertContains("formatter.embedding(\"text/plain\", \"dodgy stack trace here\");", reportJs); + assertContains("formatter.embedding(\"image/png\", \"embedded0.png\", \"Fake image\");", reportJs); + assertContains("formatter.embedding(\"text/plain\", \"dodgy stack trace here\", null);", reportJs); } @Test @@ -654,7 +654,7 @@ public void should_handle_text_embeddings_from_before_hooks() { " \"keyword\": \"Scenario\",\n" + " \"name\": \"scenario name\"\n" + "});\n", "" + - "formatter.embedding(\"text/ascii\", \"embedded from hook\");\n", "" + + "formatter.embedding(\"text/ascii\", \"embedded from hook\", null);\n", "" + "formatter.before({\n" + " \"status\": \"passed\"\n" + "});\n", "" + @@ -748,7 +748,7 @@ private void runFeaturesWithFormatter(URL outputDir) { stepsToLocation.put("first step", "path/step_definitions.java:3"); hooks.add(TestHelper.hookEntry("after", result("passed"))); hooks.add(TestHelper.hookEntry("after", result("passed"))); - hookActions.add(createEmbedHookAction("fakedata".getBytes(US_ASCII), "image/png")); + hookActions.add(createEmbedHookAction("fakedata".getBytes(US_ASCII), "image/png", "Fake image")); hookActions.add(createEmbedHookAction("dodgy stack trace here".getBytes(US_ASCII), "text/plain")); stepDuration = ofMillis(1L); diff --git a/core/src/test/java/io/cucumber/core/plugin/JSONFormatterTest.java b/core/src/test/java/io/cucumber/core/plugin/JSONFormatterTest.java index c37915d7b0..fd905b7192 100755 --- a/core/src/test/java/io/cucumber/core/plugin/JSONFormatterTest.java +++ b/core/src/test/java/io/cucumber/core/plugin/JSONFormatterTest.java @@ -857,6 +857,81 @@ public void should_handle_embed_from_a_hook() { assertThat(formatterOutput, sameJSONAs(expected)); } + @Test + public void should_handle_embed_with_name_from_a_hook() { + CucumberFeature feature = TestHelper.feature("path/test.feature", "" + + "Feature: Banana party\n" + + "\n" + + " Scenario: Monkey eats bananas\n" + + " Given there are bananas\n"); + features.add(feature); + stepsToResult.put("there are bananas", result("passed")); + stepsToLocation.put("there are bananas", "StepDefs.there_are_bananas()"); + hooks.add(TestHelper.hookEntry("before", result("passed"))); + hookLocations.add("Hooks.before_hook_1()"); + hookActions.add(createEmbedHookAction(new byte[]{1, 2, 3}, "mime-type;base64", "someEmbedding")); + stepDuration = ofMillis(1L); + + String formatterOutput = runFeaturesWithFormatter(); + + String expected = "" + + "[\n" + + " {\n" + + " \"id\": \"banana-party\",\n" + + " \"uri\": \"file:path/test.feature\",\n" + + " \"keyword\": \"Feature\",\n" + + " \"name\": \"Banana party\",\n" + + " \"line\": 1,\n" + + " \"description\": \"\",\n" + + " \"elements\": [\n" + + " {\n" + + " \"id\": \"banana-party;monkey-eats-bananas\",\n" + + " \"keyword\": \"Scenario\",\n" + + " \"start_timestamp\": \"1970-01-01T00:00:00.000Z\",\n" + + " \"name\": \"Monkey eats bananas\",\n" + + " \"line\": 3,\n" + + " \"description\": \"\",\n" + + " \"type\": \"scenario\",\n" + + " \"before\": [\n" + + " {\n" + + " \"match\": {\n" + + " \"location\": \"Hooks.before_hook_1()\"\n" + + " },\n" + + " \"embeddings\": [\n" + + " {\n" + + " \"mime_type\": \"mime-type;base64\",\n" + + " \"data\": \"AQID\",\n" + + " \"name\": \"someEmbedding\"\n" + + " }\n" + + " ],\n" + + " \"result\": {\n" + + " \"status\": \"passed\",\n" + + " \"duration\": 1000000\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"steps\": [\n" + + " {\n" + + " \"keyword\": \"Given \",\n" + + " \"name\": \"there are bananas\",\n" + + " \"line\": 4,\n" + + " \"match\": {\n" + + " \"location\": \"StepDefs.there_are_bananas()\"\n" + + " },\n" + + " \"result\": {\n" + + " \"status\": \"passed\",\n" + + " \"duration\": 1000000\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"tags\": []\n" + + " }\n" + + "]"; + assertThat(formatterOutput, sameJSONAs(expected)); + } + @Test public void should_format_scenario_with_a_step_with_a_doc_string() { CucumberFeature feature = TestHelper.feature("path/test.feature", "" + diff --git a/core/src/test/java/io/cucumber/core/runner/TestHelper.java b/core/src/test/java/io/cucumber/core/runner/TestHelper.java index 2ee8ce7e85..15e8656f6b 100644 --- a/core/src/test/java/io/cucumber/core/runner/TestHelper.java +++ b/core/src/test/java/io/cucumber/core/runner/TestHelper.java @@ -497,17 +497,24 @@ public Object answer(InvocationOnMock invocation) { } public static Answer createEmbedHookAction(final byte[] data, final String mimeType) { + return createEmbedHookAction(data, mimeType, null); + } + + public static Answer createEmbedHookAction(final byte[] data, final String mimeType, final String name) { return new Answer() { @Override public Object answer(InvocationOnMock invocation) { Scenario scenario = (Scenario) invocation.getArguments()[0]; - scenario.embed(data, mimeType); + if (name != null) { + scenario.embed(data, mimeType, name); + } else { + scenario.embed(data, mimeType); + } return null; } }; } - private static AssertionFailedError mockAssertionFailedError() { AssertionFailedError error = mock(AssertionFailedError.class); Answer printStackTraceHandler = new Answer() {