├── .README ├── Browserstack-logo.png ├── feature-failed.png ├── feature-overview.png ├── feature-passed.png ├── tag-overview.png ├── tag-report.png └── trends.png ├── .github ├── dependabot.yml └── workflows │ ├── build.yml │ ├── codecov.yml │ ├── github-pages.yml │ └── sonarcloud.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENCE ├── README.md ├── appveyor.yml ├── pom.xml └── src ├── main ├── java │ └── net │ │ └── masterthought │ │ └── cucumber │ │ ├── Configuration.java │ │ ├── EmptyReportable.java │ │ ├── ReportBuilder.java │ │ ├── ReportParser.java │ │ ├── ReportResult.java │ │ ├── Reportable.java │ │ ├── Trends.java │ │ ├── ValidationException.java │ │ ├── generators │ │ ├── AbstractPage.java │ │ ├── ErrorPage.java │ │ ├── EscapeHtmlReference.java │ │ ├── FailuresOverviewPage.java │ │ ├── FeatureReportPage.java │ │ ├── FeaturesOverviewPage.java │ │ ├── OverviewReport.java │ │ ├── StepsOverviewPage.java │ │ ├── TagReportPage.java │ │ ├── TagsOverviewPage.java │ │ └── TrendsOverviewPage.java │ │ ├── json │ │ ├── DocString.java │ │ ├── Element.java │ │ ├── Embedding.java │ │ ├── Feature.java │ │ ├── Hook.java │ │ ├── Match.java │ │ ├── Output.java │ │ ├── Result.java │ │ ├── Row.java │ │ ├── Step.java │ │ ├── Tag.java │ │ ├── deserializers │ │ │ ├── CommentsDeserializer.java │ │ │ ├── CucumberJsonDeserializer.java │ │ │ ├── EmbeddingDeserializer.java │ │ │ ├── OutputsDeserializer.java │ │ │ ├── StatusDeserializer.java │ │ │ └── TagsDeserializer.java │ │ └── support │ │ │ ├── Argument.java │ │ │ ├── Durationable.java │ │ │ ├── Resultsable.java │ │ │ ├── Status.java │ │ │ ├── StatusCounter.java │ │ │ ├── StepObject.java │ │ │ └── TagObject.java │ │ ├── presentation │ │ └── PresentationMode.java │ │ ├── reducers │ │ ├── ElementComparator.java │ │ ├── ReducingMethod.java │ │ ├── ReportFeatureAppendableMerger.java │ │ ├── ReportFeatureByIdMerger.java │ │ ├── ReportFeatureMerger.java │ │ ├── ReportFeatureMergerFactory.java │ │ └── ReportFeatureWithRetestMerger.java │ │ ├── sorting │ │ ├── FeaturesAlphabeticalComparator.java │ │ ├── SortingFactory.java │ │ ├── SortingMethod.java │ │ ├── StepObjectAlphabeticalComparator.java │ │ └── TagObjectAlphabeticalComparator.java │ │ └── util │ │ ├── Counter.java │ │ ├── StepNameFormatter.java │ │ └── Util.java └── resources │ ├── css │ ├── bootstrap.min.css │ ├── cucumber.css │ └── font-awesome.min.css │ ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ ├── fontawesome-webfont.woff2 │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 │ ├── images │ └── favicon.png │ ├── js │ ├── Chart.min.js │ ├── bootstrap.min.js │ ├── jquery.min.js │ ├── jquery.tablesorter.min.js │ └── moment.min.js │ └── templates │ ├── footer.vm │ ├── generators │ ├── errorpage.vm │ ├── overviewFailures.vm │ ├── overviewFeatures.vm │ ├── overviewSteps.vm │ ├── overviewTags.vm │ ├── overviewTrends.vm │ ├── reportFeature.vm │ └── reportTag.vm │ ├── head.vm │ ├── headers.vm │ ├── js │ ├── features-chart.js.vm │ ├── scenarios-chart.js.vm │ ├── steps-chart.js.vm │ ├── tags-chart.js.vm │ └── trends-chart.js.vm │ └── macros │ ├── array.js.vm │ ├── json │ ├── brief.vm │ ├── docstring.vm │ ├── duration.vm │ ├── element.vm │ ├── embeddings.vm │ ├── hooks.vm │ ├── message.vm │ ├── output.vm │ ├── stepName.vm │ ├── steps.vm │ └── tags.vm │ ├── page │ ├── buildinfo.vm │ ├── classifications.vm │ ├── lead.vm │ ├── navigation.vm │ ├── reportInfo.vm │ └── title.vm │ └── report │ ├── expandAllButtons.vm │ ├── reportHeader.vm │ ├── reportTable.vm │ └── statsTable.vm └── test ├── java ├── LiveDemoTest.java └── net │ └── masterthought │ └── cucumber │ ├── ConfigurationTest.java │ ├── EmptyReportableTest.java │ ├── FileReaderUtil.java │ ├── ReportBuilderTest.java │ ├── ReportGenerator.java │ ├── ReportParserTest.java │ ├── ReportResultMergeTest.java │ ├── ReportResultSimpleFeatureComparator.java │ ├── ReportResultTest.java │ ├── ReportableBuilder.java │ ├── TrendsTest.java │ ├── ValidationExceptionTest.java │ ├── generators │ ├── AbstractPageTest.java │ ├── ErrorPageTest.java │ ├── EscapeHtmlReferenceTest.java │ ├── FailuresOverviewPageTest.java │ ├── FeatureReportPageTest.java │ ├── FeaturesOverviewPageTest.java │ ├── OverviewReportTest.java │ ├── StepsOverviewPageTest.java │ ├── TagReportPageTest.java │ ├── TagsOverviewPageTest.java │ ├── TrendsOverviewPageTest.java │ └── integrations │ │ ├── ErrorPageIntegrationTest.java │ │ ├── FailuresOverviewPageIntegrationTest.java │ │ ├── FeatureReportPageIntegrationTest.java │ │ ├── FeaturesOverviewPageIntegrationTest.java │ │ ├── PageIntegrationTest.java │ │ ├── PageTest.java │ │ ├── StepsOverviewPageIntegrationTest.java │ │ ├── TagReportPageIntegrationTest.java │ │ ├── TagsOverviewPageIntegrationTest.java │ │ ├── TrendsOverviewPageIntegrationTest.java │ │ └── helpers │ │ ├── BriefAssertion.java │ │ ├── BuildInfoAssertion.java │ │ ├── DocStringAssertion.java │ │ ├── DocumentAssertion.java │ │ ├── ElementAssertion.java │ │ ├── EmbeddingAssertion.java │ │ ├── FeatureAssertion.java │ │ ├── HeadAssertion.java │ │ ├── HookAssertion.java │ │ ├── HooksAssertion.java │ │ ├── LeadAssertion.java │ │ ├── LinkAssertion.java │ │ ├── NavigationAssertion.java │ │ ├── NavigationItemAssertion.java │ │ ├── OutputAssertion.java │ │ ├── ReportAssertion.java │ │ ├── StepAssertion.java │ │ ├── StepsAssertion.java │ │ ├── SummaryAssertion.java │ │ ├── TableAssertion.java │ │ ├── TableRowAssertion.java │ │ ├── TagAssertion.java │ │ └── WebAssertion.java │ ├── json │ ├── ArgumentTest.java │ ├── DocStringTest.java │ ├── ElementTest.java │ ├── EmbeddingTest.java │ ├── EmbeddingWithNameTest.java │ ├── FeatureTest.java │ ├── HookTest.java │ ├── MatchTest.java │ ├── OutputTest.java │ ├── ResultTest.java │ ├── RowTest.java │ ├── StepTest.java │ ├── TagTest.java │ ├── deserializers │ │ ├── CommentsDeserializerTest.java │ │ ├── EmbeddingDeserializerTest.java │ │ ├── StatusDeserializerTest.java │ │ └── TagsDeserializerTest.java │ └── support │ │ ├── ResultsableBuilder.java │ │ ├── StatusTest.java │ │ ├── StepObjectTest.java │ │ ├── TagObjectTest.java │ │ └── comparators │ │ └── StatusCounterTest.java │ ├── reducers │ ├── ElementComparatorTest.java │ ├── ReportFeatureAppendableMergerTest.java │ ├── ReportFeatureByIdMergerTest.java │ ├── ReportFeatureMergerFactoryTest.java │ └── ReportFeatureWithRetestMergerTest.java │ ├── sorting │ ├── FeaturesAlphabeticalComparatorTest.java │ ├── SortingFactoryTest.java │ ├── SortingMethod.java │ ├── StepObjectAlphabeticalComparatorTest.java │ └── TagObjectAlphabeticalComparatorTest.java │ └── util │ ├── CounterTest.java │ ├── StepNameFormatterTest.java │ └── UtilTest.java └── resources ├── classifications ├── duplicate.properties ├── empty.properties ├── sample_one.properties ├── sample_two.properties └── special_characters.properties ├── css ├── stackoverflow-light.min.css └── test.css ├── cucumber-trends.json ├── demo-trends.json ├── js ├── LICENSE_highlightjs ├── enable-highlighting.js ├── highlight.min.js └── test.js ├── json ├── empty-file.json ├── empty.json ├── invalid-report.json ├── sample.json ├── sample_failed.json ├── simple.json └── timestamped │ ├── all-last-failed.json │ ├── all-passed.json │ ├── part1.json │ ├── part2-rerun-failed.json │ ├── part2-rerun-passed.json │ └── part2.json └── simplelogger.properties /.README/Browserstack-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianszczepanik/cucumber-reporting/39b99d3634b17d021ac42143dd4283e02a629036/.README/Browserstack-logo.png -------------------------------------------------------------------------------- /.README/feature-failed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianszczepanik/cucumber-reporting/39b99d3634b17d021ac42143dd4283e02a629036/.README/feature-failed.png -------------------------------------------------------------------------------- /.README/feature-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianszczepanik/cucumber-reporting/39b99d3634b17d021ac42143dd4283e02a629036/.README/feature-overview.png -------------------------------------------------------------------------------- /.README/feature-passed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianszczepanik/cucumber-reporting/39b99d3634b17d021ac42143dd4283e02a629036/.README/feature-passed.png -------------------------------------------------------------------------------- /.README/tag-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianszczepanik/cucumber-reporting/39b99d3634b17d021ac42143dd4283e02a629036/.README/tag-overview.png -------------------------------------------------------------------------------- /.README/tag-report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianszczepanik/cucumber-reporting/39b99d3634b17d021ac42143dd4283e02a629036/.README/tag-report.png -------------------------------------------------------------------------------- /.README/trends.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianszczepanik/cucumber-reporting/39b99d3634b17d021ac42143dd4283e02a629036/.README/trends.png -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # configuration: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 2 | 3 | version: 2 4 | 5 | updates: 6 | - package-ecosystem: maven 7 | directory: "/" 8 | schedule: 9 | interval: monthly 10 | open-pull-requests-limit: 3 11 | target-branch: master 12 | commit-message: 13 | prefix: "[dependency] " -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: 9 | - opened 10 | - synchronize 11 | - reopened 12 | 13 | jobs: 14 | build: 15 | strategy: 16 | matrix: 17 | java: [ '11', '17', '21' ] 18 | os: [ 'ubuntu-latest', 'windows-latest', 'macos-latest' ] 19 | 20 | runs-on: ${{ matrix.os }} 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - uses: actions/setup-java@v4 26 | with: 27 | java-version: ${{ matrix.java }} 28 | distribution: adopt 29 | 30 | - run: mvn --batch-mode verify 31 | -------------------------------------------------------------------------------- /.github/workflows/codecov.yml: -------------------------------------------------------------------------------- 1 | name: codecov 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: 9 | - opened 10 | - synchronize 11 | - reopened 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - uses: actions/setup-java@v4 21 | with: 22 | java-version: 11 23 | distribution: adopt 24 | 25 | - name: Generate code coverage 26 | run: mvn --batch-mode test 27 | 28 | - uses: codecov/codecov-action@v3 29 | -------------------------------------------------------------------------------- /.github/workflows/github-pages.yml: -------------------------------------------------------------------------------- 1 | name: github-pages 2 | 3 | on: 4 | push: 5 | branches: 6 | # upload demo report only from main branch 7 | - master 8 | 9 | jobs: 10 | build: 11 | # Do not run on forks as unnecessary 12 | if: github.repository_owner == 'damianszczepanik' 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - uses: actions/setup-java@v4 19 | with: 20 | java-version: 11 21 | distribution: adopt 22 | 23 | - name: Generate demo report 24 | run: mvn --batch-mode test 25 | 26 | - uses: peaceiris/actions-gh-pages@v3 27 | with: 28 | external_repository: damianszczepanik/damianszczepanik.github.io 29 | personal_token: ${{ secrets.GH_PAGES_UPLOAD }} 30 | publish_branch: master 31 | publish_dir: target/demo/ 32 | -------------------------------------------------------------------------------- /.github/workflows/sonarcloud.yml: -------------------------------------------------------------------------------- 1 | name: sonarcloud.io 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | # Community version only allows running against 'main' branch, see https://docs.sonarsource.com/sonarqube/latest/devops-platform-integration/github-integration/ 8 | 9 | jobs: 10 | build: 11 | # Do not run sonar on forks because SONAR_TOKEN is available only for project owner 12 | if: github.repository_owner == 'damianszczepanik' 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod 19 | 20 | - uses: actions/setup-java@v4 21 | with: 22 | java-version: 17 23 | distribution: adopt 24 | 25 | - name: Build with Maven 26 | run: mvn --batch-mode package sonar:sonar -Dsonar.projectKey=damianszczepanik_cucumber-reporting -Dsonar.organization=damianszczepanik -Dsonar.host.url=https://sonarcloud.io -Dsonar.token=$SONAR_TOKEN 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .idea 3 | .classpath 4 | .project 5 | .settings/ 6 | cucumber-reporting.iml 7 | /*.json 8 | *.iml 9 | /src/main/java/HelloCucumber.java 10 | .factorypath 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Before you send pull request, make sure that: 3 | 4 | - changes in code or new features are tested so the [coverage](https://codecov.io/github/damianszczepanik/cucumber-reporting) remains on the same level 5 | - UX should be tested by [additional tests](https://github.com/damianszczepanik/cucumber-reporting/tree/master/src/test/java/net/masterthought/cucumber/generators) 6 | - code is written according to best practice and code clean 7 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2019 2 | version: '{build}' 3 | 4 | init: 5 | - git config --global core.autocrlf true 6 | 7 | branches: 8 | only: 9 | - master 10 | 11 | environment: 12 | global: 13 | # https://stackoverflow.com/questions/42024619/maven-build-gets-connection-reset-when-downloading-artifacts 14 | MAVEN_OPTS: "-Dhttp.keepAlive=false -Dmaven.wagon.http.retryHandler.count=3" 15 | matrix: 16 | - JAVA_HOME: C:\Program Files\Java\jdk11 17 | 18 | build_script: 19 | - mvn clean package --batch-mode -DskipTest 20 | test_script: 21 | - mvn clean verify --batch-mode 22 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/EmptyReportable.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber; 2 | 3 | import net.masterthought.cucumber.json.support.Status; 4 | 5 | /** 6 | * Defines empty reportable that is usded when the build fails. 7 | * 8 | * @author Damian Szczepanik (damianszczepanik@github) 9 | */ 10 | public class EmptyReportable implements Reportable { 11 | 12 | @Override 13 | public String getName() { 14 | return null; 15 | } 16 | 17 | @Override 18 | public int getFeatures() { 19 | return 0; 20 | } 21 | 22 | @Override 23 | public int getPassedFeatures() { 24 | return 0; 25 | } 26 | 27 | @Override 28 | public int getFailedFeatures() { 29 | return 0; 30 | } 31 | 32 | @Override 33 | public int getScenarios() { 34 | return 0; 35 | } 36 | 37 | @Override 38 | public int getPassedScenarios() { 39 | return 0; 40 | } 41 | 42 | @Override 43 | public int getFailedScenarios() { 44 | return 0; 45 | } 46 | 47 | @Override 48 | public int getSteps() { 49 | return 0; 50 | } 51 | 52 | @Override 53 | public int getPassedSteps() { 54 | return 0; 55 | } 56 | 57 | @Override 58 | public int getFailedSteps() { 59 | return 0; 60 | } 61 | 62 | @Override 63 | public int getSkippedSteps() { 64 | return 0; 65 | } 66 | 67 | @Override 68 | public int getUndefinedSteps() { 69 | return 0; 70 | } 71 | 72 | @Override 73 | public int getPendingSteps() { 74 | return 0; 75 | } 76 | 77 | @Override 78 | public long getDuration() { 79 | return 0; 80 | } 81 | 82 | @Override 83 | public String getFormattedDuration() { 84 | return null; 85 | } 86 | 87 | @Override 88 | public Status getStatus() { 89 | return null; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/Reportable.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber; 2 | 3 | import net.masterthought.cucumber.json.support.Status; 4 | 5 | /** 6 | * Defines methods required to generate single report. Implementations of this interface are used by Velocity template. 7 | * 8 | * @author Damian Szczepanik (damianszczepanik@github) 9 | */ 10 | public interface Reportable { 11 | 12 | /** 13 | * @return name of the element that will be displayed to user. 14 | */ 15 | String getName(); 16 | 17 | /** 18 | * @return number of features for this element. 19 | */ 20 | int getFeatures(); 21 | 22 | /** 23 | * @return number of passed features for this element. 24 | */ 25 | int getPassedFeatures(); 26 | 27 | /** 28 | * @return number of failed features for this element. 29 | */ 30 | int getFailedFeatures(); 31 | 32 | /** 33 | * @return number of scenarios for this element. 34 | */ 35 | int getScenarios(); 36 | 37 | /** 38 | * @return number of passed scenarios for this element. 39 | */ 40 | int getPassedScenarios(); 41 | 42 | /** 43 | * @return number of failed scenarios for this element. 44 | */ 45 | int getFailedScenarios(); 46 | 47 | /** 48 | * @return number of all steps for this element. 49 | */ 50 | int getSteps(); 51 | 52 | /** 53 | * @return number of passed steps for this element. 54 | */ 55 | int getPassedSteps(); 56 | 57 | /** 58 | * @return number of failed steps for this element. 59 | */ 60 | int getFailedSteps(); 61 | 62 | /** 63 | * @return number of skipped steps for this element. 64 | */ 65 | int getSkippedSteps(); 66 | 67 | /** 68 | * @return number of undefined steps for this element. 69 | */ 70 | int getUndefinedSteps(); 71 | 72 | /** 73 | * @return number of pending steps for this element. 74 | */ 75 | int getPendingSteps(); 76 | 77 | /** 78 | * @return duration as milliseconds for this element. 79 | */ 80 | long getDuration(); 81 | 82 | /** 83 | * @return formatted duration for this element. 84 | */ 85 | String getFormattedDuration(); 86 | 87 | /** 88 | * @return status for this element. 89 | */ 90 | Status getStatus(); 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/ValidationException.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber; 2 | 3 | /** 4 | * Thrown when report cannot be generated. 5 | * 6 | * @author Damian Szczepanik (damianszczepanik@github) 7 | */ 8 | public class ValidationException extends RuntimeException { 9 | 10 | public ValidationException(Exception e) { 11 | super(e); 12 | } 13 | 14 | public ValidationException(String message) { 15 | super(message); 16 | } 17 | 18 | public ValidationException(String message, Throwable exception) { 19 | super(message, exception); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/generators/ErrorPage.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators; 2 | 3 | import java.util.List; 4 | 5 | import org.apache.commons.lang3.exception.ExceptionUtils; 6 | 7 | import net.masterthought.cucumber.Configuration; 8 | import net.masterthought.cucumber.ReportBuilder; 9 | import net.masterthought.cucumber.ReportResult; 10 | 11 | public class ErrorPage extends AbstractPage { 12 | 13 | private final Exception exception; 14 | private final List jsonFiles; 15 | 16 | public ErrorPage(ReportResult reportResult, Configuration configuration, Exception exception, 17 | List jsonFiles) { 18 | super(reportResult, "errorpage.vm", configuration); 19 | this.exception = exception; 20 | this.jsonFiles = jsonFiles; 21 | } 22 | 23 | @Override 24 | public String getWebPage() { 25 | return ReportBuilder.HOME_PAGE; 26 | } 27 | 28 | @Override 29 | public void prepareReport() { 30 | context.put("classifications", configuration.getClassifications()); 31 | 32 | context.put("output_message", ExceptionUtils.getStackTrace(exception)); 33 | context.put("json_files", jsonFiles); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/generators/EscapeHtmlReference.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators; 2 | 3 | import org.apache.commons.text.StringEscapeUtils; 4 | import org.apache.velocity.app.event.ReferenceInsertionEventHandler; 5 | import org.apache.velocity.context.Context; 6 | import org.owasp.html.HtmlPolicyBuilder; 7 | import org.owasp.html.PolicyFactory; 8 | 9 | /** 10 | * Escapes all html and xml that was provided in a reference before inserting it into a template. 11 | * 12 | * References that start with $_sanitize_ will be sanitized to allow urls. 13 | */ 14 | final class EscapeHtmlReference implements ReferenceInsertionEventHandler { 15 | 16 | private static final PolicyFactory LINKS = new HtmlPolicyBuilder() 17 | .allowStandardUrlProtocols().allowElements("a").allowAttributes("href") 18 | .onElements("a").requireRelNofollowOnLinks().requireRelsOnLinks("noopener", "noreferrer") 19 | .toFactory(); 20 | 21 | @Override 22 | public Object referenceInsert(Context context, String reference, Object value) { 23 | if (value == null) { 24 | return null; 25 | } else if(reference.startsWith("$_sanitize_")) { 26 | return LINKS.sanitize(value.toString()); 27 | } else if(reference.startsWith("$_noescape_")) { 28 | return value.toString(); 29 | } else { 30 | return StringEscapeUtils.escapeHtml4(value.toString()); 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/generators/FailuresOverviewPage.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import net.masterthought.cucumber.Configuration; 7 | import net.masterthought.cucumber.ReportResult; 8 | import net.masterthought.cucumber.json.Element; 9 | import net.masterthought.cucumber.json.Feature; 10 | 11 | public class FailuresOverviewPage extends AbstractPage { 12 | 13 | public static final String WEB_PAGE = "overview-failures.html"; 14 | 15 | public FailuresOverviewPage(ReportResult reportResult, Configuration configuration) { 16 | super(reportResult, "overviewFailures.vm", configuration); 17 | } 18 | 19 | @Override 20 | public String getWebPage() { 21 | return WEB_PAGE; 22 | } 23 | 24 | @Override 25 | public void prepareReport() { 26 | context.put("failures", collectFailures()); 27 | } 28 | 29 | private List collectFailures() { 30 | List failures = new ArrayList<>(); 31 | for (Feature feature : reportResult.getAllFeatures()) { 32 | if (feature.getStatus().isPassed()) { 33 | continue; 34 | } 35 | 36 | for (Element element : feature.getElements()) { 37 | if (!element.getStatus().isPassed()) { 38 | failures.add(element); 39 | } 40 | } 41 | } 42 | return failures; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/generators/FeatureReportPage.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators; 2 | 3 | import net.masterthought.cucumber.Configuration; 4 | import net.masterthought.cucumber.ReportResult; 5 | import net.masterthought.cucumber.json.Feature; 6 | import net.masterthought.cucumber.presentation.PresentationMode; 7 | 8 | public class FeatureReportPage extends AbstractPage { 9 | 10 | private final Feature feature; 11 | 12 | public FeatureReportPage(ReportResult reportResult, Configuration configuration, Feature feature) { 13 | super(reportResult, "reportFeature.vm", configuration); 14 | this.feature = feature; 15 | } 16 | 17 | @Override 18 | public String getWebPage() { 19 | return feature.getReportFileName(); 20 | } 21 | 22 | @Override 23 | public void prepareReport() { 24 | context.put("feature", feature); 25 | context.put("parallel_testing", configuration.containsPresentationMode(PresentationMode.PARALLEL_TESTING)); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/generators/FeaturesOverviewPage.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators; 2 | 3 | import net.masterthought.cucumber.Configuration; 4 | import net.masterthought.cucumber.ReportBuilder; 5 | import net.masterthought.cucumber.ReportResult; 6 | import net.masterthought.cucumber.presentation.PresentationMode; 7 | 8 | public class FeaturesOverviewPage extends AbstractPage { 9 | 10 | public static final String WEB_PAGE = ReportBuilder.HOME_PAGE; 11 | 12 | public FeaturesOverviewPage(ReportResult reportResult, Configuration configuration) { 13 | super(reportResult, "overviewFeatures.vm", configuration); 14 | } 15 | 16 | @Override 17 | public String getWebPage() { 18 | return WEB_PAGE; 19 | } 20 | 21 | @Override 22 | public void prepareReport() { 23 | context.put("all_features", reportResult.getAllFeatures()); 24 | context.put("report_summary", reportResult.getFeatureReport()); 25 | 26 | context.put("parallel_testing", configuration.containsPresentationMode(PresentationMode.PARALLEL_TESTING)); 27 | context.put("classifications", configuration.getClassifications()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/generators/StepsOverviewPage.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators; 2 | 3 | import java.util.List; 4 | 5 | import net.masterthought.cucumber.Configuration; 6 | import net.masterthought.cucumber.ReportResult; 7 | import net.masterthought.cucumber.json.support.StepObject; 8 | import net.masterthought.cucumber.util.Util; 9 | 10 | /** 11 | * Presents details about how long steps are executed (adds the same steps and presents sum). 12 | * 13 | * @author Damian Szczepanik (damianszczepanik@github) 14 | */ 15 | public class StepsOverviewPage extends AbstractPage { 16 | 17 | public static final String WEB_PAGE = "overview-steps.html"; 18 | 19 | public StepsOverviewPage(ReportResult reportResult, Configuration configuration) { 20 | super(reportResult, "overviewSteps.vm", configuration); 21 | } 22 | 23 | @Override 24 | public String getWebPage() { 25 | return WEB_PAGE; 26 | } 27 | 28 | @Override 29 | public void prepareReport() { 30 | context.put("all_steps", reportResult.getAllSteps()); 31 | 32 | int allOccurrences = 0; 33 | long allDurations = 0; 34 | for (StepObject stepObject : reportResult.getAllSteps()) { 35 | allOccurrences += stepObject.getTotalOccurrences(); 36 | allDurations += stepObject.getDuration(); 37 | } 38 | context.put("all_occurrences", allOccurrences); 39 | context.put("all_durations", Util.formatDuration(allDurations)); 40 | // make sure it does not divide by 0 - may happens if there is no step at all or all results have 0 ms durations 41 | context.put("all_max_duration", Util.formatDuration(maxDurationOf(reportResult.getAllSteps()))); 42 | long average = allDurations / (allOccurrences == 0 ? 1 : allOccurrences); 43 | context.put("all_average_duration", Util.formatDuration(average)); 44 | } 45 | 46 | private long maxDurationOf(List steps) { 47 | long maxDuration = 0; 48 | for (StepObject step : steps) { 49 | if (step.getDuration() > maxDuration) { 50 | maxDuration = step.getDuration(); 51 | } 52 | } 53 | return maxDuration; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/generators/TagReportPage.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators; 2 | 3 | import net.masterthought.cucumber.Configuration; 4 | import net.masterthought.cucumber.ReportResult; 5 | import net.masterthought.cucumber.json.support.TagObject; 6 | 7 | public class TagReportPage extends AbstractPage { 8 | 9 | private final TagObject tagObject; 10 | 11 | public TagReportPage(ReportResult reportResult, Configuration configuration, TagObject tagObject) { 12 | super(reportResult, "reportTag.vm", configuration); 13 | this.tagObject = tagObject; 14 | } 15 | 16 | @Override 17 | public String getWebPage() { 18 | return tagObject.getReportFileName(); 19 | } 20 | 21 | @Override 22 | public void prepareReport() { 23 | context.put("tag", tagObject); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/generators/TagsOverviewPage.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators; 2 | 3 | import java.util.List; 4 | 5 | import net.masterthought.cucumber.Configuration; 6 | import net.masterthought.cucumber.ReportResult; 7 | import net.masterthought.cucumber.json.support.Status; 8 | import net.masterthought.cucumber.json.support.TagObject; 9 | import net.masterthought.cucumber.util.Util; 10 | 11 | /** 12 | * @author Damian Szczepanik (damianszczepanik@github) 13 | */ 14 | public class TagsOverviewPage extends AbstractPage { 15 | 16 | public static final String WEB_PAGE = "overview-tags.html"; 17 | 18 | public TagsOverviewPage(ReportResult reportResult, Configuration configuration) { 19 | super(reportResult, "overviewTags.vm", configuration); 20 | } 21 | 22 | @Override 23 | public String getWebPage() { 24 | return WEB_PAGE; 25 | } 26 | 27 | @Override 28 | public void prepareReport() { 29 | List tags = reportResult.getAllTags(); 30 | context.put("all_tags", tags); 31 | context.put("report_summary", reportResult.getTagReport()); 32 | 33 | context.put("chart_categories", generateTagLabels(tags)); 34 | context.put("chart_data", generateTagValues(tags)); 35 | } 36 | 37 | static String[] generateTagLabels(List tagsObjectList) { 38 | int tagCount = tagsObjectList.size(); 39 | String[] tagNames = new String[tagCount]; 40 | 41 | for (int i = 0; i < tagCount; i++) { 42 | tagNames[i] = tagsObjectList.get(i).getName(); 43 | } 44 | return tagNames; 45 | } 46 | 47 | 48 | static String[][] generateTagValues(List tagsObjectList) { 49 | int tagsCount = tagsObjectList.size(); 50 | String[][] values = new String[Status.values().length][tagsCount]; 51 | for (int i = 0; i < tagsCount; i++) { 52 | final TagObject tagObject = tagsObjectList.get(i); 53 | final int allSteps = tagObject.getSteps(); 54 | values[0][i] = Util.formatAsDecimal(tagObject.getPassedSteps(), allSteps); 55 | values[1][i] = Util.formatAsDecimal(tagObject.getFailedSteps(), allSteps); 56 | values[2][i] = Util.formatAsDecimal(tagObject.getSkippedSteps(), allSteps); 57 | values[3][i] = Util.formatAsDecimal(tagObject.getPendingSteps(), allSteps); 58 | values[4][i] = Util.formatAsDecimal(tagObject.getUndefinedSteps(), allSteps); 59 | } 60 | 61 | return values; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/generators/TrendsOverviewPage.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators; 2 | 3 | import net.masterthought.cucumber.Configuration; 4 | import net.masterthought.cucumber.ReportResult; 5 | import net.masterthought.cucumber.Trends; 6 | 7 | /** 8 | * @author Damian Szczepanik (damianszczepanik@github) 9 | */ 10 | public class TrendsOverviewPage extends AbstractPage { 11 | 12 | public static final String WEB_PAGE = "overview-trends.html"; 13 | 14 | private final Trends trends; 15 | 16 | public TrendsOverviewPage(ReportResult reportResult, Configuration configuration, Trends trends) { 17 | super(reportResult, "overviewTrends.vm", configuration); 18 | this.trends = trends; 19 | } 20 | 21 | @Override 22 | public String getWebPage() { 23 | return WEB_PAGE; 24 | } 25 | 26 | @Override 27 | public void prepareReport() { 28 | context.put("buildNumbers", trends.getBuildNumbers()); 29 | 30 | context.put("failedFeatures", trends.getFailedFeatures()); 31 | context.put("passedFeatures", trends.getPassedFeatures()); 32 | context.put("failedScenarios", trends.getFailedScenarios()); 33 | context.put("passedScenarios", trends.getPassedScenarios()); 34 | 35 | context.put("passedSteps", trends.getPassedSteps()); 36 | context.put("failedSteps", trends.getFailedSteps()); 37 | context.put("skippedSteps", trends.getSkippedSteps()); 38 | context.put("pendingSteps", trends.getPendingSteps()); 39 | context.put("undefinedSteps", trends.getUndefinedSteps()); 40 | 41 | context.put("durations", trends.getDurations()); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/json/DocString.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json; 2 | 3 | /** 4 | * Doc Strings are handy for specifying a larger piece of text. This is inspired from Python’s Docstring syntax. 5 | * 6 | * In your step definition, there’s no need to find this text and match it in your Regexp. It will automatically be 7 | * passed as the last parameter in the step definition. 8 | */ 9 | public class DocString { 10 | 11 | // Start: attributes from JSON file report 12 | private final String value = null; 13 | // End: attributes from JSON file report 14 | 15 | public String getValue() { 16 | return value; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/json/Hook.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | import net.masterthought.cucumber.json.deserializers.OutputsDeserializer; 8 | import net.masterthought.cucumber.json.support.Resultsable; 9 | 10 | public class Hook implements Resultsable { 11 | 12 | // Start: attributes from JSON file report 13 | private final Result result = null; 14 | private final Match match = null; 15 | 16 | @JsonDeserialize(using = OutputsDeserializer.class) 17 | @JsonProperty("output") 18 | private final Output[] outputs = new Output[0]; 19 | 20 | // foe Ruby reports 21 | private final Embedding[] embeddings = new Embedding[0]; 22 | // End: attributes from JSON file report 23 | 24 | @Override 25 | public Result getResult() { 26 | return result; 27 | } 28 | 29 | @Override 30 | public Match getMatch() { 31 | return match; 32 | } 33 | 34 | @Override 35 | public Output[] getOutputs() { 36 | return outputs; 37 | } 38 | 39 | public Embedding[] getEmbeddings() { 40 | return embeddings; 41 | } 42 | 43 | /** 44 | * Checks if the hook has content meaning as it has at least attachment or result with error message. 45 | * 46 | * @return true if the hook has content otherwise false 47 | */ 48 | public boolean hasContent() { 49 | if (embeddings.length > 0) { 50 | // assuming that if the embedding exists then it is not empty 51 | return true; 52 | } 53 | if (StringUtils.isNotBlank(result.getErrorMessage())) { 54 | return true; 55 | } 56 | // TODO: hook with 'output' should be treated as empty or not? 57 | return false; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/json/Match.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json; 2 | 3 | import net.masterthought.cucumber.json.support.Argument; 4 | 5 | public class Match { 6 | 7 | // Start: attributes from JSON file report 8 | private final String location = null; 9 | private final Argument[] arguments = new Argument[0]; 10 | // End: attributes from JSON file report 11 | 12 | public String getLocation() { 13 | return location; 14 | } 15 | 16 | public Argument[] getArguments() { 17 | return arguments; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/json/Output.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json; 2 | 3 | /** 4 | * @author Damian Szczepanik (damianszczepanik@github) 5 | */ 6 | public class Output { 7 | 8 | private final String[] messages; 9 | 10 | public Output(String[] messages) { 11 | this.messages = messages; 12 | } 13 | 14 | public String[] getMessages() { 15 | return messages; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/json/Result.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import net.masterthought.cucumber.json.support.Durationable; 5 | import net.masterthought.cucumber.json.support.Status; 6 | import net.masterthought.cucumber.util.Util; 7 | import org.apache.commons.lang3.StringUtils; 8 | 9 | public class Result implements Durationable { 10 | 11 | // Start: attributes from JSON file report 12 | 13 | // by default set UNDEFINED status 14 | // for all cases where Result is not present or completed 15 | private final Status status = Status.UNDEFINED; 16 | @JsonProperty("error_message") 17 | private final String errorMessage = null; 18 | private final Long duration = 0L; 19 | // End: attributes from JSON file report 20 | 21 | public Status getStatus() { 22 | return status; 23 | } 24 | 25 | @Override 26 | public long getDuration() { 27 | return duration; 28 | } 29 | 30 | @Override 31 | public String getFormattedDuration() { 32 | return Util.formatDuration(duration); 33 | } 34 | 35 | public String getErrorMessage() { 36 | return errorMessage; 37 | } 38 | 39 | public final String getErrorMessageTitle() { 40 | if (errorMessage != null) { 41 | String[] title = errorMessage.split("[\\p{Space}]+"); 42 | if (title.length > 0) { 43 | return title[0]; 44 | } 45 | } 46 | return StringUtils.EMPTY; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/json/Row.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json; 2 | 3 | public class Row { 4 | 5 | // Start: attributes from JSON file report 6 | private final String[] cells = new String[0]; 7 | // End: attributes from JSON file report 8 | 9 | public String[] getCells() { 10 | return cells; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/json/Tag.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json; 2 | 3 | import net.masterthought.cucumber.util.Util; 4 | 5 | public class Tag { 6 | 7 | // Start: attributes from JSON file report 8 | private final String name; 9 | // End: attributes from JSON file report 10 | 11 | public Tag(String name) { 12 | this.name = name; 13 | } 14 | 15 | public String getName() { 16 | return name; 17 | } 18 | 19 | public String getFileName() { 20 | return generateFileName(name); 21 | } 22 | 23 | public static String generateFileName(String tagName) { 24 | return String.format("report-tag_%s.html", Util.toValidFileName(tagName)); 25 | } 26 | 27 | @Override 28 | public int hashCode() { 29 | return name.hashCode(); 30 | } 31 | 32 | @Override 33 | public boolean equals(Object tag) { 34 | // not fully implemented but I don't expect to have different objects here 35 | return ((Tag) tag).name.equals(name); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/json/deserializers/CommentsDeserializer.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json.deserializers; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import net.masterthought.cucumber.Configuration; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class CommentsDeserializer extends CucumberJsonDeserializer> { 10 | 11 | @Override 12 | protected List deserialize(JsonNode rootNode, Configuration configuration) { 13 | List comments = new ArrayList<>(); 14 | for (JsonNode commentNode : rootNode) { 15 | if (commentNode.isTextual()) { 16 | comments.add(commentNode.asText()); 17 | } 18 | if (commentNode.isObject() && commentNode.has("value")) { 19 | comments.add(commentNode.get("value").asText()); 20 | } 21 | } 22 | 23 | return comments; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/json/deserializers/CucumberJsonDeserializer.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json.deserializers; 2 | 3 | import java.io.IOException; 4 | 5 | import com.fasterxml.jackson.core.JsonParser; 6 | import com.fasterxml.jackson.databind.DeserializationContext; 7 | import com.fasterxml.jackson.databind.JsonDeserializer; 8 | import com.fasterxml.jackson.databind.JsonNode; 9 | 10 | import net.masterthought.cucumber.Configuration; 11 | 12 | /** 13 | * Abstract deserializer that extracts {@link Configuration} and passes to 14 | * {@link #deserialize(JsonNode, Configuration)}. 15 | * 16 | * @author Damian Szczepanik (damianszczepanik@github) 17 | */ 18 | abstract class CucumberJsonDeserializer extends JsonDeserializer { 19 | 20 | @Override 21 | public T deserialize(JsonParser parser, DeserializationContext context) 22 | throws IOException { 23 | Configuration configuration = (Configuration) context.findInjectableValue(Configuration.class.getName(), null, 24 | null); 25 | JsonNode rootNode = parser.getCodec().readTree(parser); 26 | 27 | return deserialize(rootNode, configuration); 28 | } 29 | 30 | protected abstract T deserialize(JsonNode rootNode, Configuration configuration) 31 | throws IOException; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/json/deserializers/EmbeddingDeserializer.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json.deserializers; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import net.masterthought.cucumber.Configuration; 5 | import net.masterthought.cucumber.ValidationException; 6 | import net.masterthought.cucumber.json.Embedding; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.nio.file.FileSystems; 11 | import java.nio.file.Files; 12 | import java.nio.file.Path; 13 | import java.util.Base64; 14 | 15 | import static java.nio.charset.StandardCharsets.UTF_8; 16 | 17 | /** 18 | * Deserializes embedding and stores it in attachment directory. 19 | * 20 | * @author Damian Szczepanik (damianszczepanik@github) 21 | */ 22 | public class EmbeddingDeserializer extends CucumberJsonDeserializer { 23 | 24 | @Override 25 | public Embedding deserialize(JsonNode rootNode, Configuration configuration) { 26 | String data = rootNode.get("data").asText(); 27 | String mimeType = findMimeType(rootNode); 28 | 29 | String encodedData = getBase64EncodedData(data); 30 | 31 | Embedding embedding; 32 | String nameField = "name"; 33 | if (rootNode.has(nameField)) { 34 | String name = rootNode.get(nameField).asText(); 35 | embedding = new Embedding(mimeType, encodedData, name); 36 | } else { 37 | embedding = new Embedding(mimeType, encodedData); 38 | } 39 | 40 | storeEmbedding(embedding, configuration.getEmbeddingDirectory()); 41 | 42 | return embedding; 43 | } 44 | 45 | private String getBase64EncodedData(String data) { 46 | try{ 47 | // If we can successfully decode the data we consider it to be base64 encoded, 48 | // so we do not need to do anything here 49 | Base64.getDecoder().decode(data); 50 | return data; 51 | }catch (IllegalArgumentException e){ 52 | // decoding failed, therefore we consider the data not to be encoded, 53 | // so we need to encode it 54 | return new String(Base64.getEncoder().encode(data.getBytes(UTF_8)), UTF_8); 55 | } 56 | } 57 | 58 | private String findMimeType(JsonNode rootNode) { 59 | JsonNode media = rootNode.get("media"); 60 | 61 | if (media != null) { 62 | return media.get("type").asText(); 63 | } 64 | 65 | return rootNode.get("mime_type").asText(); 66 | } 67 | 68 | private void storeEmbedding(Embedding embedding, File embeddingDirectory) { 69 | Path file = FileSystems.getDefault().getPath(embeddingDirectory.getAbsolutePath(), 70 | embedding.getFileId() + "." + embedding.getExtension()); 71 | byte[] decodedData = Base64.getDecoder().decode(embedding.getData().getBytes(UTF_8)); 72 | try { 73 | Files.write(file, decodedData); 74 | } catch (IOException e) { 75 | throw new ValidationException(e); 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/json/deserializers/OutputsDeserializer.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json.deserializers; 2 | 3 | import java.io.IOException; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import com.fasterxml.jackson.databind.JsonNode; 8 | 9 | import net.masterthought.cucumber.Configuration; 10 | import net.masterthought.cucumber.json.Output; 11 | 12 | public class OutputsDeserializer extends CucumberJsonDeserializer { 13 | 14 | @Override 15 | protected Output[] deserialize(JsonNode rootNode, Configuration configuration) throws IOException { 16 | List outputs = new ArrayList<>(); 17 | if (rootNode.get(0).isArray()) { 18 | for (JsonNode outputNode : rootNode) { 19 | outputs.add(getOutput(outputNode)); 20 | } 21 | } else { 22 | outputs.add(getOutput(rootNode)); 23 | } 24 | 25 | return outputs.toArray(new Output[outputs.size()]); 26 | } 27 | 28 | private Output getOutput(JsonNode outputNode) { 29 | List messages = new ArrayList<>(); 30 | for (JsonNode messageNode : outputNode) { 31 | messages.add(messageNode.asText()); 32 | } 33 | return new Output(messages.toArray(new String[messages.size()])); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/json/deserializers/StatusDeserializer.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json.deserializers; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.Locale; 6 | 7 | import com.fasterxml.jackson.databind.JsonNode; 8 | import net.masterthought.cucumber.Configuration; 9 | import net.masterthought.cucumber.json.support.Status; 10 | 11 | /** 12 | * Deserializes Status and maps all known but not supported into UNDEFINED status. 13 | * 14 | * @author Damian Szczepanik (damianszczepanik@github) 15 | */ 16 | public class StatusDeserializer extends CucumberJsonDeserializer { 17 | 18 | // https://github.com/cucumber/cucumber-js/blob/master/lib/cucumber/status.js 19 | static final List UNKNOWN_STATUSES = Arrays.asList("ambiguous"); 20 | 21 | @Override 22 | public Status deserialize(JsonNode rootNode, Configuration configuration) { 23 | 24 | String status = rootNode.asText(); 25 | if (UNKNOWN_STATUSES.contains(status)) { 26 | return Status.UNDEFINED; 27 | } else { 28 | return Status.valueOf(status.toUpperCase(Locale.ENGLISH)); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/json/deserializers/TagsDeserializer.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json.deserializers; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.List; 6 | import java.util.regex.Pattern; 7 | 8 | import com.fasterxml.jackson.databind.JsonNode; 9 | import net.masterthought.cucumber.Configuration; 10 | import net.masterthought.cucumber.json.Tag; 11 | 12 | public class TagsDeserializer extends CucumberJsonDeserializer { 13 | 14 | @Override 15 | protected Tag[] deserialize(JsonNode rootNode, Configuration configuration) { 16 | List tags = new ArrayList<>(); 17 | for (JsonNode tagNode : rootNode) { 18 | String tagName = tagNode.get("name").asText(); 19 | if (shouldIncludeTag(tagName, configuration.getTagsToExcludeFromChart())) { 20 | tags.add(new Tag(tagName)); 21 | } 22 | } 23 | 24 | return tags.toArray(new Tag[tags.size()]); 25 | } 26 | 27 | public boolean shouldIncludeTag(String tagName, Collection tagsToExcludeFromChart) { 28 | for (Pattern pattern : tagsToExcludeFromChart) { 29 | if (tagName.matches(pattern.pattern())) { 30 | return false; 31 | } 32 | } 33 | return true; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/json/support/Argument.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json.support; 2 | 3 | import net.masterthought.cucumber.json.Row; 4 | 5 | /** 6 | * Protractor implementation uses this for the step parameter. 7 | * 8 | * @author Damian Szczepanik (damianszczepanik@github) 9 | */ 10 | public class Argument { 11 | 12 | // Start: attributes from JSON file report 13 | private final Row[] rows = new Row[0]; 14 | private final String val = null; 15 | private final Integer offset = null; 16 | // End: attributes from JSON file report 17 | 18 | public Row[] getRows() { 19 | return rows; 20 | } 21 | 22 | public String getVal() { 23 | return val; 24 | } 25 | 26 | public Integer getOffset() { 27 | return offset; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/json/support/Durationable.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json.support; 2 | 3 | /** 4 | * Ensures that class delivers methods for duration. 5 | * 6 | * @author Damian Szczepanik (damianszczepanik@github) 7 | */ 8 | public interface Durationable { 9 | 10 | /** 11 | * Returns duration for given item. 12 | * 13 | * @return duration 14 | */ 15 | long getDuration(); 16 | 17 | /** 18 | * Returns duration displayed in humanable format. 19 | * 20 | * @return formatted duration 21 | */ 22 | String getFormattedDuration(); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/json/support/Resultsable.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json.support; 2 | 3 | import net.masterthought.cucumber.json.Match; 4 | import net.masterthought.cucumber.json.Output; 5 | import net.masterthought.cucumber.json.Result; 6 | 7 | /** 8 | * Ensures that class delivers method for counting results and matches. 9 | * 10 | * @author Damian Szczepanik (damianszczepanik@github) 11 | * 12 | */ 13 | public interface Resultsable { 14 | 15 | Result getResult(); 16 | 17 | Match getMatch(); 18 | 19 | Output[] getOutputs(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/json/support/Status.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json.support; 2 | 3 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 4 | 5 | import net.masterthought.cucumber.json.deserializers.StatusDeserializer; 6 | 7 | /** 8 | * Defines all possible statuses provided by cucumber library. 9 | * 10 | * @author Damian Szczepanik (damianszczepanik@github) 11 | */ 12 | @JsonDeserialize(using = StatusDeserializer.class) 13 | public enum Status { 14 | 15 | PASSED, 16 | FAILED, 17 | SKIPPED, 18 | PENDING, 19 | UNDEFINED; 20 | 21 | /** 22 | * Returns name of the status converted to lower case characters. 23 | * @return status name as lowercase 24 | */ 25 | public String getRawName() { 26 | return name().toLowerCase(); 27 | } 28 | 29 | /** 30 | * Returns name of the status formatted with first letter to uppercase and lowercase others. 31 | * 32 | * @return status formatted with first letter to uppercase 33 | */ 34 | public String getLabel() { 35 | return name().substring(0, 1).toUpperCase() + name().substring(1).toLowerCase(); 36 | } 37 | 38 | /** 39 | * Returns true if status is equal to {@link #PASSED}. 40 | * 41 | * @return true if the status is PASSED, otherwise false 42 | */ 43 | public boolean isPassed() { 44 | return this == PASSED; 45 | } 46 | } -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/json/support/StatusCounter.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json.support; 2 | 3 | import java.util.Collections; 4 | import java.util.EnumMap; 5 | import java.util.Set; 6 | 7 | /** 8 | * Keeps information about statuses occurrence. 9 | * 10 | * @author Damian Szczepanik (damianszczepanik@github) 11 | * 12 | */ 13 | public class StatusCounter { 14 | 15 | private EnumMap counter = new EnumMap<>(Status.class); 16 | 17 | /** 18 | * Is equal to {@link Status#FAILED} when at least counted status is not {@link Status#PASSED}, 19 | * otherwise set to {@link Status#PASSED}. 20 | */ 21 | private Status finalStatus = Status.PASSED; 22 | 23 | private int size = 0; 24 | 25 | public StatusCounter(Resultsable[] resultsables) { 26 | this(resultsables, Collections.emptySet()); 27 | } 28 | 29 | public StatusCounter(Resultsable[] resultsables, Set notFailingStatuses) { 30 | this(); 31 | for (Resultsable resultsable : resultsables) { 32 | Status status = resultsable.getResult().getStatus(); 33 | if (notFailingStatuses != null && notFailingStatuses.contains(status)) { 34 | incrementFor(Status.PASSED); 35 | } else { 36 | incrementFor(status); 37 | } 38 | } 39 | } 40 | 41 | public StatusCounter() { 42 | for (Status status : Status.values()) { 43 | counter.put(status, 0); 44 | } 45 | } 46 | 47 | /** 48 | * Increments finalStatus counter by single value. 49 | * 50 | * @param status 51 | * finalStatus for which the counter should be incremented. 52 | */ 53 | public void incrementFor(Status status) { 54 | final int statusCounter = getValueFor(status) + 1; 55 | this.counter.put(status, statusCounter); 56 | size++; 57 | 58 | if (finalStatus == Status.PASSED && status != Status.PASSED) { 59 | finalStatus = Status.FAILED; 60 | } 61 | } 62 | 63 | /** 64 | * Gets the number of occurrences for given status. 65 | * 66 | * @param status the status 67 | * @return number of occurrences for given status 68 | */ 69 | public int getValueFor(Status status) { 70 | return this.counter.get(status); 71 | } 72 | 73 | /** 74 | * Gets the sum of all occurrences for all statuses. 75 | * 76 | * @return sum of all occurrences for all statuses 77 | */ 78 | public int size() { 79 | return size; 80 | } 81 | 82 | /** 83 | * If statuses for all items are the same then this finalStatus is returned, otherwise {@link Status#FAILED}. 84 | * 85 | * @return final status for this counter 86 | */ 87 | public Status getFinalStatus() { 88 | return finalStatus; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/json/support/StepObject.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json.support; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | import net.masterthought.cucumber.ValidationException; 6 | import net.masterthought.cucumber.util.Util; 7 | 8 | /** 9 | * Keeps information about steps statistics. 10 | * 11 | * @author Damian Szczepanik (damianszczepanik@github) 12 | * 13 | */ 14 | public class StepObject { 15 | 16 | /** Name of the method / step implementation. This value is unique, there are no two steps with the same locations. */ 17 | public final String location; 18 | 19 | /** Time that was spend to execute all occurrence of this step. */ 20 | private long totalDuration; 21 | 22 | /** How many times this step was executed. */ 23 | private int totalOccurrences; 24 | 25 | /** 26 | * Max occured duration for the step. 27 | */ 28 | private long maxDuration; 29 | 30 | private final StatusCounter statusCounter = new StatusCounter(); 31 | 32 | public StepObject(String location) { 33 | if (StringUtils.isEmpty(location)) { 34 | throw new ValidationException("Location cannnot be null!"); 35 | } 36 | this.location = location; 37 | } 38 | 39 | public String getLocation() { 40 | return location; 41 | } 42 | 43 | public void addDuration(long duration, Status status) { 44 | this.totalDuration += duration; 45 | this.totalOccurrences++; 46 | this.statusCounter.incrementFor(status); 47 | if (duration > maxDuration) { 48 | this.maxDuration = duration; 49 | } 50 | } 51 | 52 | public long getDuration() { 53 | return totalDuration; 54 | } 55 | 56 | public String getFormattedTotalDuration() { 57 | return Util.formatDuration(totalDuration); 58 | } 59 | 60 | public long getAverageDuration() { 61 | return totalDuration / totalOccurrences; 62 | } 63 | 64 | public String getFormattedAverageDuration() { 65 | return Util.formatDuration(getAverageDuration()); 66 | } 67 | 68 | public int getTotalOccurrences() { 69 | return totalOccurrences; 70 | } 71 | 72 | public long getMaxDuration() { 73 | return maxDuration; 74 | } 75 | 76 | public String getFormattedMaxDuration() { 77 | return Util.formatDuration(maxDuration); 78 | } 79 | 80 | /** 81 | * Gets percentage how many steps passed (PASSED / All) formatted to double decimal precision. 82 | * 83 | * @return percentage of passed statuses 84 | */ 85 | public String getPercentageResult() { 86 | int total = 0; 87 | for (Status status : Status.values()) { 88 | total += this.statusCounter.getValueFor(status); 89 | } 90 | return Util.formatAsPercentage(this.statusCounter.getValueFor(Status.PASSED), total); 91 | } 92 | 93 | public Status getStatus() { 94 | return statusCounter.getFinalStatus(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/presentation/PresentationMode.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.presentation; 2 | 3 | /** 4 | * Defines how the report is presented. 5 | * This list contains supported modes which can be used to define how the report is presented. 6 | * 7 | * @author Damian Szczepanik (damianszczepanik@github) 8 | */ 9 | public enum PresentationMode { 10 | 11 | /** 12 | * Defines additional menu buttons that enables integration with Jenkins. 13 | */ 14 | RUN_WITH_JENKINS, 15 | 16 | /** 17 | * Expands all scenarios by default. 18 | */ 19 | EXPAND_ALL_STEPS, 20 | 21 | /** 22 | * Add "target" column to the report, when running the same tests many times. 23 | * Value of this column is same as JSON report file name. 24 | */ 25 | PARALLEL_TESTING 26 | } -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/reducers/ElementComparator.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.reducers; 2 | 3 | import net.masterthought.cucumber.json.Element; 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | import java.util.Comparator; 7 | 8 | /** 9 | * Compares two elements and shows if they have the same Id for scenario type 10 | * or they are on the same line if it's a background. 11 | */ 12 | class ElementComparator implements Comparator { 13 | 14 | /** 15 | * @return comparison result of Ids or line numbers if elements have the same type 16 | * or -1 if type of elements is different. 17 | */ 18 | @Override 19 | public int compare(Element element1, Element element2) { 20 | if (hasSameType(element1, element2)) { 21 | if (element1.isScenario()) { 22 | return Comparator.nullsFirst(String::compareToIgnoreCase) 23 | .compare(element1.getId(), element2.getId()); 24 | } 25 | /* 26 | * Compares non-scenario elements, like Background. 27 | */ 28 | return Comparator.nullsFirst(Integer::compare) 29 | .compare(element1.getLine(), element2.getLine()); 30 | } 31 | return -1; 32 | } 33 | 34 | private boolean hasSameType(Element element1, Element element2) { 35 | return element1 != null && element2 != null && 36 | StringUtils.equalsIgnoreCase(element1.getType(), element2.getType()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/reducers/ReducingMethod.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.reducers; 2 | 3 | /** 4 | * Supported reducing methods. 5 | * This list contains supported methods that allow to modify the way how reports are displayed. 6 | * 7 | * @author Damian Szczepanik (damianszczepanik@github) 8 | */ 9 | public enum ReducingMethod { 10 | 11 | /** 12 | * Merge features with different JSON files that have same ID so scenarios are be stored in single feature. 13 | */ 14 | MERGE_FEATURES_BY_ID, 15 | 16 | /** 17 | * Merge features and scenarios from different JSON files of different runs 18 | * into a single report by features' and scenarios' ids. 19 | * 20 | * Merging rules: 21 | * - Every new feature which is not in the result list is appended to the end. 22 | * 23 | * - When the results list already has a feature with such Id then we go down and apply the rules below to the scenarios: 24 | * 25 | * 1. if there is no scenario with a given Id in the feature's elements list 26 | * then add the scenario to the end of the list. 27 | * 28 | * 2. if there are no scenario with a background (which is a previous element in the elements list) 29 | * then both elements are added to the end of the current feature's elements list. 30 | * As the feature file has a structure like: 31 | * { 32 | * elements: [ 33 | * { 34 | * name: ... 35 | * type: "background"; 36 | * }, 37 | * { 38 | * name: ... 39 | * type: "scenario"; 40 | * }, 41 | * { 42 | * name: ... 43 | * type: "background"; 44 | * }, 45 | * { 46 | * name: ... 47 | * type: "scenario"; 48 | * } 49 | * .... 50 | * ] 51 | * } 52 | * 53 | * 3. if there is a scenario with a given Id then: 54 | * scenario + background case: replace both elements (existing element with Id and its background with new ones) 55 | * scenario only: replace only given scenario by index in the array. 56 | * 57 | * Example: 58 | * Original cucumber report is "cucumber.json". Let's look a situation when couple of tests failed there. 59 | * Cucumber runner generates a new report, for example, cucumber-rerun.json as a result of rerun the failed tests. 60 | * 61 | * In that case you will have a merged report where all failed tests from the original cucumber.json file 62 | * are overridden with the results from the cucumber-rerun.json. 63 | */ 64 | MERGE_FEATURES_WITH_RETEST, 65 | 66 | /** 67 | * Skip empty JSON reports. If this flag is not selected then report generation fails on empty file. 68 | */ 69 | SKIP_EMPTY_JSON_FILES, 70 | 71 | /** 72 | * Does not display hooks (@Before and @After) which do not have attachment or error message. 73 | */ 74 | HIDE_EMPTY_HOOKS 75 | } -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/reducers/ReportFeatureAppendableMerger.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.reducers; 2 | 3 | import net.masterthought.cucumber.json.Feature; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.Optional; 8 | 9 | final class ReportFeatureAppendableMerger implements ReportFeatureMerger { 10 | 11 | @Override 12 | public List merge(List features) { 13 | return Optional.ofNullable(features).orElse(new ArrayList<>()); 14 | } 15 | 16 | @Override 17 | public boolean test(List reducingMethods) { 18 | return true; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/reducers/ReportFeatureByIdMerger.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.reducers; 2 | 3 | import net.masterthought.cucumber.json.Feature; 4 | 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | final class ReportFeatureByIdMerger implements ReportFeatureMerger { 11 | 12 | @Override 13 | public List merge(List features) { 14 | Map mergedFeatures = new HashMap<>(); 15 | for (Feature feature : features) { 16 | Feature mergedFeature = mergedFeatures.get(feature.getId()); 17 | if (mergedFeature == null) { 18 | mergedFeatures.put(feature.getId(), feature); 19 | } else { 20 | mergedFeatures.get(feature.getId()).addElements(feature.getElements()); 21 | } 22 | } 23 | return new ArrayList<>(mergedFeatures.values()); 24 | } 25 | 26 | @Override 27 | public boolean test(List reducingMethods) { 28 | return reducingMethods != null && reducingMethods.contains(ReducingMethod.MERGE_FEATURES_BY_ID); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/reducers/ReportFeatureMerger.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.reducers; 2 | 3 | import java.util.List; 4 | import java.util.function.Predicate; 5 | 6 | import net.masterthought.cucumber.json.Feature; 7 | 8 | public interface ReportFeatureMerger extends Predicate> { 9 | 10 | /** 11 | * Merger's type depends on a ReducingMethod which is coming from the configuration. 12 | * 13 | * @param features features for merger 14 | * @return list of features which are organized by merger. 15 | * @see ReportFeatureAppendableMerger 16 | * @see ReportFeatureByIdMerger 17 | * @see ReportFeatureWithRetestMerger 18 | */ 19 | List merge(List features); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/reducers/ReportFeatureMergerFactory.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.reducers; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.Optional; 6 | 7 | import static java.util.Collections.emptyList; 8 | 9 | public final class ReportFeatureMergerFactory { 10 | 11 | private List mergers = Arrays.asList( 12 | new ReportFeatureByIdMerger(), 13 | new ReportFeatureWithRetestMerger() 14 | ); 15 | 16 | /** 17 | * @param reducingMethods - full list of reduce methods. 18 | * @return a merger for features by ReduceMethod with a priority mentioned in the method. 19 | */ 20 | public ReportFeatureMerger get(List reducingMethods) { 21 | List methods = Optional.ofNullable(reducingMethods).orElse(emptyList()); 22 | return mergers.stream() 23 | .filter(m -> m.test(methods)) 24 | .findFirst() 25 | .orElse(new ReportFeatureAppendableMerger()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/sorting/FeaturesAlphabeticalComparator.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.sorting; 2 | 3 | import java.util.Comparator; 4 | 5 | import org.apache.commons.lang3.ObjectUtils; 6 | 7 | import net.masterthought.cucumber.json.Feature; 8 | 9 | /** 10 | * @author Damian Szczepanik (damianszczepanik@github) 11 | */ 12 | public class FeaturesAlphabeticalComparator implements Comparator { 13 | 14 | @Override 15 | public int compare(Feature feature1, Feature feature2) { 16 | // order by the name so first compare by the name 17 | int nameCompare = ObjectUtils.compare(feature1.getName(), feature2.getName()); 18 | if (nameCompare != 0) { 19 | return nameCompare; 20 | } 21 | 22 | // if names are the same, compare by the ID which should be unieque by JSON file 23 | int idCompare = ObjectUtils.compare(feature1.getId(), feature2.getId()); 24 | if (idCompare != 0) { 25 | return idCompare; 26 | } 27 | 28 | // if ids are the same it means that feature exists in more than one JSON file so compare by JSON report 29 | return ObjectUtils.compare(feature1.getReportFileName(), feature2.getReportFileName()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/sorting/SortingFactory.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.sorting; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.Collections; 6 | import java.util.Comparator; 7 | import java.util.List; 8 | 9 | import net.masterthought.cucumber.json.Feature; 10 | import net.masterthought.cucumber.json.support.StepObject; 11 | import net.masterthought.cucumber.json.support.TagObject; 12 | 13 | /** 14 | * Keeps references to classes that sort results. 15 | * 16 | * @author Damian Szczepanik (damianszczepanik@github) 17 | */ 18 | public final class SortingFactory { 19 | 20 | private final SortingMethod sortingMethod; 21 | 22 | public SortingFactory(SortingMethod sortingMethod) { 23 | this.sortingMethod = sortingMethod; 24 | } 25 | 26 | public List sortFeatures(Collection features) { 27 | switch (sortingMethod) { 28 | case NATURAL: 29 | return new ArrayList<>(features); 30 | case ALPHABETICAL: 31 | return toSortedList(features, new FeaturesAlphabeticalComparator()); 32 | default: 33 | throw createUnknownMethodException(sortingMethod); 34 | } 35 | } 36 | 37 | public List sortTags(Collection tags) { 38 | switch (sortingMethod) { 39 | case NATURAL: 40 | return new ArrayList<>(tags); 41 | case ALPHABETICAL: 42 | return toSortedList(tags, new TagObjectAlphabeticalComparator()); 43 | default: 44 | throw createUnknownMethodException(sortingMethod); 45 | } 46 | } 47 | 48 | public List sortSteps(Collection steps) { 49 | switch (sortingMethod) { 50 | case NATURAL: 51 | return new ArrayList<>(steps); 52 | case ALPHABETICAL: 53 | return toSortedList(steps, new StepObjectAlphabeticalComparator()); 54 | default: 55 | throw createUnknownMethodException(sortingMethod); 56 | } 57 | } 58 | 59 | private static List toSortedList(Collection values, Comparator comparator) { 60 | List list = new ArrayList<>(values); 61 | Collections.sort(list, comparator); 62 | return list; 63 | } 64 | 65 | private RuntimeException createUnknownMethodException(SortingMethod sortingMethod) { 66 | return new IllegalArgumentException("Unsupported sorting method: " + sortingMethod); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/sorting/SortingMethod.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.sorting; 2 | 3 | /** 4 | * Supported sorting methods. 5 | * 6 | * @author Damian Szczepanik (damianszczepanik@github) 7 | */ 8 | public enum SortingMethod { 9 | 10 | /** 11 | * Same order as in JSON file. 12 | */ 13 | NATURAL, 14 | /** 15 | * Order by name of the element. 16 | */ 17 | ALPHABETICAL 18 | } -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/sorting/StepObjectAlphabeticalComparator.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.sorting; 2 | 3 | import java.util.Comparator; 4 | 5 | import net.masterthought.cucumber.json.support.StepObject; 6 | 7 | /** 8 | * @author Damian Szczepanik (damianszczepanik@github) 9 | */ 10 | public class StepObjectAlphabeticalComparator implements Comparator { 11 | 12 | @Override 13 | public int compare(StepObject stepObject1, StepObject stepObject2) { 14 | // since there might be the only one StepObject with given location, compare by location only 15 | return Integer.signum(stepObject1.getLocation().compareTo(stepObject2.getLocation())); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/sorting/TagObjectAlphabeticalComparator.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.sorting; 2 | 3 | import java.util.Comparator; 4 | 5 | import net.masterthought.cucumber.json.support.TagObject; 6 | 7 | /** 8 | * @author Damian Szczepanik (damianszczepanik@github) 9 | */ 10 | public class TagObjectAlphabeticalComparator implements Comparator { 11 | 12 | @Override 13 | public int compare(TagObject tagObject1, TagObject tagObject2) { 14 | // since there might be the only one TagObject with given tagName, compare by location only 15 | return Integer.signum(tagObject1.getName().compareTo(tagObject2.getName())); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/util/Counter.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.util; 2 | 3 | import org.apache.commons.lang3.mutable.MutableInt; 4 | 5 | /** 6 | * Simple counter to give elements on a page a unique ID. Using object hashes 7 | * doesn't guarantee uniqueness. 8 | */ 9 | public class Counter extends MutableInt { 10 | /** 11 | * @return The next integer 12 | */ 13 | public int next() { 14 | increment(); 15 | return intValue(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/net/masterthought/cucumber/util/StepNameFormatter.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.util; 2 | 3 | import org.apache.commons.text.StringEscapeUtils; 4 | import org.apache.commons.lang3.ArrayUtils; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | import net.masterthought.cucumber.json.support.Argument; 8 | 9 | public class StepNameFormatter { 10 | 11 | public static final StepNameFormatter INSTANCE = new StepNameFormatter(); 12 | 13 | public static String format(String stepName, Argument[] arguments, String preArgument, String postArgument) { 14 | if (ArrayUtils.isEmpty(arguments)) { 15 | return StringEscapeUtils.escapeHtml4(stepName); 16 | } 17 | 18 | String[] chars = splitIntoCharacters(stepName); 19 | 20 | escape(chars); 21 | surroundArguments(arguments, preArgument, postArgument, chars); 22 | 23 | return StringUtils.join(chars); 24 | } 25 | 26 | /** 27 | * Splits a string into an array of individual characters (each a String). 28 | * splitIntoCharacters("Text") = ["T", "e", "x", "t"] 29 | */ 30 | private static String[] splitIntoCharacters(String str) { 31 | return str.split("(?!^)"); 32 | } 33 | 34 | private static void surroundArguments(Argument[] arguments, String preArgument, String postArgument, String[] chars) { 35 | for (Argument argument : arguments) { 36 | if (!isValidArgument(argument)) { 37 | continue; 38 | } 39 | 40 | int start = argument.getOffset(); 41 | int end = start + argument.getVal().length() - 1; 42 | 43 | chars[start] = preArgument + chars[start]; 44 | chars[end] = chars[end] + postArgument; 45 | } 46 | } 47 | 48 | private static boolean isValidArgument(Argument argument) { 49 | return argument.getOffset() != null && argument.getVal().length() > 0; 50 | } 51 | 52 | private static void escape(String[] chars) { 53 | for (int i = 0; i < chars.length; i++) { 54 | chars[i] = StringEscapeUtils.escapeHtml4(chars[i]); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/resources/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianszczepanik/cucumber-reporting/39b99d3634b17d021ac42143dd4283e02a629036/src/main/resources/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /src/main/resources/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianszczepanik/cucumber-reporting/39b99d3634b17d021ac42143dd4283e02a629036/src/main/resources/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /src/main/resources/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianszczepanik/cucumber-reporting/39b99d3634b17d021ac42143dd4283e02a629036/src/main/resources/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /src/main/resources/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianszczepanik/cucumber-reporting/39b99d3634b17d021ac42143dd4283e02a629036/src/main/resources/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /src/main/resources/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianszczepanik/cucumber-reporting/39b99d3634b17d021ac42143dd4283e02a629036/src/main/resources/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /src/main/resources/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianszczepanik/cucumber-reporting/39b99d3634b17d021ac42143dd4283e02a629036/src/main/resources/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/main/resources/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianszczepanik/cucumber-reporting/39b99d3634b17d021ac42143dd4283e02a629036/src/main/resources/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/main/resources/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianszczepanik/cucumber-reporting/39b99d3634b17d021ac42143dd4283e02a629036/src/main/resources/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /src/main/resources/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianszczepanik/cucumber-reporting/39b99d3634b17d021ac42143dd4283e02a629036/src/main/resources/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/main/resources/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianszczepanik/cucumber-reporting/39b99d3634b17d021ac42143dd4283e02a629036/src/main/resources/images/favicon.png -------------------------------------------------------------------------------- /src/main/resources/templates/footer.vm: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /src/main/resources/templates/generators/errorpage.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #parse("/templates/headers.vm") 5 | #includeTitle("Error Page") 6 | 7 | 15 | 16 | 17 | 18 | 19 | #includeNavigation() 20 | 21 | #includeReportInfo() 22 | 23 | #includeLead("Error", "Something went wrong with project $build_project_name, build $build_number") 24 | 25 |
26 |
27 |
28 |
29 |
$output_message
30 |
31 |
Trying to generate report from following files. Make sure they are valid cucumber report files:
32 |
33 |
34 |           #foreach($file in $json_files) $file
35 |           #end
36 |         
37 |
38 |
39 |
40 |
41 | 42 | 43 | #include("/templates/footer.vm") 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/main/resources/templates/generators/overviewFailures.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #parse("/templates/headers.vm") 5 | #includeTitle("Failures Overview") 6 | 7 | 8 | 9 | 10 | #includeNavigation("failures") 11 | 12 | #includeReportInfo() 13 | 14 | #includeLead("Failures Overview", "The following summary displays scenarios that failed.") 15 | 16 | #if(!$failures.isEmpty()) 17 | #includeExpandingButtons() 18 | #end 19 | 20 |
21 |
22 |
23 | 24 | #if($failures.isEmpty()) 25 |

