diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 000000000..bb09719d9
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,85 @@
+name: 🐞 Bug Report
+description: File a Bug report in Java Integration
+title: "🐞: "
+labels: [ "type:bug", "triage" ]
+assignees: []
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill out this bug report!
+ - type: textarea
+ id: what-happened
+ attributes:
+ label: What happened?
+ description: Also tell us, what did you expect to happen?
+ placeholder: Tell us what you see!
+ value: "A bug happened!"
+ validations:
+ required: true
+ - type: dropdown
+ id: integration
+ attributes:
+ label: What Allure Integration are you using?
+ multiple: true
+ description: Please select the Allure integration you
+ options:
+ - allure-assertj
+ - allure-attachments
+ - allure-awaitility
+ - allure-citrus
+ - allure-cucumber2-jvm
+ - allure-cucumber3-jvm
+ - allure-cucumber4-jvm
+ - allure-cucumber5-jvm
+ - allure-cucumber6-jvm
+ - allure-cucumber7-jvm
+ - allure-descriptions-javadoc
+ - allure-grpc
+ - allure-hamcrest
+ - allure-httpclient
+ - allure-java-commons
+ - allure-jax-rs
+ - allure-jbehave
+ - allure-jbehave5
+ - allure-jsonunit
+ - allure-junit-platform
+ - allure-junit4
+ - allure-junit5
+ - allure-karate
+ - allure-okhttp
+ - allure-okhttp3
+ - allure-reader
+ - allure-rest-assured
+ - allure-scalatest
+ - allure-selenide
+ - allure-servlet-api
+ - allure-spock
+ - allure-spock2
+ - allure-spring-web
+ - allure-test-filter
+ - allure-testng
+ validations:
+ required: true
+ - type: input
+ id: integration_version
+ attributes:
+ label: What version of Allure Integration you are using?
+ placeholder: 2.22.3
+ validations:
+ required: true
+ - type: input
+ id: allure_report_version
+ attributes:
+ label: What version of Allure Report you are using?
+ placeholder: 2.22.3
+ validations:
+ required: true
+ - type: checkboxes
+ id: terms
+ attributes:
+ label: Code of Conduct
+ description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/allure-framework/allure-java/blob/main/CODE_OF_CONDUCT.md)
+ options:
+ - label: I agree to follow this project's Code of Conduct
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 000000000..fb81bedae
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,11 @@
+blank_issues_enabled: false
+contact_links:
+ - name: ⭐️ Allure Report | main repository | give us a ⭐️
+ url: https://github.com/allure-framework/allure2
+ about: For Allure Report related issues.
+ - name: 💬 Allure Report Community - for bugs, issues, dedicated support and more!
+ url: https://github.com/orgs/allure-framework/discussions
+ about: Please ask and answer questions here.
+ - name: 💚 Allure TestOps Support
+ url: https://help.qameta.io/support/home
+ about: Please report Allure TestOps issues here.
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 0dec83312..8178c9b61 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -3,7 +3,7 @@ Thank you so much for sending us a pull request!
Make sure you have a clear name for your pull request.
The name should start with a capital letter and no dot is required in the end of the sentence.
-To link the request with isses use the following notation: (fixes #123, fixes #321\)
+To link the request with issues use the following notation: (fixes #123, fixes #321\)
An example of good pull request names:
* Add Cucumber integration (fixes #123\)
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 000000000..240d09840
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,15 @@
+version: 2
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "daily"
+ labels:
+ - "type:dependencies"
+
+ - package-ecosystem: "gradle"
+ directory: "/"
+ schedule:
+ interval: "daily"
+ labels:
+ - "type:dependencies"
diff --git a/.github/labeler.yml b/.github/labeler.yml
new file mode 100644
index 000000000..a419c97f9
--- /dev/null
+++ b/.github/labeler.yml
@@ -0,0 +1,84 @@
+"theme:workflow":
+ - ".github/**"
+
+"theme:build":
+ - "gradle/**"
+ - "build.gradle.kts"
+ - "**/build.gradle.kts"
+ - "gradle.properties"
+ - ".gitignore"
+
+"theme:assertj":
+ - "allure-assertj/**"
+
+"theme:attachments":
+ - "allure-attachments/**"
+
+"theme:citrus":
+ - "allure-citrus/**"
+
+"theme:cucumber-jvm":
+ - "allure-cucumber*-jvm/**"
+
+"theme:descriptions-javadoc":
+ - "allure-descriptions-javadoc/**"
+
+"theme:httpclient":
+ - "allure-httpclient/**"
+
+"theme:model":
+ - "allure-model/**"
+
+"theme:core":
+ - "allure-java-commons/**"
+ - "allure-java-commons-test/**"
+ - "allure-test-filter/**"
+
+"theme:jax-rs":
+ - "allure-jax-rs/**"
+
+"theme:jbehave":
+ - "allure-jbehave*/**"
+
+"theme:jsonunit":
+ - "allure-jsonunit/**"
+
+"theme:junit4":
+ - "allure-junit4/**"
+ - "allure-junit4-aspect/**"
+
+"theme:junit-platform":
+ - "allure-junit5/**"
+ - "allure-junit5-assert/**"
+ - "allure-junit-platform/**"
+
+"theme:karate":
+ - "allure-karate/**"
+
+"theme:okhttp":
+ - "allure-okhttp/**"
+ - "allure-okhttp3/**"
+
+"theme:rest-assured":
+ - "allure-rest-assured/**"
+
+"theme:scalatest":
+ - "allure-scalatest/**"
+
+"theme:selenide":
+ - "allure-selenide/**"
+
+"theme:servlet-api":
+ - "allure-servlet-api/**"
+
+"theme:spock":
+ - "allure-spock/**"
+
+"theme:spring":
+ - "allure-spring-web/**"
+
+"theme:testng":
+ - "allure-testng/**"
+
+"theme:hamcrest":
+ - "allure-hamcrest/**"
diff --git a/.github/release.yml b/.github/release.yml
new file mode 100644
index 000000000..7c30a5979
--- /dev/null
+++ b/.github/release.yml
@@ -0,0 +1,25 @@
+# release.yml
+
+changelog:
+ categories:
+ - title: '🚀 New Features'
+ labels:
+ - 'type:new feature'
+ - title: '🔬 Improvements'
+ labels:
+ - 'type:improvement'
+ - title: '🐞 Bug Fixes'
+ labels:
+ - 'type:bug'
+ - title: '⬆️ Dependency Updates'
+ labels:
+ - 'type:dependencies'
+ - title: '📖 Documentation improvements'
+ labels:
+ - 'type:documentation'
+ - title: '⛔️ Security'
+ labels:
+ - 'type:security'
+ - title: '👻 Internal changes'
+ labels:
+ - 'type:internal'
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 000000000..8f537bc9c
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,34 @@
+name: Build
+
+permissions:
+ contents: read
+
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - '*'
+ push:
+ branches:
+ - 'main'
+ - 'hotfix-*'
+
+jobs:
+ build:
+ name: "Build"
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: "Set up JDK"
+ uses: actions/setup-java@v5
+ with:
+ distribution: 'zulu'
+ java-version: 21
+
+ - name: "Build with Gradle"
+ run: ./gradlew build -x test --scan
+
+ - name: "Run tests"
+ if: always()
+ run: ./gradlew --no-build-cache cleanTest test
diff --git a/.github/workflows/dependency-submission.yml b/.github/workflows/dependency-submission.yml
new file mode 100644
index 000000000..748dfe670
--- /dev/null
+++ b/.github/workflows/dependency-submission.yml
@@ -0,0 +1,22 @@
+name: Dependency Submission
+
+on:
+ push:
+ branches:
+ - main
+
+permissions:
+ contents: write
+
+jobs:
+ dependency-submission:
+ name: Dependency Submission
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout sources
+ uses: actions/checkout@v6
+ - name: Generate and submit dependency graph
+ uses: gradle/actions/dependency-submission@v5
+ env:
+ DEPENDENCY_GRAPH_EXCLUDE_PROJECTS: ':allure-java-commons-test'
+ DEPENDENCY_GRAPH_INCLUDE_CONFIGURATIONS: 'runtimeClasspath'
diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml
new file mode 100644
index 000000000..f49976b59
--- /dev/null
+++ b/.github/workflows/labeler.yml
@@ -0,0 +1,17 @@
+name: "Set theme labels"
+
+on:
+ - pull_request_target
+
+permissions:
+ contents: read
+
+jobs:
+ triage:
+ runs-on: ubuntu-latest
+ permissions:
+ pull-requests: write
+ steps:
+ - uses: actions/labeler@v4
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/labels-verify.yml b/.github/workflows/labels-verify.yml
new file mode 100644
index 000000000..7315a905a
--- /dev/null
+++ b/.github/workflows/labels-verify.yml
@@ -0,0 +1,27 @@
+name: "Verify type labels"
+
+on:
+ pull_request_target:
+ types: [opened, labeled, unlabeled, synchronize]
+
+permissions:
+ contents: none
+
+jobs:
+ triage:
+ runs-on: ubuntu-latest
+ permissions:
+ pull-requests: read
+ steps:
+ - uses: baev/action-label-verify@main
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ allowed: |
+ type:bug
+ type:dependencies
+ type:improvement
+ type:internal
+ type:invalid
+ type:new feature
+ type:security
+ type:documentation
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 000000000..2295deb90
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,41 @@
+name: Publish
+
+on:
+ release:
+ types: [ published ]
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: "Set up JDK"
+ uses: actions/setup-java@v5
+ with:
+ distribution: 'zulu'
+ java-version: '21'
+
+ - name: Set up GPG
+ run: echo -n "${GPG_PRIVATE_KEY}" | base64 --decode > ${GITHUB_WORKSPACE}/${GPG_KEY_ID}.gpg
+ env:
+ GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }}
+ GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
+
+ - name: "Gradle Build"
+ run: ./gradlew build -Pversion=${GITHUB_REF:10}
+
+ - name: "Gradle Publish"
+ run: |
+ ./gradlew publishToSonatype closeSonatypeStagingRepository -Pversion=${GITHUB_REF:10} \
+ -Psigning.keyId=${GPG_KEY_ID} \
+ -Psigning.password=${GPG_PASSPHRASE} \
+ -Psigning.secretKeyRingFile=${GITHUB_WORKSPACE}/${GPG_KEY_ID}.gpg
+ env:
+ ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.OSSRH_USERNAME }}
+ ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.OSSRH_PASSWORD }}
+ GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }}
+ GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 000000000..7d5ace556
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,62 @@
+name: Release
+run-name: Release ${{ inputs.releaseVersion }} (next ${{ inputs.nextVersion }}) by ${{ github.actor }}
+
+on:
+ workflow_dispatch:
+ inputs:
+ releaseVersion:
+ description: "The release version in .. format"
+ required: true
+ nextVersion:
+ description: "The next version in . format WITHOUT SNAPSHOT SUFFIX"
+ required: true
+
+permissions:
+ contents: read
+
+jobs:
+ triage:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ steps:
+ - name: "Check release version"
+ run: |
+ expr "${{ github.event.inputs.releaseVersion }}" : '[[:digit:]][[:digit:]]*\.[[:digit:]][[:digit:]]*\.[[:digit:]][[:digit:]]*$'
+ - name: "Check next version"
+ run: |
+ expr "${{ github.event.inputs.nextVersion }}" : '[[:digit:]][[:digit:]]*\.[[:digit:]][[:digit:]]*$'
+ - uses: actions/checkout@v6
+ with:
+ token: ${{ secrets.QAMETA_CI }}
+
+ - name: "Configure CI Git User"
+ run: |
+ git config --global user.name qameta-ci
+ git config --global user.email qameta-ci@qameta.io
+ - name: "Set release version"
+ run: |
+ sed -i -e '/version=/s/.*/version=${{ github.event.inputs.releaseVersion }}/g' gradle.properties
+ cat gradle.properties
+ - name: "Commit release version and create tag"
+ run: |
+ git commit -am "release ${{ github.event.inputs.releaseVersion }}"
+ git tag ${{ github.event.inputs.releaseVersion }}
+ git push origin ${{ github.event.inputs.releaseVersion }}
+ - name: "Set next development version"
+ run: |
+ sed -i -e '/version=/s/.*/version=${{ github.event.inputs.nextVersion }}-SNAPSHOT/g' gradle.properties
+ cat gradle.properties
+ - name: "Commit next development version and push it"
+ run: |
+ git commit -am "set next development version ${{ github.event.inputs.nextVersion }}"
+ git push origin ${{ github.ref }}
+ - name: "Publish Github Release"
+ uses: octokit/request-action@v2.x
+ with:
+ route: POST /repos/${{ github.repository }}/releases
+ tag_name: ${{ github.event.inputs.releaseVersion }}
+ generate_release_notes: true
+ target_commitish: ${{ github.ref }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.QAMETA_CI }}
diff --git a/.gitignore b/.gitignore
index fe90411d6..b85107a90 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,7 +7,9 @@ out
.gradletasknamecache
#IDEA Files
-.idea
+.idea/*
+!.idea/vcs.xml
+!.idea/icon.png
*.iml
*.ipr
@@ -21,4 +23,4 @@ out
.DS_Store
#Netbeans files
-/.nb-gradle/
\ No newline at end of file
+/.nb-gradle/
diff --git a/.idea/icon.png b/.idea/icon.png
new file mode 100644
index 000000000..252994657
Binary files /dev/null and b/.idea/icon.png differ
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 000000000..aeaa9e459
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AUTHORS b/AUTHORS
index 0edbd3fb9..8263d9471 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,4 +1,4 @@
The following authors have created the source code of "Allure Java"
-published and distributed by Qameta Software OÜ as the owner:
+published and distributed by Qameta Software Inc as the owner:
* Dmitry Baev
diff --git a/Jenkinsfile b/Jenkinsfile
deleted file mode 100644
index 64bac5a9e..000000000
--- a/Jenkinsfile
+++ /dev/null
@@ -1,40 +0,0 @@
-pipeline {
- agent { label 'java' }
- parameters {
- booleanParam(name: 'RELEASE', defaultValue: false, description: 'Perform release?')
- string(name: 'RELEASE_VERSION', defaultValue: '', description: 'Release version')
- string(name: 'NEXT_VERSION', defaultValue: '', description: 'Next version (without SNAPSHOT)')
- }
- stages {
- stage('Build') {
- steps {
- sh './gradlew build'
- }
- }
- stage('Release') {
- when { expression { return params.RELEASE } }
- steps {
- withCredentials([usernamePassword(credentialsId: 'qameta-ci_bintray',
- usernameVariable: 'BINTRAY_USER', passwordVariable: 'BINTRAY_API_KEY')]) {
- sshagent(['qameta-ci_ssh']) {
- sh 'git checkout master && git pull origin master'
- sh "./gradlew release -Prelease.useAutomaticVersion=true " +
- "-Prelease.releaseVersion=${RELEASE_VERSION} " +
- "-Prelease.newVersion=${NEXT_VERSION}-SNAPSHOT"
- }
- }
- }
- }
- }
- post {
- always {
- allure results: [[path: '**/build/allure-results']]
- deleteDir()
- }
-
- failure {
- slackSend message: "${env.JOB_NAME} - #${env.BUILD_NUMBER} failed (<${env.BUILD_URL}|Open>)",
- color: 'danger', teamDomain: 'qameta', channel: 'allure', tokenCredentialId: 'allure-channel'
- }
- }
-}
diff --git a/LICENSE b/LICENSE
index a5ff24fc6..aa0c10ae1 100644
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
- Copyright 2019 Qameta Software OÜ
+ Copyright 2016-2024 Qameta Software Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -198,4 +198,4 @@
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
- limitations under the License.
\ No newline at end of file
+ limitations under the License.
diff --git a/README.md b/README.md
index e96e77409..e452787b3 100644
--- a/README.md
+++ b/README.md
@@ -1,171 +1,242 @@
-[license]: http://www.apache.org/licenses/LICENSE-2.0 "Apache License 2.0"
-[blog]: https://qameta.io/blog
-[gitter]: https://gitter.im/allure-framework/allure-core
-[gitter-ru]: https://gitter.im/allure-framework/allure-ru
-[twitter]: https://twitter.com/QametaSoftware "Qameta Software"
-[twitter-team]: https://twitter.com/QametaSoftware/lists/team/members "Team"
-
-[bintray]: https://bintray.com/qameta/maven/allure-java "Bintray"
-[bintray-badge]: https://img.shields.io/bintray/v/qameta/maven/allure-java.svg?style=flat
-
-[CONTRIBUTING.md]: .github/CONTRIBUTING.md
-[docs]: https://docs.qameta.io/allure/2.0/
-
-# Allure Java Integrations [![bintray-badge][]][bintray]
-
-The repository contains new versions of adaptors for JVM-based test frameworks.
-
-All the artifacts are deployed to `https://dl.bintray.com/qameta/maven`.
-
-## TestNG
-
-The new TestNG adaptors is pretty much ready. To use the adaptor you should add the following dependency:
-
-```xml
-
- io.qameta.allure
- allure-testng
- $LATEST_VERSION
-
-```
-
-also you need to configure AspectJ weaver to support steps.
-
-## JUnit 4
-
-The first draft of a new JUnit 4 adaptor is ready. To use the adaptor you should add the following dependency:
-
-```xml
-
- io.qameta.allure
- allure-junit4
- $LATEST_VERSION
-
-```
-
-## JUnit 5
-
-To use JUnit 5 simply add the following dependency to your project:
-
-```xml
-
- io.qameta.allure
- allure-junit5
- $LATEST_VERSION
-
-```
-
-## Selenide
-
-Listener for Selenide, that logging steps for Allure:
-
-```xml
-
- io.qameta.allure
- allure-selenide
- $LATEST_VERSION
-
-```
-
-Usage example:
-```
-SelenideLogger.addListener("AllureSelenide", new AllureSelenide().screenshots(true).savePageSource(false));
-
-Capture selenium logs:
-SelenideLogger.addListener("AllureSelenide", new AllureSelenide().enableLogs(LogType.BROWSER, Level.ALL));
-https://github.com/SeleniumHQ/selenium/wiki/Logging
-```
-
-
-## Rest Assured
-
-Filter for rest-assured http client, that generates attachment for allure.
-
-```xml
-
- io.qameta.allure
- allure-rest-assured
- $LATEST_VERSION
-
-```
-
-Usage example:
-```
-.filter(new AllureRestAssured())
-```
-You can specify custom templates, which should be placed in src/main/resources/tpl folder:
-```
-.filter(new AllureRestAssured()
- .withRequestTemplate("custom-http-request.ftl")
- .withResponseTemplate("custom-http-response.ftl"))
-```
-
-## OkHttp
-
-Interceptor for OkHttp client, that generates attachment for allure.
-
-```xml
-
- io.qameta.allure
- allure-okhttp3
- $LATEST_VERSION
-
-```
-
-Usage example:
-```
-.addInterceptor(new AllureOkHttp3())
-```
-You can specify custom templates, which should be placed in src/main/resources/tpl folder:
-```
-.addInterceptor(new AllureOkHttp3()
- .withRequestTemplate("custom-http-request.ftl")
- .withResponseTemplate("custom-http-response.ftl"))
-
-```
-
-## Http client
-
-Interceptors for Apache HTTP client, that generates attachment for allure.
-
-```xml
-
- io.qameta.allure
- allure-httpclient
- $LATEST_VERSION
-
-```
-
-Usage example:
-```
-.addInterceptorFirst(new AllureHttpClientRequest())
-.addInterceptorLast(new AllureHttpClientResponse());
-```
-
-## JAX-RS Filter
-
-Filter that can be used with JAX-RS compliant clients such as RESTeasy and Jersey
-
-```xml
-
- io.qameta.allure
- allure-jax-rs
- $LATEST_VERSION
-
-```
-
-Usage example:
-```
-.register(AllureJaxRs.class)
-```
-
-## JsonUnit
-JsonPatchMatcher is extension of JsonUnit matcher, that generates pretty html attachment for differences based on [json diff patch](https://github.com/benjamine/jsondiffpatch/blob/master/docs/deltas.md).
-
-```xml
-
- io.qameta.allure
- allure-jsonunit
- $LATEST_VERSION
-
-```
+[license]: http://www.apache.org/licenses/LICENSE-2.0 "Apache License 2.0"
+[blog]: https://qameta.io/blog
+[gitter]: https://gitter.im/allure-framework/allure-core
+[gitter-ru]: https://gitter.im/allure-framework/allure-ru
+[twitter]: https://twitter.com/QametaSoftware "Qameta Software"
+[twitter-team]: https://twitter.com/QametaSoftware/lists/team/members "Team"
+
+[CONTRIBUTING.md]: .github/CONTRIBUTING.md
+[docs]: https://allurereport.org/docs/
+
+# Allure Java Integrations
+
+[](https://github.com/allure-framework/allure-java/actions/workflows/build.yml)
+[](https://github.com/allure-framework/allure-java/releases/latest)
+
+> The repository contains new versions of adaptors for JVM-based test frameworks.
+
+[ ](https://allurereport.org "Allure Report")
+
+- Learn more about Allure Report at [https://allurereport.org](https://allurereport.org)
+- 📚 [Documentation](https://allurereport.org/docs/) – discover official documentation for Allure Report
+- ❓ [Questions and Support](https://github.com/orgs/allure-framework/discussions/categories/questions-support) – get help from the team and community
+- 📢 [Official announcements](https://github.com/orgs/allure-framework/discussions/categories/announcements) – stay updated with our latest news and updates
+- 💬 [General Discussion](https://github.com/orgs/allure-framework/discussions/categories/general-discussion) – engage in casual conversations, share insights and ideas with the community
+- 🖥️ [Live Demo](https://demo.allurereport.org/) — explore a live example of Allure Report in action
+
+---
+## TestNG
+
+- 🚀 Documentation — https://allurereport.org/docs/testng/
+- 📚 Example project — https://github.com/allure-examples?q=topic%3Atestng
+- ✅ Generate a project in 10 seconds via Allure Start - https://allurereport.org/start/
+
+## JUnit 4
+
+- 🚀 Documentation — work in progress
+- 📚 Example project — https://github.com/allure-examples?q=topic%3Ajunit4
+- ✅ Generate a project in 10 seconds via Allure Start - https://allurereport.org/start/
+-
+## JUnit 5
+
+- 🚀 Documentation — https://allurereport.org/docs/junit5/
+- 📚 Example project — https://github.com/allure-examples?q=topic%3Ajunit5
+- ✅ Generate a project in 10 seconds via Allure Start - https://allurereport.org/start/
+
+## Cucumber JVM
+
+- 🚀 Documentation — https://allurereport.org/docs/cucumberjvm/
+- 📚 Example project — https://github.com/allure-examples?q=cucumber&type=all&language=java
+- ✅ Generate a project in 10 seconds via Allure Start - https://allurereport.org/start/
+
+## Spock
+
+- 🚀 Documentation — https://allurereport.org/docs/spock/
+- 📚 Example project — https://github.com/allure-examples?q=topic%3Aspock
+- ✅ Generate a project in 10 seconds via Allure Start - https://allurereport.org/start/
+
+## Selenide
+
+Listener for Selenide, that logging steps for Allure:
+
+```xml
+
+ io.qameta.allure
+ allure-selenide
+ $LATEST_VERSION
+
+```
+
+Usage example:
+```
+SelenideLogger.addListener("AllureSelenide", new AllureSelenide().screenshots(true).savePageSource(false));
+
+Capture selenium logs:
+SelenideLogger.addListener("AllureSelenide", new AllureSelenide().enableLogs(LogType.BROWSER, Level.ALL));
+https://github.com/SeleniumHQ/selenium/wiki/Logging
+```
+
+
+## Rest Assured
+
+Filter for rest-assured http client, that generates attachment for allure.
+
+```xml
+
+ io.qameta.allure
+ allure-rest-assured
+ $LATEST_VERSION
+
+```
+
+Usage example:
+```
+.filter(new AllureRestAssured())
+```
+You can specify custom templates, which should be placed in src/main/resources/tpl folder:
+```
+.filter(new AllureRestAssured()
+ .withRequestTemplate("custom-http-request.ftl")
+ .withResponseTemplate("custom-http-response.ftl"))
+```
+
+## OkHttp
+
+Interceptor for OkHttp client, that generates attachment for allure.
+
+```xml
+
+ io.qameta.allure
+ allure-okhttp3
+ $LATEST_VERSION
+
+```
+
+Usage example:
+```
+.addInterceptor(new AllureOkHttp3())
+```
+You can specify custom templates, which should be placed in src/main/resources/tpl folder:
+```
+.addInterceptor(new AllureOkHttp3()
+ .withRequestTemplate("custom-http-request.ftl")
+ .withResponseTemplate("custom-http-response.ftl"))
+
+```
+
+## gRPC
+
+Interceptor for gRPC stubs, that generates attachment for allure.
+
+```xml
+
+ io.qameta.allure
+ allure-grpc
+ $LATEST_VERSION
+
+```
+
+Usage example:
+```
+.newBlockingStub(channel).withInterceptors(new AllureGrpc());
+```
+You can enable interception of response metadata (disabled by default)
+```
+.withInterceptors(new AllureGrpc()
+ .interceptResponseMetadata(true))
+```
+By default, a step will be marked as failed in case that response contains any statuses except 0(OK).
+You can change this behavior, for example, for negative scenarios
+```
+.withInterceptors(new AllureGrpc()
+ .markStepFailedOnNonZeroCode(false))
+```
+You can specify custom templates, which should be placed in src/main/resources/tpl folder:
+```
+.withInterceptors(new AllureGrpc()
+ .setRequestTemplate("custom-http-request.ftl")
+ .setResponseTemplate("custom-http-response.ftl"))
+```
+
+## Http client
+
+Interceptors for Apache HTTP client, that generates attachment for allure.
+
+```xml
+
+ io.qameta.allure
+ allure-httpclient
+ $LATEST_VERSION
+
+```
+
+Usage example:
+```
+.addInterceptorFirst(new AllureHttpClientRequest())
+.addInterceptorLast(new AllureHttpClientResponse());
+```
+
+## Http client 5
+Interceptors for Apache [httpclient5](https://hc.apache.org/httpcomponents-client-5.2.x/index.html).
+Additional info can be found in module `allure-httpclient5`
+
+```xml
+
+ io.qameta.allure
+ allure-httpclient5
+ $LATEST_VERSION
+
+```
+
+Usage example:
+```java
+final HttpClientBuilder builder = HttpClientBuilder.create()
+ .addRequestInterceptorFirst(new AllureHttpClient5Request("your-request-template-attachment.ftl"))
+ .addResponseInterceptorLast(new AllureHttpClient5Response("your-response-template-attachment.ftl"));
+```
+
+## JAX-RS Filter
+
+Filter that can be used with JAX-RS compliant clients such as RESTeasy and Jersey
+
+```xml
+
+ io.qameta.allure
+ allure-jax-rs
+ $LATEST_VERSION
+
+```
+
+Usage example:
+```
+.register(AllureJaxRs.class)
+```
+
+## JsonUnit
+JsonPatchMatcher is extension of JsonUnit matcher, that generates pretty html attachment for differences based on [json diff patch](https://github.com/benjamine/jsondiffpatch/blob/master/docs/deltas.md).
+
+```xml
+
+ io.qameta.allure
+ allure-jsonunit
+ $LATEST_VERSION
+
+```
+
+## Awaitility
+Extended logging for poling and ignored exceptions for [awaitility](https://github.com/awaitility/awaitility). For
+more usage example look into module `allure-awaitility`
+
+```xml
+
+ io.qameta.allure
+ allure-awaitility
+ $LATEST_VERSION
+
+```
+
+Usage example:
+```
+Awaitility.setDefaultConditionEvaluationListener(new AllureAwaitilityListener());
+```
+
diff --git a/allure-assertj/build.gradle.kts b/allure-assertj/build.gradle.kts
index e8e064ed9..e38cca0ea 100644
--- a/allure-assertj/build.gradle.kts
+++ b/allure-assertj/build.gradle.kts
@@ -1,9 +1,6 @@
description = "Allure AssertJ Integration"
-val agent: Configuration by configurations.creating
-
dependencies {
- agent("org.aspectj:aspectjweaver")
api(project(":allure-java-commons"))
compileOnly("org.aspectj:aspectjrt")
compileOnly("org.assertj:assertj-core")
@@ -25,7 +22,4 @@ tasks.jar {
tasks.test {
useJUnitPlatform()
- doFirst {
- jvmArgs("-javaagent:${agent.singleFile}")
- }
}
diff --git a/allure-assertj/src/main/java/io/qameta/allure/assertj/AllureAspectJ.java b/allure-assertj/src/main/java/io/qameta/allure/assertj/AllureAspectJ.java
index e54ca815e..128791172 100644
--- a/allure-assertj/src/main/java/io/qameta/allure/assertj/AllureAspectJ.java
+++ b/allure-assertj/src/main/java/io/qameta/allure/assertj/AllureAspectJ.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -55,7 +55,7 @@ protected AllureLifecycle initialValue() {
}
};
- @Pointcut("execution(public org.assertj.core.api.AbstractAssert.new(..))")
+ @Pointcut("execution(!private org.assertj.core.api.AbstractAssert.new(..))")
public void anyAssertCreation() {
//pointcut body, should be empty
}
diff --git a/allure-assertj/src/test/java/io/qameta/allure/assertj/AllureAspectJTest.java b/allure-assertj/src/test/java/io/qameta/allure/assertj/AllureAspectJTest.java
index 7a48210c8..57ddef240 100644
--- a/allure-assertj/src/test/java/io/qameta/allure/assertj/AllureAspectJTest.java
+++ b/allure-assertj/src/test/java/io/qameta/allure/assertj/AllureAspectJTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
import static io.qameta.allure.test.RunUtils.runWithinTestContext;
import static org.assertj.core.api.Assertions.assertThat;
@@ -82,11 +83,28 @@ void shouldHandleByteArrayObject() {
.extracting(StepResult::getName)
.containsExactly(
"assertThat ''",
- "as 'Byte array object []'",
+ "describedAs 'Byte array object'",
"isEqualTo ''"
);
}
+ @AllureFeatures.Steps
+ @Test
+ void shouldHandleCollections() {
+ final AllureResults results = runWithinTestContext(() -> {
+ assertThat(Arrays.asList("a", "b"))
+ .containsExactly("a", "b");
+ }, AllureAspectJ::setLifecycle);
+
+ assertThat(results.getTestResults())
+ .flatExtracting(TestResult::getSteps)
+ .extracting(StepResult::getName)
+ .containsExactly(
+ "assertThatList '[a, b]'",
+ "containsExactly '[a, b]'"
+ );
+ }
+
@AllureFeatures.Steps
@Test
void softAssertions() {
diff --git a/allure-attachments/build.gradle.kts b/allure-attachments/build.gradle.kts
index bfbe95e7c..c5010a3c0 100644
--- a/allure-attachments/build.gradle.kts
+++ b/allure-attachments/build.gradle.kts
@@ -1,9 +1,6 @@
description = "Allure Attachments"
-val agent: Configuration by configurations.creating
-
dependencies {
- agent("org.aspectj:aspectjweaver")
api(project(":allure-java-commons"))
implementation("org.freemarker:freemarker")
testImplementation("org.apache.commons:commons-lang3")
@@ -26,7 +23,4 @@ tasks.jar {
tasks.test {
useJUnitPlatform()
- doFirst {
- jvmArgs("-javaagent:${agent.singleFile}")
- }
}
diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentContent.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentContent.java
index c8fe7c111..50af67ea4 100644
--- a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentContent.java
+++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentContent.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentData.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentData.java
index 86b318a05..5c6d09f34 100644
--- a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentData.java
+++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentData.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
*
* @author charlie (Dmitry Baev).
*/
+@FunctionalInterface
public interface AttachmentData {
String getName();
diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentProcessor.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentProcessor.java
index cfc93c716..99129f89a 100644
--- a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentProcessor.java
+++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
* @param the type of attachment data.
* @author charlie (Dmitry Baev).
*/
+@FunctionalInterface
public interface AttachmentProcessor {
void addAttachment(T attachmentData, AttachmentRenderer renderer);
diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentRenderException.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentRenderException.java
index 5d702bffa..daaf9cc91 100644
--- a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentRenderException.java
+++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentRenderException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentRenderer.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentRenderer.java
index 1d239331e..2b6f5b1a5 100644
--- a/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentRenderer.java
+++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/AttachmentRenderer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
* @author charlie (Dmitry Baev).
*/
@SuppressWarnings("PMD.AvoidUncheckedExceptionsInSignatures")
+@FunctionalInterface
public interface AttachmentRenderer {
AttachmentContent render(T attachmentData) throws AttachmentRenderException;
diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentContent.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentContent.java
index ab26abbb6..ccdc978a8 100644
--- a/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentContent.java
+++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentContent.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentProcessor.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentProcessor.java
index dc57e9a01..652c8ca5f 100644
--- a/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentProcessor.java
+++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/DefaultAttachmentProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/FreemarkerAttachmentRenderer.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/FreemarkerAttachmentRenderer.java
index 9179f3ed7..50ac53924 100644
--- a/allure-attachments/src/main/java/io/qameta/allure/attachment/FreemarkerAttachmentRenderer.java
+++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/FreemarkerAttachmentRenderer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,9 @@
import freemarker.template.Configuration;
import freemarker.template.Template;
+import freemarker.template.TemplateExceptionHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.StringWriter;
import java.io.Writer;
@@ -27,6 +30,8 @@
*/
public class FreemarkerAttachmentRenderer implements AttachmentRenderer {
+ private static final Logger LOGGER = LoggerFactory.getLogger(FreemarkerAttachmentRenderer.class);
+
private final Configuration configuration;
private final String templateName;
@@ -36,6 +41,7 @@ public FreemarkerAttachmentRenderer(final String templateName) {
this.configuration = new Configuration(Configuration.VERSION_2_3_23);
this.configuration.setLocalizedLookup(false);
this.configuration.setTemplateUpdateDelayMilliseconds(0);
+ this.configuration.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER);
this.configuration.setClassLoaderForTemplateLoading(getClass().getClassLoader(), "tpl");
}
@@ -46,6 +52,7 @@ public DefaultAttachmentContent render(final AttachmentData data) {
template.process(Collections.singletonMap("data", data), writer);
return new DefaultAttachmentContent(writer.toString(), "text/html", ".html");
} catch (Exception e) {
+ LOGGER.debug(data.toString());
throw new AttachmentRenderException("Could't render http attachment file", e);
}
}
diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/http/HttpRequestAttachment.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/http/HttpRequestAttachment.java
index 831b2a5da..a51052cf7 100644
--- a/allure-attachments/src/main/java/io/qameta/allure/attachment/http/HttpRequestAttachment.java
+++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/http/HttpRequestAttachment.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,9 @@
package io.qameta.allure.attachment.http;
import io.qameta.allure.attachment.AttachmentData;
+import io.qameta.allure.util.ObjectUtils;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@@ -40,9 +42,18 @@ public class HttpRequestAttachment implements AttachmentData {
private final Map cookies;
+ private final Map formParams;
+
public HttpRequestAttachment(final String name, final String url, final String method,
final String body, final String curl, final Map headers,
final Map cookies) {
+ this(name, url, method, body, curl, headers, cookies, Collections.emptyMap());
+ }
+
+ @SuppressWarnings("checkstyle:parameternumber")
+ public HttpRequestAttachment(final String name, final String url, final String method,
+ final String body, final String curl, final Map headers,
+ final Map cookies, final Map formParams) {
this.name = name;
this.url = url;
this.method = method;
@@ -50,6 +61,7 @@ public HttpRequestAttachment(final String name, final String url, final String m
this.curl = curl;
this.headers = headers;
this.cookies = cookies;
+ this.formParams = formParams;
}
public String getUrl() {
@@ -72,6 +84,10 @@ public Map getCookies() {
return cookies;
}
+ public Map getFormParams() {
+ return formParams;
+ }
+
public String getCurl() {
return curl;
}
@@ -81,10 +97,21 @@ public String getName() {
return name;
}
+ @Override
+ public String toString() {
+ return "HttpRequestAttachment("
+ + "\n\tname=" + this.name
+ + ",\n\turl=" + this.url
+ + ",\n\tbody=" + this.body
+ + ",\n\theaders=" + ObjectUtils.mapToString(this.headers)
+ + ",\n\tcookies=" + ObjectUtils.mapToString(this.cookies)
+ + ",\n\tformParams=" + ObjectUtils.mapToString(this.formParams)
+ + "\n)";
+ }
+
/**
* Builder for HttpRequestAttachment.
*/
- @SuppressWarnings("PMD.AvoidFieldNameMatchingMethodName")
public static final class Builder {
private final String name;
@@ -99,6 +126,8 @@ public static final class Builder {
private final Map cookies = new HashMap<>();
+ private final Map formParams = new HashMap<>();
+
private Builder(final String name, final String url) {
Objects.requireNonNull(name, "Name must not be null value");
Objects.requireNonNull(url, "Url must not be null value");
@@ -148,6 +177,12 @@ public Builder setBody(final String body) {
return this;
}
+ public Builder setFormParams(final Map formParams) {
+ Objects.requireNonNull(formParams, "Form params must not be null value");
+ this.formParams.putAll(formParams);
+ return this;
+ }
+
/**
* Use setter method instead.
* @deprecated scheduled for removal in 3.0 release
@@ -203,7 +238,7 @@ public Builder withBody(final String body) {
}
public HttpRequestAttachment build() {
- return new HttpRequestAttachment(name, url, method, body, getCurl(), headers, cookies);
+ return new HttpRequestAttachment(name, url, method, body, getCurl(), headers, cookies, formParams);
}
private String getCurl() {
@@ -214,6 +249,7 @@ private String getCurl() {
builder.append(" '").append(url).append('\'');
headers.forEach((key, value) -> appendHeader(builder, key, value));
cookies.forEach((key, value) -> appendCookie(builder, key, value));
+ formParams.forEach((key, value) -> appendFormParams(builder, key, value));
if (Objects.nonNull(body)) {
builder.append(" -d '").append(body).append('\'');
@@ -236,5 +272,13 @@ private static void appendCookie(final StringBuilder builder, final String key,
.append(value)
.append('\'');
}
+
+ private static void appendFormParams(final StringBuilder builder, final String key, final String value) {
+ builder.append(" --form '")
+ .append(key)
+ .append('=')
+ .append(value)
+ .append('\'');
+ }
}
}
diff --git a/allure-attachments/src/main/java/io/qameta/allure/attachment/http/HttpResponseAttachment.java b/allure-attachments/src/main/java/io/qameta/allure/attachment/http/HttpResponseAttachment.java
index 0b5ef9841..d606af57e 100644
--- a/allure-attachments/src/main/java/io/qameta/allure/attachment/http/HttpResponseAttachment.java
+++ b/allure-attachments/src/main/java/io/qameta/allure/attachment/http/HttpResponseAttachment.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package io.qameta.allure.attachment.http;
import io.qameta.allure.attachment.AttachmentData;
+import io.qameta.allure.util.ObjectUtils;
import java.util.HashMap;
import java.util.Map;
@@ -74,10 +75,21 @@ public Map getCookies() {
return cookies;
}
+ @Override
+ public String toString() {
+ return "HttpResponseAttachment("
+ + "\n\tname=" + this.name
+ + ",\n\turl=" + this.url
+ + ",\n\tbody=" + this.body
+ + ",\n\tresponseCode=" + this.responseCode
+ + ",\n\theaders=" + ObjectUtils.mapToString(this.headers)
+ + ",\n\tcookies=" + ObjectUtils.mapToString(this.cookies)
+ + "\n)";
+ }
+
/**
* Builder for HttpRequestAttachment.
*/
- @SuppressWarnings("PMD.AvoidFieldNameMatchingMethodName")
public static final class Builder {
private final String name;
@@ -146,6 +158,7 @@ public Builder setBody(final String body) {
/**
* Use setter method instead.
+ *
* @deprecated scheduled for removal in 3.0 release
*/
@Deprecated
@@ -155,6 +168,7 @@ public Builder withUrl(final String url) {
/**
* Use setter method instead.
+ *
* @deprecated scheduled for removal in 3.0 release
*/
@Deprecated
@@ -164,6 +178,7 @@ public Builder withResponseCode(final int responseCode) {
/**
* Use setter method instead.
+ *
* @deprecated scheduled for removal in 3.0 release
*/
@Deprecated
@@ -173,6 +188,7 @@ public Builder withHeader(final String name, final String value) {
/**
* Use setter method instead.
+ *
* @deprecated scheduled for removal in 3.0 release
*/
@Deprecated
@@ -182,6 +198,7 @@ public Builder withHeaders(final Map headers) {
/**
* Use setter method instead.
+ *
* @deprecated scheduled for removal in 3.0 release
*/
@Deprecated
@@ -191,6 +208,7 @@ public Builder withCookie(final String name, final String value) {
/**
* Use setter method instead.
+ *
* @deprecated scheduled for removal in 3.0 release
*/
@Deprecated
@@ -200,6 +218,7 @@ public Builder withCookies(final Map cookies) {
/**
* Use setter method instead.
+ *
* @deprecated scheduled for removal in 3.0 release
*/
@Deprecated
diff --git a/allure-attachments/src/main/resources/tpl/http-request.ftl b/allure-attachments/src/main/resources/tpl/http-request.ftl
index 5ef76b521..935c1c91a 100644
--- a/allure-attachments/src/main/resources/tpl/http-request.ftl
+++ b/allure-attachments/src/main/resources/tpl/http-request.ftl
@@ -3,36 +3,45 @@
<#if data.method??>${data.method}<#else>GET#if> to <#if data.url??>${data.url}<#else>Unknown#if>
<#if data.body??>
-Body
-
+
Body
+
- ${data.body}
+ <#t>${data.body}
-
+
#if>
<#if (data.headers)?has_content>
-Headers
-
- <#list data.headers as name, value>
-
${name}: ${value}
- #list>
-
+ Headers
+
+ <#list data.headers as name, value>
+
${name}: ${value!"null"}
+ #list>
+
#if>
<#if (data.cookies)?has_content>
-Cookies
-
- <#list data.cookies as name, value>
-
${name}: ${value}
- #list>
-
+ Cookies
+
+ <#list data.cookies as name, value>
+
${name}: ${value!"null"}
+ #list>
+
#if>
<#if data.curl??>
-Curl
-
-${data.curl}
-
+ Curl
+
+ ${data.curl}
+
+#if>
+
+<#if (data.formParams)?has_content>
+ FormParams
+
+ <#list data.formParams as name, value>
+
${name}: ${value!"null"}
+ #list>
+
#if>
diff --git a/allure-attachments/src/main/resources/tpl/http-response.ftl b/allure-attachments/src/main/resources/tpl/http-response.ftl
index 2bc55de9f..91fed7287 100644
--- a/allure-attachments/src/main/resources/tpl/http-response.ftl
+++ b/allure-attachments/src/main/resources/tpl/http-response.ftl
@@ -7,7 +7,7 @@
Body
- ${data.body}
+ <#t>${data.body}
#if>
@@ -16,7 +16,7 @@
Headers
<#list data.headers as name, value>
-
${name}: ${value}
+
${name}: ${value!"null"}
#list>
#if>
@@ -26,7 +26,7 @@
Cookies
<#list data.cookies as name, value>
-
${name}: ${value}
+
${name}: ${value!"null"}
#list>
#if>
diff --git a/allure-attachments/src/test/java/io/qameta/allure/attachment/DefaultAttachmentProcessorTest.java b/allure-attachments/src/test/java/io/qameta/allure/attachment/DefaultAttachmentProcessorTest.java
index 1283d6116..a1d2def2e 100644
--- a/allure-attachments/src/test/java/io/qameta/allure/attachment/DefaultAttachmentProcessorTest.java
+++ b/allure-attachments/src/test/java/io/qameta/allure/attachment/DefaultAttachmentProcessorTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/allure-attachments/src/test/java/io/qameta/allure/attachment/FreemarkerAttachmentRendererTest.java b/allure-attachments/src/test/java/io/qameta/allure/attachment/FreemarkerAttachmentRendererTest.java
index 75ddac32d..0a773e59f 100644
--- a/allure-attachments/src/test/java/io/qameta/allure/attachment/FreemarkerAttachmentRendererTest.java
+++ b/allure-attachments/src/test/java/io/qameta/allure/attachment/FreemarkerAttachmentRendererTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,10 +16,12 @@
package io.qameta.allure.attachment;
import io.qameta.allure.attachment.http.HttpRequestAttachment;
+import io.qameta.allure.attachment.http.HttpResponseAttachment;
import io.qameta.allure.test.AllureFeatures;
import org.junit.jupiter.api.Test;
import static io.qameta.allure.attachment.testdata.TestData.randomHttpRequestAttachment;
+import static io.qameta.allure.attachment.testdata.TestData.randomHttpResponseAttachment;
import static org.assertj.core.api.Assertions.assertThat;
/**
@@ -27,6 +29,13 @@
*/
class FreemarkerAttachmentRendererTest {
+ private static final String CONTENT = "content";
+ private static final String CONTENT_TYPE = "contentType";
+ private static final String TEXT_HTML = "text/html";
+ private static final String FILE_EXTENSION = "fileExtension";
+ private static final String HTML = ".html";
+
+
@AllureFeatures.Attachments
@Test
void shouldRenderRequestAttachment() {
@@ -35,21 +44,21 @@ void shouldRenderRequestAttachment() {
.render(data);
assertThat(content)
- .hasFieldOrPropertyWithValue("contentType", "text/html")
- .hasFieldOrPropertyWithValue("fileExtension", ".html")
- .hasFieldOrProperty("content");
+ .hasFieldOrPropertyWithValue(CONTENT_TYPE, TEXT_HTML)
+ .hasFieldOrPropertyWithValue(FILE_EXTENSION, HTML)
+ .hasFieldOrProperty(CONTENT);
}
@AllureFeatures.Attachments
@Test
void shouldRenderResponseAttachment() {
- final HttpRequestAttachment data = randomHttpRequestAttachment();
+ final HttpResponseAttachment data = randomHttpResponseAttachment();
final DefaultAttachmentContent content = new FreemarkerAttachmentRenderer("http-response.ftl")
.render(data);
assertThat(content)
- .hasFieldOrPropertyWithValue("contentType", "text/html")
- .hasFieldOrPropertyWithValue("fileExtension", ".html")
- .hasFieldOrProperty("content");
+ .hasFieldOrPropertyWithValue(CONTENT_TYPE, TEXT_HTML)
+ .hasFieldOrPropertyWithValue(FILE_EXTENSION, HTML)
+ .hasFieldOrProperty(CONTENT);
}
}
diff --git a/allure-attachments/src/test/java/io/qameta/allure/attachment/NegativeFreemarkerAttachmentRendererTest.java b/allure-attachments/src/test/java/io/qameta/allure/attachment/NegativeFreemarkerAttachmentRendererTest.java
new file mode 100644
index 000000000..fbc2e6611
--- /dev/null
+++ b/allure-attachments/src/test/java/io/qameta/allure/attachment/NegativeFreemarkerAttachmentRendererTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2016-2024 Qameta Software Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.qameta.allure.attachment;
+
+import io.qameta.allure.attachment.http.HttpRequestAttachment;
+import io.qameta.allure.test.AllureFeatures;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+
+import static io.qameta.allure.attachment.testdata.TestData.negativeHttpRequestAttachment;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * @author a-simeshin (Simeshin Artem).
+ */
+class NegativeFreemarkerAttachmentRendererTest {
+
+ private static final String TEMPLATE_FOR_EXCEPTION = "body-npe-non-safe-attachment.ftl";
+
+ private PrintStream realSysOut;
+ private ByteArrayOutputStream sysOutBuffer;
+
+ @BeforeEach
+ void setUpSysOut() throws UnsupportedEncodingException {
+ realSysOut = System.err;
+ sysOutBuffer = new ByteArrayOutputStream();
+ System.setErr(new PrintStream(sysOutBuffer, false, StandardCharsets.UTF_8.toString()));
+ }
+
+ @AfterEach
+ void rollBackSysOut() {
+ System.setErr(realSysOut);
+ }
+
+ @AllureFeatures.Attachments
+ @Test
+ void shouldThrowExceptionalSituationsForFreeMarketRendererWithIncorrectAttachmentData() {
+ assertThrows(AttachmentRenderException.class, () -> {
+ final HttpRequestAttachment data = negativeHttpRequestAttachment();
+ new FreemarkerAttachmentRenderer(TEMPLATE_FOR_EXCEPTION).render(data);
+ });
+ }
+
+ @AllureFeatures.Attachments
+ @Test
+ void shouldExplainExceptionalSituationsForFreeMarketRenderer() throws UnsupportedEncodingException {
+ try {
+ final HttpRequestAttachment data = negativeHttpRequestAttachment();
+ new FreemarkerAttachmentRenderer(TEMPLATE_FOR_EXCEPTION).render(data);
+ } catch (Exception ignored) {
+ // for test purposes
+ }
+ assertThat(sysOutBuffer.toString(StandardCharsets.UTF_8.toString()))
+ .contains("SEVERE: Error executing FreeMarker template")
+ .contains("FreeMarker template error:")
+ .contains("The following has evaluated to null or missing:")
+ .contains("==> data.body")
+ .contains("[in template \"body-npe-non-safe-attachment.ftl\" at line 8, column 11]")
+ .contains("\t- Failed at: ${data.body.size}")
+ .contains("io.qameta.allure.attachment.FreemarkerAttachmentRenderer - HttpRequestAttachment")
+ .contains("\tname=null,")
+ .contains("\turl=null,")
+ .contains("\tbody=null,")
+ .contains("\theaders={},")
+ .contains("\tcookies={}");
+ }
+}
diff --git a/allure-attachments/src/test/java/io/qameta/allure/attachment/testdata/TestData.java b/allure-attachments/src/test/java/io/qameta/allure/attachment/testdata/TestData.java
index 26eba1c6f..f5933c6db 100644
--- a/allure-attachments/src/test/java/io/qameta/allure/attachment/testdata/TestData.java
+++ b/allure-attachments/src/test/java/io/qameta/allure/attachment/testdata/TestData.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -69,6 +69,20 @@ public static Map randomMap() {
final Map map = new HashMap<>();
map.put(randomString(), randomString());
map.put(randomString(), randomString());
+ map.put(randomString(), null);
return map;
}
+
+ public static HttpRequestAttachment negativeHttpRequestAttachment() {
+ return new HttpRequestAttachment(
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ );
+ }
+
}
diff --git a/allure-attachments/src/test/resources/tpl/body-npe-non-safe-attachment.ftl b/allure-attachments/src/test/resources/tpl/body-npe-non-safe-attachment.ftl
new file mode 100644
index 000000000..813425567
--- /dev/null
+++ b/allure-attachments/src/test/resources/tpl/body-npe-non-safe-attachment.ftl
@@ -0,0 +1,36 @@
+<#ftl output_format="HTML">
+<#-- @ftlvariable name="data" type="io.qameta.allure.attachment.http.HttpRequestAttachment" -->
+<#if data.method??>${data.method}<#else>GET#if> to <#if data.url??>${data.url}<#else>Unknown#if>
+
+Body
+
+
+ <#t>${data.body.size}
+
+
+
+<#if (data.headers)?has_content>
+ Headers
+
+ <#list data.headers as name, value>
+
${name}: ${value!"null"}
+ #list>
+
+#if>
+
+
+<#if (data.cookies)?has_content>
+ Cookies
+
+ <#list data.cookies as name, value>
+
${name}: ${value!"null"}
+ #list>
+
+#if>
+
+<#if data.curl??>
+ Curl
+
+ ${data.curl}
+
+#if>
diff --git a/allure-cucumber3-jvm/build.gradle.kts b/allure-awaitility/build.gradle.kts
similarity index 56%
rename from allure-cucumber3-jvm/build.gradle.kts
rename to allure-awaitility/build.gradle.kts
index 681f1ab7c..3c9ed6c79 100644
--- a/allure-cucumber3-jvm/build.gradle.kts
+++ b/allure-awaitility/build.gradle.kts
@@ -1,17 +1,16 @@
-description = "Allure CucumberJVM 3.0 Integration"
+description = "Allure Awaitlity Integration"
val agent: Configuration by configurations.creating
-val cucumberVersion = "3.0.2"
+val awaitilityVersion = "4.3.0"
dependencies {
agent("org.aspectj:aspectjweaver")
api(project(":allure-java-commons"))
- implementation("io.cucumber:cucumber-core:$cucumberVersion")
- implementation("io.cucumber:cucumber-java:$cucumberVersion")
- testImplementation("commons-io:commons-io")
- testImplementation("io.github.glytching:junit-extensions")
+ compileOnly("org.awaitility:awaitility:$awaitilityVersion")
+ testImplementation("javax.annotation:javax.annotation-api")
testImplementation("org.assertj:assertj-core")
+ testImplementation("org.awaitility:awaitility:$awaitilityVersion")
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("org.slf4j:slf4j-simple")
testImplementation(project(":allure-java-commons-test"))
@@ -22,14 +21,11 @@ dependencies {
tasks.jar {
manifest {
attributes(mapOf(
- "Automatic-Module-Name" to "io.qameta.allure.cucumber3jvm"
+ "Automatic-Module-Name" to "io.qameta.allure.awaitility"
))
}
}
tasks.test {
useJUnitPlatform()
- doFirst {
- jvmArgs("-javaagent:${agent.singleFile}")
- }
}
diff --git a/allure-awaitility/readme.md b/allure-awaitility/readme.md
new file mode 100644
index 000000000..1fcb34691
--- /dev/null
+++ b/allure-awaitility/readme.md
@@ -0,0 +1,71 @@
+## Allure-awaitility
+Extended logging for poling and ignored exceptions for [awaitility](https://github.com/awaitility/awaitility)
+
+
+## Wiki
+For more information about awaitility highly recommended look into [awaitility usage guide](https://github.com/awaitility/awaitility/wiki/Usage)
+
+
+### Configuration examples
+Single line for all awaitility conditions in project
+```java
+Awaitility.setDefaultConditionEvaluationListener(new AllureAwaitilityListener());
+```
+
+And another line to prevent breaking allure lifecycle for Steps inside Awaitility evaluations
+```java
+Awaitility.pollInSameThread();
+```
+
+Moreover, it's possible logging only few unstable conditions with method `.conditionEvaluationListener()`
+```java
+final AtomicInteger atomicInteger = new AtomicInteger(0);
+await().with()
+ .conditionEvaluationListener(new AllureAwaitilityListener())
+ .alias("Checking that important counter reached value around 3")
+ .atMost(Duration.of(1000, ChronoUnit.MILLIS))
+ .pollInterval(Duration.of(50, ChronoUnit.MILLIS))
+ .until(atomicInteger::getAndIncrement, is(3));
+```
+
+How it looks like:
+1. Top-level step with condition evaluation definition such as alias or default information.
+2. Second-level steps with poling process information
+3. Optional second-level step with timeout information
+4. Optional second-level steps with ignored information
+
+
+### TimeUnit
+Most awaitility users count time as milliseconds, but you can feel free to change print poll information.
+```java
+Awaitility.setDefaultConditionEvaluationListener(new AllureAwaitilityListener().setUnit(TimeUnit.SECONDS));
+```
+
+
+### Exceptions handling
+By default, it's not possible to handle and log any exceptions, but you can try to
+[ignore](https://github.com/awaitility/awaitility/wiki/Usage#ignoring-exceptions) and log ignored exceptions.
+
+```java
+final AtomicInteger atomicInteger = new AtomicInteger(0);
+await().with()
+ .ignoreExceptions() //required
+ .atMost(Duration.of(1000, ChronoUnit.MILLIS))
+ .pollInterval(Duration.of(500, ChronoUnit.MILLIS))
+ .until(() -> {
+ if (atomicInteger.getAndIncrement() != 3) {
+ //this exception will be ignored by awaitility, but logged into Allure
+ throw new RuntimeException("Something wrong happens");
+ } else {
+ return true;
+ }
+ });
+```
+
+Then, if you are not impressed with the large volume of logged exceptions, there is the way to disable logging for
+ignored exceptions globally.
+
+```java
+Awaitility.ignoreExceptionsByDefault();
+Awaitility.setDefaultConditionEvaluationListener(new AllureAwaitilityListener().setLogIgnoredExceptions(false));
+```
\ No newline at end of file
diff --git a/allure-awaitility/src/main/java/io/qameta/allure/awaitility/AllureAwaitilityListener.java b/allure-awaitility/src/main/java/io/qameta/allure/awaitility/AllureAwaitilityListener.java
new file mode 100644
index 000000000..01b793cf5
--- /dev/null
+++ b/allure-awaitility/src/main/java/io/qameta/allure/awaitility/AllureAwaitilityListener.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright 2016-2024 Qameta Software Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.qameta.allure.awaitility;
+
+import io.qameta.allure.Allure;
+import io.qameta.allure.AllureLifecycle;
+import io.qameta.allure.model.Status;
+import io.qameta.allure.model.StepResult;
+import org.awaitility.Awaitility;
+import org.awaitility.core.ConditionEvaluationListener;
+import org.awaitility.core.ConditionFactory;
+import org.awaitility.core.EvaluatedCondition;
+import org.awaitility.core.IgnoredException;
+import org.awaitility.core.StartEvaluationEvent;
+import org.awaitility.core.TimeoutEvent;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+/**
+ *
+ * Class implements Awaitility condition listener to log all awaiting and polls as {@link io.qameta.allure.Step}.
+ *
+ *
+ * Usage with single condition
+ *
+ *
+ * Awaitility.await()
+ * .conditionEvaluationListener(new AllureAwaitilityListener())
+ * .until(() -> somethingHappen());
+ *
+ *
+ *
+ *
+ * Usage globally for all conditions
+ *
+ *
+ * Awaitility.setDefaultConditionEvaluationListener(new AllureAwaitilityListener());
+ *
+ *
+ *
+ *
+ * @author a-simeshin (Simeshin Artem)
+ * @see org.awaitility.core.ConditionEvaluationListener
+ * @see Awaitility#setDefaultConditionEvaluationListener(ConditionEvaluationListener)
+ * @see ConditionFactory#conditionEvaluationListener(ConditionEvaluationListener)
+ * @see awaitlity wiki
+ */
+@SuppressWarnings("unused")
+public class AllureAwaitilityListener implements ConditionEvaluationListener {
+
+ private TimeUnit unit;
+ private boolean logIgnoredExceptions;
+ private final String onStartStepTextPattern;
+ private final String onSatisfiedStepTextPattern;
+ private final String onAwaitStepTextPattern;
+ private final String onTimeoutStepTextPattern;
+ private final String onExceptionStepTextPattern;
+
+ private String currentConditionStepUUID;
+
+ private static final InheritableThreadLocal LIFECYCLE
+ = new InheritableThreadLocal() {
+ @Override
+ protected AllureLifecycle initialValue() {
+ return Allure.getLifecycle();
+ }
+ };
+
+ public static AllureLifecycle getLifecycle() {
+ return LIFECYCLE.get();
+ }
+
+ /**
+ * Default all args constructor with default params.
+ */
+ public AllureAwaitilityListener() {
+ this.unit = MILLISECONDS;
+ this.logIgnoredExceptions = true;
+ this.onStartStepTextPattern = "Awaitility: %s";
+ this.onSatisfiedStepTextPattern = "%s after %d %s (remaining time %d %s, last poll interval was %s)";
+ this.onAwaitStepTextPattern = "%s (elapsed time %d %s, remaining time %d %s (last poll interval was %s))";
+ this.onTimeoutStepTextPattern = "Condition timeout. %s";
+ this.onExceptionStepTextPattern = "Exception ignored. %s";
+ }
+
+ /**
+ * Set default timeunit used in awaitility in project for properly printing.
+ *
+ * @param unit default timeunit in project
+ * @return this factory
+ */
+ public AllureAwaitilityListener setUnit(final TimeUnit unit) {
+ this.unit = unit;
+ return this;
+ }
+
+ /**
+ * Set logging ignored exceptions. True by default.
+ *
+ * @param logging to log or not
+ * @return this factory
+ */
+ public AllureAwaitilityListener setLogIgnoredExceptions(final boolean logging) {
+ this.logIgnoredExceptions = logging;
+ return this;
+ }
+
+ /**
+ * Method creates top-level step with short description about condition.
+ *
+ * @param startEvaluationEvent condition evaluation started
+ */
+ @Override
+ public void beforeEvaluation(final StartEvaluationEvent startEvaluationEvent) {
+ currentConditionStepUUID = UUID.randomUUID().toString();
+ final String nameWoAlias = String.format(onStartStepTextPattern, startEvaluationEvent.getDescription());
+ final String nameWithAlias = String.format(onStartStepTextPattern, startEvaluationEvent.getAlias());
+ final String stepName = startEvaluationEvent.getAlias() != null ? nameWithAlias : nameWoAlias;
+ getLifecycle().startStep(
+ currentConditionStepUUID,
+ new StepResult()
+ .setName(stepName)
+ .setDescription("Awaitility condition started")
+ .setStatus(Status.FAILED)
+ );
+ }
+
+ /**
+ * Logging timeout evaluation result. Method should create second-level step with useful info about timeout.
+ *
+ * @param timeoutEvent poling ended with timeout
+ */
+ @Override
+ public void onTimeout(final TimeoutEvent timeoutEvent) {
+ getLifecycle().updateStep(awaitilityCondition -> {
+ final String currentTimeoutStepUUID = UUID.randomUUID().toString();
+ getLifecycle().startStep(
+ currentConditionStepUUID,
+ currentTimeoutStepUUID,
+ new StepResult()
+ .setName(String.format(onTimeoutStepTextPattern, timeoutEvent.getDescription()))
+ .setDescription("Awaitility condition timeout")
+ .setStatus(Status.BROKEN)
+ );
+ getLifecycle().stopStep(currentTimeoutStepUUID);
+ });
+ getLifecycle().stopStep(currentConditionStepUUID);
+ }
+
+ /**
+ * Logging any evaluation result. Method should create second-level step with evaluation result and useful info.
+ *
+ * @param condition evaluation result for poling iteration
+ */
+ @Override
+ public void conditionEvaluated(final EvaluatedCondition condition) {
+ final String description = condition.getDescription();
+ final long elapsedTime = unit.convert(condition.getElapsedTimeInMS(), MILLISECONDS);
+ final long remainingTime = unit.convert(condition.getRemainingTimeInMS(), MILLISECONDS);
+ final String unitAsString = unit.toString().toLowerCase();
+
+ final String message = String.format(
+ condition.isSatisfied() ? onSatisfiedStepTextPattern : onAwaitStepTextPattern,
+ description,
+ elapsedTime,
+ unitAsString,
+ remainingTime,
+ unitAsString,
+ new TemporalDuration(condition.getPollInterval())
+ );
+
+ getLifecycle().updateStep(awaitilityCondition -> {
+ final String lastAwaitStepUUID = UUID.randomUUID().toString();
+ getLifecycle().startStep(
+ currentConditionStepUUID,
+ lastAwaitStepUUID,
+ new StepResult()
+ .setName(message)
+ .setDescription("Awaitility condition satisfied or not, but awaiting still in progress")
+ .setStatus(Status.PASSED)
+ );
+ getLifecycle().stopStep(lastAwaitStepUUID);
+ if (condition.isSatisfied()) {
+ awaitilityCondition.setStatus(Status.PASSED);
+ getLifecycle().stopStep(currentConditionStepUUID);
+ }
+ });
+ }
+
+ /**
+ * Logging ignored exceptions while poling conditions. Active only with
+ * await().with().ignoreExceptions()
+ * or
+ * Awaitility.ignoreExceptionsByDefault()
+ *
+ * @param ignoredException ignored exception
+ * @see Awaitility#ignoreExceptionsByDefault()
+ * @see Awaitility#ignoreExceptionByDefault(Class)
+ * @see ConditionFactory#ignoreExceptions()
+ * @see ConditionFactory#ignoreException(Class)
+ * @see awaitlity wiki
+ */
+ @Override
+ public void exceptionIgnored(final IgnoredException ignoredException) {
+ if (logIgnoredExceptions) {
+ getLifecycle().updateStep(awaitilityCondition -> {
+ final String currentExceptionIgnoredStepUUID = UUID.randomUUID().toString();
+ final String message = String.format(
+ onExceptionStepTextPattern, ignoredException.getThrowable().getMessage());
+ final StringWriter stringWriter = new StringWriter();
+ ignoredException.getThrowable().printStackTrace(new PrintWriter(stringWriter));
+ final String stackTrace = stringWriter.toString();
+ getLifecycle().startStep(
+ currentConditionStepUUID,
+ currentExceptionIgnoredStepUUID,
+ new StepResult()
+ .setName(message)
+ .setDescription("Exception occurred and ignored, but awaiting still in progress")
+ .setStatus(Status.SKIPPED)
+ );
+ getLifecycle().addAttachment(
+ ignoredException.getThrowable().getMessage(), "text/plain", ".txt",
+ stackTrace.getBytes(StandardCharsets.UTF_8));
+ getLifecycle().stopStep(currentExceptionIgnoredStepUUID);
+ });
+ }
+ }
+
+ /**
+ * For tests only.
+ *
+ * @param allure allure lifecycle to set
+ */
+ public static void setLifecycle(final AllureLifecycle allure) {
+ LIFECYCLE.set(allure);
+ }
+
+}
diff --git a/allure-awaitility/src/main/java/io/qameta/allure/awaitility/TemporalDuration.java b/allure-awaitility/src/main/java/io/qameta/allure/awaitility/TemporalDuration.java
new file mode 100644
index 000000000..d549ee811
--- /dev/null
+++ b/allure-awaitility/src/main/java/io/qameta/allure/awaitility/TemporalDuration.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2016-2024 Qameta Software Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.qameta.allure.awaitility;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAccessor;
+import java.time.temporal.TemporalField;
+import java.time.temporal.UnsupportedTemporalTypeException;
+
+import static java.time.temporal.ChronoField.DAY_OF_MONTH;
+import static java.time.temporal.ChronoField.HOUR_OF_DAY;
+import static java.time.temporal.ChronoField.MILLI_OF_SECOND;
+import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
+import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
+import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
+import static java.time.temporal.ChronoField.YEAR;
+
+/**
+ * Class helper for Duration printing purposes.
+ */
+public class TemporalDuration implements TemporalAccessor {
+ private static final Temporal BASE = LocalDateTime.of(0, 1, 1, 0, 0, 0, 0);
+
+ private static final DateTimeFormatter DTF = new DateTimeFormatterBuilder().optionalStart()
+ .appendValue(YEAR)
+ .appendLiteral(" years ").optionalEnd()
+ .optionalStart().appendLiteral(' ').appendValue(MONTH_OF_YEAR).appendLiteral(" months ").optionalEnd()
+ .optionalStart().appendLiteral(' ').appendValue(DAY_OF_MONTH).appendLiteral(" days ").optionalEnd()
+ .optionalStart().appendLiteral(' ').appendValue(HOUR_OF_DAY).appendLiteral(" hours ").optionalEnd()
+ .optionalStart().appendLiteral(' ').appendValue(MINUTE_OF_HOUR).appendLiteral(" minutes ").optionalEnd()
+ .optionalStart().appendLiteral(' ').appendValue(SECOND_OF_MINUTE).appendLiteral(" seconds").optionalEnd()
+ .optionalStart().appendLiteral(' ').appendValue(MILLI_OF_SECOND).appendLiteral(" milliseconds")
+ .optionalEnd()
+ .toFormatter();
+
+ private final Duration duration;
+ private final Temporal temporal;
+
+ TemporalDuration(final Duration duration) {
+ this.duration = duration;
+ this.temporal = duration.addTo(BASE);
+ }
+
+ @Override
+ public boolean isSupported(final TemporalField field) {
+ return temporal.isSupported(field) && temporal.getLong(field) - BASE.getLong(field) != 0L;
+ }
+
+ @Override
+ public long getLong(final TemporalField temporalField) {
+ if (!isSupported(temporalField)) {
+ throw new UnsupportedTemporalTypeException(temporalField.toString());
+ }
+ return temporal.getLong(temporalField) - BASE.getLong(temporalField);
+ }
+
+ @Override
+ public String toString() {
+ if (duration.compareTo(Duration.ofMillis(1)) < 0) {
+ return duration.toNanos() + " nanoseconds";
+ } else {
+ return DTF.format(this).trim();
+ }
+ }
+
+}
diff --git a/allure-awaitility/src/test/java/io/qameta/allure/awaitility/ConditionListenersPositiveTest.java b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/ConditionListenersPositiveTest.java
new file mode 100644
index 000000000..1a3bad814
--- /dev/null
+++ b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/ConditionListenersPositiveTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2016-2024 Qameta Software Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.qameta.allure.awaitility;
+
+import io.qameta.allure.model.Status;
+import io.qameta.allure.model.StepResult;
+import io.qameta.allure.model.TestResult;
+import org.awaitility.Awaitility;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.DynamicNode;
+import org.junit.jupiter.api.DynamicTest;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestFactory;
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Stream;
+
+import static io.qameta.allure.test.RunUtils.runWithinTestContext;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.DynamicTest.dynamicTest;
+
+class ConditionListenersPositiveTest {
+
+ @BeforeAll
+ static void setup() {
+ Awaitility.pollInSameThread();
+ }
+
+ /**
+ * Positive test to check proper allure steps generation.
+ *
+ * Precondition: listener into condition declaration, await without alias
+ *
+ * Test should check that:
+ *
1. Allure has exactly 1 top-level step for 1 await condition
+ * 2. Top level step has passed status
+ * 3. Top level step has default name
+ */
+ @TestFactory
+ Stream globalSettingsAwaitWoAliasCheckTopLevelPassedStep() {
+ final List testResult = runWithinTestContext(() -> {
+ final AtomicInteger atomicInteger = new AtomicInteger(0);
+ await().with()
+ .conditionEvaluationListener(new AllureAwaitilityListener())
+ .atMost(Duration.of(1000, ChronoUnit.MILLIS))
+ .pollInterval(Duration.of(50, ChronoUnit.MILLIS))
+ .until(atomicInteger::getAndIncrement, is(3));
+ },
+ AllureAwaitilityListener::setLifecycle
+ ).getTestResults();
+
+ return Stream.of(
+ DynamicTest.dynamicTest("Exactly 1 top level step for 1 awaitility condition", () ->
+ assertThat(testResult.get(0).getSteps())
+ .hasSize(1)
+ ),
+ DynamicTest.dynamicTest("Top level step has passed status", () ->
+ assertThat(testResult.get(0).getSteps())
+ .allMatch(step -> Status.PASSED.equals(step.getStatus()))
+ ),
+ DynamicTest.dynamicTest("Top level step has default name because await() wo alias", () ->
+ assertThat(testResult.get(0).getSteps())
+ .extracting(StepResult::getName)
+ .containsExactly("Awaitility: Starting evaluation")
+ )
+ );
+ }
+
+ /**
+ * Positive test to check proper allure steps generation.
+ *
+ * Precondition: listener into condition declaration, await with alias
+ *
+ * Test should check that:
+ *
1. Top level step has name with alias from await('alias')
+ */
+ @Test
+ void globalSettingsAwaitWithAliasCheckTopLevelPassedStep() {
+ final List testResult = runWithinTestContext(() -> {
+ final AtomicInteger atomicInteger = new AtomicInteger(0);
+ await("Counter should be at least 3").with()
+ .conditionEvaluationListener(new AllureAwaitilityListener())
+ .atMost(Duration.of(1000, ChronoUnit.MILLIS))
+ .pollInterval(Duration.of(50, ChronoUnit.MILLIS))
+ .until(atomicInteger::getAndIncrement, is(3));
+ },
+ AllureAwaitilityListener::setLifecycle
+ ).getTestResults();
+ assertEquals(
+ "Awaitility: Counter should be at least 3",
+ testResult.get(0).getSteps().get(0).getName(),
+ "Top level step has name with alias"
+ );
+ }
+
+ /**
+ * Positive test to check proper allure steps generation.
+ *
+ * Precondition: listener into condition declaration, await without alias
+ *
+ * Test should check that:
+ *
1. Allure has exactly 4 second-level steps for condition with 4 polls iteration
+ * 2. All second-level steps should have passed status for successful condition evaluation
+ * 3. All second-level steps should have information about polling intervals and evaluation
+ */
+ @TestFactory
+ Stream globalSettingsCheckAwaitWoAliasSecondLevelPassedSteps() {
+ final List testResult = runWithinTestContext(() -> {
+ final AtomicInteger atomicInteger = new AtomicInteger(0);
+ await().with()
+ .conditionEvaluationListener(new AllureAwaitilityListener())
+ .atMost(Duration.of(1000, ChronoUnit.MILLIS))
+ .pollInterval(Duration.of(50, ChronoUnit.MILLIS))
+ .until(atomicInteger::getAndIncrement, is(3));
+ },
+ AllureAwaitilityListener::setLifecycle
+ ).getTestResults();
+
+ return Stream.of(
+ dynamicTest("Exactly 4 second level steps for 4 polling iterations", () ->
+ assertThat(testResult.get(0).getSteps().get(0).getSteps())
+ .hasSize(4)
+ ),
+ dynamicTest("All second level steps has passed statuses", () ->
+ assertThat(testResult.get(0).getSteps().get(0).getSteps())
+ .allMatch(x -> x.getStatus().equals(Status.PASSED))
+ ),
+ dynamicTest("Second level step 1 name", () ->
+ assertThat(testResult.get(0).getSteps().get(0).getSteps().get(0).getName())
+ .contains("io.qameta.allure.awaitility.ConditionListenersPositiveTest")
+ .contains("expected <3> but was <0>")
+ .contains("elapsed time")
+ .contains("remaining time")
+ .contains("last poll interval was")
+ ),
+ dynamicTest("Second level step 2 name", () ->
+ assertThat(testResult.get(0).getSteps().get(0).getSteps().get(1).getName())
+ .contains("io.qameta.allure.awaitility.ConditionListenersPositiveTest")
+ .contains("expected <3> but was <1>")
+ .contains("elapsed time")
+ .contains("remaining time")
+ .contains("last poll interval was")
+ ),
+ dynamicTest("Second level step 3 name", () ->
+ assertThat(testResult.get(0).getSteps().get(0).getSteps().get(2).getName())
+ .contains("io.qameta.allure.awaitility.ConditionListenersPositiveTest")
+ .contains("expected <3> but was <2>")
+ .contains("elapsed time")
+ .contains("remaining time")
+ .contains("last poll interval was")
+ ),
+ dynamicTest("Second level step 4 name", () ->
+ assertThat(testResult.get(0).getSteps().get(0).getSteps().get(3).getName())
+ .contains("io.qameta.allure.awaitility.ConditionListenersPositiveTest")
+ .contains("reached its end value of <3> after")
+ .contains("remaining time")
+ .contains("last poll interval was")
+ )
+ );
+ }
+
+}
diff --git a/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsNegativeTest.java b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsNegativeTest.java
new file mode 100644
index 000000000..5c169fd63
--- /dev/null
+++ b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsNegativeTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2016-2024 Qameta Software Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.qameta.allure.awaitility;
+
+import io.qameta.allure.model.Status;
+import io.qameta.allure.model.TestResult;
+import org.awaitility.Awaitility;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DynamicNode;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestFactory;
+import org.junit.jupiter.api.TestInstance;
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Stream;
+
+import static io.qameta.allure.test.RunUtils.runWithinTestContext;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.DynamicTest.dynamicTest;
+
+@TestInstance(TestInstance.Lifecycle.PER_METHOD)
+class GlobalSettingsNegativeTest {
+
+ @AfterEach
+ void reset() {
+ Awaitility.reset();
+ }
+
+ @BeforeEach
+ void setup() {
+ Awaitility.pollInSameThread();
+ Awaitility.setDefaultConditionEvaluationListener(new AllureAwaitilityListener());
+ }
+
+ /**
+ * Negative test to check proper allure steps generation.
+ *
+ * Precondition: static settings, await without alias
+ *
+ * Test should check that:
+ *
1. Top level step has broken status
+ */
+ @Test
+ void globalSettingsAwaitWoAliasCheckTopLevelBrokenStep() {
+ final List testResult = runWithinTestContext(() -> {
+ final AtomicInteger atomicInteger = new AtomicInteger(0);
+ await().with()
+ .atMost(Duration.of(1000, ChronoUnit.MILLIS))
+ .pollInterval(Duration.of(500, ChronoUnit.MILLIS))
+ .until(atomicInteger::getAndIncrement, is(3));
+ },
+ AllureAwaitilityListener::setLifecycle
+ ).getTestResults();
+ assertEquals(
+ Status.FAILED, testResult.get(0).getSteps().get(0).getStatus(),
+ "Top level step has broken status"
+ );
+ }
+
+ /**
+ * Positive test to check proper allure steps generation.
+ *
+ * Precondition: static settings, await without alias
+ *
+ * Test should check that:
+ *
1. Allure has exactly 2 second-level steps for condition with 2 polls iteration
+ * 2. All second-level steps should have passed or broken status for successful and timeout evaluations
+ * 3. All second-level steps should have information about polling intervals and evaluation
+ */
+ @TestFactory
+ Stream globalSettingsCheckAwaitWoAliasSecondLevelTimeoutStep() {
+ final List testResult = runWithinTestContext(() -> {
+ final AtomicInteger atomicInteger = new AtomicInteger(0);
+ await().with()
+ .atMost(Duration.of(1000, ChronoUnit.MILLIS))
+ .pollInterval(Duration.of(500, ChronoUnit.MILLIS))
+ .until(atomicInteger::getAndIncrement, is(3));
+ },
+ AllureAwaitilityListener::setLifecycle
+ ).getTestResults();
+
+ return Stream.of(
+ dynamicTest("Second level steps count", () ->
+ assertThat(testResult.get(0).getSteps().get(0).getSteps())
+ .as("Exactly 2 second level steps for 2 polling iterations")
+ .hasSize(2)),
+ dynamicTest("Second level step 1 name", () ->
+ assertThat(testResult.get(0).getSteps().get(0).getSteps().get(0).getName())
+ .contains("io.qameta.allure.awaitility.GlobalSettingsNegativeTest")
+ .contains("expected <3> but was <0>")
+ .contains("elapsed time")
+ .contains("remaining time")
+ .contains("last poll interval was")),
+ dynamicTest("Second level step 1 status", () ->
+ assertThat(testResult.get(0).getSteps().get(0).getSteps().get(0).getStatus())
+ .isEqualTo(Status.PASSED)),
+ dynamicTest("Second level step 2 name", () ->
+ assertThat(testResult.get(0).getSteps().get(0).getSteps().get(1).getName())
+ .contains("Condition timeout.")
+ .contains("io.qameta.allure.awaitility.GlobalSettingsNegativeTest")),
+ dynamicTest("Second level step 2 status", () ->
+ assertThat(testResult.get(0).getSteps().get(0).getSteps().get(1).getStatus())
+ .isEqualTo(Status.BROKEN))
+ );
+ }
+
+}
diff --git a/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsPositiveTest.java b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsPositiveTest.java
new file mode 100644
index 000000000..52a6b50db
--- /dev/null
+++ b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsPositiveTest.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2016-2024 Qameta Software Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.qameta.allure.awaitility;
+
+import io.qameta.allure.model.Status;
+import io.qameta.allure.model.StepResult;
+import io.qameta.allure.model.TestResult;
+import org.awaitility.Awaitility;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DynamicNode;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Stream;
+
+import static io.qameta.allure.test.RunUtils.runWithinTestContext;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.DynamicTest.dynamicTest;
+
+@TestInstance(TestInstance.Lifecycle.PER_METHOD)
+class GlobalSettingsPositiveTest {
+
+ @AfterEach
+ void reset() {
+ Awaitility.reset();
+ }
+
+ @BeforeEach
+ void setup() {
+ Awaitility.pollInSameThread();
+ Awaitility.setDefaultConditionEvaluationListener(new AllureAwaitilityListener());
+ }
+
+ /**
+ * Positive test to check proper allure steps generation.
+ *
+ * Precondition: static settings, await without alias
+ *
+ * Test should check that:
+ *
1. Allure has exactly 1 top-level step for 1 await condition
+ * 2. Top level step has passed status
+ * 3. Top level step has default name
+ */
+ @Test
+ Stream globalSettingsAwaitWoAliasCheckTopLevelPassedStep() {
+ final List testResult = runWithinTestContext(() -> {
+ final AtomicInteger atomicInteger = new AtomicInteger(0);
+ await().with()
+ .atMost(Duration.of(1000, ChronoUnit.MILLIS))
+ .pollInterval(Duration.of(50, ChronoUnit.MILLIS))
+ .until(atomicInteger::getAndIncrement, is(3));
+ },
+ AllureAwaitilityListener::setLifecycle
+ ).getTestResults();
+
+ return Stream.of(
+ dynamicTest("Steps count", () ->
+ assertThat(testResult.get(0).getSteps())
+ .as("Exactly 1 top level step for 1 awaitility condition")
+ .hasSize(1)
+ ),
+ dynamicTest("Top level step status", () ->
+ assertEquals(
+ Status.PASSED, testResult.get(0).getSteps().get(0).getStatus(),
+ "Top level step has passed status"
+ )
+ ),
+ dynamicTest("Top level step name", () ->
+ assertEquals(
+ "Awaitility: Starting evaluation",
+ testResult.get(0).getSteps().get(0).getName(),
+ "Top level step has default name because await() wo alias"
+ )
+ )
+ );
+ }
+
+ /**
+ * Positive test to check proper allure steps generation.
+ *
+ * Precondition: static settings, await with alias
+ *
+ * Test should check that:
+ *
1. Top level step has name with alias from await('alias')
+ */
+ @Test
+ void globalSettingsAwaitWithAliasCheckTopLevelPassedStep() {
+ final List testResult = runWithinTestContext(() -> {
+ final AtomicInteger atomicInteger = new AtomicInteger(0);
+ await("Counter should be at least 3").with()
+ .atMost(Duration.of(1000, ChronoUnit.MILLIS))
+ .pollInterval(Duration.of(50, ChronoUnit.MILLIS))
+ .until(atomicInteger::getAndIncrement, is(3));
+ },
+ AllureAwaitilityListener::setLifecycle
+ ).getTestResults();
+ assertEquals(
+ "Awaitility: Counter should be at least 3",
+ testResult.get(0).getSteps().get(0).getName(),
+ "Top level step has name with alias"
+ );
+ }
+
+ /**
+ * Positive test to check proper allure steps generation.
+ *
+ * Precondition: static settings, await without alias
+ *
+ * Test should check that:
+ *
1. Allure has exactly 4 second-level steps for condition with 4 polls iteration
+ * 2. All second-level steps should have passed status for successful condition evaluation
+ * 3. All second-level steps should have information about polling intervals and evaluation
+ */
+ @Test
+ Stream globalSettingsCheckAwaitWoAliasSecondLevelPassedSteps() {
+ final List testResult = runWithinTestContext(() -> {
+ final AtomicInteger atomicInteger = new AtomicInteger(0);
+ await().with()
+ .atMost(Duration.of(1000, ChronoUnit.MILLIS))
+ .pollInterval(Duration.of(50, ChronoUnit.MILLIS))
+ .until(atomicInteger::getAndIncrement, is(3));
+ },
+ AllureAwaitilityListener::setLifecycle
+ ).getTestResults();
+
+ return Stream.of(
+ dynamicTest("Second level steps count", () ->
+ assertEquals(
+ 4, testResult.get(0).getSteps().get(0).getSteps().size(),
+ "Exactly 4 second level steps for 4 polling iterations"
+ )
+ ),
+ dynamicTest("Second level steps all passed", () ->
+ assertThat(testResult.get(0).getSteps().get(0).getSteps())
+ .extracting(StepResult::getStatus)
+ .containsExactlyInAnyOrder(
+ Status.PASSED,
+ Status.PASSED,
+ Status.PASSED,
+ Status.PASSED
+ )
+ ),
+ dynamicTest("Second level step 1 name", () ->
+ assertThat(testResult.get(0).getSteps().get(0).getSteps().get(0).getName())
+ .contains("io.qameta.allure.awaitility.GlobalSettingsPositiveTest")
+ .contains("expected <3> but was <0>")
+ .contains("elapsed time")
+ .contains("remaining time")
+ .contains("last poll interval was")
+ ),
+ dynamicTest("Second level step 2 name", () ->
+ assertThat(testResult.get(0).getSteps().get(0).getSteps().get(1).getName())
+ .contains("io.qameta.allure.awaitility.GlobalSettingsPositiveTest")
+ .contains("expected <3> but was <1>")
+ .contains("elapsed time")
+ .contains("remaining time")
+ .contains("last poll interval was")
+ ),
+ dynamicTest("Second level step 3 name", () ->
+ assertThat(testResult.get(0).getSteps().get(0).getSteps().get(2).getName())
+ .contains("io.qameta.allure.awaitility.GlobalSettingsPositiveTest")
+ .contains("expected <3> but was <2>")
+ .contains("elapsed time")
+ .contains("remaining time")
+ .contains("last poll interval was")
+ ),
+ dynamicTest("Second level step 4 name", () ->
+ assertThat(testResult.get(0).getSteps().get(0).getSteps().get(3).getName())
+ .contains("io.qameta.allure.awaitility.GlobalSettingsPositiveTest")
+ .contains("java.util.concurrent.atomic.AtomicInteger:")
+ .contains("reached its end value of <3> after")
+ .contains("remaining time")
+ .contains("last poll interval was")
+ )
+ );
+ }
+
+}
diff --git a/allure-awaitility/src/test/java/io/qameta/allure/awaitility/MultipleConditionsTest.java b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/MultipleConditionsTest.java
new file mode 100644
index 000000000..36a74a164
--- /dev/null
+++ b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/MultipleConditionsTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2016-2024 Qameta Software Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.qameta.allure.awaitility;
+
+import io.qameta.allure.model.Status;
+import io.qameta.allure.model.TestResult;
+import org.awaitility.Awaitility;
+import org.junit.jupiter.api.*;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import static io.qameta.allure.test.RunUtils.runWithinTestContext;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+
+public class MultipleConditionsTest {
+
+ @AfterEach
+ void reset() {
+ Awaitility.reset();
+ }
+
+ @BeforeEach
+ void setup() {
+ Awaitility.pollInSameThread();
+ Awaitility.setDefaultConditionEvaluationListener(new AllureAwaitilityListener());
+ }
+
+ @TestFactory
+ Stream bothAwaitilityStepsShouldAppearTest() {
+ final List testResult = runWithinTestContext(() -> {
+ await().with()
+ .alias("First waiting")
+ .until(() -> true);
+ await().with()
+ .alias("Second waiting")
+ .until(() -> true);
+ },
+ AllureAwaitilityListener::setLifecycle
+ ).getTestResults();
+
+ return Stream.of(
+ DynamicTest.dynamicTest("Exactly 2 top level step for 2 awaitility condition", () ->
+ assertThat(testResult.get(0).getSteps())
+ .describedAs("Allure TestResult contains exactly 2 top level step for 2 awaitility condition")
+ .hasSize(2)
+ ),
+ DynamicTest.dynamicTest("All top level step for all awaitility condition has PASSED", () ->
+ assertThat(testResult.get(0).getSteps())
+ .describedAs("Allure TestResult contains all top level step for all awaitility with PASSED condition")
+ .allMatch(step -> Status.PASSED.equals(step.getStatus()))
+ )
+ );
+ }
+
+}
diff --git a/allure-bom/build.gradle.kts b/allure-bom/build.gradle.kts
new file mode 100644
index 000000000..9593300aa
--- /dev/null
+++ b/allure-bom/build.gradle.kts
@@ -0,0 +1,33 @@
+plugins {
+ `java-platform`
+}
+
+description = "Allure Java (Bill of Materials)"
+
+dependencies {
+ constraints {
+ rootProject.subprojects.sorted()
+ .forEach { api("${it.group}:${it.name}:${it.version}") }
+ }
+}
+
+tasks.withType(Jar::class) {
+ enabled = false
+}
+
+configurations.archives {
+ artifacts.removeAll{ it.extension == "jar" }
+}
+
+publishing.publications.named("maven") {
+ pom {
+ from(components["javaPlatform"])
+ description.set("This Bill of Materials POM can be used to ease dependency management " +
+ "when referencing multiple Allure artifacts using Gradle or Maven.")
+ packaging = "pom"
+ withXml {
+ val filteredContent = asString().replace("\\s*compile ".toRegex(), "")
+ asString().clear().append(filteredContent)
+ }
+ }
+}
diff --git a/allure-citrus/build.gradle.kts b/allure-citrus/build.gradle.kts
index bd740f9a7..dbad67a9c 100644
--- a/allure-citrus/build.gradle.kts
+++ b/allure-citrus/build.gradle.kts
@@ -1,11 +1,8 @@
description = "Allure Citrus Integration"
-val agent: Configuration by configurations.creating
-
val citrusVersion = "2.8.0"
dependencies {
- agent("org.aspectj:aspectjweaver")
api(project(":allure-java-commons"))
compileOnly("com.consol.citrus:citrus-core:$citrusVersion")
testImplementation("com.consol.citrus:citrus-http:$citrusVersion")
@@ -32,7 +29,4 @@ tasks.jar {
tasks.test {
useJUnitPlatform()
exclude("**/samples/*")
- doFirst {
- jvmArgs("-javaagent:${agent.singleFile}")
- }
}
diff --git a/allure-citrus/src/main/java/io/qameta/allure/citrus/AllureCitrus.java b/allure-citrus/src/main/java/io/qameta/allure/citrus/AllureCitrus.java
index 3db26c236..21f68bebe 100644
--- a/allure-citrus/src/main/java/io/qameta/allure/citrus/AllureCitrus.java
+++ b/allure-citrus/src/main/java/io/qameta/allure/citrus/AllureCitrus.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/allure-citrus/src/test/java/io/qameta/allure/citrus/AllureCitrusTest.java b/allure-citrus/src/test/java/io/qameta/allure/citrus/AllureCitrusTest.java
index a31fd6f00..7f29c0490 100644
--- a/allure-citrus/src/test/java/io/qameta/allure/citrus/AllureCitrusTest.java
+++ b/allure-citrus/src/test/java/io/qameta/allure/citrus/AllureCitrusTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/AllureCucumberJvm.java b/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/AllureCucumberJvm.java
deleted file mode 100644
index 311802432..000000000
--- a/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/AllureCucumberJvm.java
+++ /dev/null
@@ -1,318 +0,0 @@
-/*
- * Copyright 2019 Qameta Software OÜ
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.qameta.allure.cucumberjvm;
-
-import cucumber.runtime.StepDefinitionMatch;
-import gherkin.I18n;
-import gherkin.formatter.Formatter;
-import gherkin.formatter.Reporter;
-import gherkin.formatter.model.Background;
-import gherkin.formatter.model.DataTableRow;
-import gherkin.formatter.model.Examples;
-import gherkin.formatter.model.Feature;
-import gherkin.formatter.model.Match;
-import gherkin.formatter.model.Result;
-import gherkin.formatter.model.Scenario;
-import gherkin.formatter.model.ScenarioOutline;
-import gherkin.formatter.model.Step;
-import gherkin.formatter.model.Tag;
-import io.qameta.allure.Allure;
-import io.qameta.allure.AllureLifecycle;
-import io.qameta.allure.model.Status;
-import io.qameta.allure.model.StatusDetails;
-import io.qameta.allure.model.StepResult;
-import io.qameta.allure.model.TestResult;
-import io.qameta.allure.util.ResultsUtils;
-
-import java.io.ByteArrayInputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Allure plugin for Cucumber-JVM.
- */
-@SuppressWarnings({
- "PMD.ExcessiveImports",
- "ClassFanOutComplexity",
- "ClassDataAbstractionCoupling",
- "unused"
-})
-public class AllureCucumberJvm implements Reporter, Formatter {
-
- private static final List SCENARIO_OUTLINE_KEYWORDS = Collections.synchronizedList(new ArrayList());
-
- private static final String FAILED = "failed";
- private static final String PASSED = "passed";
- private static final String SKIPPED = "skipped";
- private static final String PENDING = "pending";
-
- private static final String TXT_EXTENSION = ".txt";
- private static final String TEXT_PLAIN = "text/plain";
-
- private final Map scenarioUuids = new ConcurrentHashMap<>();
- private final Deque gherkinSteps = new LinkedList<>();
- private final AllureLifecycle lifecycle;
- private Feature currentFeature;
- private boolean isNullMatch = true;
- private Scenario currentScenario;
-
-
- @SuppressWarnings("unused")
- public AllureCucumberJvm() {
- this(Allure.getLifecycle());
- }
-
- public AllureCucumberJvm(final AllureLifecycle lifecycle) {
- this.lifecycle = lifecycle;
- final List i18nList = I18n.getAll();
- i18nList.forEach(i18n -> SCENARIO_OUTLINE_KEYWORDS.addAll(i18n.keywords("scenario_outline")));
- }
-
- @Override
- public void feature(final Feature feature) {
- this.currentFeature = feature;
- }
-
- @Override
- public void before(final Match match, final Result result) {
- new StepUtils(currentFeature, currentScenario).fireFixtureStep(match, result, true);
- }
-
- @Override
- public void after(final Match match, final Result result) {
- new StepUtils(currentFeature, currentScenario).fireFixtureStep(match, result, false);
- }
-
- @Override
- public void startOfScenarioLifeCycle(final Scenario scenario) {
- this.currentScenario = scenario;
-
- final Deque tags = new LinkedList<>(scenario.getTags());
-
- if (SCENARIO_OUTLINE_KEYWORDS.contains(scenario.getKeyword())) {
- synchronized (gherkinSteps) {
- gherkinSteps.clear();
- }
- } else {
- tags.addAll(currentFeature.getTags());
- }
-
- final LabelBuilder labelBuilder = new LabelBuilder(currentFeature, scenario, tags);
-
- final String uuid = UUID.randomUUID().toString();
- scenarioUuids.put(scenario, uuid);
-
- final TestResult result = new TestResult()
- .setUuid(uuid)
- .setHistoryId(StepUtils.getHistoryId(scenario.getId()))
- .setFullName(String.format("%s: %s", currentFeature.getName(), scenario.getName()))
- .setName(scenario.getName())
- .setLabels(labelBuilder.getScenarioLabels())
- .setLinks(labelBuilder.getScenarioLinks());
-
- if (!currentFeature.getDescription().isEmpty()) {
- result.setDescription(currentFeature.getDescription());
- }
-
- lifecycle.scheduleTestCase(result);
- lifecycle.startTestCase(uuid);
-
- }
-
- @Override
- public void step(final Step step) {
- synchronized (gherkinSteps) {
- gherkinSteps.add(step);
- }
- }
-
- @Override
- public void match(final Match match) {
- final StepUtils stepUtils = new StepUtils(currentFeature, currentScenario);
- if (match instanceof StepDefinitionMatch) {
- isNullMatch = false;
- final Step step = stepUtils.extractStep((StepDefinitionMatch) match);
- synchronized (gherkinSteps) {
- while (gherkinSteps.peek() != null && !stepUtils.isEqualSteps(step, gherkinSteps.peek())) {
- stepUtils.fireCanceledStep(gherkinSteps.remove());
- }
- if (stepUtils.isEqualSteps(step, gherkinSteps.peek())) {
- gherkinSteps.remove();
- }
- }
- final StepResult stepResult = new StepResult();
- stepResult.setName(String.format("%s %s", step.getKeyword(), getStepName(step)))
- .setStart(System.currentTimeMillis());
-
- final String scenarioUuid = scenarioUuids.get(currentScenario);
- lifecycle.startStep(scenarioUuid, stepUtils.getStepUuid(step), stepResult);
- createDataTableAttachment(step.getRows());
- }
- }
-
- @SuppressWarnings("PMD.NcssCount")
- @Override
- public void result(final Result result) {
- if (!isNullMatch) {
- final StatusDetails statusDetails =
- ResultsUtils.getStatusDetails(result.getError()).orElse(new StatusDetails());
- final TagParser tagParser = new TagParser(currentFeature, currentScenario);
- statusDetails
- .setFlaky(tagParser.isFlaky())
- .setMuted(tagParser.isMuted())
- .setKnown(tagParser.isKnown());
-
- final String scenarioUuid = scenarioUuids.get(currentScenario);
- switch (result.getStatus()) {
- case FAILED:
- final Status status = ResultsUtils.getStatus(result.getError())
- .orElse(Status.FAILED);
- lifecycle.updateStep(stepResult -> stepResult.setStatus(Status.FAILED));
- lifecycle.updateTestCase(scenarioUuid, scenarioResult ->
- scenarioResult.setStatus(status)
- .setStatusDetails(statusDetails));
- lifecycle.stopStep();
- break;
- case PENDING:
- lifecycle.updateStep(stepResult -> stepResult.setStatus(Status.SKIPPED));
- lifecycle.updateTestCase(scenarioUuid, scenarioResult ->
- scenarioResult.setStatus(Status.SKIPPED)
- .setStatusDetails(statusDetails));
- lifecycle.stopStep();
- break;
- case SKIPPED:
- lifecycle.updateStep(stepResult -> stepResult.setStatus(Status.SKIPPED));
- lifecycle.stopStep();
- break;
- case PASSED:
- lifecycle.updateStep(stepResult -> stepResult.setStatus(Status.PASSED));
- lifecycle.stopStep();
- lifecycle.updateTestCase(scenarioUuid, scenarioResult ->
- scenarioResult.setStatus(Status.PASSED)
- .setStatusDetails(statusDetails));
- break;
- default:
- break;
- }
- isNullMatch = true;
- }
- }
-
- @Override
- public void endOfScenarioLifeCycle(final Scenario scenario) {
- final StepUtils stepUtils = new StepUtils(currentFeature, currentScenario);
- synchronized (gherkinSteps) {
- while (gherkinSteps.peek() != null) {
- stepUtils.fireCanceledStep(gherkinSteps.remove());
- }
- }
- final String scenarioUuid = scenarioUuids.remove(scenario);
- lifecycle.stopTestCase(scenarioUuid);
- lifecycle.writeTestCase(scenarioUuid);
- }
-
- public String getStepName(final Step step) {
- return step.getName();
- }
-
- private void createDataTableAttachment(final List dataTableRows) {
- final StringBuilder dataTableCsv = new StringBuilder();
-
- if (dataTableRows != null && !dataTableRows.isEmpty()) {
- dataTableRows.forEach(dataTableRow -> {
- dataTableCsv.append(String.join("\t", dataTableRow.getCells()));
- dataTableCsv.append('\n');
- });
-
- final String attachmentSource = lifecycle
- .prepareAttachment("Data table", "text/tab-separated-values", "csv");
- lifecycle.writeAttachment(attachmentSource,
- new ByteArrayInputStream(dataTableCsv.toString().getBytes(StandardCharsets.UTF_8)));
- }
- }
-
- @Override
- public void embedding(final String string, final byte[] bytes) {
- lifecycle.addAttachment("Screenshot", null, null, new ByteArrayInputStream(bytes));
- }
-
- @Override
- public void write(final String string) {
- lifecycle.addAttachment(
- "Text output",
- TEXT_PLAIN,
- TXT_EXTENSION,
- Objects.toString(string).getBytes(StandardCharsets.UTF_8)
- );
- }
-
- @Override
- public void syntaxError(final String state, final String event,
- final List legalEvents, final String uri, final Integer line) {
- //Nothing to do with Allure
- }
-
- @Override
- public void uri(final String uri) {
- //Nothing to do with Allure
- }
-
- @Override
- public void scenarioOutline(final ScenarioOutline so) {
- //Nothing to do with Allure
- }
-
- @Override
- public void examples(final Examples exmpls) {
- //Nothing to do with Allure
- }
-
-
- @Override
- public void background(final Background b) {
- //Nothing to do with Allure
- }
-
- @Override
- public void scenario(final Scenario scnr) {
- //Nothing to do with Allure
- }
-
- @Override
- public void done() {
- //Nothing to do with Allure
- }
-
- @Override
- public void close() {
- //Nothing to do with Allure
- }
-
- @Override
- public void eof() {
- //Nothing to do with Allure
-
- }
-
-}
diff --git a/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/LabelBuilder.java b/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/LabelBuilder.java
deleted file mode 100644
index 8acfe213d..000000000
--- a/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/LabelBuilder.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright 2019 Qameta Software OÜ
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.qameta.allure.cucumberjvm;
-
-import gherkin.formatter.model.Feature;
-import gherkin.formatter.model.Scenario;
-import gherkin.formatter.model.Tag;
-import io.qameta.allure.model.Label;
-import io.qameta.allure.model.Link;
-import io.qameta.allure.util.ResultsUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Deque;
-import java.util.List;
-import java.util.Objects;
-import java.util.regex.Pattern;
-
-import static io.qameta.allure.util.ResultsUtils.createFeatureLabel;
-import static io.qameta.allure.util.ResultsUtils.createFrameworkLabel;
-import static io.qameta.allure.util.ResultsUtils.createHostLabel;
-import static io.qameta.allure.util.ResultsUtils.createLanguageLabel;
-import static io.qameta.allure.util.ResultsUtils.createPackageLabel;
-import static io.qameta.allure.util.ResultsUtils.createSeverityLabel;
-import static io.qameta.allure.util.ResultsUtils.createStoryLabel;
-import static io.qameta.allure.util.ResultsUtils.createSuiteLabel;
-import static io.qameta.allure.util.ResultsUtils.createTagLabel;
-import static io.qameta.allure.util.ResultsUtils.createTestClassLabel;
-import static io.qameta.allure.util.ResultsUtils.createThreadLabel;
-
-/**
- * Scenario labels and links builder.
- */
-@SuppressWarnings({"CyclomaticComplexity", "PMD.CyclomaticComplexity", "PMD.NcssCount"})
-class LabelBuilder {
- private static final Logger LOGGER = LoggerFactory.getLogger(LabelBuilder.class);
- private static final String COMPOSITE_TAG_DELIMITER = "=";
-
- private static final String SEVERITY = "@SEVERITY";
- private static final String ISSUE_LINK = "@ISSUE";
- private static final String TMS_LINK = "@TMSLINK";
- private static final String PLAIN_LINK = "@LINK";
-
- private final List scenarioLabels = new ArrayList<>();
- private final List scenarioLinks = new ArrayList<>();
-
- LabelBuilder(final Feature feature, final Scenario scenario, final Deque tags) {
- final TagParser tagParser = new TagParser(feature, scenario);
-
- getScenarioLabels().add(createFeatureLabel(feature.getName()));
- getScenarioLabels().add(createStoryLabel(scenario.getName()));
-
- while (tags.peek() != null) {
- final Tag tag = tags.remove();
-
- final String tagString = tag.getName();
-
- if (tagString.contains(COMPOSITE_TAG_DELIMITER)) {
-
- final String[] tagParts = tagString.split(COMPOSITE_TAG_DELIMITER, 2);
- if (tagParts.length < 2 || Objects.isNull(tagParts[1]) || tagParts[1].isEmpty()) {
- // skip empty tags, e.g. '@tmsLink=', to avoid formatter errors
- continue;
- }
-
- final String tagKey = tagParts[0].toUpperCase();
- final String tagValue = tagParts[1];
-
- // Handle composite named links
- if (tagKey.startsWith(PLAIN_LINK + ".")) {
- tryHandleNamedLink(tagString);
- continue;
- }
-
- switch (tagKey) {
- case SEVERITY:
- getScenarioLabels().add(createSeverityLabel(tagValue.toLowerCase()));
- break;
- case TMS_LINK:
- getScenarioLinks().add(ResultsUtils.createTmsLink(tagValue));
- break;
- case ISSUE_LINK:
- getScenarioLinks().add(ResultsUtils.createIssueLink(tagValue));
- break;
- case PLAIN_LINK:
- getScenarioLinks().add(ResultsUtils.createLink(null, tagValue, tagValue, null));
- break;
- default:
- LOGGER.warn("Composite tag {} is not supported. adding it as RAW", tagKey);
- getScenarioLabels().add(getTagLabel(tag));
- break;
- }
- } else if (tagParser.isPureSeverityTag(tag)) {
- getScenarioLabels().add(createSeverityLabel(tagString.substring(1)));
- } else if (!tagParser.isResultTag(tag)) {
- getScenarioLabels().add(getTagLabel(tag));
- }
- }
-
- getScenarioLabels().addAll(Arrays.asList(
- createHostLabel(),
- createThreadLabel(),
- createPackageLabel(feature.getName()),
- createSuiteLabel(feature.getName()),
- createTestClassLabel(scenario.getName()),
- createFrameworkLabel("cucumberjvm"),
- createLanguageLabel("java")
- ));
-
- }
-
- public List getScenarioLabels() {
- return scenarioLabels;
- }
-
- public List getScenarioLinks() {
- return scenarioLinks;
- }
-
- private Label getTagLabel(final Tag tag) {
- return createTagLabel(tag.getName().substring(1));
- }
-
- /**
- * Handle composite named links.
- *
- * @param tagString Full tag name and value
- */
- private void tryHandleNamedLink(final String tagString) {
- final String namedLinkPatternString = PLAIN_LINK + "\\.(\\w+-?)+=(\\w+(-|_)?)+";
- final Pattern namedLinkPattern = Pattern.compile(namedLinkPatternString, Pattern.CASE_INSENSITIVE);
-
- if (namedLinkPattern.matcher(tagString).matches()) {
- final String type = tagString.split(COMPOSITE_TAG_DELIMITER)[0].split("[.]")[1];
- final String name = tagString.split(COMPOSITE_TAG_DELIMITER)[1];
- getScenarioLinks().add(ResultsUtils.createLink(null, name, null, type));
- } else {
- LOGGER.warn("Composite named tag {} is not matches regex {}. skipping", tagString,
- namedLinkPatternString);
- }
- }
-
-}
diff --git a/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/StepUtils.java b/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/StepUtils.java
deleted file mode 100644
index 99c262b38..000000000
--- a/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/StepUtils.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2019 Qameta Software OÜ
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.qameta.allure.cucumberjvm;
-
-import cucumber.runtime.CucumberException;
-import cucumber.runtime.StepDefinitionMatch;
-import gherkin.formatter.model.Feature;
-import gherkin.formatter.model.Match;
-import gherkin.formatter.model.Result;
-import gherkin.formatter.model.Scenario;
-import gherkin.formatter.model.Step;
-import io.qameta.allure.Allure;
-import io.qameta.allure.AllureLifecycle;
-import io.qameta.allure.model.Status;
-import io.qameta.allure.model.StatusDetails;
-import io.qameta.allure.model.StepResult;
-import io.qameta.allure.util.ResultsUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.lang.reflect.Field;
-import java.util.Objects;
-
-import static io.qameta.allure.util.ResultsUtils.md5;
-
-/**
- * Step utils.
- */
-class StepUtils {
- private static final Logger LOG = LoggerFactory.getLogger(StepUtils.class);
- private static final String FAILED = "failed";
-
- private final AllureLifecycle lifecycle;
- private final Feature feature;
- private final Scenario scenario;
-
- StepUtils(final Feature feature, final Scenario scenario) {
- this.lifecycle = Allure.getLifecycle();
- this.feature = feature;
- this.scenario = scenario;
- }
-
- protected Step extractStep(final StepDefinitionMatch match) {
- try {
- final Field step = match.getClass().getDeclaredField("step");
- step.setAccessible(true);
- return (Step) step.get(match);
- } catch (ReflectiveOperationException e) {
- //shouldn't ever happen
- LOG.error(e.getMessage(), e);
- throw new CucumberException(e);
- }
- }
-
- protected boolean isEqualSteps(final Step step, final Step gherkinStep) {
- return Objects.equals(step.getLine(), gherkinStep.getLine());
- }
-
- protected void fireCanceledStep(final Step unimplementedStep) {
- final StepResult stepResult = new StepResult();
- stepResult.setName(unimplementedStep.getName())
- .setStart(System.currentTimeMillis())
- .setStop(System.currentTimeMillis())
- .setStatus(Status.SKIPPED)
- .setStatusDetails(new StatusDetails().setMessage("Unimplemented step"));
- lifecycle.startStep(scenario.getId(), getStepUuid(unimplementedStep), stepResult);
- lifecycle.stopStep(getStepUuid(unimplementedStep));
-
- final StatusDetails statusDetails = new StatusDetails();
- final TagParser tagParser = new TagParser(feature, scenario);
- statusDetails
- .setFlaky(tagParser.isFlaky())
- .setMuted(tagParser.isMuted())
- .setKnown(tagParser.isKnown());
- lifecycle.updateTestCase(scenario.getId(), scenarioResult ->
- scenarioResult.setStatus(Status.SKIPPED)
- .setStatusDetails(statusDetails
- .setMessage("Unimplemented steps were found")));
- }
-
- protected String getStepUuid(final Step step) {
- return feature.getId() + scenario.getId() + step.getName() + step.getLine();
- }
-
- protected static String getHistoryId(final String id) {
- return md5(id);
- }
-
- protected void fireFixtureStep(final Match match, final Result result, final boolean isBefore) {
- final String uuid = md5(match.getLocation());
- final StepResult stepResult = new StepResult()
- .setName(match.getLocation())
- .setStatus(Status.fromValue(result.getStatus()))
- .setStart(System.currentTimeMillis() - result.getDuration())
- .setStop(System.currentTimeMillis());
- if (FAILED.equals(result.getStatus())) {
- final StatusDetails statusDetails = ResultsUtils.getStatusDetails(result.getError()).get();
- stepResult.setStatusDetails(statusDetails);
- if (isBefore) {
- final TagParser tagParser = new TagParser(feature, scenario);
- statusDetails
- .setMessage("Before is failed: " + result.getError().getLocalizedMessage())
- .setFlaky(tagParser.isFlaky())
- .setMuted(tagParser.isMuted())
- .setKnown(tagParser.isKnown());
- lifecycle.updateTestCase(scenario.getId(), scenarioResult ->
- scenarioResult.setStatus(Status.SKIPPED)
- .setStatusDetails(statusDetails));
- }
- }
- lifecycle.startStep(scenario.getId(), uuid, stepResult);
- lifecycle.stopStep(uuid);
- }
-}
diff --git a/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/TagParser.java b/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/TagParser.java
deleted file mode 100644
index 670aad996..000000000
--- a/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/TagParser.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2019 Qameta Software OÜ
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.qameta.allure.cucumberjvm;
-
-import gherkin.formatter.model.Feature;
-import gherkin.formatter.model.Scenario;
-import gherkin.formatter.model.Tag;
-import io.qameta.allure.SeverityLevel;
-
-import java.util.Arrays;
-
-/**
- * Parser for tags.
- */
-class TagParser {
- private static final String FLAKY = "@FLAKY";
- private static final String KNOWN = "@KNOWN";
- private static final String MUTED = "@MUTED";
-
- private final Feature feature;
- private final Scenario scenario;
-
- TagParser(final Feature feature, final Scenario scenario) {
- this.feature = feature;
- this.scenario = scenario;
- }
-
- protected boolean isFlaky() {
- return getStatusDetailByTag(FLAKY);
- }
-
- protected boolean isMuted() {
- return getStatusDetailByTag(MUTED);
- }
-
- protected boolean isKnown() {
- return getStatusDetailByTag(KNOWN);
- }
-
- protected boolean getStatusDetailByTag(final String tagName) {
- return scenario.getTags().stream()
- .anyMatch(tag -> tag.getName().equalsIgnoreCase(tagName))
- || feature.getTags().stream()
- .anyMatch(tag -> tag.getName().equalsIgnoreCase(tagName));
- }
-
- protected boolean isResultTag(final Tag tag) {
- return Arrays.asList(new String[]{FLAKY, KNOWN, MUTED})
- .contains(tag.getName().toUpperCase());
- }
-
- protected boolean isPureSeverityTag(final Tag tag) {
- return Arrays.stream(SeverityLevel.values())
- .map(SeverityLevel::value)
- .map(value -> "@" + value)
- .anyMatch(value -> value.equalsIgnoreCase(tag.getName()));
- }
-
-}
diff --git a/allure-cucumber-jvm/src/test/java/io/qameta/allure/cucumberjvm/AllureCucumberJvmTest.java b/allure-cucumber-jvm/src/test/java/io/qameta/allure/cucumberjvm/AllureCucumberJvmTest.java
deleted file mode 100644
index 5b63d63ba..000000000
--- a/allure-cucumber-jvm/src/test/java/io/qameta/allure/cucumberjvm/AllureCucumberJvmTest.java
+++ /dev/null
@@ -1,439 +0,0 @@
-/*
- * Copyright 2019 Qameta Software OÜ
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.qameta.allure.cucumberjvm;
-
-import cucumber.api.testng.FeatureResultListener;
-import cucumber.runtime.ClassFinder;
-import cucumber.runtime.Runtime;
-import cucumber.runtime.RuntimeOptions;
-import cucumber.runtime.io.MultiLoader;
-import cucumber.runtime.io.ResourceLoader;
-import cucumber.runtime.io.ResourceLoaderClassFinder;
-import cucumber.runtime.model.CucumberFeature;
-import io.github.glytching.junit.extension.system.SystemProperty;
-import io.github.glytching.junit.extension.system.SystemPropertyExtension;
-import io.qameta.allure.AllureLifecycle;
-import io.qameta.allure.Issue;
-import io.qameta.allure.model.Attachment;
-import io.qameta.allure.model.Label;
-import io.qameta.allure.model.Link;
-import io.qameta.allure.model.Parameter;
-import io.qameta.allure.model.Stage;
-import io.qameta.allure.model.Status;
-import io.qameta.allure.model.StatusDetails;
-import io.qameta.allure.model.StepResult;
-import io.qameta.allure.model.TestResult;
-import io.qameta.allure.test.AllureFeatures;
-import io.qameta.allure.test.AllureResults;
-import io.qameta.allure.test.AllureResultsWriterStub;
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-
-import java.nio.charset.StandardCharsets;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-import static java.lang.Thread.currentThread;
-import static java.util.Objects.nonNull;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.tuple;
-
-/**
- * @author charlie (Dmitry Baev).
- */
-@SuppressWarnings("unchecked")
-class AllureCucumberJvmTest {
-
- @AllureFeatures.Base
- @Test
- void shouldSetName() {
- final AllureResults results = runFeature("features/simple.feature");
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getName)
- .containsExactlyInAnyOrder("Add a to b");
-
- }
-
- @AllureFeatures.PassedTests
- @Test
- void shouldSetStatus() {
- final AllureResults results = runFeature("features/simple.feature");
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.PASSED);
- }
-
- @AllureFeatures.FailedTests
- @Test
- void shouldSetFailedStatus() {
- final AllureResults results = runFeature("features/failed.feature");
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.FAILED);
- }
-
- @AllureFeatures.FailedTests
- @Test
- void shouldSetStatusDetails() {
- final AllureResults results = runFeature("features/failed.feature");
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getStatusDetails)
- .extracting(StatusDetails::getMessage)
- .containsExactlyInAnyOrder("expected: <15> but was: <123>");
- }
-
- @AllureFeatures.BrokenTests
- @Test
- void shouldSetBrokenStatus() {
- final AllureResults results = runFeature("features/broken.feature");
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.BROKEN);
- }
-
- @AllureFeatures.Stages
- @Test
- void shouldSetStage() {
- final AllureResults results = runFeature("features/simple.feature");
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getStage)
- .containsExactlyInAnyOrder(Stage.FINISHED);
- }
-
- @AllureFeatures.Timings
- @Test
- void shouldSetStart() {
- final long before = Instant.now().toEpochMilli();
- final AllureResults results = runFeature("features/simple.feature");
- final long after = Instant.now().toEpochMilli();
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getStart)
- .allMatch(v -> v >= before && v <= after);
- }
-
- @AllureFeatures.Timings
- @Test
- void shouldSetStop() {
- final long before = Instant.now().toEpochMilli();
- final AllureResults results = runFeature("features/simple.feature");
- final long after = Instant.now().toEpochMilli();
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getStop)
- .allMatch(v -> v >= before && v <= after);
- }
-
- @AllureFeatures.FullName
- @Test
- void shouldSetFullName() {
- final AllureResults results = runFeature("features/simple.feature");
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getFullName)
- .containsExactlyInAnyOrder("Simple feature: Add a to b");
- }
-
- @AllureFeatures.Descriptions
- @Test
- void shouldSetDescription() {
- final AllureResults results = runFeature("features/description.feature");
-
- final String expected = "\nThis is description for current feature.\n"
- + "It should appear on each scenario in report";
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getDescription)
- .containsExactlyInAnyOrder(
- expected,
- expected
- );
- }
-
- @AllureFeatures.Attachments
- @Test
- void shouldAddDataTableAttachment() {
- final AllureResults results = runFeature("features/datatable.feature");
-
- final List attachments = results.getTestResults().stream()
- .map(TestResult::getSteps)
- .flatMap(Collection::stream)
- .map(StepResult::getAttachments)
- .flatMap(Collection::stream)
- .collect(Collectors.toList());
-
- assertThat(attachments)
- .extracting(Attachment::getName, Attachment::getType)
- .containsExactlyInAnyOrder(
- tuple("Data table", "text/tab-separated-values")
- );
-
- final Attachment dataTableAttachment = attachments.iterator().next();
- final Map attachmentFiles = results.getAttachments();
- assertThat(attachmentFiles)
- .containsKeys(dataTableAttachment.getSource());
-
- final byte[] bytes = attachmentFiles.get(dataTableAttachment.getSource());
- final String attachmentContent = new String(bytes, StandardCharsets.UTF_8);
-
- assertThat(attachmentContent)
- .isEqualTo("name\tlogin\temail\n" +
- "Viktor\tclicman\tclicman@ya.ru\n" +
- "Viktor2\tclicman2\tclicman2@ya.ru\n"
- );
-
- }
-
- @AllureFeatures.Attachments
- @Test
- void shouldAddAttachments() {
- final AllureResults results = runFeature("features/attachments.feature");
-
- final List attachments = results.getTestResults().stream()
- .map(TestResult::getSteps)
- .flatMap(Collection::stream)
- .map(StepResult::getAttachments)
- .flatMap(Collection::stream)
- .collect(Collectors.toList());
-
- assertThat(attachments)
- .extracting(Attachment::getName, Attachment::getType)
- .containsExactlyInAnyOrder(
- tuple("Text output", "text/plain"),
- tuple("Screenshot", null)
- );
-
- final List attachmentContents = results.getAttachments().values().stream()
- .map(bytes -> new String(bytes, StandardCharsets.UTF_8))
- .collect(Collectors.toList());
-
- assertThat(attachmentContents)
- .containsExactlyInAnyOrder("text attachment", "image attachment");
- }
-
- @AllureFeatures.Steps
- @Disabled("unsupported")
- @Test
- void shouldAddBackgroundSteps() {
- final AllureResults results = runFeature("features/background.feature");
-
- final List testResults = results.getTestResults();
-
- assertThat(testResults)
- .hasSize(1)
- .flatExtracting(TestResult::getSteps)
- .extracting(StepResult::getName)
- .containsExactly(
- "Given cat is sad",
- "And cat is murmur",
- "When Pet the cat",
- "Then Cat is happy"
- );
- }
-
- @AllureFeatures.Parameters
- @Disabled("unsupported")
- @Test
- void shouldAddParametersFromExamples() {
- final AllureResults results = runFeature("features/examples.feature");
-
- final List testResults = results.getTestResults();
-
- assertThat(testResults)
- .hasSize(2);
-
- assertThat(testResults)
- .flatExtracting(TestResult::getParameters)
- .extracting(Parameter::getName, Parameter::getValue)
- .containsExactlyInAnyOrder(
- tuple("a", "1"), tuple("b", "3"), tuple("result", "4"),
- tuple("a", "2"), tuple("b", "4"), tuple("result", "6")
- );
-
- }
-
- @AllureFeatures.MarkerAnnotations
- @Test
- void shouldAddTags() {
- final AllureResults results = runFeature("features/tags.feature");
-
- final List testResults = results.getTestResults();
-
- assertThat(testResults)
- .flatExtracting(TestResult::getLabels)
- .extracting(Label::getName, Label::getValue)
- .contains(
- tuple("tag", "FeatureTag"),
- tuple("tag", "good")
- );
- }
-
- @AllureFeatures.Links
- @ExtendWith(SystemPropertyExtension.class)
- @SystemProperty(name = "allure.link.issue.pattern", value = "https://example.org/issue/{}")
- @SystemProperty(name = "allure.link.tms.pattern", value = "https://example.org/tms/{}")
- @Test
- void shouldAddLinks() {
- final AllureResults results = runFeature("features/tags.feature");
-
- final List testResults = results.getTestResults();
-
- assertThat(testResults)
- .flatExtracting(TestResult::getLinks)
- .extracting(Link::getName, Link::getType, Link::getUrl)
- .contains(
- tuple("OAT-4444", "tms", "https://example.org/tms/OAT-4444"),
- tuple("BUG-22400", "issue", "https://example.org/issue/BUG-22400")
- );
- }
-
- @AllureFeatures.MarkerAnnotations
- @Test
- void shouldAddBddLabels() {
- final AllureResults results = runFeature("features/tags.feature");
-
- final List testResults = results.getTestResults();
-
- assertThat(testResults)
- .flatExtracting(TestResult::getLabels)
- .extracting(Label::getName, Label::getValue)
- .contains(
- tuple("feature", "Test Simple Scenarios"),
- tuple("story", "Add a to b")
- );
- }
-
- @AllureFeatures.Timeline
- @Test
- void shouldThreadHostLabels() {
- final AllureResults results = runFeature("features/tags.feature");
-
- final List testResults = results.getTestResults();
-
- assertThat(testResults)
- .flatExtracting(TestResult::getLabels)
- .extracting(Label::getName)
- .contains("host", "thread");
- }
-
- @AllureFeatures.MarkerAnnotations
- @Test
- void shouldCommonLabels() {
- final AllureResults results = runFeature("features/tags.feature");
-
- final List testResults = results.getTestResults();
-
- assertThat(testResults)
- .flatExtracting(TestResult::getLabels)
- .extracting(Label::getName, Label::getValue)
- .contains(
- tuple("package", "Test Simple Scenarios"),
- tuple("suite", "Test Simple Scenarios"),
- tuple("testClass", "Add a to b")
- );
- }
-
- @AllureFeatures.NotImplementedTests
- @Test
- void shouldProcessNotImplementedScenario() {
- final AllureResults results = runFeature("features/undefined.feature");
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder((Status) null);
- }
-
- @AllureFeatures.Base
- @Disabled("unsupported")
- @Test
- void shouldSupportDryRun() {
- final AllureResults results = runFeature("features/simple.feature", "--dry-run");
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getName, TestResult::getStatus)
- .containsExactlyInAnyOrder(
- tuple("Add a to b", Status.SKIPPED)
- );
- }
-
- @AllureFeatures.Base
- @Issue("173")
- @Issue("164")
- @Test
- void shouldUseUuid() {
- final AllureResults results = runFeature("features/simple.feature");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getUuid)
- .allMatch(uuid -> nonNull(uuid) && uuid.matches("[\\-a-z0-9]+"), "UUID");
- }
-
- private AllureResults runFeature(final String featureResource,
- final String... moreOptions) {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- final AllureLifecycle lifecycle = new AllureLifecycle(writer);
- final AllureCucumberJvm cucumberJvm = new AllureCucumberJvm(lifecycle);
- final ClassLoader classLoader = currentThread().getContextClassLoader();
- final ResourceLoader resourceLoader = new MultiLoader(classLoader);
- final ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader);
- final List opts = new ArrayList<>(Arrays.asList(
- "--glue", "io.qameta.allure.cucumberjvm.samples",
- "--plugin", "null",
- "src/test/resources/" + featureResource
- ));
- opts.addAll(Arrays.asList(moreOptions));
- final RuntimeOptions options = new RuntimeOptions(opts);
- final Runtime runtime = new Runtime(resourceLoader, classFinder, classLoader, options);
-
- options.addPlugin(cucumberJvm);
-
- final FeatureResultListener resultListener = new FeatureResultListener(
- options.reporter(classLoader),
- options.isStrict()
- );
- final List features = options.cucumberFeatures(resourceLoader);
- features.forEach(cucumberFeature -> cucumberFeature.run(
- options.formatter(classLoader),
- resultListener,
- runtime)
- );
- return writer;
- }
-}
diff --git a/allure-cucumber-jvm/src/test/java/io/qameta/allure/cucumberjvm/samples/AttachmentSteps.java b/allure-cucumber-jvm/src/test/java/io/qameta/allure/cucumberjvm/samples/AttachmentSteps.java
deleted file mode 100644
index b6d92c5b5..000000000
--- a/allure-cucumber-jvm/src/test/java/io/qameta/allure/cucumberjvm/samples/AttachmentSteps.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2019 Qameta Software OÜ
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.qameta.allure.cucumberjvm.samples;
-
-import cucumber.api.Scenario;
-import cucumber.api.java.Before;
-import cucumber.api.java.en.Given;
-
-public class AttachmentSteps
-{
- private Scenario scenario;
-
- @Before("@attachments")
- public void setup(Scenario scenario)
- {
- this.scenario = scenario;
- }
-
- @Given("step with scenario write")
- public void stepWithScenarioWrite()
- {
- scenario.write("text attachment");
- }
-
- @Given("step with scenario embed")
- public void stepWithScenarioEmbed()
- {
- scenario.embed("image attachment".getBytes(), "image/png");
- }
-}
diff --git a/allure-cucumber-jvm/src/test/resources/features/undefined.feature b/allure-cucumber-jvm/src/test/resources/features/undefined.feature
deleted file mode 100644
index caa0b507b..000000000
--- a/allure-cucumber-jvm/src/test/resources/features/undefined.feature
+++ /dev/null
@@ -1,4 +0,0 @@
-Feature: Simple feature
-
- Scenario: Step is not implemented
- Given hello my friend
diff --git a/allure-cucumber2-jvm/src/main/java/io/qameta/allure/cucumber2jvm/AllureCucumber2Jvm.java b/allure-cucumber2-jvm/src/main/java/io/qameta/allure/cucumber2jvm/AllureCucumber2Jvm.java
deleted file mode 100644
index 0dd1a38bf..000000000
--- a/allure-cucumber2-jvm/src/main/java/io/qameta/allure/cucumber2jvm/AllureCucumber2Jvm.java
+++ /dev/null
@@ -1,408 +0,0 @@
-/*
- * Copyright 2019 Qameta Software OÜ
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.qameta.allure.cucumber2jvm;
-
-import cucumber.api.HookType;
-import cucumber.api.PendingException;
-import cucumber.api.Result;
-import cucumber.api.TestCase;
-import cucumber.api.TestStep;
-import cucumber.api.event.EmbedEvent;
-import cucumber.api.event.EventHandler;
-import cucumber.api.event.EventPublisher;
-import cucumber.api.event.TestCaseFinished;
-import cucumber.api.event.TestCaseStarted;
-import cucumber.api.event.TestSourceRead;
-import cucumber.api.event.TestStepFinished;
-import cucumber.api.event.TestStepStarted;
-import cucumber.api.event.WriteEvent;
-import cucumber.api.formatter.Formatter;
-import cucumber.runner.UnskipableStep;
-import gherkin.ast.Examples;
-import gherkin.ast.Feature;
-import gherkin.ast.ScenarioDefinition;
-import gherkin.ast.ScenarioOutline;
-import gherkin.ast.TableCell;
-import gherkin.pickles.PickleCell;
-import gherkin.pickles.PickleRow;
-import gherkin.pickles.PickleTable;
-import gherkin.pickles.PickleTag;
-import io.qameta.allure.Allure;
-import io.qameta.allure.AllureLifecycle;
-import io.qameta.allure.model.FixtureResult;
-import io.qameta.allure.model.Parameter;
-import io.qameta.allure.model.Status;
-import io.qameta.allure.model.StatusDetails;
-import io.qameta.allure.model.StepResult;
-import io.qameta.allure.model.TestResult;
-import io.qameta.allure.model.TestResultContainer;
-
-import java.io.ByteArrayInputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.UUID;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-
-import static io.qameta.allure.util.ResultsUtils.getStatus;
-import static io.qameta.allure.util.ResultsUtils.getStatusDetails;
-import static io.qameta.allure.util.ResultsUtils.md5;
-
-/**
- * Allure plugin for Cucumber JVM 2.0.
- */
-@SuppressWarnings({
- "PMD.ExcessiveImports",
- "ClassFanOutComplexity", "ClassDataAbstractionCoupling"
-})
-public class AllureCucumber2Jvm implements Formatter {
-
- private final AllureLifecycle lifecycle;
-
- private final Map scenarioUuids = new HashMap<>();
-
- private final CucumberSourceUtils cucumberSourceUtils = new CucumberSourceUtils();
- private Feature currentFeature;
- private String currentFeatureFile;
- private TestCase currentTestCase;
- private String currentContainer;
- private boolean forbidTestCaseStatusChange;
-
- private final EventHandler featureStartedHandler = this::handleFeatureStartedHandler;
- private final EventHandler caseStartedHandler = this::handleTestCaseStarted;
- private final EventHandler caseFinishedHandler = this::handleTestCaseFinished;
- private final EventHandler stepStartedHandler = this::handleTestStepStarted;
- private final EventHandler stepFinishedHandler = this::handleTestStepFinished;
- private final EventHandler writeEventHandler = this::handleWriteEvent;
- private final EventHandler embedEventHandler = this::handleEmbedEvent;
-
- private static final String TXT_EXTENSION = ".txt";
- private static final String TEXT_PLAIN = "text/plain";
-
- @SuppressWarnings("unused")
- public AllureCucumber2Jvm() {
- this(Allure.getLifecycle());
- }
-
- public AllureCucumber2Jvm(final AllureLifecycle lifecycle) {
- this.lifecycle = lifecycle;
- }
-
- @Override
- public void setEventPublisher(final EventPublisher publisher) {
- publisher.registerHandlerFor(TestSourceRead.class, featureStartedHandler);
-
- publisher.registerHandlerFor(TestCaseStarted.class, caseStartedHandler);
- publisher.registerHandlerFor(TestCaseFinished.class, caseFinishedHandler);
-
- publisher.registerHandlerFor(TestStepStarted.class, stepStartedHandler);
- publisher.registerHandlerFor(TestStepFinished.class, stepFinishedHandler);
-
- publisher.registerHandlerFor(WriteEvent.class, writeEventHandler);
- publisher.registerHandlerFor(EmbedEvent.class, embedEventHandler);
- }
-
- /*
- Event Handlers
- */
-
- private void handleFeatureStartedHandler(final TestSourceRead event) {
- cucumberSourceUtils.addTestSourceReadEvent(event.uri, event);
- }
-
- private void handleTestCaseStarted(final TestCaseStarted event) {
- currentTestCase = event.testCase;
- currentFeatureFile = currentTestCase.getUri();
- currentFeature = cucumberSourceUtils.getFeature(currentFeatureFile);
- currentContainer = UUID.randomUUID().toString();
- forbidTestCaseStatusChange = false;
-
-
- final Deque tags = new LinkedList<>(currentTestCase.getTags());
-
- final LabelBuilder labelBuilder = new LabelBuilder(currentFeature, currentTestCase, tags);
-
- final String name = currentTestCase.getName();
- final String featureName = currentFeature.getName();
-
- final TestResult result = new TestResult()
- .setUuid(getTestCaseUuid(currentTestCase))
- .setHistoryId(getHistoryId(currentTestCase))
- .setFullName(featureName + ": " + name)
- .setName(name)
- .setLabels(labelBuilder.getScenarioLabels())
- .setLinks(labelBuilder.getScenarioLinks());
-
- final ScenarioDefinition scenarioDefinition =
- cucumberSourceUtils.getScenarioDefinition(currentFeatureFile, currentTestCase.getLine());
- if (scenarioDefinition instanceof ScenarioOutline) {
- result.setParameters(
- getExamplesAsParameters((ScenarioOutline) scenarioDefinition)
- );
- }
-
- if (currentFeature.getDescription() != null && !currentFeature.getDescription().isEmpty()) {
- result.setDescription(currentFeature.getDescription());
- }
-
- final TestResultContainer resultContainer = new TestResultContainer()
- .setName(String.format("%s: %s", scenarioDefinition.getKeyword(), scenarioDefinition.getName()))
- .setUuid(getTestContainerUuid())
- .setChildren(Collections.singletonList(getTestCaseUuid(currentTestCase)));
-
- lifecycle.scheduleTestCase(result);
- lifecycle.startTestContainer(getTestContainerUuid(), resultContainer);
- lifecycle.startTestCase(getTestCaseUuid(currentTestCase));
- }
-
- private void handleTestCaseFinished(final TestCaseFinished event) {
-
- final String uuid = getTestCaseUuid(event.testCase);
- final Optional details = getStatusDetails(event.result.getError());
- details.ifPresent(statusDetails -> lifecycle.updateTestCase(
- uuid,
- testResult -> testResult.setStatusDetails(statusDetails)
- ));
- lifecycle.stopTestCase(uuid);
- lifecycle.stopTestContainer(getTestContainerUuid());
- lifecycle.writeTestCase(uuid);
- lifecycle.writeTestContainer(getTestContainerUuid());
- }
-
- private void handleTestStepStarted(final TestStepStarted event) {
- if (!event.testStep.isHook()) {
- final String stepKeyword = Optional.ofNullable(
- cucumberSourceUtils.getKeywordFromSource(currentFeatureFile, event.testStep.getStepLine())
- ).orElse("UNDEFINED");
-
- final StepResult stepResult = new StepResult()
- .setName(String.format("%s %s", stepKeyword, event.testStep.getPickleStep().getText()))
- .setStart(System.currentTimeMillis());
-
- lifecycle.startStep(getTestCaseUuid(currentTestCase), getStepUuid(event.testStep), stepResult);
-
- event.testStep.getStepArgument().stream()
- .filter(PickleTable.class::isInstance)
- .findFirst()
- .ifPresent(table -> createDataTableAttachment((PickleTable) table));
- } else if (event.testStep instanceof UnskipableStep) {
- initHook((UnskipableStep) event.testStep);
- }
- }
-
- private void initHook(final UnskipableStep hook) {
-
- final FixtureResult hookResult = new FixtureResult()
- .setName(hook.getCodeLocation())
- .setStart(System.currentTimeMillis());
-
- if (hook.getHookType() == HookType.Before) {
- lifecycle.startPrepareFixture(getTestContainerUuid(), getHookStepUuid(hook), hookResult);
- } else {
- lifecycle.startTearDownFixture(getTestContainerUuid(), getHookStepUuid(hook), hookResult);
- }
-
- }
-
- private void handleTestStepFinished(final TestStepFinished event) {
- if (event.testStep.isHook() && event.testStep instanceof UnskipableStep) {
- handleHookStep(event);
- } else {
- handlePickleStep(event);
- }
- }
-
- private void handleWriteEvent(final WriteEvent event) {
- lifecycle.addAttachment(
- "Text output",
- TEXT_PLAIN,
- TXT_EXTENSION,
- Objects.toString(event.text).getBytes(StandardCharsets.UTF_8)
- );
- }
-
- private void handleEmbedEvent(final EmbedEvent event) {
- lifecycle.addAttachment("Screenshot", null, null, new ByteArrayInputStream(event.data));
- }
-
- /*
- Utility Methods
- */
-
- private String getTestContainerUuid() {
- return currentContainer;
- }
-
- private String getTestCaseUuid(final TestCase testCase) {
- return scenarioUuids.computeIfAbsent(getHistoryId(testCase), it -> UUID.randomUUID().toString());
- }
-
- private String getStepUuid(final TestStep step) {
- return currentFeature.getName() + getTestCaseUuid(currentTestCase)
- + step.getPickleStep().getText() + step.getStepLine();
- }
-
- private String getHookStepUuid(final TestStep step) {
- return currentFeature.getName() + getTestCaseUuid(currentTestCase)
- + step.getHookType().toString() + step.getCodeLocation();
- }
-
- private String getHistoryId(final TestCase testCase) {
- final String testCaseLocation = testCase.getUri() + ":" + testCase.getLine();
- return md5(testCaseLocation);
- }
-
- private Status translateTestCaseStatus(final Result testCaseResult) {
- switch (testCaseResult.getStatus()) {
- case FAILED:
- return getStatus(testCaseResult.getError())
- .orElse(Status.FAILED);
- case PASSED:
- return Status.PASSED;
- case SKIPPED:
- case PENDING:
- return Status.SKIPPED;
- case AMBIGUOUS:
- case UNDEFINED:
- default:
- return null;
- }
- }
-
- private List getExamplesAsParameters(final ScenarioOutline scenarioOutline) {
- final int gap = 2;
- final Optional examplesBlock = scenarioOutline.getExamples().stream()
- .filter(e -> currentTestCase.getLine() >= e.getLocation().getLine() + gap)
- .filter(e -> currentTestCase.getLine() < e.getLocation().getLine() + e.getTableBody().size() + gap)
- .findFirst();
-
- if (examplesBlock.isPresent()) {
- final Examples examples = examplesBlock.get();
- final int rowIndex = currentTestCase.getLine() - examples.getLocation().getLine() - gap;
- final List names = examples.getTableHeader().getCells();
- final List values = examples.getTableBody().get(rowIndex).getCells();
- return IntStream.range(0, examplesBlock.get().getTableHeader().getCells().size()).mapToObj(index -> {
- final String name = names.get(index).getValue();
- final String value = values.get(index).getValue();
- return new Parameter().setName(name).setValue(value);
- }).collect(Collectors.toList());
- }
- return Collections.emptyList();
- }
-
- private void createDataTableAttachment(final PickleTable pickleTable) {
- final List rows = pickleTable.getRows();
-
- final StringBuilder dataTableCsv = new StringBuilder();
- if (!rows.isEmpty()) {
- rows.forEach(dataTableRow -> {
- dataTableCsv.append(
- dataTableRow.getCells().stream()
- .map(PickleCell::getValue)
- .collect(Collectors.joining("\t"))
- );
- dataTableCsv.append('\n');
- });
-
- final String attachmentSource = lifecycle
- .prepareAttachment("Data table", "text/tab-separated-values", "csv");
- lifecycle.writeAttachment(attachmentSource,
- new ByteArrayInputStream(dataTableCsv.toString().getBytes(StandardCharsets.UTF_8)));
- }
- }
-
- private void handleHookStep(final TestStepFinished event) {
- final String uuid = getHookStepUuid(event.testStep);
- final FixtureResult fixtureResult = new FixtureResult().setStatus(translateTestCaseStatus(event.result));
-
- if (!Status.PASSED.equals(fixtureResult.getStatus())) {
- final TestResult testResult = new TestResult().setStatus(translateTestCaseStatus(event.result));
- final StatusDetails statusDetails = getStatusDetails(event.result.getError()).get();
-
- statusDetails.setMessage(event.testStep.getHookType()
- .name() + " is failed: " + event.result.getError().getLocalizedMessage());
-
- if (event.testStep.getHookType() == HookType.Before) {
- final TagParser tagParser = new TagParser(currentFeature, currentTestCase);
- statusDetails
- .setFlaky(tagParser.isFlaky())
- .setMuted(tagParser.isMuted())
- .setKnown(tagParser.isKnown());
- testResult.setStatus(Status.SKIPPED);
- updateTestCaseStatus(testResult.getStatus());
- forbidTestCaseStatusChange = true;
- } else {
- testResult.setStatus(Status.BROKEN);
- updateTestCaseStatus(testResult.getStatus());
- }
- fixtureResult.setStatusDetails(statusDetails);
- }
-
- lifecycle.updateFixture(uuid, result -> result.setStatus(fixtureResult.getStatus())
- .setStatusDetails(fixtureResult.getStatusDetails()));
- lifecycle.stopFixture(uuid);
- }
-
- private void handlePickleStep(final TestStepFinished event) {
-
- final Status stepStatus = translateTestCaseStatus(event.result);
- final StatusDetails statusDetails;
- if (event.result.getStatus() == Result.Type.UNDEFINED) {
- updateTestCaseStatus(Status.PASSED);
-
- statusDetails =
- getStatusDetails(new PendingException("TODO: implement me"))
- .orElse(new StatusDetails());
- lifecycle.updateTestCase(getTestCaseUuid(currentTestCase), scenarioResult ->
- scenarioResult
- .setStatusDetails(statusDetails));
- } else {
- statusDetails =
- getStatusDetails(event.result.getError())
- .orElse(new StatusDetails());
- updateTestCaseStatus(stepStatus);
- }
-
-
- if (!Status.PASSED.equals(stepStatus) && stepStatus != null) {
- forbidTestCaseStatusChange = true;
- }
-
- final TagParser tagParser = new TagParser(currentFeature, currentTestCase);
- statusDetails
- .setFlaky(tagParser.isFlaky())
- .setMuted(tagParser.isMuted())
- .setKnown(tagParser.isKnown());
-
- lifecycle.updateStep(getStepUuid(event.testStep),
- stepResult -> stepResult.setStatus(stepStatus).setStatusDetails(statusDetails));
- lifecycle.stopStep(getStepUuid(event.testStep));
- }
-
- private void updateTestCaseStatus(final Status status) {
- if (!forbidTestCaseStatusChange) {
- lifecycle.updateTestCase(getTestCaseUuid(currentTestCase),
- result -> result.setStatus(status));
- }
- }
-}
diff --git a/allure-cucumber2-jvm/src/main/java/io/qameta/allure/cucumber2jvm/CucumberSourceUtils.java b/allure-cucumber2-jvm/src/main/java/io/qameta/allure/cucumber2jvm/CucumberSourceUtils.java
deleted file mode 100644
index 519db53f6..000000000
--- a/allure-cucumber2-jvm/src/main/java/io/qameta/allure/cucumber2jvm/CucumberSourceUtils.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright 2019 Qameta Software OÜ
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.qameta.allure.cucumber2jvm;
-
-import cucumber.api.event.TestSourceRead;
-
-import gherkin.Parser;
-import gherkin.AstBuilder;
-import gherkin.TokenMatcher;
-import gherkin.ParserException;
-import gherkin.GherkinDialect;
-import gherkin.GherkinDialectProvider;
-
-import gherkin.ast.GherkinDocument;
-import gherkin.ast.Feature;
-import gherkin.ast.ScenarioDefinition;
-import gherkin.ast.Node;
-import gherkin.ast.ScenarioOutline;
-import gherkin.ast.TableRow;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.stream.IntStream;
-
-/**
- * Parts of package-private cucumber.runtime.formatter.TestSourcesModel needed for Allure 2 adapter.
- */
-public final class CucumberSourceUtils {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(CucumberSourceUtils.class);
-
- private final Map pathToReadEventMap = new HashMap<>();
- private final Map pathToAstMap = new HashMap<>();
- private final Map> pathToNodeMap = new HashMap<>();
-
- public void addTestSourceReadEvent(final String path, final TestSourceRead event) {
- pathToReadEventMap.put(path, event);
- }
-
- public Feature getFeature(final String path) {
- if (!pathToAstMap.containsKey(path)) {
- parseGherkinSource(path);
- }
- if (pathToAstMap.containsKey(path)) {
- return pathToAstMap.get(path).getFeature();
- }
- return null;
- }
-
- private void parseGherkinSource(final String path) {
- if (!pathToReadEventMap.containsKey(path)) {
- return;
- }
- final Parser parser = new Parser<>(new AstBuilder());
- final TokenMatcher matcher = new TokenMatcher();
- try {
- final GherkinDocument gherkinDocument = parser.parse(pathToReadEventMap.get(path).source, matcher);
- pathToAstMap.put(path, gherkinDocument);
- final Map nodeMap = new HashMap<>();
- final AstNode currentParent = new AstNode(gherkinDocument.getFeature(), null);
- for (ScenarioDefinition child : gherkinDocument.getFeature().getChildren()) {
- processScenarioDefinition(nodeMap, child, currentParent);
- }
- pathToNodeMap.put(path, nodeMap);
- } catch (ParserException e) {
- LOGGER.trace(e.getMessage(), e);
- }
- }
-
- private void processScenarioDefinition(
- final Map nodeMap, final ScenarioDefinition child, final AstNode currentParent
- ) {
- final AstNode childNode = new AstNode(child, currentParent);
- nodeMap.put(child.getLocation().getLine(), childNode);
-
- child.getSteps().forEach(
- step -> nodeMap.put(step.getLocation().getLine(), new AstNode(step, childNode))
- );
-
- if (child instanceof ScenarioOutline) {
- processScenarioOutlineExamples(nodeMap, (ScenarioOutline) child, childNode);
- }
- }
-
- private void processScenarioOutlineExamples(
- final Map nodeMap, final ScenarioOutline scenarioOutline, final AstNode childNode
- ) {
- scenarioOutline.getExamples().forEach(examples -> {
- final AstNode examplesNode = new AstNode(examples, childNode);
- final TableRow headerRow = examples.getTableHeader();
- final AstNode headerNode = new AstNode(headerRow, examplesNode);
- nodeMap.put(headerRow.getLocation().getLine(), headerNode);
- IntStream.range(0, examples.getTableBody().size()).forEach(i -> {
- final TableRow examplesRow = examples.getTableBody().get(i);
- final Node rowNode = new CucumberSourceUtils.ExamplesRowWrapperNode(examplesRow, i);
- final AstNode expandedScenarioNode = new AstNode(rowNode, examplesNode);
- nodeMap.put(examplesRow.getLocation().getLine(), expandedScenarioNode);
- });
- });
- }
-
- private AstNode getAstNode(final String path, final int line) {
- if (!pathToNodeMap.containsKey(path)) {
- parseGherkinSource(path);
- }
- if (pathToNodeMap.containsKey(path)) {
- return pathToNodeMap.get(path).get(line);
- }
- return null;
- }
-
- public ScenarioDefinition getScenarioDefinition(final String path, final int line) {
- return getScenarioDefinition(getAstNode(path, line));
- }
-
- private ScenarioDefinition getScenarioDefinition(final AstNode astNode) {
- return astNode.getNode() instanceof ScenarioDefinition
- ? (ScenarioDefinition) astNode.getNode()
- : (ScenarioDefinition) astNode.getParent().getParent().getNode();
- }
-
- public String getKeywordFromSource(final String uri, final int stepLine) {
- final Feature feature = getFeature(uri);
- if (feature != null) {
- final TestSourceRead event = getTestSourceReadEvent(uri);
- final String trimmedSourceLine = event.source.split("\n")[stepLine - 1].trim();
- final GherkinDialect dialect = new GherkinDialectProvider(feature.getLanguage()).getDefaultDialect();
- for (String keyword : dialect.getStepKeywords()) {
- if (trimmedSourceLine.startsWith(keyword)) {
- return keyword;
- }
- }
- }
- return "";
- }
-
- private TestSourceRead getTestSourceReadEvent(final String uri) {
- if (pathToReadEventMap.containsKey(uri)) {
- return pathToReadEventMap.get(uri);
- }
- return null;
- }
-
- /**
- * Representation of Examples row.
- */
- private static class ExamplesRowWrapperNode extends Node {
- private final int bodyRowIndex;
-
- ExamplesRowWrapperNode(final Node examplesRow, final int bodyRowIndex) {
- super(examplesRow.getLocation());
- this.bodyRowIndex = bodyRowIndex;
- }
-
- public int getBodyRowIndex() {
- return bodyRowIndex;
- }
- }
-
- /**
- * Representation of leaf node.
- */
- private static class AstNode {
- private final Node node;
- private final AstNode parent;
-
- AstNode(final Node node, final AstNode parent) {
- this.node = node;
- this.parent = parent;
- }
-
- public Node getNode() {
- return node;
- }
-
- public AstNode getParent() {
- return parent;
- }
- }
-}
diff --git a/allure-cucumber2-jvm/src/test/java/io/qameta/allure/cucumber2jvm/AllureCucumber2JvmTest.java b/allure-cucumber2-jvm/src/test/java/io/qameta/allure/cucumber2jvm/AllureCucumber2JvmTest.java
deleted file mode 100644
index 10498f014..000000000
--- a/allure-cucumber2-jvm/src/test/java/io/qameta/allure/cucumber2jvm/AllureCucumber2JvmTest.java
+++ /dev/null
@@ -1,566 +0,0 @@
-/*
- * Copyright 2019 Qameta Software OÜ
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.qameta.allure.cucumber2jvm;
-
-import cucumber.runtime.ClassFinder;
-import cucumber.runtime.Runtime;
-import cucumber.runtime.RuntimeOptions;
-import cucumber.runtime.io.MultiLoader;
-import cucumber.runtime.io.ResourceLoader;
-import cucumber.runtime.io.ResourceLoaderClassFinder;
-import cucumber.runtime.model.CucumberFeature;
-import gherkin.AstBuilder;
-import gherkin.Parser;
-import gherkin.TokenMatcher;
-import gherkin.ast.GherkinDocument;
-import io.github.glytching.junit.extension.system.SystemProperty;
-import io.github.glytching.junit.extension.system.SystemPropertyExtension;
-import io.qameta.allure.AllureLifecycle;
-import io.qameta.allure.Issue;
-import io.qameta.allure.model.Attachment;
-import io.qameta.allure.model.Label;
-import io.qameta.allure.model.Link;
-import io.qameta.allure.model.Parameter;
-import io.qameta.allure.model.Stage;
-import io.qameta.allure.model.Status;
-import io.qameta.allure.model.StatusDetails;
-import io.qameta.allure.model.StepResult;
-import io.qameta.allure.model.TestResult;
-import io.qameta.allure.test.AllureFeatures;
-import io.qameta.allure.test.AllureResults;
-import io.qameta.allure.test.AllureResultsWriterStub;
-import org.apache.commons.io.IOUtils;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-import static java.lang.Thread.currentThread;
-import static java.util.Objects.nonNull;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.tuple;
-
-/**
- * @author charlie (Dmitry Baev).
- */
-@SuppressWarnings("unchecked")
-class AllureCucumber2JvmTest {
-
- @AllureFeatures.Base
- @Test
- void shouldSetName() {
- final AllureResults results = runFeature("features/simple.feature");
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getName)
- .containsExactlyInAnyOrder("Add a to b");
-
- }
-
- @AllureFeatures.PassedTests
- @Test
- void shouldSetStatus() {
- final AllureResults results = runFeature("features/simple.feature");
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.PASSED);
- }
-
- @AllureFeatures.FailedTests
- @Test
- void shouldSetFailedStatus() {
- final AllureResults results = runFeature("features/failed.feature");
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.FAILED);
- }
-
- @AllureFeatures.FailedTests
- @Test
- void shouldSetStatusDetails() {
- final AllureResults results = runFeature("features/failed.feature");
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getStatusDetails)
- .extracting(StatusDetails::getMessage)
- .containsExactlyInAnyOrder("expected: <15> but was: <123>");
- }
-
- @AllureFeatures.BrokenTests
- @Test
- void shouldSetBrokenStatus() {
- final AllureResults results = runFeature("features/broken.feature");
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.BROKEN);
- }
-
- @AllureFeatures.Stages
- @Test
- void shouldSetStage() {
- final AllureResults results = runFeature("features/simple.feature");
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getStage)
- .containsExactlyInAnyOrder(Stage.FINISHED);
- }
-
- @AllureFeatures.Timeline
- @Test
- void shouldSetStart() {
- final long before = Instant.now().toEpochMilli();
- final AllureResults results = runFeature("features/simple.feature");
- final long after = Instant.now().toEpochMilli();
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getStart)
- .allMatch(v -> v >= before && v <= after);
- }
-
- @AllureFeatures.Timings
- @Test
- void shouldSetStop() {
- final long before = Instant.now().toEpochMilli();
- final AllureResults results = runFeature("features/simple.feature");
- final long after = Instant.now().toEpochMilli();
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getStop)
- .allMatch(v -> v >= before && v <= after);
- }
-
- @AllureFeatures.FullName
- @Test
- void shouldSetFullName() {
- final AllureResults results = runFeature("features/simple.feature");
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getFullName)
- .containsExactlyInAnyOrder("Simple feature: Add a to b");
- }
-
- @AllureFeatures.Descriptions
- @Test
- void shouldSetDescription() {
- final AllureResults results = runFeature("features/description.feature");
-
- final String expected = "This is description for current feature.\n"
- + "It should appear on each scenario in report";
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getDescription)
- .containsExactlyInAnyOrder(
- expected,
- expected
- );
- }
-
- @AllureFeatures.Attachments
- @Test
- void shouldAddDataTableAttachment() {
- final AllureResults results = runFeature("features/datatable.feature");
-
- final List attachments = results.getTestResults().stream()
- .map(TestResult::getSteps)
- .flatMap(Collection::stream)
- .map(StepResult::getAttachments)
- .flatMap(Collection::stream)
- .collect(Collectors.toList());
-
- assertThat(attachments)
- .extracting(Attachment::getName, Attachment::getType)
- .containsExactlyInAnyOrder(
- tuple("Data table", "text/tab-separated-values")
- );
-
- final Attachment dataTableAttachment = attachments.iterator().next();
- final Map attachmentFiles = results.getAttachments();
- assertThat(attachmentFiles)
- .containsKeys(dataTableAttachment.getSource());
-
- final byte[] bytes = attachmentFiles.get(dataTableAttachment.getSource());
- final String attachmentContent = new String(bytes, StandardCharsets.UTF_8);
-
- assertThat(attachmentContent)
- .isEqualTo("name\tlogin\temail\n" +
- "Viktor\tclicman\tclicman@ya.ru\n" +
- "Viktor2\tclicman2\tclicman2@ya.ru\n"
- );
-
- }
-
- @AllureFeatures.Attachments
- @Test
- void shouldAddAttachments() {
- final AllureResults results = runFeature("features/attachments.feature");
-
- final List attachments = results.getTestResults().stream()
- .map(TestResult::getSteps)
- .flatMap(Collection::stream)
- .map(StepResult::getAttachments)
- .flatMap(Collection::stream)
- .collect(Collectors.toList());
-
- assertThat(attachments)
- .extracting(Attachment::getName, Attachment::getType)
- .containsExactlyInAnyOrder(
- tuple("Text output", "text/plain"),
- tuple("Screenshot", null)
- );
-
- final List attachmentContents = results.getAttachments().values().stream()
- .map(bytes -> new String(bytes, StandardCharsets.UTF_8))
- .collect(Collectors.toList());
-
- assertThat(attachmentContents)
- .containsExactlyInAnyOrder("text attachment", "image attachment");
- }
-
- @AllureFeatures.Steps
- @Test
- void shouldAddBackgroundSteps() {
- final AllureResults results = runFeature("features/background.feature");
-
- final List testResults = results.getTestResults();
-
- assertThat(testResults)
- .hasSize(1)
- .flatExtracting(TestResult::getSteps)
- .extracting(StepResult::getName)
- .containsExactly(
- "Given cat is sad",
- "And cat is murmur",
- "When Pet the cat",
- "Then Cat is happy"
- );
- }
-
- @AllureFeatures.Parameters
- @Test
- void shouldAddParametersFromExamples() {
- final AllureResults results = runFeature("features/examples.feature");
-
- final List testResults = results.getTestResults();
-
- assertThat(testResults)
- .hasSize(2);
-
- assertThat(testResults.get(0).getParameters())
- .hasSize(3);
-
- assertThat(testResults.get(1).getParameters())
- .hasSize(3);
-
- assertThat(testResults)
- .flatExtracting(TestResult::getParameters)
- .extracting(Parameter::getName, Parameter::getValue)
- .containsExactlyInAnyOrder(
- tuple("a", "1"), tuple("b", "3"), tuple("result", "4"),
- tuple("a", "2"), tuple("b", "4"), tuple("result", "6")
- );
- }
-
- @AllureFeatures.Parameters
- @Test
- void shouldHandleMultipleExampleTables() {
- final AllureResults results = runFeature("features/multipleExamples.feature");
-
- final List testResults = results.getTestResults();
-
- assertThat(testResults)
- .hasSize(2);
- }
-
- @AllureFeatures.MarkerAnnotations
- @Test
- void shouldAddTags() {
- final AllureResults results = runFeature("features/tags.feature");
-
- final List testResults = results.getTestResults();
-
- assertThat(testResults)
- .flatExtracting(TestResult::getLabels)
- .extracting(Label::getName, Label::getValue)
- .contains(
- tuple("tag", "FeatureTag"),
- tuple("tag", "good")
- );
- }
-
- @AllureFeatures.Links
- @ExtendWith(SystemPropertyExtension.class)
- @SystemProperty(name = "allure.link.issue.pattern", value = "https://example.org/issue/{}")
- @SystemProperty(name = "allure.link.tms.pattern", value = "https://example.org/tms/{}")
- @Test
- void shouldAddLinks() {
- final AllureResults results = runFeature("features/tags.feature");
-
- final List testResults = results.getTestResults();
-
- assertThat(testResults)
- .flatExtracting(TestResult::getLinks)
- .extracting(Link::getName, Link::getType, Link::getUrl)
- .contains(
- tuple("OAT-4444", "tms", "https://example.org/tms/OAT-4444"),
- tuple("BUG-22400", "issue", "https://example.org/issue/BUG-22400")
- );
- }
-
- @AllureFeatures.MarkerAnnotations
- @Test
- void shouldAddBddLabels() {
- final AllureResults results = runFeature("features/tags.feature");
-
- final List testResults = results.getTestResults();
-
- assertThat(testResults)
- .flatExtracting(TestResult::getLabels)
- .extracting(Label::getName, Label::getValue)
- .contains(
- tuple("feature", "Test Simple Scenarios"),
- tuple("story", "Add a to b")
- );
- }
-
- @AllureFeatures.Timeline
- @Test
- void shouldThreadHostLabels() {
- final AllureResults results = runFeature("features/tags.feature");
-
- final List testResults = results.getTestResults();
-
- assertThat(testResults)
- .flatExtracting(TestResult::getLabels)
- .extracting(Label::getName)
- .contains("host", "thread");
- }
-
- @AllureFeatures.MarkerAnnotations
- @Test
- void shouldCommonLabels() {
- final AllureResults results = runFeature("features/tags.feature");
-
- final List testResults = results.getTestResults();
-
- assertThat(testResults)
- .flatExtracting(TestResult::getLabels)
- .extracting(Label::getName, Label::getValue)
- .contains(
- tuple("package", "Test Simple Scenarios"),
- tuple("suite", "Test Simple Scenarios"),
- tuple("testClass", "Add a to b")
- );
- }
-
- @AllureFeatures.NotImplementedTests
- @Test
- void shouldProcessNotImplementedScenario() {
- final AllureResults results = runFeature("features/undefined.feature");
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.PASSED);
- }
-
- @AllureFeatures.Base
- @Test
- void shouldSupportDryRun() {
- final AllureResults results = runFeature("features/simple.feature", "--dry-run");
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getName, TestResult::getStatus)
- .containsExactlyInAnyOrder(
- tuple("Add a to b", Status.SKIPPED)
- );
- }
-
- @AllureFeatures.Base
- @Issue("173")
- @Issue("164")
- @Test
- void shouldUseUuid() {
- final AllureResults results = runFeature("features/simple.feature");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getUuid)
- .allMatch(uuid -> nonNull(uuid) && uuid.matches("[\\-a-z0-9]+"), "UUID");
- }
-
- private AllureResults runFeature(final String featureResource,
- final String... moreOptions) {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- final AllureLifecycle lifecycle = new AllureLifecycle(writer);
- final AllureCucumber2Jvm cucumber2Jvm = new AllureCucumber2Jvm(lifecycle);
- final ClassLoader classLoader = currentThread().getContextClassLoader();
- final ResourceLoader resourceLoader = new MultiLoader(classLoader);
- final ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader);
- final List opts = new ArrayList<>(Arrays.asList(
- "--glue", "io.qameta.allure.cucumber2jvm.samples",
- "--plugin", "null"
- ));
- opts.addAll(Arrays.asList(moreOptions));
- final RuntimeOptions options = new RuntimeOptions(opts);
- final Runtime runtime = new Runtime(resourceLoader, classFinder, classLoader, options);
-
- options.addPlugin(cucumber2Jvm);
-
- final String gherkin = readResource(featureResource);
- Parser parser = new Parser<>(new AstBuilder());
- TokenMatcher matcher = new TokenMatcher();
- GherkinDocument gherkinDocument = parser.parse(gherkin, matcher);
- CucumberFeature feature = new CucumberFeature(gherkinDocument, featureResource, gherkin);
-
- feature.sendTestSourceRead(runtime.getEventBus());
- runtime.runFeature(feature);
- return writer;
- }
-
- @AllureFeatures.Fixtures
- @Test
- void shouldSetStatusFailedOnBadAfter() {
- final AllureResults results = runFeature("features/hooks.feature", "-t", "@bp_sf_af");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.FAILED);
- }
-
-
- @AllureFeatures.Fixtures
- @Test
- void shouldSetStatusSkippedOnBadBefore() {
- final AllureResults results = runFeature("features/hooks.feature", "-t", "@bf_ap");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.SKIPPED);
- }
-
- @AllureFeatures.Fixtures
- @Test
- void shouldSetStatusSkippedOnBadBeforeAndBadAfter() {
- final AllureResults results = runFeature("features/hooks.feature", "-t", "@bf_af");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.SKIPPED);
- }
-
- @AllureFeatures.Fixtures
- @Test
- void shouldSetStatusBrokenOnBadAfter() {
- final AllureResults results = runFeature("features/hooks.feature", "-t", "@bp_af");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.BROKEN);
- }
-
- @AllureFeatures.Fixtures
- @Test
- void shouldSetStatusFailedOnBadSteps() {
- final AllureResults results = runFeature("features/hooks.feature", "-t", "@bp_sf_ap");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.FAILED);
- }
-
- @AllureFeatures.NotImplementedTests
- @Test
- void shouldSetStatusBrokenOnUndefinedStepsAndBadAfter() {
- final AllureResults results = runFeature("features/hooks.feature", "-t", "@bp_su_af");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.BROKEN);
- }
-
-
- @AllureFeatures.NotImplementedTests
- @Test
- void shouldSetStatusPassedOnUndefinedSteps() {
- final AllureResults results = runFeature("features/hooks.feature", "-t", "@bp_su_ap");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.PASSED);
- }
-
- @AllureFeatures.NotImplementedTests
- @Test
- void shouldSetStatusSkippedOnUndefinedAndFailedSteps() {
- final AllureResults results = runFeature("features/hooks.feature", "-t", "@bp_suf_ap");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.SKIPPED);
- }
-
-
- @AllureFeatures.NotImplementedTests
- @Test
- void shouldSetStatusPassedOnPassedAndUndefinedSteps() {
- final AllureResults results = runFeature("features/hooks.feature", "-t", "@bp_spu_ap");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.PASSED);
- }
-
- @AllureFeatures.NotImplementedTests
- @Test
- void shouldSetStatusFailedOnFailedAndUndefinedSteps() {
- final AllureResults results = runFeature("features/hooks.feature", "-t", "@bp_sfu_ap");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.FAILED);
- }
-
-
- private String readResource(final String resourceName) {
- try (InputStream is = currentThread().getContextClassLoader().getResourceAsStream(resourceName)) {
- return IOUtils.toString(is, StandardCharsets.UTF_8);
- } catch (IOException e) {
- throw new RuntimeException("Feature file not found " + resourceName);
- }
- }
-}
diff --git a/allure-cucumber2-jvm/src/test/resources/features/undefined.feature b/allure-cucumber2-jvm/src/test/resources/features/undefined.feature
deleted file mode 100644
index caa0b507b..000000000
--- a/allure-cucumber2-jvm/src/test/resources/features/undefined.feature
+++ /dev/null
@@ -1,4 +0,0 @@
-Feature: Simple feature
-
- Scenario: Step is not implemented
- Given hello my friend
diff --git a/allure-cucumber3-jvm/src/main/java/io/qameta/allure/cucumber3jvm/AllureCucumber3Jvm.java b/allure-cucumber3-jvm/src/main/java/io/qameta/allure/cucumber3jvm/AllureCucumber3Jvm.java
deleted file mode 100644
index 531bf58dc..000000000
--- a/allure-cucumber3-jvm/src/main/java/io/qameta/allure/cucumber3jvm/AllureCucumber3Jvm.java
+++ /dev/null
@@ -1,410 +0,0 @@
-/*
- * Copyright 2019 Qameta Software OÜ
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.qameta.allure.cucumber3jvm;
-
-import cucumber.api.HookTestStep;
-import cucumber.api.HookType;
-import cucumber.api.PendingException;
-import cucumber.api.PickleStepTestStep;
-import cucumber.api.Result;
-import cucumber.api.TestCase;
-import cucumber.api.event.EmbedEvent;
-import cucumber.api.event.EventHandler;
-import cucumber.api.event.EventPublisher;
-import cucumber.api.event.TestCaseFinished;
-import cucumber.api.event.TestCaseStarted;
-import cucumber.api.event.TestSourceRead;
-import cucumber.api.event.TestStepFinished;
-import cucumber.api.event.TestStepStarted;
-import cucumber.api.event.WriteEvent;
-import cucumber.api.formatter.Formatter;
-import gherkin.ast.Examples;
-import gherkin.ast.Feature;
-import gherkin.ast.ScenarioDefinition;
-import gherkin.ast.ScenarioOutline;
-import gherkin.ast.TableRow;
-import gherkin.pickles.PickleCell;
-import gherkin.pickles.PickleRow;
-import gherkin.pickles.PickleTable;
-import gherkin.pickles.PickleTag;
-import io.qameta.allure.Allure;
-import io.qameta.allure.AllureLifecycle;
-import io.qameta.allure.model.FixtureResult;
-import io.qameta.allure.model.Parameter;
-import io.qameta.allure.model.Status;
-import io.qameta.allure.model.StatusDetails;
-import io.qameta.allure.model.StepResult;
-import io.qameta.allure.model.TestResult;
-import io.qameta.allure.model.TestResultContainer;
-
-import java.io.ByteArrayInputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.UUID;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-
-import static io.qameta.allure.util.ResultsUtils.createParameter;
-import static io.qameta.allure.util.ResultsUtils.getStatus;
-import static io.qameta.allure.util.ResultsUtils.getStatusDetails;
-import static io.qameta.allure.util.ResultsUtils.md5;
-
-/**
- * Allure plugin for Cucumber JVM 3.0.
- */
-@SuppressWarnings({
- "PMD.ExcessiveImports",
- "ClassFanOutComplexity", "ClassDataAbstractionCoupling"
-})
-public class AllureCucumber3Jvm implements Formatter {
-
- private final AllureLifecycle lifecycle;
-
- private final Map scenarioUuids = new HashMap<>();
-
- private final CucumberSourceUtils cucumberSourceUtils = new CucumberSourceUtils();
- private Feature currentFeature;
- private String currentFeatureFile;
- private TestCase currentTestCase;
- private String currentContainer;
- private boolean forbidTestCaseStatusChange;
-
- private final EventHandler featureStartedHandler = this::handleFeatureStartedHandler;
- private final EventHandler caseStartedHandler = this::handleTestCaseStarted;
- private final EventHandler caseFinishedHandler = this::handleTestCaseFinished;
- private final EventHandler stepStartedHandler = this::handleTestStepStarted;
- private final EventHandler stepFinishedHandler = this::handleTestStepFinished;
- private final EventHandler writeEventHandler = this::handleWriteEvent;
- private final EventHandler embedEventHandler = this::handleEmbedEvent;
-
- private static final String TXT_EXTENSION = ".txt";
- private static final String TEXT_PLAIN = "text/plain";
-
- @SuppressWarnings("unused")
- public AllureCucumber3Jvm() {
- this(Allure.getLifecycle());
- }
-
- public AllureCucumber3Jvm(final AllureLifecycle lifecycle) {
- this.lifecycle = lifecycle;
- }
-
- @Override
- public void setEventPublisher(final EventPublisher publisher) {
- publisher.registerHandlerFor(TestSourceRead.class, featureStartedHandler);
-
- publisher.registerHandlerFor(TestCaseStarted.class, caseStartedHandler);
- publisher.registerHandlerFor(TestCaseFinished.class, caseFinishedHandler);
-
- publisher.registerHandlerFor(TestStepStarted.class, stepStartedHandler);
- publisher.registerHandlerFor(TestStepFinished.class, stepFinishedHandler);
-
- publisher.registerHandlerFor(WriteEvent.class, writeEventHandler);
- publisher.registerHandlerFor(EmbedEvent.class, embedEventHandler);
- }
-
- /*
- Event Handlers
- */
-
- private void handleFeatureStartedHandler(final TestSourceRead event) {
- cucumberSourceUtils.addTestSourceReadEvent(event.uri, event);
- }
-
- private void handleTestCaseStarted(final TestCaseStarted event) {
- currentTestCase = event.testCase;
- currentFeatureFile = currentTestCase.getUri();
- currentFeature = cucumberSourceUtils.getFeature(currentFeatureFile);
- currentContainer = UUID.randomUUID().toString();
- forbidTestCaseStatusChange = false;
-
-
- final Deque tags = new LinkedList<>(currentTestCase.getTags());
-
- final LabelBuilder labelBuilder = new LabelBuilder(currentFeature, currentTestCase, tags);
-
- final String name = currentTestCase.getName();
- final String featureName = currentFeature.getName();
-
- final TestResult result = new TestResult()
- .setUuid(getTestCaseUuid(currentTestCase))
- .setHistoryId(getHistoryId(currentTestCase))
- .setFullName(featureName + ": " + name)
- .setName(name)
- .setLabels(labelBuilder.getScenarioLabels())
- .setLinks(labelBuilder.getScenarioLinks());
-
- final ScenarioDefinition scenarioDefinition =
- cucumberSourceUtils.getScenarioDefinition(currentFeatureFile, currentTestCase.getLine());
- if (scenarioDefinition instanceof ScenarioOutline) {
- result.setParameters(
- getExamplesAsParameters((ScenarioOutline) scenarioDefinition)
- );
- }
-
- if (currentFeature.getDescription() != null && !currentFeature.getDescription().isEmpty()) {
- result.setDescription(currentFeature.getDescription());
- }
-
- final TestResultContainer resultContainer = new TestResultContainer()
- .setName(String.format("%s: %s", scenarioDefinition.getKeyword(), scenarioDefinition.getName()))
- .setUuid(getTestContainerUuid())
- .setChildren(Collections.singletonList(getTestCaseUuid(currentTestCase)));
-
- lifecycle.scheduleTestCase(result);
- lifecycle.startTestContainer(getTestContainerUuid(), resultContainer);
- lifecycle.startTestCase(getTestCaseUuid(currentTestCase));
- }
-
- private void handleTestCaseFinished(final TestCaseFinished event) {
-
- final String uuid = getTestCaseUuid(event.testCase);
- final Optional details = getStatusDetails(event.result.getError());
- details.ifPresent(statusDetails -> lifecycle.updateTestCase(
- uuid,
- testResult -> testResult.setStatusDetails(statusDetails)
- ));
- lifecycle.stopTestCase(uuid);
- lifecycle.stopTestContainer(getTestContainerUuid());
- lifecycle.writeTestCase(uuid);
- lifecycle.writeTestContainer(getTestContainerUuid());
- }
-
- private void handleTestStepStarted(final TestStepStarted event) {
- if (event.testStep instanceof PickleStepTestStep) {
- final PickleStepTestStep pickleStep = (PickleStepTestStep) event.testStep;
- final String stepKeyword = Optional.ofNullable(
- cucumberSourceUtils.getKeywordFromSource(currentFeatureFile, pickleStep.getStepLine())
- ).orElse("UNDEFINED");
-
- final StepResult stepResult = new StepResult()
- .setName(String.format("%s %s", stepKeyword, pickleStep.getPickleStep().getText()))
- .setStart(System.currentTimeMillis());
-
- lifecycle.startStep(getTestCaseUuid(currentTestCase), getStepUuid(pickleStep), stepResult);
-
- pickleStep.getStepArgument().stream()
- .filter(PickleTable.class::isInstance)
- .findFirst()
- .ifPresent(table -> createDataTableAttachment((PickleTable) table));
- } else if (event.testStep instanceof HookTestStep) {
- initHook((HookTestStep) event.testStep);
- }
- }
-
- private void initHook(final HookTestStep hook) {
-
- final FixtureResult hookResult = new FixtureResult()
- .setName(hook.getCodeLocation())
- .setStart(System.currentTimeMillis());
-
- if (hook.getHookType() == HookType.Before) {
- lifecycle.startPrepareFixture(getTestContainerUuid(), getHookStepUuid(hook), hookResult);
- } else {
- lifecycle.startTearDownFixture(getTestContainerUuid(), getHookStepUuid(hook), hookResult);
- }
-
- }
-
- private void handleTestStepFinished(final TestStepFinished event) {
- if (event.testStep instanceof HookTestStep) {
- handleHookStep(event);
- } else {
- handlePickleStep(event);
- }
- }
-
- private void handleWriteEvent(final WriteEvent event) {
- lifecycle.addAttachment(
- "Text output",
- TEXT_PLAIN,
- TXT_EXTENSION,
- Objects.toString(event.text).getBytes(StandardCharsets.UTF_8)
- );
- }
-
- private void handleEmbedEvent(final EmbedEvent event) {
- lifecycle.addAttachment("Screenshot", null, null, new ByteArrayInputStream(event.data));
- }
-
- /*
- Utility Methods
- */
-
- private String getTestContainerUuid() {
- return currentContainer;
- }
-
- private String getTestCaseUuid(final TestCase testCase) {
- return scenarioUuids.computeIfAbsent(getHistoryId(testCase), it -> UUID.randomUUID().toString());
- }
-
- private String getStepUuid(final PickleStepTestStep step) {
- return currentFeature.getName() + getTestCaseUuid(currentTestCase)
- + step.getPickleStep().getText() + step.getStepLine();
- }
-
- private String getHookStepUuid(final HookTestStep step) {
- return currentFeature.getName() + getTestCaseUuid(currentTestCase)
- + step.getHookType().toString() + step.getCodeLocation();
- }
-
- private String getHistoryId(final TestCase testCase) {
- final String testCaseLocation = testCase.getUri() + ":" + testCase.getLine();
- return md5(testCaseLocation);
- }
-
- private Status translateTestCaseStatus(final Result testCaseResult) {
- switch (testCaseResult.getStatus()) {
- case FAILED:
- return getStatus(testCaseResult.getError())
- .orElse(Status.FAILED);
- case PASSED:
- return Status.PASSED;
- case SKIPPED:
- case PENDING:
- return Status.SKIPPED;
- case AMBIGUOUS:
- case UNDEFINED:
- default:
- return null;
- }
- }
-
- private List getExamplesAsParameters(final ScenarioOutline scenarioOutline) {
- final Optional examplesBlock =
- scenarioOutline.getExamples().stream()
- .filter(example -> example.getTableBody().stream()
- .anyMatch(row -> row.getLocation().getLine() == currentTestCase.getLine())
- ).findFirst();
-
- if (examplesBlock.isPresent()) {
- final TableRow row = examplesBlock.get().getTableBody().stream()
- .filter(example -> example.getLocation().getLine() == currentTestCase.getLine())
- .findFirst().get();
- return IntStream.range(0, examplesBlock.get().getTableHeader().getCells().size()).mapToObj(index -> {
- final String name = examplesBlock.get().getTableHeader().getCells().get(index).getValue();
- final String value = row.getCells().get(index).getValue();
- return createParameter(name, value);
- }).collect(Collectors.toList());
- } else {
- return Collections.emptyList();
- }
- }
-
- private void createDataTableAttachment(final PickleTable pickleTable) {
- final List rows = pickleTable.getRows();
-
- final StringBuilder dataTableCsv = new StringBuilder();
- if (!rows.isEmpty()) {
- rows.forEach(dataTableRow -> {
- dataTableCsv.append(
- dataTableRow.getCells().stream()
- .map(PickleCell::getValue)
- .collect(Collectors.joining("\t"))
- );
- dataTableCsv.append('\n');
- });
-
- final String attachmentSource = lifecycle
- .prepareAttachment("Data table", "text/tab-separated-values", "csv");
- lifecycle.writeAttachment(attachmentSource,
- new ByteArrayInputStream(dataTableCsv.toString().getBytes(StandardCharsets.UTF_8)));
- }
- }
-
- private void handleHookStep(final TestStepFinished event) {
- final HookTestStep hookStep = (HookTestStep) event.testStep;
- final String uuid = getHookStepUuid(hookStep);
- final FixtureResult fixtureResult = new FixtureResult().setStatus(translateTestCaseStatus(event.result));
-
- if (!Status.PASSED.equals(fixtureResult.getStatus())) {
- final TestResult testResult = new TestResult().setStatus(translateTestCaseStatus(event.result));
- final StatusDetails statusDetails = getStatusDetails(event.result.getError()).get();
-
- statusDetails.setMessage(hookStep.getHookType()
- .name() + " is failed: " + event.result.getError().getLocalizedMessage());
-
- if (hookStep.getHookType() == HookType.Before) {
- final TagParser tagParser = new TagParser(currentFeature, currentTestCase);
- statusDetails
- .setFlaky(tagParser.isFlaky())
- .setMuted(tagParser.isMuted())
- .setKnown(tagParser.isKnown());
- testResult.setStatus(Status.SKIPPED);
- updateTestCaseStatus(testResult.getStatus());
- forbidTestCaseStatusChange = true;
- } else {
- testResult.setStatus(Status.BROKEN);
- updateTestCaseStatus(testResult.getStatus());
- }
- fixtureResult.setStatusDetails(statusDetails);
- }
-
- lifecycle.updateFixture(uuid, result -> result.setStatus(fixtureResult.getStatus())
- .setStatusDetails(fixtureResult.getStatusDetails()));
- lifecycle.stopFixture(uuid);
- }
-
- private void handlePickleStep(final TestStepFinished event) {
-
- final Status stepStatus = translateTestCaseStatus(event.result);
- final StatusDetails statusDetails;
- if (event.result.getStatus() == Result.Type.UNDEFINED) {
- updateTestCaseStatus(Status.PASSED);
-
- statusDetails =
- getStatusDetails(new PendingException("TODO: implement me"))
- .orElse(new StatusDetails());
- lifecycle.updateTestCase(getTestCaseUuid(currentTestCase), scenarioResult ->
- scenarioResult
- .setStatusDetails(statusDetails));
- } else {
- statusDetails =
- getStatusDetails(event.result.getError())
- .orElse(new StatusDetails());
- updateTestCaseStatus(stepStatus);
- }
-
- if (!Status.PASSED.equals(stepStatus) && stepStatus != null) {
- forbidTestCaseStatusChange = true;
- }
-
- final TagParser tagParser = new TagParser(currentFeature, currentTestCase);
- statusDetails
- .setFlaky(tagParser.isFlaky())
- .setMuted(tagParser.isMuted())
- .setKnown(tagParser.isKnown());
-
- lifecycle.updateStep(getStepUuid((PickleStepTestStep) event.testStep),
- stepResult -> stepResult.setStatus(stepStatus).setStatusDetails(statusDetails));
- lifecycle.stopStep(getStepUuid((PickleStepTestStep) event.testStep));
- }
-
- private void updateTestCaseStatus(final Status status) {
- if (!forbidTestCaseStatusChange) {
- lifecycle.updateTestCase(getTestCaseUuid(currentTestCase),
- result -> result.setStatus(status));
- }
- }
-}
diff --git a/allure-cucumber3-jvm/src/main/java/io/qameta/allure/cucumber3jvm/CucumberSourceUtils.java b/allure-cucumber3-jvm/src/main/java/io/qameta/allure/cucumber3jvm/CucumberSourceUtils.java
deleted file mode 100644
index 2f2ed0f37..000000000
--- a/allure-cucumber3-jvm/src/main/java/io/qameta/allure/cucumber3jvm/CucumberSourceUtils.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright 2019 Qameta Software OÜ
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.qameta.allure.cucumber3jvm;
-
-import cucumber.api.event.TestSourceRead;
-
-import gherkin.Parser;
-import gherkin.AstBuilder;
-import gherkin.TokenMatcher;
-import gherkin.ParserException;
-import gherkin.GherkinDialect;
-import gherkin.GherkinDialectProvider;
-
-import gherkin.ast.GherkinDocument;
-import gherkin.ast.Feature;
-import gherkin.ast.ScenarioDefinition;
-import gherkin.ast.Node;
-import gherkin.ast.ScenarioOutline;
-import gherkin.ast.TableRow;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.stream.IntStream;
-
-/**
- * Parts of package-private cucumber.runtime.formatter.TestSourcesModel needed for Allure 2 adapter.
- */
-final class CucumberSourceUtils {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(CucumberSourceUtils.class);
-
- private final Map pathToReadEventMap = new HashMap<>();
- private final Map pathToAstMap = new HashMap<>();
- private final Map> pathToNodeMap = new HashMap<>();
-
- public void addTestSourceReadEvent(final String path, final TestSourceRead event) {
- pathToReadEventMap.put(path, event);
- }
-
- public Feature getFeature(final String path) {
- if (!pathToAstMap.containsKey(path)) {
- parseGherkinSource(path);
- }
- if (pathToAstMap.containsKey(path)) {
- return pathToAstMap.get(path).getFeature();
- }
- return null;
- }
-
- private void parseGherkinSource(final String path) {
- if (!pathToReadEventMap.containsKey(path)) {
- return;
- }
- final Parser parser = new Parser<>(new AstBuilder());
- final TokenMatcher matcher = new TokenMatcher();
- try {
- final GherkinDocument gherkinDocument = parser.parse(pathToReadEventMap.get(path).source, matcher);
- pathToAstMap.put(path, gherkinDocument);
- final Map nodeMap = new HashMap<>();
- final AstNode currentParent = new AstNode(gherkinDocument.getFeature(), null);
- for (ScenarioDefinition child : gherkinDocument.getFeature().getChildren()) {
- processScenarioDefinition(nodeMap, child, currentParent);
- }
- pathToNodeMap.put(path, nodeMap);
- } catch (ParserException e) {
- LOGGER.trace(e.getMessage(), e);
- }
- }
-
- private void processScenarioDefinition(
- final Map nodeMap, final ScenarioDefinition child, final AstNode currentParent
- ) {
- final AstNode childNode = new AstNode(child, currentParent);
- nodeMap.put(child.getLocation().getLine(), childNode);
-
- child.getSteps().forEach(
- step -> nodeMap.put(step.getLocation().getLine(), new AstNode(step, childNode))
- );
-
- if (child instanceof ScenarioOutline) {
- processScenarioOutlineExamples(nodeMap, (ScenarioOutline) child, childNode);
- }
- }
-
- private void processScenarioOutlineExamples(
- final Map nodeMap, final ScenarioOutline scenarioOutline, final AstNode childNode
- ) {
- scenarioOutline.getExamples().forEach(examples -> {
- final AstNode examplesNode = new AstNode(examples, childNode);
- final TableRow headerRow = examples.getTableHeader();
- final AstNode headerNode = new AstNode(headerRow, examplesNode);
- nodeMap.put(headerRow.getLocation().getLine(), headerNode);
- IntStream.range(0, examples.getTableBody().size()).forEach(i -> {
- final TableRow examplesRow = examples.getTableBody().get(i);
- final Node rowNode = new CucumberSourceUtils.ExamplesRowWrapperNode(examplesRow, i);
- final AstNode expandedScenarioNode = new AstNode(rowNode, examplesNode);
- nodeMap.put(examplesRow.getLocation().getLine(), expandedScenarioNode);
- });
- });
- }
-
- private AstNode getAstNode(final String path, final int line) {
- if (!pathToNodeMap.containsKey(path)) {
- parseGherkinSource(path);
- }
- if (pathToNodeMap.containsKey(path)) {
- return pathToNodeMap.get(path).get(line);
- }
- return null;
- }
-
- public ScenarioDefinition getScenarioDefinition(final String path, final int line) {
- return getScenarioDefinition(getAstNode(path, line));
- }
-
- private ScenarioDefinition getScenarioDefinition(final AstNode astNode) {
- return astNode.getNode() instanceof ScenarioDefinition
- ? (ScenarioDefinition) astNode.getNode()
- : (ScenarioDefinition) astNode.getParent().getParent().getNode();
- }
-
- public String getKeywordFromSource(final String uri, final int stepLine) {
- final Feature feature = getFeature(uri);
- if (feature != null) {
- final TestSourceRead event = getTestSourceReadEvent(uri);
- final String trimmedSourceLine = event.source.split("\n")[stepLine - 1].trim();
- final GherkinDialect dialect = new GherkinDialectProvider(feature.getLanguage()).getDefaultDialect();
- for (String keyword : dialect.getStepKeywords()) {
- if (trimmedSourceLine.startsWith(keyword)) {
- return keyword;
- }
- }
- }
- return "";
- }
-
- private TestSourceRead getTestSourceReadEvent(final String uri) {
- if (pathToReadEventMap.containsKey(uri)) {
- return pathToReadEventMap.get(uri);
- }
- return null;
- }
-
- /**
- * Representation of Examples row.
- */
- private static class ExamplesRowWrapperNode extends Node {
- private final int bodyRowIndex;
-
- ExamplesRowWrapperNode(final Node examplesRow, final int bodyRowIndex) {
- super(examplesRow.getLocation());
- this.bodyRowIndex = bodyRowIndex;
- }
-
- public int getBodyRowIndex() {
- return bodyRowIndex;
- }
- }
-
- /**
- * Representation of leaf node.
- */
- private static class AstNode {
- private final Node node;
- private final AstNode parent;
-
- AstNode(final Node node, final AstNode parent) {
- this.node = node;
- this.parent = parent;
- }
-
- public Node getNode() {
- return node;
- }
-
- public AstNode getParent() {
- return parent;
- }
- }
-}
diff --git a/allure-cucumber3-jvm/src/test/java/io/qameta/allure/cucumber3jvm/AllureCucumber3JvmTest.java b/allure-cucumber3-jvm/src/test/java/io/qameta/allure/cucumber3jvm/AllureCucumber3JvmTest.java
deleted file mode 100644
index d6aaf4e46..000000000
--- a/allure-cucumber3-jvm/src/test/java/io/qameta/allure/cucumber3jvm/AllureCucumber3JvmTest.java
+++ /dev/null
@@ -1,578 +0,0 @@
-/*
- * Copyright 2019 Qameta Software OÜ
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.qameta.allure.cucumber3jvm;
-
-import cucumber.runtime.ClassFinder;
-import cucumber.runtime.Runtime;
-import cucumber.runtime.RuntimeOptions;
-import cucumber.runtime.io.MultiLoader;
-import cucumber.runtime.io.ResourceLoader;
-import cucumber.runtime.io.ResourceLoaderClassFinder;
-import cucumber.runtime.model.CucumberFeature;
-import gherkin.AstBuilder;
-import gherkin.Parser;
-import gherkin.TokenMatcher;
-import gherkin.ast.GherkinDocument;
-import io.github.glytching.junit.extension.system.SystemProperty;
-import io.github.glytching.junit.extension.system.SystemPropertyExtension;
-import io.qameta.allure.AllureLifecycle;
-import io.qameta.allure.Issue;
-import io.qameta.allure.model.Attachment;
-import io.qameta.allure.model.Label;
-import io.qameta.allure.model.Link;
-import io.qameta.allure.model.Parameter;
-import io.qameta.allure.model.Stage;
-import io.qameta.allure.model.Status;
-import io.qameta.allure.model.StatusDetails;
-import io.qameta.allure.model.StepResult;
-import io.qameta.allure.model.TestResult;
-import io.qameta.allure.test.AllureFeatures;
-import io.qameta.allure.test.AllureResults;
-import io.qameta.allure.test.AllureResultsWriterStub;
-import org.apache.commons.io.IOUtils;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-import static java.lang.Thread.currentThread;
-import static java.util.Objects.nonNull;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.tuple;
-
-/**
- * @author charlie (Dmitry Baev).
- */
-@SuppressWarnings("unchecked")
-class AllureCucumber3JvmTest {
-
- @AllureFeatures.Base
- @Test
- void shouldSetName() {
- final AllureResults results = runFeature("features/simple.feature");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getName)
- .containsExactlyInAnyOrder("Add a to b");
-
- }
-
- @AllureFeatures.PassedTests
- @Test
- void shouldSetStatus() {
- final AllureResults results = runFeature("features/simple.feature");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.PASSED);
- }
-
- @AllureFeatures.FailedTests
- @Test
- void shouldSetFailedStatus() {
- final AllureResults results = runFeature("features/failed.feature");
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.FAILED);
- }
-
- @AllureFeatures.FailedTests
- @Test
- void shouldSetStatusDetails() {
- final AllureResults results = runFeature("features/failed.feature");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatusDetails)
- .extracting(StatusDetails::getMessage)
- .containsExactlyInAnyOrder("expected: <15> but was: <123>");
- }
-
- @AllureFeatures.BrokenTests
- @Test
- void shouldSetBrokenStatus() {
- final AllureResults results = runFeature("features/broken.feature");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.BROKEN);
- }
-
- @AllureFeatures.Stages
- @Test
- void shouldSetStage() {
- final AllureResults results = runFeature("features/simple.feature");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStage)
- .containsExactlyInAnyOrder(Stage.FINISHED);
- }
-
- @AllureFeatures.Timings
- @Test
- void shouldSetStart() {
- final long before = Instant.now().toEpochMilli();
- final AllureResults results = runFeature("features/simple.feature");
- final long after = Instant.now().toEpochMilli();
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStart)
- .allMatch(v -> v >= before && v <= after);
- }
-
- @AllureFeatures.Timings
- @Test
- void shouldSetStop() {
- final long before = Instant.now().toEpochMilli();
- final AllureResults results = runFeature("features/simple.feature");
- final long after = Instant.now().toEpochMilli();
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStop)
- .allMatch(v -> v >= before && v <= after);
- }
-
- @AllureFeatures.FullName
- @Test
- void shouldSetFullName() {
- final AllureResults results = runFeature("features/simple.feature");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getFullName)
- .containsExactlyInAnyOrder("Simple feature: Add a to b");
- }
-
- @AllureFeatures.Descriptions
- @Test
- void shouldSetDescription() {
- final AllureResults results = runFeature("features/description.feature");
-
- final String expected = "This is description for current feature.\n"
- + "It should appear on each scenario in report";
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getDescription)
- .containsExactlyInAnyOrder(
- expected,
- expected
- );
- }
-
- @AllureFeatures.Attachments
- @Test
- void shouldAddDataTableAttachment() {
- final AllureResults results = runFeature("features/datatable.feature");
-
- final List attachments = results.getTestResults().stream()
- .map(TestResult::getSteps)
- .flatMap(Collection::stream)
- .map(StepResult::getAttachments)
- .flatMap(Collection::stream)
- .collect(Collectors.toList());
-
- assertThat(attachments)
- .extracting(Attachment::getName, Attachment::getType)
- .containsExactlyInAnyOrder(
- tuple("Data table", "text/tab-separated-values")
- );
-
- final Attachment dataTableAttachment = attachments.iterator().next();
- final Map attachmentFiles = results.getAttachments();
- assertThat(attachmentFiles)
- .containsKeys(dataTableAttachment.getSource());
-
- final byte[] bytes = attachmentFiles.get(dataTableAttachment.getSource());
- final String attachmentContent = new String(bytes, StandardCharsets.UTF_8);
-
- assertThat(attachmentContent)
- .isEqualTo("name\tlogin\temail\n" +
- "Viktor\tclicman\tclicman@ya.ru\n" +
- "Viktor2\tclicman2\tclicman2@ya.ru\n"
- );
-
- }
-
- @AllureFeatures.Attachments
- @Test
- void shouldAddAttachments() {
- final AllureResults results = runFeature("features/attachments.feature");
-
- final List attachments = results.getTestResults().stream()
- .map(TestResult::getSteps)
- .flatMap(Collection::stream)
- .map(StepResult::getAttachments)
- .flatMap(Collection::stream)
- .collect(Collectors.toList());
-
- assertThat(attachments)
- .extracting(Attachment::getName, Attachment::getType)
- .containsExactlyInAnyOrder(
- tuple("Text output", "text/plain"),
- tuple("Screenshot", null)
- );
-
- final List attachmentContents = results.getAttachments().values().stream()
- .map(bytes -> new String(bytes, StandardCharsets.UTF_8))
- .collect(Collectors.toList());
-
- assertThat(attachmentContents)
- .containsExactlyInAnyOrder("text attachment", "image attachment");
- }
-
- @AllureFeatures.Steps
- @Test
- void shouldAddBackgroundSteps() {
- final AllureResults results = runFeature("features/background.feature");
-
- assertThat(results.getTestResults())
- .hasSize(1)
- .flatExtracting(TestResult::getSteps)
- .extracting(StepResult::getName)
- .containsExactly(
- "Given cat is sad",
- "And cat is murmur",
- "When Pet the cat",
- "Then Cat is happy"
- );
- }
-
- @AllureFeatures.Parameters
- @Test
- void shouldAddParametersFromExamples() {
- final AllureResults results = runFeature("features/examples.feature");
-
- final List testResults = results.getTestResults();
-
- assertThat(testResults)
- .hasSize(2);
-
- assertThat(testResults.get(0).getParameters())
- .hasSize(3);
-
- assertThat(testResults.get(1).getParameters())
- .hasSize(3);
-
- assertThat(testResults)
- .flatExtracting(TestResult::getParameters)
- .extracting(Parameter::getName, Parameter::getValue)
- .containsExactlyInAnyOrder(
- tuple("a", "1"), tuple("b", "3"), tuple("result", "4"),
- tuple("a", "2"), tuple("b", "4"), tuple("result", "6")
- );
-
- }
-
- @AllureFeatures.Parameters
- @Test
- void shouldHandleMultipleExamplesPerOutline() throws IOException {
- final AllureResults results = runFeature("features/multi-examples.feature");
-
- final List testResults = results.getTestResults();
-
- assertThat(testResults)
- .hasSize(2);
-
- assertThat(testResults)
- .flatExtracting(TestResult::getParameters)
- .extracting(Parameter::getName, Parameter::getValue)
- .containsExactlyInAnyOrder(
- tuple("a", "1"), tuple("b", "3"), tuple("result", "4"),
- tuple("a", "2"), tuple("b", "4"), tuple("result", "6")
- );
- }
-
- @AllureFeatures.Parameters
- @Test
- void shouldSupportTaggedExamplesBlocks() throws IOException {
- final AllureResults results = runFeature("features/multi-examples.feature", "--tags", "@ExamplesTag2");
-
- final List testResults = results.getTestResults();
-
- assertThat(testResults)
- .hasSize(1);
-
- assertThat(testResults)
- .flatExtracting(TestResult::getLabels)
- .extracting(Label::getName, Label::getValue)
- .contains(
- tuple("tag", "ExamplesTag2")
- );
-
- assertThat(testResults)
- .flatExtracting(TestResult::getParameters)
- .extracting(Parameter::getName, Parameter::getValue)
- .containsExactlyInAnyOrder(
- tuple("a", "2"), tuple("b", "4"), tuple("result", "6")
- );
- }
-
- @AllureFeatures.MarkerAnnotations
- @Test
- void shouldAddTags() {
- final AllureResults results = runFeature("features/tags.feature");
-
- assertThat(results.getTestResults())
- .flatExtracting(TestResult::getLabels)
- .extracting(Label::getName, Label::getValue)
- .contains(
- tuple("tag", "FeatureTag"),
- tuple("tag", "good")
- );
- }
-
- @AllureFeatures.Links
- @ExtendWith(SystemPropertyExtension.class)
- @SystemProperty(name = "allure.link.issue.pattern", value = "https://example.org/issue/{}")
- @SystemProperty(name = "allure.link.tms.pattern", value = "https://example.org/tms/{}")
- @Test
- void shouldAddLinks() {
- final AllureResults results = runFeature("features/tags.feature");
-
- assertThat(results.getTestResults())
- .flatExtracting(TestResult::getLinks)
- .extracting(Link::getName, Link::getType, Link::getUrl)
- .contains(
- tuple("OAT-4444", "tms", "https://example.org/tms/OAT-4444"),
- tuple("BUG-22400", "issue", "https://example.org/issue/BUG-22400")
- );
- }
-
- @AllureFeatures.MarkerAnnotations
- @Test
- void shouldAddBddLabels() {
- final AllureResults results = runFeature("features/tags.feature");
-
- assertThat(results.getTestResults())
- .flatExtracting(TestResult::getLabels)
- .extracting(Label::getName, Label::getValue)
- .contains(
- tuple("feature", "Test Simple Scenarios"),
- tuple("story", "Add a to b")
- );
- }
-
- @AllureFeatures.Timeline
- @Test
- void shouldThreadHostLabels() {
- final AllureResults results = runFeature("features/tags.feature");
-
- assertThat(results.getTestResults())
- .flatExtracting(TestResult::getLabels)
- .extracting(Label::getName)
- .contains("host", "thread");
- }
-
- @AllureFeatures.MarkerAnnotations
- @Test
- void shouldCommonLabels() {
- final AllureResults results = runFeature("features/tags.feature");
-
- assertThat(results.getTestResults())
- .flatExtracting(TestResult::getLabels)
- .extracting(Label::getName, Label::getValue)
- .contains(
- tuple("package", "Test Simple Scenarios"),
- tuple("suite", "Test Simple Scenarios"),
- tuple("testClass", "Add a to b")
- );
- }
-
- @AllureFeatures.NotImplementedTests
- @Test
- void shouldProcessNotImplementedScenario() {
- final AllureResults results = runFeature("features/undefined.feature");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.PASSED);
- }
-
- @AllureFeatures.Base
- @Test
- void shouldSupportDryRun() {
- final AllureResults results = runFeature("features/simple.feature", "--dry-run");
-
- final List testResults = results.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getName, TestResult::getStatus)
- .containsExactlyInAnyOrder(
- tuple("Add a to b", Status.SKIPPED)
- );
- }
-
- @AllureFeatures.Base
- @Issue("173")
- @Issue("164")
- @Test
- void shouldUseUuid() {
- final AllureResults results = runFeature("features/simple.feature");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getUuid)
- .allMatch(uuid -> nonNull(uuid) && uuid.matches("[\\-a-z0-9]+"), "UUID");
- }
-
- private AllureResults runFeature(final String featureResource,
- final String... moreOptions) {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- final AllureLifecycle lifecycle = new AllureLifecycle(writer);
- final AllureCucumber3Jvm cucumber3Jvm = new AllureCucumber3Jvm(lifecycle);
- final ClassLoader classLoader = currentThread().getContextClassLoader();
- final ResourceLoader resourceLoader = new MultiLoader(classLoader);
- final ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader);
- final List opts = new ArrayList<>(Arrays.asList(
- "--glue", "io.qameta.allure.cucumber3jvm.samples",
- "--plugin", "null"
- ));
- opts.addAll(Arrays.asList(moreOptions));
- final RuntimeOptions options = new RuntimeOptions(opts);
- final Runtime runtime = new Runtime(resourceLoader, classFinder, classLoader, options);
-
- options.addPlugin(cucumber3Jvm);
- options.noSummaryPrinter();
-
- final String gherkin = readResource(featureResource);
- Parser parser = new Parser<>(new AstBuilder());
- TokenMatcher matcher = new TokenMatcher();
- GherkinDocument gherkinDocument = parser.parse(gherkin, matcher);
- CucumberFeature feature = new CucumberFeature(gherkinDocument, featureResource, gherkin);
-
- feature.sendTestSourceRead(runtime.getEventBus());
- runtime.runFeature(feature);
- return writer;
- }
-
- @AllureFeatures.Fixtures
- @Test
- void shouldSetStatusFailedOnBadAfter() {
- final AllureResults results = runFeature("features/hooks.feature", "-t", "@bp_sf_af");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.FAILED);
- }
-
-
- @AllureFeatures.Fixtures
- @Test
- void shouldSetStatusSkippedOnBadBefore() {
- final AllureResults results = runFeature("features/hooks.feature", "-t", "@bf_ap");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.SKIPPED);
- }
-
- @AllureFeatures.Fixtures
- @Test
- void shouldSetStatusSkippedOnBadBeforeAndBadAfter() {
- final AllureResults results = runFeature("features/hooks.feature", "-t", "@bf_af");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.SKIPPED);
- }
-
- @AllureFeatures.Fixtures
- @Test
- void shouldSetStatusBrokenOnBadAfter() {
- final AllureResults results = runFeature("features/hooks.feature", "-t", "@bp_af");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.BROKEN);
- }
-
- @AllureFeatures.Fixtures
- @Test
- void shouldSetStatusFailedOnBadSteps() {
- final AllureResults results = runFeature("features/hooks.feature", "-t", "@bp_sf_ap");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.FAILED);
- }
-
- @AllureFeatures.NotImplementedTests
- @Test
- void shouldSetStatusBrokenOnUndefinedStepsAndBadAfter() {
- final AllureResults results = runFeature("features/hooks.feature", "-t", "@bp_su_af");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.BROKEN);
- }
-
-
- @AllureFeatures.NotImplementedTests
- @Test
- void shouldSetStatusPassedOnUndefinedSteps() {
- final AllureResults results = runFeature("features/hooks.feature", "-t", "@bp_su_ap");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.PASSED);
- }
-
- @AllureFeatures.NotImplementedTests
- @Test
- void shouldSetStatusSkippedOnUndefinedAndFailedSteps() {
- final AllureResults results = runFeature("features/hooks.feature", "-t", "@bp_suf_ap");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.SKIPPED);
- }
-
-
- @AllureFeatures.NotImplementedTests
- @Test
- void shouldSetStatusPassedOnPassedAndUndefinedSteps() {
- final AllureResults results = runFeature("features/hooks.feature", "-t", "@bp_spu_ap");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.PASSED);
- }
-
- @AllureFeatures.NotImplementedTests
- @Test
- void shouldSetStatusFailedOnFailedAndUndefinedSteps() {
- final AllureResults results = runFeature("features/hooks.feature", "-t", "@bp_sfu_ap");
-
- assertThat(results.getTestResults())
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.FAILED);
- }
-
- private String readResource(final String resourceName) {
- try (InputStream is = currentThread().getContextClassLoader().getResourceAsStream(resourceName)) {
- return IOUtils.toString(is, StandardCharsets.UTF_8);
- } catch (IOException e) {
- throw new RuntimeException("Feature file not found " + resourceName);
- }
- }
-}
diff --git a/allure-cucumber3-jvm/src/test/resources/features/attachments.feature b/allure-cucumber3-jvm/src/test/resources/features/attachments.feature
deleted file mode 100644
index 1585fbe81..000000000
--- a/allure-cucumber3-jvm/src/test/resources/features/attachments.feature
+++ /dev/null
@@ -1,8 +0,0 @@
-@attachments
-Feature: Feature with attachments
-
- Scenario: Simple scenario with a text attachment
- Given step with scenario write
-
- Scenario: Simple scenario with an image attachment
- Given step with scenario embed
\ No newline at end of file
diff --git a/allure-cucumber3-jvm/src/test/resources/features/background.feature b/allure-cucumber3-jvm/src/test/resources/features/background.feature
deleted file mode 100644
index 307f10267..000000000
--- a/allure-cucumber3-jvm/src/test/resources/features/background.feature
+++ /dev/null
@@ -1,9 +0,0 @@
-Feature: Test Scenarios with backgrounds
-
- Background:
- Given cat is sad
- And cat is murmur
-
- Scenario: Scenario with background
- When Pet the cat
- Then Cat is happy
diff --git a/allure-cucumber3-jvm/src/test/resources/features/broken.feature b/allure-cucumber3-jvm/src/test/resources/features/broken.feature
deleted file mode 100644
index 42bd7ba4f..000000000
--- a/allure-cucumber3-jvm/src/test/resources/features/broken.feature
+++ /dev/null
@@ -1,4 +0,0 @@
-Feature: Simple feature
-
- Scenario: Exception during step
- Given everything is broken
diff --git a/allure-cucumber3-jvm/src/test/resources/features/datatable.feature b/allure-cucumber3-jvm/src/test/resources/features/datatable.feature
deleted file mode 100644
index ea5e94d3b..000000000
--- a/allure-cucumber3-jvm/src/test/resources/features/datatable.feature
+++ /dev/null
@@ -1,8 +0,0 @@
-Feature: Test Scenarios with Data Tables
-
- Scenario: Simple data table
- Given users are:
- | name | login | email |
- | Viktor | clicman | clicman@ya.ru |
- | Viktor2 | clicman2 | clicman2@ya.ru |
-
diff --git a/allure-cucumber3-jvm/src/test/resources/features/examples.feature b/allure-cucumber3-jvm/src/test/resources/features/examples.feature
deleted file mode 100644
index 23d6c5f21..000000000
--- a/allure-cucumber3-jvm/src/test/resources/features/examples.feature
+++ /dev/null
@@ -1,12 +0,0 @@
-Feature: Test Scenarios with Examples
-
- Scenario Outline: Scenario with Positive Examples
- Given a is
- And b is
- When I add a to b
- Then result is
-
- Examples:
- | a | b | result |
- | 1 | 3 | 4 |
- | 2 | 4 | 6 |
diff --git a/allure-cucumber3-jvm/src/test/resources/features/undefined.feature b/allure-cucumber3-jvm/src/test/resources/features/undefined.feature
deleted file mode 100644
index caa0b507b..000000000
--- a/allure-cucumber3-jvm/src/test/resources/features/undefined.feature
+++ /dev/null
@@ -1,4 +0,0 @@
-Feature: Simple feature
-
- Scenario: Step is not implemented
- Given hello my friend
diff --git a/allure-cucumber4-jvm/build.gradle.kts b/allure-cucumber4-jvm/build.gradle.kts
index cb87fff60..26b244ee0 100644
--- a/allure-cucumber4-jvm/build.gradle.kts
+++ b/allure-cucumber4-jvm/build.gradle.kts
@@ -1,21 +1,20 @@
description = "Allure CucumberJVM 4.0"
-val agent: Configuration by configurations.creating
-
-val cucumberVersion = "4.7.2"
+val cucumberVersion = "4.8.0"
dependencies {
- agent("org.aspectj:aspectjweaver")
api(project(":allure-java-commons"))
- implementation("io.cucumber:cucumber-core:$cucumberVersion")
- implementation("io.cucumber:cucumber-java:$cucumberVersion")
+ compileOnly("io.cucumber:cucumber-core:$cucumberVersion")
+ compileOnly("io.cucumber:cucumber-java:$cucumberVersion")
testImplementation("commons-io:commons-io")
- testImplementation("io.cucumber:cucumber-testng:$cucumberVersion")
+ testImplementation("io.cucumber:cucumber-core:$cucumberVersion")
+ testImplementation("io.cucumber:cucumber-java:$cucumberVersion")
testImplementation("io.github.glytching:junit-extensions")
testImplementation("org.assertj:assertj-core")
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("org.slf4j:slf4j-simple")
testImplementation(project(":allure-java-commons-test"))
+ testImplementation(project(":allure-junit-platform"))
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
}
@@ -29,7 +28,4 @@ tasks.jar {
tasks.test {
useJUnitPlatform()
- doFirst {
- jvmArgs("-javaagent:${agent.singleFile}")
- }
}
diff --git a/allure-cucumber4-jvm/src/main/java/cucumber/runtime/formatter/TestSourcesModelProxy.java b/allure-cucumber4-jvm/src/main/java/cucumber/runtime/formatter/TestSourcesModelProxy.java
index e6af04883..f55994b76 100644
--- a/allure-cucumber4-jvm/src/main/java/cucumber/runtime/formatter/TestSourcesModelProxy.java
+++ b/allure-cucumber4-jvm/src/main/java/cucumber/runtime/formatter/TestSourcesModelProxy.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/AllureCucumber4Jvm.java b/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/AllureCucumber4Jvm.java
index 57d0a95df..b5a5d429d 100644
--- a/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/AllureCucumber4Jvm.java
+++ b/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/AllureCucumber4Jvm.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,10 +17,10 @@
import cucumber.api.HookTestStep;
import cucumber.api.HookType;
-import cucumber.api.PendingException;
import cucumber.api.PickleStepTestStep;
import cucumber.api.Result;
import cucumber.api.TestCase;
+import cucumber.api.TestStep;
import cucumber.api.event.ConcurrentEventListener;
import cucumber.api.event.EmbedEvent;
import cucumber.api.event.EventHandler;
@@ -39,6 +39,7 @@
import gherkin.ast.TableRow;
import gherkin.pickles.PickleCell;
import gherkin.pickles.PickleRow;
+import gherkin.pickles.PickleStep;
import gherkin.pickles.PickleTable;
import gherkin.pickles.PickleTag;
import io.qameta.allure.Allure;
@@ -52,11 +53,14 @@
import io.qameta.allure.model.TestResultContainer;
import java.io.ByteArrayInputStream;
+import java.net.URI;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Paths;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
@@ -65,6 +69,7 @@
import java.util.stream.IntStream;
import java.util.stream.Stream;
+import static cucumber.api.HookType.Before;
import static io.qameta.allure.util.ResultsUtils.createParameter;
import static io.qameta.allure.util.ResultsUtils.getStatus;
import static io.qameta.allure.util.ResultsUtils.getStatusDetails;
@@ -74,22 +79,18 @@
* Allure plugin for Cucumber JVM 4.0.
*/
@SuppressWarnings({
- "PMD.ExcessiveImports",
- "ClassFanOutComplexity", "ClassDataAbstractionCoupling"
+ "ClassDataAbstractionCoupling",
+ "ClassFanOutComplexity",
+ "MultipleStringLiterals",
})
public class AllureCucumber4Jvm implements ConcurrentEventListener {
+ private static final String COLON = ":";
+
private final AllureLifecycle lifecycle;
- private final ConcurrentHashMap scenarioUuids = new ConcurrentHashMap<>();
private final TestSourcesModelProxy testSources = new TestSourcesModelProxy();
- private final ThreadLocal currentFeature = new InheritableThreadLocal<>();
- private final ThreadLocal currentFeatureFile = new InheritableThreadLocal<>();
- private final ThreadLocal currentTestCase = new InheritableThreadLocal<>();
- private final ThreadLocal currentContainer = new InheritableThreadLocal<>();
- private final ThreadLocal forbidTestCaseStatusChange = new InheritableThreadLocal<>();
-
private final EventHandler featureStartedHandler = this::handleFeatureStartedHandler;
private final EventHandler caseStartedHandler = this::handleTestCaseStarted;
private final EventHandler caseFinishedHandler = this::handleTestCaseFinished;
@@ -98,8 +99,14 @@ public class AllureCucumber4Jvm implements ConcurrentEventListener {
private final EventHandler writeEventHandler = this::handleWriteEvent;
private final EventHandler embedEventHandler = this::handleEmbedEvent;
+ private final Map hookStepContainerUuid = new ConcurrentHashMap<>();
+ private final Map testCaseUuids = new ConcurrentHashMap<>();
+ private final Map stepUuids = new ConcurrentHashMap<>();
+ private final Map fixtureUuids = new ConcurrentHashMap<>();
+
private static final String TXT_EXTENSION = ".txt";
private static final String TEXT_PLAIN = "text/plain";
+ private static final String CUCUMBER_WORKING_DIR = Paths.get("").toUri().getSchemeSpecificPart();
@SuppressWarnings("unused")
public AllureCucumber4Jvm() {
@@ -124,42 +131,49 @@ public void setEventPublisher(final EventPublisher publisher) {
publisher.registerHandlerFor(EmbedEvent.class, embedEventHandler);
}
- /*
- Event Handlers
- */
-
private void handleFeatureStartedHandler(final TestSourceRead event) {
testSources.addTestSourceReadEvent(event.uri, event);
}
private void handleTestCaseStarted(final TestCaseStarted event) {
- currentFeatureFile.set(event.testCase.getUri());
- currentFeature.set(testSources.getFeature(currentFeatureFile.get()));
- currentTestCase.set(event.testCase);
- currentContainer.set(UUID.randomUUID().toString());
- forbidTestCaseStatusChange.set(false);
+ final TestCase testCase = event.getTestCase();
+ final Feature feature = testSources.getFeature(testCase.getUri());
+
+ final Deque tags = new LinkedList<>(testCase.getTags());
+ final LabelBuilder labelBuilder = new LabelBuilder(feature, testCase, tags);
+
+ final String name = testCase.getName();
- final Deque tags = new LinkedList<>(currentTestCase.get().getTags());
- final Feature feature = currentFeature.get();
- final LabelBuilder labelBuilder = new LabelBuilder(feature, currentTestCase.get(), tags);
+ // the same way full name is generated for
+ // org.junit.platform.engine.support.descriptor.ClasspathResourceSource
+ // to support io.qameta.allure.junitplatform.AllurePostDiscoveryFilter
+ final String fullName = String.format("%s:%d",
+ getTestCaseUri(testCase),
+ testCase.getLine()
+ );
- final String name = currentTestCase.get().getName();
- final String featureName = feature.getName();
+ final String testCaseUuid = testCaseUuids
+ .computeIfAbsent(testCase, tc -> UUID.randomUUID().toString());
final TestResult result = new TestResult()
- .setUuid(getTestCaseUuid(currentTestCase.get()))
- .setHistoryId(getHistoryId(currentTestCase.get()))
- .setFullName(featureName + ": " + name)
+ .setUuid(testCaseUuid)
+ .setTestCaseId(getTestCaseId(testCase))
+ .setHistoryId(getHistoryId(testCase))
+ .setFullName(fullName)
.setName(name)
.setLabels(labelBuilder.getScenarioLabels())
.setLinks(labelBuilder.getScenarioLinks());
final ScenarioDefinition scenarioDefinition =
- testSources.getScenarioDefinition(currentFeatureFile.get(), currentTestCase.get().getLine());
+ testSources.getScenarioDefinition(
+ testCase.getUri(),
+ testCase.getLine()
+ );
+
if (scenarioDefinition instanceof ScenarioOutline) {
result.setParameters(
- getExamplesAsParameters((ScenarioOutline) scenarioDefinition, currentTestCase.get())
+ getExamplesAsParameters((ScenarioOutline) scenarioDefinition, testCase)
);
}
@@ -172,74 +186,157 @@ private void handleTestCaseStarted(final TestCaseStarted event) {
result.setDescription(description);
}
- final TestResultContainer resultContainer = new TestResultContainer()
- .setName(String.format("%s: %s", scenarioDefinition.getKeyword(), scenarioDefinition.getName()))
- .setUuid(getTestContainerUuid())
- .setChildren(Collections.singletonList(getTestCaseUuid(currentTestCase.get())));
-
lifecycle.scheduleTestCase(result);
- lifecycle.startTestContainer(getTestContainerUuid(), resultContainer);
- lifecycle.startTestCase(getTestCaseUuid(currentTestCase.get()));
+ lifecycle.startTestCase(testCaseUuid);
}
private void handleTestCaseFinished(final TestCaseFinished event) {
+ final TestCase testCase = event.getTestCase();
+ final String uuid = testCaseUuids.get(testCase);
+ if (Objects.isNull(uuid)) {
+ return;
+ }
+
+ final Feature feature = testSources.getFeature(testCase.getUri());
+ final Result result = event.result;
+ final Status status = translateTestCaseStatus(result);
+ final StatusDetails statusDetails = getStatusDetails(result.getError())
+ .orElseGet(StatusDetails::new);
+
+ final TagParser tagParser = new TagParser(feature, testCase);
+ statusDetails
+ .setFlaky(tagParser.isFlaky())
+ .setMuted(tagParser.isMuted())
+ .setKnown(tagParser.isKnown());
+
+ lifecycle.updateTestCase(uuid, testResult -> testResult
+ .setStatus(status)
+ .setStatusDetails(statusDetails)
+ );
- final String uuid = getTestCaseUuid(event.testCase);
- final Optional details = getStatusDetails(event.result.getError());
- details.ifPresent(statusDetails -> lifecycle.updateTestCase(
- uuid,
- testResult -> testResult.setStatusDetails(statusDetails)
- ));
lifecycle.stopTestCase(uuid);
- lifecycle.stopTestContainer(getTestContainerUuid());
lifecycle.writeTestCase(uuid);
- lifecycle.writeTestContainer(getTestContainerUuid());
}
private void handleTestStepStarted(final TestStepStarted event) {
- if (event.testStep instanceof PickleStepTestStep) {
- final PickleStepTestStep pickleStep = (PickleStepTestStep) event.testStep;
- final String stepKeyword = Optional.ofNullable(
- testSources.getKeywordFromSource(currentFeatureFile.get(), pickleStep.getStepLine())
- ).orElse("UNDEFINED");
-
- final StepResult stepResult = new StepResult()
- .setName(String.format("%s %s", stepKeyword, pickleStep.getPickleStep().getText()))
- .setStart(System.currentTimeMillis());
-
- lifecycle.startStep(getTestCaseUuid(currentTestCase.get()), getStepUuid(pickleStep), stepResult);
-
- pickleStep.getStepArgument().stream()
- .filter(PickleTable.class::isInstance)
- .findFirst()
- .ifPresent(table -> createDataTableAttachment((PickleTable) table));
- } else if (event.testStep instanceof HookTestStep) {
- initHook((HookTestStep) event.testStep);
+ final TestCase testCase = event.getTestCase();
+ if (event.testStep instanceof HookTestStep) {
+ final HookTestStep hook = (HookTestStep) event.testStep;
+
+ if (isFixtureHook(hook)) {
+ handleStartFixtureHook(testCase, hook);
+ } else {
+ handleStartStepHook(testCase, hook);
+ }
+ } else if (event.testStep instanceof PickleStepTestStep) {
+ handleStartPickleStep(testCase, (PickleStepTestStep) event.testStep);
}
}
- private void initHook(final HookTestStep hook) {
+ private void handleStartPickleStep(final TestCase testCase,
+ final PickleStepTestStep pickleStep) {
+ final String uuid = testCaseUuids.get(testCase);
+ if (Objects.isNull(uuid)) {
+ return;
+ }
- final FixtureResult hookResult = new FixtureResult()
+ final PickleStep step = pickleStep.getPickleStep();
+ final String stepKeyword = Optional
+ .ofNullable(
+ testSources.getKeywordFromSource(
+ testCase.getUri(),
+ pickleStep.getStepLine()
+ )
+ )
+ .orElse("");
+
+ final StepResult stepResult = new StepResult()
+ .setName(stepKeyword + step.getText())
+ .setStart(System.currentTimeMillis());
+
+ final String stepUuid = stepUuids.computeIfAbsent(
+ pickleStep,
+ cl -> UUID.randomUUID().toString()
+ );
+
+ lifecycle.setCurrentTestCase(uuid);
+ lifecycle.startStep(uuid, stepUuid, stepResult);
+
+ pickleStep.getStepArgument()
+ .stream()
+ .filter(PickleTable.class::isInstance)
+ .map(PickleTable.class::cast)
+ .findFirst()
+ .ifPresent(this::createDataTableAttachment);
+
+ }
+
+ private void handleStartStepHook(final TestCase testCase,
+ final HookTestStep hook) {
+ final String uuid = testCaseUuids.get(testCase);
+ if (Objects.isNull(uuid)) {
+ return;
+ }
+
+ final StepResult stepResult = new StepResult()
.setName(hook.getCodeLocation())
.setStart(System.currentTimeMillis());
- if (hook.getHookType() == HookType.Before) {
- lifecycle.startPrepareFixture(getTestContainerUuid(), getHookStepUuid(hook), hookResult);
- } else {
- lifecycle.startTearDownFixture(getTestContainerUuid(), getHookStepUuid(hook), hookResult);
+ final String stepUuid = stepUuids.computeIfAbsent(
+ hook, unused -> UUID.randomUUID().toString()
+ );
+
+ lifecycle.setCurrentTestCase(uuid);
+ lifecycle.startStep(uuid, stepUuid, stepResult);
+ }
+
+ private void handleStartFixtureHook(final TestCase testCase,
+ final HookTestStep hook) {
+ final String uuid = testCaseUuids.get(testCase);
+ if (Objects.isNull(uuid)) {
+ return;
}
+
+ final String containerUuid = hookStepContainerUuid
+ .computeIfAbsent(hook, unused -> UUID.randomUUID().toString());
+
+ lifecycle.startTestContainer(new TestResultContainer()
+ .setUuid(containerUuid)
+ .setChildren(Collections.singletonList(uuid))
+ );
+
+ final FixtureResult hookResult = new FixtureResult()
+ .setName(hook.getCodeLocation());
+
+ final String fixtureUuid = fixtureUuids.computeIfAbsent(
+ hook, unused -> UUID.randomUUID().toString()
+ );
+ if (hook.getHookType() == Before) {
+ lifecycle.startPrepareFixture(containerUuid, fixtureUuid, hookResult);
+ } else {
+ lifecycle.startTearDownFixture(containerUuid, fixtureUuid, hookResult);
+ }
}
private void handleTestStepFinished(final TestStepFinished event) {
if (event.testStep instanceof HookTestStep) {
- handleHookStep(event);
- } else {
- handlePickleStep(event);
+ final HookTestStep hook = (HookTestStep) event.testStep;
+ if (isFixtureHook(hook)) {
+ handleStopHookStep(event.result, hook);
+ } else {
+ handleStopStep(event.getTestCase(), event.result, hook);
+ }
+ } else if (event.testStep instanceof PickleStepTestStep) {
+ final PickleStepTestStep pickleStep = (PickleStepTestStep) event.testStep;
+ handleStopStep(event.getTestCase(), event.result, pickleStep);
}
}
+ private static boolean isFixtureHook(final HookTestStep hook) {
+ return hook.getHookType() == Before || hook.getHookType() == HookType.After;
+ }
+
private void handleWriteEvent(final WriteEvent event) {
lifecycle.addAttachment(
"Text output",
@@ -250,34 +347,41 @@ private void handleWriteEvent(final WriteEvent event) {
}
private void handleEmbedEvent(final EmbedEvent event) {
- lifecycle.addAttachment("Screenshot", null, null, new ByteArrayInputStream(event.data));
+ lifecycle.addAttachment(
+ Objects.isNull(event.name)
+ ? "Embedding"
+ : event.name,
+ event.mimeType,
+ null,
+ new ByteArrayInputStream(event.data)
+ );
}
- /*
- Utility Methods
- */
-
- private String getTestContainerUuid() {
- return currentContainer.get();
+ private String getHistoryId(final TestCase testCase) {
+ final String testCaseLocation = getTestCaseUri(testCase) + COLON + testCase.getLine();
+ return md5(testCaseLocation);
}
- private String getTestCaseUuid(final TestCase testCase) {
- return scenarioUuids.computeIfAbsent(getHistoryId(testCase), it -> UUID.randomUUID().toString());
+ private String getTestCaseId(final TestCase testCase) {
+ final String testCaseId = getTestCaseUri(testCase) + COLON + testCase.getName();
+ return md5(testCaseId);
}
- private String getStepUuid(final PickleStepTestStep step) {
- return currentFeature.get().getName() + getTestCaseUuid(currentTestCase.get())
- + step.getPickleStep().getText() + step.getStepLine();
- }
+ private String getTestCaseUri(final TestCase testCase) {
+ final String testCaseUri = getUriWithoutScheme(testCase);
- private String getHookStepUuid(final HookTestStep step) {
- return currentFeature.get().getName() + getTestCaseUuid(currentTestCase.get())
- + step.getHookType().toString() + step.getCodeLocation();
+ if (testCaseUri.startsWith(CUCUMBER_WORKING_DIR)) {
+ return testCaseUri.substring(CUCUMBER_WORKING_DIR.length());
+ }
+ return testCaseUri;
}
- private String getHistoryId(final TestCase testCase) {
- final String testCaseLocation = testCase.getUri() + ":" + testCase.getLine();
- return md5(testCaseLocation);
+ private static String getUriWithoutScheme(final TestCase testCase) {
+ try {
+ return URI.create(testCase.getUri()).getSchemeSpecificPart();
+ } catch (Exception ignored) {
+ return testCase.getUri();
+ }
}
private Status translateTestCaseStatus(final Result testCaseResult) {
@@ -298,124 +402,116 @@ private Status translateTestCaseStatus(final Result testCaseResult) {
}
private List getExamplesAsParameters(
- final ScenarioOutline scenarioOutline, final TestCase localCurrentTestCase
- ) {
- final Optional examplesBlock =
- scenarioOutline.getExamples().stream()
+ final ScenarioOutline scenario,
+ final TestCase localCurrentTestCase) {
+ final Optional maybeExample =
+ scenario.getExamples().stream()
.filter(example -> example.getTableBody().stream()
- .anyMatch(row -> row.getLocation().getLine() == localCurrentTestCase.getLine())
- ).findFirst();
-
- if (examplesBlock.isPresent()) {
- final TableRow row = examplesBlock.get().getTableBody().stream()
- .filter(example -> example.getLocation().getLine() == localCurrentTestCase.getLine())
- .findFirst().get();
- return IntStream.range(0, examplesBlock.get().getTableHeader().getCells().size()).mapToObj(index -> {
- final String name = examplesBlock.get().getTableHeader().getCells().get(index).getValue();
- final String value = row.getCells().get(index).getValue();
- return createParameter(name, value);
- }).collect(Collectors.toList());
- } else {
+ .anyMatch(row -> row.getLocation().getLine()
+ == localCurrentTestCase.getLine())
+ )
+ .findFirst();
+
+ if (!maybeExample.isPresent()) {
+ return Collections.emptyList();
+ }
+
+ final Examples examples = maybeExample.get();
+
+ final Optional maybeRow = examples.getTableBody().stream()
+ .filter(example -> example.getLocation().getLine() == localCurrentTestCase.getLine())
+ .findFirst();
+
+ if (!maybeRow.isPresent()) {
return Collections.emptyList();
}
+
+ final TableRow row = maybeRow.get();
+
+ return IntStream.range(0, examples.getTableHeader().getCells().size())
+ .mapToObj(index -> {
+ final String name = examples.getTableHeader().getCells().get(index).getValue();
+ final String value = row.getCells().get(index).getValue();
+ return createParameter(name, value);
+ })
+ .collect(Collectors.toList());
}
private void createDataTableAttachment(final PickleTable pickleTable) {
final List rows = pickleTable.getRows();
final StringBuilder dataTableCsv = new StringBuilder();
- if (!rows.isEmpty()) {
- rows.forEach(dataTableRow -> {
- dataTableCsv.append(
- dataTableRow.getCells().stream()
- .map(PickleCell::getValue)
- .collect(Collectors.joining("\t"))
- );
- dataTableCsv.append('\n');
- });
-
- final String attachmentSource = lifecycle
- .prepareAttachment("Data table", "text/tab-separated-values", "csv");
- lifecycle.writeAttachment(attachmentSource,
- new ByteArrayInputStream(dataTableCsv.toString().getBytes(StandardCharsets.UTF_8)));
+ for (PickleRow row : rows) {
+ final String rowString = row.getCells().stream()
+ .map(PickleCell::getValue)
+ .collect(Collectors.joining("\t", "", "\n"));
+ dataTableCsv.append(rowString);
}
+ final String attachmentSource = lifecycle
+ .prepareAttachment("Data table", "text/tab-separated-values", "csv");
+ lifecycle.writeAttachment(attachmentSource,
+ new ByteArrayInputStream(dataTableCsv.toString().getBytes(StandardCharsets.UTF_8)));
}
- private void handleHookStep(final TestStepFinished event) {
- final HookTestStep hookStep = (HookTestStep) event.testStep;
- final String uuid = getHookStepUuid(hookStep);
- final FixtureResult fixtureResult = new FixtureResult().setStatus(translateTestCaseStatus(event.result));
-
- if (!Status.PASSED.equals(fixtureResult.getStatus())) {
- final TestResult testResult = new TestResult().setStatus(translateTestCaseStatus(event.result));
- final StatusDetails statusDetails = getStatusDetails(event.result.getError())
- .orElseGet(StatusDetails::new);
-
- final String errorMessage = event.result.getError() == null ? hookStep.getHookType()
- .name() + " is failed." : hookStep.getHookType()
- .name() + " is failed: " + event.result.getError().getLocalizedMessage();
- statusDetails.setMessage(errorMessage);
-
- if (hookStep.getHookType() == HookType.Before) {
- final TagParser tagParser = new TagParser(currentFeature.get(), currentTestCase.get());
- statusDetails
- .setFlaky(tagParser.isFlaky())
- .setMuted(tagParser.isMuted())
- .setKnown(tagParser.isKnown());
- testResult.setStatus(Status.SKIPPED);
- updateTestCaseStatus(testResult.getStatus());
- forbidTestCaseStatusChange.set(true);
- } else {
- testResult.setStatus(Status.BROKEN);
- updateTestCaseStatus(testResult.getStatus());
- }
- fixtureResult.setStatusDetails(statusDetails);
+ private void handleStopHookStep(final Result eventResult,
+ final HookTestStep hook) {
+ final String containerUuid = hookStepContainerUuid.get(hook);
+ if (Objects.isNull(containerUuid)) {
+ // maybe throw an exception?
+ return;
+ }
+
+ final String uuid = fixtureUuids.get(hook);
+ if (Objects.isNull(uuid)) {
+ // maybe throw an exception?
+ return;
}
- lifecycle.updateFixture(uuid, result -> result.setStatus(fixtureResult.getStatus())
- .setStatusDetails(fixtureResult.getStatusDetails()));
+ final Status status = translateTestCaseStatus(eventResult);
+ final StatusDetails statusDetails = getStatusDetails(eventResult.getError())
+ .orElseGet(StatusDetails::new);
+
+ lifecycle.updateFixture(uuid, result -> result
+ .setStatus(status)
+ .setStatusDetails(statusDetails)
+ );
lifecycle.stopFixture(uuid);
+
+ lifecycle.stopTestContainer(containerUuid);
+ lifecycle.writeTestContainer(containerUuid);
}
- private void handlePickleStep(final TestStepFinished event) {
+ private void handleStopStep(final TestCase testCase,
+ final Result eventResult,
+ final TestStep step) {
+ final String stepUuid = stepUuids.get(step);
+ if (Objects.isNull(stepUuid)) {
+ // maybe exception?
+ return;
+ }
- final Status stepStatus = translateTestCaseStatus(event.result);
- final StatusDetails statusDetails;
- if (event.result.getStatus() == Result.Type.UNDEFINED) {
- updateTestCaseStatus(Status.PASSED);
+ final Feature feature = testSources.getFeature(testCase.getUri());
- statusDetails =
- getStatusDetails(new PendingException("TODO: implement me"))
- .orElse(new StatusDetails());
- lifecycle.updateTestCase(getTestCaseUuid(currentTestCase.get()), scenarioResult ->
- scenarioResult
- .setStatusDetails(statusDetails));
- } else {
- statusDetails =
- getStatusDetails(event.result.getError())
- .orElse(new StatusDetails());
- updateTestCaseStatus(stepStatus);
- }
+ final Status stepStatus = translateTestCaseStatus(eventResult);
- if (!Status.PASSED.equals(stepStatus) && stepStatus != null) {
- forbidTestCaseStatusChange.set(true);
- }
+ final StatusDetails statusDetails
+ = eventResult.getStatus() == Result.Type.UNDEFINED
+ ? new StatusDetails().setMessage("Undefined Step. Please add step definition")
+ : getStatusDetails(eventResult.getError())
+ .orElse(new StatusDetails());
- final TagParser tagParser = new TagParser(currentFeature.get(), currentTestCase.get());
+ final TagParser tagParser = new TagParser(feature, testCase);
statusDetails
.setFlaky(tagParser.isFlaky())
.setMuted(tagParser.isMuted())
.setKnown(tagParser.isKnown());
- lifecycle.updateStep(getStepUuid((PickleStepTestStep) event.testStep),
- stepResult -> stepResult.setStatus(stepStatus).setStatusDetails(statusDetails));
- lifecycle.stopStep(getStepUuid((PickleStepTestStep) event.testStep));
- }
-
- private void updateTestCaseStatus(final Status status) {
- if (!forbidTestCaseStatusChange.get()) {
- lifecycle.updateTestCase(getTestCaseUuid(currentTestCase.get()),
- result -> result.setStatus(status));
- }
+ lifecycle.updateStep(
+ stepUuid,
+ stepResult -> stepResult
+ .setStatus(stepStatus)
+ .setStatusDetails(statusDetails)
+ );
+ lifecycle.stopStep(stepUuid);
}
}
diff --git a/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/LabelBuilder.java b/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/LabelBuilder.java
index ef7dcf7f1..fbcbb3f10 100644
--- a/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/LabelBuilder.java
+++ b/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/LabelBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -49,8 +49,8 @@
/**
* Scenario labels and links builder.
*/
-@SuppressWarnings({"CyclomaticComplexity", "PMD.CyclomaticComplexity", "PMD.NcssCount", "MultipleStringLiterals"})
-class LabelBuilder {
+@SuppressWarnings({"CyclomaticComplexity", "MultipleStringLiterals"})
+final class LabelBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(LabelBuilder.class);
private static final String COMPOSITE_TAG_DELIMITER = "=";
@@ -89,33 +89,34 @@ class LabelBuilder {
switch (tagKey) {
case SEVERITY:
- getScenarioLabels().add(ResultsUtils.createSeverityLabel(tagValue.toLowerCase()));
+ scenarioLabels.add(ResultsUtils.createSeverityLabel(tagValue.toLowerCase()));
break;
case TMS_LINK:
- getScenarioLinks().add(ResultsUtils.createTmsLink(tagValue));
+ scenarioLinks.add(ResultsUtils.createTmsLink(tagValue));
break;
case ISSUE_LINK:
- getScenarioLinks().add(ResultsUtils.createIssueLink(tagValue));
+ scenarioLinks.add(ResultsUtils.createIssueLink(tagValue));
break;
case PLAIN_LINK:
- getScenarioLinks().add(ResultsUtils.createLink(null, tagValue, tagValue, null));
+ scenarioLinks.add(ResultsUtils.createLink(null, tagValue, tagValue, null));
break;
default:
LOGGER.warn("Composite tag {} is not supported. adding it as RAW", tagKey);
- getScenarioLabels().add(getTagLabel(tag));
+ scenarioLabels.add(getTagLabel(tag));
break;
}
} else if (tagParser.isPureSeverityTag(tag)) {
- getScenarioLabels().add(ResultsUtils.createSeverityLabel(tagString.substring(1)));
+ scenarioLabels.add(ResultsUtils.createSeverityLabel(tagString.substring(1)));
} else if (!tagParser.isResultTag(tag)) {
- getScenarioLabels().add(getTagLabel(tag));
+ scenarioLabels.add(getTagLabel(tag));
}
}
final String featureName = feature.getName();
final String uri = scenario.getUri();
- getScenarioLabels().addAll(Arrays.asList(
+ scenarioLabels.addAll(ResultsUtils.getProvidedLabels());
+ scenarioLabels.addAll(Arrays.asList(
createHostLabel(),
createThreadLabel(),
createFeatureLabel(featureName),
@@ -129,7 +130,7 @@ class LabelBuilder {
featurePackage(uri, featureName)
.map(ResultsUtils::createPackageLabel)
- .ifPresent(getScenarioLabels()::add);
+ .ifPresent(scenarioLabels::add);
}
public List getScenarioLabels() {
@@ -150,13 +151,13 @@ private Label getTagLabel(final PickleTag tag) {
* @param tagString Full tag name and value
*/
private void tryHandleNamedLink(final String tagString) {
- final String namedLinkPatternString = PLAIN_LINK + "\\.(\\w+-?)+=(\\w+(-|_)?)+";
+ final String namedLinkPatternString = PLAIN_LINK + "\\.(\\w+-?)+=(\\w+([-_])?)+";
final Pattern namedLinkPattern = Pattern.compile(namedLinkPatternString, Pattern.CASE_INSENSITIVE);
if (namedLinkPattern.matcher(tagString).matches()) {
final String type = tagString.split(COMPOSITE_TAG_DELIMITER)[0].split("[.]")[1];
final String name = tagString.split(COMPOSITE_TAG_DELIMITER)[1];
- getScenarioLinks().add(ResultsUtils.createLink(null, name, null, type));
+ scenarioLinks.add(ResultsUtils.createLink(null, name, null, type));
} else {
LOGGER.warn("Composite named tag {} does not match regex {}. Skipping", tagString,
namedLinkPatternString);
diff --git a/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/TagParser.java b/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/TagParser.java
index dec84aeb5..0c6f76e44 100644
--- a/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/TagParser.java
+++ b/allure-cucumber4-jvm/src/main/java/io/qameta/allure/cucumber4jvm/TagParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -52,13 +52,13 @@ public boolean isKnown() {
private boolean getStatusDetailByTag(final String tagName) {
return scenario.getTags().stream()
- .anyMatch(tag -> tag.getName().equalsIgnoreCase(tagName))
- || feature.getTags().stream()
- .anyMatch(tag -> tag.getName().equalsIgnoreCase(tagName));
+ .anyMatch(tag -> tag.getName().equalsIgnoreCase(tagName))
+ || feature.getTags().stream()
+ .anyMatch(tag -> tag.getName().equalsIgnoreCase(tagName));
}
public boolean isResultTag(final PickleTag tag) {
- return Arrays.asList(new String[]{FLAKY, KNOWN, MUTED})
+ return Arrays.asList(FLAKY, KNOWN, MUTED)
.contains(tag.getName().toUpperCase());
}
diff --git a/allure-cucumber-jvm/src/main/resources/META-INF/aop-ajc.xml b/allure-cucumber4-jvm/src/main/resources/META-INF/aop-ajc.xml
similarity index 74%
rename from allure-cucumber-jvm/src/main/resources/META-INF/aop-ajc.xml
rename to allure-cucumber4-jvm/src/main/resources/META-INF/aop-ajc.xml
index a980b7a2d..09e38300c 100644
--- a/allure-cucumber-jvm/src/main/resources/META-INF/aop-ajc.xml
+++ b/allure-cucumber4-jvm/src/main/resources/META-INF/aop-ajc.xml
@@ -1,6 +1,7 @@
+
-
\ No newline at end of file
+
diff --git a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/AllureCucumber4JvmTest.java b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/AllureCucumber4JvmTest.java
index a24160f02..9cc09bbfc 100644
--- a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/AllureCucumber4JvmTest.java
+++ b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/AllureCucumber4JvmTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,25 +15,17 @@
*/
package io.qameta.allure.cucumber4jvm;
-import cucumber.runtime.ClassFinder;
-import cucumber.runtime.FeatureSupplier;
+import cucumber.runtime.FeaturePathFeatureSupplier;
import cucumber.runtime.Runtime;
import cucumber.runtime.io.MultiLoader;
import cucumber.runtime.io.ResourceLoader;
-import cucumber.runtime.io.ResourceLoaderClassFinder;
-import cucumber.runtime.model.CucumberFeature;
-import gherkin.AstBuilder;
-import gherkin.Parser;
-import gherkin.TokenMatcher;
-import gherkin.ast.GherkinDocument;
-import gherkin.events.PickleEvent;
-import gherkin.pickles.Compiler;
-import gherkin.pickles.Pickle;
+import cucumber.runtime.model.FeatureLoader;
+import io.cucumber.core.model.FeatureWithLines;
import io.cucumber.core.options.CommandlineOptionsParser;
import io.cucumber.core.options.RuntimeOptions;
import io.github.glytching.junit.extension.system.SystemProperty;
import io.github.glytching.junit.extension.system.SystemPropertyExtension;
-import io.qameta.allure.AllureLifecycle;
+import io.qameta.allure.Step;
import io.qameta.allure.model.Attachment;
import io.qameta.allure.model.FixtureResult;
import io.qameta.allure.model.Label;
@@ -44,22 +36,19 @@
import io.qameta.allure.model.StatusDetails;
import io.qameta.allure.model.StepResult;
import io.qameta.allure.model.TestResult;
+import io.qameta.allure.model.TestResultContainer;
import io.qameta.allure.test.AllureFeatures;
-import io.qameta.allure.test.AllureResultsWriterStub;
-import org.apache.commons.io.IOUtils;
+import io.qameta.allure.test.AllureResults;
+import io.qameta.allure.test.RunUtils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.parallel.ResourceLock;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -67,23 +56,24 @@
import static io.qameta.allure.util.ResultsUtils.PACKAGE_LABEL_NAME;
import static io.qameta.allure.util.ResultsUtils.SUITE_LABEL_NAME;
import static io.qameta.allure.util.ResultsUtils.TEST_CLASS_LABEL_NAME;
+import static io.qameta.allure.util.ResultsUtils.md5;
import static java.lang.Thread.currentThread;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
+import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE;
+import static org.junit.jupiter.api.parallel.Resources.SYSTEM_PROPERTIES;
/**
* @author charlie (Dmitry Baev).
*/
-@SuppressWarnings("unchecked")
class AllureCucumber4JvmTest {
@AllureFeatures.Base
@Test
void shouldSetName() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/simple.feature");
+ final AllureResults results = runFeature("features/simple.feature");
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.extracting(TestResult::getName)
.containsExactlyInAnyOrder("Add a to b");
@@ -92,22 +82,22 @@ void shouldSetName() {
@AllureFeatures.PassedTests
@Test
void shouldSetStatus() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/simple.feature");
+ final AllureResults results = runFeature("features/simple.feature");
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.PASSED);
+ .extracting(TestResult::getName, TestResult::getStatus)
+ .containsExactlyInAnyOrder(
+ tuple("Add a to b", Status.PASSED)
+ );
}
@AllureFeatures.FailedTests
@Test
void shouldSetFailedStatus() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/failed.feature");
+ final AllureResults results = runFeature("features/failed.feature");
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.extracting(TestResult::getStatus)
.containsExactlyInAnyOrder(Status.FAILED);
@@ -116,23 +106,21 @@ void shouldSetFailedStatus() {
@AllureFeatures.FailedTests
@Test
void shouldSetStatusDetails() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/failed.feature");
+ final AllureResults results = runFeature("features/failed.feature");
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.extracting(TestResult::getStatusDetails)
.extracting(StatusDetails::getMessage)
- .containsExactlyInAnyOrder("expected [123] but found [15]");
+ .containsExactlyInAnyOrder("expecting 15 to be equal to 123");
}
@AllureFeatures.BrokenTests
@Test
void shouldSetBrokenStatus() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/broken.feature");
+ final AllureResults results = runFeature("features/broken.feature");
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.extracting(TestResult::getStatus)
.containsExactlyInAnyOrder(Status.BROKEN);
@@ -141,10 +129,9 @@ void shouldSetBrokenStatus() {
@AllureFeatures.Stages
@Test
void shouldSetStage() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/simple.feature");
+ final AllureResults results = runFeature("features/simple.feature");
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.extracting(TestResult::getStage)
.containsExactlyInAnyOrder(Stage.FINISHED);
@@ -154,11 +141,10 @@ void shouldSetStage() {
@Test
void shouldSetStart() {
final long before = Instant.now().toEpochMilli();
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/simple.feature");
+ final AllureResults results = runFeature("features/simple.feature");
final long after = Instant.now().toEpochMilli();
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.extracting(TestResult::getStart)
.allMatch(v -> v >= before && v <= after);
@@ -168,11 +154,10 @@ void shouldSetStart() {
@Test
void shouldSetStop() {
final long before = Instant.now().toEpochMilli();
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/simple.feature");
+ final AllureResults results = runFeature("features/simple.feature");
final long after = Instant.now().toEpochMilli();
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.extracting(TestResult::getStop)
.allMatch(v -> v >= before && v <= after);
@@ -181,25 +166,23 @@ void shouldSetStop() {
@AllureFeatures.FullName
@Test
void shouldSetFullName() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/simple.feature");
+ final AllureResults results = runFeature("features/simple.feature");
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.extracting(TestResult::getFullName)
- .containsExactlyInAnyOrder("Simple feature: Add a to b");
+ .containsExactlyInAnyOrder("src/test/resources/features/simple.feature:3");
}
@AllureFeatures.Descriptions
@Test
void shouldSetDescription() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/description.feature");
+ final AllureResults results = runFeature("features/description.feature");
final String expected = "This is description for current feature.\n"
- + "It should appear on each scenario in report";
+ + "It should appear on each scenario in report";
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.extracting(TestResult::getDescription)
.containsExactlyInAnyOrder(
@@ -211,13 +194,12 @@ void shouldSetDescription() {
@AllureFeatures.Descriptions
@Test
void shouldSetScenarioDescription() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/scenario_description.feature");
+ final AllureResults results = runFeature("features/scenario_description.feature");
final String expected = "This is description for current feature.\n"
- + "It should appear on each scenario in report";
+ + "It should appear on each scenario in report";
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.extracting(TestResult::getDescription)
.containsExactlyInAnyOrder(
@@ -229,10 +211,9 @@ void shouldSetScenarioDescription() {
@AllureFeatures.Attachments
@Test
void shouldAddDataTableAttachment() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/datatable.feature");
+ final AllureResults results = runFeature("features/datatable.feature");
- final List attachments = writer.getTestResults().stream()
+ final List attachments = results.getTestResults().stream()
.map(TestResult::getSteps)
.flatMap(Collection::stream)
.map(StepResult::getAttachments)
@@ -246,7 +227,7 @@ void shouldAddDataTableAttachment() {
);
final Attachment dataTableAttachment = attachments.iterator().next();
- final Map attachmentFiles = writer.getAttachments();
+ final Map attachmentFiles = results.getAttachments();
assertThat(attachmentFiles)
.containsKeys(dataTableAttachment.getSource());
@@ -254,9 +235,11 @@ void shouldAddDataTableAttachment() {
final String attachmentContent = new String(bytes, StandardCharsets.UTF_8);
assertThat(attachmentContent)
- .isEqualTo("name\tlogin\temail\n" +
- "Viktor\tclicman\tclicman@ya.ru\n" +
- "Viktor2\tclicman2\tclicman2@ya.ru\n"
+ .isEqualTo("""
+ name\tlogin\temail
+ Viktor\tclicman\tclicman@ya.ru
+ Viktor2\tclicman2\tclicman2@ya.ru
+ """
);
}
@@ -264,10 +247,9 @@ void shouldAddDataTableAttachment() {
@AllureFeatures.Attachments
@Test
void shouldAddAttachments() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/attachments.feature");
+ final AllureResults results = runFeature("features/attachments.feature");
- final List attachments = writer.getTestResults().stream()
+ final List attachments = results.getTestResults().stream()
.map(TestResult::getSteps)
.flatMap(Collection::stream)
.map(StepResult::getAttachments)
@@ -278,10 +260,10 @@ void shouldAddAttachments() {
.extracting(Attachment::getName, Attachment::getType)
.containsExactlyInAnyOrder(
tuple("Text output", "text/plain"),
- tuple("Screenshot", null)
+ tuple("Embedding", "image/png")
);
- final List attachmentContents = writer.getAttachments().values().stream()
+ final List attachmentContents = results.getAttachments().values().stream()
.map(bytes -> new String(bytes, StandardCharsets.UTF_8))
.collect(Collectors.toList());
@@ -292,30 +274,28 @@ void shouldAddAttachments() {
@AllureFeatures.Steps
@Test
void shouldAddBackgroundSteps() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/background.feature");
+ final AllureResults results = runFeature("features/background.feature");
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.hasSize(1)
.flatExtracting(TestResult::getSteps)
.extracting(StepResult::getName)
.containsExactly(
- "Given cat is sad",
- "And cat is murmur",
- "When Pet the cat",
- "Then Cat is happy"
+ "Given cat is sad",
+ "And cat is murmur",
+ "When Pet the cat",
+ "Then Cat is happy"
);
}
@AllureFeatures.Parameters
@Test
void shouldAddParametersFromExamples() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/examples.feature");
+ final AllureResults results = runFeature("features/examples.feature");
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.hasSize(2);
@@ -339,10 +319,9 @@ void shouldAddParametersFromExamples() {
@AllureFeatures.Parameters
@Test
void shouldHandleMultipleExamplesPerOutline() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/multi-examples.feature");
+ final AllureResults results = runFeature("features/multi-examples.feature");
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.hasSize(2);
@@ -359,10 +338,9 @@ void shouldHandleMultipleExamplesPerOutline() {
@AllureFeatures.Parameters
@Test
void shouldSupportTaggedExamplesBlocks() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/multi-examples.feature", "--tags", "@ExamplesTag2");
+ final AllureResults results = runFeature("features/multi-examples.feature", "--tags", "@ExamplesTag2");
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.hasSize(1);
@@ -385,10 +363,9 @@ void shouldSupportTaggedExamplesBlocks() {
@AllureFeatures.MarkerAnnotations
@Test
void shouldAddTags() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/tags.feature");
+ final AllureResults results = runFeature("features/tags.feature");
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.flatExtracting(TestResult::getLabels)
@@ -405,10 +382,9 @@ void shouldAddTags() {
@SystemProperty(name = "allure.link.tms.pattern", value = "https://example.org/tms/{}")
@Test
void shouldAddLinks() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/tags.feature");
+ final AllureResults results = runFeature("features/tags.feature");
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.flatExtracting(TestResult::getLinks)
@@ -422,10 +398,9 @@ void shouldAddLinks() {
@AllureFeatures.MarkerAnnotations
@Test
void shouldAddBddLabels() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/tags.feature");
+ final AllureResults results = runFeature("features/tags.feature");
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.flatExtracting(TestResult::getLabels)
@@ -439,10 +414,9 @@ void shouldAddBddLabels() {
@AllureFeatures.Timeline
@Test
void shouldAddThreadHostLabels() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/tags.feature");
+ final AllureResults results = runFeature("features/tags.feature");
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.flatExtracting(TestResult::getLabels)
@@ -453,16 +427,15 @@ void shouldAddThreadHostLabels() {
@AllureFeatures.MarkerAnnotations
@Test
void shouldAddCommonLabels() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/tags.feature");
+ final AllureResults results = runFeature("features/tags.feature");
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.flatExtracting(TestResult::getLabels)
.extracting(Label::getName, Label::getValue)
.contains(
- tuple(PACKAGE_LABEL_NAME, "features.tags_feature.Test Simple Scenarios"),
+ tuple(PACKAGE_LABEL_NAME, "src.test.resources.features.tags_feature.Test Simple Scenarios"),
tuple(SUITE_LABEL_NAME, "Test Simple Scenarios"),
tuple(TEST_CLASS_LABEL_NAME, "Add a to b")
);
@@ -471,20 +444,21 @@ void shouldAddCommonLabels() {
@AllureFeatures.Steps
@Test
void shouldProcessUndefinedSteps() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/undefined.feature");
+ final AllureResults results = runFeature("features/undefined.feature");
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
- .extracting(TestResult::getStatus)
- .containsExactlyInAnyOrder(Status.SKIPPED);
+ .extracting(TestResult::getName, TestResult::getStatus)
+ .containsExactlyInAnyOrder(
+ tuple("Step is not defined", null)
+ );
assertThat(testResults.get(0).getSteps())
.extracting(StepResult::getName, StepResult::getStatus)
.containsExactlyInAnyOrder(
- tuple("Given a is 5", Status.PASSED),
- tuple("When step is undefined", null),
- tuple("Then b is 10", Status.SKIPPED)
+ tuple("Given a is 5", Status.PASSED),
+ tuple("When step is undefined", null),
+ tuple("Then b is 10", Status.SKIPPED)
);
}
@@ -492,10 +466,9 @@ void shouldProcessUndefinedSteps() {
@AllureFeatures.Steps
@Test
void shouldProcessPendingExceptionsInSteps() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/pending.feature");
+ final AllureResults results = runFeature("features/pending.feature");
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.extracting(TestResult::getStatus)
.containsExactlyInAnyOrder(Status.SKIPPED);
@@ -503,19 +476,18 @@ void shouldProcessPendingExceptionsInSteps() {
assertThat(testResults.get(0).getSteps())
.extracting(StepResult::getName, StepResult::getStatus)
.containsExactlyInAnyOrder(
- tuple("Given a is 5", Status.PASSED),
- tuple("When step is yet to be implemented", Status.SKIPPED),
- tuple("Then b is 10", Status.SKIPPED)
+ tuple("Given a is 5", Status.PASSED),
+ tuple("When step is yet to be implemented", Status.SKIPPED),
+ tuple("Then b is 10", Status.SKIPPED)
);
}
@AllureFeatures.Base
@Test
void shouldSupportDryRunForSimpleFeatures() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/simple.feature", "--dry-run");
+ final AllureResults results = runFeature("features/simple.feature", "--dry-run");
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.extracting(TestResult::getName, TestResult::getStatus)
.containsExactlyInAnyOrder(
@@ -525,10 +497,10 @@ void shouldSupportDryRunForSimpleFeatures() {
assertThat(testResults.get(0).getSteps())
.extracting(StepResult::getName, StepResult::getStatus)
.containsExactlyInAnyOrder(
- tuple("Given a is 5", Status.SKIPPED),
- tuple("And b is 10", Status.SKIPPED),
- tuple("When I add a to b", Status.SKIPPED),
- tuple("Then result is 15", Status.SKIPPED)
+ tuple("Given a is 5", Status.SKIPPED),
+ tuple("And b is 10", Status.SKIPPED),
+ tuple("When I add a to b", Status.SKIPPED),
+ tuple("Then result is 15", Status.SKIPPED)
);
}
@@ -536,103 +508,140 @@ void shouldSupportDryRunForSimpleFeatures() {
@AllureFeatures.Base
@Test
void shouldSupportDryRunForHooks() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/hooks.feature", "--dry-run", "-t",
+ final AllureResults results = runFeature("features/hooks.feature", "--dry-run", "-t",
"@WithHooks or @BeforeHookWithException or @AfterHookWithException");
- final List testResults = writer.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getName, TestResult::getStatus)
- .startsWith(
- tuple("Simple scenario with Before and After hooks", Status.SKIPPED)
- );
+ final TestResult tr1 = results.getTestResultByName("Simple scenario with Before and After hooks");
- assertThat(writer.getTestResultContainers().get(0).getBefores())
+ assertThat(results.getTestResultContainersForTestResult(tr1))
+ .flatExtracting(TestResultContainer::getBefores)
.extracting(FixtureResult::getName, FixtureResult::getStatus)
.containsExactlyInAnyOrder(
tuple("HookSteps.beforeHook()", Status.SKIPPED)
);
- assertThat(writer.getTestResultContainers().get(0).getAfters())
+ assertThat(results.getTestResultContainersForTestResult(tr1))
+ .flatExtracting(TestResultContainer::getAfters)
.extracting(FixtureResult::getName, FixtureResult::getStatus)
.containsExactlyInAnyOrder(
tuple("HookSteps.afterHook()", Status.SKIPPED)
);
- assertThat(testResults.get(0).getSteps())
+ assertThat(tr1.getSteps())
.extracting(StepResult::getName, StepResult::getStatus)
.containsExactlyInAnyOrder(
- tuple("Given a is 7", Status.SKIPPED),
- tuple("And b is 8", Status.SKIPPED),
- tuple("When I add a to b", Status.SKIPPED),
- tuple("Then result is 15", Status.SKIPPED)
+ tuple("Given a is 7", Status.SKIPPED),
+ tuple("And b is 8", Status.SKIPPED),
+ tuple("When I add a to b", Status.SKIPPED),
+ tuple("Then result is 15", Status.SKIPPED)
);
}
@AllureFeatures.History
@Test
void shouldPersistHistoryIdForScenarios() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/simple.feature");
+ final AllureResults results = runFeature("features/simple.feature");
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults.get(0).getHistoryId())
- .isEqualTo("2f9965e6cc23d5c5f9c5268a2ff6f921");
+ .isEqualTo("892e5eabe51184301cf1358453c9f052");
}
@AllureFeatures.History
@Test
void shouldPersistHistoryIdForExamples() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/examples.feature", "--threads", "2");
+ final AllureResults results = runFeature("features/examples.feature", "--threads", "2");
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.extracting(TestResult::getHistoryId)
- .containsExactlyInAnyOrder("16d71185f704fe21cbc98f9eaa3292bb", "d64467f9921e0209deae4286f5599f5d");
+ .containsExactlyInAnyOrder("c0f824814a130048e9f86358363cf23e", "646aca5d0775cd4f13161e1ea1a68c39");
+ }
+
+ @AllureFeatures.History
+ @Test
+ void shouldPersistDifferentHistoryIdComparedToTheSameTestCaseInDifferentLocation() {
+ final AllureResults results1 = runFeature("features/simple.feature");
+ final AllureResults results2 = runFeature("features/same/simple.feature");
+
+ assertThat(results1.getTestResults().get(0).getHistoryId())
+ .isNotEqualTo(results2.getTestResults().get(0).getHistoryId());
}
- private Comparator byHistoryId =
- Comparator.comparing(TestResult::getHistoryId);
+ @AllureFeatures.History
+ @Test
+ void shouldSetTestCaseIdForScenarios() {
+ final AllureResults results = runFeature("features/simple.feature");
+
+ final List testResults = results.getTestResults();
+ assertThat(testResults)
+ .extracting(TestResult::getName, TestResult::getTestCaseId)
+ .containsExactlyInAnyOrder(
+ tuple(
+ "Add a to b",
+ md5("src/test/resources/features/simple.feature:Add a to b")
+ )
+ );
+ }
+
+ @AllureFeatures.History
+ @Test
+ void shouldSetTestCaseIdForExamples() {
+ final AllureResults results = runFeature("features/examples.feature", "--threads", "2");
+
+ final List testResults = results.getTestResults();
+ assertThat(testResults)
+ .extracting(TestResult::getName, TestResult::getTestCaseId)
+ .containsExactlyInAnyOrder(
+ tuple(
+ "Scenario with Positive Examples",
+ md5("src/test/resources/features/examples.feature:Scenario with Positive Examples")
+ ),
+ tuple(
+ "Scenario with Positive Examples",
+ md5("src/test/resources/features/examples.feature:Scenario with Positive Examples")
+ )
+ );
+ }
@AllureFeatures.Parallel
@Test
void shouldProcessScenariosInParallelMode() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/parallel.feature", "--threads", "3");
+ final AllureResults results = runFeature("features/parallel.feature", "--threads", "3");
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.hasSize(3);
- List sortedTestResults = testResults.stream().sorted(byHistoryId).collect(Collectors.toList());
-
- assertThat(sortedTestResults.get(0).getSteps())
+ assertThat(testResults)
+ .flatExtracting(TestResult::getSteps)
.extracting(StepResult::getName)
- .containsExactly(
- "Given a is 1",
- "And b is 3",
- "When I add a to b",
- "Then result is 4"
+ .containsSubsequence(
+ "Given a is 1",
+ "And b is 3",
+ "When I add a to b",
+ "Then result is 4"
);
- assertThat(sortedTestResults.get(1).getSteps())
+ assertThat(testResults)
+ .flatExtracting(TestResult::getSteps)
.extracting(StepResult::getName)
- .containsExactly(
- "Given a is 2",
- "And b is 4",
- "When I add a to b",
- "Then result is 6"
+ .containsSubsequence(
+ "Given a is 2",
+ "And b is 4",
+ "When I add a to b",
+ "Then result is 6"
);
- assertThat(sortedTestResults.get(2).getSteps())
+ assertThat(testResults)
+ .flatExtracting(TestResult::getSteps)
.extracting(StepResult::getName)
- .containsExactly(
- "Given a is 7",
- "And b is 8",
- "When I add a to b",
- "Then result is 15"
+ .containsSubsequence(
+ "Given a is 7",
+ "And b is 8",
+ "When I add a to b",
+ "Then result is 15"
);
}
@@ -640,68 +649,64 @@ void shouldProcessScenariosInParallelMode() {
@AllureFeatures.Stages
@Test
void shouldDisplayHooksAsStages() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/hooks.feature", "-t",
+ final AllureResults results = runFeature("features/hooks.feature", "-t",
"@WithHooks or @BeforeHookWithException or @AfterHookWithException");
- final List testResults = writer.getTestResults();
- assertThat(testResults)
- .extracting(TestResult::getName, TestResult::getStatus)
- .containsExactlyInAnyOrder(
- tuple("Simple scenario with Before and After hooks", Status.PASSED),
- tuple("Simple scenario with Before hook with Exception", Status.SKIPPED),
- tuple("Simple scenario with After hook with Exception", Status.BROKEN)
- );
+ final TestResult tr1 = results.getTestResultByName("Simple scenario with Before and After hooks");
+ final TestResult tr2 = results.getTestResultByName("Simple scenario with Before hook with Exception");
+ final TestResult tr3 = results.getTestResultByName("Simple scenario with After hook with Exception");
- assertThat(testResults.get(0).getSteps())
+ assertThat(tr1.getSteps())
.extracting(StepResult::getName, StepResult::getStatus)
.containsExactlyInAnyOrder(
- tuple("Given a is 7", Status.PASSED),
- tuple("And b is 8", Status.PASSED),
- tuple("When I add a to b", Status.PASSED),
- tuple("Then result is 15", Status.PASSED)
+ tuple("Given a is 7", Status.PASSED),
+ tuple("And b is 8", Status.PASSED),
+ tuple("When I add a to b", Status.PASSED),
+ tuple("Then result is 15", Status.PASSED)
);
-
- assertThat(writer.getTestResultContainers().get(0).getBefores())
+ assertThat(results.getTestResultContainersForTestResult(tr1))
+ .flatExtracting(TestResultContainer::getBefores)
.extracting(FixtureResult::getName, FixtureResult::getStatus)
.containsExactlyInAnyOrder(
tuple("HookSteps.beforeHook()", Status.PASSED)
);
- assertThat(writer.getTestResultContainers().get(0).getAfters())
+ assertThat(results.getTestResultContainersForTestResult(tr1))
+ .flatExtracting(TestResultContainer::getAfters)
.extracting(FixtureResult::getName, FixtureResult::getStatus)
.containsExactlyInAnyOrder(
tuple("HookSteps.afterHook()", Status.PASSED)
);
- assertThat(testResults.get(1).getSteps())
+ assertThat(tr2.getSteps())
.extracting(StepResult::getName, StepResult::getStatus)
.containsExactlyInAnyOrder(
- tuple("Given a is 7", Status.SKIPPED),
- tuple("And b is 8", Status.SKIPPED),
- tuple("When I add a to b", Status.SKIPPED),
- tuple("Then result is 15", Status.SKIPPED)
+ tuple("Given a is 7", Status.SKIPPED),
+ tuple("And b is 8", Status.SKIPPED),
+ tuple("When I add a to b", Status.SKIPPED),
+ tuple("Then result is 15", Status.SKIPPED)
);
- assertThat(writer.getTestResultContainers().get(1).getBefores())
+ assertThat(results.getTestResultContainersForTestResult(tr2))
+ .flatExtracting(TestResultContainer::getBefores)
.extracting(FixtureResult::getName, FixtureResult::getStatus)
.containsExactlyInAnyOrder(
tuple("HookSteps.beforeHookWithException()", Status.FAILED)
);
-
- assertThat(testResults.get(2).getSteps())
+ assertThat(tr3.getSteps())
.extracting(StepResult::getName, StepResult::getStatus)
.containsExactlyInAnyOrder(
- tuple("Given a is 7", Status.PASSED),
- tuple("And b is 8", Status.PASSED),
- tuple("When I add a to b", Status.PASSED),
- tuple("Then result is 15", Status.PASSED)
+ tuple("Given a is 7", Status.PASSED),
+ tuple("And b is 8", Status.PASSED),
+ tuple("When I add a to b", Status.PASSED),
+ tuple("Then result is 15", Status.PASSED)
);
- assertThat(writer.getTestResultContainers().get(2).getAfters())
+ assertThat(results.getTestResultContainersForTestResult(tr3))
+ .flatExtracting(TestResultContainer::getAfters)
.extracting(FixtureResult::getName, FixtureResult::getStatus)
.containsExactlyInAnyOrder(
tuple("HookSteps.afterHookWithException()", Status.FAILED)
@@ -712,83 +717,99 @@ void shouldDisplayHooksAsStages() {
@AllureFeatures.BrokenTests
@Test
void shouldHandleAmbigiousStepsExceptions() {
- final AllureResultsWriterStub writer = new AllureResultsWriterStub();
- runFeature(writer, "features/ambigious.feature");
+ final AllureResults results = runFeature("features/ambigious.feature");
- final List testResults = writer.getTestResults();
+ final List testResults = results.getTestResults();
assertThat(testResults)
.extracting(TestResult::getName, TestResult::getStatus)
.containsExactlyInAnyOrder(
- tuple("Simple scenario with ambigious steps", Status.SKIPPED)
+ tuple("Simple scenario with ambigious steps", null)
);
assertThat(testResults.get(0).getSteps())
.extracting(StepResult::getName, StepResult::getStatus)
.containsExactly(
- tuple("When ambigious step present", null),
- tuple("Then something bad should happen", Status.SKIPPED)
- );
- }
-
- private byte runFeature(final AllureResultsWriterStub writer,
- final String featureResource,
- final String... moreOptions) {
-
- final AllureLifecycle lifecycle = new AllureLifecycle(writer);
- final AllureCucumber4Jvm cucumber4Jvm = new AllureCucumber4Jvm(lifecycle);
- final ClassLoader classLoader = currentThread().getContextClassLoader();
- final ResourceLoader resourceLoader = new MultiLoader(classLoader);
- final ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader);
- final List opts = new ArrayList<>(Arrays.asList(
- "--glue", "io.qameta.allure.cucumber4jvm.samples",
- "--plugin", "null_summary"
- ));
- opts.addAll(Arrays.asList(moreOptions));
- final RuntimeOptions options = new CommandlineOptionsParser().parse(opts).build();
- boolean mt = options.isMultiThreaded();
-
- FeatureSupplier featureSupplier = () -> {
- try {
- final String gherkin = readResource(featureResource);
- Parser parser = new Parser<>(new AstBuilder());
- TokenMatcher matcher = new TokenMatcher();
- GherkinDocument gherkinDocument = parser.parse(gherkin, matcher);
- List pickleEvents = compilePickles(gherkinDocument, featureResource);
- CucumberFeature feature = new CucumberFeature(gherkinDocument, URI.create(featureResource), gherkin, pickleEvents);
-
- return Collections.singletonList(feature);
- } catch (IOException e) {
- return Collections.EMPTY_LIST;
- }
- };
- final Runtime runtime = Runtime.builder()
- .withResourceLoader(resourceLoader)
- .withClassFinder(classFinder)
- .withClassLoader(classLoader)
- .withRuntimeOptions(options)
- .withAdditionalPlugins(cucumber4Jvm)
- .withFeatureSupplier(featureSupplier)
- .build();
-
- runtime.run();
- return runtime.exitStatus();
- }
-
- private static List compilePickles(GherkinDocument gherkinDocument, String resource) {
- if (gherkinDocument.getFeature() == null) {
- return Collections.emptyList();
- }
- List pickleEvents = new ArrayList<>();
- for (Pickle pickle : new Compiler().compile(gherkinDocument)) {
- pickleEvents.add(new PickleEvent(resource, pickle));
- }
- return pickleEvents;
- }
-
-
- private String readResource(final String resourceName) throws IOException {
- try (InputStream is = currentThread().getContextClassLoader().getResourceAsStream(resourceName)) {
- return IOUtils.toString(is, StandardCharsets.UTF_8);
- }
+ tuple("When ambigious step present", null),
+ tuple("Then something bad should happen", Status.SKIPPED)
+ );
}
+
+ @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE)
+ @SystemProperty(name = "allure.label.x-provided", value = "cucumberjvm5-test-provided")
+ @Test
+ void shouldSupportProvidedLabels() {
+ final AllureResults results = runFeature("features/simple.feature");
+
+ final List testResults = results.getTestResults();
+ assertThat(testResults)
+ .hasSize(1)
+ .flatExtracting(TestResult::getLabels)
+ .extracting(Label::getName, Label::getValue)
+ .contains(
+ tuple("x-provided", "cucumberjvm5-test-provided")
+ );
+ }
+
+ @Test
+ void shouldSupportRuntimeApiInStepsWhenHooksAreUsed() {
+ final AllureResults results = runFeature("features/runtimeapi.feature");
+
+ final List testResults = results.getTestResults();
+
+ assertThat(testResults)
+ .hasSize(1)
+ .flatExtracting(TestResult::getSteps)
+ .extracting(StepResult::getName)
+ .containsExactly(
+ "When step 1",
+ "When step 2",
+ "And step 3",
+ "Then step 4",
+ "And step 5"
+ );
+
+ assertThat(testResults)
+ .flatExtracting(TestResult::getLinks)
+ .extracting(Link::getName, Link::getUrl)
+ .containsExactly(
+ tuple("step1", "https://example.org/step1"),
+ tuple("step2", "https://example.org/step2"),
+ tuple("step3", "https://example.org/step3"),
+ tuple("step4", "https://example.org/step4"),
+ tuple("step5", "https://example.org/step5")
+ );
+ }
+
+ @SystemProperty(name = "cucumber.junit-platform.naming-strategy", value = "long")
+ @Step
+ private AllureResults runFeature(final String featureResource,
+ final String... moreOptions) {
+ return RunUtils.runTests(lifecycle -> {
+ final AllureCucumber4Jvm cucumber4Jvm = new AllureCucumber4Jvm(lifecycle);
+ final ClassLoader classLoader = currentThread().getContextClassLoader();
+ final ResourceLoader resourceLoader = new MultiLoader(classLoader);
+ final List opts = new ArrayList<>(Arrays.asList(
+ "--glue", "io.qameta.allure.cucumber4jvm.samples",
+ "--plugin", "null_summary"
+ ));
+ opts.addAll(Arrays.asList(moreOptions));
+ final FeatureWithLines featureWithLines = FeatureWithLines.parse("src/test/resources/" + featureResource);
+ final RuntimeOptions options = new CommandlineOptionsParser()
+ .parse(opts.toArray(new String[]{})).addFeature(featureWithLines).build();
+
+ final FeaturePathFeatureSupplier supplier
+ = new FeaturePathFeatureSupplier(new FeatureLoader(resourceLoader), options);
+
+ final Runtime runtime = Runtime.builder()
+ .withClassLoader(classLoader)
+ .withRuntimeOptions(options)
+ .withAdditionalPlugins(cucumber4Jvm)
+ .withFeatureSupplier(supplier)
+ .build();
+
+ runtime.run();
+ });
+ }
+
+
}
diff --git a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/AmbigiousSteps.java b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/AmbigiousSteps.java
index 3f76659a8..2b43b9ea8 100644
--- a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/AmbigiousSteps.java
+++ b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/AmbigiousSteps.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/AttachmentSteps.java b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/AttachmentSteps.java
index e72223093..c0308b3f3 100644
--- a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/AttachmentSteps.java
+++ b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/AttachmentSteps.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/BackgroundFeatureSteps.java b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/BackgroundFeatureSteps.java
index b2ff7f6e6..b39835696 100644
--- a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/BackgroundFeatureSteps.java
+++ b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/BackgroundFeatureSteps.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/BrokenFeatureSteps.java b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/BrokenFeatureSteps.java
index 7ff508ab6..53e0895c5 100644
--- a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/BrokenFeatureSteps.java
+++ b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/BrokenFeatureSteps.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/DatatableFeatureSteps.java b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/DatatableFeatureSteps.java
index d903702e6..4faa730ab 100644
--- a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/DatatableFeatureSteps.java
+++ b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/DatatableFeatureSteps.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/HookSteps.java b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/HookSteps.java
index 3a2f55c93..68aa59fd7 100644
--- a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/HookSteps.java
+++ b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/HookSteps.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@
import io.cucumber.java.After;
import io.cucumber.java.Before;
-import org.testng.Assert;
+import org.assertj.core.api.Assertions;
/**
* @author letsrokk (Dmitry Mayer).
@@ -36,12 +36,12 @@ public void afterHook(){
@Before("@BeforeHookWithException")
public void beforeHookWithException(){
- Assert.fail("Exception in Hook step");
+ Assertions.fail("Exception in Hook step");
}
@After("@AfterHookWithException")
public void afterHookWithException(){
- Assert.fail("Exception in Hook step");
+ Assertions.fail("Exception in Hook step");
}
@Before("@bp")
diff --git a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/PendingSteps.java b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/PendingSteps.java
index c1ab1b965..7d122c577 100644
--- a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/PendingSteps.java
+++ b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/PendingSteps.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/RuntimeApiSteps.java b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/RuntimeApiSteps.java
new file mode 100644
index 000000000..41bd4bfa6
--- /dev/null
+++ b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/RuntimeApiSteps.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2016-2024 Qameta Software Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.qameta.allure.cucumber4jvm.samples;
+
+import io.cucumber.java.Before;
+import io.cucumber.java.en.And;
+import io.cucumber.java.en.Then;
+import io.cucumber.java.en.When;
+import io.qameta.allure.Allure;
+
+/**
+ * @author charlie (Dmitry Baev).
+ */
+public class RuntimeApiSteps {
+
+ @Before("@beforeScenario")
+ public void beforeScenario(){
+ // nothing
+ }
+
+ @Before("@beforeFeature")
+ public void beforeFeature(){
+ // nothing
+ }
+
+ @When("^step 1$")
+ public void step1() {
+ Allure.step("step1 nested");
+ Allure.link("step1", "https://example.org/step1");
+ Allure.getLifecycle().getCurrentTestCase().ifPresent(uuid -> {
+ System.out.println("step1: " + uuid);
+ });
+ }
+
+ @When("^step 2$")
+ public void step2() {
+ Allure.step("step2 nested");
+ Allure.link("step2", "https://example.org/step2");
+ Allure.getLifecycle().getCurrentTestCase().ifPresent(uuid -> {
+ System.out.println("step2: " + uuid);
+ });
+ }
+
+ @And("^step 3$")
+ public void step3() {
+ Allure.step("step3 nested");
+ Allure.link("step3", "https://example.org/step3");
+ Allure.getLifecycle().getCurrentTestCase().ifPresent(uuid -> {
+ System.out.println("step3: " + uuid);
+ });
+ }
+
+ @Then("^step 4$")
+ public void step4() {
+ Allure.step("step4 nested");
+ Allure.link("step4", "https://example.org/step4");
+ Allure.getLifecycle().getCurrentTestCase().ifPresent(uuid -> {
+ System.out.println("step4: " + uuid);
+ });
+ }
+
+ @And("^step 5$")
+ public void step5() {
+ Allure.step("step5 nested");
+ Allure.link("step5", "https://example.org/step5");
+ Allure.getLifecycle().getCurrentTestCase().ifPresent(uuid -> {
+ System.out.println("step5: " + uuid);
+ });
+ }
+}
diff --git a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/SimpleFeatureSteps.java b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/SimpleFeatureSteps.java
index 7325cc855..f2baf281a 100644
--- a/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/SimpleFeatureSteps.java
+++ b/allure-cucumber4-jvm/src/test/java/io/qameta/allure/cucumber4jvm/samples/SimpleFeatureSteps.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,7 @@
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
-import org.testng.Assert;
+import org.assertj.core.api.Assertions;
/**
* @author charlie (Dmitry Baev).
@@ -46,7 +46,10 @@ public void i_add_a_to_b() {
@Then("^result is (\\d+)$")
public void result_is(int arg1) {
- Assert.assertEquals(this.c, arg1);
+ // use manual fail for more stable message format
+ if (this.c != arg1) {
+ Assertions.fail("expecting %d to be equal to %d", this.c, arg1);
+ }
}
}
diff --git a/allure-cucumber4-jvm/src/test/resources/features/runtimeapi.feature b/allure-cucumber4-jvm/src/test/resources/features/runtimeapi.feature
new file mode 100644
index 000000000..ec43eccb7
--- /dev/null
+++ b/allure-cucumber4-jvm/src/test/resources/features/runtimeapi.feature
@@ -0,0 +1,10 @@
+@beforeFeature
+Feature: Should support runtime API in all steps
+
+ @beforeScenario
+ Scenario: Scenario with Runtime API usage
+ When step 1
+ When step 2
+ And step 3
+ Then step 4
+ And step 5
diff --git a/allure-cucumber-jvm/src/test/resources/features/simple.feature b/allure-cucumber4-jvm/src/test/resources/features/same/simple.feature
similarity index 100%
rename from allure-cucumber-jvm/src/test/resources/features/simple.feature
rename to allure-cucumber4-jvm/src/test/resources/features/same/simple.feature
diff --git a/allure-cucumber5-jvm/build.gradle.kts b/allure-cucumber5-jvm/build.gradle.kts
index c74c9a200..4e0d44c9d 100644
--- a/allure-cucumber5-jvm/build.gradle.kts
+++ b/allure-cucumber5-jvm/build.gradle.kts
@@ -1,25 +1,23 @@
description = "Allure CucumberJVM 5.0"
-val agent: Configuration by configurations.creating
-
val cucumberVersion = "5.1.2"
val cucumberGherkinVersion = "5.1.0"
dependencies {
- agent("org.aspectj:aspectjweaver")
api(project(":allure-java-commons"))
compileOnly("io.cucumber:cucumber-plugin:$cucumberVersion")
- implementation("io.cucumber:gherkin:$cucumberGherkinVersion")
- testImplementation("io.cucumber:gherkin:$cucumberGherkinVersion")
+ compileOnly("io.cucumber:gherkin:$cucumberGherkinVersion")
+ testImplementation("commons-io:commons-io")
testImplementation("io.cucumber:cucumber-core:$cucumberVersion")
testImplementation("io.cucumber:cucumber-java:$cucumberVersion")
- testImplementation("io.cucumber:cucumber-testng:$cucumberVersion")
- testImplementation("commons-io:commons-io")
+ testImplementation("io.cucumber:gherkin:$cucumberGherkinVersion")
testImplementation("io.github.glytching:junit-extensions")
testImplementation("org.assertj:assertj-core")
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("org.slf4j:slf4j-simple")
+ testImplementation(project(":allure-assertj"))
testImplementation(project(":allure-java-commons-test"))
+ testImplementation(project(":allure-junit-platform"))
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
}
@@ -33,7 +31,4 @@ tasks.jar {
tasks.test {
useJUnitPlatform()
- doFirst {
- jvmArgs("-javaagent:${agent.singleFile}")
- }
}
diff --git a/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/AllureCucumber5Jvm.java b/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/AllureCucumber5Jvm.java
index 932e0df7a..7b7a0f0e9 100644
--- a/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/AllureCucumber5Jvm.java
+++ b/allure-cucumber5-jvm/src/main/java/io/qameta/allure/cucumber5jvm/AllureCucumber5Jvm.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Qameta Software OÜ
+ * Copyright 2016-2024 Qameta Software Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,47 +15,78 @@
*/
package io.qameta.allure.cucumber5jvm;
-
-import gherkin.ast.*;
-import io.qameta.allure.cucumber5jvm.testsourcemodel.TestSourcesModelProxy;
+import gherkin.ast.Examples;
+import gherkin.ast.Feature;
+import gherkin.ast.ScenarioDefinition;
+import gherkin.ast.ScenarioOutline;
+import gherkin.ast.TableRow;
import io.cucumber.plugin.ConcurrentEventListener;
-import io.cucumber.plugin.event.*;
+import io.cucumber.plugin.event.DataTableArgument;
+import io.cucumber.plugin.event.EmbedEvent;
+import io.cucumber.plugin.event.EventHandler;
+import io.cucumber.plugin.event.EventPublisher;
+import io.cucumber.plugin.event.HookTestStep;
+import io.cucumber.plugin.event.HookType;
+import io.cucumber.plugin.event.PickleStepTestStep;
+import io.cucumber.plugin.event.Result;
+import io.cucumber.plugin.event.Step;
+import io.cucumber.plugin.event.StepArgument;
+import io.cucumber.plugin.event.TestCase;
+import io.cucumber.plugin.event.TestCaseFinished;
+import io.cucumber.plugin.event.TestCaseStarted;
+import io.cucumber.plugin.event.TestSourceRead;
+import io.cucumber.plugin.event.TestStep;
+import io.cucumber.plugin.event.TestStepFinished;
+import io.cucumber.plugin.event.TestStepStarted;
+import io.cucumber.plugin.event.WriteEvent;
import io.qameta.allure.Allure;
import io.qameta.allure.AllureLifecycle;
-import io.qameta.allure.model.*;
+import io.qameta.allure.cucumber5jvm.testsourcemodel.TestSourcesModelProxy;
+import io.qameta.allure.model.FixtureResult;
+import io.qameta.allure.model.Parameter;
import io.qameta.allure.model.Status;
+import io.qameta.allure.model.StatusDetails;
+import io.qameta.allure.model.StepResult;
+import io.qameta.allure.model.TestResult;
+import io.qameta.allure.model.TestResultContainer;
import java.io.ByteArrayInputStream;
-import java.net.URI;
import java.nio.charset.StandardCharsets;
-import java.util.*;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
-import static io.qameta.allure.util.ResultsUtils.*;
+import static io.qameta.allure.util.ResultsUtils.createParameter;
+import static io.qameta.allure.util.ResultsUtils.getStatus;
+import static io.qameta.allure.util.ResultsUtils.getStatusDetails;
+import static io.qameta.allure.util.ResultsUtils.md5;
/**
* Allure plugin for Cucumber JVM 5.0.
*/
@SuppressWarnings({
- "PMD.ExcessiveImports",
- "ClassFanOutComplexity", "ClassDataAbstractionCoupling"
+ "ClassDataAbstractionCoupling",
+ "ClassFanOutComplexity",
+ "MultipleStringLiterals",
})
public class AllureCucumber5Jvm implements ConcurrentEventListener {
+ private static final String COLON = ":";
+
private final AllureLifecycle lifecycle;
- private final ConcurrentHashMap scenarioUuids = new ConcurrentHashMap<>();
private final TestSourcesModelProxy testSources = new TestSourcesModelProxy();
- private final ThreadLocal currentFeature = new InheritableThreadLocal<>();
- private final ThreadLocal currentFeatureFile = new InheritableThreadLocal<>();
- private final ThreadLocal currentTestCase = new InheritableThreadLocal<>();
- private final ThreadLocal currentContainer = new InheritableThreadLocal<>();
- private final ThreadLocal forbidTestCaseStatusChange = new InheritableThreadLocal<>();
-
private final EventHandler featureStartedHandler = this::handleFeatureStartedHandler;
private final EventHandler caseStartedHandler = this::handleTestCaseStarted;
private final EventHandler caseFinishedHandler = this::handleTestCaseFinished;
@@ -64,8 +95,13 @@ public class AllureCucumber5Jvm implements ConcurrentEventListener {
private final EventHandler writeEventHandler = this::handleWriteEvent;
private final EventHandler embedEventHandler = this::handleEmbedEvent;
+ private final Map hookStepContainerUuid = new ConcurrentHashMap<>();
+ private final Map stepUuids = new ConcurrentHashMap<>();
+ private final Map fixtureUuids = new ConcurrentHashMap<>();
+
private static final String TXT_EXTENSION = ".txt";
private static final String TEXT_PLAIN = "text/plain";
+ private static final String CUCUMBER_WORKING_DIR = Paths.get("").toUri().getSchemeSpecificPart();
@SuppressWarnings("unused")
public AllureCucumber5Jvm() {
@@ -76,9 +112,6 @@ public AllureCucumber5Jvm(final AllureLifecycle lifecycle) {
this.lifecycle = lifecycle;
}
- /*
- Event Handlers
- */
@Override
public void setEventPublisher(final EventPublisher publisher) {
publisher.registerHandlerFor(TestSourceRead.class, featureStartedHandler);
@@ -98,33 +131,43 @@ private void handleFeatureStartedHandler(final TestSourceRead event) {
}
private void handleTestCaseStarted(final TestCaseStarted event) {
- currentFeatureFile.set(event.getTestCase().getUri());
- currentFeature.set(testSources.getFeature(currentFeatureFile.get()));
- currentTestCase.set(event.getTestCase());
- currentContainer.set(UUID.randomUUID().toString());
- forbidTestCaseStatusChange.set(false);
+ final TestCase testCase = event.getTestCase();
+ final Feature feature = testSources.getFeature(testCase.getUri());
- final Deque