From 2cfbfecd7eb966f2c1c91fa38da6627862c116f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Sch=C3=A4fer?= Date: Tue, 20 Jan 2015 22:51:29 +0100 Subject: [PATCH 1/4] first works on an attachment feature --- .../java/com/tngtech/jgiven/StepAccess.java | 35 +++++++ .../tngtech/jgiven/attachment/Attachment.java | 10 ++ .../jgiven/attachment/BinaryAttachment.java | 39 ++++++++ .../jgiven/attachment/TextAttachment.java | 23 +++++ .../tngtech/jgiven/impl/ScenarioExecutor.java | 48 ++++++---- .../impl/intercept/NoOpScenarioListener.java | 4 + .../impl/intercept/ScenarioListener.java | 3 + .../jgiven/report/model/AttachmentModel.java | 22 +++++ .../report/model/ReportModelBuilder.java | 8 ++ .../jgiven/report/model/StepModel.java | 16 ++++ .../html5/Html5AttachmentGenerator.java | 91 +++++++++++++++++++ .../report/html5/Html5ReportGenerator.java | 1 + .../jgiven/report/html5/ThenHtml5Report.java | 15 ++- 13 files changed, 295 insertions(+), 20 deletions(-) create mode 100644 jgiven-core/src/main/java/com/tngtech/jgiven/StepAccess.java create mode 100644 jgiven-core/src/main/java/com/tngtech/jgiven/attachment/Attachment.java create mode 100644 jgiven-core/src/main/java/com/tngtech/jgiven/attachment/BinaryAttachment.java create mode 100644 jgiven-core/src/main/java/com/tngtech/jgiven/attachment/TextAttachment.java create mode 100644 jgiven-core/src/main/java/com/tngtech/jgiven/report/model/AttachmentModel.java create mode 100644 jgiven-html5-report/src/main/java/com/tngtech/jgiven/report/html5/Html5AttachmentGenerator.java diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/StepAccess.java b/jgiven-core/src/main/java/com/tngtech/jgiven/StepAccess.java new file mode 100644 index 0000000000..e20f4a12b7 --- /dev/null +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/StepAccess.java @@ -0,0 +1,35 @@ +package com.tngtech.jgiven; + + +import com.tngtech.jgiven.attachment.Attachment; + +/** + * This interface can be injected into a stage by using the {@link com.tngtech.jgiven.annotation.ExpectedScenarioState} + * annotation. It provides programmatic access to the current executed step. + */ +public interface StepAccess { + + /** + * Adds an attachment to the current step + * + * @param attachment an attachment to add + */ + void addAttachment( Attachment attachment ); + + /** + * Set the description of the current step + * + * @param description the description to set + * @see com.tngtech.jgiven.annotation.Description + */ + void setDescription( String description ); + + /** + * Set the extended description of the current step + * + * @param extendedDescription the extended description + * @see com.tngtech.jgiven.annotation.ExtendedDescription + */ + void setExtendedDescription( String extendedDescription ); + +} diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/Attachment.java b/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/Attachment.java new file mode 100644 index 0000000000..317407ae34 --- /dev/null +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/Attachment.java @@ -0,0 +1,10 @@ +package com.tngtech.jgiven.attachment; + +/** + * Represents an attachment of a step + */ +public interface Attachment { + String asString(); + + String getMimeType(); +} diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/BinaryAttachment.java b/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/BinaryAttachment.java new file mode 100644 index 0000000000..422709eb3a --- /dev/null +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/BinaryAttachment.java @@ -0,0 +1,39 @@ +package com.tngtech.jgiven.attachment; + +import javax.xml.bind.DatatypeConverter; + +/** + * Represents an binary attachment + */ +public class BinaryAttachment implements Attachment { + + private final String base64; + private final String mimeType; + + private BinaryAttachment( String base64, String mimeType ) { + this.base64 = base64; + this.mimeType = mimeType; + } + + public static BinaryAttachment fromBase64PngImage( String base64 ) { + return fromBase64( base64, "image/png" ); + } + + public static BinaryAttachment fromBase64( String base64, String mimeType ) { + return new BinaryAttachment( base64, mimeType ); + } + + public static BinaryAttachment fromBytes( byte[] bytes, String mimeType ) { + return new BinaryAttachment( DatatypeConverter.printBase64Binary( bytes ), mimeType ); + } + + @Override + public String asString() { + return base64; + } + + @Override + public String getMimeType() { + return mimeType; + } +} diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/TextAttachment.java b/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/TextAttachment.java new file mode 100644 index 0000000000..f334182f5c --- /dev/null +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/TextAttachment.java @@ -0,0 +1,23 @@ +package com.tngtech.jgiven.attachment; + +/** + * Represents a text attachment + */ +public class TextAttachment implements Attachment { + + private final String content; + + public TextAttachment( String content ) { + this.content = content; + } + + @Override + public String asString() { + return content; + } + + @Override + public String getMimeType() { + return "text/plain"; + } +} diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/ScenarioExecutor.java b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/ScenarioExecutor.java index 6cec2fe836..eea769abc7 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/ScenarioExecutor.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/ScenarioExecutor.java @@ -1,8 +1,10 @@ package com.tngtech.jgiven.impl; -import static com.google.common.collect.Lists.*; -import static com.tngtech.jgiven.impl.ScenarioExecutor.State.*; -import static com.tngtech.jgiven.impl.util.ReflectionUtil.*; +import static com.google.common.collect.Lists.newArrayList; +import static com.google.common.collect.Lists.reverse; +import static com.tngtech.jgiven.impl.ScenarioExecutor.State.FINISHED; +import static com.tngtech.jgiven.impl.ScenarioExecutor.State.STARTED; +import static com.tngtech.jgiven.impl.util.ReflectionUtil.hasAtLeastOneAnnotation; import java.lang.annotation.Annotation; import java.lang.reflect.Field; @@ -18,25 +20,16 @@ import com.google.common.base.Optional; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.tngtech.jgiven.annotation.AfterScenario; -import com.tngtech.jgiven.annotation.AfterStage; -import com.tngtech.jgiven.annotation.BeforeScenario; -import com.tngtech.jgiven.annotation.BeforeStage; -import com.tngtech.jgiven.annotation.Hidden; -import com.tngtech.jgiven.annotation.IntroWord; -import com.tngtech.jgiven.annotation.NotImplementedYet; -import com.tngtech.jgiven.annotation.ScenarioRule; -import com.tngtech.jgiven.annotation.ScenarioStage; +import com.tngtech.jgiven.StepAccess; +import com.tngtech.jgiven.annotation.*; +import com.tngtech.jgiven.attachment.Attachment; import com.tngtech.jgiven.exception.FailIfPassedException; import com.tngtech.jgiven.exception.JGivenUserException; import com.tngtech.jgiven.impl.inject.ValueInjector; -import com.tngtech.jgiven.impl.intercept.InvocationMode; -import com.tngtech.jgiven.impl.intercept.NoOpScenarioListener; -import com.tngtech.jgiven.impl.intercept.ScenarioListener; -import com.tngtech.jgiven.impl.intercept.StepMethodHandler; -import com.tngtech.jgiven.impl.intercept.StepMethodInterceptor; +import com.tngtech.jgiven.impl.intercept.*; import com.tngtech.jgiven.impl.util.ReflectionUtil; -import com.tngtech.jgiven.impl.util.ReflectionUtil.*; +import com.tngtech.jgiven.impl.util.ReflectionUtil.FieldAction; +import com.tngtech.jgiven.impl.util.ReflectionUtil.MethodAction; import com.tngtech.jgiven.impl.util.ScenarioUtil; import com.tngtech.jgiven.integration.CanWire; import com.tngtech.jgiven.report.model.NamedArgument; @@ -85,6 +78,7 @@ public enum State { public ScenarioExecutor() { injector.injectValueByType( ScenarioExecutor.class, this ); + injector.injectValueByType( StepAccess.class, new StepAccessImpl() ); } static class StageState { @@ -97,6 +91,24 @@ static class StageState { } } + class StepAccessImpl implements StepAccess { + + @Override + public void addAttachment( Attachment attachment ) { + listener.attachmentAdded( attachment ); + } + + @Override + public void setDescription( String description ) { + // listener.setDescription(description); + } + + @Override + public void setExtendedDescription( String extendedDescription ) { + // listener.setExtendedDescription(extendedDescription); + } + } + class MethodHandler implements StepMethodHandler { @Override public void handleMethod( Object stageInstance, Method paramMethod, Object[] arguments, InvocationMode mode ) diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/NoOpScenarioListener.java b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/NoOpScenarioListener.java index 37bcd1f3f7..bb626674c1 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/NoOpScenarioListener.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/NoOpScenarioListener.java @@ -3,6 +3,7 @@ import java.lang.reflect.Method; import java.util.List; +import com.tngtech.jgiven.attachment.Attachment; import com.tngtech.jgiven.report.model.NamedArgument; public class NoOpScenarioListener implements ScenarioListener { @@ -30,4 +31,7 @@ public void stepMethodFinished( long durationInNanos ) {} @Override public void scenarioFinished() {} + + @Override + public void attachmentAdded( Attachment attachment ) {} } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/ScenarioListener.java b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/ScenarioListener.java index 748f3f918b..0418f078e0 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/ScenarioListener.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/ScenarioListener.java @@ -3,6 +3,7 @@ import java.lang.reflect.Method; import java.util.List; +import com.tngtech.jgiven.attachment.Attachment; import com.tngtech.jgiven.report.model.NamedArgument; public interface ScenarioListener { @@ -22,4 +23,6 @@ public interface ScenarioListener { void stepMethodFinished( long durationInNanos ); void scenarioFinished(); + + void attachmentAdded( Attachment attachment ); } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/AttachmentModel.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/AttachmentModel.java new file mode 100644 index 0000000000..ac6c1d7cb2 --- /dev/null +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/AttachmentModel.java @@ -0,0 +1,22 @@ +package com.tngtech.jgiven.report.model; + +public class AttachmentModel { + private String value; + private String mimeType; + + public String getValue() { + return value; + } + + public void setValue( String value ) { + this.value = value; + } + + public String getMimeType() { + return mimeType; + } + + public void setMimeType( String mimeType ) { + this.mimeType = mimeType; + } +} diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ReportModelBuilder.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ReportModelBuilder.java index a28293fb9d..08599405cd 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ReportModelBuilder.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ReportModelBuilder.java @@ -15,6 +15,7 @@ import com.google.common.base.Throwables; import com.google.common.collect.Lists; import com.tngtech.jgiven.annotation.*; +import com.tngtech.jgiven.attachment.Attachment; import com.tngtech.jgiven.config.AbstractJGivenConfiguraton; import com.tngtech.jgiven.config.ConfigurationUtil; import com.tngtech.jgiven.config.DefaultConfiguration; @@ -37,6 +38,7 @@ public class ReportModelBuilder implements ScenarioListener { private ScenarioModel currentScenarioModel; private ScenarioCaseModel currentScenarioCase; + private StepModel currentStep; private ReportModel reportModel; private Word introWord; @@ -113,6 +115,7 @@ public void addStepMethod( Method paramMethod, List arguments, In } stepModel.setStatus( mode.toStepStatus() ); + currentStep = stepModel; writeStep( stepModel ); } @@ -405,6 +408,11 @@ public void scenarioFinished() { currentScenarioModel.addDurationInNanos( durationInNanos ); } + @Override + public void attachmentAdded( Attachment attachment ) { + currentStep.setAttachment( attachment ); + } + public void setTestClass( Class testClass ) { setClassName( testClass.getName() ); if( testClass.isAnnotationPresent( Description.class ) ) { diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/StepModel.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/StepModel.java index 2a78e7a123..aef5c66108 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/StepModel.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/StepModel.java @@ -6,6 +6,7 @@ import com.google.common.base.Joiner; import com.google.common.collect.Lists; +import com.tngtech.jgiven.attachment.Attachment; public class StepModel { /** @@ -34,6 +35,11 @@ public class StepModel { */ private String extendedDescription; + /** + * An optional attachment of the step + */ + private AttachmentModel attachment; + public StepModel() {} public StepModel( String name, List words ) { @@ -105,4 +111,14 @@ public Iterable getWords() { public Word getLastWord() { return words.get( words.size() - 1 ); } + + public void setAttachment( Attachment attachment ) { + this.attachment = new AttachmentModel(); + this.attachment.setValue( attachment.asString() ); + this.attachment.setMimeType( attachment.getMimeType() ); + } + + public AttachmentModel getAttachment() { + return attachment; + } } \ No newline at end of file diff --git a/jgiven-html5-report/src/main/java/com/tngtech/jgiven/report/html5/Html5AttachmentGenerator.java b/jgiven-html5-report/src/main/java/com/tngtech/jgiven/report/html5/Html5AttachmentGenerator.java new file mode 100644 index 0000000000..fbb9bdc424 --- /dev/null +++ b/jgiven-html5-report/src/main/java/com/tngtech/jgiven/report/html5/Html5AttachmentGenerator.java @@ -0,0 +1,91 @@ +package com.tngtech.jgiven.report.html5; + +import static javax.xml.bind.DatatypeConverter.parseBase64Binary; + +import java.io.File; +import java.io.IOException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Charsets; +import com.google.common.io.Files; +import com.google.common.net.MediaType; +import com.tngtech.jgiven.exception.JGivenInstallationException; +import com.tngtech.jgiven.report.model.AttachmentModel; +import com.tngtech.jgiven.report.model.ReportModel; +import com.tngtech.jgiven.report.model.ReportModelVisitor; +import com.tngtech.jgiven.report.model.StepModel; + +class Html5AttachmentGenerator extends ReportModelVisitor { + private static final Logger log = LoggerFactory.getLogger( Html5AttachmentGenerator.class ); + private static final String ATTACHMENT_DIRNAME = "attachments"; + + private int fileCounter; + private File attachmentsDir; + + public void generateAttachments( File targetDir, ReportModel model ) { + attachmentsDir = new File( targetDir, ATTACHMENT_DIRNAME ); + if( !attachmentsDir.exists() && !attachmentsDir.mkdirs() ) { + throw new JGivenInstallationException( "Could not create directory " + attachmentsDir ); + } + model.accept( this ); + } + + @Override + public void visit( StepModel stepModel ) { + AttachmentModel attachment = stepModel.getAttachment(); + if( attachment == null ) { + return; + } + + String mimeType = attachment.getMimeType(); + MediaType mediaType = MediaType.parse( mimeType ); + File targetfile = null; + if( mediaType.is( MediaType.ANY_TEXT_TYPE ) ) { + targetfile = writeTextFile( attachment ); + } else if( mediaType.is( MediaType.ANY_IMAGE_TYPE ) ) { + targetfile = writeImageFile( attachment, mediaType ); + } + + if( targetfile != null ) { + attachment.setValue( ATTACHMENT_DIRNAME + "/" + targetfile.getName() ); + } else { + attachment.setValue( null ); + } + log.info( "Attachment written to " + targetfile ); + } + + private File writeImageFile( AttachmentModel attachment, MediaType mediaType ) { + if( !mediaType.is( MediaType.PNG ) ) { + log.error( "Mime type " + mediaType + " is not supported as an image attachment. Only PNG is supported." ); + } + + String extension = "png"; + File targetFile = getTargetFile( extension ); + try { + Files.write( parseBase64Binary( attachment.getValue() ), targetFile ); + } catch( IOException e ) { + log.error( "Error while trying to write attachment to file " + targetFile, e ); + } + return targetFile; + } + + private File getTargetFile( String extension ) { + return new File( attachmentsDir, "attachment" + getNextFileCounter() + "." + extension ); + } + + private File writeTextFile( AttachmentModel attachment ) { + File targetFile = getTargetFile( "txt" ); + try { + Files.write( attachment.getValue(), targetFile, Charsets.UTF_8 ); + } catch( IOException e ) { + log.error( "Error while trying to write attachment to file " + targetFile, e ); + } + return targetFile; + } + + private int getNextFileCounter() { + return fileCounter++; + } +} diff --git a/jgiven-html5-report/src/main/java/com/tngtech/jgiven/report/html5/Html5ReportGenerator.java b/jgiven-html5-report/src/main/java/com/tngtech/jgiven/report/html5/Html5ReportGenerator.java index 56923407df..7bc7d95a07 100644 --- a/jgiven-html5-report/src/main/java/com/tngtech/jgiven/report/html5/Html5ReportGenerator.java +++ b/jgiven-html5-report/src/main/java/com/tngtech/jgiven/report/html5/Html5ReportGenerator.java @@ -32,6 +32,7 @@ public class Html5ReportGenerator implements ReportModelFileHandler, FileGenerat @Override public void handleReportModel( ReportModel model, File file ) { model.calculateExecutionStatus(); + new Html5AttachmentGenerator().generateAttachments( targetDirectory, model ); createWriter(); diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/ThenHtml5Report.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/ThenHtml5Report.java index 007afbdac9..5d016282b0 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/ThenHtml5Report.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/ThenHtml5Report.java @@ -1,15 +1,24 @@ package com.tngtech.jgiven.report.html5; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; import org.openqa.selenium.By; +import org.openqa.selenium.OutputType; +import org.openqa.selenium.TakesScreenshot; import org.openqa.selenium.WebDriver; import com.tngtech.jgiven.Stage; +import com.tngtech.jgiven.StepAccess; import com.tngtech.jgiven.annotation.ExpectedScenarioState; +import com.tngtech.jgiven.attachment.BinaryAttachment; public class ThenHtml5Report> extends Stage { + @ExpectedScenarioState + StepAccess stepAccess; + @ExpectedScenarioState protected WebDriver webDriver; @@ -18,8 +27,10 @@ public SELF the_page_title_is( String pageTitle ) { return self(); } - public SELF the_page_statistics_line_contains_text( String text ) { + public SELF the_page_statistics_line_contains_text( String text ) throws IOException { assertThat( webDriver.findElement( By.className( "page-statistics" ) ).getText() ).contains( text ); + String base64 = ( (TakesScreenshot) webDriver ).getScreenshotAs( OutputType.BASE64 ); + stepAccess.addAttachment( BinaryAttachment.fromBase64PngImage( base64 ) ); return self(); } } From 3e657b2dcf810a35e417be336150e0aa6bddf4fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Sch=C3=A4fer?= Date: Wed, 21 Jan 2015 23:28:41 +0100 Subject: [PATCH 2/4] include attachments in html5 report --- jgiven-html5-report/src/app/index.html | 3 +++ .../report/html5/Html5AttachmentGenerator.java | 12 ++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/jgiven-html5-report/src/app/index.html b/jgiven-html5-report/src/app/index.html index 08ec2d83ea..231a4c2ae6 100644 --- a/jgiven-html5-report/src/app/index.html +++ b/jgiven-html5-report/src/app/index.html @@ -258,6 +258,9 @@
+ + diff --git a/jgiven-html5-report/src/main/java/com/tngtech/jgiven/report/html5/Html5AttachmentGenerator.java b/jgiven-html5-report/src/main/java/com/tngtech/jgiven/report/html5/Html5AttachmentGenerator.java index fbb9bdc424..2bb0d8ce8b 100644 --- a/jgiven-html5-report/src/main/java/com/tngtech/jgiven/report/html5/Html5AttachmentGenerator.java +++ b/jgiven-html5-report/src/main/java/com/tngtech/jgiven/report/html5/Html5AttachmentGenerator.java @@ -41,19 +41,19 @@ public void visit( StepModel stepModel ) { String mimeType = attachment.getMimeType(); MediaType mediaType = MediaType.parse( mimeType ); - File targetfile = null; + File targetFile = null; if( mediaType.is( MediaType.ANY_TEXT_TYPE ) ) { - targetfile = writeTextFile( attachment ); + targetFile = writeTextFile( attachment ); } else if( mediaType.is( MediaType.ANY_IMAGE_TYPE ) ) { - targetfile = writeImageFile( attachment, mediaType ); + targetFile = writeImageFile( attachment, mediaType ); } - if( targetfile != null ) { - attachment.setValue( ATTACHMENT_DIRNAME + "/" + targetfile.getName() ); + if( targetFile != null ) { + attachment.setValue( ATTACHMENT_DIRNAME + "/" + targetFile.getName() ); } else { attachment.setValue( null ); } - log.info( "Attachment written to " + targetfile ); + log.info( "Attachment written to " + targetFile ); } private File writeImageFile( AttachmentModel attachment, MediaType mediaType ) { From 05b531e71bf10e9039814713d682b783340b64ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Sch=C3=A4fer?= Date: Sat, 24 Jan 2015 18:23:24 +0100 Subject: [PATCH 3/4] refactored attachment support --- .../{StepAccess.java => CurrentStep.java} | 13 +- .../tngtech/jgiven/attachment/Attachment.java | 180 +++++++++++++++++- .../jgiven/attachment/BinaryAttachment.java | 39 ---- .../tngtech/jgiven/attachment/MediaType.java | 118 ++++++++++++ .../jgiven/attachment/TextAttachment.java | 23 --- .../tngtech/jgiven/impl/ScenarioExecutor.java | 13 +- .../impl/intercept/NoOpScenarioListener.java | 3 + .../impl/intercept/ScenarioListener.java | 2 + .../jgiven/report/model/AttachmentModel.java | 28 ++- .../report/model/ReportModelBuilder.java | 5 + .../jgiven/report/model/StepModel.java | 6 +- jgiven-html5-report/src/app/index.html | 7 +- .../html5/Html5AttachmentGenerator.java | 2 +- .../jgiven/tags/FeatureAttachments.java | 13 ++ .../tngtech/jgiven/tags/FeatureCaseDiffs.java | 2 +- .../jgiven/tags/FeatureDataTables.java | 2 +- .../jgiven/tags/FeatureDerivedParameters.java | 2 +- .../report/html5/Html5GeneratorTest.java | 20 ++ .../jgiven/report/html5/Html5ReportStage.java | 30 +++ .../jgiven/report/html5/ThenHtml5Report.java | 43 +++-- .../jgiven/report/html5/WhenHtml5Report.java | 52 ++++- .../jgiven/report/model/GivenReportModel.java | 12 +- .../report/model/GivenReportModels.java | 5 + 23 files changed, 494 insertions(+), 126 deletions(-) rename jgiven-core/src/main/java/com/tngtech/jgiven/{StepAccess.java => CurrentStep.java} (67%) delete mode 100644 jgiven-core/src/main/java/com/tngtech/jgiven/attachment/BinaryAttachment.java create mode 100644 jgiven-core/src/main/java/com/tngtech/jgiven/attachment/MediaType.java delete mode 100644 jgiven-core/src/main/java/com/tngtech/jgiven/attachment/TextAttachment.java create mode 100644 jgiven-tests/src/main/java/com/tngtech/jgiven/tags/FeatureAttachments.java create mode 100644 jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/Html5ReportStage.java diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/StepAccess.java b/jgiven-core/src/main/java/com/tngtech/jgiven/CurrentStep.java similarity index 67% rename from jgiven-core/src/main/java/com/tngtech/jgiven/StepAccess.java rename to jgiven-core/src/main/java/com/tngtech/jgiven/CurrentStep.java index e20f4a12b7..b3bf09d4a2 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/StepAccess.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/CurrentStep.java @@ -1,13 +1,12 @@ package com.tngtech.jgiven; - import com.tngtech.jgiven.attachment.Attachment; /** - * This interface can be injected into a stage by using the {@link com.tngtech.jgiven.annotation.ExpectedScenarioState} + * This interface can be injected into a stage by using the {@link com.tngtech.jgiven.annotation.ScenarioState} * annotation. It provides programmatic access to the current executed step. */ -public interface StepAccess { +public interface CurrentStep { /** * Adds an attachment to the current step @@ -16,14 +15,6 @@ public interface StepAccess { */ void addAttachment( Attachment attachment ); - /** - * Set the description of the current step - * - * @param description the description to set - * @see com.tngtech.jgiven.annotation.Description - */ - void setDescription( String description ); - /** * Set the extended description of the current step * diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/Attachment.java b/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/Attachment.java index 317407ae34..39c1dd656a 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/Attachment.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/Attachment.java @@ -1,10 +1,182 @@ package com.tngtech.jgiven.attachment; +import java.io.*; +import java.nio.charset.Charset; + +import javax.xml.bind.DatatypeConverter; + +import com.google.common.io.ByteStreams; +import com.google.common.io.CharStreams; +import com.google.common.io.Files; +import com.tngtech.jgiven.impl.util.ResourceUtil; + /** - * Represents an attachment of a step + * Represents an attachment of a step. + * Attachments must be representable as a String so that it can be stored as JSON. + * For binary attachments this means that they have to be encoded with Base64. + * In addition, attachments must have a media type so that reporters know + * how to present an attachment. + * */ -public interface Attachment { - String asString(); +public class Attachment { + + /** + * An optional title. + * Can be {@code null}. + */ + private String title; + + /** + * The content of the attachment. + * In case the media type is binary, this is a Base64 encoded string + */ + private final String content; + + private final MediaType mediaType; + + /** + * Convenience constructor, where title is set to {@code null} + */ + protected Attachment( String content, MediaType mediaType ) { + this( content, mediaType, null ); + } + + /** + * Creates a new instance of this Attachment + * @param content the content of this attachment. In case of a binary attachment, this must be + * Base64 encoded. Must not be {@code null} + * @param mediaType the mediaType. Must not be {@code null} + * @param title an optional title, may be {@code null} + */ + protected Attachment( String content, MediaType mediaType, String title ) { + if( mediaType == null ) { + throw new IllegalArgumentException( "MediaType must not be null" ); + } + + if( content == null ) { + throw new IllegalArgumentException( "Content must not be null" ); + } + + this.content = content; + this.mediaType = mediaType; + this.title = title; + } + + /** + * An optional title of the attachment. + * The title can be used by reporters, e.g. as a tooltip. + * Can be {@code null}. + */ + public String title() { + return title; + } + + /** + * The content of the attachment represented as a string. + * Binary attachments must be encoded in Base64 format. + */ + public String content() { + return content; + } + + /** + * The type of the attachment. + * It depends on the reporter how this information is used. + */ + public MediaType getMediaType() { + return mediaType; + } + + /** + * An optional title + */ + public String getTitle() { + return title; + } + + /** + * Sets the title and returns {@code this} + */ + public Attachment withTitle( String title ) { + this.title = title; + return this; + } + + /** + * Creates an attachment from a given array of bytes. + * The bytes will be Base64 encoded. + * @throws java.lang.IllegalArgumentException if mediaType is not binary + */ + public static Attachment fromBinaryBytes( byte[] bytes, MediaType mediaType ) { + if( !mediaType.isBinary() ) { + throw new IllegalArgumentException( "MediaType must be binary" ); + } + return new Attachment( DatatypeConverter.printBase64Binary( bytes ), mediaType, null ); + } + + /** + * Creates an attachment from a binary input stream. + * The content of the stream will be transformed into a Base64 encoded string + * @throws IOException if an I/O error occurs + * @throws java.lang.IllegalArgumentException if mediaType is not binary + */ + public static Attachment fromBinaryInputStream( InputStream inputStream, MediaType mediaType ) throws IOException { + return fromBinaryBytes( ByteStreams.toByteArray( inputStream ), mediaType ); + } + + /** + * Creates an attachment from the given binary file {@code file}. + * The content of the file will be transformed into a Base64 encoded string. + * @throws IOException if an I/O error occurs + * @throws java.lang.IllegalArgumentException if mediaType is not binary + */ + public static Attachment fromBinaryFile( File file, MediaType mediaType ) throws IOException { + FileInputStream stream = new FileInputStream( file ); + try { + return fromBinaryInputStream( stream, mediaType ); + } finally { + ResourceUtil.close( stream ); + } + } + + /** + * Creates a non-binary attachment from the given file. + * @throws IOException if an I/O error occurs + * @throws java.lang.IllegalArgumentException if mediaType is binary + */ + public static Attachment fromTextFile( File file, MediaType mediaType, Charset charSet ) throws IOException { + return fromText( Files.toString( file, charSet ), mediaType ); + } + + /** + * Creates a non-binary attachment from the given file. + * @throws IOException if an I/O error occurs + * @throws java.lang.IllegalArgumentException if mediaType is binary + */ + public static Attachment fromTextInputStream( InputStream inputStream, MediaType mediaType, Charset charset ) throws IOException { + return fromText( CharStreams.toString( new InputStreamReader( inputStream, charset ) ), mediaType ); + } + + /** + * Equivalent to {@link com.tngtech.jgiven.attachment.Attachment#Attachment(String, MediaType)} + * @throws java.lang.IllegalArgumentException if mediaType is binary + */ + public static Attachment fromText( String content, MediaType mediaType ) { + if( mediaType.isBinary() ) { + throw new IllegalArgumentException( "MediaType must not be binary" ); + } + return new Attachment( content, mediaType ); + } + + /** + * Equivalent to {@link com.tngtech.jgiven.attachment.Attachment#Attachment(String, MediaType)} + * @throws java.lang.IllegalArgumentException if mediaType is not binary + */ + public static Attachment fromBase64( String base64encodedContent, MediaType mediaType ) { + if( !mediaType.isBinary() ) { + throw new IllegalArgumentException( "MediaType must be binary" ); + } + return new Attachment( base64encodedContent, mediaType ); + } - String getMimeType(); } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/BinaryAttachment.java b/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/BinaryAttachment.java deleted file mode 100644 index 422709eb3a..0000000000 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/BinaryAttachment.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.tngtech.jgiven.attachment; - -import javax.xml.bind.DatatypeConverter; - -/** - * Represents an binary attachment - */ -public class BinaryAttachment implements Attachment { - - private final String base64; - private final String mimeType; - - private BinaryAttachment( String base64, String mimeType ) { - this.base64 = base64; - this.mimeType = mimeType; - } - - public static BinaryAttachment fromBase64PngImage( String base64 ) { - return fromBase64( base64, "image/png" ); - } - - public static BinaryAttachment fromBase64( String base64, String mimeType ) { - return new BinaryAttachment( base64, mimeType ); - } - - public static BinaryAttachment fromBytes( byte[] bytes, String mimeType ) { - return new BinaryAttachment( DatatypeConverter.printBase64Binary( bytes ), mimeType ); - } - - @Override - public String asString() { - return base64; - } - - @Override - public String getMimeType() { - return mimeType; - } -} diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/MediaType.java b/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/MediaType.java new file mode 100644 index 0000000000..b8436d397d --- /dev/null +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/MediaType.java @@ -0,0 +1,118 @@ +package com.tngtech.jgiven.attachment; + +import static com.tngtech.jgiven.attachment.MediaType.Type.*; + +/** + * Represents a Media Type. + */ +public class MediaType { + + /** + * Represents the type of a Media Type + */ + public static enum Type { + APPLICATION( "application" ), + AUDIO( "audio" ), + IMAGE( "image" ), + TEXT( "text" ), + VIDEO( "video" ); + + private final String value; + + Type( String value ) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + + /** + * Get the type from a given string + */ + public Type fromString( String string ) { + for( Type type : values() ) { + if( type.value.equalsIgnoreCase( string ) ) { + return type; + } + } + throw new IllegalArgumentException( "Unknown type " + string ); + } + + } + + public static final MediaType PNG = image( "png" ); + public static final MediaType PLAIN_TEXT = text( "plain" ); + + private final Type type; + private final String subType; + private final boolean binary; + + /** + * Creates a new MediaType + * @param type the type + * @param subType the subtype + * @param binary whether or not content of this media type is binary. If {@code true}, the + * content will be encoded as Base64 when stored in the JSON model. + */ + public MediaType( Type type, String subType, boolean binary ) { + this.type = type; + this.subType = subType; + this.binary = binary; + } + + /** + * The type of the Media Type. + */ + public Type getType() { + return type; + } + + /** + * The subtype of the Media Type. + */ + public String getSubType() { + return subType; + } + + /** + * Whether this media type is binary or not. + */ + public boolean isBinary() { + return binary; + } + + public String asString() { + return type.value + "/" + subType; + } + + /** + * Creates a binary image media type with the given subtype. + */ + public static MediaType image( String subType ) { + return new MediaType( IMAGE, subType, true ); + } + + /** + * Creates a binary video media type with the given subtype. + */ + public static MediaType video( String subType ) { + return new MediaType( VIDEO, subType, true ); + } + + /** + * Creates a binary audio media type with the given subtype. + */ + public static MediaType audio( String subType ) { + return new MediaType( AUDIO, subType, true ); + } + + /** + * Creates a non-binary text media type with the given subtype. + */ + public static MediaType text( String subType ) { + return new MediaType( TEXT, subType, false ); + } + +} diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/TextAttachment.java b/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/TextAttachment.java deleted file mode 100644 index f334182f5c..0000000000 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/attachment/TextAttachment.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.tngtech.jgiven.attachment; - -/** - * Represents a text attachment - */ -public class TextAttachment implements Attachment { - - private final String content; - - public TextAttachment( String content ) { - this.content = content; - } - - @Override - public String asString() { - return content; - } - - @Override - public String getMimeType() { - return "text/plain"; - } -} diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/ScenarioExecutor.java b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/ScenarioExecutor.java index eea769abc7..356daf59eb 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/ScenarioExecutor.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/ScenarioExecutor.java @@ -20,7 +20,7 @@ import com.google.common.base.Optional; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.tngtech.jgiven.StepAccess; +import com.tngtech.jgiven.CurrentStep; import com.tngtech.jgiven.annotation.*; import com.tngtech.jgiven.attachment.Attachment; import com.tngtech.jgiven.exception.FailIfPassedException; @@ -78,7 +78,7 @@ public enum State { public ScenarioExecutor() { injector.injectValueByType( ScenarioExecutor.class, this ); - injector.injectValueByType( StepAccess.class, new StepAccessImpl() ); + injector.injectValueByType( CurrentStep.class, new StepAccessImpl() ); } static class StageState { @@ -91,21 +91,16 @@ static class StageState { } } - class StepAccessImpl implements StepAccess { + class StepAccessImpl implements CurrentStep { @Override public void addAttachment( Attachment attachment ) { listener.attachmentAdded( attachment ); } - @Override - public void setDescription( String description ) { - // listener.setDescription(description); - } - @Override public void setExtendedDescription( String extendedDescription ) { - // listener.setExtendedDescription(extendedDescription); + listener.extendedDescriptionUpdated( extendedDescription ); } } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/NoOpScenarioListener.java b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/NoOpScenarioListener.java index bb626674c1..b29434680f 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/NoOpScenarioListener.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/NoOpScenarioListener.java @@ -34,4 +34,7 @@ public void scenarioFinished() {} @Override public void attachmentAdded( Attachment attachment ) {} + + @Override + public void extendedDescriptionUpdated( String extendedDescription ) {} } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/ScenarioListener.java b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/ScenarioListener.java index 0418f078e0..b86bd8328e 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/ScenarioListener.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/intercept/ScenarioListener.java @@ -25,4 +25,6 @@ public interface ScenarioListener { void scenarioFinished(); void attachmentAdded( Attachment attachment ); + + void extendedDescriptionUpdated( String extendedDescription ); } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/AttachmentModel.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/AttachmentModel.java index ac6c1d7cb2..6392f81b64 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/AttachmentModel.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/AttachmentModel.java @@ -1,8 +1,10 @@ package com.tngtech.jgiven.report.model; public class AttachmentModel { + private String title; private String value; - private String mimeType; + private String mediaType; + private boolean binary; public String getValue() { return value; @@ -12,11 +14,27 @@ public void setValue( String value ) { this.value = value; } - public String getMimeType() { - return mimeType; + public String getMediaType() { + return mediaType; } - public void setMimeType( String mimeType ) { - this.mimeType = mimeType; + public void setMediaType( String mimeType ) { + this.mediaType = mimeType; + } + + public void setTitle( String title ) { + this.title = title; + } + + public String getTitle() { + return title; + } + + public void setIsBinary( boolean isBinary ) { + this.binary = isBinary; + } + + public boolean isBinary() { + return binary; } } diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ReportModelBuilder.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ReportModelBuilder.java index 08599405cd..21e08431ce 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ReportModelBuilder.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/ReportModelBuilder.java @@ -413,6 +413,11 @@ public void attachmentAdded( Attachment attachment ) { currentStep.setAttachment( attachment ); } + @Override + public void extendedDescriptionUpdated( String extendedDescription ) { + currentStep.setExtendedDescription( extendedDescription ); + } + public void setTestClass( Class testClass ) { setClassName( testClass.getName() ); if( testClass.isAnnotationPresent( Description.class ) ) { diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/StepModel.java b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/StepModel.java index aef5c66108..c184f77f6d 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/StepModel.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/report/model/StepModel.java @@ -114,8 +114,10 @@ public Word getLastWord() { public void setAttachment( Attachment attachment ) { this.attachment = new AttachmentModel(); - this.attachment.setValue( attachment.asString() ); - this.attachment.setMimeType( attachment.getMimeType() ); + this.attachment.setTitle(attachment.getTitle()); + this.attachment.setValue( attachment.content() ); + this.attachment.setMediaType(attachment.getMediaType().asString()); + this.attachment.setIsBinary( attachment.getMediaType().isBinary()); } public AttachmentModel getAttachment() { diff --git a/jgiven-html5-report/src/app/index.html b/jgiven-html5-report/src/app/index.html index 231a4c2ae6..d35142bab5 100644 --- a/jgiven-html5-report/src/app/index.html +++ b/jgiven-html5-report/src/app/index.html @@ -259,7 +259,12 @@
- + + + diff --git a/jgiven-html5-report/src/main/java/com/tngtech/jgiven/report/html5/Html5AttachmentGenerator.java b/jgiven-html5-report/src/main/java/com/tngtech/jgiven/report/html5/Html5AttachmentGenerator.java index 2bb0d8ce8b..800bff0add 100644 --- a/jgiven-html5-report/src/main/java/com/tngtech/jgiven/report/html5/Html5AttachmentGenerator.java +++ b/jgiven-html5-report/src/main/java/com/tngtech/jgiven/report/html5/Html5AttachmentGenerator.java @@ -39,7 +39,7 @@ public void visit( StepModel stepModel ) { return; } - String mimeType = attachment.getMimeType(); + String mimeType = attachment.getMediaType(); MediaType mediaType = MediaType.parse( mimeType ); File targetFile = null; if( mediaType.is( MediaType.ANY_TEXT_TYPE ) ) { diff --git a/jgiven-tests/src/main/java/com/tngtech/jgiven/tags/FeatureAttachments.java b/jgiven-tests/src/main/java/com/tngtech/jgiven/tags/FeatureAttachments.java new file mode 100644 index 0000000000..c39aaec6e7 --- /dev/null +++ b/jgiven-tests/src/main/java/com/tngtech/jgiven/tags/FeatureAttachments.java @@ -0,0 +1,13 @@ +package com.tngtech.jgiven.tags; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import com.tngtech.jgiven.annotation.IsTag; + +@IsTag( type = "Feature", value = "Attachments", + description = "In order to get additional information about a step, like screenshots, for example
" + + "As a JGiven user,
" + + "I want that steps can have attachments" ) +@Retention( RetentionPolicy.RUNTIME ) +public @interface FeatureAttachments {} diff --git a/jgiven-tests/src/main/java/com/tngtech/jgiven/tags/FeatureCaseDiffs.java b/jgiven-tests/src/main/java/com/tngtech/jgiven/tags/FeatureCaseDiffs.java index a2cd67b82a..20f7857b0d 100644 --- a/jgiven-tests/src/main/java/com/tngtech/jgiven/tags/FeatureCaseDiffs.java +++ b/jgiven-tests/src/main/java/com/tngtech/jgiven/tags/FeatureCaseDiffs.java @@ -7,7 +7,7 @@ @IsTag( type = "Feature", value = "Case Diffs", description = "In order to get a better overview over structurally different cases of a scenario
" - + "As a human,
" + + "As a human,
" + "I want the differences highlighted in the generated report" ) @Retention( RetentionPolicy.RUNTIME ) public @interface FeatureCaseDiffs { diff --git a/jgiven-tests/src/main/java/com/tngtech/jgiven/tags/FeatureDataTables.java b/jgiven-tests/src/main/java/com/tngtech/jgiven/tags/FeatureDataTables.java index f7e2dd2d73..a8c61f7902 100644 --- a/jgiven-tests/src/main/java/com/tngtech/jgiven/tags/FeatureDataTables.java +++ b/jgiven-tests/src/main/java/com/tngtech/jgiven/tags/FeatureDataTables.java @@ -7,7 +7,7 @@ @IsTag( type = "Feature", value = "Data Tables", description = "In order to get a better overview over the different cases of a scenario
" - + "As a human,
" + + "As a human,
" + "I want to have different cases represented as a data table" ) @Retention( RetentionPolicy.RUNTIME ) public @interface FeatureDataTables { diff --git a/jgiven-tests/src/main/java/com/tngtech/jgiven/tags/FeatureDerivedParameters.java b/jgiven-tests/src/main/java/com/tngtech/jgiven/tags/FeatureDerivedParameters.java index 2c5749e014..b711e81d3e 100644 --- a/jgiven-tests/src/main/java/com/tngtech/jgiven/tags/FeatureDerivedParameters.java +++ b/jgiven-tests/src/main/java/com/tngtech/jgiven/tags/FeatureDerivedParameters.java @@ -7,7 +7,7 @@ @IsTag( type = "Feature", value = "Derived Parameters", description = "In order to not have to specify easily derivable parameters explicitly
" - + "As a developer,
" + + "As a developer,
" + "I want that step arguments derived from parameters appear in a data table" ) @Retention( RetentionPolicy.RUNTIME ) public @interface FeatureDerivedParameters { diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/Html5GeneratorTest.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/Html5GeneratorTest.java index b18078b070..13c5f48d73 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/Html5GeneratorTest.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/Html5GeneratorTest.java @@ -10,6 +10,7 @@ import com.tngtech.jgiven.report.WhenReportGenerator; import com.tngtech.jgiven.report.json.GivenJsonReports; import com.tngtech.jgiven.report.model.StepStatus; +import com.tngtech.jgiven.tags.FeatureAttachments; import com.tngtech.jgiven.tags.FeatureHtml5Report; import com.tngtech.jgiven.tags.Issue; @@ -67,4 +68,23 @@ public void clicking_on_tag_labels_opens_the_tag_page( boolean prependType, Stri then().the_page_title_is( tagName ); } + + @Test + @FeatureAttachments + public void attachments_appear_in_the_HTML5_report() throws Exception { + String content = "Some Example Attachment\nwith some example content"; + given().a_report_model() + .and().step_$_of_scenario_$_has_a_text_attachment_with_content( 1, 1, content ) + .and().the_report_exist_as_JSON_file(); + + whenReport + .and().the_HTML5_report_has_been_generated(); + + when().the_page_of_scenario_$_is_opened( 1 ) + .and().scenario_$_is_expanded( 1 ); + + then().an_attachment_icon_exists() + .and().the_content_of_the_referenced_attachment_is( content ); + + } } diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/Html5ReportStage.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/Html5ReportStage.java new file mode 100644 index 0000000000..96bea2912f --- /dev/null +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/Html5ReportStage.java @@ -0,0 +1,30 @@ +package com.tngtech.jgiven.report.html5; + +import java.io.File; + +import org.openqa.selenium.OutputType; +import org.openqa.selenium.TakesScreenshot; +import org.openqa.selenium.WebDriver; + +import com.tngtech.jgiven.CurrentStep; +import com.tngtech.jgiven.Stage; +import com.tngtech.jgiven.annotation.ExpectedScenarioState; +import com.tngtech.jgiven.attachment.Attachment; +import com.tngtech.jgiven.attachment.MediaType; + +public class Html5ReportStage> extends Stage { + @ExpectedScenarioState + protected CurrentStep currentStep; + + @ExpectedScenarioState + protected WebDriver webDriver; + + @ExpectedScenarioState + protected File targetReportDir; + + protected void takeScreenshot() { + String base64 = ( (TakesScreenshot) webDriver ).getScreenshotAs( OutputType.BASE64 ); + currentStep.addAttachment( Attachment.fromBase64( base64, MediaType.PNG ).withTitle( "Screenshot" ) ); + } + +} diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/ThenHtml5Report.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/ThenHtml5Report.java index 5d016282b0..57224155b9 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/ThenHtml5Report.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/ThenHtml5Report.java @@ -2,25 +2,18 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.io.File; import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Arrays; +import java.util.List; import org.openqa.selenium.By; -import org.openqa.selenium.OutputType; -import org.openqa.selenium.TakesScreenshot; -import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.testng.reporters.Files; -import com.tngtech.jgiven.Stage; -import com.tngtech.jgiven.StepAccess; -import com.tngtech.jgiven.annotation.ExpectedScenarioState; -import com.tngtech.jgiven.attachment.BinaryAttachment; - -public class ThenHtml5Report> extends Stage { - - @ExpectedScenarioState - StepAccess stepAccess; - - @ExpectedScenarioState - protected WebDriver webDriver; +public class ThenHtml5Report> extends Html5ReportStage { public SELF the_page_title_is( String pageTitle ) { assertThat( webDriver.findElement( By.id( "page-title" ) ).getText() ).isEqualTo( pageTitle ); @@ -29,8 +22,24 @@ public SELF the_page_title_is( String pageTitle ) { public SELF the_page_statistics_line_contains_text( String text ) throws IOException { assertThat( webDriver.findElement( By.className( "page-statistics" ) ).getText() ).contains( text ); - String base64 = ( (TakesScreenshot) webDriver ).getScreenshotAs( OutputType.BASE64 ); - stepAccess.addAttachment( BinaryAttachment.fromBase64PngImage( base64 ) ); + return self(); + } + + public SELF an_attachment_icon_exists() { + assertThat( findAttachmentIcon() ).isNotEmpty(); + return self(); + } + + private List findAttachmentIcon() { + return webDriver.findElements( By.className( "fa-paperclip" ) ); + } + + public SELF the_content_of_the_referenced_attachment_is( String content ) throws IOException, URISyntaxException { + String href = findAttachmentIcon().get( 0 ).findElement( By.xpath( ".." ) ).getAttribute( "href" ); + System.out.println( "==========" + href ); + System.out.println( Arrays.toString( targetReportDir.listFiles() ) ); + String foundContent = Files.readFile( new File( new URL( href ).toURI() ) ).trim(); + assertThat( content ).isEqualTo( foundContent ); return self(); } } diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/WhenHtml5Report.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/WhenHtml5Report.java index 2b88f89fe8..864ac04b07 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/WhenHtml5Report.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/WhenHtml5Report.java @@ -5,27 +5,34 @@ import java.util.List; import org.openqa.selenium.By; -import org.openqa.selenium.WebDriver; +import org.openqa.selenium.Dimension; import org.openqa.selenium.WebElement; import org.openqa.selenium.phantomjs.PhantomJSDriver; -import com.tngtech.jgiven.Stage; import com.tngtech.jgiven.annotation.AfterScenario; -import com.tngtech.jgiven.annotation.ProvidedScenarioState; +import com.tngtech.jgiven.annotation.AfterStage; +import com.tngtech.jgiven.annotation.BeforeScenario; +import com.tngtech.jgiven.annotation.ExpectedScenarioState; +import com.tngtech.jgiven.impl.util.WordUtil; +import com.tngtech.jgiven.report.model.ReportModel; +import com.tngtech.jgiven.report.model.ScenarioModel; -public class WhenHtml5Report> extends Stage { +public class WhenHtml5Report> extends Html5ReportStage { - @ProvidedScenarioState - protected WebDriver webDriver = new PhantomJSDriver(); - - @ProvidedScenarioState - protected File targetReportDir; + @ExpectedScenarioState + protected List reportModels; public SELF the_index_page_is_opened() throws MalformedURLException { url_$_is_opened( "" ); return self(); } + @BeforeScenario + protected void setupWebDriver() { + webDriver = new PhantomJSDriver(); + webDriver.manage().window().setSize( new Dimension( 1280, 768 ) ); + } + @AfterScenario protected void closeWebDriver() { webDriver.close(); @@ -55,4 +62,31 @@ public SELF the_All_Scenarios_page_is_opened() throws MalformedURLException { } return self(); } + + public SELF scenario_$_is_expanded( int scenarioNr ) { + ScenarioModel scenarioModel = getScenarioModel( scenarioNr ); + webDriver.findElement( By.xpath( "//h4[contains(text(),'" + + WordUtil.capitalize( scenarioModel.getDescription() ) + "')]" ) ) + .click(); + return self(); + } + + private ScenarioModel getScenarioModel( int scenarioNr ) { + return reportModels.get( 0 ).getScenarios().get( scenarioNr - 1 ); + } + + public SELF the_page_of_scenario_$_is_opened( int scenarioNr ) throws MalformedURLException { + + ScenarioModel scenarioModel = getScenarioModel( scenarioNr ); + url_$_is_opened( "#/scenario/" + + scenarioModel.getClassName() + + "/" + scenarioModel.getTestMethodName() ); + return self(); + } + + @AfterStage + public void takeScreenshotAfterStage() { + takeScreenshot(); + + } } diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/report/model/GivenReportModel.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/report/model/GivenReportModel.java index 81fc852035..402b1375e2 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/report/model/GivenReportModel.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/report/model/GivenReportModel.java @@ -11,6 +11,8 @@ import com.tngtech.jgiven.annotation.ExtendedDescription; import com.tngtech.jgiven.annotation.ProvidedScenarioState; import com.tngtech.jgiven.annotation.Table; +import com.tngtech.jgiven.attachment.Attachment; +import com.tngtech.jgiven.attachment.MediaType; import com.tngtech.jgiven.report.analysis.CaseArgumentAnalyser; public class GivenReportModel> extends Stage { @@ -207,8 +209,8 @@ private DataTable toDataTable( String[][] dataTable ) { public SELF case_$_has_a_when_step_$_with_argument_$_and_argument_name_$( int ncase, String name, String arg, String argName ) { getCase( ncase ) .addStep( - new StepModel(name, - Arrays.asList(Word.introWord("when"), new Word(name), Word.argWord(argName, arg, (String) null)))); + new StepModel( name, + Arrays.asList( Word.introWord( "when" ), new Word( name ), Word.argWord( argName, arg, (String) null ) ) ) ); return self(); } @@ -239,4 +241,10 @@ public SELF header_type_set_to( Table.HeaderType headerType ) { latestWord.getArgumentInfo().getDataTable().setHeaderType( headerType ); return self(); } + + public SELF step_$_of_scenario_$_has_an_attachment_with_content( int stepNr, int scenarioNr, String content ) { + StepModel step = reportModel.getScenarios().get( scenarioNr - 1 ).getScenarioCases().get( 0 ).getStep( stepNr - 1 ); + step.setAttachment( Attachment.fromText( content, MediaType.PLAIN_TEXT ) ); + return self(); + } } diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/report/model/GivenReportModels.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/report/model/GivenReportModels.java index 7ef377524d..e98ddb9d42 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/report/model/GivenReportModels.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/report/model/GivenReportModels.java @@ -66,4 +66,9 @@ public SELF the_tag_has_prependType_set_to( boolean prependType ) { givenReportModel.step_$_of_case_$_has_status( stepNr, caseNr, status ); return self(); } + + public SELF step_$_of_scenario_$_has_a_text_attachment_with_content(int stepNr, int scenarioNr, String content) { + givenReportModel.step_$_of_scenario_$_has_an_attachment_with_content(stepNr, scenarioNr,content); + return self(); + } } From 61cbad23bd23c85e3bf6bc30635518929f5a0554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Sch=C3=A4fer?= Date: Sat, 24 Jan 2015 18:26:07 +0100 Subject: [PATCH 4/4] removed unneeded console output in test --- .../java/com/tngtech/jgiven/report/html5/ThenHtml5Report.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/ThenHtml5Report.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/ThenHtml5Report.java index 57224155b9..c9a907ad02 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/ThenHtml5Report.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/report/html5/ThenHtml5Report.java @@ -6,7 +6,6 @@ import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; -import java.util.Arrays; import java.util.List; import org.openqa.selenium.By; @@ -36,8 +35,6 @@ private List findAttachmentIcon() { public SELF the_content_of_the_referenced_attachment_is( String content ) throws IOException, URISyntaxException { String href = findAttachmentIcon().get( 0 ).findElement( By.xpath( ".." ) ).getAttribute( "href" ); - System.out.println( "==========" + href ); - System.out.println( Arrays.toString( targetReportDir.listFiles() ) ); String foundContent = Files.readFile( new File( new URL( href ).toURI() ) ).trim(); assertThat( content ).isEqualTo( foundContent ); return self();