You have no failed scenarios in your Cucumber report

26 | #else 27 |
28 | #foreach($element in $failures) 29 | #includeElement($element, true) 30 | #end 31 |
32 | #end 33 | 34 |
35 |
36 |
37 | 38 | #if(!$failures.isEmpty()) 39 | #includeExpandingButtons() 40 | #end 41 |
42 | 43 | #include("/templates/footer.vm") 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/main/resources/templates/generators/overviewFeatures.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #parse("/templates/headers.vm") 5 | #includeTitle("Features Overview") 6 | 7 | 12 | 13 | 14 | 15 | 16 | #includeNavigation("features") 17 | 18 | #includeReportInfo() 19 | 20 | #includeLead("Features Statistics", "The following graphs show passing and failing statistics for features") 21 | 22 | 23 | #if(!$all_features.isEmpty()) 24 |
25 |
26 |
27 | 60 |
61 |
62 |
63 |
64 | #end 65 | 66 | 67 |
68 |
69 |
70 | #includeStatsTable("Feature", $all_features, $report_summary) 71 |
72 |
73 |
74 | 75 | #include("/templates/footer.vm") 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/main/resources/templates/generators/overviewSteps.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #parse("/templates/headers.vm") 5 | #includeTitle("Steps Overview") 6 | 7 | 8 | 9 | #includeNavigation("steps") 10 | 11 | #includeReportInfo() 12 | 13 | #includeLead("Steps Statistics", "The following graph shows step statistics for this build. Below list is based on results. 14 | step does not provide information about result then is not listed below. 15 | Additionally @Before and @After are not counted because they are part of the scenarios, not steps.") 16 | 17 |
18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | #foreach($step in $all_steps) 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | #end 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
ImplementationOccurrencesAverage durationMax durationTotal durationsRatio
$step.getLocation()$step.getTotalOccurrences()$step.getFormattedAverageDuration()$step.getFormattedMaxDuration()$step.getFormattedTotalDuration()$step.getPercentageResult()
$all_steps.size()$all_occurrences$all_average_duration$all_max_duration$all_durationsTotals
58 |
59 |
60 |
61 | 62 | 63 | #include("/templates/footer.vm") 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/main/resources/templates/generators/overviewTags.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #parse("/templates/headers.vm") 5 | #includeTitle("Tags Overview") 6 | 7 | 10 | 11 | 12 | 13 | #includeNavigation("tags") 14 | 15 | #includeReportInfo() 16 | 17 | #includeLead("Tags Statistics", "The following graph shows passing and failing statistics for tags") 18 | 19 | 20 | #if(!$all_tags.isEmpty()) 21 |
22 |
23 |
24 | 25 |
26 |
27 |
28 | #end 29 | 30 |
31 |
32 |
33 |
34 | #if($all_tags.isEmpty()) 35 |

