diff --git a/README.md b/README.md index e46b344..c5970ad 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,30 @@ Serenity integration with Report Portal Module allows to report Serenity powered tests to [reportportal.io](http://reportportal.io). Supplies additional reporting facility to Serenity based test automation frameworks. -Setup -------------- +Table of Contents +----------------- +1. [Setup](#setup) + 1. [Maven](#maven) + 2. [Gradle](#gradle) + 3. [Native Serenity reporting](#native-serenity-reporting) +2. [Integration configuration](#integration-configuration) + 1. [Profiles](#profiles) + 2. [Executors](#executors) + 3. [Handler type (experimental feature)](#handler-type-experimental-feature) + 4. [Narrative formatter](#narrative-formatter) +3. [Data mapping](#data-mapping) +4. [Versioning](#versioning) +5. [Important release notes](#important-release-notes) +6. [Limitations](#limitations) + +## Setup + To add support of integration between Serenity and Report Portal simply add dependencies to your project based on used build tool. > **Warning** > Don't add any extra Report Portal listeners or agents. Integration is provided by single module for all available Serenity approaches -**Maven** +#### Maven Edit project's `pom.xml` file ``` @@ -38,7 +54,7 @@ Report Portal core libraries are used, but they placed in a separate repository, ``` -**Gradle** +#### Gradle Edit `build.gradle` file in the project root ``` @@ -72,12 +88,11 @@ It is also possible to use Report portal integration with log frameworks in orde > **Notice** > Actually to add logs to Report Portal, they should be emitted in scope of test method, otherwise they will not be displayed at all -**Native Serenity reporting** +#### Native Serenity reporting Serenity TAF may produce its own reporting facility via separate plugins. But `serenity-reportportal-integration` may be used in parallel with it or independently. Both reporting mechanisms should be configured accordingly and do not depends on each other. -Integration configuration -------------- +## Integration configuration All available configurations are provided via `ReportIntegrationConfig` object. Each set method returns object itself, so chain of configuration is possible: ``` @@ -88,7 +103,7 @@ configuration.useHandler(HandlerType.TREE).useProfile(StepsSetProfile.TREE_OPTIM > **Notice** All integration configurations should be provided before Serenity facility init (For example on `@BeforeClass` method on the parent test class for jUnit style tests). Otherwise default values will be used. -**Profiles** +#### Profiles Each Serenity `TestStep` object is passed through chain of configured `StepDataExtractors`. This approach allows to flexible configure reporting behaviour on a step level. By default integration provides following configuration profiles: @@ -110,7 +125,7 @@ profile.registerProcessors(new StartStep(), new FinishStep()); ReportIntegrationConfig.get().useProfile(profile); ``` -**Executors** +#### Executors All step executors are available out of the box may be observed in `com.github.invictum.reportportal.extractor` package. For now following processors are available: @@ -157,7 +172,7 @@ public class GreetingExtractor implements StepDataExtractor { Extracted collection of `EnhancedMessage` will be used to push logs to to Report Portal and their order will be based on timestamp. -**Handler type (experimental feature)** +#### Handler type (experimental feature) Integration provides two strategies of storing Serenity's test data to Report Portal facility: - *FLAT* (default behaviour) - Represents steps data as plain logs emitted to the test scope @@ -173,23 +188,23 @@ Handler type may be changed with following configuration ReportIntegrationConfig.get().useHandler(HandlerType.TREE); ``` -**Narrative formatter** +#### Narrative formatter -By default, narrative is formatted as a bullet list before storing to the test description field. It is possible to alter this logic in accordance to project needs. +By default, narrative is formatted as a card with title and bullet list before storing to the test description field. It is possible to alter this logic in accordance to project needs. -To achieve it implement `NarrativeFormatter` interface and define your own implementation of formatter. For example +To achieve it supply `Function` function to configuration and define your own implementation logic ``` -public class NumberedListFormatter implements NarrativeFormatter { - - @Override - public String format(String[] strings) { - return IntStream.range(0, strings.length).mapToObj(index -> (index + 1) + ". " + strings[index]) - .collect(Collectors.joining("\n")); - } -} +// Define Function that will format narrative as a numbered list +Function narrativeFormatter = narrative -> { + String[] strings = narrative.text(); + return IntStream.range(0, strings.length).mapToObj(index -> (index + 1) + ". " + strings[index]) + .collect(Collectors.joining("\n")); +}; +// Add defined function to the configuration +ReportIntegrationConfig.get().useNarrativeFormatter(narrativeFormatter); ``` -Code snippet above will format narrative lines as a numbered list. +Code snippet above will format narrative lines as a numbered list ``` Initial lines line 1, line 2 @@ -199,15 +214,12 @@ Result lines 2. line 2 ``` -Custom `NarrativeFormatter` should be registered via configuration -``` -ReportIntegrationConfig.get().useNarrativeFormatter(new NumberedListFormatter()); -``` +> **Info** +> Text returned by `Function` function is treated as markdown, so markdown syntax could be used -Data mapping -------------- +## Data mapping -Serenity framework and Report Portal facility have a different entities structure. This section explains how data related to each other in mentioned systems. +Serenity framework and Report Portal facility have a different entities structure. This section explains how data relates to each other. **Name** relation is straightforward. @@ -233,7 +245,7 @@ When for simple scenario Then for simple scenario ``` -For jUnit there is a `@Narrative` annotation. Each line of narrative text will be concatinated +For jUnit there is a `@Narrative` annotation ``` @RunWith(SerenityRunner.class) @Narrative(text = {"line 1", "line 2"}) @@ -241,6 +253,7 @@ public class SimpleTest { ... } ``` +Narrative is transformed with function that may be passed to integration configuration **Tags** supplying depends on test source. For jBehave (BDD) tests tags is defined in Meta section with `@tag` or `@tags` keyword @@ -265,8 +278,8 @@ public class SimpleTest { } ``` -Versioning ----------- +## Versioning + Report Portal integration uses 3 digit version format - x.y.z **z** - regular release increment version. Includes bugfix and extending with minor features. Also includes Serenity and Report Portal modules versions update. Backward compatibility is guaranteed. @@ -275,18 +288,18 @@ Report Portal integration uses 3 digit version format - x.y.z **x** - major version update. Dramatically changed integration architecture. Backward compatibility doesn't guaranteed. Actually increment of major version is not expected at all -Important release notes ------------------------ +## Important release notes + Important release notes are described below. Use [releases](https://github.com/Invictum/serenity-reportportal-integration/releases) section for details regarding regular release notes. Version | Note ---------------|--------------------------- 1.0.0 - 1.0.6 | Supports RP v3 and below 1.1.0 - 1.1.3 | Minor version update due RP v4 release. Versions older than 1.1.0 are not compatible with RP v4+ and vise versa -1.2.0+ | Minor version updated due Configuration approach refactoring. New configuratiion approach is not compatible with versions under 1.2.0 +1.2.0+ | Minor version updated due internal mechanisms approach major refactoring + +## Limitations -Limitations -------------- Integration does not support concurrency for parametrized Serenity tests execution. -Report Portal launch finish timestamp is calculated before Java VM shutdown. Overall launch duration will also include the time of Serenity report generation. +Report Portal launch finish timestamp is calculated before Java VM shutdown. Overall launch duration will also include the time of Serenity report generation (only in case if both RP and Serenity reporting are used). diff --git a/src/main/java/com/github/invictum/reportportal/NarrativeBulletListFormatter.java b/src/main/java/com/github/invictum/reportportal/NarrativeBulletListFormatter.java deleted file mode 100644 index 3ef37be..0000000 --- a/src/main/java/com/github/invictum/reportportal/NarrativeBulletListFormatter.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.github.invictum.reportportal; - -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * Format narrative as a bullet list - */ -public class NarrativeBulletListFormatter implements NarrativeFormatter { - - @Override - public String format(String[] narrative) { - return Stream.of(narrative).map(item -> "* " + item).collect(Collectors.joining("\n")); - } -} diff --git a/src/main/java/com/github/invictum/reportportal/NarrativeFormatter.java b/src/main/java/com/github/invictum/reportportal/NarrativeFormatter.java deleted file mode 100644 index 74ef386..0000000 --- a/src/main/java/com/github/invictum/reportportal/NarrativeFormatter.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.github.invictum.reportportal; - -/** - * Allows to alter narrative lines before passing it to test description - */ -public interface NarrativeFormatter { - /** - * Formats a list of narrative text lines to description - * Returned {@link String} should be formatted as Markdown because Report Portal expects it - * - * @param narrative lines extracted from test or story - * @return Markdown formatted text - */ - String format(String[] narrative); -} diff --git a/src/main/java/com/github/invictum/reportportal/ReportIntegrationConfig.java b/src/main/java/com/github/invictum/reportportal/ReportIntegrationConfig.java index 28d924d..a20bbac 100644 --- a/src/main/java/com/github/invictum/reportportal/ReportIntegrationConfig.java +++ b/src/main/java/com/github/invictum/reportportal/ReportIntegrationConfig.java @@ -2,8 +2,12 @@ import com.github.invictum.reportportal.handler.HandlerType; import com.github.invictum.reportportal.injector.IntegrationInjector; +import net.thucydides.core.annotations.Narrative; import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Configuration entry point for integration. @@ -12,7 +16,7 @@ public class ReportIntegrationConfig { private StepsSetProfile profile; - private NarrativeFormatter narrativeFormatter; + private Function narrativeFormatter; private HandlerType handlerType; /** @@ -39,14 +43,14 @@ public StepsSetProfile profile() { } /** - * Defines {@link NarrativeFormatter} configuration + * Defines narrative format {@link Function} */ - public ReportIntegrationConfig useNarrativeFormatter(NarrativeFormatter narrativeFormatter) { + public ReportIntegrationConfig useNarrativeFormatter(Function narrativeFormatter) { this.narrativeFormatter = Objects.requireNonNull(narrativeFormatter, "Narrative formatter could not be null"); return this; } - public NarrativeFormatter narrativeFormatter() { + public Function narrativeFormatter() { return narrativeFormatter; } @@ -67,7 +71,11 @@ public HandlerType handlerType() { */ public void resetToDefaults() { this.profile = StepsSetProfile.DEFAULT; - this.narrativeFormatter = new NarrativeBulletListFormatter(); + // Returned text is treated by RP as markdown + this.narrativeFormatter = narrative -> { + String text = Stream.of(narrative.text()).map(item -> "* " + item).collect(Collectors.joining("\n")); + return narrative.title().isEmpty() ? text : String.format("**%s**\n", narrative.title()) + text; + }; this.handlerType = HandlerType.FLAT; } } diff --git a/src/main/java/com/github/invictum/reportportal/handler/FlatHandler.java b/src/main/java/com/github/invictum/reportportal/handler/FlatHandler.java index 22def1b..754ee44 100644 --- a/src/main/java/com/github/invictum/reportportal/handler/FlatHandler.java +++ b/src/main/java/com/github/invictum/reportportal/handler/FlatHandler.java @@ -7,6 +7,7 @@ import com.github.invictum.reportportal.injector.IntegrationInjector; import com.google.inject.Inject; import io.reactivex.Maybe; +import net.thucydides.core.annotations.Narrative; import net.thucydides.core.model.Story; import net.thucydides.core.model.TestOutcome; import net.thucydides.core.requirements.annotations.NarrativeFinder; @@ -14,6 +15,7 @@ import java.time.Duration; import java.util.Calendar; import java.util.Date; +import java.util.function.Function; /** * Handler builds Serenity's {@link TestOutcome} in Report Portal as flat sequence of logs @@ -41,11 +43,10 @@ public void startSuite(Class storyClass) { startSuite.setName(storyClass.getSimpleName()); startSuite.setStartTime(Calendar.getInstance().getTime()); /* Add narrative to description if present */ - if (NarrativeFinder.forClass(storyClass).isPresent()) { - NarrativeFormatter narrativeFormatter = ReportIntegrationConfig.get().narrativeFormatter(); - String description = narrativeFormatter.format(NarrativeFinder.forClass(storyClass).get().text()); - startSuite.setDescription(description); - } + NarrativeFinder.forClass(storyClass).ifPresent(narrative -> { + Function narrativeFormatter = ReportIntegrationConfig.get().narrativeFormatter(); + startSuite.setDescription(narrativeFormatter.apply(narrative)); + }); suiteId = launch.startTestItem(startSuite); } } diff --git a/src/test/java/com/github/invictum/reportportal/NarrativeFormatterTest.java b/src/test/java/com/github/invictum/reportportal/NarrativeFormatterTest.java deleted file mode 100644 index 3491d8c..0000000 --- a/src/test/java/com/github/invictum/reportportal/NarrativeFormatterTest.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.github.invictum.reportportal; - -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class NarrativeFormatterTest { - - @Test - public void narrativeBulletListFormatter() { - NarrativeFormatter narrativeFormatter = new NarrativeBulletListFormatter(); - String actual = narrativeFormatter.format(new String[]{"line 1", "line 2", "line 3"}); - String expected = "* line 1\n* line 2\n* line 3"; - Assert.assertEquals("Narrative formatted wrong", expected, actual); - } -} diff --git a/src/test/java/com/github/invictum/reportportal/ReportIntegrationConfigTest.java b/src/test/java/com/github/invictum/reportportal/ReportIntegrationConfigTest.java index d0419c8..5b515d8 100644 --- a/src/test/java/com/github/invictum/reportportal/ReportIntegrationConfigTest.java +++ b/src/test/java/com/github/invictum/reportportal/ReportIntegrationConfigTest.java @@ -1,11 +1,13 @@ package com.github.invictum.reportportal; import com.github.invictum.reportportal.handler.HandlerType; +import net.thucydides.core.annotations.Narrative; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.mockito.Mockito; @RunWith(JUnit4.class) public class ReportIntegrationConfigTest { @@ -40,8 +42,12 @@ public void nullHandlerTypeTest() { @Test public void defaultNarrativeFormatterTest() { - Class actual = config.narrativeFormatter().getClass(); - Assert.assertEquals(NarrativeBulletListFormatter.class, actual); + Narrative narrativeMock = Mockito.mock(Narrative.class); + Mockito.when(narrativeMock.title()).thenReturn("Title"); + Mockito.when(narrativeMock.text()).thenReturn(new String[]{"line 1", "line 2"}); + config.resetToDefaults(); + String actual = config.narrativeFormatter().apply(narrativeMock); + Assert.assertEquals("**Title**\n* line 1\n* line 2", actual); } @Test(expected = NullPointerException.class)