diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000..8af9297
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,167 @@
+name: .NET Reqnroll Automation Tests
+
+on:
+ push:
+ branches: [ "master" ]
+ workflow_dispatch:
+ inputs:
+ test_type:
+ description: "Select which tests to run"
+ required: true
+ default: "All"
+ type: choice
+ options: [ "UI", "API", "All" ]
+ browser:
+ description: "Select browser"
+ required: true
+ default: "Chrome"
+ type: choice
+ options: [ "Chrome", "Edge", "Both" ]
+
+jobs:
+ build-and-test:
+ runs-on: windows-latest
+
+ env:
+ DOTNET_VERSION: "8.0.x"
+ # Defaults for push (because push has no inputs)
+ TEST_TYPE: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.test_type || 'All' }}
+ BROWSER: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.browser || 'Chrome' }}
+ HEADLESS: "true"
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: ${{ env.DOTNET_VERSION }}
+
+ - name: dotnet --info
+ run: dotnet --info
+
+ - name: Restore
+ run: dotnet restore
+
+ - name: Build (Release)
+ run: dotnet build --configuration Release --no-restore
+
+ - name: Run tests (Release)
+ shell: pwsh
+ run: |
+ Write-Host "Running tests for: $env:TEST_TYPE"
+ Write-Host "Browser: $env:BROWSER"
+
+ # If your framework reads browser from ENV, this will work.
+ # If not, you can later map this to -- TestRunParameters or appsettings.
+ $env:BROWSER = "$env:BROWSER"
+
+ if ($env:TEST_TYPE -eq "UI") {
+ dotnet test .\UI_Automation\UI_Automation.csproj --configuration Release --no-build `
+ --logger "trx;LogFileName=ui-tests.trx"
+ }
+ elseif ($env:TEST_TYPE -eq "API") {
+ dotnet test .\API_Automation\API_Automation.csproj --configuration Release --no-build `
+ --logger "trx;LogFileName=api-tests.trx"
+ }
+ else {
+ dotnet test --configuration Release --no-build `
+ --logger "trx;LogFileName=all-tests.trx"
+ }
+
+ # Upload TRX files always (success or fail)
+ - name: Upload TRX test results
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: dotnet-trx-results
+ path: |
+ **/*.trx
+ if-no-files-found: warn
+
+ # Collect allure-results from anywhere into one folder
+ - name: Collect Allure results
+ if: always()
+ shell: pwsh
+ run: |
+ Write-Host "Collecting allure-results..."
+ if (Test-Path "allure-results") { Remove-Item "allure-results" -Recurse -Force }
+ New-Item -ItemType Directory -Path "allure-results" | Out-Null
+
+ $dirs = Get-ChildItem -Path . -Recurse -Directory -Filter "allure-results" |
+ Where-Object { $_.FullName -notmatch "\\allure-results$" }
+
+ Write-Host "Found allure-results dirs:"
+ $dirs | ForEach-Object { Write-Host $_.FullName }
+
+ foreach ($d in $dirs) {
+ Copy-Item -Path (Join-Path $d.FullName "*") -Destination "allure-results" -Recurse -Force -ErrorAction SilentlyContinue
+ }
+
+ if ((Get-ChildItem "allure-results" -ErrorAction SilentlyContinue | Measure-Object).Count -gt 0) {
+ "HAS_ALLURE_RESULTS=true" | Out-File -FilePath $env:GITHUB_ENV -Append
+ Write-Host "HAS_ALLURE_RESULTS=true"
+ } else {
+ "HAS_ALLURE_RESULTS=false" | Out-File -FilePath $env:GITHUB_ENV -Append
+ Write-Host "HAS_ALLURE_RESULTS=false"
+ }
+
+ # Generate Allure HTML report (no Docker) using Node + allure-commandline
+ - name: Setup Node
+ if: always()
+ uses: actions/setup-node@v4
+ with:
+ node-version: "20"
+
+ - name: Install Allure CLI
+ if: always()
+ run: npm i -g allure-commandline
+
+ - name: Download previous Allure history
+ if: always()
+ uses: dawidd6/action-download-artifact@v3
+ continue-on-error: true
+ with:
+ workflow: main.yml
+ name: allure-history
+ path: allure-results/history
+ if_no_artifact_found: warn
+
+
+ - name: Generate Allure HTML report
+ if: always()
+ shell: pwsh
+ run: |
+ if ($env:HAS_ALLURE_RESULTS -eq "true") {
+ if (Test-Path "allure-report") { Remove-Item "allure-report" -Recurse -Force }
+ allure generate allure-results -o allure-report --clean
+ Write-Host "Allure report generated."
+ }
+ else {
+ Write-Host "No allure-results found. Skipping report generation."
+ }
+
+ - name: Upload Allure HTML report
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: allure-html-report
+ path: allure-report/**
+ if-no-files-found: warn
+
+ - name: Upload Allure results
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: allure-results
+ path: allure-results/**
+ if-no-files-found: warn
+
+ - name: Save Allure history for next run
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: allure-history
+ path: allure-report/history
+ if-no-files-found: warn
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index e4d9ca5..707f647 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,26 +1,125 @@
-# Build folders
-bin/
-obj/
+##################################
+# .NET build artifacts
+##################################
+**/bin/
+**/obj/
+**/out/
+artifacts/
+publish/
-# Allure outputs
-allure-results/
-allure-report/
+##################################
+# Test results
+##################################
+**/TestResults/
+**/*.trx
+**/*.coverage
+**/*.coveragexml
+coverage.opencover.xml
+coverage.xml
+
+##################################
+# Allure
+##################################
+**/allure-results/
+**/allure-report/
+**/allure-history/
+
+##################################
+# Selenium / Playwright artifacts
+##################################
+**/Screenshots/
+**/screenshots/
+**/Videos/
+**/videos/
+**/Downloads/
+**/downloads/
+**/*.png
+**/*.jpg
+**/*.jpeg
+
+##################################
+# Logs
+##################################
+**/*.log
+**/logs/
-# Visual Studio
+##################################
+# Environment / secrets
+##################################
+**/.env
+**/.env.*
+**/appsettings.Local.json
+**/appsettings.*.Local.json
+**/secrets.json
+**/appsettings.Secret.json
+
+##################################
+# IDE / OS
+##################################
.vs/
+.idea/
+.vscode/
*.user
*.suo
-*.cache
+*.userprefs
+*.rsuser
+*.swp
+.DS_Store
+Thumbs.db
+
+##################################
+# NuGet
+##################################
*.nupkg
+.nuget/
+packages/
-# Test results
-TestResult*/
-*.trx
+##################################
+# Rider
+##################################
+.idea/
+*.sln.iml
-# OS junk
-Thumbs.db
-.DS_Store
+##################################
+# Resharper
+##################################
+_ReSharper*/
+*.DotSettings.user
-# Logs
+##################################
+# Node (ako koristiš npx / allure-commandline)
+##################################
+node_modules/
+npm-debug.log*
+yarn-error.log*
+package-lock.json
+
+##################################
+# CI / temp
+##################################
+**/temp/
+**/tmp/
+
+##################################
+# Misc build files
+##################################
+project.lock.json
+project.fragment.lock.json
+project.assets.json
*.log
-*.tmp
+*.vsix
+
+##################################
+# Coverage and profiling
+##################################
+*.coverage
+*.coveragexml
+coverage/
+
+##################################
+# Visual Studio Code workspace settings
+##################################
+.vscode/*
+!.vscode/extensions.json
+
+# End of file
diff --git a/API_Automation/API_Automation.csproj b/API_Automation/API_Automation.csproj
index 4b8c6cb..b224d24 100644
--- a/API_Automation/API_Automation.csproj
+++ b/API_Automation/API_Automation.csproj
@@ -2,21 +2,24 @@
net8.0
+ true
enable
enable
+
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
@@ -50,7 +53,7 @@
-
+
PreserveNewest
diff --git a/API_Automation/Client/RestApiClient.cs b/API_Automation/Client/RestApiClient.cs
index 10d4490..33afdf5 100644
--- a/API_Automation/Client/RestApiClient.cs
+++ b/API_Automation/Client/RestApiClient.cs
@@ -1,9 +1,9 @@
-using Newtonsoft.Json;
-using RestSharp;
-using API_Automation.Constants;
+using API_Automation.Constants;
using API_Automation.Helpers;
using API_Automation.Models.Response;
using API_Automation.Utils;
+using Newtonsoft.Json;
+using RestSharp;
namespace API_Automation.Client
{
@@ -13,14 +13,14 @@ public class RestApiClient : IApiClient
public RestApiClient(string baseUrl)
{
-
var timeoutSeconds = ConfigReader.GetTimeout();
var options = new RestClientOptions(baseUrl)
{
ThrowOnAnyError = false,
- MaxTimeout = timeoutSeconds * 1000 // Convert seconds to milliseconds
+ Timeout = TimeSpan.FromSeconds(timeoutSeconds)
};
+
_client = new RestClient(options);
}
diff --git a/API_Automation/Features/ReplaceABook.feature b/API_Automation/Features/ReplaceABook.feature
index 6382801..5e454c1 100644
--- a/API_Automation/Features/ReplaceABook.feature
+++ b/API_Automation/Features/ReplaceABook.feature
@@ -1,6 +1,10 @@
-@api
+@api @smoke
Feature: Replace a book via Bookstore API
+ As a registered user
+ I want to search for available books and manage my book list
+ So that I can replace a book in my collection with another one using the Bookstore API
+
Scenario: Verify that a user can replace a book
Given A user is created and authorized
When I get all books
diff --git a/API_Automation/Features/ReplaceABook.feature.cs b/API_Automation/Features/ReplaceABook.feature.cs
index f8ede95..5645ac0 100644
--- a/API_Automation/Features/ReplaceABook.feature.cs
+++ b/API_Automation/Features/ReplaceABook.feature.cs
@@ -21,15 +21,19 @@ namespace API_Automation.Features
[global::NUnit.Framework.DescriptionAttribute("Replace a book via Bookstore API")]
[global::NUnit.Framework.FixtureLifeCycleAttribute(global::NUnit.Framework.LifeCycle.InstancePerTestCase)]
[global::NUnit.Framework.CategoryAttribute("api")]
+ [global::NUnit.Framework.CategoryAttribute("smoke")]
public partial class ReplaceABookViaBookstoreAPIFeature
{
private global::Reqnroll.ITestRunner testRunner;
private static string[] featureTags = new string[] {
- "api"};
+ "api",
+ "smoke"};
- private static global::Reqnroll.FeatureInfo featureInfo = new global::Reqnroll.FeatureInfo(new global::System.Globalization.CultureInfo("en-US"), "Features", "Replace a book via Bookstore API", null, global::Reqnroll.ProgrammingLanguage.CSharp, featureTags, InitializeCucumberMessages());
+ private static global::Reqnroll.FeatureInfo featureInfo = new global::Reqnroll.FeatureInfo(new global::System.Globalization.CultureInfo("en-US"), "Features", "Replace a book via Bookstore API", " As a registered user\r\n I want to search for available books and manage my book" +
+ " list\r\n So that I can replace a book in my collection with another one using th" +
+ "e Bookstore API", global::Reqnroll.ProgrammingLanguage.CSharp, featureTags, InitializeCucumberMessages());
#line 1 "ReplaceABook.feature"
#line hidden
@@ -120,7 +124,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa
global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("Verify that a user can replace a book", null, tagsOfScenario, argumentsOfScenario, featureTags, pickleIndex);
string[] tagsOfRule = ((string[])(null));
global::Reqnroll.RuleInfo ruleInfo = null;
-#line 4
+#line 8
this.ScenarioInitialize(scenarioInfo, ruleInfo);
#line hidden
if ((global::Reqnroll.TagHelper.ContainsIgnoreTag(scenarioInfo.CombinedTags) || global::Reqnroll.TagHelper.ContainsIgnoreTag(featureTags)))
@@ -130,22 +134,22 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa
else
{
await this.ScenarioStartAsync();
-#line 5
+#line 9
await testRunner.GivenAsync("A user is created and authorized", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given ");
#line hidden
-#line 6
+#line 10
await testRunner.WhenAsync("I get all books", ((string)(null)), ((global::Reqnroll.Table)(null)), "When ");
#line hidden
-#line 7
+#line 11
await testRunner.AndAsync("I add the first book to user\'s list", ((string)(null)), ((global::Reqnroll.Table)(null)), "And ");
#line hidden
-#line 8
+#line 12
await testRunner.ThenAsync("User has only one book and it matches the added one", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then ");
#line hidden
-#line 9
+#line 13
await testRunner.WhenAsync("I replace the book with the second one", ((string)(null)), ((global::Reqnroll.Table)(null)), "When ");
#line hidden
-#line 10
+#line 14
await testRunner.ThenAsync("The user\'s book list contains only the replaced book", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then ");
#line hidden
}
diff --git a/API_Automation/Helpers/Logger.cs b/API_Automation/Helpers/Logger.cs
index 7c27901..e1362b4 100644
--- a/API_Automation/Helpers/Logger.cs
+++ b/API_Automation/Helpers/Logger.cs
@@ -23,7 +23,7 @@ public static void LogError(string message, Exception ex = null)
}
}
-
+
public static void LogWarning(string message)
{
TestContext.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] WARNING: {message}");
diff --git a/API_Automation/ImplicitUsings.cs b/API_Automation/ImplicitUsings.cs
index 77e491f..c5abf5a 100644
--- a/API_Automation/ImplicitUsings.cs
+++ b/API_Automation/ImplicitUsings.cs
@@ -1,2 +1 @@
-global using NUnit;
global using Reqnroll;
diff --git a/API_Automation/Models/Response/BooksGetResponse.cs b/API_Automation/Models/Response/BooksGetResponse.cs
index 69b750a..0a9c915 100644
--- a/API_Automation/Models/Response/BooksGetResponse.cs
+++ b/API_Automation/Models/Response/BooksGetResponse.cs
@@ -15,26 +15,8 @@ public class Book
[JsonProperty("title")]
public string Title { get; set; }
- [JsonProperty("subTitle")]
- public string SubTitle { get; set; }
-
[JsonProperty("author")]
public string Author { get; set; }
-
- [JsonProperty("publish_date")]
- public string PublishDate { get; set; }
-
- [JsonProperty("publisher")]
- public string Publisher { get; set; }
-
- [JsonProperty("pages")]
- public int Pages { get; set; }
-
- [JsonProperty("description")]
- public string Description { get; set; }
-
- [JsonProperty("website")]
- public string Website { get; set; }
}
}
}
diff --git a/API_Automation/Models/Response/BooksPostResponse.cs b/API_Automation/Models/Response/BooksPostResponse.cs
deleted file mode 100644
index 8aed027..0000000
--- a/API_Automation/Models/Response/BooksPostResponse.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using Newtonsoft.Json;
-
-namespace API_Automation.Models.Response
-{
- public class BooksPostResponse
- {
- [JsonProperty("books")]
- public List Books { get; set; }
-
- public class Book
- {
- [JsonProperty("isbn")]
- public string Isbn { get; set; }
- }
- }
-}
diff --git a/API_Automation/Models/Response/CreateUserResponse.cs b/API_Automation/Models/Response/CreateUserResponse.cs
index a909132..3d315d3 100644
--- a/API_Automation/Models/Response/CreateUserResponse.cs
+++ b/API_Automation/Models/Response/CreateUserResponse.cs
@@ -9,9 +9,5 @@ public class CreateUserResponse
[JsonProperty("username")]
public string Username { get; set; }
-
- [JsonProperty("books")]
- public List