You have no tags in your cucumber report

36 | #else 37 | #includeStatsTable("Tag", $all_tags, $report_summary) 38 | #end 39 |
40 |
41 |
42 | 43 | #include("/templates/footer.vm") 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/main/resources/templates/generators/overviewTrends.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #parse("/templates/headers.vm") 5 | #includeTitle("Trends Overview") 6 | 7 | 10 | 11 | 12 | 13 | #includeNavigation("trends") 14 | 15 | #includeReportInfo() 16 | 17 | #includeLead("Trends Statistics", "The following graph shows features, scenarios and steps for a period of time.") 18 | 19 |
20 |
21 |
22 |

Features:

23 | 24 |
25 | 26 |

Scenarios:

27 | 28 |
29 | 30 |

Steps:

31 | 32 |
33 | 34 |

Durations:

35 | 36 |
37 |
38 |
39 |
40 | 41 | #include("/templates/footer.vm") 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/main/resources/templates/generators/reportFeature.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #parse("/templates/headers.vm") 5 | #includeTitle("Feature: $feature.getName()") 6 | 7 | 8 | 9 | #includeNavigation() 10 | 11 | #includeReportInfo() 12 | 13 | #includeLead("Feature Report") 14 | 15 | #includeReportTable("Feature", $feature) 16 | 17 | #includeExpandingButtons() 18 | 19 |
20 |
21 |
22 |
23 | #includeTags($feature.getTags()) 24 | #includeBrief($feature.getKeyword(), $feature.getStatus(), $feature.getName()) 25 |
$feature.getDescription()
26 | 27 |
28 | #foreach($element in $feature.getElements()) 29 | #includeElement($element, false) 30 | #end 31 |
32 | 33 |
34 |
35 |
36 |
37 | 38 | #includeExpandingButtons() 39 |
40 | 41 | #include("/templates/footer.vm") 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/main/resources/templates/generators/reportTag.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #parse("/templates/headers.vm") 5 | #includeTitle("Tag: $tag.getName()") 6 | 7 | 8 | 9 | #includeNavigation() 10 | 11 | #includeReportInfo() 12 | 13 | #includeLead("Tag Report") 14 | 15 | #includeReportTable("Tag", $tag, false) 16 | 17 | #includeExpandingButtons() 18 | 19 |
20 |
21 |
22 |
23 | 24 | #foreach($element in $tag.getElements()) 25 | #includeElement($element, true) 26 | #end 27 | 28 |
29 |
30 |
31 |
32 | 33 | #includeExpandingButtons() 34 |
35 | 36 | #include("/templates/footer.vm") 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/main/resources/templates/head.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | #foreach($jsFile in $js_files) 9 | 10 | #end 11 | 12 | 13 | 14 | 15 | 16 | #foreach($cssFile in $css_files) 17 | 18 | #end 19 | 20 | 35 | -------------------------------------------------------------------------------- /src/main/resources/templates/headers.vm: -------------------------------------------------------------------------------- 1 | 2 | #parse("/templates/head.vm") 3 | 4 | #parse("/templates/macros/array.js.vm") 5 | 6 | #parse("/templates/macros/page/buildinfo.vm") 7 | #parse("/templates/macros/page/classifications.vm") 8 | #parse("/templates/macros/page/lead.vm") 9 | #parse("/templates/macros/page/navigation.vm") 10 | #parse("/templates/macros/page/reportInfo.vm") 11 | #parse("/templates/macros/page/title.vm") 12 | 13 | #parse("/templates/macros/report/expandAllButtons.vm") 14 | #parse("/templates/macros/report/reportTable.vm") 15 | #parse("/templates/macros/report/reportHeader.vm") 16 | #parse("/templates/macros/report/statsTable.vm") 17 | 18 | #parse("/templates/macros/json/brief.vm") 19 | #parse("/templates/macros/json/docstring.vm") 20 | #parse("/templates/macros/json/duration.vm") 21 | #parse("/templates/macros/json/element.vm") 22 | #parse("/templates/macros/json/embeddings.vm") 23 | #parse("/templates/macros/json/hooks.vm") 24 | #parse("/templates/macros/json/output.vm") 25 | #parse("/templates/macros/json/message.vm") 26 | #parse("/templates/macros/json/steps.vm") 27 | #parse("/templates/macros/json/stepName.vm") 28 | #parse("/templates/macros/json/tags.vm") 29 | -------------------------------------------------------------------------------- /src/main/resources/templates/js/features-chart.js.vm: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | var chartData = { 4 | datasets: [{ 5 | data: [ 6 | $report_summary.getPassedFeatures(), 7 | $report_summary.getFailedFeatures() 8 | ], 9 | backgroundColor: [ 10 | "#00B000", 11 | "#FF3030" 12 | ] 13 | }], 14 | labels: [ 15 | "Passed", 16 | "Failed" 17 | ] 18 | }; 19 | 20 | var context = document.getElementById("features-chart"); 21 | window.myBar = new Chart(context, { 22 | type: "doughnut", 23 | data: chartData, 24 | options: { 25 | title: { 26 | display: true, 27 | fontSize: 20, 28 | text: "Features" 29 | }, 30 | responsive: true, 31 | legend: { 32 | display: false 33 | }, 34 | tooltips: { 35 | callbacks: { 36 | label: function(tooltipItem, data) { 37 | var allData = data.datasets[tooltipItem.datasetIndex].data; 38 | var tooltipLabel = data.labels[tooltipItem.index]; 39 | var tooltipData = allData[tooltipItem.index]; 40 | var tooltipPercentage = Math.round(10000 * tooltipData / $all_features.size()) / 100; 41 | return tooltipLabel + ": " + tooltipData + " (" + tooltipPercentage + "%)"; 42 | } 43 | } 44 | } 45 | } 46 | }); 47 | 48 | }); -------------------------------------------------------------------------------- /src/main/resources/templates/js/scenarios-chart.js.vm: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | var chartData = { 4 | datasets: [{ 5 | data: [ 6 | $report_summary.getPassedScenarios(), 7 | $report_summary.getFailedScenarios() 8 | ], 9 | backgroundColor: [ 10 | "#00B000", 11 | "#FF3030" 12 | ] 13 | }], 14 | labels: [ 15 | "Passed", 16 | "Failed" 17 | ] 18 | }; 19 | 20 | var context = document.getElementById("scenarios-chart"); 21 | window.myBar = new Chart(context, { 22 | type: "doughnut", 23 | data: chartData, 24 | options: { 25 | title: { 26 | display: true, 27 | fontSize: 20, 28 | text: "Scenarios" 29 | }, 30 | responsive: true, 31 | legend: { 32 | display: false 33 | }, 34 | tooltips: { 35 | callbacks: { 36 | label: function(tooltipItem, data) { 37 | var allData = data.datasets[tooltipItem.datasetIndex].data; 38 | var tooltipLabel = data.labels[tooltipItem.index]; 39 | var tooltipData = allData[tooltipItem.index]; 40 | var tooltipPercentage = Math.round(10000 * tooltipData / $report_summary.getScenarios()) / 100; 41 | return tooltipLabel + ": " + tooltipData + " (" + tooltipPercentage + "%)"; 42 | } 43 | } 44 | } 45 | } 46 | }); 47 | 48 | }); -------------------------------------------------------------------------------- /src/main/resources/templates/js/steps-chart.js.vm: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | var chartData = { 4 | datasets: [{ 5 | data: [ 6 | $report_summary.getPassedSteps(), 7 | $report_summary.getFailedSteps(), 8 | $report_summary.getSkippedSteps(), 9 | $report_summary.getPendingSteps(), 10 | $report_summary.getUndefinedSteps() 11 | ], 12 | backgroundColor: [ 13 | "#00B000", 14 | "#FF3030", 15 | "#88AAFF", 16 | "#F5F28F", 17 | "#F5B975" 18 | ] 19 | }], 20 | labels: [ 21 | "Passed", 22 | "Failed", 23 | "Skipped", 24 | "Pending", 25 | "Undefined" 26 | ] 27 | }; 28 | 29 | var context = document.getElementById("steps-chart"); 30 | window.myBar = new Chart(context, { 31 | type: "doughnut", 32 | data: chartData, 33 | options: { 34 | title: { 35 | display: true, 36 | fontSize: 20, 37 | text: "Steps" 38 | }, 39 | responsive: true, 40 | legend: { 41 | display: false 42 | }, 43 | tooltips: { 44 | callbacks: { 45 | label: function(tooltipItem, data) { 46 | var allData = data.datasets[tooltipItem.datasetIndex].data; 47 | var tooltipLabel = data.labels[tooltipItem.index]; 48 | var tooltipData = allData[tooltipItem.index]; 49 | var tooltipPercentage = Math.round(10000 * tooltipData / $report_summary.getSteps()) / 100; 50 | return tooltipLabel + ": " + tooltipData + " (" + tooltipPercentage + "%)"; 51 | } 52 | } 53 | } 54 | } 55 | }); 56 | 57 | }); -------------------------------------------------------------------------------- /src/main/resources/templates/js/tags-chart.js.vm: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | var chartData = { 4 | labels: #stringArray($chart_categories), 5 | datasets: [ 6 | { 7 | label: "Passed", 8 | backgroundColor: "#92DD96", 9 | data: #numberArray($chart_data[0]) 10 | }, 11 | { 12 | label: "Failed", 13 | backgroundColor: "#F2928C", 14 | data: #numberArray($chart_data[1]) 15 | }, 16 | { 17 | label: "Skipped", 18 | backgroundColor: "#8AF", 19 | data: #numberArray($chart_data[2]) 20 | }, 21 | { 22 | label: "Pending", 23 | backgroundColor: "#F5F28F", 24 | data: #numberArray($chart_data[3]) 25 | }, 26 | { 27 | label: "Undefined", 28 | backgroundColor: "#F5B975", 29 | data: #numberArray($chart_data[4]) 30 | } 31 | ] 32 | }; 33 | 34 | var context = document.getElementById("tags-chart"); 35 | window.myBar = new Chart(context, { 36 | type: "bar", 37 | data: chartData, 38 | options: { 39 | tooltips: { 40 | mode: "label" 41 | }, 42 | responsive: true, 43 | legend: { 44 | display: false 45 | }, 46 | scales: { 47 | xAxes: [{ 48 | stacked: true 49 | }], 50 | yAxes: [{ 51 | stacked: true 52 | }] 53 | } 54 | } 55 | }); 56 | 57 | }); -------------------------------------------------------------------------------- /src/main/resources/templates/macros/array.js.vm: -------------------------------------------------------------------------------- 1 | #macro(stringArray $array) [#foreach($element in $array) "$element", #end] #end 2 | #macro(numberArray $array) [#foreach($element in $array) $element, #end] #end -------------------------------------------------------------------------------- /src/main/resources/templates/macros/json/brief.vm: -------------------------------------------------------------------------------- 1 | #macro(includeBrief, $keyword, $status, $keyword_value, $expandable, $result) 2 | 3 |
4 | $keyword 5 | $keyword_value 6 | #includeDuration($result) 7 | #if ($expandable) 8 | 9 | #end 10 |
11 | 12 | #end 13 | -------------------------------------------------------------------------------- /src/main/resources/templates/macros/json/docstring.vm: -------------------------------------------------------------------------------- 1 | #macro(includeDocString, $docString) 2 | 3 | #if ($docString) 4 |
5 |
6 | #set($docStringId = $counter.next()) 7 |
8 | Doc string 9 |
10 |
11 |
$docString.getValue()
12 |
13 |
14 |
15 | #end 16 | 17 | #end 18 | -------------------------------------------------------------------------------- /src/main/resources/templates/macros/json/duration.vm: -------------------------------------------------------------------------------- 1 | #macro(includeDuration, $durationable) 2 | 3 | #if ($durationable) 4 | $durationable.getFormattedDuration() 5 | #end 6 | 7 | #end 8 | -------------------------------------------------------------------------------- /src/main/resources/templates/macros/json/element.vm: -------------------------------------------------------------------------------- 1 | #macro(includeElement, $element, $linkToFeature) 2 | 3 |
4 | #if ($linkToFeature) 5 |
6 | Feature: 7 | $element.getFeature().getName() 8 |
9 | #end 10 | 11 | #includeTags($element.getTags()) 12 | #includeDuration($element) 13 | 14 | #set($elementId = $counter.next()) 15 | 18 |
$element.getDescription()
19 | 20 |
21 | #includeHooks("Before", $element.getBefore(), $element.getBeforeStatus(), "element") 22 | 23 | #includeSteps($element.getSteps()) 24 | 25 | #includeHooks("After", $element.getAfter(), $element.getAfterStatus(), "element") 26 |
27 |
28 | 29 | #end 30 | -------------------------------------------------------------------------------- /src/main/resources/templates/macros/json/hooks.vm: -------------------------------------------------------------------------------- 1 | #macro(includeHooks $keyword, $hooks, $status, $level) 2 | 3 | #if($hide_empty_hooks) 4 | #set($hooks = $util.eliminateEmptyHooks($hooks)) 5 | #end 6 | 7 | #if(!$hooks.isEmpty()) 8 |
9 | #set($hookId = $counter.next()) 10 | 13 | 14 |
15 | #foreach($hook in $hooks) 16 |
17 |
18 | $keyword 19 | #if ($hook.getMatch()) 20 | $hook.getMatch().getLocation() 21 | #end 22 | #includeDuration($hook.getResult()) 23 |
24 | 25 | #if ($hook.getResult()) 26 | #includeMessage("Error message", $hook.getResult().getErrorMessage()) 27 | #end 28 | 29 | #set($isPassed = $hook.getResult().getStatus().isPassed()) 30 | #includeOutput($hook.getOutputs(), $isPassed) 31 | #includeEmbeddings($hook.getEmbeddings()) 32 |
33 | #end 34 |
35 |
36 | #end 37 | 38 | #end 39 | -------------------------------------------------------------------------------- /src/main/resources/templates/macros/json/message.vm: -------------------------------------------------------------------------------- 1 | #macro(includeMessage, $messageName, $message, $isPassed) 2 | 3 | #if ($message) 4 | #set($msgId = $counter.next()) 5 | 6 |
7 |
8 | 11 |
12 |
$message
13 |
14 |
15 |
16 | #end 17 | 18 | #end 19 | -------------------------------------------------------------------------------- /src/main/resources/templates/macros/json/output.vm: -------------------------------------------------------------------------------- 1 | #macro(includeOutput $outputs, $isPassed) 2 | 3 | #if(!$outputs.isEmpty()) 4 |
5 | #foreach($output in $outputs) 6 | #set($outputId = $counter.next()) 7 | #set($index = $foreach.index + 1) 8 |
9 | 13 |
14 | #** 15 | * DO NOT format the line below. Whitespace nodes are significant in a pre-block. 16 | *# 17 |
#foreach($message in $output.getMessages())

