diff --git a/.github/workflows/Allure_Report.yml b/.github/workflows/Allure_Report.yml new file mode 100644 index 0000000..044edea --- /dev/null +++ b/.github/workflows/Allure_Report.yml @@ -0,0 +1,49 @@ +name: allure-report +on: + workflow_call: + inputs: + artifact_name: + required: true + type: string + workflow: + required: true + type: string + + +jobs: + allure: + name: Generate Allure Report + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Get Allure history + uses: actions/checkout@v2 + if: always() + continue-on-error: true + with: + ref: gh-pages + path: gh-pages + + - uses: LexisNexis-Public-GHA/sbs-action-download-artifact@v2.13.1 + with: + workflow: | + ${{ inputs.workflow }} + name: | + ${{ inputs.artifact_name }} + + - name: Allure Report action from marketplace + uses: simple-elf/allure-report-action@master + if: always() + with: + allure_results: allure-results + allure_history: allure-history + keep_reports: 20 + + - name: Deploy report to Github Pages + if: always() + uses: peaceiris/actions-gh-pages@v2 + env: + PERSONAL_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PUBLISH_BRANCH: gh-pages + PUBLISH_DIR: allure-history \ No newline at end of file diff --git a/.github/workflows/Run_web_tests.yml b/.github/workflows/Run_web_tests.yml index 76e4565..4876ad1 100644 --- a/.github/workflows/Run_web_tests.yml +++ b/.github/workflows/Run_web_tests.yml @@ -1,84 +1,95 @@ -name: Run-web-tests - -on: - workflow_call: - inputs: - driver_type: - required: true - type: string - test_results_path: - required: true - type: string - environment: - required: true - type: string -jobs: - web-tests: - strategy: - matrix: - os: ${{fromJson(inputs.environment)}} - browser: ${{fromJson(inputs.driver_type)}} - fail-fast: false - - name: Run WEB (${{ matrix.browser }}) tests - ${{ matrix.os }} - runs-on: ${{ matrix.os }} - - steps: - - name: Setup .NET - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 6.0.x - - uses: LexisNexis-Public-GHA/sbs-action-download-artifact@v2.13.1 - with: - workflow: CI.yml - name: testing-artifact-selenium-web - - name: Run git init - run: git init - - name: Get Driver path by browser - run: | - switch ( "${{ matrix.browser }}".ToLower() ) - { - "chrome" { $driverPathRaw = "$env:CHROMEWEBDRIVER" } - "firefox" { $driverPathRaw = "$env:GeckoWebDriver" } - "edge" { $driverPathRaw = "$env:EdgeWebDriver" } - "internetexplorer" { $driverPathRaw = "$env:IEWebDriver" } - } - echo "driverPathRaw=$driverPathRaw" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf-8 -Append - shell: pwsh - - name: config replacement - uses: microsoft/variable-substitution@v1 - with: - files: '.\TestConfiguration.Web.json' - env: - Configurations.0.Capabilities.0.Path: ${{ env.driverPathRaw }} - Configurations.0.Capabilities.0.Driver: ${{ matrix.browser }} - Configurations.1.Capabilities.0.Driver: ${{ matrix.browser }} - Configurations.1.Capabilities.0.Path: ${{ env.driverPathRaw }} - Configurations.1.Capabilities.1.Driver: ${{ matrix.browser }} - Configurations.1.Capabilities.1.Path: ${{ env.driverPathRaw }} - Configurations.1.Capabilities.2.Driver: ${{ matrix.browser }} - Configurations.1.Capabilities.2.Path: ${{ env.driverPathRaw }} - TestResultPath: ${{ inputs.test_results_path }} - - name: Read configuration - id: config_file - uses: juliangruber/read-file-action@v1 - with: - path: '.\TestConfiguration.Web.json' - - name: Echo configuration.json - run: echo "${{ steps.config_file.outputs.content }}" - - name: Run tests - run: dotnet test TestWare.Samples.Selenium.Web.dll --logger "trx;LogFileName=results.trx" --results-directory "${{ inputs.test_results_path }}" - - name: Archive WEB (${{ matrix.browser }}) screenshots - if: always() - uses: actions/upload-artifact@v2 - with: - name: web-${{ matrix.browser }}-screenshots - path: | - ${{ inputs.test_results_path }} - - name: Test Report - uses: dorny/test-reporter@v1 - if: success() || failure() - with: - name: Report - WEB (${{ matrix.browser }} - ${{ matrix.os }}) - path: ${{ inputs.test_results_path }}/results.trx - reporter: dotnet-trx +name: Run-web-tests + +on: + workflow_call: + inputs: + driver_type: + required: true + type: string + test_results_path: + required: true + type: string + environment: + required: true + type: string +jobs: + web-tests: + strategy: + matrix: + os: ${{fromJson(inputs.environment)}} + browser: ${{fromJson(inputs.driver_type)}} + fail-fast: false + + name: Run WEB (${{ matrix.browser }}) tests - ${{ matrix.os }} + runs-on: ${{ matrix.os }} + + steps: + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 6.0.x + - uses: LexisNexis-Public-GHA/sbs-action-download-artifact@v2.13.1 + with: + workflow: CI.yml + name: testing-artifact-selenium-web + - name: Run git init + run: git init + - name: Get Driver path by browser + run: | + switch ( "${{ matrix.browser }}".ToLower() ) + { + "chrome" { $driverPathRaw = "$env:CHROMEWEBDRIVER" } + "firefox" { $driverPathRaw = "$env:GeckoWebDriver" } + "edge" { $driverPathRaw = "$env:EdgeWebDriver" } + "internetexplorer" { $driverPathRaw = "$env:IEWebDriver" } + } + echo "driverPathRaw=$driverPathRaw" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf-8 -Append + shell: pwsh + - name: config replacement + uses: microsoft/variable-substitution@v1 + with: + files: '.\TestConfiguration.Web.json' + env: + Configurations.0.Capabilities.0.Path: ${{ env.driverPathRaw }} + Configurations.0.Capabilities.0.Driver: ${{ matrix.browser }} + Configurations.1.Capabilities.0.Driver: ${{ matrix.browser }} + Configurations.1.Capabilities.0.Path: ${{ env.driverPathRaw }} + Configurations.1.Capabilities.1.Driver: ${{ matrix.browser }} + Configurations.1.Capabilities.1.Path: ${{ env.driverPathRaw }} + Configurations.1.Capabilities.2.Driver: ${{ matrix.browser }} + Configurations.1.Capabilities.2.Path: ${{ env.driverPathRaw }} + TestResultPath: ${{ inputs.test_results_path }} + - name: Read configuration + id: config_file + uses: juliangruber/read-file-action@v1 + with: + path: '.\TestConfiguration.Web.json' + - name: Echo configuration.json + run: echo "${{ steps.config_file.outputs.content }}" + - name: Run tests + run: dotnet test TestWare.Samples.Selenium.Web.dll --logger "trx;LogFileName=results.trx" --results-directory "${{ inputs.test_results_path }}" + - name: Archive WEB (${{ matrix.browser }}) screenshots + if: always() + uses: actions/upload-artifact@v2 + with: + name: web-${{ matrix.browser }}-screenshots + path: | + ${{ inputs.test_results_path }} + - name: Archive Testing artifacts - Allure + uses: actions/upload-artifact@v2 + with: + name: allure-results-web + path: '.\allure-results' + - name: Test Report + uses: dorny/test-reporter@v1 + if: success() || failure() + with: + name: Report - WEB (${{ matrix.browser }} - ${{ matrix.os }}) + path: ${{ inputs.test_results_path }}/results.trx + reporter: dotnet-trx + + allure-results: + uses: ./.github/workflows/Allure_Report.yml + with: + artifact_name: 'allure-results-web' + workflow: 'Run_web_tests.yml' diff --git a/TestWare.sln b/TestWare.sln index 3c7ff8c..6877c08 100644 --- a/TestWare.sln +++ b/TestWare.sln @@ -38,6 +38,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestWare.ExtentReport", "sr EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Reporting", "Reporting", "{DCCEF363-0EBE-46EA-B02B-CD59010626F6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestWare.AllureReport", "src\Core\TestWare.AllureReport\TestWare.AllureReport.csproj", "{91F2B723-A68B-4711-969B-89897B3C6161}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -84,6 +86,10 @@ Global {94025C6B-DF0D-4AC4-BBC5-A94A9FA3AB0A}.Debug|Any CPU.Build.0 = Debug|Any CPU {94025C6B-DF0D-4AC4-BBC5-A94A9FA3AB0A}.Release|Any CPU.ActiveCfg = Release|Any CPU {94025C6B-DF0D-4AC4-BBC5-A94A9FA3AB0A}.Release|Any CPU.Build.0 = Release|Any CPU + {91F2B723-A68B-4711-969B-89897B3C6161}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {91F2B723-A68B-4711-969B-89897B3C6161}.Debug|Any CPU.Build.0 = Debug|Any CPU + {91F2B723-A68B-4711-969B-89897B3C6161}.Release|Any CPU.ActiveCfg = Release|Any CPU + {91F2B723-A68B-4711-969B-89897B3C6161}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -102,6 +108,7 @@ Global {71120454-DD7A-484C-93F2-699B4C363297} = {58B1446D-98A3-46A2-A668-305F0170B39F} {94025C6B-DF0D-4AC4-BBC5-A94A9FA3AB0A} = {DCCEF363-0EBE-46EA-B02B-CD59010626F6} {DCCEF363-0EBE-46EA-B02B-CD59010626F6} = {4678C707-68DB-4E06-9184-A07FB398832C} + {91F2B723-A68B-4711-969B-89897B3C6161} = {DCCEF363-0EBE-46EA-B02B-CD59010626F6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4F88CB24-23C8-4E0E-8B83-29023C1642B8} diff --git a/samples/TestWare.Samples.Selenium.Web/Features/SwagLabs/Login.feature b/samples/TestWare.Samples.Selenium.Web/Features/SwagLabs/Login.feature index 70cf142..d4bf237 100644 --- a/samples/TestWare.Samples.Selenium.Web/Features/SwagLabs/Login.feature +++ b/samples/TestWare.Samples.Selenium.Web/Features/SwagLabs/Login.feature @@ -1,6 +1,7 @@ @WebDriver Feature: Login +@critical Scenario Outline: Login Given the user enters username '' And the user enters password '' @@ -12,6 +13,9 @@ Scenario Outline: Login | standard | standard_user | secret_sauce | | problem | problem_user | secret_sauce | +@blocker +@tms:5 +@issue:3 Scenario: Logout Given user 'standard_user' is logged with 'secret_sauce' into SwagLabs When the user clicks Logout button diff --git a/samples/TestWare.Samples.Selenium.Web/Hook.cs b/samples/TestWare.Samples.Selenium.Web/Hook.cs index a325de5..2ab590a 100644 --- a/samples/TestWare.Samples.Selenium.Web/Hook.cs +++ b/samples/TestWare.Samples.Selenium.Web/Hook.cs @@ -1,5 +1,6 @@ using System.Globalization; using TestWare.Reporting.ExtentReport; +using TestWare.Reporting.AllureReport; namespace TestWare.Samples.Selenium.Web; @@ -9,7 +10,8 @@ public sealed class Hook private readonly TestContext _testContext; private int _stepCounter; private static readonly LifeCycle _lifeCycle = new(); - private static ExtentReport _testReport; + private static ExtentReport _extentReport; + private static AllureReport _allureReport; public Hook(TestContext testContext) { @@ -23,7 +25,7 @@ public static void BeforeFeature(FeatureContext featureContext) var tags = featureContext.FeatureInfo.Tags; _lifeCycle.BeginTestSuite(name); - _testReport.CreateFeature(name, tags); + _extentReport.CreateFeature(name, tags); } [AfterFeature] @@ -41,7 +43,7 @@ public void BeforeScenario(FeatureContext featureContext, ScenarioContext scenar var description = scenarioContext.ScenarioInfo.Description ?? ""; var scenarioTags = scenarioContext.ScenarioInfo.Tags; - _testReport.CreateTestCase(name, description, scenarioTags); + _extentReport.CreateTestCase(name, description, scenarioTags); _testContext.WriteLine("----------------------------------------- \r\n"); _testContext.WriteLine($"Feature: {featureContext.FeatureInfo.Title}"); @@ -55,7 +57,7 @@ public void BeforeScenario(FeatureContext featureContext, ScenarioContext scenar [AfterScenario] public void AfterScenario() { - _testReport.SetTestcaseOutcome(_testContext.CurrentTestOutcome); + _extentReport.SetTestcaseOutcome(_testContext.CurrentTestOutcome); _lifeCycle.EndTestCase(); } @@ -63,14 +65,19 @@ public void AfterScenario() public static void BeforeTestRun() { _lifeCycle.BeginTestExecution(); - _testReport = new ExtentReport(_lifeCycle.GetCurrentResultsDirectory()); + _extentReport = new ExtentReport(_lifeCycle.GetCurrentResultsDirectory()); + + var allureConfiguration = _lifeCycle.TestConfiguration.Configurations.First(x=>x.Tag == "AllureConfiguration"); + _allureReport = new AllureReport(allureConfiguration.Capabilities.FirstOrDefault()); + _allureReport.CleanResultsFolder(); + _allureReport.GenerateAllureEnvironmentFile(); } [AfterTestRun] public static void AfterTestRun() { _lifeCycle.EndTestExecution(); - _testReport.CreateTestReportFile(); + _extentReport.CreateTestReportFile(); } [BeforeStep] @@ -78,7 +85,7 @@ public void BeforeStep(ScenarioContext scenarioContext) { var name = scenarioContext.CurrentScenarioBlock.ToString(); var description = scenarioContext.StepContext.StepInfo.Text; - _testReport.CreateStep(name, description); + _extentReport.CreateStep(name, description); var stepId = $"{_stepCounter:00} {description}"; _stepCounter++; @@ -93,7 +100,8 @@ public void AfterStep(ScenarioContext scenarioContext) foreach (var evidence in evidencesPath) { - _testReport.AddScreenshotToStep(evidence); + _extentReport.AddScreenshotToStep(evidence); + _allureReport.AddAttachment(evidence, Path.GetFileNameWithoutExtension(evidence)); _testContext.AddResultFile(evidence); } } diff --git a/samples/TestWare.Samples.Selenium.Web/TestConfiguration.Web.json b/samples/TestWare.Samples.Selenium.Web/TestConfiguration.Web.json index a6053a2..26be19d 100644 --- a/samples/TestWare.Samples.Selenium.Web/TestConfiguration.Web.json +++ b/samples/TestWare.Samples.Selenium.Web/TestConfiguration.Web.json @@ -83,6 +83,27 @@ ] } ] + }, + { + "Tag": "AllureConfiguration", + "Capabilities": [ + { + "IssueTrackerBaseUrl": "https://www.myissuetracker.com/", + "IssueTagRegex": "^issue:(\\d+)", + "TestManagementSystemBaseUrl": "https://www.mytms.com/", + "TestManagementSystemTagRegex": "^tms:(\\d+)", + "EnvironmentValues": [ + { + "key": "System", + "value": "Windows" + }, + { + "key": "WebDriver", + "value": "Chrome" + } + ] + } + ] } ], "TestResultPath": "C:\\workspace\\ERNI\\results\\" diff --git a/samples/TestWare.Samples.Selenium.Web/TestWare.Samples.Selenium.Web.csproj b/samples/TestWare.Samples.Selenium.Web/TestWare.Samples.Selenium.Web.csproj index 07ae10b..de40c02 100644 --- a/samples/TestWare.Samples.Selenium.Web/TestWare.Samples.Selenium.Web.csproj +++ b/samples/TestWare.Samples.Selenium.Web/TestWare.Samples.Selenium.Web.csproj @@ -29,6 +29,7 @@ + @@ -40,6 +41,9 @@ + + Always + Always diff --git a/samples/TestWare.Samples.Selenium.Web/specflow.json b/samples/TestWare.Samples.Selenium.Web/specflow.json new file mode 100644 index 0000000..e6e9e06 --- /dev/null +++ b/samples/TestWare.Samples.Selenium.Web/specflow.json @@ -0,0 +1,5 @@ +{ + "stepAssemblies": [ + { "assembly": "Allure.SpecFlowPlugin" } + ] +} diff --git a/src/Core/TestWare.AllureReport/AllureReport.cs b/src/Core/TestWare.AllureReport/AllureReport.cs new file mode 100644 index 0000000..6828ef3 --- /dev/null +++ b/src/Core/TestWare.AllureReport/AllureReport.cs @@ -0,0 +1,70 @@ +using Allure.Commons; +using Newtonsoft.Json; +using System.Text; +using System.Text.Json.Nodes; +using System.Text.RegularExpressions; + +namespace TestWare.Reporting.AllureReport; + +public class AllureReport +{ + private const string AllureConfigFile = "allureConfig.json"; + private const string AllureEnvironmentFile = "environment.properties"; + private const string AllureResultsFolder = "allure-results"; + + private readonly Capabilities AllureCapabilities; + + public AllureReport(JsonObject capabilities) + { + + AllureCapabilities = JsonConvert.DeserializeObject(capabilities.ToString()) ?? throw new ArgumentNullException(nameof(capabilities)); + + SetIssueTrackerBaseUrl(AllureCapabilities.IssueTrackerBaseUrl ?? throw new ArgumentNullException(nameof(AllureCapabilities.IssueTrackerBaseUrl))); + SetTestManagementSystemBaseUrl(AllureCapabilities.TestManagementSystemBaseUrl ?? throw new ArgumentNullException(nameof(AllureCapabilities.TestManagementSystemBaseUrl))); + } + + public void AddAttachment(string path, string attachmentTitle) + { + AllureLifecycle.Instance.AddAttachment(path, attachmentTitle); + } + + public void CleanResultsFolder() + { + AllureLifecycle.Instance.CleanupResultDirectory(); + } + + public void GenerateAllureEnvironmentFile() + { + using var sw = new StreamWriter(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AllureResultsFolder, AllureEnvironmentFile), true); + foreach (var keyValuePair in AllureCapabilities.EnvironmentValues) + { + sw.WriteLine("{0}={1}", keyValuePair.Key, keyValuePair.Value); + } + } + + private void SetIssueTrackerBaseUrl(string issueTrackerBaseUrl) + { + string text = ReadAllureConfigFile(); + text = Regex.Replace(text, @".*{issue}.*", '"' + issueTrackerBaseUrl + "{issue}"); + WriteAllureConfigFile(text); + } + + private void SetTestManagementSystemBaseUrl(string testManagementSystemBaseUrl) + { + string text = ReadAllureConfigFile(); + text = Regex.Replace(text, @".*{tms}.*", '"' + testManagementSystemBaseUrl + "{tms}"); + WriteAllureConfigFile(text); + } + + private string ReadAllureConfigFile() + { + var allureConfigPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AllureConfigFile); + return File.ReadAllText(allureConfigPath, Encoding.UTF8); + } + + private void WriteAllureConfigFile(string text) + { + var allureConfigPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AllureConfigFile); + File.WriteAllText(allureConfigPath, text); + } +} diff --git a/src/Core/TestWare.AllureReport/Configuration/Capabilities.cs b/src/Core/TestWare.AllureReport/Configuration/Capabilities.cs new file mode 100644 index 0000000..5809d1e --- /dev/null +++ b/src/Core/TestWare.AllureReport/Configuration/Capabilities.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; + +namespace TestWare.Reporting.AllureReport; + +internal class Capabilities +{ + public string? IssueTrackerBaseUrl { get; set; } + + public string? IssueTagRegex { get; set; } + + public string? TestManagementSystemBaseUrl { get; set; } + + public string? TestManagementSystemTagRegex { get; set; } + + public IEnumerable EnvironmentValues { get; set; } = Enumerable.Empty(); +} + +public partial class EnvironmentValue +{ + public string? Key { get; set; } + + public string? Value { get; set; } +} + diff --git a/src/Core/TestWare.AllureReport/TestWare.AllureReport.csproj b/src/Core/TestWare.AllureReport/TestWare.AllureReport.csproj new file mode 100644 index 0000000..0b5667c --- /dev/null +++ b/src/Core/TestWare.AllureReport/TestWare.AllureReport.csproj @@ -0,0 +1,23 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + Always + + + + diff --git a/src/Core/TestWare.AllureReport/allureConfig.json b/src/Core/TestWare.AllureReport/allureConfig.json new file mode 100644 index 0000000..43704b8 --- /dev/null +++ b/src/Core/TestWare.AllureReport/allureConfig.json @@ -0,0 +1,41 @@ +{ + "allure": { + "directory": "allure-results", + "links": [ + "{link}", + "{issue}", + "{tms}" + ] + }, + "specflow": { + "stepArguments": { + "convertToParameters": "true", + "paramNameRegex": "", + "paramValueRegex": "" + }, + "grouping": { + "suites": { + "parentSuite": "^parentSuite:?(.+)", + "suite": "^suite:?(.+)", + "subSuite": "^subSuite:?(.+)" + }, + "behaviors": { + "epic": "^epic:?(.+)", + "story": "^story:?(.+)" + }, + "packages": { + "package": "^package:?(.+)", + "testClass": "^class:?(.+)", + "testMethod": "^method:?(.+)" + } + }, + "labels": { + "owner": "^owner:?(.+)", + "severity": "^(normal|blocker|critical|minor|trivial)" + }, + "links": { + "issue": "^issue:(\\d+)", + "tms": "^tms:(\\d+)" + } + } +}