$message

#end
18 |
19 |
20 | #end 21 |
22 | #end 23 | #end 24 | -------------------------------------------------------------------------------- /src/main/resources/templates/macros/json/stepName.vm: -------------------------------------------------------------------------------- 1 | #macro(includeStepName, $keyword, $name, $args, $status, $result) 2 | 3 | #set($_noescape_stepName = $stepNameFormatter.format($name, $args, '', '')) 4 | #set($stepNameClass = "step-name-$keyword") 5 | 6 |
7 | $keyword 8 | $_noescape_stepName 9 | #includeDuration($result) 10 |
11 | 12 | #end 13 | -------------------------------------------------------------------------------- /src/main/resources/templates/macros/json/steps.vm: -------------------------------------------------------------------------------- 1 | #macro(includeSteps, $steps) 2 | 3 |
4 | #set($stepsId = $counter.next()) 5 | 8 | 9 |
10 | #foreach($step in $element.getSteps()) 11 |
12 | #foreach($comment in $step.getComments()) 13 |
$comment
14 | #end 15 | #includeStepName($step.getKeyword(), $step.getName(), $step.getMatch().getArguments(), $step.getResult().getStatus(), $step.getResult()) 16 | #set($isPassed = $step.getResult().getStatus().isPassed()) 17 | #includeHooks("Before", $step.getBefore(), $step.getBeforeStatus(), "step") 18 | #includeMessage($step.getResult().getErrorMessageTitle(), $step.getResult().getErrorMessage(), $isPassed) 19 | 20 | #if (!$step.getRows().isEmpty()) 21 | 22 | #foreach($row in $step.getRows()) 23 | 24 | #foreach($cell in $row.getCells()) 25 | 26 | #end 27 | 28 | #end 29 |
$cell
30 | #end 31 | 32 | #includeDocString($step.getDocString()) 33 | #includeOutput($step.getOutputs(), $isPassed) 34 | #includeEmbeddings($step.getEmbeddings()) 35 | #includeHooks("After", $step.getAfter(), $step.getAfterStatus(), "step") 36 |
37 | #end 38 |
39 |
40 | 41 | #end 42 | -------------------------------------------------------------------------------- /src/main/resources/templates/macros/json/tags.vm: -------------------------------------------------------------------------------- 1 | #macro(includeTags, $tags) 2 | 3 |
4 | #if (!$tags.isEmpty()) 5 | Tags: 6 | #foreach($tag in $tags) 7 | $tag.getName() 8 | #end 9 | #end 10 |
11 | 12 | #end 13 | -------------------------------------------------------------------------------- /src/main/resources/templates/macros/page/buildinfo.vm: -------------------------------------------------------------------------------- 1 | #macro(includeBuildInfo) 2 | 3 | 4 | 5 | 6 | 7 | #if($build_number) 8 | 9 | #end 10 | 11 | 12 | 13 | 14 | 15 | 16 | #if($build_number) 17 | 18 | #end 19 | 20 | 21 | 22 |
ProjectNumberDate
$build_project_name$build_number$build_time
23 | 24 | #end 25 | -------------------------------------------------------------------------------- /src/main/resources/templates/macros/page/classifications.vm: -------------------------------------------------------------------------------- 1 | #macro(includeClassifications) 2 | 3 | 4 | 5 | #foreach($classification in $classifications) 6 | #set($key = $classification.getKey()) 7 | #set($_sanitize_value = $classification.getValue()) 8 | 9 | 10 | 11 | 12 | #end 13 | 14 |
$key$_sanitize_value
15 | 16 | #end 17 | -------------------------------------------------------------------------------- /src/main/resources/templates/macros/page/lead.vm: -------------------------------------------------------------------------------- 1 | #macro(includeLead, $page_title, $page_description) 2 |
3 |
4 |

$page_title

5 | #if($page_description) 6 |

$page_description

7 | #end 8 |
9 |
10 | #end 11 | -------------------------------------------------------------------------------- /src/main/resources/templates/macros/page/navigation.vm: -------------------------------------------------------------------------------- 1 | #macro(includeNavigation, $active_tab) 2 | 25 | #end 26 | -------------------------------------------------------------------------------- /src/main/resources/templates/macros/page/reportInfo.vm: -------------------------------------------------------------------------------- 1 | #macro(includeReportInfo) 2 | 3 |
4 |
5 | #includeBuildInfo() 6 |
7 |
8 | #if ($classifications && !$classifications.isEmpty()) 9 | #includeClassifications() 10 | #end 11 |
12 |
13 | 14 | #end 15 | -------------------------------------------------------------------------------- /src/main/resources/templates/macros/page/title.vm: -------------------------------------------------------------------------------- 1 | #macro(includeTitle, $reportName) 2 | 3 | #set($buildNo = "") 4 | #if($build_number) 5 | #set($buildNo = "(no $build_number)") 6 | #end 7 | Cucumber Reports $buildNo - $reportName 8 | 9 | #end 10 | -------------------------------------------------------------------------------- /src/main/resources/templates/macros/report/expandAllButtons.vm: -------------------------------------------------------------------------------- 1 | #macro(includeExpandingButtons) 2 | 3 |
4 |
5 |
6 | 8 | 10 |
11 |
12 |
13 | 14 | #end 15 | -------------------------------------------------------------------------------- /src/main/resources/templates/macros/report/reportHeader.vm: -------------------------------------------------------------------------------- 1 | #macro(includeReportHeader, $table_key) 2 | 3 | 4 | 5 | #if ($parallel_testing) 6 | 7 | #else 8 | 9 | #end 10 | Steps 11 | Scenarios 12 | Features 13 | 14 | 15 | $table_key 16 | #if ($parallel_testing) 17 | Qualifier 18 | #end 19 | Passed 20 | Failed 21 | Skipped 22 | Pending 23 | Undefined 24 | Total 25 | 26 | Passed 27 | Failed 28 | Total 29 | 30 | Duration 31 | Status 32 | 33 | 34 | 35 | #end 36 | -------------------------------------------------------------------------------- /src/main/resources/templates/macros/report/reportTable.vm: -------------------------------------------------------------------------------- 1 | #macro(includeReportTable, $table_key, $reportable) 2 | 3 |
4 |
5 |
6 | 7 | 8 | #includeReportHeader($table_key) 9 | 10 | 11 | 12 | 13 | #if ($parallel_testing) 14 | 15 | #end 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
$reportable.getName()$reportable.getQualifier()$reportable.getPassedSteps()$reportable.getFailedSteps()$reportable.getSkippedSteps()$reportable.getPendingSteps()$reportable.getUndefinedSteps()$reportable.getSteps()$reportable.getPassedScenarios()$reportable.getFailedScenarios()$reportable.getScenarios()$reportable.getFormattedDuration()$reportable.getStatus().getLabel()
33 |
34 |
35 |
36 | 37 | #end 38 | -------------------------------------------------------------------------------- /src/test/java/LiveDemoTest.java: -------------------------------------------------------------------------------- 1 | import java.io.File; 2 | import java.util.ArrayList; 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | import net.masterthought.cucumber.Configuration; 8 | import net.masterthought.cucumber.ReportBuilder; 9 | import net.masterthought.cucumber.presentation.PresentationMode; 10 | import net.masterthought.cucumber.sorting.SortingMethod; 11 | import org.junit.jupiter.api.Test; 12 | 13 | /** 14 | * @author Damian Szczepanik (damianszczepanik@github) 15 | */ 16 | class LiveDemoTest { 17 | 18 | // test annotation only to make sure it is generated during "mvn test" 19 | // what is needed to publish generated report via github.com 20 | // http://damianszczepanik.github.io/cucumber-html-reports/overview-features.html 21 | @Test 22 | void generateDemoReport() { 23 | File reportOutputDirectory = new File("target/demo"); 24 | List jsonFiles = new ArrayList<>(); 25 | jsonFiles.add("src/test/resources/json/sample.json"); 26 | 27 | String buildNumber = "101"; 28 | String projectName = "Live Demo Project"; 29 | Configuration configuration = new Configuration(reportOutputDirectory, projectName); 30 | configuration.setBuildNumber(buildNumber); 31 | 32 | configuration.addClassifications("Browser", "Firefox"); 33 | configuration.addClassifications("Branch", "release/1.0"); 34 | configuration.setSortingMethod(SortingMethod.NATURAL); 35 | configuration.addPresentationModes(PresentationMode.EXPAND_ALL_STEPS); 36 | configuration.addPresentationModes(PresentationMode.PARALLEL_TESTING); 37 | configuration.setQualifier("sample", "Chrome 80, mobile"); 38 | // points to the demo trends which is not used for other tests 39 | configuration.setTrendsStatsFile(new File("target/test-classes/demo-trends.json")); 40 | 41 | configuration.addCustomCssFiles(Collections.singletonList("src/test/resources/css/stackoverflow-light.min.css")); 42 | configuration.addCustomJsFiles(Arrays.asList("src/test/resources/js/enable-highlighting.js", "src/test/resources/js/highlight.min.js")); 43 | 44 | ReportBuilder reportBuilder = new ReportBuilder(jsonFiles, configuration); 45 | reportBuilder.generateReports(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/EmptyReportableTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | /** 8 | * @author Damian Szczepanik (damianszczepanik@github) 9 | */ 10 | class EmptyReportableTest { 11 | 12 | @Test 13 | void allMethods_ReturnsDefaultValues() { 14 | 15 | // given 16 | EmptyReportable reportable = new EmptyReportable(); 17 | 18 | // then 19 | assertThat(reportable.getName()).isNull(); 20 | assertThat(reportable.getFeatures()).isZero(); 21 | assertThat(reportable.getPassedFeatures()).isZero(); 22 | assertThat(reportable.getFailedFeatures()).isZero(); 23 | assertThat(reportable.getScenarios()).isZero(); 24 | assertThat(reportable.getPassedScenarios()).isZero(); 25 | assertThat(reportable.getFailedScenarios()).isZero(); 26 | assertThat(reportable.getSteps()).isZero(); 27 | assertThat(reportable.getPassedSteps()).isZero(); 28 | assertThat(reportable.getFailedSteps()).isZero(); 29 | assertThat(reportable.getSkippedSteps()).isZero(); 30 | assertThat(reportable.getUndefinedSteps()).isZero(); 31 | assertThat(reportable.getPendingSteps()).isZero(); 32 | assertThat(reportable.getUndefinedSteps()).isZero(); 33 | assertThat(reportable.getDuration()).isZero(); 34 | assertThat(reportable.getFormattedDuration()).isNull(); 35 | assertThat(reportable.getStatus()).isNull(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/FileReaderUtil.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber; 2 | 3 | import java.io.File; 4 | import java.net.URISyntaxException; 5 | 6 | public class FileReaderUtil { 7 | 8 | public static String getAbsolutePathFromResource(String resource) { 9 | try { 10 | return new File(FileReaderUtil.class.getClassLoader().getResource(resource).toURI()).getAbsolutePath(); 11 | } catch (URISyntaxException e) { 12 | throw new ValidationException("Could not read resource: " + resource, e); 13 | } 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/ReportResultSimpleFeatureComparator.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber; 2 | 3 | import net.masterthought.cucumber.json.Element; 4 | import net.masterthought.cucumber.json.Feature; 5 | import net.masterthought.cucumber.json.Step; 6 | import org.assertj.core.groups.Tuple; 7 | import org.assertj.core.internal.StandardComparisonStrategy; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Comparator; 11 | import java.util.List; 12 | 13 | /** 14 | * Simple comparator for features to compare it somehow. 15 | */ 16 | public class ReportResultSimpleFeatureComparator implements Comparator { 17 | 18 | @Override 19 | public int compare(Feature feature1, Feature feature2) { 20 | return StandardComparisonStrategy.instance() 21 | .areEqual(buildTupleByFeature(feature1), buildTupleByFeature(feature2)) ? 0 : -1; 22 | } 23 | 24 | private Tuple buildTupleByFeature(Feature feature) { 25 | List values = new ArrayList<>(); 26 | values.add(feature.getUri()); 27 | values.add(feature.getLine()); 28 | values.add(feature.getId()); 29 | values.add(feature.getName()); 30 | 31 | for (Element e : feature.getElements()) { 32 | values.add(e.getLine()); 33 | values.add(e.getType()); 34 | values.add(e.getId()); 35 | values.add(e.getName()); 36 | 37 | for (Step s : e.getSteps()) { 38 | values.add(s.getLine()); 39 | values.add(s.getName()); 40 | values.add(s.getResult().getStatus()); 41 | } 42 | } 43 | return new Tuple(values); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/ReportResultTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.List; 6 | import java.util.regex.Pattern; 7 | 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.Test; 10 | import net.masterthought.cucumber.json.Feature; 11 | import net.masterthought.cucumber.json.support.StepObject; 12 | import net.masterthought.cucumber.json.support.TagObject; 13 | 14 | /** 15 | * @author Damian Szczepanik (damianszczepanik@github) 16 | */ 17 | class ReportResultTest extends ReportGenerator { 18 | 19 | @BeforeEach 20 | void setUp() { 21 | setUpWithJson(SAMPLE_JSON); 22 | } 23 | 24 | @Test 25 | void getAllFeatures_ReturnsFeatures() { 26 | 27 | // given 28 | // from @Before 29 | 30 | // when 31 | List features = reportResult.getAllFeatures(); 32 | 33 | // then 34 | assertThat(features).hasSize(2); 35 | } 36 | 37 | @Test 38 | void getTags_ReturnsTags() { 39 | 40 | // given 41 | // from @Before 42 | 43 | // when 44 | List tags = reportResult.getAllTags(); 45 | 46 | // then 47 | assertThat(tags).hasSize(3); 48 | } 49 | 50 | @Test 51 | void getAllSteps_ReturnsSteps() { 52 | 53 | // given 54 | // from @Before 55 | 56 | // when 57 | List tags = reportResult.getAllSteps(); 58 | 59 | // then 60 | assertThat(tags).hasSize(16); 61 | } 62 | 63 | @Test 64 | void getTagReport_ReturnsTagReport() { 65 | 66 | // given 67 | // from @Before 68 | 69 | // when 70 | Reportable reportable = reportResult.getTagReport(); 71 | 72 | // then 73 | assertThat(reportable.getDuration()).isEqualTo(509064334L); 74 | } 75 | 76 | @Test 77 | void getAllXXXFeatures_ReturnsFeaturesByStatus() { 78 | 79 | // given 80 | // from @Before 81 | 82 | // when 83 | int passingFeatures = reportResult.getFeatureReport().getPassedFeatures(); 84 | int failedFeatures = reportResult.getFeatureReport().getFailedFeatures(); 85 | 86 | // then 87 | assertThat(passingFeatures).isEqualTo(1); 88 | assertThat(failedFeatures).isEqualTo(1); 89 | } 90 | 91 | @Test 92 | void getBuildTime_ReturnsFormattedBuildTime() { 93 | 94 | // given 95 | // from @Before 96 | 97 | // when 98 | String time = reportResult.getBuildTime(); 99 | 100 | // then 101 | // validate only format such as "17 lip 2016, 18:40" (dot for month because it can have local no ASCII characters 102 | assertThat(time).containsPattern(Pattern.compile("^\\d{0,2} .{3} \\d{4}, \\d{1,2}:\\d{1,2}$")); 103 | } 104 | } -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/ValidationExceptionTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | /** 8 | * @author Damian Szczepanik (damianszczepanik@github) 9 | */ 10 | class ValidationExceptionTest { 11 | 12 | @Test 13 | void constructor_PassesCause() { 14 | 15 | // given 16 | Exception cause = new Exception(); 17 | 18 | // when 19 | ValidationException exception = new ValidationException(cause); 20 | 21 | // then 22 | assertThat(exception).hasCause(cause); 23 | } 24 | 25 | @Test 26 | void constructor_PassesMessage() { 27 | 28 | // given 29 | String message = "ups..."; 30 | 31 | // when 32 | ValidationException exception = new ValidationException(message); 33 | 34 | // then 35 | assertThat(exception).hasMessage(message); 36 | } 37 | 38 | @Test 39 | void constructor_PassesMessageAndCause() { 40 | 41 | // given 42 | Exception cause = new Exception(); 43 | String message = "ups..."; 44 | 45 | 46 | // when 47 | ValidationException exception = new ValidationException(message, cause); 48 | 49 | // then 50 | assertThat(exception) 51 | .hasCause(cause) 52 | .hasMessage(message); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/ErrorPageTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators; 2 | 3 | import java.util.List; 4 | 5 | import net.masterthought.cucumber.generators.integrations.PageTest; 6 | import org.apache.commons.lang3.exception.ExceptionUtils; 7 | import org.apache.velocity.VelocityContext; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | /** 14 | * @author Damian Szczepanik (damianszczepanik@github) 15 | */ 16 | class ErrorPageTest extends PageTest { 17 | 18 | @BeforeEach 19 | void setUp() { 20 | setUpWithJson(SAMPLE_JSON); 21 | } 22 | 23 | @Test 24 | void prepareReport_AddsCustomProperties() { 25 | 26 | // give 27 | Exception exception = new Exception(); 28 | page = new ErrorPage(null, configuration, exception, jsonReports); 29 | 30 | // when 31 | page.prepareReport(); 32 | 33 | // then 34 | VelocityContext context = page.context; 35 | assertThat(context.getKeys()).hasSize(16); 36 | assertThat(context.get("classifications")).isInstanceOf(List.class); 37 | assertThat(context.get("output_message")).isEqualTo(ExceptionUtils.getStackTrace(exception)); 38 | assertThat(context.get("json_files")).isEqualTo(jsonReports); 39 | assertThat(context.get("directory_suffix")).isEqualTo(configuration.getDirectorySuffixWithSeparator()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/EscapeHtmlReferenceTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators; 2 | 3 | import static org.apache.commons.text.StringEscapeUtils.escapeHtml4; 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | 6 | import org.apache.velocity.app.event.ReferenceInsertionEventHandler; 7 | import org.junit.jupiter.api.Test; 8 | 9 | /** 10 | * @author M.P. Korstanje (mpkorstanje@github) 11 | */ 12 | class EscapeHtmlReferenceTest { 13 | 14 | private static final String SOME_REFERENCE = "someReference"; 15 | private final ReferenceInsertionEventHandler insertionEventHandler = new EscapeHtmlReference(); 16 | 17 | @Test 18 | void referenceInsert_returnNormalText() { 19 | // given 20 | String normalText = "a plain statement"; 21 | 22 | // when 23 | Object result = insertionEventHandler.referenceInsert(null, SOME_REFERENCE, normalText); 24 | 25 | // then 26 | assertThat(result).isEqualTo(normalText); 27 | } 28 | 29 | @Test 30 | void referenceInsert_shouldEscapeHtmlForAnyLabel() { 31 | // given 32 | String html = "a bold statement"; 33 | 34 | // when 35 | Object result = insertionEventHandler.referenceInsert(null, SOME_REFERENCE, html); 36 | 37 | // then 38 | assertThat(result).isEqualTo(escapeHtml4(html)); 39 | } 40 | 41 | @Test 42 | void referenceInsert_shouldNotEscapeWithSpecialTag() { 43 | // given 44 | String html = "a bold statement"; 45 | 46 | // when 47 | Object result = insertionEventHandler.referenceInsert(null, "$_noescape_" + SOME_REFERENCE, html); 48 | 49 | // then 50 | assertThat(result).isEqualTo(html); 51 | } 52 | 53 | @Test 54 | void referenceInsert_shouldReturnNullForNull() { 55 | // given 56 | String html = null; 57 | 58 | // when 59 | Object result = insertionEventHandler.referenceInsert(null, SOME_REFERENCE, html); 60 | 61 | // then 62 | assertThat(result).isNull(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/FailuresOverviewPageTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import net.masterthought.cucumber.generators.integrations.PageTest; 7 | import net.masterthought.cucumber.json.Element; 8 | import net.masterthought.cucumber.json.Feature; 9 | import org.apache.velocity.VelocityContext; 10 | import org.junit.jupiter.api.BeforeEach; 11 | import org.junit.jupiter.api.Test; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | 15 | /** 16 | * @author Damian Szczepanik (damianszczepanik@github) 17 | */ 18 | class FailuresOverviewPageTest extends PageTest { 19 | 20 | @BeforeEach 21 | void setUp() { 22 | setUpWithJson(SAMPLE_JSON); 23 | } 24 | 25 | @Test 26 | void getWebPage_ReturnsFailureReportFileName() { 27 | 28 | // given 29 | page = new FailuresOverviewPage(reportResult, configuration); 30 | 31 | // when 32 | String fileName = page.getWebPage(); 33 | 34 | // then 35 | assertThat(fileName).isEqualTo(FailuresOverviewPage.WEB_PAGE); 36 | } 37 | 38 | @Test 39 | void prepareReport_AddsCustomProperties() { 40 | 41 | // given 42 | page = new FailuresOverviewPage(reportResult, configuration); 43 | // this page only has failed scenarios (elements) so extract them into 44 | // a list to compare 45 | List failures = new ArrayList<>(); 46 | for (Feature feature : features) { 47 | if (feature.getStatus().isPassed()) { 48 | continue; 49 | } 50 | 51 | for (Element element : feature.getElements()) { 52 | if (element.getStepsStatus().isPassed()) 53 | continue; 54 | 55 | failures.add(element); 56 | } 57 | } 58 | 59 | // when 60 | page.prepareReport(); 61 | 62 | // then 63 | VelocityContext context = page.context; 64 | assertThat(context.getKeys()).hasSize(14); 65 | 66 | List elements = (List) context.get("failures"); 67 | assertThat(elements).hasSameElementsAs(failures); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/FeatureReportPageTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators; 2 | 3 | import net.masterthought.cucumber.generators.integrations.PageTest; 4 | import net.masterthought.cucumber.json.Element; 5 | import net.masterthought.cucumber.json.Embedding; 6 | import net.masterthought.cucumber.json.Feature; 7 | import net.masterthought.cucumber.json.Step; 8 | import org.apache.velocity.VelocityContext; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | 14 | /** 15 | * @author Damian Szczepanik (damianszczepanik@github) 16 | */ 17 | class FeatureReportPageTest extends PageTest { 18 | 19 | @BeforeEach 20 | void setUp() { 21 | setUpWithJson(SAMPLE_JSON); 22 | } 23 | 24 | @Test 25 | void getWebPage_ReturnsFeatureFileName() { 26 | 27 | // given 28 | Feature feature = features.get(1); 29 | page = new FeatureReportPage(reportResult, configuration, feature); 30 | 31 | // when 32 | String fileName = page.getWebPage(); 33 | 34 | // then 35 | assertThat(fileName).isEqualTo(feature.getReportFileName()); 36 | } 37 | 38 | @Test 39 | void prepareReport_AddsCustomProperties() { 40 | 41 | // given 42 | Feature feature = features.get(1); 43 | page = new FeatureReportPage(reportResult, configuration, feature); 44 | 45 | // when 46 | page.prepareReport(); 47 | 48 | // then 49 | VelocityContext context = page.context; 50 | assertThat(context.getKeys()).hasSize(15); 51 | assertThat(context.get("feature")).isEqualTo(feature); 52 | } 53 | 54 | @Test 55 | void getMimeType_OnEmbeddingFromV2CucumberReportFile_SupportsScreenshots() { 56 | // given 57 | Feature feature = features.get(0); 58 | Element element = feature.getElements()[0]; 59 | Step step = element.getSteps()[0]; 60 | 61 | // when 62 | Embedding[] embeddings = step.getEmbeddings(); 63 | 64 | // then 65 | assertThat(embeddings[0].getMimeType()).isEqualTo("image/url"); 66 | } 67 | 68 | @Test 69 | void getMimeType_OnEmbeddingFromV3CucumberReportFile_SupportsScreenshots() { 70 | // given 71 | Feature feature = features.get(0); 72 | Element element = feature.getElements()[0]; 73 | Step step = element.getSteps()[0]; 74 | 75 | // when 76 | Embedding[] embeddings = step.getEmbeddings(); 77 | 78 | // then 79 | assertThat(embeddings[1].getMimeType()).isEqualTo("text/plain"); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/FeaturesOverviewPageTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.apache.velocity.VelocityContext; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | import net.masterthought.cucumber.ReportBuilder; 9 | import net.masterthought.cucumber.generators.integrations.PageTest; 10 | 11 | /** 12 | * @author Damian Szczepanik (damianszczepanik@github) 13 | */ 14 | class FeaturesOverviewPageTest extends PageTest { 15 | 16 | @BeforeEach 17 | void setUp() { 18 | setUpWithJson(SAMPLE_JSON); 19 | } 20 | 21 | @Test 22 | void getWebPage_ReturnsFeatureFileName() { 23 | 24 | // given 25 | page = new FeaturesOverviewPage(reportResult, configuration); 26 | 27 | // when 28 | String fileName = page.getWebPage(); 29 | 30 | // then 31 | assertThat(fileName).isEqualTo(ReportBuilder.HOME_PAGE); 32 | } 33 | 34 | @Test 35 | void prepareReport_AddsCustomProperties() { 36 | 37 | // given 38 | page = new FeaturesOverviewPage(reportResult, configuration); 39 | 40 | // when 41 | page.prepareReport(); 42 | 43 | // then 44 | VelocityContext context = page.context; 45 | assertThat(context.getKeys()).hasSize(17); 46 | 47 | assertThat(context.get("all_features")).isEqualTo(features); 48 | assertThat(context.get("report_summary")).isEqualTo(reportResult.getFeatureReport()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/StepsOverviewPageTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators; 2 | 3 | import net.masterthought.cucumber.generators.integrations.PageTest; 4 | import net.masterthought.cucumber.json.support.StepObject; 5 | import net.masterthought.cucumber.util.Util; 6 | import org.apache.velocity.VelocityContext; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | /** 13 | * @author Damian Szczepanik (damianszczepanik@github) 14 | */ 15 | class StepsOverviewPageTest extends PageTest { 16 | 17 | @BeforeEach 18 | void setUp() { 19 | setUpWithJson(SAMPLE_JSON); 20 | } 21 | 22 | @Test 23 | void getWebPage_ReturnsStepsOverviewFileName() { 24 | 25 | // given 26 | page = new StepsOverviewPage(reportResult, configuration); 27 | 28 | // when 29 | String fileName = page.getWebPage(); 30 | 31 | // then 32 | assertThat(fileName).isEqualTo(StepsOverviewPage.WEB_PAGE); 33 | } 34 | 35 | @Test 36 | void prepareReport_AddsCustomProperties() { 37 | 38 | // given 39 | page = new StepsOverviewPage(reportResult, configuration); 40 | 41 | // when 42 | page.prepareReport(); 43 | 44 | // then 45 | VelocityContext context = page.context; 46 | assertThat(context.getKeys()).hasSize(18); 47 | assertThat(context.get("all_steps")).isEqualTo(steps); 48 | 49 | int allOccurrences = 0; 50 | long allDurations = 0; 51 | long maxDuration = 0; 52 | for (StepObject stepObject : reportResult.getAllSteps()) { 53 | allOccurrences += stepObject.getTotalOccurrences(); 54 | allDurations += stepObject.getDuration(); 55 | if (stepObject.getDuration() > maxDuration) { 56 | maxDuration = stepObject.getMaxDuration(); 57 | } 58 | } 59 | assertThat(context.get("all_occurrences")).isEqualTo(allOccurrences); 60 | long average = allDurations / (allOccurrences == 0 ? 1 : allOccurrences); 61 | assertThat(context.get("all_average_duration")).isEqualTo(Util.formatDuration(average)); 62 | assertThat(context.get("all_max_duration")).isEqualTo(Util.formatDuration(maxDuration)); 63 | assertThat(context.get("all_durations")).isEqualTo(Util.formatDuration(allDurations)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/TagReportPageTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators; 2 | 3 | import net.masterthought.cucumber.generators.integrations.PageTest; 4 | import net.masterthought.cucumber.json.support.TagObject; 5 | import org.apache.velocity.VelocityContext; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | /** 12 | * @author Damian Szczepanik (damianszczepanik@github) 13 | */ 14 | class TagReportPageTest extends PageTest { 15 | 16 | @BeforeEach 17 | void setUp() { 18 | setUpWithJson(SAMPLE_JSON); 19 | } 20 | 21 | @Test 22 | void getWebPage_ReturnsTagReportFileName() { 23 | 24 | // given 25 | TagObject tag = tags.get(0); 26 | page = new TagReportPage(reportResult, configuration, tag); 27 | 28 | // when 29 | String fileName = page.getWebPage(); 30 | 31 | // then 32 | assertThat(fileName).isEqualTo(tag.getReportFileName()); 33 | } 34 | 35 | @Test 36 | void prepareReport_AddsCustomProperties() { 37 | 38 | // given 39 | TagObject tag = tags.get(1); 40 | page = new TagReportPage(reportResult, configuration, tag); 41 | 42 | // when 43 | page.prepareReport(); 44 | 45 | // then 46 | VelocityContext context = page.context; 47 | assertThat(context.getKeys()).hasSize(14); 48 | assertThat(context.get("tag")).isEqualTo(tag); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/TagsOverviewPageTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.List; 6 | 7 | import net.masterthought.cucumber.generators.integrations.PageTest; 8 | import net.masterthought.cucumber.json.support.TagObject; 9 | import org.apache.velocity.VelocityContext; 10 | import org.junit.jupiter.api.BeforeEach; 11 | import org.junit.jupiter.api.Test; 12 | 13 | /** 14 | * @author Damian Szczepanik (damianszczepanik@github) 15 | */ 16 | class TagsOverviewPageTest extends PageTest { 17 | 18 | @BeforeEach 19 | void setUp() { 20 | setUpWithJson(SAMPLE_JSON); 21 | } 22 | 23 | @Test 24 | void getWebPage_ReturnsTagOverviewReportFileName() { 25 | 26 | // given 27 | page = new TagsOverviewPage(reportResult, configuration); 28 | 29 | // when 30 | String fileName = page.getWebPage(); 31 | 32 | // then 33 | assertThat(fileName).isEqualTo(TagsOverviewPage.WEB_PAGE); 34 | } 35 | 36 | @Test 37 | void prepareReport_AddsCustomProperties() { 38 | 39 | // given 40 | page = new TagsOverviewPage(reportResult, configuration); 41 | 42 | // when 43 | page.prepareReport(); 44 | 45 | // then 46 | VelocityContext context = page.context; 47 | assertThat(context.getKeys()).hasSize(17); 48 | 49 | assertThat(context.get("all_tags")).isEqualTo(tags); 50 | assertThat(context.get("report_summary")).isEqualTo(reportResult.getTagReport()); 51 | assertThat(context.get("chart_categories")).isEqualTo(TagsOverviewPage.generateTagLabels(tags)); 52 | assertThat(context.get("chart_data")).isEqualTo(TagsOverviewPage.generateTagValues(tags)); 53 | } 54 | 55 | @Test 56 | void generateTagLabels_ReturnsTags() { 57 | 58 | // given 59 | List allTags = this.tags; 60 | 61 | // when 62 | String[] labels = TagsOverviewPage.generateTagLabels(allTags); 63 | 64 | // then 65 | assertThat(labels).isEqualTo(new String[]{"@checkout", "@fast", "@featureTag"}); 66 | } 67 | 68 | @Test 69 | void generateTagValues_ReturnsTagValues() { 70 | 71 | // given 72 | List allTags = this.tags; 73 | 74 | // when 75 | String[][] labels = TagsOverviewPage.generateTagValues(allTags); 76 | 77 | // then 78 | assertThat(labels).isEqualTo(new String[][]{ 79 | {"62.50", "100.00", "100.00"}, 80 | {"6.25", "0.00", "0.00"}, 81 | {"12.50", "0.00", "0.00"}, 82 | {"6.25", "0.00", "0.00"}, 83 | {"12.50", "0.00", "0.00"} 84 | }); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/integrations/PageTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators.integrations; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.charset.StandardCharsets; 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | import net.masterthought.cucumber.ReportBuilder; 11 | import net.masterthought.cucumber.ReportGenerator; 12 | import net.masterthought.cucumber.ValidationException; 13 | import net.masterthought.cucumber.generators.AbstractPage; 14 | import net.masterthought.cucumber.generators.integrations.helpers.DocumentAssertion; 15 | import net.masterthought.cucumber.json.Output; 16 | import org.jsoup.Jsoup; 17 | 18 | /** 19 | * @author Damian Szczepanik (damianszczepanik@github) 20 | */ 21 | public abstract class PageTest extends ReportGenerator { 22 | 23 | protected AbstractPage page; 24 | 25 | protected DocumentAssertion documentFrom(String pageName) { 26 | File input = new File(configuration.getReportDirectory(), 27 | ReportBuilder.BASE_DIRECTORY + configuration.getDirectorySuffixWithSeparator() + File.separatorChar + pageName); 28 | try { 29 | return new DocumentAssertion(Jsoup.parse(input, StandardCharsets.UTF_8.name(), "")); 30 | } catch (IOException e) { 31 | throw new ValidationException(e); 32 | } 33 | } 34 | 35 | protected String[] getMessages(Output[] outputs) { 36 | List messages = new ArrayList<>(); 37 | for (Output output : outputs) { 38 | messages.addAll(Arrays.asList(output.getMessages())); 39 | } 40 | 41 | return messages.toArray(new String[messages.size()]); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/integrations/helpers/BriefAssertion.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators.integrations.helpers; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import net.masterthought.cucumber.json.support.Status; 6 | import net.masterthought.cucumber.util.Util; 7 | 8 | /** 9 | * @author Damian Szczepanik (damianszczepanik@github) 10 | */ 11 | public class BriefAssertion extends ReportAssertion { 12 | 13 | public String getKeyword() { 14 | return oneByClass("keyword", WebAssertion.class).text(); 15 | } 16 | 17 | public String getName() { 18 | return oneByClass("name", WebAssertion.class).text(); 19 | } 20 | 21 | public String getLocation() { 22 | return oneByClass("location", WebAssertion.class).text(); 23 | } 24 | 25 | public void hasDuration(long duration) { 26 | String found = oneByClass("duration", WebAssertion.class).text(); 27 | assertThat(found).isEqualTo(Util.formatDuration(duration)); 28 | } 29 | 30 | public void hasStatus(Status status) { 31 | assertThat(classNames()).contains(status.getRawName()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/integrations/helpers/BuildInfoAssertion.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators.integrations.helpers; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | /** 6 | * @author Damian Szczepanik (damianszczepanik@github) 7 | */ 8 | public class BuildInfoAssertion extends TableAssertion { 9 | 10 | public String getProjectName() { 11 | WebAssertion[] cells = getBodyRow().getCells(); 12 | assertThat(cells).isNotEmpty(); 13 | return cells[0].text(); 14 | } 15 | 16 | public String getBuildNumber() { 17 | WebAssertion[] cells = getBodyRow().getCells(); 18 | assertThat(cells).hasSizeGreaterThanOrEqualTo(2); 19 | return cells[1].text(); 20 | } 21 | 22 | public void hasBuildDate(boolean withBuildNumber) { 23 | // date format: dd MMM yyyy, HH:mm 24 | WebAssertion[] cells = getBodyRow().getCells(); 25 | assertThat(cells).hasSize(withBuildNumber ? 3 : 2); 26 | assertThat(cells[withBuildNumber ? 2 : 1].text()).matches("^[0-3][0-9] \\w{3} \\d{4}, \\d{2}:\\d{2}$"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/integrations/helpers/DocStringAssertion.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators.integrations.helpers; 2 | 3 | import net.masterthought.cucumber.json.DocString; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | 7 | /** 8 | * @author Fang Yuan (fayndee@github) 9 | */ 10 | public class DocStringAssertion extends ReportAssertion { 11 | 12 | public void hasDocString(DocString docString) { 13 | assertThat(docString).isNotNull(); 14 | assertThat(oneBySelector("pre", WebAssertion.class).text()).isEqualTo(docString.getValue()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/integrations/helpers/DocumentAssertion.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators.integrations.helpers; 2 | 3 | import org.jsoup.nodes.Element; 4 | 5 | /** 6 | * @author Damian Szczepanik (damianszczepanik@github) 7 | */ 8 | public class DocumentAssertion extends ReportAssertion { 9 | 10 | public DocumentAssertion(Element element) { 11 | this.element = element; 12 | } 13 | 14 | public HeadAssertion getHead() { 15 | return oneBySelector("head", HeadAssertion.class); 16 | } 17 | 18 | public NavigationAssertion getNavigation() { 19 | return byId("navigation", NavigationAssertion.class); 20 | } 21 | 22 | public LeadAssertion getLead() { 23 | return byId("report-lead", LeadAssertion.class); 24 | } 25 | 26 | public BuildInfoAssertion getBuildInfo() { 27 | return byId("build-info", BuildInfoAssertion.class); 28 | } 29 | 30 | public TableRowAssertion[] getClassifications() { 31 | return byId("classifications", WebAssertion.class).allBySelector("tbody tr", TableRowAssertion.class); 32 | } 33 | 34 | public SummaryAssertion getReport() { 35 | return byId("report", SummaryAssertion.class); 36 | } 37 | 38 | public FeatureAssertion getFeature() { 39 | return oneByClass("feature", FeatureAssertion.class); 40 | } 41 | 42 | public ElementAssertion[] getElements() { 43 | return oneByClass("elements", WebAssertion.class).allByClass("element", ElementAssertion.class); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/integrations/helpers/ElementAssertion.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators.integrations.helpers; 2 | 3 | /** 4 | * @author Damian Szczepanik (damianszczepanik@github) 5 | */ 6 | public class ElementAssertion extends ReportAssertion { 7 | 8 | public LinkAssertion getFeatureName() { 9 | WebAssertion[] webs = allBySelector("div", WebAssertion.class); 10 | // get the first nested
which has no its class but contains the feature name 11 | return webs[0].oneBySelector("a", LinkAssertion.class); 12 | } 13 | 14 | @Override 15 | public TagAssertion[] getTags() { 16 | return super.getTags(); 17 | } 18 | 19 | @Override 20 | public BriefAssertion getBrief() { 21 | return super.getCollapseControl(BriefAssertion.class).getBrief(); 22 | } 23 | 24 | public HooksAssertion getBefore() { 25 | return oneByClass("hooks-element-before", HooksAssertion.class); 26 | } 27 | 28 | public HooksAssertion getAfter() { 29 | return oneByClass("hooks-element-after", HooksAssertion.class); 30 | } 31 | 32 | public StepsAssertion getStepsSection() { 33 | return oneByClass("steps", StepsAssertion.class); 34 | } 35 | 36 | public String getDescription() { 37 | return childByClass("description", WebAssertion.class).text(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/integrations/helpers/EmbeddingAssertion.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators.integrations.helpers; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.nio.charset.StandardCharsets; 6 | 7 | import java.util.Base64; 8 | 9 | import net.masterthought.cucumber.json.Embedding; 10 | 11 | /** 12 | * @author Damian Szczepanik (damianszczepanik@github) 13 | */ 14 | public class EmbeddingAssertion extends ReportAssertion { 15 | 16 | public void hasImageContent(Embedding embedding) { 17 | String src = getBox().oneBySelector("img", WebAssertion.class).attr("src"); 18 | assertThat(src).endsWith(embedding.getFileName()); 19 | } 20 | 21 | public void hasSrcDocContent(String content) { 22 | assertThat(getBox().oneBySelector("iframe", WebAssertion.class).attr("srcDoc")) 23 | .isEqualTo(getDecodedData(content)); 24 | } 25 | 26 | public void hasTextContent(String content) { 27 | assertThat(getBox().oneBySelector("pre", WebAssertion.class).text()) 28 | .isEqualTo(getDecodedData(content)); 29 | } 30 | 31 | private WebAssertion getBox() { 32 | return oneByClass("embedding-content", WebAssertion.class); 33 | } 34 | 35 | private String getDecodedData(String data) { 36 | return new String(Base64.getDecoder().decode(data.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/integrations/helpers/FeatureAssertion.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators.integrations.helpers; 2 | 3 | /** 4 | * @author Damian Szczepanik (damianszczepanik@github) 5 | */ 6 | public class FeatureAssertion extends ReportAssertion { 7 | 8 | public ElementAssertion[] getElements() { 9 | return allByClass("element", ElementAssertion.class); 10 | } 11 | 12 | @Override 13 | public TagAssertion[] getTags() { 14 | return super.getTags(); 15 | } 16 | 17 | @Override 18 | public BriefAssertion getBrief() { 19 | return super.getBrief(); 20 | } 21 | 22 | public String getDescription() { 23 | return childByClass("description", WebAssertion.class).text(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/integrations/helpers/HeadAssertion.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators.integrations.helpers; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.stream.Collectors; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | 9 | /** 10 | * @author Damian Szczepanik (damianszczepanik@github) 11 | */ 12 | public class HeadAssertion extends ReportAssertion { 13 | 14 | public String getTitle() { 15 | return oneBySelector("title", WebAssertion.class).text(); 16 | } 17 | 18 | /** 19 | * Validates the included JS scripts in the head 20 | * 21 | * @param files js file names 22 | */ 23 | public void hasAtLeastTheseJsFilesIncluded(String... files) { 24 | List scripts = Arrays.asList(allBySelector("script", HeadAssertion.class)); 25 | assertThat(scripts).hasSizeGreaterThanOrEqualTo(files.length); 26 | 27 | for (final String file : files) { 28 | scripts.stream() 29 | .filter(s -> file.equals(s.attr("src"))) 30 | .findAny() 31 | .orElseThrow(() -> new AssertionError(String.format("%s not found", file))); 32 | } 33 | } 34 | 35 | /** 36 | * Validates the included CSS scripts in the head 37 | * 38 | * @param files js file names 39 | */ 40 | public void hasAtLeastTheseCssFilesIncluded(String... files) { 41 | List stylesheets = Arrays.stream(allBySelector("link", HeadAssertion.class)) 42 | .filter(l -> "stylesheet".equalsIgnoreCase(l.attr("rel"))) 43 | .collect(Collectors.toList()); 44 | assertThat(stylesheets).hasSizeGreaterThanOrEqualTo(files.length); 45 | 46 | for (final String file : files) { 47 | stylesheets.stream() 48 | .filter(s -> file.equals(s.attr("href"))) 49 | .findAny() 50 | .orElseThrow(() -> new AssertionError(String.format("%s not found", file))); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/integrations/helpers/HookAssertion.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators.integrations.helpers; 2 | 3 | /** 4 | * @author Damian Szczepanik (damianszczepanik@github) 5 | */ 6 | public class HookAssertion extends ReportAssertion { 7 | 8 | @Override 9 | public BriefAssertion getBrief() { 10 | return super.getBrief(); 11 | } 12 | 13 | @Override 14 | public String getErrorMessage() { 15 | return oneByClass("message", WebAssertion.class).oneBySelector("pre", WebAssertion.class).text(); 16 | } 17 | 18 | public OutputAssertion[] getOutputs() { 19 | return allByClass("output", OutputAssertion.class); 20 | } 21 | 22 | public EmbeddingAssertion[] getEmbedding() { 23 | return oneByClass("embeddings", WebAssertion.class).allByClass("embedding", EmbeddingAssertion.class); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/integrations/helpers/HooksAssertion.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators.integrations.helpers; 2 | 3 | /** 4 | * @author Damian Szczepanik (damianszczepanik@github) 5 | */ 6 | public class HooksAssertion extends ReportAssertion { 7 | 8 | @Override 9 | public BriefAssertion getBrief() { 10 | return super.getCollapseControl(BriefAssertion.class).getBrief(); 11 | } 12 | 13 | public HookAssertion[] getHooks() { 14 | return allByClass("hook", HookAssertion.class); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/integrations/helpers/LeadAssertion.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators.integrations.helpers; 2 | 3 | /** 4 | * @author Damian Szczepanik (damianszczepanik@github) 5 | */ 6 | public class LeadAssertion extends ReportAssertion { 7 | 8 | public String getHeader() { 9 | return oneBySelector("h2", WebAssertion.class).text(); 10 | } 11 | 12 | public String getDescription() { 13 | return oneBySelector("p", WebAssertion.class).text(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/integrations/helpers/LinkAssertion.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators.integrations.helpers; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | /** 6 | * @author Damian Szczepanik (damianszczepanik@github) 7 | */ 8 | public class LinkAssertion extends ReportAssertion { 9 | 10 | public void hasLabelAndAddress(String label, String address) { 11 | assertThat(element.attr("href")).isEqualTo(address); 12 | assertThat(element.text()).isEqualTo(label); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/integrations/helpers/NavigationAssertion.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators.integrations.helpers; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | /** 6 | * @author Damian Szczepanik (damianszczepanik@github) 7 | */ 8 | public class NavigationAssertion extends ReportAssertion { 9 | 10 | public NavigationItemAssertion[] getNaviBarLinks() { 11 | return allBySelector("a", NavigationItemAssertion.class); 12 | } 13 | 14 | public void hasPluginName() { 15 | String pluginName = oneBySelector("p", WebAssertion.class).text(); 16 | assertThat(pluginName).isEqualTo("Cucumber Report"); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/integrations/helpers/NavigationItemAssertion.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators.integrations.helpers; 2 | 3 | import net.masterthought.cucumber.Configuration; 4 | import net.masterthought.cucumber.ReportBuilder; 5 | 6 | /** 7 | * @author Damian Szczepanik (damianszczepanik@github) 8 | */ 9 | public class NavigationItemAssertion extends LinkAssertion { 10 | 11 | public void hasLinkToJenkins(Configuration configuration) { 12 | hasLabelAndAddress("Jenkins", "../"); 13 | } 14 | 15 | public void hasLinkToPreviousResult(Configuration configuration, String page) { 16 | final Integer prevBuildNumber = Integer.parseInt(configuration.getBuildNumber()) - 1; 17 | hasLabelAndAddress("Previous results", "../../" + prevBuildNumber 18 | + "/" + ReportBuilder.BASE_DIRECTORY + configuration.getDirectorySuffixWithSeparator() + "/" + page); 19 | } 20 | 21 | public void hasLinkToLastResult(Configuration configuration, String page) { 22 | hasLabelAndAddress("Latest results", "../../lastCompletedBuild/" + ReportBuilder.BASE_DIRECTORY + configuration.getDirectorySuffixWithSeparator() + "/" + page); 23 | } 24 | 25 | public void hasLinkToFeatures() { 26 | hasLabelAndAddress("Features", ReportBuilder.HOME_PAGE); 27 | } 28 | 29 | public void hasLinkToTags() { 30 | hasLabelAndAddress("Tags", "overview-tags.html"); 31 | } 32 | 33 | public void hasLinkToSteps() { 34 | hasLabelAndAddress("Steps", "overview-steps.html"); 35 | } 36 | 37 | public void hasLinkToTrends() { 38 | hasLabelAndAddress("Trends", "overview-trends.html"); 39 | } 40 | 41 | public void hasLinkToFailures() { 42 | hasLabelAndAddress("Failures", "overview-failures.html"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/integrations/helpers/OutputAssertion.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators.integrations.helpers; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | /** 6 | * @author Damian Szczepanik (damianszczepanik@github) 7 | */ 8 | public class OutputAssertion extends ReportAssertion { 9 | 10 | public void hasMessages(String[] messages) { 11 | WebAssertion[] outputMessages = allBySelector("p", WebAssertion.class); 12 | assertThat(outputMessages).hasSameSizeAs(messages); 13 | for (int i = 0; i < messages.length; i++) { 14 | assertThat(outputMessages[i].text()).isEqualTo(messages[i]); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/integrations/helpers/ReportAssertion.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators.integrations.helpers; 2 | 3 | /** 4 | * @author Damian Szczepanik (damianszczepanik@github) 5 | */ 6 | public abstract class ReportAssertion extends WebAssertion { 7 | 8 | protected WebAssertion getReport() { 9 | return byId("report", WebAssertion.class); 10 | } 11 | 12 | protected TagAssertion[] getTags() { 13 | return TagAssertion.getTags(this); 14 | } 15 | 16 | protected BriefAssertion getBrief() { 17 | return childByClass("brief", BriefAssertion.class); 18 | } 19 | 20 | protected T getCollapseControl(Class clazz) { 21 | return childByClass("collapsable-control", clazz); 22 | } 23 | 24 | public LinkAssertion getLink() { 25 | return oneBySelector("a", LinkAssertion.class); 26 | } 27 | 28 | public LinkAssertion[] getLinks() { 29 | return allBySelector("a", LinkAssertion.class); 30 | } 31 | 32 | protected String getErrorMessage() { 33 | return oneByClass("message", WebAssertion.class).html(); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/integrations/helpers/StepAssertion.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators.integrations.helpers; 2 | 3 | import org.jsoup.nodes.Element; 4 | import org.jsoup.select.Elements; 5 | 6 | import java.util.List; 7 | 8 | import static java.util.stream.Collectors.toList; 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | /** 12 | * @author Damian Szczepanik (damianszczepanik@github) 13 | */ 14 | public class StepAssertion extends ReportAssertion { 15 | 16 | @Override 17 | public BriefAssertion getBrief() { 18 | return super.getBrief(); 19 | } 20 | 21 | public TableAssertion getArgumentsTable() { 22 | return oneByClass("step-arguments", TableAssertion.class); 23 | } 24 | 25 | public EmbeddingAssertion[] getEmbedding() { 26 | return oneByClass("embeddings", WebAssertion.class).allByClass("embedding", EmbeddingAssertion.class); 27 | } 28 | 29 | public OutputAssertion getOutput() { 30 | return oneByClass("outputs", OutputAssertion.class); 31 | } 32 | 33 | public DocStringAssertion getDocString() { 34 | return oneByClass("docstring", DocStringAssertion.class); 35 | } 36 | 37 | public WebAssertion getMessage() { 38 | return oneByClass("message", WebAssertion.class); 39 | } 40 | 41 | public HooksAssertion getBefore() { 42 | return oneByClass("hooks-step-before", HooksAssertion.class); 43 | } 44 | 45 | public HooksAssertion getAfter() { 46 | return oneByClass("hooks-step-after", HooksAssertion.class); 47 | } 48 | 49 | public void hasComments(List expectedComments) { 50 | Elements elements = element.getElementsByClass("comment"); 51 | List comments = elements.stream() 52 | .map(Element::text) 53 | .collect(toList()); 54 | assertThat(comments).isEqualTo(expectedComments); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/integrations/helpers/StepsAssertion.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators.integrations.helpers; 2 | 3 | /** 4 | * @author Damian Szczepanik (damianszczepanik@github) 5 | */ 6 | public class StepsAssertion extends ReportAssertion { 7 | 8 | @Override 9 | public BriefAssertion getBrief() { 10 | return super.getCollapseControl(BriefAssertion.class).getBrief(); 11 | } 12 | 13 | public StepAssertion[] getSteps() { 14 | return allByClass("step", StepAssertion.class); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/integrations/helpers/SummaryAssertion.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators.integrations.helpers; 2 | 3 | /** 4 | * @author Damian Szczepanik (damianszczepanik@github) 5 | */ 6 | public class SummaryAssertion extends ReportAssertion { 7 | 8 | public String getEmptyReportMessage() { 9 | return oneBySelector("p", WebAssertion.class).text(); 10 | } 11 | 12 | public TableAssertion getTableStats() { 13 | return oneByClass("stats-table", TableAssertion.class); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/integrations/helpers/TableAssertion.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators.integrations.helpers; 2 | 3 | /** 4 | * @author Damian Szczepanik (damianszczepanik@github) 5 | */ 6 | public class TableAssertion extends ReportAssertion { 7 | 8 | public TableRowAssertion[] getHeaderRows() { 9 | return allBySelector("thead tr", TableRowAssertion.class); 10 | } 11 | 12 | public TableRowAssertion getHeaderRow() { 13 | return oneBySelector("thead tr", TableRowAssertion.class); 14 | } 15 | 16 | public TableRowAssertion[] getBodyRows() { 17 | return allBySelector("tbody tr", TableRowAssertion.class); 18 | } 19 | 20 | public TableRowAssertion getBodyRow() { 21 | return oneBySelector("tbody tr", TableRowAssertion.class); 22 | } 23 | 24 | public TableRowAssertion getFooterRow() { 25 | return oneBySelector("tfoot tr", TableRowAssertion.class); 26 | } 27 | 28 | public TableRowAssertion[] getAllFooterRows() { 29 | return allBySelector("tfoot tr", TableRowAssertion.class); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/generators/integrations/helpers/TagAssertion.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.generators.integrations.helpers; 2 | 3 | /** 4 | * @author Damian Szczepanik (damianszczepanik@github) 5 | */ 6 | public class TagAssertion extends ReportAssertion { 7 | 8 | public static TagAssertion[] getTags(WebAssertion report) { 9 | return report.childByClass("tags", WebAssertion.class).allBySelector("a", TagAssertion.class); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/json/ArgumentTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | import org.powermock.reflect.Whitebox; 8 | 9 | import net.masterthought.cucumber.generators.integrations.PageTest; 10 | import net.masterthought.cucumber.json.support.Argument; 11 | 12 | /** 13 | * @author Damian Szczepanik (damianszczepanik@github) 14 | */ 15 | class ArgumentTest extends PageTest { 16 | 17 | @BeforeEach 18 | void setUp() { 19 | setUpWithJson(SAMPLE_JSON); 20 | } 21 | 22 | @Test 23 | void getRows_ReturnsRows() { 24 | 25 | // given 26 | Step step = features.get(0).getElements()[1].getSteps()[5]; 27 | Argument[] arguments = Whitebox.getInternalState(step, "arguments"); 28 | 29 | // when 30 | Row[] rows = arguments[0].getRows(); 31 | 32 | // then 33 | assertThat(rows).hasSize(2); 34 | assertThat(rows[0].getCells()).containsOnlyOnce("max", "min"); 35 | } 36 | 37 | @Test 38 | void getVal_ReturnsVal() { 39 | 40 | // given 41 | Argument matchArgument = features.get(0).getElements()[1].getSteps()[0].getMatch().getArguments()[0]; 42 | 43 | // when 44 | String val = matchArgument.getVal(); 45 | 46 | // then 47 | assertThat(val).isEqualTo("100"); 48 | } 49 | 50 | @Test 51 | void getArguments_ReturnsArguments() { 52 | 53 | // given 54 | Argument matchArgument = features.get(0).getElements()[1].getSteps()[0].getMatch().getArguments()[0]; 55 | 56 | // when 57 | int offset = matchArgument.getOffset(); 58 | 59 | // then 60 | assertThat(offset).isEqualTo(23); 61 | } 62 | } -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/json/DocStringTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | import net.masterthought.cucumber.generators.integrations.PageTest; 8 | 9 | /** 10 | * @author Damian Szczepanik (damianszczepanik@github) 11 | */ 12 | class DocStringTest extends PageTest { 13 | 14 | @BeforeEach 15 | void setUp() { 16 | setUpWithJson(SAMPLE_JSON); 17 | } 18 | 19 | @Test 20 | void getName_ReturnsFeatureTagName() { 21 | 22 | // give 23 | DocString docString = features.get(0).getElements()[0].getSteps()[1].getDocString(); 24 | 25 | // when 26 | String value = docString.getValue(); 27 | 28 | // then 29 | assertThat(value).isEqualTo("{\n" + 30 | "\"issuer\": {\n" + 31 | "\"name\": \"Real Bank Inc.\",\n" + 32 | "\"isn:\": \"RB55800093842N\"\n" + 33 | "},\n" + 34 | "\"card_number\": \"4896 0215 8478 6325\",\n" + 35 | "\"holder\": \"A guy\"\n" + 36 | "}"); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/json/MatchTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import net.masterthought.cucumber.json.support.Argument; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | import net.masterthought.cucumber.generators.integrations.PageTest; 9 | 10 | /** 11 | * @author Damian Szczepanik (damianszczepanik@github) 12 | */ 13 | class MatchTest extends PageTest { 14 | 15 | @BeforeEach 16 | void setUp() { 17 | setUpWithJson(SAMPLE_JSON); 18 | } 19 | 20 | @Test 21 | void getLocation_ReturnsLocation() { 22 | 23 | // given 24 | Match match = features.get(0).getElements()[1].getSteps()[0].getMatch(); 25 | 26 | // when 27 | String location = match.getLocation(); 28 | 29 | // then 30 | assertThat(location).isEqualTo("ATMScenario.createAccount(int)"); 31 | } 32 | 33 | @Test 34 | void getArguments_ReturnsArguments() { 35 | 36 | // given 37 | Match match = features.get(0).getElements()[1].getSteps()[0].getMatch(); 38 | 39 | // when 40 | Argument[] arguments = match.getArguments(); 41 | 42 | // then 43 | assertThat(arguments).hasSize(1); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/json/OutputTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | /** 8 | * @author Damian Szczepanik (damianszczepanik@github) 9 | */ 10 | class OutputTest { 11 | 12 | @Test 13 | void getMessages_ReturnsMessages() { 14 | 15 | // given 16 | String[] messages = { "a", "b", "c", "a" }; 17 | 18 | // when 19 | Output output = new Output(messages); 20 | 21 | // then 22 | assertThat(output.getMessages()).containsExactly(messages); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/json/RowTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | import net.masterthought.cucumber.generators.integrations.PageTest; 8 | 9 | /** 10 | * @author Damian Szczepanik (damianszczepanik@github) 11 | */ 12 | class RowTest extends PageTest { 13 | 14 | @BeforeEach 15 | void setUp() { 16 | setUpWithJson(SAMPLE_JSON); 17 | } 18 | 19 | @Test 20 | void getCells_ReturnsCells() { 21 | 22 | // given 23 | Row[] rows = features.get(0).getElements()[0].getSteps()[2].getRows(); 24 | 25 | // when 26 | String[] cells = rows[0].getCells(); 27 | 28 | // then 29 | assertThat(rows).hasSize(5); 30 | assertThat(cells).containsExactly("Müller", "Deutschland"); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/json/TagTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import net.masterthought.cucumber.generators.integrations.PageTest; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | 9 | /** 10 | * @author Damian Szczepanik (damianszczepanik@github) 11 | */ 12 | class TagTest extends PageTest { 13 | 14 | @BeforeEach 15 | void setUp() { 16 | setUpWithJson(SAMPLE_JSON); 17 | } 18 | 19 | @Test 20 | void getName_ReturnsFeatureTagName() { 21 | 22 | // given 23 | Tag tag = features.get(0).getTags()[0]; 24 | 25 | // when 26 | String tagName = tag.getName(); 27 | 28 | // then 29 | assertThat(tagName).isEqualTo("@featureTag"); 30 | } 31 | 32 | @Test 33 | void getName_ReturnsElementTagName() { 34 | 35 | // given 36 | Tag tag = features.get(0).getElements()[1].getTags()[2]; 37 | 38 | // when 39 | String tagName = tag.getName(); 40 | 41 | // then 42 | assertThat(tagName).isEqualTo("@checkout"); 43 | } 44 | 45 | @Test 46 | void getFileName_ReturnsTagFileName() { 47 | 48 | // given 49 | Tag tag = features.get(1).getElements()[0].getTags()[0]; 50 | 51 | // when 52 | String fileName = tag.getFileName(); 53 | 54 | // then 55 | assertThat(fileName).isEqualTo("report-tag_3971419525.html"); 56 | } 57 | 58 | @Test 59 | void generateFileName_OnInvalidTagName_ReturnsValidFileName() { 60 | 61 | // given 62 | final String[] tags = {"@up s", "?any", "9/3"}; 63 | final String[] names = {"2210183277", "2149457228", "2147539932"}; 64 | 65 | // when & then 66 | for (int i = 0; i < tags.length; i++) { 67 | assertThat(Tag.generateFileName(tags[i])).isEqualTo(String.format("report-tag_%s.html", names[i])); 68 | } 69 | } 70 | 71 | @Test 72 | void hashCode_OnSameName_ReturnsHashCode() { 73 | 74 | // given 75 | final String tagName = "@superTaggggg"; 76 | Tag tag = new Tag(tagName); 77 | 78 | // when 79 | int hashCode = tag.hashCode(); 80 | 81 | // then 82 | assertThat(hashCode).isEqualTo(tagName.hashCode()); 83 | } 84 | 85 | @Test 86 | void equals_OnSameName_ReturnsTrue() { 87 | 88 | // given 89 | final String tagName = "@superTaggggg"; 90 | Tag tag1 = new Tag(tagName); 91 | Tag tag2 = new Tag(tagName); 92 | 93 | // when 94 | boolean isSame = tag1.equals(tag2); 95 | 96 | // then 97 | assertThat(isSame).isTrue(); 98 | } 99 | 100 | @Test 101 | void equals_OnDifferentName_ReturnsFalse() { 102 | 103 | // given 104 | final String tagName = "@superTaggggg"; 105 | Tag tag1 = new Tag(tagName); 106 | Tag tag2 = new Tag(tagName + tagName); 107 | 108 | // when 109 | boolean isSame = tag1.equals(tag2); 110 | 111 | // then 112 | assertThat(isSame).isFalse(); 113 | } 114 | } -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/json/deserializers/StatusDeserializerTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json.deserializers; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 5 | import static org.mockito.Mockito.mock; 6 | import static org.mockito.Mockito.when; 7 | 8 | import java.util.Locale; 9 | 10 | import com.fasterxml.jackson.databind.JsonNode; 11 | import net.masterthought.cucumber.json.support.Status; 12 | import org.junit.jupiter.api.Test; 13 | 14 | /** 15 | * @author Damian Szczepanik (damianszczepanik@github) 16 | */ 17 | class StatusDeserializerTest { 18 | 19 | @Test 20 | void deserialize_OnDefaultStatus_ReturnsStatus() { 21 | 22 | // given 23 | Status status = Status.PASSED; 24 | JsonNode node = mock(JsonNode.class); 25 | when(node.asText()).thenReturn(status.name().toLowerCase(Locale.ENGLISH)); 26 | 27 | StatusDeserializer deserializer = new StatusDeserializer(); 28 | 29 | // when 30 | Status newStatus = deserializer.deserialize(node, null); 31 | 32 | // then 33 | assertThat(newStatus).isEqualTo(status); 34 | } 35 | 36 | @Test 37 | void deserialize_OnFailedStatus_ReturnsStatus() { 38 | 39 | // given 40 | Status status = Status.FAILED; 41 | JsonNode node = mock(JsonNode.class); 42 | when(node.asText()).thenReturn(status.name().toLowerCase(Locale.ENGLISH)); 43 | 44 | StatusDeserializer deserializer = new StatusDeserializer(); 45 | 46 | // when 47 | Status newStatus = deserializer.deserialize(node, null); 48 | 49 | // then 50 | assertThat(newStatus).isEqualTo(status); 51 | } 52 | 53 | 54 | @Test 55 | void deserialize_OnAdditionalStatus_ReturnsUndefinedStatus() { 56 | 57 | // given 58 | Status status = Status.UNDEFINED; 59 | JsonNode node = mock(JsonNode.class); 60 | // test any status from supported list 61 | when(node.asText()).thenReturn(StatusDeserializer.UNKNOWN_STATUSES.get(0)); 62 | 63 | StatusDeserializer deserializer = new StatusDeserializer(); 64 | 65 | // when 66 | Status newStatus = deserializer.deserialize(node, null); 67 | 68 | // then 69 | assertThat(newStatus).isEqualTo(status); 70 | } 71 | 72 | @Test 73 | void deserialize_OnUnknownStatus_ThrowsException() { 74 | 75 | // given 76 | String status = "thisIsNotStatus"; 77 | JsonNode node = mock(JsonNode.class); 78 | when(node.asText()).thenReturn(status); 79 | 80 | StatusDeserializer deserializer = new StatusDeserializer(); 81 | 82 | // when & then 83 | assertThatThrownBy(() -> deserializer.deserialize(node, null)) 84 | .isInstanceOf(IllegalArgumentException.class) 85 | .hasMessage(String.format("No enum constant %s.THISISNOTSTATUS", Status.class.getName())); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/json/deserializers/TagsDeserializerTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json.deserializers; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.mockito.Mockito.mock; 5 | import static org.mockito.Mockito.when; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | import com.fasterxml.jackson.databind.JsonNode; 11 | import net.masterthought.cucumber.Configuration; 12 | import net.masterthought.cucumber.json.Tag; 13 | import org.junit.jupiter.api.Test; 14 | 15 | class TagsDeserializerTest { 16 | 17 | @Test 18 | void deserialize_returnsTags() { 19 | 20 | // given 21 | TagsDeserializer tagsDeserializer = new TagsDeserializer(); 22 | 23 | String tagName = "@bestTag"; 24 | JsonNode tagNode = buildNode(tagName); 25 | 26 | JsonNode rootNode = mock(JsonNode.class); 27 | List tagNodes = new ArrayList<>(); 28 | tagNodes.add(tagNode); 29 | when(rootNode.iterator()).thenReturn(tagNodes.iterator()); 30 | 31 | Configuration configuration = new Configuration(null, null); 32 | 33 | // when 34 | Tag[] tags = tagsDeserializer.deserialize(rootNode, configuration); 35 | 36 | // then 37 | assertThat(tags).hasSize(1); 38 | assertThat(tags[0].getName()).isEqualTo(tagName); 39 | } 40 | 41 | @Test 42 | void deserialize_OnExcludedTags_returnsTags() { 43 | 44 | // given 45 | TagsDeserializer tagsDeserializer = new TagsDeserializer(); 46 | 47 | String tagName = "@bestTag"; 48 | JsonNode tagNode = buildNode(tagName); 49 | String excludedTagName = "@superTag"; 50 | JsonNode excludedTagNode = buildNode(excludedTagName); 51 | 52 | JsonNode rootNode = mock(JsonNode.class); 53 | List tagNodes = new ArrayList<>(); 54 | tagNodes.add(tagNode); 55 | tagNodes.add(excludedTagNode); 56 | when(rootNode.iterator()).thenReturn(tagNodes.iterator()); 57 | 58 | Configuration configuration = new Configuration(null, null); 59 | configuration.setTagsToExcludeFromChart(excludedTagName); 60 | 61 | // when 62 | Tag[] tags = tagsDeserializer.deserialize(rootNode, configuration); 63 | 64 | // then 65 | assertThat(tags).hasSize(1); 66 | assertThat(tags[0].getName()).isEqualTo(tagName); 67 | } 68 | 69 | private JsonNode buildNode(String tagName) { 70 | JsonNode tagNode = mock(JsonNode.class); 71 | JsonNode tagValue = mock(JsonNode.class); 72 | when(tagValue.asText()).thenReturn(tagName); 73 | when(tagNode.get("name")).thenReturn(tagValue); 74 | 75 | return tagNode; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/json/support/ResultsableBuilder.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json.support; 2 | 3 | import org.powermock.reflect.Whitebox; 4 | 5 | import net.masterthought.cucumber.json.Match; 6 | import net.masterthought.cucumber.json.Output; 7 | import net.masterthought.cucumber.json.Result; 8 | 9 | /** 10 | * @author Damian Szczepanik (damianszczepanik@github) 11 | */ 12 | public class ResultsableBuilder { 13 | 14 | public static Resultsable[] Resultsable(Status... statuses) { 15 | Resultsable[] resultsables = new Resultsable[statuses.length]; 16 | for (int i = 0; i < statuses.length; i++) { 17 | resultsables[i] = buildWith(statuses[i]); 18 | } 19 | return resultsables; 20 | } 21 | 22 | public static Resultsable buildWith(Status status) { 23 | return new ResultsableMock(status); 24 | } 25 | 26 | private static class ResultsableMock implements Resultsable { 27 | 28 | private Result result; 29 | 30 | ResultsableMock(Status status) { 31 | this.result = new Result(); 32 | Whitebox.setInternalState(this.result, "status", status); 33 | } 34 | 35 | @Override 36 | public Result getResult() { 37 | return result; 38 | } 39 | 40 | @Override 41 | public Match getMatch() { 42 | return null; 43 | } 44 | 45 | @Override 46 | public Output[] getOutputs() { 47 | return new Output[0]; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/json/support/StatusTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.json.support; 2 | 3 | import static net.masterthought.cucumber.json.support.Status.FAILED; 4 | import static net.masterthought.cucumber.json.support.Status.PASSED; 5 | import static net.masterthought.cucumber.json.support.Status.PENDING; 6 | import static net.masterthought.cucumber.json.support.Status.SKIPPED; 7 | import static net.masterthought.cucumber.json.support.Status.UNDEFINED; 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | 10 | import org.junit.jupiter.api.Test; 11 | 12 | /** 13 | * @author Damian Szczepanik (damianszczepanik@github) 14 | */ 15 | class StatusTest { 16 | 17 | @Test 18 | void valuesOf_ReturnsOrderedStatuses() { 19 | 20 | // given 21 | // tables displays result with following order 22 | final Status[] reference = { PASSED, FAILED, SKIPPED, PENDING, UNDEFINED }; 23 | 24 | // when 25 | Status[] orderedStatuses = Status.values(); 26 | 27 | // then 28 | assertThat(orderedStatuses).isEqualTo(reference); 29 | } 30 | 31 | @Test 32 | void getName_ReturnsNameAsLowerCase() { 33 | 34 | // given 35 | final Status status = PASSED; 36 | final String refName = "passed"; 37 | 38 | // when 39 | String rawName = status.getRawName(); 40 | 41 | // then 42 | assertThat(rawName).isEqualTo(refName); 43 | } 44 | 45 | @Test 46 | void getLabel_ReturnsNameStartingFromUpperCase() { 47 | 48 | // given 49 | final Status status = UNDEFINED; 50 | final String refLabel = "Undefined"; 51 | 52 | // when 53 | String label = status.getLabel(); 54 | 55 | // then 56 | assertThat(label).isEqualTo(refLabel); 57 | } 58 | 59 | @Test 60 | void isPassed_ReturnsTrueForPASSEDStatus() { 61 | 62 | // given 63 | Status status = PASSED; 64 | 65 | // when 66 | boolean isPassed = status.isPassed(); 67 | 68 | // then 69 | assertThat(isPassed).isTrue(); 70 | } 71 | 72 | @Test 73 | void hasPassed_ReturnsFalseForNoPASSED() { 74 | 75 | // given 76 | Status[] notPassed = {FAILED, SKIPPED, PENDING, UNDEFINED}; 77 | 78 | for (Status status : notPassed) { 79 | // when 80 | boolean isPassed = status.isPassed(); 81 | 82 | // then 83 | assertThat(isPassed).isFalse(); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/reducers/ElementComparatorTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.reducers; 2 | 3 | import net.masterthought.cucumber.ReportGenerator; 4 | import net.masterthought.cucumber.json.Element; 5 | import net.masterthought.cucumber.json.Feature; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.util.Arrays; 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | 14 | class ElementComparatorTest extends ReportGenerator { 15 | 16 | @Test 17 | void compare() { 18 | // given 19 | setUpWithJson(SAMPLE_JSON); 20 | List elements = Arrays.stream(getFeatureByName("Second feature").getElements()) 21 | .filter(Element::isScenario) 22 | .collect(Collectors.toList()); 23 | 24 | // when 25 | // then 26 | assertThat(elements.get(0)).usingComparator(new ElementComparator()).isEqualTo(elements.get(0)); 27 | assertThat(elements.get(0)).usingComparator(new ElementComparator()).isNotEqualTo(elements.get(1)); 28 | assertThat(elements.get(0)).usingComparator(new ElementComparator()).isNotNull(); 29 | } 30 | 31 | private Feature getFeatureByName(String name) { 32 | return reportResult.getAllFeatures() 33 | .stream() 34 | .filter(f -> name.equals(f.getName())) 35 | .findFirst() 36 | .orElse(null); 37 | } 38 | 39 | @Test 40 | void compare_backgrounds() { 41 | // given 42 | setUpWithJson(SAMPLE_JSON); 43 | List elements = Arrays.stream(getFeatureByName("1st feature").getElements()) 44 | .filter(Element::isBackground) 45 | .collect(Collectors.toList()); 46 | 47 | // when 48 | // then 49 | assertThat(new ElementComparator().compare(elements.get(0), elements.get(0))).isZero(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/reducers/ReportFeatureAppendableMergerTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.reducers; 2 | 3 | import net.masterthought.cucumber.json.Feature; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.List; 7 | 8 | import static java.util.Collections.singletonList; 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | class ReportFeatureAppendableMergerTest { 12 | 13 | @Test 14 | void merge_NullSafe() { 15 | // given 16 | ReportFeatureAppendableMerger merger = new ReportFeatureAppendableMerger(); 17 | 18 | // when 19 | List result = merger.merge(null); 20 | 21 | // then 22 | assertThat(result).isNotNull().isEmpty(); 23 | } 24 | 25 | @Test 26 | void merge_ReturnsOriginArray() { 27 | // given 28 | List origin = singletonList(new Feature()); 29 | 30 | // when 31 | List actual = new ReportFeatureAppendableMerger().merge(origin); 32 | 33 | // then 34 | assertThat(actual).isSameAs(origin); 35 | } 36 | 37 | @Test 38 | void checksMergerIsAvailableForAllReducingMethods() { 39 | // given 40 | ReportFeatureAppendableMerger merger = new ReportFeatureAppendableMerger(); 41 | 42 | // when 43 | // then 44 | for (ReducingMethod m : ReducingMethod.values()) { 45 | assertThat(merger.test(singletonList(m))).isTrue(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/reducers/ReportFeatureByIdMergerTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.reducers; 2 | 3 | import net.masterthought.cucumber.ReportGenerator; 4 | import net.masterthought.cucumber.json.Feature; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | import static net.masterthought.cucumber.reducers.ReducingMethod.MERGE_FEATURES_BY_ID; 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | 14 | class ReportFeatureByIdMergerTest extends ReportGenerator { 15 | 16 | @Test 17 | void merge_TheSameFeatureTwiceById() { 18 | // given 19 | setUpWithJson(SAMPLE_JSON); 20 | Integer expectedSize = reportResult.getAllFeatures().size(); 21 | 22 | List features = new ArrayList<>(); 23 | features.addAll(reportResult.getAllFeatures()); 24 | features.addAll(reportResult.getAllFeatures()); 25 | 26 | // when 27 | List merged = new ReportFeatureByIdMerger().merge(features); 28 | 29 | // then 30 | assertThat(merged).hasSize(expectedSize); 31 | } 32 | 33 | @Test 34 | void check_MergerIsApplicableByType_NullParam() { 35 | // given 36 | ReportFeatureByIdMerger merger = new ReportFeatureByIdMerger(); 37 | 38 | // when 39 | boolean isApplicable = merger.test(null); 40 | 41 | // then 42 | assertThat(isApplicable).isFalse(); 43 | } 44 | 45 | @Test 46 | void check_MergerIsApplicableByType_CorrectParam() { 47 | // given 48 | ReportFeatureByIdMerger merger = new ReportFeatureByIdMerger(); 49 | 50 | // when 51 | boolean isApplicableByType = merger.test(Arrays.asList(MERGE_FEATURES_BY_ID)); 52 | 53 | // then 54 | assertThat(isApplicableByType).isTrue(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/reducers/ReportFeatureMergerFactoryTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.reducers; 2 | 3 | import java.util.Collections; 4 | 5 | import static net.masterthought.cucumber.reducers.ReducingMethod.MERGE_FEATURES_BY_ID; 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | class ReportFeatureMergerFactoryTest { 11 | 12 | @Test 13 | void get_NullSafe() { 14 | // given 15 | // when 16 | ReportFeatureMerger merger = new ReportFeatureMergerFactory().get(null); 17 | 18 | // then 19 | assertThat(merger).isInstanceOf(ReportFeatureAppendableMerger.class); 20 | } 21 | 22 | @Test 23 | void get_FeatureByIdMerger() { 24 | // given 25 | // when 26 | ReportFeatureMerger merger = new ReportFeatureMergerFactory().get(Collections.singletonList(MERGE_FEATURES_BY_ID)); 27 | 28 | // then 29 | assertThat(merger).isInstanceOf(ReportFeatureByIdMerger.class); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/sorting/FeaturesAlphabeticalComparatorTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.sorting; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.Comparator; 6 | 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | import org.powermock.reflect.Whitebox; 10 | 11 | import net.masterthought.cucumber.generators.integrations.PageTest; 12 | import net.masterthought.cucumber.json.Feature; 13 | 14 | /** 15 | * @author Damian Szczepanik (damianszczepanik@github) 16 | */ 17 | class FeaturesAlphabeticalComparatorTest extends PageTest { 18 | 19 | private final Comparator comparator = new FeaturesAlphabeticalComparator(); 20 | 21 | @BeforeEach 22 | void setUp() { 23 | setUpWithJson(SAMPLE_JSON); 24 | } 25 | 26 | @Test 27 | void compareTo_OnSameFeature_ReturnsZero() { 28 | 29 | // given 30 | Feature feature1 = features.get(0); 31 | Feature feature2 = features.get(0); 32 | 33 | // when 34 | int result = comparator.compare(feature1, feature2); 35 | 36 | // then 37 | assertThat(result).isZero(); 38 | } 39 | 40 | @Test 41 | void compareTo_OnSameName_ReturnsNotZero() { 42 | 43 | // given 44 | Feature feature1 = features.get(0); 45 | Feature feature2 = buildFeature(feature1.getName(), "myId", "myFile.json"); 46 | 47 | // then 48 | int result = comparator.compare(feature1, feature2); 49 | 50 | // then 51 | assertThat(result).isEqualTo(feature1.getId().compareTo(feature2.getId())); 52 | } 53 | 54 | @Test 55 | void compareTo_OnSameNameAndId_ReturnsNotZero() { 56 | 57 | // given 58 | Feature feature1 = features.get(0); 59 | Feature feature2 = buildFeature(feature1.getName(), feature1.getId(), "myFile.json"); 60 | 61 | // then 62 | int result = comparator.compare(feature1, feature2); 63 | 64 | // then 65 | assertThat(result).isEqualTo(feature1.getReportFileName().compareTo(feature2.getReportFileName())); 66 | } 67 | 68 | @Test 69 | void compareTo_OnDifferentName_ReturnsNotZero() { 70 | 71 | // given 72 | Feature feature1 = features.get(0); 73 | Feature feature2 = buildFeature(feature1.getName() + "_", feature1.getId(), feature1.getReportFileName()); 74 | 75 | // then 76 | int result = comparator.compare(feature1, feature2); 77 | 78 | // then 79 | assertThat(result).isEqualTo(feature1.getName().compareTo(feature2.getName())); 80 | } 81 | 82 | private static Feature buildFeature(final String name, final String id, final String reportFileName) { 83 | Feature feature = new Feature(); 84 | Whitebox.setInternalState(feature, "name", name); 85 | Whitebox.setInternalState(feature, "id", id); 86 | Whitebox.setInternalState(feature, "reportFileName", reportFileName); 87 | 88 | return feature; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/sorting/SortingMethod.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.sorting; 2 | 3 | /** 4 | * Copy of the class from main profile with one item for testing. 5 | * 6 | * @author Damian Szczepanik (damianszczepanik@github) 7 | */ 8 | public enum SortingMethod { 9 | NATURAL, 10 | ALPHABETICAL, 11 | // this item is only for testing 12 | INVALID 13 | } -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/sorting/StepObjectAlphabeticalComparatorTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.sorting; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.Comparator; 6 | 7 | import net.masterthought.cucumber.json.support.StepObject; 8 | import org.junit.jupiter.api.Test; 9 | 10 | /** 11 | * @author Damian Szczepanik (damianszczepanik@github) 12 | */ 13 | class StepObjectAlphabeticalComparatorTest { 14 | 15 | private final Comparator comparator = new StepObjectAlphabeticalComparator(); 16 | 17 | @Test 18 | void compareTo_OnDifferentLocation_ReturnsNoneZero() { 19 | 20 | // given 21 | StepObject step1 = new StepObject("one"); 22 | StepObject step2 = new StepObject("two"); 23 | 24 | // when 25 | int result = comparator.compare(step1, step2); 26 | 27 | // then 28 | assertThat(result).isNotZero(); 29 | } 30 | 31 | @Test 32 | void compareTo_OnSameLocation_ReturnsZero() { 33 | 34 | // given 35 | StepObject step1 = new StepObject("one"); 36 | StepObject step2 = new StepObject("one"); 37 | 38 | // when 39 | int result = comparator.compare(step1, step2); 40 | 41 | // then 42 | assertThat(result).isZero(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/sorting/TagObjectAlphabeticalComparatorTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.sorting; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.Comparator; 6 | 7 | import net.masterthought.cucumber.json.support.TagObject; 8 | import org.junit.jupiter.api.Test; 9 | 10 | /** 11 | * @author Damian Szczepanik (damianszczepanik@github) 12 | */ 13 | class TagObjectAlphabeticalComparatorTest { 14 | 15 | private final Comparator comparator = new TagObjectAlphabeticalComparator(); 16 | 17 | @Test 18 | void compareTo_OnDifferentTagName_ReturnsNoneZero() { 19 | 20 | // given 21 | TagObject tag1 = new TagObject("one"); 22 | TagObject tag2 = new TagObject("two"); 23 | 24 | // when 25 | int result = comparator.compare(tag1, tag2); 26 | 27 | // then 28 | assertThat(result).isNotZero(); 29 | } 30 | 31 | @Test 32 | void compareTo_OnSameLocation_ReturnsZero() { 33 | 34 | // given 35 | TagObject tag1 = new TagObject("one"); 36 | TagObject tag2 = new TagObject("one"); 37 | 38 | // when 39 | int result = comparator.compare(tag1, tag2); 40 | 41 | // then 42 | assertThat(result).isZero(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/util/CounterTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.util; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 5 | 6 | import org.junit.jupiter.api.Test; 7 | 8 | class CounterTest { 9 | 10 | @Test 11 | void next_shouldIncrement() { 12 | 13 | // given 14 | Counter counter = new Counter(); 15 | int initValue = counter.intValue(); 16 | 17 | // when 18 | int nextValue = counter.next(); 19 | 20 | // then 21 | assertNotEquals(initValue, nextValue); 22 | assertEquals(initValue + 1, nextValue); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/net/masterthought/cucumber/util/UtilTest.java: -------------------------------------------------------------------------------- 1 | package net.masterthought.cucumber.util; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.List; 6 | 7 | import net.masterthought.cucumber.generators.integrations.PageTest; 8 | import net.masterthought.cucumber.json.Hook; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Test; 11 | 12 | /** 13 | * @author Damian Szczepanik (damianszczepanik@github) 14 | */ 15 | class UtilTest extends PageTest { 16 | 17 | @BeforeEach 18 | void setUp() { 19 | setUpWithJson(SAMPLE_JSON); 20 | } 21 | 22 | @Test 23 | void formatAsPercentage_ReturnsFormattedValue() { 24 | 25 | // given 26 | final int[][] values = {{1, 3}, {2, 2}, {1, 5}, {0, 5}}; 27 | String[] formatted = {"33.33%", "100.00%", "20.00%", "0.00%"}; 28 | 29 | // then 30 | for (int i = 0; i < values.length; i++) { 31 | assertThat(Util.formatAsPercentage(values[i][0], values[i][1])).isEqualTo(formatted[i]); 32 | } 33 | } 34 | 35 | @Test 36 | void formatAsPercentage_OnZeroTotal_ReturnsFormattedValue() { 37 | 38 | // given 39 | final int[] values = {1, 2, 0}; 40 | 41 | // when & then 42 | for (int value : values) { 43 | assertThat(Util.formatAsPercentage(value, 0)).isEqualTo("0.00%"); 44 | } 45 | } 46 | 47 | @Test 48 | void formatAsDecimal_ReturnsFormattedValue() { 49 | 50 | // given 51 | final int[][] values = {{1, 3}, {2, 2}, {1, 5}, {0, 5}, {0, 0}}; 52 | String[] formatted = {"33.33", "100.00", "20.00", "0.00", "0.00"}; 53 | 54 | // when & then 55 | for (int i = 0; i < values.length; i++) { 56 | assertThat(Util.formatAsDecimal(values[i][0], values[i][1])).isEqualTo(formatted[i]); 57 | } 58 | } 59 | 60 | @Test 61 | void toValidFileName_RemovesInvalidChars() { 62 | 63 | // given 64 | final String[] ids = {"simpleFile", "file-dash", "東京", "żółć"}; 65 | final String[] hashes = {"715485773", "784542018", "2148324698", "2159047995"}; 66 | 67 | // when & then 68 | for (int i = 0; i < ids.length; i++) { 69 | assertThat(Util.toValidFileName(ids[i])).isEqualTo(hashes[i]); 70 | } 71 | } 72 | 73 | @Test 74 | void eliminateEmptyHooks_RemovesEmptyHooks() { 75 | 76 | // given 77 | Hook[] hooks = features.get(0).getElements()[0].getBefore(); 78 | 79 | // when 80 | List reducedHooks = Util.eliminateEmptyHooks(hooks); 81 | 82 | // then 83 | assertThat(reducedHooks).isEmpty(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/test/resources/classifications/duplicate.properties: -------------------------------------------------------------------------------- 1 | BaseUrl_QA:Internal=https://internal.test.com 2 | BaseUrl_QA:External=https://external.test.com 3 | -------------------------------------------------------------------------------- /src/test/resources/classifications/empty.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianszczepanik/cucumber-reporting/39b99d3634b17d021ac42143dd4283e02a629036/src/test/resources/classifications/empty.properties -------------------------------------------------------------------------------- /src/test/resources/classifications/sample_one.properties: -------------------------------------------------------------------------------- 1 | AngularVersion=1.5.11 2 | AutUiVersion=1.25.3 3 | AutApiVersion=1.25.0.7 4 | chromeVersion=61.0.3163.100 5 | firefoxVersion=56.0 6 | -------------------------------------------------------------------------------- /src/test/resources/classifications/sample_two.properties: -------------------------------------------------------------------------------- 1 | NodeJsVersion=8.5.0 2 | Proxy=http=//172.22.240.68:18717 3 | NpmVersion=5.3.0 4 | -------------------------------------------------------------------------------- /src/test/resources/classifications/special_characters.properties: -------------------------------------------------------------------------------- 1 | # You are reading the ".properties" entry. 2 | ! The exclamation mark can also mark text as comments. 3 | # The key characters =, and : should be written with 4 | # a preceding backslash to ensure that they are properly loaded. 5 | # However, there is no need to precede the value characters =, and : by a backslash. 6 | website = https://en.wikipedia.org/ 7 | language = English 8 | # The backslash below tells the application to continue reading 9 | # the value onto the next line. 10 | message = Welcome to \ 11 | Wikipedia! 12 | # Add spaces to the key 13 | key\ with\ spaces = This is the value that could be looked up with the key "key with spaces". 14 | # Unicode 15 | tab : \u0009 16 | # If you want your property to include a backslash, it should be escaped by another backslash 17 | path=c:\\wiki\\templates 18 | # However, some editors will handle this automatically 19 | -------------------------------------------------------------------------------- /src/test/resources/css/stackoverflow-light.min.css: -------------------------------------------------------------------------------- 1 | pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*! 2 | Theme: StackOverflow Light 3 | Description: Light theme as used on stackoverflow.com 4 | Author: stackoverflow.com 5 | Maintainer: @Hirse 6 | Website: https://github.com/StackExchange/Stacks 7 | License: MIT 8 | Updated: 2021-05-15 9 | 10 | Updated for @stackoverflow/stacks v0.64.0 11 | Code Blocks: /blob/v0.64.0/lib/css/components/_stacks-code-blocks.less 12 | Colors: /blob/v0.64.0/lib/css/exports/_stacks-constants-colors.less 13 | */.hljs{color:#2f3337;background:#f6f6f6}.hljs-subst{color:#2f3337}.hljs-comment{color:#656e77}.hljs-attr,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-section,.hljs-selector-tag{color:#015692}.hljs-attribute{color:#803378}.hljs-name,.hljs-number,.hljs-quote,.hljs-selector-id,.hljs-template-tag,.hljs-type{color:#b75501}.hljs-selector-class{color:#015692}.hljs-link,.hljs-regexp,.hljs-selector-attr,.hljs-string,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#54790d}.hljs-meta,.hljs-selector-pseudo{color:#015692}.hljs-built_in,.hljs-literal,.hljs-title{color:#b75501}.hljs-bullet,.hljs-code{color:#535a60}.hljs-meta .hljs-string{color:#54790d}.hljs-deletion{color:#c02d2e}.hljs-addition{color:#2f6f44}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700} -------------------------------------------------------------------------------- /src/test/resources/css/test.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianszczepanik/cucumber-reporting/39b99d3634b17d021ac42143dd4283e02a629036/src/test/resources/css/test.css -------------------------------------------------------------------------------- /src/test/resources/cucumber-trends.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildNumbers": [ 3 | "01_first", "other build", "05last" 4 | ], 5 | 6 | "passedFeatures": [ 7 | 9, 18, 25 8 | ], 9 | "failedFeatures": [ 10 | 1, 2, 5 11 | ], 12 | "totalFeatures": [ 13 | 10, 20, 30 14 | ], 15 | 16 | "passedScenarios": [ 17 | 10, 20, 20 18 | ], 19 | "failedScenarios": [ 20 | 10, 20, 20 21 | ], 22 | "totalScenarios": [ 23 | 10, 2, 5 24 | ], 25 | 26 | "passedSteps": [ 27 | 1, 3, 5 28 | ], 29 | "failedSteps": [ 30 | 10, 30, 50 31 | ], 32 | "skippedSteps": [ 33 | 100, 300, 500 34 | ], 35 | "pendingSteps": [ 36 | 1000, 3000, 5000 37 | ], 38 | "undefinedSteps": [ 39 | 10000, 30000, 50000 40 | ], 41 | "totalSteps": [ 42 | 100000, 300000, 500000 43 | ], 44 | "durations": [ 45 | 3206126182398, 3206126182399, 3206126182310 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /src/test/resources/demo-trends.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildNumbers": [ 3 | "01_first", "other build", "05last" 4 | ], 5 | 6 | "passedFeatures": [ 7 | 9, 18, 25 8 | ], 9 | "failedFeatures": [ 10 | 1, 2, 5 11 | ], 12 | "totalFeatures": [ 13 | 10, 20, 30 14 | ], 15 | 16 | "passedScenarios": [ 17 | 10, 20, 20 18 | ], 19 | "failedScenarios": [ 20 | 10, 20, 20 21 | ], 22 | "totalScenarios": [ 23 | 10, 2, 5 24 | ], 25 | 26 | "passedSteps": [ 27 | 1, 3, 5 28 | ], 29 | "failedSteps": [ 30 | 10, 30, 50 31 | ], 32 | "skippedSteps": [ 33 | 100, 300, 500 34 | ], 35 | "pendingSteps": [ 36 | 1000, 3000, 5000 37 | ], 38 | "undefinedSteps": [ 39 | 10000, 30000, 50000 40 | ], 41 | "totalSteps": [ 42 | 100000, 300000, 500000 43 | ], 44 | "durations": [ 45 | 3206126182398, 3206126182399, 3206126182310 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /src/test/resources/js/LICENSE_highlightjs: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2006, Ivan Sagalaev. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /src/test/resources/js/enable-highlighting.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', (event) => { 2 | // included highlight.js script is trimmed to only support these languages 3 | // visit https://highlightjs.org/ for more information 4 | let fileTypes = [ '.xml', '.json' ]; 5 | 6 | fileTypes.forEach((ext) => { 7 | document.querySelectorAll(`a[href\$='${ext}']`).forEach((el) => { 8 | hljs.highlightElement(el.parentElement.parentElement.querySelector('pre')); 9 | }); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/test/resources/js/test.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianszczepanik/cucumber-reporting/39b99d3634b17d021ac42143dd4283e02a629036/src/test/resources/js/test.js -------------------------------------------------------------------------------- /src/test/resources/json/empty-file.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/damianszczepanik/cucumber-reporting/39b99d3634b17d021ac42143dd4283e02a629036/src/test/resources/json/empty-file.json -------------------------------------------------------------------------------- /src/test/resources/json/empty.json: -------------------------------------------------------------------------------- 1 | [ 2 | ] -------------------------------------------------------------------------------- /src/test/resources/json/invalid-report.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id":["this is","invalid id"], 4 | "name":"1st feature", 5 | "keyword":"Feature", 6 | "line":2, 7 | "elements":[ 8 | { 9 | "description":"Perfect background", 10 | "name":"Activate Credit Card", 11 | "keyword":"Background", 12 | "line":7, 13 | "steps":[ 14 | { 15 | "result":{ 16 | "duration":99107447000, 17 | "status":"passed" 18 | }, 19 | "name":"I have a new credit card", 20 | "keyword":"Given " 21 | } 22 | ], 23 | "type":"scenario" 24 | } 25 | ], 26 | "uri":"simple:uri" 27 | } 28 | ] 29 | -------------------------------------------------------------------------------- /src/test/resources/json/simple.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id":"simpleId", 4 | "name":"Simple feature", 5 | "keyword":"Feature", 6 | "elements":[ 7 | { 8 | "keyword":"Scenario", 9 | "steps":[ 10 | { 11 | "result":{ 12 | "duration":123456789, 13 | "status":"passed" 14 | }, 15 | "name":"Simple step name", 16 | "keyword":"Given ", 17 | "match":{ 18 | "location":"simple.location()" 19 | } 20 | } 21 | ], 22 | "type":"scenario" 23 | } 24 | ], 25 | "uri":"simple:uri" 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /src/test/resources/json/timestamped/part1.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "line": 1, 4 | "elements": [ 5 | { 6 | "line": 3, 7 | "type": "background", 8 | "keyword": "Background", 9 | "steps": [ 10 | { 11 | "result": { 12 | "duration": 13340837400, 13 | "status": "passed" 14 | }, 15 | "line": 4, 16 | "name": "Open Home page", 17 | "match": { 18 | "location": "HomePageSteps.openHomePage()" 19 | }, 20 | "keyword": "Given " 21 | } 22 | ] 23 | }, 24 | { 25 | "start_timestamp": "2019-11-25T13:36:37.562Z", 26 | "line": 7, 27 | "name": "All categories are displayed on site", 28 | "id": "product-categories;all-categories-are-displayed-on-site", 29 | "after": [ 30 | { 31 | "result": { 32 | "duration": 944241801, 33 | "status": "passed" 34 | }, 35 | "match": { 36 | "location": "ContextShutdownHook.teardown()" 37 | } 38 | } 39 | ], 40 | "type": "scenario", 41 | "keyword": "Scenario", 42 | "steps": [ 43 | { 44 | "result": { 45 | "duration": 391179701, 46 | "status": "passed" 47 | }, 48 | "line": 8, 49 | "name": "Get list of existing products", 50 | "match": { 51 | "location": "ProductDataSteps.readListOfExistingProducts()" 52 | }, 53 | "keyword": "When " 54 | }, 55 | { 56 | "result": { 57 | "duration": 620118000, 58 | "status": "passed" 59 | }, 60 | "line": 9, 61 | "name": "List of categories is displayed on Home page and contains all values", 62 | "match": { 63 | "location": "HomePageSteps.verifyListOfCategories()" 64 | }, 65 | "keyword": "Then " 66 | } 67 | ] 68 | } 69 | ], 70 | "name": "Product categories", 71 | "id": "product-categories", 72 | "keyword": "Feature", 73 | "uri": "classpath:reporting/categories.feature" 74 | } 75 | ] 76 | -------------------------------------------------------------------------------- /src/test/resources/json/timestamped/part2-rerun-failed.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "line": 1, 4 | "elements": [ 5 | { 6 | "start_timestamp": "2019-11-25T13:36:53.146Z", 7 | "line": 4, 8 | "name": "Open Home page", 9 | "id": "home-page;open-home-page", 10 | "after": [ 11 | { 12 | "result": { 13 | "duration": 853411900, 14 | "status": "passed" 15 | }, 16 | "match": { 17 | "location": "ContextShutdownHook.teardown()" 18 | } 19 | } 20 | ], 21 | "type": "scenario", 22 | "keyword": "Scenario", 23 | "steps": [ 24 | { 25 | "result": { 26 | "duration": 12449931600, 27 | "status": "failed" 28 | }, 29 | "line": 5, 30 | "name": "Open Home page", 31 | "match": { 32 | "location": "HomePageSteps.openHomePage()" 33 | }, 34 | "keyword": "Given " 35 | } 36 | ] 37 | } 38 | ], 39 | "name": "Home page", 40 | "id": "home-page", 41 | "keyword": "Feature", 42 | "uri": "classpath:reporting/home-page.feature" 43 | } 44 | ] 45 | -------------------------------------------------------------------------------- /src/test/resources/json/timestamped/part2-rerun-passed.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "line": 1, 4 | "elements": [ 5 | { 6 | "start_timestamp": "2019-11-25T13:36:53.146Z", 7 | "line": 4, 8 | "name": "Open Home page", 9 | "id": "home-page;open-home-page", 10 | "after": [ 11 | { 12 | "result": { 13 | "duration": 853411900, 14 | "status": "passed" 15 | }, 16 | "match": { 17 | "location": "ContextShutdownHook.teardown()" 18 | } 19 | } 20 | ], 21 | "type": "scenario", 22 | "keyword": "Scenario", 23 | "steps": [ 24 | { 25 | "result": { 26 | "duration": 12449931600, 27 | "status": "passed" 28 | }, 29 | "line": 5, 30 | "name": "Open Home page", 31 | "match": { 32 | "location": "HomePageSteps.openHomePage()" 33 | }, 34 | "keyword": "Given " 35 | } 36 | ] 37 | } 38 | ], 39 | "name": "Home page", 40 | "id": "home-page", 41 | "keyword": "Feature", 42 | "uri": "classpath:reporting/home-page.feature" 43 | } 44 | ] 45 | -------------------------------------------------------------------------------- /src/test/resources/json/timestamped/part2.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "line": 1, 4 | "elements": [ 5 | { 6 | "start_timestamp": "2019-11-25T13:36:53.146Z", 7 | "line": 4, 8 | "name": "Open Home page", 9 | "id": "home-page;open-home-page", 10 | "after": [ 11 | { 12 | "result": { 13 | "duration": 853411700, 14 | "status": "passed" 15 | }, 16 | "match": { 17 | "location": "ContextShutdownHook.teardown()" 18 | } 19 | } 20 | ], 21 | "type": "scenario", 22 | "keyword": "Scenario", 23 | "steps": [ 24 | { 25 | "result": { 26 | "duration": 12449931300, 27 | "status": "failed" 28 | }, 29 | "line": 5, 30 | "name": "Open Home page", 31 | "match": { 32 | "location": "HomePageSteps.openHomePage()" 33 | }, 34 | "keyword": "Given " 35 | } 36 | ] 37 | } 38 | ], 39 | "name": "Home page", 40 | "id": "home-page", 41 | "keyword": "Feature", 42 | "uri": "classpath:reporting/home-page.feature" 43 | } 44 | ] 45 | -------------------------------------------------------------------------------- /src/test/resources/simplelogger.properties: -------------------------------------------------------------------------------- 1 | org.slf4j.simpleLogger.defaultLogLevel=error 2 | --------------------------------------------------------------------------------