From 694478953933d06409fa40f84b56da547e4fc2ac Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Sun, 26 Oct 2025 15:50:22 +0100 Subject: [PATCH 01/42] Add GitHub Actions workflow for .NET Reqnroll tests --- API_Automation/Features/ReplaceABook.feature | 2 +- Csharp_Automation_Task.slnx | 4 + .../Features/SearchAndNavigate.feature | 2 +- UI_Automation/appsettings.json | 2 +- automation-tests.yml | 80 +++++++++++++++++++ 5 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 automation-tests.yml diff --git a/API_Automation/Features/ReplaceABook.feature b/API_Automation/Features/ReplaceABook.feature index 6382801..365050f 100644 --- a/API_Automation/Features/ReplaceABook.feature +++ b/API_Automation/Features/ReplaceABook.feature @@ -1,4 +1,4 @@ -ο»Ώ@api +ο»Ώ@api @smoke Feature: Replace a book via Bookstore API Scenario: Verify that a user can replace a book diff --git a/Csharp_Automation_Task.slnx b/Csharp_Automation_Task.slnx index e3a2049..90c3900 100644 --- a/Csharp_Automation_Task.slnx +++ b/Csharp_Automation_Task.slnx @@ -1,4 +1,8 @@ + + + + diff --git a/UI_Automation/Features/SearchAndNavigate.feature b/UI_Automation/Features/SearchAndNavigate.feature index 28782a6..a9ca316 100644 --- a/UI_Automation/Features/SearchAndNavigate.feature +++ b/UI_Automation/Features/SearchAndNavigate.feature @@ -4,7 +4,7 @@ I want to search for game and navigate to the official Steam About page So that I can play games on the Steam platform -@regression +@ui @regression Scenario: Search for Steam game and navigate to the About page Given I open Store page When I search for "FIFA" game diff --git a/UI_Automation/appsettings.json b/UI_Automation/appsettings.json index ce4e29b..eed76b2 100644 --- a/UI_Automation/appsettings.json +++ b/UI_Automation/appsettings.json @@ -1,5 +1,5 @@ ο»Ώ{ - "Headless": false, + "Headless": true, "Incognito": true, "Browser": "Chrome", "BaseUrl": "https://store.steampowered.com" diff --git a/automation-tests.yml b/automation-tests.yml new file mode 100644 index 0000000..c0e4759 --- /dev/null +++ b/automation-tests.yml @@ -0,0 +1,80 @@ +name: Automation Test Pipeline (.NET + Reqnroll + Allure) + +on: + workflow_dispatch: + inputs: + test_type: + description: 'Select which tests to run' + required: true + type: choice + options: + - UI + - API + - All + browser: + description: 'Select browser' + required: true + type: choice + options: + - Chrome + - Edge + - Both + +jobs: + build-and-test: + runs-on: windows-latest + env: + DOTNET_VERSION: 8.0.x + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Restore dependencies + run: dotnet restore Csharp_Automation_Task.sln + + - name: Build solution + run: dotnet build Csharp_Automation_Task.sln --configuration Release --no-restore + + # ========================== + # Run UI / API / All tests + # ========================== + - name: Run Tests + run: | + if ("${{ github.event.inputs.test_type }}" -eq "UI") { + dotnet test UI_Automation/UI_Automation.csproj --configuration Release --filter "ReqnrollTags=@ui" --logger "trx;LogFileName=ui-results.trx" + } + elseif ("${{ github.event.inputs.test_type }}" -eq "API") { + dotnet test API_Automation/API_Automation.csproj --configuration Release --filter "ReqnrollTags=@api" --logger "trx;LogFileName=api-results.trx" + } + else { + dotnet test Csharp_Automation_Task.sln --configuration Release --logger "trx;LogFileName=all-results.trx" + } + + # ========================== + # Run Allure Report + # ========================== + - name: Generate Allure Results + if: always() + run: | + echo "Allure results generated in allure-results/" + dir allure-results || echo "No allure results folder found" + + - name: Upload Allure Results + if: always() + uses: actions/upload-artifact@v4 + with: + name: allure-results + path: allure-results/ + + - name: Upload TRX Results + if: always() + uses: actions/upload-artifact@v4 + with: + name: dotnet-test-results + path: '**/*.trx' From fbd93742423c330affea38f4934713ac75e4bbe3 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Sun, 26 Oct 2025 16:06:52 +0100 Subject: [PATCH 02/42] Add GitHub Actions workflow for .NET testing --- .github/workflows/main.yml | 77 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..c703985 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,77 @@ +name: .NET Reqnroll Automation Tests + +on: + workflow_dispatch: + inputs: + test_type: + description: "Select which tests to run" + required: true + default: "All" + type: choice + options: + - API + - UI + - All + browser: + description: "Select browser" + required: true + default: "Chrome" + type: choice + options: + - Chrome + - Edge + - Both + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 9.0.x + + - name: Restore dependencies + run: dotnet restore + + - name: Build solution + run: dotnet build --no-restore --configuration Release + + - name: Run tests with selected filters + run: | + echo "Running tests for type: ${{ github.event.inputs.test_type }} on browser: ${{ github.event.inputs.browser }}" + + if [ "${{ github.event.inputs.test_type }}" == "API" ]; then + dotnet test ./API_Automation/API_Automation.csproj --filter "Category=API" --logger "trx;LogFileName=api-tests.trx" + elif [ "${{ github.event.inputs.test_type }}" == "UI" ]; then + dotnet test ./UI_Automation/UI_Automation.csproj --filter "Category=UI" --logger "trx;LogFileName=ui-tests.trx" + else + dotnet test --logger "trx;LogFileName=all-tests.trx" + fi + + - name: Publish Test Results + uses: actions/upload-artifact@v4 + with: + name: test-results + path: | + **/*.trx + **/TestResults/ + + - name: Allure Report Generation + uses: simple-elf/allure-report-action@v1.8 + if: always() + with: + allure_results: "**/allure-results" + allure_report: "**/allure-report" + keep_reports: 5 + + - name: Upload Allure Report + if: always() + uses: actions/upload-artifact@v4 + with: + name: allure-report + path: allure-report/ From 7c839f3386f3d129093391b76f5126509fd1df0e Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Sun, 26 Oct 2025 16:21:41 +0100 Subject: [PATCH 03/42] Modify Allure report upload configuration Updated artifact upload paths and added warning for missing files. --- .github/workflows/main.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c703985..812d53c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -70,8 +70,11 @@ jobs: keep_reports: 5 - name: Upload Allure Report - if: always() uses: actions/upload-artifact@v4 with: name: allure-report - path: allure-report/ + path: | + **/allure-report/** + **/allure-results/** + if-no-files-found: warn + From bfc3b46528f33c662fe689058b04a1673710ad90 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Sun, 26 Oct 2025 16:31:07 +0100 Subject: [PATCH 04/42] Update GitHub Actions workflow to be triggered on push --- .github/workflows/main.yml | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 812d53c..3d85f70 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,27 +1,31 @@ name: .NET Reqnroll Automation Tests on: + push: + branches: + - master workflow_dispatch: inputs: test_type: - description: "Select which tests to run" + description: 'Select which tests to run' required: true - default: "All" + default: 'All' type: choice options: - - API - UI + - API - All browser: - description: "Select browser" + description: 'Select browser' required: true - default: "Chrome" + default: 'Chrome' type: choice options: - Chrome - Edge - Both + jobs: build-and-test: runs-on: ubuntu-latest @@ -72,9 +76,10 @@ jobs: - name: Upload Allure Report uses: actions/upload-artifact@v4 with: - name: allure-report - path: | - **/allure-report/** - **/allure-results/** - if-no-files-found: warn + name: allure-report + path: | + allure-report + allure-results + if-no-files-found: warn + From bc71f158036f4db6a8acbb89ce91925518ad9855 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Sun, 26 Oct 2025 16:41:16 +0100 Subject: [PATCH 05/42] Refactor GitHub Actions workflow for report generation Updated test workflow to use new test commands and added HTML report generation. --- .github/workflows/main.yml | 52 +++++++++++++++----------------------- 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3d85f70..c833d24 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,25 +7,24 @@ on: workflow_dispatch: inputs: test_type: - description: 'Select which tests to run' + description: "Select which tests to run" required: true - default: 'All' + default: "All" type: choice options: - UI - API - All browser: - description: 'Select browser' + description: "Select browser" required: true - default: 'Chrome' + default: "Chrome" type: choice options: - Chrome - Edge - Both - jobs: build-and-test: runs-on: ubuntu-latest @@ -37,7 +36,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: 9.0.x + dotnet-version: '8.0.x' - name: Restore dependencies run: dotnet restore @@ -47,39 +46,30 @@ jobs: - name: Run tests with selected filters run: | - echo "Running tests for type: ${{ github.event.inputs.test_type }} on browser: ${{ github.event.inputs.browser }}" - - if [ "${{ github.event.inputs.test_type }}" == "API" ]; then - dotnet test ./API_Automation/API_Automation.csproj --filter "Category=API" --logger "trx;LogFileName=api-tests.trx" - elif [ "${{ github.event.inputs.test_type }}" == "UI" ]; then - dotnet test ./UI_Automation/UI_Automation.csproj --filter "Category=UI" --logger "trx;LogFileName=ui-tests.trx" + echo "Running tests for: ${{ github.event.inputs.test_type }}" + if [ "${{ github.event.inputs.test_type }}" = "UI" ]; then + dotnet test UI_Automation --logger "trx;LogFileName=all-tests.trx" + elif [ "${{ github.event.inputs.test_type }}" = "API" ]; then + dotnet test API_Automation --logger "trx;LogFileName=all-tests.trx" else dotnet test --logger "trx;LogFileName=all-tests.trx" fi - - name: Publish Test Results - uses: actions/upload-artifact@v4 - with: - name: test-results - path: | - **/*.trx - **/TestResults/ + - name: Generate HTML Report + run: | + dotnet tool install -g trx2html + trx2html -i **/TestResults/all-tests.trx -o TestResults/report.html - - name: Allure Report Generation - uses: simple-elf/allure-report-action@v1.8 - if: always() + - name: Upload HTML Report + uses: actions/upload-artifact@v4 with: - allure_results: "**/allure-results" - allure_report: "**/allure-report" - keep_reports: 5 + name: html-report + path: TestResults/report.html - - name: Upload Allure Report + - name: Upload TRX Report (optional) uses: actions/upload-artifact@v4 with: - name: allure-report + name: trx-results path: | - allure-report - allure-results - if-no-files-found: warn - + **/TestResults/*.trx From ddd87ec10f04d1512602d86bd8f00ae5c51c6d33 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Sun, 26 Oct 2025 16:52:12 +0100 Subject: [PATCH 06/42] Add Allure report generation and upload report generation --- .github/workflows/main.yml | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c833d24..7aa2839 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -55,21 +55,39 @@ jobs: dotnet test --logger "trx;LogFileName=all-tests.trx" fi + # Generate Allure Report + - name: Generate Allure Report + uses: simple-elf/allure-report-action@v1.8 + if: always() + with: + allure_results: "**/allure-results" + allure_report: "allure-report" + keep_reports: 5 + + # Upload Allure Report + - name: Upload Allure Report + uses: actions/upload-artifact@v4 + with: + name: allure-report + path: allure-report + + # Generate standard HTML report from TRX - name: Generate HTML Report run: | - dotnet tool install -g trx2html - trx2html -i **/TestResults/all-tests.trx -o TestResults/report.html + dotnet tool install -g dotnet-reportgenerator-globaltool + reportgenerator '-reports:**/TestResults/*.trx' '-targetdir:TestResults/report' '-reporttypes:Html' + # Upload HTML Report - name: Upload HTML Report uses: actions/upload-artifact@v4 with: name: html-report - path: TestResults/report.html + path: TestResults/report + # Upload TRX Results - name: Upload TRX Report (optional) uses: actions/upload-artifact@v4 with: - name: trx-results - path: | - **/TestResults/*.trx - + name: trx-results + path: | + **/TestResults/*.trx From 4c80ca4cfbdd3f765d473a21a7a645872c2ab23c Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Sun, 26 Oct 2025 17:05:08 +0100 Subject: [PATCH 07/42] Update GitHub Actions workflow for Allure reports --- .github/workflows/main.yml | 49 ++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7aa2839..746028d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -44,48 +44,41 @@ jobs: - name: Build solution run: dotnet build --no-restore --configuration Release - - name: Run tests with selected filters + # Run tests and let Allure.Reqnroll generate allure-results + allure-report + - name: Run tests with Allure.Reqnroll run: | echo "Running tests for: ${{ github.event.inputs.test_type }}" if [ "${{ github.event.inputs.test_type }}" = "UI" ]; then - dotnet test UI_Automation --logger "trx;LogFileName=all-tests.trx" + dotnet test UI_Automation --logger "trx;LogFileName=ui-tests.trx" --logger "Allure" elif [ "${{ github.event.inputs.test_type }}" = "API" ]; then - dotnet test API_Automation --logger "trx;LogFileName=all-tests.trx" + dotnet test API_Automation --logger "trx;LogFileName=api-tests.trx" --logger "Allure" else - dotnet test --logger "trx;LogFileName=all-tests.trx" + dotnet test --logger "trx;LogFileName=all-tests.trx" --logger "Allure" fi - # Generate Allure Report - - name: Generate Allure Report - uses: simple-elf/allure-report-action@v1.8 + # Upload Allure reports (UI and API) + - name: Upload Allure Reports if: always() - with: - allure_results: "**/allure-results" - allure_report: "allure-report" - keep_reports: 5 - - # Upload Allure Report - - name: Upload Allure Report uses: actions/upload-artifact@v4 with: - name: allure-report - path: allure-report - - # Generate standard HTML report from TRX - - name: Generate HTML Report - run: | - dotnet tool install -g dotnet-reportgenerator-globaltool - reportgenerator '-reports:**/TestResults/*.trx' '-targetdir:TestResults/report' '-reporttypes:Html' + name: allure-reports + path: | + UI_Automation/allure-report/** + API_Automation/allure-report/** - # Upload HTML Report - - name: Upload HTML Report + # Upload Allure results (raw data) + - name: Upload Allure Results + if: always() uses: actions/upload-artifact@v4 with: - name: html-report - path: TestResults/report + name: allure-results + path: | + UI_Automation/allure-results/** + API_Automation/allure-results/** - # Upload TRX Results - - name: Upload TRX Report (optional) + # Upload TRX files for debugging (optional) + - name: Upload TRX Results + if: always() uses: actions/upload-artifact@v4 with: name: trx-results From c7e3ba0591d3152cd2a80621ad5827bee720c8c9 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Sun, 26 Oct 2025 17:14:11 +0100 Subject: [PATCH 08/42] Update GitHub Actions workflow for .NET and Allure --- .github/workflows/main.yml | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 746028d..a0c431e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,21 +30,29 @@ jobs: runs-on: ubuntu-latest steps: + # Checkout source code - name: Checkout code uses: actions/checkout@v4 + # Setup .NET SDK - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: '8.0.x' + dotnet-version: "8.0.x" + # Install Allure.Reqnroll tool globally so "logger Allure" is recognized + - name: Install Allure Reqnroll logger + run: dotnet tool install --global "Allure.Reqnroll" + + # Restore dependencies - name: Restore dependencies run: dotnet restore + # Build the solution - name: Build solution run: dotnet build --no-restore --configuration Release - # Run tests and let Allure.Reqnroll generate allure-results + allure-report + # Run tests (Allure logger will generate allure-results & allure-report) - name: Run tests with Allure.Reqnroll run: | echo "Running tests for: ${{ github.event.inputs.test_type }}" @@ -56,7 +64,7 @@ jobs: dotnet test --logger "trx;LogFileName=all-tests.trx" --logger "Allure" fi - # Upload Allure reports (UI and API) + # Upload Allure Reports (from both UI & API projects) - name: Upload Allure Reports if: always() uses: actions/upload-artifact@v4 @@ -66,7 +74,7 @@ jobs: UI_Automation/allure-report/** API_Automation/allure-report/** - # Upload Allure results (raw data) + # Upload Allure Results (raw JSON for manual rebuild if needed) - name: Upload Allure Results if: always() uses: actions/upload-artifact@v4 @@ -76,11 +84,10 @@ jobs: UI_Automation/allure-results/** API_Automation/allure-results/** - # Upload TRX files for debugging (optional) + # Upload TRX logs (for optional Azure or debugging) - name: Upload TRX Results if: always() uses: actions/upload-artifact@v4 with: name: trx-results - path: | - **/TestResults/*.trx + path: "**/TestResults/*.trx" From dc3a71d5f5bd695cc2caf26f5aaebc4ee41e9aed Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Sun, 26 Oct 2025 17:16:42 +0100 Subject: [PATCH 09/42] Refactor comments and remove Allure logger installation --- .github/workflows/main.yml | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a0c431e..e7a5928 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,41 +30,37 @@ jobs: runs-on: ubuntu-latest steps: - # Checkout source code + # Checkout source - name: Checkout code uses: actions/checkout@v4 - # Setup .NET SDK + # Setup .NET SDK - name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: "8.0.x" - # Install Allure.Reqnroll tool globally so "logger Allure" is recognized - - name: Install Allure Reqnroll logger - run: dotnet tool install --global "Allure.Reqnroll" - - # Restore dependencies + # Restore dependencies - name: Restore dependencies run: dotnet restore - # Build the solution + # Build solution - name: Build solution run: dotnet build --no-restore --configuration Release - # Run tests (Allure logger will generate allure-results & allure-report) + # Run tests (Allure.Reqnroll will automatically generate allure-results & allure-report) - name: Run tests with Allure.Reqnroll run: | echo "Running tests for: ${{ github.event.inputs.test_type }}" if [ "${{ github.event.inputs.test_type }}" = "UI" ]; then - dotnet test UI_Automation --logger "trx;LogFileName=ui-tests.trx" --logger "Allure" + dotnet test UI_Automation --logger "trx;LogFileName=ui-tests.trx" elif [ "${{ github.event.inputs.test_type }}" = "API" ]; then - dotnet test API_Automation --logger "trx;LogFileName=api-tests.trx" --logger "Allure" + dotnet test API_Automation --logger "trx;LogFileName=api-tests.trx" else - dotnet test --logger "trx;LogFileName=all-tests.trx" --logger "Allure" + dotnet test --logger "trx;LogFileName=all-tests.trx" fi - # Upload Allure Reports (from both UI & API projects) + # Upload Allure Reports (UI + API) - name: Upload Allure Reports if: always() uses: actions/upload-artifact@v4 @@ -74,7 +70,7 @@ jobs: UI_Automation/allure-report/** API_Automation/allure-report/** - # Upload Allure Results (raw JSON for manual rebuild if needed) + # Upload Allure Results (raw JSONs) - name: Upload Allure Results if: always() uses: actions/upload-artifact@v4 @@ -84,7 +80,7 @@ jobs: UI_Automation/allure-results/** API_Automation/allure-results/** - # Upload TRX logs (for optional Azure or debugging) + # Upload TRX Results - name: Upload TRX Results if: always() uses: actions/upload-artifact@v4 From c871021c00b162435ddb60ae5a5a1961c0c51962 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Sun, 26 Oct 2025 17:21:41 +0100 Subject: [PATCH 10/42] Refactor GitHub Actions workflow for Allure reports Updated the GitHub Actions workflow to improve report generation and cleanup. --- .github/workflows/main.yml | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e7a5928..4ce624e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,25 +30,20 @@ jobs: runs-on: ubuntu-latest steps: - # Checkout source - name: Checkout code uses: actions/checkout@v4 - # Setup .NET SDK - name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: "8.0.x" - # Restore dependencies - name: Restore dependencies run: dotnet restore - # Build solution - name: Build solution run: dotnet build --no-restore --configuration Release - # Run tests (Allure.Reqnroll will automatically generate allure-results & allure-report) - name: Run tests with Allure.Reqnroll run: | echo "Running tests for: ${{ github.event.inputs.test_type }}" @@ -60,17 +55,26 @@ jobs: dotnet test --logger "trx;LogFileName=all-tests.trx" fi - # Upload Allure Reports (UI + API) - - name: Upload Allure Reports + # Generate Allure HTML report (using simple-elf action) + - name: Generate Allure HTML Report + uses: simple-elf/allure-report-action@v1.8 + if: always() + with: + allure_results: | + UI_Automation/allure-results + API_Automation/allure-results + allure_report: allure-report + keep_reports: 5 + + # Upload generated Allure HTML report + - name: Upload Allure HTML Report if: always() uses: actions/upload-artifact@v4 with: - name: allure-reports - path: | - UI_Automation/allure-report/** - API_Automation/allure-report/** + name: allure-html-report + path: allure-report - # Upload Allure Results (raw JSONs) + # Upload raw Allure results (for local serve) - name: Upload Allure Results if: always() uses: actions/upload-artifact@v4 @@ -79,11 +83,3 @@ jobs: path: | UI_Automation/allure-results/** API_Automation/allure-results/** - - # Upload TRX Results - - name: Upload TRX Results - if: always() - uses: actions/upload-artifact@v4 - with: - name: trx-results - path: "**/TestResults/*.trx" From bd11e57c5ea9d623f5f67658f26279898e47983c Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Sun, 26 Oct 2025 19:00:02 +0100 Subject: [PATCH 11/42] Add README with project overview and setup instructions Added detailed project information, tech stack, prerequisites, and instructions for running tests and generating reports. --- README.md | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..7760221 --- /dev/null +++ b/README.md @@ -0,0 +1,79 @@ +# TA_Csharp_Selenium_RestSharp + +Modern **Test Automation Framework** built with **C# (.NET 8)**, combining **Selenium WebDriver** for UI testing, **RestSharp** for API testing, and **Reqnroll** (SpecFlow-style) for BDD scenarios. +Enhanced with **Allure Reporting** and fully integrated into **GitHub Actions** CI/CD. + +--- + +## Tech Stack + +| Layer | Tools / Libraries | +|-------|--------------------| +| UI Testing | Selenium WebDriver | +| API Testing | RestSharp | +| BDD Framework | Reqnroll + NUnit | +| Reporting | Allure.Reqnroll + TRX | +| Platform | .NET 8.0 | + +--- + +## Getting Started + +### Prerequisites +- [.NET 8 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) +- Chrome / Edge / Firefox browser +- [Allure Commandline](https://docs.qameta.io/allure/) + Install globally: + ```bash + npm install -g allure-commandline +Run Tests +bash +Copy code +dotnet test # Run all tests +dotnet test UI_Automation/UI_Automation.csproj +dotnet test API_Automation/API_Automation.csproj +dotnet test --filter "Category=Smoke" # Filter by tag +Allure Report +After running tests, reports are generated in allure-results/. +To create and open the report locally: + +bash +Copy code +allure generate allure-results --clean -o allure-report +allure open allure-report +Input folder must be allure-results, not allure-report. + +Highlights +Unified UI & API test layers + +Page Object Model (POM) for maintainable UI tests + +Reqnroll Hooks for driver and context management + +Allure.Reqnroll integrated for BDD-driven reporting + +GitHub Actions workflow for automated execution + +Project Structure +swift +Copy code +Csharp_Automation_Task/ + ┣ πŸ“ API_Automation/ + ┣ πŸ“ UI_Automation/ + ┣ πŸ“ .github/workflows/ + ┣ πŸ“„ Csharp_Automation_Task.sln + ┣ πŸ“„ README.md + β”— πŸ“„ .gitignore +CI/CD Integration +The pipeline automatically: + +Builds and tests both layers (UI + API) + +Generates Allure, TRX, and HTML reports + +Uploads them as GitHub Action artifacts + +Workflow file: .github/workflows/automation-tests.yml + +πŸ‘€ Author +Srdjan Miljus β€” Senior QA Automation Architect From f0dc1eb3b07d2b810f0c09aa25bba3d4bca808ba Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Sun, 26 Oct 2025 19:03:34 +0100 Subject: [PATCH 12/42] Revise README for clarity and structure Updated README to enhance clarity and structure, added tech stack section, and improved formatting. --- README.md | 119 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 67 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 7760221..0d581ab 100644 --- a/README.md +++ b/README.md @@ -1,79 +1,94 @@ -# TA_Csharp_Selenium_RestSharp +TA_Csharp_Selenium_RestSharp -Modern **Test Automation Framework** built with **C# (.NET 8)**, combining **Selenium WebDriver** for UI testing, **RestSharp** for API testing, and **Reqnroll** (SpecFlow-style) for BDD scenarios. -Enhanced with **Allure Reporting** and fully integrated into **GitHub Actions** CI/CD. +Modern Test Automation Framework built with C# (.NET 8) β€” combining +Selenium WebDriver for UI testing, RestSharp for API testing, and Reqnroll (SpecFlow-style) for BDD scenarios. +Enhanced with Allure Reporting and fully integrated into GitHub Actions CI/CD. ---- +βš™οΈ Tech Stack +Layer Tools / Libraries +UI Testing Selenium WebDriver +API Testing RestSharp +BDD Framework Reqnroll + NUnit +Reporting Allure.Reqnroll + TRX +Platform .NET 8.0 +πŸš€ Getting Started +Prerequisites -## Tech Stack +.NET SDK 8.0+ -| Layer | Tools / Libraries | -|-------|--------------------| -| UI Testing | Selenium WebDriver | -| API Testing | RestSharp | -| BDD Framework | Reqnroll + NUnit | -| Reporting | Allure.Reqnroll + TRX | -| Platform | .NET 8.0 | +Chrome / Edge / Firefox browser ---- +Allure Commandline (npm install -g allure-commandline) -## Getting Started - -### Prerequisites -- [.NET 8 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) -- Chrome / Edge / Firefox browser -- [Allure Commandline](https://docs.qameta.io/allure/) - Install globally: - ```bash - npm install -g allure-commandline Run Tests -bash -Copy code -dotnet test # Run all tests +# Run all tests +dotnet test + +# Run specific projects dotnet test UI_Automation/UI_Automation.csproj dotnet test API_Automation/API_Automation.csproj -dotnet test --filter "Category=Smoke" # Filter by tag -Allure Report -After running tests, reports are generated in allure-results/. -To create and open the report locally: -bash -Copy code +# Run by category +dotnet test --filter "Category=Smoke" + +πŸ“Š Allure Report + +After running tests, results are stored in allure-results/. + +Generate and open the report locally: + allure generate allure-results --clean -o allure-report allure open allure-report -Input folder must be allure-results, not allure-report. -Highlights + +Make sure the input folder is allure-results, not allure-report. + +πŸ’‘ Highlights + Unified UI & API test layers Page Object Model (POM) for maintainable UI tests -Reqnroll Hooks for driver and context management +Reqnroll Hooks for driver & context management -Allure.Reqnroll integrated for BDD-driven reporting +Allure.Reqnroll for BDD-driven reporting GitHub Actions workflow for automated execution -Project Structure -swift -Copy code +πŸ“ Project Structure Csharp_Automation_Task/ - ┣ πŸ“ API_Automation/ - ┣ πŸ“ UI_Automation/ - ┣ πŸ“ .github/workflows/ - ┣ πŸ“„ Csharp_Automation_Task.sln - ┣ πŸ“„ README.md - β”— πŸ“„ .gitignore -CI/CD Integration -The pipeline automatically: - -Builds and tests both layers (UI + API) +β”œβ”€β”€ πŸ“‚ UI_Automation/ +β”‚ β”œβ”€β”€ Features/ +β”‚ β”œβ”€β”€ Pages/ +β”‚ β”œβ”€β”€ Steps/ +β”‚ β”œβ”€β”€ Support/ +β”‚ └── UI_Automation.csproj +β”‚ +β”œβ”€β”€ πŸ“‚ API_Automation/ +β”‚ β”œβ”€β”€ Client/ +β”‚ β”œβ”€β”€ Tests/ +β”‚ β”œβ”€β”€ Models/ +β”‚ └── API_Automation.csproj +β”‚ +β”œβ”€β”€ πŸ“‚ .github/ +β”‚ └── workflows/ +β”‚ └── automation-tests.yml +β”‚ +β”œβ”€β”€ πŸ“„ Csharp_Automation_Task.sln +β”œβ”€β”€ πŸ“„ README.md +└── πŸ“„ .gitignore + +πŸ”„ CI/CD Integration + +GitHub Actions pipeline automatically: + +Builds and tests both UI + API layers Generates Allure, TRX, and HTML reports -Uploads them as GitHub Action artifacts +Uploads reports as GitHub Action artifacts -Workflow file: .github/workflows/automation-tests.yml +Workflow file: +.github/workflows/automation-tests.yml -πŸ‘€ Author -Srdjan Miljus β€” Senior QA Automation Architect +πŸ‘€ Author: Srdjan Miljus β€” Senior QA Automation Architect From c62fc2dab85cd5fdac8b494950f11ecc675b6c4e Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Sun, 26 Oct 2025 19:10:17 +0100 Subject: [PATCH 13/42] Delete README.md --- README.md | 94 ------------------------------------------------------- 1 file changed, 94 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 0d581ab..0000000 --- a/README.md +++ /dev/null @@ -1,94 +0,0 @@ -TA_Csharp_Selenium_RestSharp - -Modern Test Automation Framework built with C# (.NET 8) β€” combining -Selenium WebDriver for UI testing, RestSharp for API testing, and Reqnroll (SpecFlow-style) for BDD scenarios. -Enhanced with Allure Reporting and fully integrated into GitHub Actions CI/CD. - -βš™οΈ Tech Stack -Layer Tools / Libraries -UI Testing Selenium WebDriver -API Testing RestSharp -BDD Framework Reqnroll + NUnit -Reporting Allure.Reqnroll + TRX -Platform .NET 8.0 -πŸš€ Getting Started -Prerequisites - -.NET SDK 8.0+ - -Chrome / Edge / Firefox browser - -Allure Commandline (npm install -g allure-commandline) - -Run Tests -# Run all tests -dotnet test - -# Run specific projects -dotnet test UI_Automation/UI_Automation.csproj -dotnet test API_Automation/API_Automation.csproj - -# Run by category -dotnet test --filter "Category=Smoke" - -πŸ“Š Allure Report - -After running tests, results are stored in allure-results/. - -Generate and open the report locally: - -allure generate allure-results --clean -o allure-report -allure open allure-report - - -Make sure the input folder is allure-results, not allure-report. - -πŸ’‘ Highlights - -Unified UI & API test layers - -Page Object Model (POM) for maintainable UI tests - -Reqnroll Hooks for driver & context management - -Allure.Reqnroll for BDD-driven reporting - -GitHub Actions workflow for automated execution - -πŸ“ Project Structure -Csharp_Automation_Task/ -β”œβ”€β”€ πŸ“‚ UI_Automation/ -β”‚ β”œβ”€β”€ Features/ -β”‚ β”œβ”€β”€ Pages/ -β”‚ β”œβ”€β”€ Steps/ -β”‚ β”œβ”€β”€ Support/ -β”‚ └── UI_Automation.csproj -β”‚ -β”œβ”€β”€ πŸ“‚ API_Automation/ -β”‚ β”œβ”€β”€ Client/ -β”‚ β”œβ”€β”€ Tests/ -β”‚ β”œβ”€β”€ Models/ -β”‚ └── API_Automation.csproj -β”‚ -β”œβ”€β”€ πŸ“‚ .github/ -β”‚ └── workflows/ -β”‚ └── automation-tests.yml -β”‚ -β”œβ”€β”€ πŸ“„ Csharp_Automation_Task.sln -β”œβ”€β”€ πŸ“„ README.md -└── πŸ“„ .gitignore - -πŸ”„ CI/CD Integration - -GitHub Actions pipeline automatically: - -Builds and tests both UI + API layers - -Generates Allure, TRX, and HTML reports - -Uploads reports as GitHub Action artifacts - -Workflow file: -.github/workflows/automation-tests.yml - -πŸ‘€ Author: Srdjan Miljus β€” Senior QA Automation Architect From 204bb002ae9f50c508660a1fa416a06c6eaa685e Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Sun, 26 Oct 2025 19:27:32 +0100 Subject: [PATCH 14/42] added Readme.md file --- Csharp_Automation_Task.slnx | 5 +- README.md | 139 ++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 README.md diff --git a/Csharp_Automation_Task.slnx b/Csharp_Automation_Task.slnx index 90c3900..a4d6470 100644 --- a/Csharp_Automation_Task.slnx +++ b/Csharp_Automation_Task.slnx @@ -1,10 +1,7 @@ - - - - + diff --git a/README.md b/README.md new file mode 100644 index 0000000..4b56e79 --- /dev/null +++ b/README.md @@ -0,0 +1,139 @@ +ο»Ώ# 🧩 TA_Csharp_Selenium_RestSharp + +Modern **Test Automation Framework** built with **C# (.NET 8)** β€” combining +**Selenium WebDriver** for UI testing, **RestSharp** for API testing, and **Reqnroll** (SpecFlow-style) for BDD scenarios. +Enhanced with **Allure Reporting** and fully integrated into **GitHub Actions CI/CD**. + +--- + +## βš™οΈ Tech Stack + +| Layer | Tools / Libraries | +| ------------- | --------------------- | +| UI Testing | Selenium WebDriver | +| API Testing | RestSharp | +| BDD Framework | Reqnroll + NUnit | +| Reporting | Allure.Reqnroll + TRX | +| Platform | .NET 8.0 | + +--- + +## πŸš€ Getting Started + +### 🧩 Prerequisites + +* .NET SDK 8.0+ +* Chrome / Edge / Firefox browser +* Allure Commandline + Install globally: + + ```bash + npm install -g allure-commandline + ``` + +--- + +## ▢️ Run Tests + +You can execute tests from the root or specific project level. + +```bash +# Run all tests +dotnet test + +# Run specific projects +dotnet test UI_Automation/UI_Automation.csproj +dotnet test API_Automation/API_Automation.csproj + +# Run by category/tag (e.g. Smoke) +dotnet test --filter "Category=Smoke" +``` + +> πŸ’‘ Tip: Add `--logger "trx;LogFileName=test_results.trx"` to export TRX reports for CI/CD pipelines. + +--- + +## πŸ“Š Allure Report + +After running tests, Allure result files are generated in the `allure-results/` folder. +You can generate and open the HTML report locally with: + +```bash +allure generate allure-results --clean -o allure-report +allure open allure-report +``` + +> ⚠️ Make sure the input folder is **allure-results**, not **allure-report**. + +**Allure integration notes:** + +* Allure.Reqnroll automatically attaches scenario metadata and screenshots. +* Reports include: Feature β†’ Scenario β†’ Steps β†’ Attachments β†’ Logs. +* Works seamlessly with GitHub Actions for artifact uploads. + +--- + +## πŸ’‘ Highlights + +* Unified **UI** & **API** automation layers +* Page Object Model (**POM**) for clean, maintainable UI tests +* Reqnroll Hooks for driver, context, and test lifecycle management +* Integrated Allure.Reqnroll for rich BDD reporting +* Supports test categorization (Smoke, Regression, E2E, API) +* GitHub Actions pipeline for continuous testing and reporting + +--- + +## πŸ— Project Structure + +```text +Csharp_Automation_Task/ +β”œβ”€β”€ UI_Automation/ +β”‚ β”œβ”€β”€ Features/ +β”‚ β”œβ”€β”€ Pages/ +β”‚ β”œβ”€β”€ Steps/ +β”‚ β”œβ”€β”€ Support/ +β”‚ └── UI_Automation.csproj +β”‚ +β”œβ”€β”€ API_Automation/ +β”‚ β”œβ”€β”€ Client/ +β”‚ β”œβ”€β”€ Tests/ +β”‚ β”œβ”€β”€ Models/ +β”‚ └── API_Automation.csproj +β”‚ +β”œβ”€β”€ .github/ +β”‚ └── workflows/ +β”‚ └── automation-tests.yml +β”‚ +β”œβ”€β”€ Csharp_Automation_Task.sln +β”œβ”€β”€ README.md +└── .gitignore +``` + +--- + +## πŸ”„ CI/CD Integration + +GitHub Actions pipeline automatically: + +* Builds and tests both **UI + API** layers +* Generates **Allure**, **TRX**, and optional HTML reports +* Uploads them as **GitHub Action artifacts** for download + +**Workflow file:** `.github/workflows/automation-tests.yml` + +Example snippet from workflow: + +```yaml +- name: Run Tests + run: dotnet test --logger "trx;LogFileName=test_results.trx" + +- name: Generate Allure Report + run: | + allure generate allure-results --clean -o allure-report + allure open allure-report +``` + +--- + +πŸ‘€ **Author:** *Srdjan Miljus β€” Senior QA Automation Arc From 98cf659aadcf72743cb47ebbebf0a8b839dbfd95 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Sun, 26 Oct 2025 19:31:06 +0100 Subject: [PATCH 15/42] Update test categorization in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4b56e79..ea9461c 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ allure open allure-report * Page Object Model (**POM**) for clean, maintainable UI tests * Reqnroll Hooks for driver, context, and test lifecycle management * Integrated Allure.Reqnroll for rich BDD reporting -* Supports test categorization (Smoke, Regression, E2E, API) +* Supports test categorization (Smoke, Regression, UI, API) * GitHub Actions pipeline for continuous testing and reporting --- From 0014c83be3902c773527f1256a3bab9a74283f8c Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Sun, 26 Oct 2025 19:32:37 +0100 Subject: [PATCH 16/42] Remove emoji from README title --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ea9461c..00af92a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -ο»Ώ# 🧩 TA_Csharp_Selenium_RestSharp +ο»Ώ# TA_Csharp_Selenium_RestSharp Modern **Test Automation Framework** built with **C# (.NET 8)** β€” combining **Selenium WebDriver** for UI testing, **RestSharp** for API testing, and **Reqnroll** (SpecFlow-style) for BDD scenarios. From d846acb7df251821c47e4c384e775b194eac5bb1 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Sun, 26 Oct 2025 21:19:22 +0100 Subject: [PATCH 17/42] Refactor: remove unused models and redundant code --- API_Automation/Client/RestApiClient.cs | 2 +- API_Automation/Features/ReplaceABook.feature | 4 +++ .../Features/ReplaceABook.feature.cs | 22 +++++++++------- .../Models/Response/BooksGetResponse.cs | 18 ------------- .../Models/Response/BooksPostResponse.cs | 16 ------------ .../Models/Response/CreateUserResponse.cs | 4 --- .../Models/Response/TokenResponse.cs | 1 - .../Models/Response/UserResponse.cs | 25 ------------------- 8 files changed, 18 insertions(+), 74 deletions(-) delete mode 100644 API_Automation/Models/Response/BooksPostResponse.cs diff --git a/API_Automation/Client/RestApiClient.cs b/API_Automation/Client/RestApiClient.cs index 10d4490..60ebad4 100644 --- a/API_Automation/Client/RestApiClient.cs +++ b/API_Automation/Client/RestApiClient.cs @@ -19,7 +19,7 @@ public RestApiClient(string baseUrl) var options = new RestClientOptions(baseUrl) { ThrowOnAnyError = false, - MaxTimeout = timeoutSeconds * 1000 // Convert seconds to milliseconds + MaxTimeout = timeoutSeconds * 1000 }; _client = new RestClient(options); } diff --git a/API_Automation/Features/ReplaceABook.feature b/API_Automation/Features/ReplaceABook.feature index 365050f..5e454c1 100644 --- a/API_Automation/Features/ReplaceABook.feature +++ b/API_Automation/Features/ReplaceABook.feature @@ -1,6 +1,10 @@ ο»Ώ@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/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 Books { get; set; } } } - diff --git a/API_Automation/Models/Response/TokenResponse.cs b/API_Automation/Models/Response/TokenResponse.cs index 223b998..68bd5be 100644 --- a/API_Automation/Models/Response/TokenResponse.cs +++ b/API_Automation/Models/Response/TokenResponse.cs @@ -17,4 +17,3 @@ public class TokenResponse public string Result { get; set; } } } - diff --git a/API_Automation/Models/Response/UserResponse.cs b/API_Automation/Models/Response/UserResponse.cs index 241fa36..8c66dd6 100644 --- a/API_Automation/Models/Response/UserResponse.cs +++ b/API_Automation/Models/Response/UserResponse.cs @@ -17,31 +17,6 @@ public class Book { [JsonProperty("isbn")] public string Isbn { get; set; } - - [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; } } } } - From 42adec901f5dd3639102398b7cf4a1bf7a4d6241 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Sun, 26 Oct 2025 21:28:14 +0100 Subject: [PATCH 18/42] added #region for locators and actions --- UI_Automation/Features/SearchAndNavigate.feature.cs | 2 ++ UI_Automation/Pages/AboutPage.cs | 5 ++++- UI_Automation/Pages/StorePage.cs | 9 ++++++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/UI_Automation/Features/SearchAndNavigate.feature.cs b/UI_Automation/Features/SearchAndNavigate.feature.cs index bf2c1c9..5307194 100644 --- a/UI_Automation/Features/SearchAndNavigate.feature.cs +++ b/UI_Automation/Features/SearchAndNavigate.feature.cs @@ -108,10 +108,12 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa [global::NUnit.Framework.TestAttribute()] [global::NUnit.Framework.DescriptionAttribute("Search for Steam game and navigate to the About page")] + [global::NUnit.Framework.CategoryAttribute("ui")] [global::NUnit.Framework.CategoryAttribute("regression")] public async global::System.Threading.Tasks.Task SearchForSteamGameAndNavigateToTheAboutPage() { string[] tagsOfScenario = new string[] { + "ui", "regression"}; global::System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new global::System.Collections.Specialized.OrderedDictionary(); string pickleIndex = "0"; diff --git a/UI_Automation/Pages/AboutPage.cs b/UI_Automation/Pages/AboutPage.cs index df8dc6b..6792610 100644 --- a/UI_Automation/Pages/AboutPage.cs +++ b/UI_Automation/Pages/AboutPage.cs @@ -5,11 +5,13 @@ public class AboutPage { private readonly IWebDriver _driver; - // Locators + #region Locators + private IWebElement InstallSteamButton => _driver.FindElement(By.CssSelector("#about_greeting .about_install_steam_link")); private IList OnlineStatus => _driver.FindElements(By.XPath("//div[contains(@class,'gamers_online')]/parent::div")); private IList PlayingNowStatus => _driver.FindElements(By.XPath("//div[contains(@class,'gamers_in_game')]/parent::div")); + #endregion public AboutPage(IWebDriver driver) { _driver = driver; @@ -29,4 +31,5 @@ public bool CompareIfPlayingNowStatusIsLessThanOnlineStatus() return onlineStatus > playingNowStatus; } } + } diff --git a/UI_Automation/Pages/StorePage.cs b/UI_Automation/Pages/StorePage.cs index 7c46d98..e08350d 100644 --- a/UI_Automation/Pages/StorePage.cs +++ b/UI_Automation/Pages/StorePage.cs @@ -7,13 +7,14 @@ public class StorePage private readonly IWebDriver _driver; private readonly IJavaScriptExecutor _jsExecutor; - // Locators + #region Locators + private IWebElement SearchBox => _driver.FindElement(By.XPath("//input[@class='_2tlUAG6WNyYFlk9caIiLj5']")); private IList SearchResults => _driver.FindElements(By.CssSelector(".search_result_row")); private IWebElement GameNameHeading => _driver.FindElement(By.Id("appHubAppName")); private IWebElement PlayGameButton => _driver.FindElement(By.Id("freeGameBtn")); private IWebElement NoINeedSteamButton => _driver.FindElement(By.XPath("//h3[contains(text(),'No, I need Steam')]")); - + #endregion public StorePage(IWebDriver driver) { @@ -21,7 +22,8 @@ public StorePage(IWebDriver driver) _jsExecutor = (IJavaScriptExecutor)driver; } - // Actions + #region Actions + public void SearchForGame(string gameName) { @@ -76,4 +78,5 @@ private void ScrollToElement(IWebElement element) _jsExecutor.ExecuteScript("window.scrollBy(0," + linkYPositionShift + ");"); } } + #endregion } From 5077974722aa2e94545615b411329eac04f21700 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Mon, 27 Oct 2025 06:56:51 +0100 Subject: [PATCH 19/42] Correct author title in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 00af92a..22b3095 100644 --- a/README.md +++ b/README.md @@ -136,4 +136,4 @@ Example snippet from workflow: --- -πŸ‘€ **Author:** *Srdjan Miljus β€” Senior QA Automation Arc +πŸ‘€ **Author:** *Srdjan Miljus β€” Senior QA Automation Architect From e26eb6c6c138f31ff54e3ce606ba696ea9775c28 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Mon, 9 Feb 2026 22:36:20 +0100 Subject: [PATCH 20/42] refactoring --- .../Features/SearchAndNavigate.feature | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/UI_Automation/Features/SearchAndNavigate.feature b/UI_Automation/Features/SearchAndNavigate.feature index a9ca316..3547bdb 100644 --- a/UI_Automation/Features/SearchAndNavigate.feature +++ b/UI_Automation/Features/SearchAndNavigate.feature @@ -1,4 +1,9 @@ -ο»ΏFeature: SearchAndNavigate +ο»Ώ@regression +@allure.epic:SteamWebInterface +@allure.feature:Search +@allure.owner:SrdjanMiljus +@allure.severity:critical +Feature: Search and navigate As a user I want to search for game and navigate to the official Steam About page @@ -8,10 +13,10 @@ Scenario: Search for Steam game and navigate to the About page Given I open Store page When I search for "FIFA" game - Then I should see the first search result "FIFA 22" - And I should see the second search result "EA SPORTS FCβ„’ 25" + Then I should see the first search result "EA SPORTS FCβ„’ 25" + And I should see the second search result "FIFA 22" When I search for "THE FINALS" game - When I click on the first search result in the search results + And I click on the first search result in the search results Then I should be redirected to the "THE_FINALS" page And I should see the game name "THE FINALS" from the 1st search result When I click on Play Game button @@ -20,4 +25,25 @@ Scenario: Search for Steam game and navigate to the About page And I should see the Install Steam button is clickable And I should see that Playing Now gamers status are less than Online gamers status +@ui @e2e +Scenario: Navigate to the About page + Given I open Store page + When I search for "THE FINALS" game + And I click on the first search result in the search results + Then I should be redirected to the "THE_FINALS" page + And I should see the game name "THE FINALS" from the 1st search result + When I click on Play Game button + And I click on No, I need Steam button + Then I should be redirected to the "adout" page + And I should see the Install Steam button is clickable + And I should see that Playing Now gamers status are less than Online gamers status +@ui @smoke +Scenario: About page + Given I open Store page + When I search for "THE FINALS" game + And I click on the first search result in the search results + Then I should be redirected to the "THE_FINALS" page + And I should see the game name "THE FINALS" from the 1st search result + When I click on Play Game button + \ No newline at end of file From 0aebbdc96a13b9e1be41cd1061a50dcb6e0564a0 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Mon, 9 Feb 2026 22:38:10 +0100 Subject: [PATCH 21/42] refactoring --- .gitignore | 131 +++++++++-- API_Automation/API_Automation.csproj | 1 + UI_Automation/Features/CopyAndPaste.feature | 49 ++++ .../Features/CopyAndPaste.feature.cs | 222 ++++++++++++++++++ .../Features/SearchAndNavigate.feature | 8 +- .../Features/SearchAndNavigate.feature.cs | 92 +++++++- .../Hooks/AllureStepScreenshotHook.cs | 45 ++++ UI_Automation/Hooks/Hooks.cs | 65 ----- UI_Automation/Setup/TestSetup.cs | 91 ++++--- .../SearchAndNavigateStepDefinitions.cs | 16 +- UI_Automation/UI_Automation.csproj | 6 + UI_Automation/allureConfig.json | 4 +- UI_Automation/appsettings.json | 2 +- 13 files changed, 600 insertions(+), 132 deletions(-) create mode 100644 UI_Automation/Features/CopyAndPaste.feature create mode 100644 UI_Automation/Features/CopyAndPaste.feature.cs create mode 100644 UI_Automation/Hooks/AllureStepScreenshotHook.cs delete mode 100644 UI_Automation/Hooks/Hooks.cs 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..b46b5b5 100644 --- a/API_Automation/API_Automation.csproj +++ b/API_Automation/API_Automation.csproj @@ -7,6 +7,7 @@ + diff --git a/UI_Automation/Features/CopyAndPaste.feature b/UI_Automation/Features/CopyAndPaste.feature new file mode 100644 index 0000000..4d3602f --- /dev/null +++ b/UI_Automation/Features/CopyAndPaste.feature @@ -0,0 +1,49 @@ +ο»Ώ@regression +@allure.epic:SteamWebInterface +@allure.feature:CopyPaste +@allure.owner:Srdjan_Miljus +@allure.severity:critical +Feature: Copy and paste + + As a user + I want to search for game and navigate to the official Steam About page + So that I can play games on the Steam platform + +@ui +Scenario: Copy + Given I open Store page + When I search for "FIFA" game + Then I should see the first search result "EA SPORTS FCβ„’ 25" + And I should see the second search result "FIFA 22" + When I search for "THE FINALS" game + And I click on the first search result in the search results + Then I should be redirected to the "THE_FINALS" page + And I should see the game name "THE FINALS" from the 1st search result + When I click on Play Game button + And I click on No, I need Steam button + Then I should be redirected to the "about" page + And I should see the Install Steam button is clickable + And I should see that Playing Now gamers status are less than Online gamers status + +@ui +Scenario: Paste + Given I open Store page + When I search for "THE FINALS" game + And I click on the first search result in the search results + Then I should be redirected to the "THE_FINALS" page + And I should see the game name "THE FINALS" from the 1st search result + When I click on Play Game button + And I click on No, I need Steam button + Then I should be redirected to the "adout" page + And I should see the Install Steam button is clickable + And I should see that Playing Now gamers status are less than Online gamers status + +@ui +Scenario: Cut + Given I open Store page + When I search for "THE FINALS" game + And I click on the first search result in the search results + Then I should be redirected to the "THE_FINALS" page + And I should see the game name "THE FINALS" from the 1st search result + When I click on Play Game button + \ No newline at end of file diff --git a/UI_Automation/Features/CopyAndPaste.feature.cs b/UI_Automation/Features/CopyAndPaste.feature.cs new file mode 100644 index 0000000..3263394 --- /dev/null +++ b/UI_Automation/Features/CopyAndPaste.feature.cs @@ -0,0 +1,222 @@ +ο»Ώ// ------------------------------------------------------------------------------ +// +// This code was generated by Reqnroll (https://reqnroll.net/). +// Reqnroll Version:3.0.0.0 +// Reqnroll Generator Version:3.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +#region Designer generated code +#pragma warning disable +using Reqnroll; +namespace UI_Automation.Features +{ + + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Reqnroll", "3.0.0.0")] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::NUnit.Framework.TestFixtureAttribute()] + [global::NUnit.Framework.DescriptionAttribute("Copy and paste")] + [global::NUnit.Framework.FixtureLifeCycleAttribute(global::NUnit.Framework.LifeCycle.InstancePerTestCase)] + [global::NUnit.Framework.CategoryAttribute("regression")] + [global::NUnit.Framework.CategoryAttribute("allure.epic:SteamWebInterface")] + [global::NUnit.Framework.CategoryAttribute("allure.feature:CopyPaste")] + [global::NUnit.Framework.CategoryAttribute("allure.owner:Srdjan_Miljus")] + [global::NUnit.Framework.CategoryAttribute("allure.severity:critical")] + public partial class CopyAndPasteFeature + { + + private global::Reqnroll.ITestRunner testRunner; + + private static string[] featureTags = new string[] { + "regression", + "allure.epic:SteamWebInterface", + "allure.feature:CopyPaste", + "allure.owner:Srdjan_Miljus", + "allure.severity:critical"}; + + private static global::Reqnroll.FeatureInfo featureInfo = new global::Reqnroll.FeatureInfo(new global::System.Globalization.CultureInfo("en-US"), "Features", "Copy and paste", " As a user\r\n I want to search for game and navigate to the official Steam About" + + " page\r\n So that I can play games on the Steam platform", global::Reqnroll.ProgrammingLanguage.CSharp, featureTags, InitializeCucumberMessages()); + + [global::NUnit.Framework.OneTimeSetUpAttribute()] + public static async global::System.Threading.Tasks.Task FeatureSetupAsync() + { + } + + [global::NUnit.Framework.OneTimeTearDownAttribute()] + public static async global::System.Threading.Tasks.Task FeatureTearDownAsync() + { + await global::Reqnroll.TestRunnerManager.ReleaseFeatureAsync(featureInfo); + } + + [global::NUnit.Framework.SetUpAttribute()] + public async global::System.Threading.Tasks.Task TestInitializeAsync() + { + testRunner = global::Reqnroll.TestRunnerManager.GetTestRunnerForAssembly(featureHint: featureInfo); + try + { + if (((testRunner.FeatureContext != null) + && (testRunner.FeatureContext.FeatureInfo.Equals(featureInfo) == false))) + { + await testRunner.OnFeatureEndAsync(); + } + } + finally + { + if (((testRunner.FeatureContext != null) + && testRunner.FeatureContext.BeforeFeatureHookFailed)) + { + throw new global::Reqnroll.ReqnrollException("Scenario skipped because of previous before feature hook error"); + } + if ((testRunner.FeatureContext == null)) + { + await testRunner.OnFeatureStartAsync(featureInfo); + } + } + } + + [global::NUnit.Framework.TearDownAttribute()] + public async global::System.Threading.Tasks.Task TestTearDownAsync() + { + if ((testRunner == null)) + { + return; + } + try + { + await testRunner.OnScenarioEndAsync(); + } + finally + { + global::Reqnroll.TestRunnerManager.ReleaseTestRunner(testRunner); + testRunner = null; + } + } + + public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, global::Reqnroll.RuleInfo ruleInfo) + { + testRunner.OnScenarioInitialize(scenarioInfo, ruleInfo); + testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(global::NUnit.Framework.TestContext.CurrentContext); + } + + public async global::System.Threading.Tasks.Task ScenarioStartAsync() + { + await testRunner.OnScenarioStartAsync(); + } + + public async global::System.Threading.Tasks.Task ScenarioCleanupAsync() + { + await testRunner.CollectScenarioErrorsAsync(); + } + + private static global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages InitializeCucumberMessages() + { + return new global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages("Features/CopyAndPaste.feature.ndjson", 5); + } + + [global::NUnit.Framework.TestAttribute()] + [global::NUnit.Framework.DescriptionAttribute("Copy")] + [global::NUnit.Framework.CategoryAttribute("ui")] + public async global::System.Threading.Tasks.Task Copy() + { + string[] tagsOfScenario = new string[] { + "ui"}; + global::System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new global::System.Collections.Specialized.OrderedDictionary(); + string pickleIndex = "0"; + global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("Copy", null, tagsOfScenario, argumentsOfScenario, featureTags, pickleIndex); + string[] tagsOfRule = ((string[])(null)); + global::Reqnroll.RuleInfo ruleInfo = null; + this.ScenarioInitialize(scenarioInfo, ruleInfo); + if ((global::Reqnroll.TagHelper.ContainsIgnoreTag(scenarioInfo.CombinedTags) || global::Reqnroll.TagHelper.ContainsIgnoreTag(featureTags))) + { + await testRunner.SkipScenarioAsync(); + } + else + { + await this.ScenarioStartAsync(); + await testRunner.GivenAsync("I open Store page", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); + await testRunner.WhenAsync("I search for \"FIFA\" game", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); + await testRunner.ThenAsync("I should see the first search result \"EA SPORTS FCβ„’ 25\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); + await testRunner.AndAsync("I should see the second search result \"FIFA 22\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); + await testRunner.WhenAsync("I search for \"THE FINALS\" game", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); + await testRunner.AndAsync("I click on the first search result in the search results", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); + await testRunner.ThenAsync("I should be redirected to the \"THE_FINALS\" page", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); + await testRunner.AndAsync("I should see the game name \"THE FINALS\" from the 1st search result", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); + await testRunner.WhenAsync("I click on Play Game button", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); + await testRunner.AndAsync("I click on No, I need Steam button", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); + await testRunner.ThenAsync("I should be redirected to the \"about\" page", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); + await testRunner.AndAsync("I should see the Install Steam button is clickable", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); + await testRunner.AndAsync("I should see that Playing Now gamers status are less than Online gamers status", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); + } + await this.ScenarioCleanupAsync(); + } + + [global::NUnit.Framework.TestAttribute()] + [global::NUnit.Framework.DescriptionAttribute("Paste")] + [global::NUnit.Framework.CategoryAttribute("ui")] + public async global::System.Threading.Tasks.Task Paste() + { + string[] tagsOfScenario = new string[] { + "ui"}; + global::System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new global::System.Collections.Specialized.OrderedDictionary(); + string pickleIndex = "1"; + global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("Paste", null, tagsOfScenario, argumentsOfScenario, featureTags, pickleIndex); + string[] tagsOfRule = ((string[])(null)); + global::Reqnroll.RuleInfo ruleInfo = null; + this.ScenarioInitialize(scenarioInfo, ruleInfo); + if ((global::Reqnroll.TagHelper.ContainsIgnoreTag(scenarioInfo.CombinedTags) || global::Reqnroll.TagHelper.ContainsIgnoreTag(featureTags))) + { + await testRunner.SkipScenarioAsync(); + } + else + { + await this.ScenarioStartAsync(); + await testRunner.GivenAsync("I open Store page", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); + await testRunner.WhenAsync("I search for \"THE FINALS\" game", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); + await testRunner.AndAsync("I click on the first search result in the search results", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); + await testRunner.ThenAsync("I should be redirected to the \"THE_FINALS\" page", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); + await testRunner.AndAsync("I should see the game name \"THE FINALS\" from the 1st search result", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); + await testRunner.WhenAsync("I click on Play Game button", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); + await testRunner.AndAsync("I click on No, I need Steam button", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); + await testRunner.ThenAsync("I should be redirected to the \"adout\" page", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); + await testRunner.AndAsync("I should see the Install Steam button is clickable", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); + await testRunner.AndAsync("I should see that Playing Now gamers status are less than Online gamers status", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); + } + await this.ScenarioCleanupAsync(); + } + + [global::NUnit.Framework.TestAttribute()] + [global::NUnit.Framework.DescriptionAttribute("Cut")] + [global::NUnit.Framework.CategoryAttribute("ui")] + public async global::System.Threading.Tasks.Task Cut() + { + string[] tagsOfScenario = new string[] { + "ui"}; + global::System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new global::System.Collections.Specialized.OrderedDictionary(); + string pickleIndex = "2"; + global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("Cut", null, tagsOfScenario, argumentsOfScenario, featureTags, pickleIndex); + string[] tagsOfRule = ((string[])(null)); + global::Reqnroll.RuleInfo ruleInfo = null; + this.ScenarioInitialize(scenarioInfo, ruleInfo); + if ((global::Reqnroll.TagHelper.ContainsIgnoreTag(scenarioInfo.CombinedTags) || global::Reqnroll.TagHelper.ContainsIgnoreTag(featureTags))) + { + await testRunner.SkipScenarioAsync(); + } + else + { + await this.ScenarioStartAsync(); + await testRunner.GivenAsync("I open Store page", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); + await testRunner.WhenAsync("I search for \"THE FINALS\" game", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); + await testRunner.AndAsync("I click on the first search result in the search results", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); + await testRunner.ThenAsync("I should be redirected to the \"THE_FINALS\" page", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); + await testRunner.AndAsync("I should see the game name \"THE FINALS\" from the 1st search result", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); + await testRunner.WhenAsync("I click on Play Game button", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); + } + await this.ScenarioCleanupAsync(); + } + } +} +#pragma warning restore +#endregion diff --git a/UI_Automation/Features/SearchAndNavigate.feature b/UI_Automation/Features/SearchAndNavigate.feature index 3547bdb..7d32fe1 100644 --- a/UI_Automation/Features/SearchAndNavigate.feature +++ b/UI_Automation/Features/SearchAndNavigate.feature @@ -1,7 +1,7 @@ ο»Ώ@regression @allure.epic:SteamWebInterface @allure.feature:Search -@allure.owner:SrdjanMiljus +@allure.owner:Srdjan_Miljus @allure.severity:critical Feature: Search and navigate @@ -9,7 +9,7 @@ Feature: Search and navigate I want to search for game and navigate to the official Steam About page So that I can play games on the Steam platform -@ui @regression +@ui Scenario: Search for Steam game and navigate to the About page Given I open Store page When I search for "FIFA" game @@ -25,7 +25,7 @@ Scenario: Search for Steam game and navigate to the About page And I should see the Install Steam button is clickable And I should see that Playing Now gamers status are less than Online gamers status -@ui @e2e +@ui Scenario: Navigate to the About page Given I open Store page When I search for "THE FINALS" game @@ -38,7 +38,7 @@ Scenario: Navigate to the About page And I should see the Install Steam button is clickable And I should see that Playing Now gamers status are less than Online gamers status -@ui @smoke +@ui Scenario: About page Given I open Store page When I search for "THE FINALS" game diff --git a/UI_Automation/Features/SearchAndNavigate.feature.cs b/UI_Automation/Features/SearchAndNavigate.feature.cs index 5307194..c9d4019 100644 --- a/UI_Automation/Features/SearchAndNavigate.feature.cs +++ b/UI_Automation/Features/SearchAndNavigate.feature.cs @@ -18,16 +18,26 @@ namespace UI_Automation.Features [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Reqnroll", "3.0.0.0")] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::NUnit.Framework.TestFixtureAttribute()] - [global::NUnit.Framework.DescriptionAttribute("SearchAndNavigate")] + [global::NUnit.Framework.DescriptionAttribute("Search and navigate")] [global::NUnit.Framework.FixtureLifeCycleAttribute(global::NUnit.Framework.LifeCycle.InstancePerTestCase)] + [global::NUnit.Framework.CategoryAttribute("regression")] + [global::NUnit.Framework.CategoryAttribute("allure.epic:SteamWebInterface")] + [global::NUnit.Framework.CategoryAttribute("allure.feature:Search")] + [global::NUnit.Framework.CategoryAttribute("allure.owner:Srdjan_Miljus")] + [global::NUnit.Framework.CategoryAttribute("allure.severity:critical")] public partial class SearchAndNavigateFeature { private global::Reqnroll.ITestRunner testRunner; - private static string[] featureTags = ((string[])(null)); + private static string[] featureTags = new string[] { + "regression", + "allure.epic:SteamWebInterface", + "allure.feature:Search", + "allure.owner:Srdjan_Miljus", + "allure.severity:critical"}; - private static global::Reqnroll.FeatureInfo featureInfo = new global::Reqnroll.FeatureInfo(new global::System.Globalization.CultureInfo("en-US"), "Features", "SearchAndNavigate", " As a user\r\n I want to search for game and navigate to the official Steam About" + + private static global::Reqnroll.FeatureInfo featureInfo = new global::Reqnroll.FeatureInfo(new global::System.Globalization.CultureInfo("en-US"), "Features", "Search and navigate", " As a user\r\n I want to search for game and navigate to the official Steam About" + " page\r\n So that I can play games on the Steam platform", global::Reqnroll.ProgrammingLanguage.CSharp, featureTags, InitializeCucumberMessages()); [global::NUnit.Framework.OneTimeSetUpAttribute()] @@ -103,18 +113,16 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa private static global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages InitializeCucumberMessages() { - return new global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages("Features/SearchAndNavigate.feature.ndjson", 3); + return new global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages("Features/SearchAndNavigate.feature.ndjson", 5); } [global::NUnit.Framework.TestAttribute()] [global::NUnit.Framework.DescriptionAttribute("Search for Steam game and navigate to the About page")] [global::NUnit.Framework.CategoryAttribute("ui")] - [global::NUnit.Framework.CategoryAttribute("regression")] public async global::System.Threading.Tasks.Task SearchForSteamGameAndNavigateToTheAboutPage() { string[] tagsOfScenario = new string[] { - "ui", - "regression"}; + "ui"}; global::System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new global::System.Collections.Specialized.OrderedDictionary(); string pickleIndex = "0"; global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("Search for Steam game and navigate to the About page", null, tagsOfScenario, argumentsOfScenario, featureTags, pickleIndex); @@ -130,10 +138,10 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa await this.ScenarioStartAsync(); await testRunner.GivenAsync("I open Store page", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); await testRunner.WhenAsync("I search for \"FIFA\" game", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); - await testRunner.ThenAsync("I should see the first search result \"FIFA 22\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); - await testRunner.AndAsync("I should see the second search result \"EA SPORTS FCβ„’ 25\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); + await testRunner.ThenAsync("I should see the first search result \"EA SPORTS FCβ„’ 25\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); + await testRunner.AndAsync("I should see the second search result \"FIFA 22\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); await testRunner.WhenAsync("I search for \"THE FINALS\" game", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); - await testRunner.WhenAsync("I click on the first search result in the search results", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); + await testRunner.AndAsync("I click on the first search result in the search results", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); await testRunner.ThenAsync("I should be redirected to the \"THE_FINALS\" page", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); await testRunner.AndAsync("I should see the game name \"THE FINALS\" from the 1st search result", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); await testRunner.WhenAsync("I click on Play Game button", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); @@ -144,6 +152,70 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa } await this.ScenarioCleanupAsync(); } + + [global::NUnit.Framework.TestAttribute()] + [global::NUnit.Framework.DescriptionAttribute("Navigate to the About page")] + [global::NUnit.Framework.CategoryAttribute("ui")] + public async global::System.Threading.Tasks.Task NavigateToTheAboutPage() + { + string[] tagsOfScenario = new string[] { + "ui"}; + global::System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new global::System.Collections.Specialized.OrderedDictionary(); + string pickleIndex = "1"; + global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("Navigate to the About page", null, tagsOfScenario, argumentsOfScenario, featureTags, pickleIndex); + string[] tagsOfRule = ((string[])(null)); + global::Reqnroll.RuleInfo ruleInfo = null; + this.ScenarioInitialize(scenarioInfo, ruleInfo); + if ((global::Reqnroll.TagHelper.ContainsIgnoreTag(scenarioInfo.CombinedTags) || global::Reqnroll.TagHelper.ContainsIgnoreTag(featureTags))) + { + await testRunner.SkipScenarioAsync(); + } + else + { + await this.ScenarioStartAsync(); + await testRunner.GivenAsync("I open Store page", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); + await testRunner.WhenAsync("I search for \"THE FINALS\" game", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); + await testRunner.AndAsync("I click on the first search result in the search results", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); + await testRunner.ThenAsync("I should be redirected to the \"THE_FINALS\" page", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); + await testRunner.AndAsync("I should see the game name \"THE FINALS\" from the 1st search result", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); + await testRunner.WhenAsync("I click on Play Game button", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); + await testRunner.AndAsync("I click on No, I need Steam button", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); + await testRunner.ThenAsync("I should be redirected to the \"adout\" page", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); + await testRunner.AndAsync("I should see the Install Steam button is clickable", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); + await testRunner.AndAsync("I should see that Playing Now gamers status are less than Online gamers status", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); + } + await this.ScenarioCleanupAsync(); + } + + [global::NUnit.Framework.TestAttribute()] + [global::NUnit.Framework.DescriptionAttribute("About page")] + [global::NUnit.Framework.CategoryAttribute("ui")] + public async global::System.Threading.Tasks.Task AboutPage() + { + string[] tagsOfScenario = new string[] { + "ui"}; + global::System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new global::System.Collections.Specialized.OrderedDictionary(); + string pickleIndex = "2"; + global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("About page", null, tagsOfScenario, argumentsOfScenario, featureTags, pickleIndex); + string[] tagsOfRule = ((string[])(null)); + global::Reqnroll.RuleInfo ruleInfo = null; + this.ScenarioInitialize(scenarioInfo, ruleInfo); + if ((global::Reqnroll.TagHelper.ContainsIgnoreTag(scenarioInfo.CombinedTags) || global::Reqnroll.TagHelper.ContainsIgnoreTag(featureTags))) + { + await testRunner.SkipScenarioAsync(); + } + else + { + await this.ScenarioStartAsync(); + await testRunner.GivenAsync("I open Store page", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); + await testRunner.WhenAsync("I search for \"THE FINALS\" game", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); + await testRunner.AndAsync("I click on the first search result in the search results", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); + await testRunner.ThenAsync("I should be redirected to the \"THE_FINALS\" page", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); + await testRunner.AndAsync("I should see the game name \"THE FINALS\" from the 1st search result", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); + await testRunner.WhenAsync("I click on Play Game button", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); + } + await this.ScenarioCleanupAsync(); + } } } #pragma warning restore diff --git a/UI_Automation/Hooks/AllureStepScreenshotHook.cs b/UI_Automation/Hooks/AllureStepScreenshotHook.cs new file mode 100644 index 0000000..7f49e17 --- /dev/null +++ b/UI_Automation/Hooks/AllureStepScreenshotHook.cs @@ -0,0 +1,45 @@ +ο»Ώusing System.Text; +using Allure.Net.Commons; +using OpenQA.Selenium; +using Reqnroll; + +namespace UI_Automation.Hooks +{ + [Binding] + public class AllureStepScreenshotHook + { + private readonly ScenarioContext _scenarioContext; + + public AllureStepScreenshotHook(ScenarioContext scenarioContext) + { + _scenarioContext = scenarioContext; + } + + [AfterStep(Order = 9999)] + public void AfterStep() + { + + if (_scenarioContext.TestError == null) + return; + + if (!_scenarioContext.TryGetValue("driver", out IWebDriver driver) || driver == null) + return; + + + var step = _scenarioContext.StepContext.StepInfo; + var stepText = $"{step.StepDefinitionType} {step.Text}"; + AllureApi.AddAttachment("Failed step", "text/plain", Encoding.UTF8.GetBytes(stepText), ".txt"); + + + AllureApi.AddAttachment("URL", "text/plain", Encoding.UTF8.GetBytes(driver.Url ?? "N/A"), ".txt"); + + + if (driver is ITakesScreenshot ts) + { + var shot = ts.GetScreenshot(); + AllureApi.AddAttachment("Screenshot", "image/png", shot.AsByteArray, ".png"); + } + } + } +} + diff --git a/UI_Automation/Hooks/Hooks.cs b/UI_Automation/Hooks/Hooks.cs deleted file mode 100644 index 0dc5f71..0000000 --- a/UI_Automation/Hooks/Hooks.cs +++ /dev/null @@ -1,65 +0,0 @@ -ο»Ώusing Allure.Net.Commons; -using OpenQA.Selenium; - - -namespace UI_Automation.Hooks -{ - [Binding] - public class Hooks - { - private readonly ScenarioContext _scenarioContext; - private readonly IWebDriver _driver; - - public Hooks(ScenarioContext scenarioContext) - { - _scenarioContext = scenarioContext; - - _driver = scenarioContext.ContainsKey("driver") ? (IWebDriver)scenarioContext["driver"] : null; - } - - [BeforeTestRun] - public static void BeforeTestRun() - { - Console.WriteLine("=== Starting UI test run with Allure.Reqnroll ==="); - } - - [AfterStep] - public void AfterStep() - { - if (_scenarioContext.TestError != null && _driver != null) - { - TakeScreenshot("AfterStepFailure"); - } - } - - [AfterScenario] - public void AfterScenario() - { - if (_scenarioContext.TestError != null && _driver != null) - { - TakeScreenshot("AfterScenarioFailure"); - } - - _driver?.Quit(); - } - - private void TakeScreenshot(string namePrefix) - { - try - { - var screenshot = ((ITakesScreenshot)_driver).GetScreenshot(); - var fileName = $"{namePrefix}_{DateTime.Now:yyyyMMdd_HHmmss}.png"; - var path = Path.Combine(Directory.GetCurrentDirectory(), fileName); - - screenshot.SaveAsFile(path); - - AllureApi.AddAttachment(fileName, "image/png", path); - Console.WriteLine($"Screenshot saved and attached: {path}"); - } - catch (Exception ex) - { - Console.WriteLine($"Failed to capture screenshot: {ex.Message}"); - } - } - } -} diff --git a/UI_Automation/Setup/TestSetup.cs b/UI_Automation/Setup/TestSetup.cs index 1954500..b0cad2f 100644 --- a/UI_Automation/Setup/TestSetup.cs +++ b/UI_Automation/Setup/TestSetup.cs @@ -1,35 +1,37 @@ -ο»Ώusing Allure.Net.Commons; +using Allure.Net.Commons; using NUnit.Framework; using OpenQA.Selenium; using OpenQA.Selenium.Chrome; using OpenQA.Selenium.Edge; using OpenQA.Selenium.Firefox; +using Reqnroll; using Reqnroll.BoDi; +using System.Collections.Generic; +using System.IO; using System.Runtime.InteropServices; +using System.Text; using UI_Automation.Enums; using UI_Automation.Support; +//[assembly: Parallelizable(ParallelScope.Self | ParallelScope.Children)] +//[assembly: Parallelizable(ParallelScope.Fixtures)] +[assembly: Parallelizable(ParallelScope.All)] +[assembly: LevelOfParallelism(2)] + namespace UI_Automation.Setup { [Binding] - public class TestSetup : IDisposable { private readonly IObjectContainer _container; - private readonly IScenarioContext _scenarioContext; + private readonly ScenarioContext _scenarioContext; private IWebDriver _driver; private static Config _config; public TestSetup(IObjectContainer container, ScenarioContext scenarioContext) { _container = container; - _scenarioContext = _container.Resolve(); - } - - [OneTimeSetUp] - public void Init() - { - Environment.CurrentDirectory = Path.GetDirectoryName(GetType().Assembly.Location); + _scenarioContext = scenarioContext; } [BeforeTestRun] @@ -37,17 +39,48 @@ public static void GlobalSetup() { _config = Config.Load(); AllureLifecycle.Instance.CleanupResultDirectory(); + + var resultsDir = AllureLifecycle.Instance.ResultsDirectory + ?? Path.Combine(AppContext.BaseDirectory, "allure-results"); + Directory.CreateDirectory(resultsDir); + + // environment.properties + var osName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "Windows" : + RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "macOS" : "Linux"; + + File.WriteAllLines(Path.Combine(resultsDir, "environment.properties"), new[] + { + "env=QA", + $"baseUrl={_config.BaseUrl}", + $"browser={_config.Browser}", + $"os={osName}", + "framework=Reqnroll + NUnit + Selenium" + }); + + + File.WriteAllText(Path.Combine(resultsDir, "executor.json"), + """ + { + "name": "Local Run", + "type": "other", + "buildName": "UI_Automation" + } + """, Encoding.UTF8); } - [BeforeScenario] public void InitializeWebDriver() { _driver = CreateWebDriver(_config.Browser, _config.Headless, _config.Incognito); - _driver.Manage().Window.Maximize(); + try { _driver.Manage().Window.Maximize(); } catch { } _driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10); - + + _container.RegisterInstanceAs(_driver); + + + _scenarioContext["driver"] = _driver; + Logger.Log($"Initialized WebDriver for browser: {_config.Browser}, Headless: {_config.Headless}, Incognito: {_config.Incognito}"); } @@ -59,26 +92,20 @@ private static IWebDriver CreateWebDriver(string browserName, bool headless, boo { case BrowserType.Chrome: var chromeOptions = new ChromeOptions(); - if (headless) - chromeOptions.AddArgument("--headless=new"); - if (incognito) - chromeOptions.AddArgument("--incognito"); + if (headless) chromeOptions.AddArgument("--headless=new"); + if (incognito) chromeOptions.AddArgument("--incognito"); return new ChromeDriver(chromeOptions); case BrowserType.Firefox: var firefoxOptions = new FirefoxOptions(); - if (headless) - firefoxOptions.AddArgument("--headless"); - if (incognito) - firefoxOptions.AddArgument("-private"); + if (headless) firefoxOptions.AddArgument("--headless"); + if (incognito) firefoxOptions.AddArgument("-private"); return new FirefoxDriver(firefoxOptions); case BrowserType.Edge: var edgeOptions = new EdgeOptions(); - if (headless) - edgeOptions.AddArgument("headless"); - if (incognito) - edgeOptions.AddArgument("inprivate"); + if (headless) edgeOptions.AddArgument("headless"); + if (incognito) edgeOptions.AddArgument("inprivate"); return new EdgeDriver(edgeOptions); case BrowserType.Safari: @@ -96,20 +123,24 @@ private static IWebDriver CreateWebDriver(string browserName, bool headless, boo public void NavigateToBaseUrl() { - string baseUrl = _config.BaseUrl ?? "https://store.steampowered.com/"; _driver.Navigate().GoToUrl(baseUrl); } - [AfterScenario] - + [AfterScenario(Order = -10000)] public void Dispose() { - if (_driver != null) + if (_driver == null) return; + + try { _driver.Quit(); } + catch { } + finally { - _driver.Quit(); + try { _driver.Dispose(); } catch { } _driver = null; } } + } } + diff --git a/UI_Automation/StepDefinitions/SearchAndNavigateStepDefinitions.cs b/UI_Automation/StepDefinitions/SearchAndNavigateStepDefinitions.cs index d0e0961..9f67ae4 100644 --- a/UI_Automation/StepDefinitions/SearchAndNavigateStepDefinitions.cs +++ b/UI_Automation/StepDefinitions/SearchAndNavigateStepDefinitions.cs @@ -19,7 +19,7 @@ public class SearchAndNavigateStepDefinitions public SearchAndNavigateStepDefinitions(IWebDriver driver, StorePage storePage, AboutPage aboutPage, ScenarioContext scenarioContext) { _driver = driver; - _wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(10)); + _wait = new WebDriverWait(_driver, TimeSpan.Zero); _config = Config.Load(); _storePage = storePage; _aboutPage = aboutPage; @@ -66,12 +66,20 @@ public void WhenIClickOnTheFirstSearchResultInTheSearchResults() } [Then("I should be redirected to the {string} page")] - public void ThenIShouldBeRedirectedToThePage(string pageUrl) { + try + { + _wait.Until(d => d.Url.Contains(pageUrl)); + } + catch (WebDriverTimeoutException ex) + { + Assert.Fail($"Expected to be redirected to URL containing '{pageUrl}' within 10s. " + + $"Actual URL was '{_driver.Url}'. Timeout: {ex.Message}"); + } - _wait.Until(driver => driver.Url.Contains(pageUrl)); - Assert.That(_storePage.GetPageUrl(), Does.Contain(pageUrl), $"Expected to be redirected to the page containing '{pageUrl}'"); + Assert.That(_driver.Url, Does.Contain(pageUrl), + $"Expected to be redirected to the page containing '{pageUrl}'"); Logger.Log($"Verified redirection to the page: {pageUrl}"); } diff --git a/UI_Automation/UI_Automation.csproj b/UI_Automation/UI_Automation.csproj index 3013db8..024dc0d 100644 --- a/UI_Automation/UI_Automation.csproj +++ b/UI_Automation/UI_Automation.csproj @@ -9,6 +9,7 @@ + @@ -53,4 +54,9 @@ + + + + + diff --git a/UI_Automation/allureConfig.json b/UI_Automation/allureConfig.json index f97e274..4d31b0d 100644 --- a/UI_Automation/allureConfig.json +++ b/UI_Automation/allureConfig.json @@ -1,6 +1,6 @@ { - "$schema": "https://raw.githubusercontent.com/allure-framework/allure-csharp/2.14.1/Allure.Reqnroll/Schemas/allureConfig.schema.json", "allure": { - "directory": "allure-results" + "resultsDirectory": "allure-results", + "reportDirectory": "allure-report" } } diff --git a/UI_Automation/appsettings.json b/UI_Automation/appsettings.json index eed76b2..ce4e29b 100644 --- a/UI_Automation/appsettings.json +++ b/UI_Automation/appsettings.json @@ -1,5 +1,5 @@ ο»Ώ{ - "Headless": true, + "Headless": false, "Incognito": true, "Browser": "Chrome", "BaseUrl": "https://store.steampowered.com" From c91312e406124f4fd00d400dab3fa23cdb388f8d Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Mon, 9 Feb 2026 22:49:08 +0100 Subject: [PATCH 22/42] yaml changed to new allure report --- .github/workflows/main.yml | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4ce624e..685bace 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -55,14 +55,29 @@ jobs: dotnet test --logger "trx;LogFileName=all-tests.trx" fi + - name: Merge Allure results + if: always() + run: | + echo "Merging allure-results from projects" + rm -rf allure-results || true + mkdir -p allure-results + if [ -d UI_Automation/allure-results ]; then + cp -r UI_Automation/allure-results/* allure-results/ || true + fi + if [ -d API_Automation/allure-results ]; then + cp -r API_Automation/allure-results/* allure-results/ || true + fi + echo "Allure results files:" + ls -la allure-results || true + + # Generate Allure HTML report (using simple-elf action) - name: Generate Allure HTML Report uses: simple-elf/allure-report-action@v1.8 if: always() with: allure_results: | - UI_Automation/allure-results - API_Automation/allure-results + allure-results allure_report: allure-report keep_reports: 5 @@ -81,5 +96,4 @@ jobs: with: name: allure-results path: | - UI_Automation/allure-results/** - API_Automation/allure-results/** + allure-results/** From 59cb51ab720287cf46b511f6042ba0f91778513f Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Mon, 9 Feb 2026 23:00:47 +0100 Subject: [PATCH 23/42] changed yaml --- .github/workflows/main.yml | 88 +++++++++++++++++++++++----------- UI_Automation/appsettings.json | 2 +- 2 files changed, 61 insertions(+), 29 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 685bace..9b32f69 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,6 +29,11 @@ jobs: build-and-test: runs-on: ubuntu-latest + env: + # Default values for push runs (workflow_dispatch has inputs, push doesn't) + TEST_TYPE: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.test_type || 'All' }} + BROWSER: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.browser || 'Chrome' }} + steps: - name: Checkout code uses: actions/checkout@v4 @@ -44,56 +49,83 @@ jobs: - name: Build solution run: dotnet build --no-restore --configuration Release - - name: Run tests with Allure.Reqnroll + - name: Run tests run: | - echo "Running tests for: ${{ github.event.inputs.test_type }}" - if [ "${{ github.event.inputs.test_type }}" = "UI" ]; then - dotnet test UI_Automation --logger "trx;LogFileName=ui-tests.trx" - elif [ "${{ github.event.inputs.test_type }}" = "API" ]; then - dotnet test API_Automation --logger "trx;LogFileName=api-tests.trx" + echo "Running tests for: $TEST_TYPE" + echo "Browser: $BROWSER" + + if [ "$TEST_TYPE" = "UI" ]; then + dotnet test UI_Automation --configuration Release --logger "trx;LogFileName=ui-tests.trx" + elif [ "$TEST_TYPE" = "API" ]; then + dotnet test API_Automation --configuration Release --logger "trx;LogFileName=api-tests.trx" else - dotnet test --logger "trx;LogFileName=all-tests.trx" + dotnet test --configuration Release --logger "trx;LogFileName=all-tests.trx" fi - - name: Merge Allure results + # Merge Allure results from anywhere in the repo into one folder + - name: Collect Allure results if: always() + shell: bash run: | - echo "Merging allure-results from projects" + echo "Collecting allure-results..." rm -rf allure-results || true mkdir -p allure-results - if [ -d UI_Automation/allure-results ]; then - cp -r UI_Automation/allure-results/* allure-results/ || true - fi - if [ -d API_Automation/allure-results ]; then - cp -r API_Automation/allure-results/* allure-results/ || true - fi - echo "Allure results files:" + + # Find any allure-results folders (Reqnroll/Allure can put them under bin/... or project root) + mapfile -t dirs < <(find . -type d -name "allure-results" -not -path "./allure-results" 2>/dev/null || true) + + echo "Found allure-results dirs:" + printf '%s\n' "${dirs[@]}" + + for d in "${dirs[@]}"; do + echo "Copying from: $d" + cp -r "$d"/. allure-results/ 2>/dev/null || true + done + + echo "Final allure-results content:" ls -la allure-results || true + # Create a flag so next steps can decide whether to generate report + if [ -n "$(ls -A allure-results 2>/dev/null)" ]; then + echo "HAS_ALLURE_RESULTS=true" >> $GITHUB_ENV + else + echo "HAS_ALLURE_RESULTS=false" >> $GITHUB_ENV + fi - # Generate Allure HTML report (using simple-elf action) - - name: Generate Allure HTML Report - uses: simple-elf/allure-report-action@v1.8 + # Generate Allure HTML report WITHOUT Docker + - name: Setup Node if: always() + uses: actions/setup-node@v4 with: - allure_results: | - allure-results - allure_report: allure-report - keep_reports: 5 + node-version: "20" + + - name: Install Allure CLI + if: always() + run: npm i -g allure-commandline + + - name: Generate Allure HTML report + if: always() + run: | + if [ "$HAS_ALLURE_RESULTS" = "true" ]; then + rm -rf allure-report || true + allure generate allure-results -o allure-report --clean + echo "Allure report generated." + else + echo "No allure-results found. Skipping report generation." + fi - # Upload generated Allure HTML report - name: Upload Allure HTML Report if: always() uses: actions/upload-artifact@v4 with: name: allure-html-report - path: allure-report + path: allure-report/** + if-no-files-found: warn - # Upload raw Allure results (for local serve) - name: Upload Allure Results if: always() uses: actions/upload-artifact@v4 with: name: allure-results - path: | - allure-results/** + path: allure-results/** + if-no-files-found: warn diff --git a/UI_Automation/appsettings.json b/UI_Automation/appsettings.json index ce4e29b..eed76b2 100644 --- a/UI_Automation/appsettings.json +++ b/UI_Automation/appsettings.json @@ -1,5 +1,5 @@ ο»Ώ{ - "Headless": false, + "Headless": true, "Incognito": true, "Browser": "Chrome", "BaseUrl": "https://store.steampowered.com" From 4e15647ae0f87b3f110700e153f7b1da3f9eda02 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Mon, 9 Feb 2026 23:26:32 +0100 Subject: [PATCH 24/42] commented parallism --- UI_Automation/Setup/TestSetup.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UI_Automation/Setup/TestSetup.cs b/UI_Automation/Setup/TestSetup.cs index b0cad2f..1525dea 100644 --- a/UI_Automation/Setup/TestSetup.cs +++ b/UI_Automation/Setup/TestSetup.cs @@ -15,8 +15,8 @@ //[assembly: Parallelizable(ParallelScope.Self | ParallelScope.Children)] //[assembly: Parallelizable(ParallelScope.Fixtures)] -[assembly: Parallelizable(ParallelScope.All)] -[assembly: LevelOfParallelism(2)] +//[assembly: Parallelizable(ParallelScope.All)] +//[assembly: LevelOfParallelism(2)] namespace UI_Automation.Setup { From 6cf8a25c1d10fce51ff5577dc986d55c55ca4ea5 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Mon, 9 Feb 2026 23:35:55 +0100 Subject: [PATCH 25/42] fixed failed tests --- UI_Automation/Features/CopyAndPaste.feature | 2 +- UI_Automation/Features/CopyAndPaste.feature.cs | 2 +- UI_Automation/Features/SearchAndNavigate.feature | 2 +- UI_Automation/Features/SearchAndNavigate.feature.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/UI_Automation/Features/CopyAndPaste.feature b/UI_Automation/Features/CopyAndPaste.feature index 4d3602f..11cddcb 100644 --- a/UI_Automation/Features/CopyAndPaste.feature +++ b/UI_Automation/Features/CopyAndPaste.feature @@ -34,7 +34,7 @@ Scenario: Paste And I should see the game name "THE FINALS" from the 1st search result When I click on Play Game button And I click on No, I need Steam button - Then I should be redirected to the "adout" page + Then I should be redirected to the "about" page And I should see the Install Steam button is clickable And I should see that Playing Now gamers status are less than Online gamers status diff --git a/UI_Automation/Features/CopyAndPaste.feature.cs b/UI_Automation/Features/CopyAndPaste.feature.cs index 3263394..9d56309 100644 --- a/UI_Automation/Features/CopyAndPaste.feature.cs +++ b/UI_Automation/Features/CopyAndPaste.feature.cs @@ -180,7 +180,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa await testRunner.AndAsync("I should see the game name \"THE FINALS\" from the 1st search result", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); await testRunner.WhenAsync("I click on Play Game button", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); await testRunner.AndAsync("I click on No, I need Steam button", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); - await testRunner.ThenAsync("I should be redirected to the \"adout\" page", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); + await testRunner.ThenAsync("I should be redirected to the \"about\" page", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); await testRunner.AndAsync("I should see the Install Steam button is clickable", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); await testRunner.AndAsync("I should see that Playing Now gamers status are less than Online gamers status", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); } diff --git a/UI_Automation/Features/SearchAndNavigate.feature b/UI_Automation/Features/SearchAndNavigate.feature index 7d32fe1..8d42fa0 100644 --- a/UI_Automation/Features/SearchAndNavigate.feature +++ b/UI_Automation/Features/SearchAndNavigate.feature @@ -34,7 +34,7 @@ Scenario: Navigate to the About page And I should see the game name "THE FINALS" from the 1st search result When I click on Play Game button And I click on No, I need Steam button - Then I should be redirected to the "adout" page + Then I should be redirected to the "about" page And I should see the Install Steam button is clickable And I should see that Playing Now gamers status are less than Online gamers status diff --git a/UI_Automation/Features/SearchAndNavigate.feature.cs b/UI_Automation/Features/SearchAndNavigate.feature.cs index c9d4019..83417cd 100644 --- a/UI_Automation/Features/SearchAndNavigate.feature.cs +++ b/UI_Automation/Features/SearchAndNavigate.feature.cs @@ -180,7 +180,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa await testRunner.AndAsync("I should see the game name \"THE FINALS\" from the 1st search result", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); await testRunner.WhenAsync("I click on Play Game button", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); await testRunner.AndAsync("I click on No, I need Steam button", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); - await testRunner.ThenAsync("I should be redirected to the \"adout\" page", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); + await testRunner.ThenAsync("I should be redirected to the \"about\" page", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); await testRunner.AndAsync("I should see the Install Steam button is clickable", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); await testRunner.AndAsync("I should see that Playing Now gamers status are less than Online gamers status", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); } From 10de78394eb3d639a5f947fda996f6379a08e0c0 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Mon, 9 Feb 2026 23:42:47 +0100 Subject: [PATCH 26/42] title changed --- UI_Automation/Features/CopyAndPaste.feature | 4 ++-- UI_Automation/Features/SearchAndNavigate.feature | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/UI_Automation/Features/CopyAndPaste.feature b/UI_Automation/Features/CopyAndPaste.feature index 11cddcb..d85ddd8 100644 --- a/UI_Automation/Features/CopyAndPaste.feature +++ b/UI_Automation/Features/CopyAndPaste.feature @@ -13,8 +13,8 @@ Feature: Copy and paste Scenario: Copy Given I open Store page When I search for "FIFA" game - Then I should see the first search result "EA SPORTS FCβ„’ 25" - And I should see the second search result "FIFA 22" + Then I should see the first search result "FIFA 22" + And I should see the second search result "EA SPORTS FCβ„’ 25" When I search for "THE FINALS" game And I click on the first search result in the search results Then I should be redirected to the "THE_FINALS" page diff --git a/UI_Automation/Features/SearchAndNavigate.feature b/UI_Automation/Features/SearchAndNavigate.feature index 8d42fa0..01a1bb4 100644 --- a/UI_Automation/Features/SearchAndNavigate.feature +++ b/UI_Automation/Features/SearchAndNavigate.feature @@ -13,8 +13,8 @@ Feature: Search and navigate Scenario: Search for Steam game and navigate to the About page Given I open Store page When I search for "FIFA" game - Then I should see the first search result "EA SPORTS FCβ„’ 25" - And I should see the second search result "FIFA 22" + Then I should see the first search result "FIFA 22" + And I should see the second search result "EA SPORTS FCβ„’ 25" When I search for "THE FINALS" game And I click on the first search result in the search results Then I should be redirected to the "THE_FINALS" page From 519a10779e692bb3e2257504253b88b7493e8a57 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Mon, 9 Feb 2026 23:45:56 +0100 Subject: [PATCH 27/42] changed title --- UI_Automation/Features/CopyAndPaste.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UI_Automation/Features/CopyAndPaste.feature b/UI_Automation/Features/CopyAndPaste.feature index d85ddd8..11cddcb 100644 --- a/UI_Automation/Features/CopyAndPaste.feature +++ b/UI_Automation/Features/CopyAndPaste.feature @@ -13,8 +13,8 @@ Feature: Copy and paste Scenario: Copy Given I open Store page When I search for "FIFA" game - Then I should see the first search result "FIFA 22" - And I should see the second search result "EA SPORTS FCβ„’ 25" + Then I should see the first search result "EA SPORTS FCβ„’ 25" + And I should see the second search result "FIFA 22" When I search for "THE FINALS" game And I click on the first search result in the search results Then I should be redirected to the "THE_FINALS" page From 3fe6085838f0133df3c7e3c41ee4a28786448ec4 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Tue, 10 Feb 2026 09:16:49 +0100 Subject: [PATCH 28/42] fixed copy test --- UI_Automation/Features/SearchAndNavigate.feature.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UI_Automation/Features/SearchAndNavigate.feature.cs b/UI_Automation/Features/SearchAndNavigate.feature.cs index 83417cd..7e9fb55 100644 --- a/UI_Automation/Features/SearchAndNavigate.feature.cs +++ b/UI_Automation/Features/SearchAndNavigate.feature.cs @@ -138,8 +138,8 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa await this.ScenarioStartAsync(); await testRunner.GivenAsync("I open Store page", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); await testRunner.WhenAsync("I search for \"FIFA\" game", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); - await testRunner.ThenAsync("I should see the first search result \"EA SPORTS FCβ„’ 25\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); - await testRunner.AndAsync("I should see the second search result \"FIFA 22\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); + await testRunner.ThenAsync("I should see the first search result \"FIFA 22\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); + await testRunner.AndAsync("I should see the second search result \"EA SPORTS FCβ„’ 25\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); await testRunner.WhenAsync("I search for \"THE FINALS\" game", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); await testRunner.AndAsync("I click on the first search result in the search results", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); await testRunner.ThenAsync("I should be redirected to the \"THE_FINALS\" page", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); From c655da49813c6f41f966bea7cb02f2fedb5c1196 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Tue, 10 Feb 2026 11:41:33 +0100 Subject: [PATCH 29/42] fixed title and time --- UI_Automation/Features/CopyAndPaste.feature | 6 +++--- UI_Automation/Features/SearchAndNavigate.feature | 4 ++-- UI_Automation/Features/SearchAndNavigate.feature.cs | 4 ++-- .../StepDefinitions/SearchAndNavigateStepDefinitions.cs | 2 +- UI_Automation/appsettings.json | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/UI_Automation/Features/CopyAndPaste.feature b/UI_Automation/Features/CopyAndPaste.feature index 11cddcb..a39e806 100644 --- a/UI_Automation/Features/CopyAndPaste.feature +++ b/UI_Automation/Features/CopyAndPaste.feature @@ -9,7 +9,7 @@ Feature: Copy and paste I want to search for game and navigate to the official Steam About page So that I can play games on the Steam platform -@ui +@ui Scenario: Copy Given I open Store page When I search for "FIFA" game @@ -25,7 +25,7 @@ Scenario: Copy And I should see the Install Steam button is clickable And I should see that Playing Now gamers status are less than Online gamers status -@ui +@ui Scenario: Paste Given I open Store page When I search for "THE FINALS" game @@ -38,7 +38,7 @@ Scenario: Paste And I should see the Install Steam button is clickable And I should see that Playing Now gamers status are less than Online gamers status -@ui +@ui Scenario: Cut Given I open Store page When I search for "THE FINALS" game diff --git a/UI_Automation/Features/SearchAndNavigate.feature b/UI_Automation/Features/SearchAndNavigate.feature index 01a1bb4..8d42fa0 100644 --- a/UI_Automation/Features/SearchAndNavigate.feature +++ b/UI_Automation/Features/SearchAndNavigate.feature @@ -13,8 +13,8 @@ Feature: Search and navigate Scenario: Search for Steam game and navigate to the About page Given I open Store page When I search for "FIFA" game - Then I should see the first search result "FIFA 22" - And I should see the second search result "EA SPORTS FCβ„’ 25" + Then I should see the first search result "EA SPORTS FCβ„’ 25" + And I should see the second search result "FIFA 22" When I search for "THE FINALS" game And I click on the first search result in the search results Then I should be redirected to the "THE_FINALS" page diff --git a/UI_Automation/Features/SearchAndNavigate.feature.cs b/UI_Automation/Features/SearchAndNavigate.feature.cs index 7e9fb55..83417cd 100644 --- a/UI_Automation/Features/SearchAndNavigate.feature.cs +++ b/UI_Automation/Features/SearchAndNavigate.feature.cs @@ -138,8 +138,8 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa await this.ScenarioStartAsync(); await testRunner.GivenAsync("I open Store page", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); await testRunner.WhenAsync("I search for \"FIFA\" game", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); - await testRunner.ThenAsync("I should see the first search result \"FIFA 22\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); - await testRunner.AndAsync("I should see the second search result \"EA SPORTS FCβ„’ 25\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); + await testRunner.ThenAsync("I should see the first search result \"EA SPORTS FCβ„’ 25\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); + await testRunner.AndAsync("I should see the second search result \"FIFA 22\"", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); await testRunner.WhenAsync("I search for \"THE FINALS\" game", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); await testRunner.AndAsync("I click on the first search result in the search results", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); await testRunner.ThenAsync("I should be redirected to the \"THE_FINALS\" page", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); diff --git a/UI_Automation/StepDefinitions/SearchAndNavigateStepDefinitions.cs b/UI_Automation/StepDefinitions/SearchAndNavigateStepDefinitions.cs index 9f67ae4..68a3105 100644 --- a/UI_Automation/StepDefinitions/SearchAndNavigateStepDefinitions.cs +++ b/UI_Automation/StepDefinitions/SearchAndNavigateStepDefinitions.cs @@ -19,7 +19,7 @@ public class SearchAndNavigateStepDefinitions public SearchAndNavigateStepDefinitions(IWebDriver driver, StorePage storePage, AboutPage aboutPage, ScenarioContext scenarioContext) { _driver = driver; - _wait = new WebDriverWait(_driver, TimeSpan.Zero); + _wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(10)); _config = Config.Load(); _storePage = storePage; _aboutPage = aboutPage; diff --git a/UI_Automation/appsettings.json b/UI_Automation/appsettings.json index eed76b2..ce4e29b 100644 --- a/UI_Automation/appsettings.json +++ b/UI_Automation/appsettings.json @@ -1,5 +1,5 @@ ο»Ώ{ - "Headless": true, + "Headless": false, "Incognito": true, "Browser": "Chrome", "BaseUrl": "https://store.steampowered.com" From 6325247a17f8503939a9a69305b460e0b7b8df67 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Tue, 10 Feb 2026 15:14:08 +0100 Subject: [PATCH 30/42] updated yaml --- .github/workflows/main.yml | 136 ++++++++++++++++++--------------- UI_Automation/appsettings.json | 2 +- automation-tests.yml | 80 ------------------- 3 files changed, 77 insertions(+), 141 deletions(-) delete mode 100644 automation-tests.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9b32f69..0109895 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,8 +2,7 @@ name: .NET Reqnroll Automation Tests on: push: - branches: - - master + branches: [ "master" ] workflow_dispatch: inputs: test_type: @@ -11,88 +10,103 @@ on: required: true default: "All" type: choice - options: - - UI - - API - - All + options: [ "UI", "API", "All" ] browser: description: "Select browser" required: true default: "Chrome" type: choice - options: - - Chrome - - Edge - - Both + options: [ "Chrome", "Edge", "Both" ] jobs: build-and-test: - runs-on: ubuntu-latest + runs-on: windows-latest env: - # Default values for push runs (workflow_dispatch has inputs, push doesn't) + 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' }} steps: - - name: Checkout code + - name: Checkout uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: "8.0.x" + dotnet-version: ${{ env.DOTNET_VERSION }} - - name: Restore dependencies + - name: dotnet --info + run: dotnet --info + + - name: Restore run: dotnet restore - - name: Build solution - run: dotnet build --no-restore --configuration Release + - name: Build (Release) + run: dotnet build --configuration Release --no-restore - - name: Run tests + - name: Run tests (Release) + shell: pwsh run: | - echo "Running tests for: $TEST_TYPE" - echo "Browser: $BROWSER" - - if [ "$TEST_TYPE" = "UI" ]; then - dotnet test UI_Automation --configuration Release --logger "trx;LogFileName=ui-tests.trx" - elif [ "$TEST_TYPE" = "API" ]; then - dotnet test API_Automation --configuration Release --logger "trx;LogFileName=api-tests.trx" - else - dotnet test --configuration Release --logger "trx;LogFileName=all-tests.trx" - fi - - # Merge Allure results from anywhere in the repo into one folder + 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: bash + shell: pwsh run: | - echo "Collecting allure-results..." - rm -rf allure-results || true - mkdir -p allure-results - - # Find any allure-results folders (Reqnroll/Allure can put them under bin/... or project root) - mapfile -t dirs < <(find . -type d -name "allure-results" -not -path "./allure-results" 2>/dev/null || true) + 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 - echo "Found allure-results dirs:" - printf '%s\n' "${dirs[@]}" + $dirs = Get-ChildItem -Path . -Recurse -Directory -Filter "allure-results" | + Where-Object { $_.FullName -notmatch "\\allure-results$" } - for d in "${dirs[@]}"; do - echo "Copying from: $d" - cp -r "$d"/. allure-results/ 2>/dev/null || true - done + Write-Host "Found allure-results dirs:" + $dirs | ForEach-Object { Write-Host $_.FullName } - echo "Final allure-results content:" - ls -la allure-results || true + foreach ($d in $dirs) { + Copy-Item -Path (Join-Path $d.FullName "*") -Destination "allure-results" -Recurse -Force -ErrorAction SilentlyContinue + } - # Create a flag so next steps can decide whether to generate report - if [ -n "$(ls -A allure-results 2>/dev/null)" ]; then - echo "HAS_ALLURE_RESULTS=true" >> $GITHUB_ENV - else - echo "HAS_ALLURE_RESULTS=false" >> $GITHUB_ENV - fi + 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 WITHOUT Docker + # Generate Allure HTML report (no Docker) using Node + allure-commandline - name: Setup Node if: always() uses: actions/setup-node@v4 @@ -105,16 +119,18 @@ jobs: - name: Generate Allure HTML report if: always() + shell: pwsh run: | - if [ "$HAS_ALLURE_RESULTS" = "true" ]; then - rm -rf allure-report || true + 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 - echo "Allure report generated." - else - echo "No allure-results found. Skipping report generation." - fi + Write-Host "Allure report generated." + } + else { + Write-Host "No allure-results found. Skipping report generation." + } - - name: Upload Allure HTML Report + - name: Upload Allure HTML report if: always() uses: actions/upload-artifact@v4 with: @@ -122,7 +138,7 @@ jobs: path: allure-report/** if-no-files-found: warn - - name: Upload Allure Results + - name: Upload Allure results if: always() uses: actions/upload-artifact@v4 with: diff --git a/UI_Automation/appsettings.json b/UI_Automation/appsettings.json index ce4e29b..eed76b2 100644 --- a/UI_Automation/appsettings.json +++ b/UI_Automation/appsettings.json @@ -1,5 +1,5 @@ ο»Ώ{ - "Headless": false, + "Headless": true, "Incognito": true, "Browser": "Chrome", "BaseUrl": "https://store.steampowered.com" diff --git a/automation-tests.yml b/automation-tests.yml deleted file mode 100644 index c0e4759..0000000 --- a/automation-tests.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: Automation Test Pipeline (.NET + Reqnroll + Allure) - -on: - workflow_dispatch: - inputs: - test_type: - description: 'Select which tests to run' - required: true - type: choice - options: - - UI - - API - - All - browser: - description: 'Select browser' - required: true - type: choice - options: - - Chrome - - Edge - - Both - -jobs: - build-and-test: - runs-on: windows-latest - env: - DOTNET_VERSION: 8.0.x - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: ${{ env.DOTNET_VERSION }} - - - name: Restore dependencies - run: dotnet restore Csharp_Automation_Task.sln - - - name: Build solution - run: dotnet build Csharp_Automation_Task.sln --configuration Release --no-restore - - # ========================== - # Run UI / API / All tests - # ========================== - - name: Run Tests - run: | - if ("${{ github.event.inputs.test_type }}" -eq "UI") { - dotnet test UI_Automation/UI_Automation.csproj --configuration Release --filter "ReqnrollTags=@ui" --logger "trx;LogFileName=ui-results.trx" - } - elseif ("${{ github.event.inputs.test_type }}" -eq "API") { - dotnet test API_Automation/API_Automation.csproj --configuration Release --filter "ReqnrollTags=@api" --logger "trx;LogFileName=api-results.trx" - } - else { - dotnet test Csharp_Automation_Task.sln --configuration Release --logger "trx;LogFileName=all-results.trx" - } - - # ========================== - # Run Allure Report - # ========================== - - name: Generate Allure Results - if: always() - run: | - echo "Allure results generated in allure-results/" - dir allure-results || echo "No allure results folder found" - - - name: Upload Allure Results - if: always() - uses: actions/upload-artifact@v4 - with: - name: allure-results - path: allure-results/ - - - name: Upload TRX Results - if: always() - uses: actions/upload-artifact@v4 - with: - name: dotnet-test-results - path: '**/*.trx' From d8e47d22d63ac1db7c3ef16601324e2d4ba4ca1f Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Tue, 17 Feb 2026 09:57:29 +0100 Subject: [PATCH 31/42] added jenkins, docker, selenium grid --- API_Automation/API_Automation.csproj | 16 +-- API_Automation/Client/RestApiClient.cs | 10 +- API_Automation/Helpers/Logger.cs | 2 +- API_Automation/ImplicitUsings.cs | 1 - API_Automation/dockerfile | 11 ++ Csharp_Automation_Task.slnx | 1 + .../Hooks/AllureStepScreenshotHook.cs | 7 +- UI_Automation/Pages/AboutPage.cs | 2 +- UI_Automation/Pages/StorePage.cs | 98 ++++++++++++----- UI_Automation/Reqnroll/userid | 1 + UI_Automation/Setup/TestSetup.cs | 101 ++++++++++++------ .../SearchAndNavigateStepDefinitions.cs | 24 ++--- UI_Automation/Support/Config.cs | 29 +++-- UI_Automation/UI_Automation.csproj | 25 ++--- UI_Automation/appsettings.json | 5 +- UI_Automation/dockerfile | 14 +++ docker-compose.yml | 34 ++++++ global.json | 6 ++ 18 files changed, 270 insertions(+), 117 deletions(-) create mode 100644 API_Automation/dockerfile create mode 100644 UI_Automation/Reqnroll/userid create mode 100644 UI_Automation/dockerfile create mode 100644 docker-compose.yml create mode 100644 global.json diff --git a/API_Automation/API_Automation.csproj b/API_Automation/API_Automation.csproj index b46b5b5..adf5335 100644 --- a/API_Automation/API_Automation.csproj +++ b/API_Automation/API_Automation.csproj @@ -9,15 +9,15 @@ - - - - - - + + + + + + - - + + diff --git a/API_Automation/Client/RestApiClient.cs b/API_Automation/Client/RestApiClient.cs index 60ebad4..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 + Timeout = TimeSpan.FromSeconds(timeoutSeconds) }; + _client = new RestClient(options); } 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/dockerfile b/API_Automation/dockerfile new file mode 100644 index 0000000..8a76b06 --- /dev/null +++ b/API_Automation/dockerfile @@ -0,0 +1,11 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0 + +WORKDIR /app +COPY . . + +WORKDIR /app/API_Automation + +RUN dotnet restore +RUN dotnet build -c Release + +ENTRYPOINT ["dotnet", "test", "API_Automation.csproj", "-c", "Release"] diff --git a/Csharp_Automation_Task.slnx b/Csharp_Automation_Task.slnx index a4d6470..c3e099c 100644 --- a/Csharp_Automation_Task.slnx +++ b/Csharp_Automation_Task.slnx @@ -1,6 +1,7 @@ + diff --git a/UI_Automation/Hooks/AllureStepScreenshotHook.cs b/UI_Automation/Hooks/AllureStepScreenshotHook.cs index 7f49e17..187a238 100644 --- a/UI_Automation/Hooks/AllureStepScreenshotHook.cs +++ b/UI_Automation/Hooks/AllureStepScreenshotHook.cs @@ -1,7 +1,6 @@ -ο»Ώusing System.Text; -using Allure.Net.Commons; +ο»Ώusing Allure.Net.Commons; using OpenQA.Selenium; -using Reqnroll; +using System.Text; namespace UI_Automation.Hooks { @@ -18,7 +17,7 @@ public AllureStepScreenshotHook(ScenarioContext scenarioContext) [AfterStep(Order = 9999)] public void AfterStep() { - + if (_scenarioContext.TestError == null) return; diff --git a/UI_Automation/Pages/AboutPage.cs b/UI_Automation/Pages/AboutPage.cs index 6792610..45157db 100644 --- a/UI_Automation/Pages/AboutPage.cs +++ b/UI_Automation/Pages/AboutPage.cs @@ -6,7 +6,7 @@ public class AboutPage private readonly IWebDriver _driver; #region Locators - + private IWebElement InstallSteamButton => _driver.FindElement(By.CssSelector("#about_greeting .about_install_steam_link")); private IList OnlineStatus => _driver.FindElements(By.XPath("//div[contains(@class,'gamers_online')]/parent::div")); private IList PlayingNowStatus => _driver.FindElements(By.XPath("//div[contains(@class,'gamers_in_game')]/parent::div")); diff --git a/UI_Automation/Pages/StorePage.cs b/UI_Automation/Pages/StorePage.cs index e08350d..2a3cdb4 100644 --- a/UI_Automation/Pages/StorePage.cs +++ b/UI_Automation/Pages/StorePage.cs @@ -1,4 +1,5 @@ ο»Ώusing OpenQA.Selenium; +using OpenQA.Selenium.Support.UI; namespace UI_Automation.Pages { @@ -6,77 +7,116 @@ public class StorePage { private readonly IWebDriver _driver; private readonly IJavaScriptExecutor _jsExecutor; + private readonly WebDriverWait _wait; - #region Locators + #region Locators (By) + + private readonly By SearchBoxInput = By.XPath("//input[@class='_2tlUAG6WNyYFlk9caIiLj5']"); + private readonly By SearchResultsRows = By.CssSelector(".search_result_row"); + private readonly By GameNameHeading = By.Id("appHubAppName"); + private readonly By PlayGameButton = By.Id("freeGameBtn"); + private readonly By NoINeedSteamButton = By.XPath("//h3[contains(text(),'No, I need Steam')]"); - private IWebElement SearchBox => _driver.FindElement(By.XPath("//input[@class='_2tlUAG6WNyYFlk9caIiLj5']")); - private IList SearchResults => _driver.FindElements(By.CssSelector(".search_result_row")); - private IWebElement GameNameHeading => _driver.FindElement(By.Id("appHubAppName")); - private IWebElement PlayGameButton => _driver.FindElement(By.Id("freeGameBtn")); - private IWebElement NoINeedSteamButton => _driver.FindElement(By.XPath("//h3[contains(text(),'No, I need Steam')]")); #endregion public StorePage(IWebDriver driver) { _driver = driver; _jsExecutor = (IJavaScriptExecutor)driver; + _wait = new WebDriverWait(driver, TimeSpan.FromSeconds(20)); + } + + #region Helpers + + private IWebElement WaitVisible(By by) + => _wait.Until(SeleniumExtras.WaitHelpers.ExpectedConditions.ElementIsVisible(by)); + + private IWebElement WaitClickable(By by) + => _wait.Until(SeleniumExtras.WaitHelpers.ExpectedConditions.ElementToBeClickable(by)); + + private IReadOnlyCollection WaitForSearchResults(int minCount = 1) + { + _wait.Until(d => + { + var els = d.FindElements(SearchResultsRows); + return els != null && els.Count >= minCount; + }); + + return _driver.FindElements(SearchResultsRows); + } + + private void ScrollTo(By by, int yShift = 350) + { + var el = WaitVisible(by); + var linkYPositionShift = el.Location.Y - yShift; + _jsExecutor.ExecuteScript("window.scrollBy(0, arguments[0]);", linkYPositionShift); } + #endregion + #region Actions - public void SearchForGame(string gameName) { - SearchBox.Clear(); - SearchBox.SendKeys(gameName); - SearchBox.SendKeys(Keys.Enter); + var search = WaitVisible(SearchBoxInput); + search.Clear(); + search.SendKeys(gameName); + search.SendKeys(Keys.Enter); + + // wait that results load (prevents "Sequence contains no elements") + WaitForSearchResults(1); } public string GetFirstSearchResultText() { - return SearchResults.First().Text; + var results = WaitForSearchResults(1); + return results.First().Text; } public string GetSecondSearchResultText() { - return SearchResults.Count > 1 ? SearchResults[1].Text : string.Empty; + var results = WaitForSearchResults(2); + return results.Skip(1).First().Text; + } + + public void ClickFirstSearchResult() + { + var results = WaitForSearchResults(1); + results.First().Click(); } public void ClickFirstSearchResultWithJs() { - var firstResult = SearchResults.FirstOrDefault(); - if (firstResult != null) - { - _jsExecutor.ExecuteScript("arguments[0].click();", firstResult); - } + var results = WaitForSearchResults(1); + var first = results.First(); + _jsExecutor.ExecuteScript("arguments[0].click();", first); } - public string GetPageUrl() + public void WaitForGameDetailsPage() { - return _driver.Url; + // Steam page sometimes loads slower in Grid/headless + WaitVisible(GameNameHeading); } + public string GetPageUrl() => _driver.Url; + public string GetGameNameHeadingText() { - return GameNameHeading.Text; + WaitForGameDetailsPage(); + return WaitVisible(GameNameHeading).Text; } public void ClickPlayGameButton() { - ScrollToElement(PlayGameButton); - PlayGameButton.Click(); + ScrollTo(PlayGameButton); + WaitClickable(PlayGameButton).Click(); } public void ClickNoINeedSteamButton() { - NoINeedSteamButton.Click(); + WaitClickable(NoINeedSteamButton).Click(); } - private void ScrollToElement(IWebElement element) - { - var linkYPositionShift = element.Location.Y - 350; - _jsExecutor.ExecuteScript("window.scrollBy(0," + linkYPositionShift + ");"); - } + #endregion } - #endregion } diff --git a/UI_Automation/Reqnroll/userid b/UI_Automation/Reqnroll/userid new file mode 100644 index 0000000..bd09a63 --- /dev/null +++ b/UI_Automation/Reqnroll/userid @@ -0,0 +1 @@ +fc99c5a2-452d-4e45-bb2c-378d19af1108 \ No newline at end of file diff --git a/UI_Automation/Setup/TestSetup.cs b/UI_Automation/Setup/TestSetup.cs index 1525dea..7540669 100644 --- a/UI_Automation/Setup/TestSetup.cs +++ b/UI_Automation/Setup/TestSetup.cs @@ -4,10 +4,8 @@ using OpenQA.Selenium.Chrome; using OpenQA.Selenium.Edge; using OpenQA.Selenium.Firefox; -using Reqnroll; +using OpenQA.Selenium.Remote; using Reqnroll.BoDi; -using System.Collections.Generic; -using System.IO; using System.Runtime.InteropServices; using System.Text; using UI_Automation.Enums; @@ -15,8 +13,8 @@ //[assembly: Parallelizable(ParallelScope.Self | ParallelScope.Children)] //[assembly: Parallelizable(ParallelScope.Fixtures)] -//[assembly: Parallelizable(ParallelScope.All)] -//[assembly: LevelOfParallelism(2)] +[assembly: Parallelizable(ParallelScope.All)] +[assembly: LevelOfParallelism(2)] namespace UI_Automation.Setup { @@ -24,14 +22,14 @@ namespace UI_Automation.Setup public class TestSetup : IDisposable { private readonly IObjectContainer _container; - private readonly ScenarioContext _scenarioContext; + private readonly ScenarioContext _scenarioContext; private IWebDriver _driver; private static Config _config; public TestSetup(IObjectContainer container, ScenarioContext scenarioContext) { _container = container; - _scenarioContext = scenarioContext; + _scenarioContext = scenarioContext; } [BeforeTestRun] @@ -44,48 +42,88 @@ public static void GlobalSetup() ?? Path.Combine(AppContext.BaseDirectory, "allure-results"); Directory.CreateDirectory(resultsDir); - // environment.properties + var osName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "Windows" : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "macOS" : "Linux"; File.WriteAllLines(Path.Combine(resultsDir, "environment.properties"), new[] { - "env=QA", - $"baseUrl={_config.BaseUrl}", - $"browser={_config.Browser}", - $"os={osName}", - "framework=Reqnroll + NUnit + Selenium" - }); - - + "env=QA", + $"baseUrl={_config.BaseUrl}", + $"browser={_config.Browser}", + $"os={osName}", + "framework=Reqnroll + NUnit + Selenium" + }); + File.WriteAllText(Path.Combine(resultsDir, "executor.json"), """ - { - "name": "Local Run", - "type": "other", - "buildName": "UI_Automation" - } - """, Encoding.UTF8); + { + "name": "Local Run", + "type": "other", + "buildName": "UI_Automation" + } + """, Encoding.UTF8); } [BeforeScenario] public void InitializeWebDriver() { - _driver = CreateWebDriver(_config.Browser, _config.Headless, _config.Incognito); + // Remote toggle (Docker/Grid) + var useRemote = IsRemoteEnabled(); + + _driver = CreateWebDriver( + _config.Browser, + _config.Headless, + _config.Incognito, + useRemote + ); + try { _driver.Manage().Window.Maximize(); } catch { } - _driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10); - - _container.RegisterInstanceAs(_driver); - + _container.RegisterInstanceAs(_driver); _scenarioContext["driver"] = _driver; - Logger.Log($"Initialized WebDriver for browser: {_config.Browser}, Headless: {_config.Headless}, Incognito: {_config.Incognito}"); + Logger.Log($"Initialized WebDriver for browser: {_config.Browser}, Headless: {_config.Headless}, Incognito: {_config.Incognito}, Remote: {useRemote}"); + } + + private static bool IsRemoteEnabled() + { + var raw = Environment.GetEnvironmentVariable("USE_REMOTE") ?? "false"; + return raw.Equals("true", StringComparison.OrdinalIgnoreCase) || + raw.Equals("1", StringComparison.OrdinalIgnoreCase) || + raw.Equals("yes", StringComparison.OrdinalIgnoreCase); + } + + private static string GetRemoteUrl() + { + // docker-compose: http://selenium:4444/wd/hub + // local host: http://localhost:4444/wd/hub + return Environment.GetEnvironmentVariable("SELENIUM_REMOTE_URL") + ?? "http://selenium:4444/wd/hub"; + } - private static IWebDriver CreateWebDriver(string browserName, bool headless, bool incognito) + private static IWebDriver CreateWebDriver(string browserName, bool headless, bool incognito, bool useRemote) { + //If remote is enabled β†’ use Selenium Grid (docker chrome) + if (useRemote) + { + var remoteUrl = GetRemoteUrl(); + + // Remote side is typically Chrome in selenium/standalone-chrome + // Keep options aligned with your existing flags. + var options = new ChromeOptions(); + if (headless) options.AddArgument("--headless=new"); + if (incognito) options.AddArgument("--incognito"); + + // Optional stability options (safe for Docker) + options.AddArgument("--no-sandbox"); + options.AddArgument("--disable-dev-shm-usage"); + + return new RemoteWebDriver(new Uri(remoteUrl), options); + } + if (Enum.TryParse(browserName, true, out BrowserType browser)) { switch (browser) @@ -118,7 +156,8 @@ private static IWebDriver CreateWebDriver(string browserName, bool headless, boo return new OpenQA.Selenium.Safari.SafariDriver(); } } - throw new ArgumentOutOfRangeException(nameof(browser), $"Browser '{browser}' is not supported."); + + throw new ArgumentOutOfRangeException(nameof(browserName), $"Browser '{browserName}' is not supported."); } public void NavigateToBaseUrl() @@ -140,7 +179,5 @@ public void Dispose() _driver = null; } } - } } - diff --git a/UI_Automation/StepDefinitions/SearchAndNavigateStepDefinitions.cs b/UI_Automation/StepDefinitions/SearchAndNavigateStepDefinitions.cs index 68a3105..b571f61 100644 --- a/UI_Automation/StepDefinitions/SearchAndNavigateStepDefinitions.cs +++ b/UI_Automation/StepDefinitions/SearchAndNavigateStepDefinitions.cs @@ -19,22 +19,22 @@ public class SearchAndNavigateStepDefinitions public SearchAndNavigateStepDefinitions(IWebDriver driver, StorePage storePage, AboutPage aboutPage, ScenarioContext scenarioContext) { _driver = driver; - _wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(10)); + _wait = new WebDriverWait(_driver, TimeSpan.Zero); _config = Config.Load(); _storePage = storePage; _aboutPage = aboutPage; } [Given("I open Store page")] - + public void GivenIOpenStorePage() { - _driver.Navigate().GoToUrl(_config.BaseUrl); + _driver.Navigate().GoToUrl(_config.BaseUrl); Logger.Log("Navigated to Steam Store page: " + _config.BaseUrl); } [When("I search for {string} game")] - + public void WhenISearchForGame(string gameName) { _storePage.SearchForGame(gameName); @@ -42,7 +42,7 @@ public void WhenISearchForGame(string gameName) } [Then("I should see the first search result {string}")] - + public void ThenIShouldSeeTheFirstSearchResult(string searchedFirstGame) { Assert.That(_storePage.GetFirstSearchResultText(), Does.Contain(searchedFirstGame), "Expected the first search result to not be null or empty"); @@ -50,7 +50,7 @@ public void ThenIShouldSeeTheFirstSearchResult(string searchedFirstGame) } [Then("I should see the second search result {string}")] - + public void ThenIShouldSeeTheSecondSearchResult(string searchedSecondGame) { Assert.That(_storePage.GetSecondSearchResultText(), Does.Contain(searchedSecondGame), "Expected the second search result to not be null or empty"); @@ -58,7 +58,7 @@ public void ThenIShouldSeeTheSecondSearchResult(string searchedSecondGame) } [When("I click on the first search result in the search results")] - + public void WhenIClickOnTheFirstSearchResultInTheSearchResults() { _storePage.ClickFirstSearchResultWithJs(); @@ -84,7 +84,7 @@ public void ThenIShouldBeRedirectedToThePage(string pageUrl) } [Then("I should see the game name {string} from the 1st search result")] - + public void ThenIShouldSeeTheGameNameFromTheStSearchResult(string gameName) { Assert.That(_storePage.GetGameNameHeadingText(), Does.Contain(gameName), "Expected the game name heading text to not be null or empty"); @@ -92,7 +92,7 @@ public void ThenIShouldSeeTheGameNameFromTheStSearchResult(string gameName) } [When("I click on Play Game button")] - + public void WhenIClickOnPlayGameButton() { _storePage.ClickPlayGameButton(); @@ -100,7 +100,7 @@ public void WhenIClickOnPlayGameButton() } [When("I click on No, I need Steam button")] - + public void WhenIClickOnNoINeedSteamButton() { _storePage.ClickNoINeedSteamButton(); @@ -108,7 +108,7 @@ public void WhenIClickOnNoINeedSteamButton() } [Then("I should see the Install Steam button is clickable")] - + public void ThenIShouldSeeTheInstallSteamButtonIsClickable() { Assert.That(_aboutPage.IsInstallSteamButtonClickable(), Is.True, "Expected the Install Steam button to be clickable"); @@ -116,7 +116,7 @@ public void ThenIShouldSeeTheInstallSteamButtonIsClickable() } [Then("I should see that Playing Now gamers status are less than Online gamers status")] - + public void ThenIShouldSeeThatPlayingNowGamersStatusAreLessThanOnlineGamersStatus() { Assert.That(_aboutPage.CompareIfPlayingNowStatusIsLessThanOnlineStatus(), Is.True, "Expected to find at least one Online status element"); diff --git a/UI_Automation/Support/Config.cs b/UI_Automation/Support/Config.cs index 6d4e40c..82403d8 100644 --- a/UI_Automation/Support/Config.cs +++ b/UI_Automation/Support/Config.cs @@ -3,33 +3,35 @@ namespace UI_Automation.Support { - public class Config { public string BaseUrl { get; set; } public string Browser { get; set; } - public bool Headless { get; set; } = true; + public bool Headless { get; set; } = true; public bool Incognito { get; set; } = true; + public bool UseRemote { get; set; } = false; + public string RemoteUrl { get; set; } = "http://localhost:4444/wd/hub"; - private static Config _instance; private static readonly object _lock = new(); - [JsonConstructor] - public Config(string baseUrl, string browser, bool headless = true, bool incognito = true) + public Config( + string baseUrl, + string browser, + bool headless = true, + bool incognito = true, + bool useRemote = false, + string remoteUrl = "http://localhost:4444/wd/hub") { BaseUrl = baseUrl; Browser = browser; Headless = headless; Incognito = incognito; + UseRemote = useRemote; + RemoteUrl = remoteUrl; } - /// - /// Loads configuration from appsettings.json and returns a singleton instance. - /// - /// The name of the settings file (default: "appsettings.json"). - /// Config instance with properties set from the file. public static Config Load(string fileName = "appsettings.json") { if (_instance != null) @@ -47,7 +49,12 @@ public static Config Load(string fileName = "appsettings.json") throw new FileNotFoundException($"Configuration file '{fileName}' not found at '{filePath}'."); var json = File.ReadAllText(filePath); - _instance = JsonSerializer.Deserialize(json); + + _instance = JsonSerializer.Deserialize(json, + new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); if (_instance == null) throw new InvalidOperationException("Failed to deserialize configuration."); diff --git a/UI_Automation/UI_Automation.csproj b/UI_Automation/UI_Automation.csproj index 024dc0d..518c020 100644 --- a/UI_Automation/UI_Automation.csproj +++ b/UI_Automation/UI_Automation.csproj @@ -11,27 +11,28 @@ - - - + + + + - - + + - - - + + + - + - - - + + + diff --git a/UI_Automation/appsettings.json b/UI_Automation/appsettings.json index eed76b2..01b8110 100644 --- a/UI_Automation/appsettings.json +++ b/UI_Automation/appsettings.json @@ -2,5 +2,8 @@ "Headless": true, "Incognito": true, "Browser": "Chrome", - "BaseUrl": "https://store.steampowered.com" + "BaseUrl": "https://store.steampowered.com", + + "UseRemote": true, + "RemoteUrl": "http://localhost:4444/wd/hub" } diff --git a/UI_Automation/dockerfile b/UI_Automation/dockerfile new file mode 100644 index 0000000..a5c4abd --- /dev/null +++ b/UI_Automation/dockerfile @@ -0,0 +1,14 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0 + +WORKDIR /app +COPY . . + +WORKDIR /app/UI_Automation + +RUN dotnet restore +RUN dotnet build -c Release + +ENV USE_REMOTE=true +ENV SELENIUM_REMOTE_URL=http://selenium:4444/wd/hub + +ENTRYPOINT ["dotnet", "test", "UI_Automation.csproj", "-c", "Release"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..09f81cd --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,34 @@ +name: csharp-automation-task + +services: + selenium: + image: selenium/standalone-chrome:latest + shm_size: 2gb + ports: + - "4444:4444" + environment: + - SE_NODE_MAX_SESSIONS=2 + - SE_NODE_OVERRIDE_MAX_SESSIONS=true + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:4444/status"] + interval: 5s + timeout: 5s + retries: 20 + + ui-tests: + build: + context: . + dockerfile: UI_Automation/Dockerfile + image: csharp-automation-task-ui-tests:latest + depends_on: + selenium: + condition: service_healthy + environment: + - USE_REMOTE=true + - SELENIUM_REMOTE_URL=http://selenium:4444/wd/hub + + api-tests: + build: + context: . + dockerfile: API_Automation/Dockerfile + image: csharp-automation-task-api-tests:latest diff --git a/global.json b/global.json new file mode 100644 index 0000000..ed75c1b --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "8.0.417", + "rollForward": "latestPatch" + } +} From 369f9c858f7418fe2d2a02cab8b056dd8e14374b Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Tue, 17 Feb 2026 10:31:36 +0100 Subject: [PATCH 32/42] yaml updated to slnx --- .github/workflows/main.yml | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0109895..1fa6e6f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,7 +24,7 @@ jobs: env: DOTNET_VERSION: "8.0.x" - # Defaults for push (because push has no inputs) + SOLUTION: ".\\Csharp_Automation_Task.slnx" TEST_TYPE: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.test_type || 'All' }} BROWSER: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.browser || 'Chrome' }} @@ -40,11 +40,11 @@ jobs: - name: dotnet --info run: dotnet --info - - name: Restore - run: dotnet restore + - name: Restore (solution) + run: dotnet restore ${{ env.SOLUTION }} - name: Build (Release) - run: dotnet build --configuration Release --no-restore + run: dotnet build ${{ env.SOLUTION }} --configuration Release --no-restore - name: Run tests (Release) shell: pwsh @@ -52,8 +52,7 @@ jobs: 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. + # Pass browser via ENV (if your framework reads it) $env:BROWSER = "$env:BROWSER" if ($env:TEST_TYPE -eq "UI") { @@ -65,11 +64,13 @@ jobs: --logger "trx;LogFileName=api-tests.trx" } else { - dotnet test --configuration Release --no-build ` - --logger "trx;LogFileName=all-tests.trx" + dotnet test .\UI_Automation\UI_Automation.csproj --configuration Release --no-build ` + --logger "trx;LogFileName=ui-tests.trx" + + dotnet test .\API_Automation\API_Automation.csproj --configuration Release --no-build ` + --logger "trx;LogFileName=api-tests.trx" } - # Upload TRX files always (success or fail) - name: Upload TRX test results if: always() uses: actions/upload-artifact@v4 @@ -79,7 +80,6 @@ jobs: **/*.trx if-no-files-found: warn - # Collect allure-results from anywhere into one folder - name: Collect Allure results if: always() shell: pwsh @@ -106,7 +106,6 @@ jobs: 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 From 9619bb8fff90f43b651addfddc3e99939ba7cdd1 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Tue, 17 Feb 2026 10:49:01 +0100 Subject: [PATCH 33/42] yaml fix --- .github/workflows/main.yml | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1fa6e6f..05e9d76 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,7 +24,7 @@ jobs: env: DOTNET_VERSION: "8.0.x" - SOLUTION: ".\\Csharp_Automation_Task.slnx" + # FIX: do NOT use .slnx in CI TEST_TYPE: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.test_type || 'All' }} BROWSER: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.browser || 'Chrome' }} @@ -40,11 +40,23 @@ jobs: - name: dotnet --info run: dotnet --info - - name: Restore (solution) - run: dotnet restore ${{ env.SOLUTION }} + # ========================= + # FIX: Restore per project + # ========================= + - name: Restore (projects) + shell: pwsh + run: | + dotnet restore .\UI_Automation\UI_Automation.csproj + dotnet restore .\API_Automation\API_Automation.csproj + + # ========================= + # FIX: Build per project + # ========================= + - name: Build UI (Release) + run: dotnet build .\UI_Automation\UI_Automation.csproj --configuration Release --no-restore - - name: Build (Release) - run: dotnet build ${{ env.SOLUTION }} --configuration Release --no-restore + - name: Build API (Release) + run: dotnet build .\API_Automation\API_Automation.csproj --configuration Release --no-restore - name: Run tests (Release) shell: pwsh From 2abea7223d253b396773400da9e148e94a5ad638 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Tue, 17 Feb 2026 11:35:43 +0100 Subject: [PATCH 34/42] added new sln --- Csharp_Automation_Task.sln | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 Csharp_Automation_Task.sln diff --git a/Csharp_Automation_Task.sln b/Csharp_Automation_Task.sln new file mode 100644 index 0000000..3215ac4 --- /dev/null +++ b/Csharp_Automation_Task.sln @@ -0,0 +1,28 @@ +ο»Ώ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "API_Automation", "API_Automation\API_Automation.csproj", "{E4562554-D396-4CF4-8575-A9A56340DE1D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UI_Automation", "UI_Automation\UI_Automation.csproj", "{5F6AA054-7E59-4B01-9768-6DB105BF2FA2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E4562554-D396-4CF4-8575-A9A56340DE1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E4562554-D396-4CF4-8575-A9A56340DE1D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E4562554-D396-4CF4-8575-A9A56340DE1D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E4562554-D396-4CF4-8575-A9A56340DE1D}.Release|Any CPU.Build.0 = Release|Any CPU + {5F6AA054-7E59-4B01-9768-6DB105BF2FA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5F6AA054-7E59-4B01-9768-6DB105BF2FA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5F6AA054-7E59-4B01-9768-6DB105BF2FA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5F6AA054-7E59-4B01-9768-6DB105BF2FA2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From 2f8b0cbc332f5ef40f985cbd0091ddc3a8f532c0 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Fri, 13 Mar 2026 10:52:06 +0100 Subject: [PATCH 35/42] refactoring --- .github/workflows/main.yml | 36 +++++++------------ UI_Automation/Pages/AboutPage.cs | 36 +++++++++++++++---- UI_Automation/Setup/TestSetup.cs | 8 +++-- .../SearchAndNavigateStepDefinitions.cs | 2 +- UI_Automation/Support/Config.cs | 9 +++++ UI_Automation/appsettings.json | 2 +- global.json | 2 +- 7 files changed, 61 insertions(+), 34 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 05e9d76..b0e9ae0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,9 +24,10 @@ jobs: env: DOTNET_VERSION: "8.0.x" - # FIX: do NOT use .slnx in CI + # 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 @@ -40,23 +41,11 @@ jobs: - name: dotnet --info run: dotnet --info - # ========================= - # FIX: Restore per project - # ========================= - - name: Restore (projects) - shell: pwsh - run: | - dotnet restore .\UI_Automation\UI_Automation.csproj - dotnet restore .\API_Automation\API_Automation.csproj + - name: Restore + run: dotnet restore - # ========================= - # FIX: Build per project - # ========================= - - name: Build UI (Release) - run: dotnet build .\UI_Automation\UI_Automation.csproj --configuration Release --no-restore - - - name: Build API (Release) - run: dotnet build .\API_Automation\API_Automation.csproj --configuration Release --no-restore + - name: Build (Release) + run: dotnet build --configuration Release --no-restore - name: Run tests (Release) shell: pwsh @@ -64,7 +53,8 @@ jobs: Write-Host "Running tests for: $env:TEST_TYPE" Write-Host "Browser: $env:BROWSER" - # Pass browser via ENV (if your framework reads it) + # 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") { @@ -76,13 +66,11 @@ jobs: --logger "trx;LogFileName=api-tests.trx" } else { - dotnet test .\UI_Automation\UI_Automation.csproj --configuration Release --no-build ` - --logger "trx;LogFileName=ui-tests.trx" - - dotnet test .\API_Automation\API_Automation.csproj --configuration Release --no-build ` - --logger "trx;LogFileName=api-tests.trx" + 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 @@ -92,6 +80,7 @@ jobs: **/*.trx if-no-files-found: warn + # Collect allure-results from anywhere into one folder - name: Collect Allure results if: always() shell: pwsh @@ -118,6 +107,7 @@ jobs: 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 diff --git a/UI_Automation/Pages/AboutPage.cs b/UI_Automation/Pages/AboutPage.cs index 45157db..a665974 100644 --- a/UI_Automation/Pages/AboutPage.cs +++ b/UI_Automation/Pages/AboutPage.cs @@ -1,32 +1,56 @@ ο»Ώusing OpenQA.Selenium; +using OpenQA.Selenium.Support.UI; +using SeleniumExtras.WaitHelpers; + namespace UI_Automation.Pages { public class AboutPage { private readonly IWebDriver _driver; + private readonly WebDriverWait _wait; #region Locators - private IWebElement InstallSteamButton => _driver.FindElement(By.CssSelector("#about_greeting .about_install_steam_link")); - private IList OnlineStatus => _driver.FindElements(By.XPath("//div[contains(@class,'gamers_online')]/parent::div")); - private IList PlayingNowStatus => _driver.FindElements(By.XPath("//div[contains(@class,'gamers_in_game')]/parent::div")); + private readonly By InstallSteamButtonLocator = By.CssSelector("#about_greeting .about_install_steam_link"); + private readonly By OnlineStatusLocator = By.XPath("//div[contains(@class,'gamers_online')]/parent::div"); + private readonly By PlayingNowStatusLocator = By.XPath("//div[contains(@class,'gamers_in_game')]/parent::div"); #endregion public AboutPage(IWebDriver driver) { _driver = driver; + _wait = new WebDriverWait(driver, TimeSpan.FromSeconds(20)); } public bool IsInstallSteamButtonClickable() { - return InstallSteamButton.Displayed && InstallSteamButton.Enabled; + var button = _wait.Until(ExpectedConditions.ElementToBeClickable(InstallSteamButtonLocator)); + return button.Displayed && button.Enabled; } public bool CompareIfPlayingNowStatusIsLessThanOnlineStatus() { - int onlineStatus = int.Parse(OnlineStatus.Last().Text.Replace("ONLINE", "").Replace(",", "")); - int playingNowStatus = int.Parse(PlayingNowStatus.Last().Text.Replace("PLAYING NOW", "").Replace(",", "")); + var js = (IJavaScriptExecutor)_driver; + + // Scroll to the bottom so the stats section loads into view + js.ExecuteScript("window.scrollTo(0, document.body.scrollHeight);"); + + // Wait for both stat elements to appear and have non-empty text + _wait.Until(d => + { + var online = d.FindElements(OnlineStatusLocator); + var playing = d.FindElements(PlayingNowStatusLocator); + return online.Count > 0 && playing.Count > 0 + && !string.IsNullOrWhiteSpace(online.Last().Text) + && !string.IsNullOrWhiteSpace(playing.Last().Text); + }); + + var onlineElements = _driver.FindElements(OnlineStatusLocator); + var playingNowElements = _driver.FindElements(PlayingNowStatusLocator); + + int onlineStatus = int.Parse(onlineElements.Last().Text.Replace("ONLINE", "").Replace(",", "").Trim()); + int playingNowStatus = int.Parse(playingNowElements.Last().Text.Replace("PLAYING NOW", "").Replace(",", "").Trim()); return onlineStatus > playingNowStatus; } diff --git a/UI_Automation/Setup/TestSetup.cs b/UI_Automation/Setup/TestSetup.cs index 7540669..e314dbf 100644 --- a/UI_Automation/Setup/TestSetup.cs +++ b/UI_Automation/Setup/TestSetup.cs @@ -13,8 +13,8 @@ //[assembly: Parallelizable(ParallelScope.Self | ParallelScope.Children)] //[assembly: Parallelizable(ParallelScope.Fixtures)] -[assembly: Parallelizable(ParallelScope.All)] -[assembly: LevelOfParallelism(2)] +//[assembly: Parallelizable(ParallelScope.All)] +//[assembly: LevelOfParallelism(2)] namespace UI_Automation.Setup { @@ -120,6 +120,7 @@ private static IWebDriver CreateWebDriver(string browserName, bool headless, boo // Optional stability options (safe for Docker) options.AddArgument("--no-sandbox"); options.AddArgument("--disable-dev-shm-usage"); + options.AddArgument("--window-size=1920,1080"); return new RemoteWebDriver(new Uri(remoteUrl), options); } @@ -132,6 +133,9 @@ private static IWebDriver CreateWebDriver(string browserName, bool headless, boo var chromeOptions = new ChromeOptions(); if (headless) chromeOptions.AddArgument("--headless=new"); if (incognito) chromeOptions.AddArgument("--incognito"); + chromeOptions.AddArgument("--no-sandbox"); + chromeOptions.AddArgument("--disable-dev-shm-usage"); + chromeOptions.AddArgument("--window-size=1920,1080"); return new ChromeDriver(chromeOptions); case BrowserType.Firefox: diff --git a/UI_Automation/StepDefinitions/SearchAndNavigateStepDefinitions.cs b/UI_Automation/StepDefinitions/SearchAndNavigateStepDefinitions.cs index b571f61..a447749 100644 --- a/UI_Automation/StepDefinitions/SearchAndNavigateStepDefinitions.cs +++ b/UI_Automation/StepDefinitions/SearchAndNavigateStepDefinitions.cs @@ -19,7 +19,7 @@ public class SearchAndNavigateStepDefinitions public SearchAndNavigateStepDefinitions(IWebDriver driver, StorePage storePage, AboutPage aboutPage, ScenarioContext scenarioContext) { _driver = driver; - _wait = new WebDriverWait(_driver, TimeSpan.Zero); + _wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15)); _config = Config.Load(); _storePage = storePage; _aboutPage = aboutPage; diff --git a/UI_Automation/Support/Config.cs b/UI_Automation/Support/Config.cs index 82403d8..f90b8a1 100644 --- a/UI_Automation/Support/Config.cs +++ b/UI_Automation/Support/Config.cs @@ -59,6 +59,15 @@ public static Config Load(string fileName = "appsettings.json") if (_instance == null) throw new InvalidOperationException("Failed to deserialize configuration."); + // Allow environment variable overrides for CI + var envBrowser = Environment.GetEnvironmentVariable("BROWSER"); + if (!string.IsNullOrWhiteSpace(envBrowser)) + _instance.Browser = envBrowser; + + var envHeadless = Environment.GetEnvironmentVariable("HEADLESS"); + if (!string.IsNullOrWhiteSpace(envHeadless)) + _instance.Headless = envHeadless.Equals("true", StringComparison.OrdinalIgnoreCase); + return _instance; } } diff --git a/UI_Automation/appsettings.json b/UI_Automation/appsettings.json index 01b8110..85a3cf5 100644 --- a/UI_Automation/appsettings.json +++ b/UI_Automation/appsettings.json @@ -4,6 +4,6 @@ "Browser": "Chrome", "BaseUrl": "https://store.steampowered.com", - "UseRemote": true, + "UseRemote": false, "RemoteUrl": "http://localhost:4444/wd/hub" } diff --git a/global.json b/global.json index ed75c1b..1406adc 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { "version": "8.0.417", - "rollForward": "latestPatch" + "rollForward": "latestMinor" } } From ae0284ba642c297310984ac5314d57fc21032f7b Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Fri, 13 Mar 2026 11:10:38 +0100 Subject: [PATCH 36/42] added more js actions --- UI_Automation/Pages/StorePage.cs | 7 ++++++- UI_Automation/Setup/TestSetup.cs | 8 +++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/UI_Automation/Pages/StorePage.cs b/UI_Automation/Pages/StorePage.cs index 2a3cdb4..9ae7678 100644 --- a/UI_Automation/Pages/StorePage.cs +++ b/UI_Automation/Pages/StorePage.cs @@ -114,7 +114,12 @@ public void ClickPlayGameButton() public void ClickNoINeedSteamButton() { - WaitClickable(NoINeedSteamButton).Click(); + // Dismiss any browser-level protocol handler dialog + try { _driver.SwitchTo().Alert().Dismiss(); } catch { } + + var button = WaitClickable(NoINeedSteamButton); + _jsExecutor.ExecuteScript("arguments[0].scrollIntoView({block:'center'});", button); + _jsExecutor.ExecuteScript("arguments[0].click();", button); } #endregion diff --git a/UI_Automation/Setup/TestSetup.cs b/UI_Automation/Setup/TestSetup.cs index e314dbf..9160e6a 100644 --- a/UI_Automation/Setup/TestSetup.cs +++ b/UI_Automation/Setup/TestSetup.cs @@ -117,10 +117,13 @@ private static IWebDriver CreateWebDriver(string browserName, bool headless, boo if (headless) options.AddArgument("--headless=new"); if (incognito) options.AddArgument("--incognito"); - // Optional stability options (safe for Docker) + // Optional stability options (safe for Docker/CI) options.AddArgument("--no-sandbox"); options.AddArgument("--disable-dev-shm-usage"); options.AddArgument("--window-size=1920,1080"); + options.AddArgument("--disable-popup-blocking"); + options.AddExcludedArgument("enable-automation"); + options.AddUserProfilePreference("protocol_handler.excluded_schemes", new Dictionary { { "steam", true } }); return new RemoteWebDriver(new Uri(remoteUrl), options); } @@ -136,6 +139,9 @@ private static IWebDriver CreateWebDriver(string browserName, bool headless, boo chromeOptions.AddArgument("--no-sandbox"); chromeOptions.AddArgument("--disable-dev-shm-usage"); chromeOptions.AddArgument("--window-size=1920,1080"); + chromeOptions.AddArgument("--disable-popup-blocking"); + chromeOptions.AddExcludedArgument("enable-automation"); + chromeOptions.AddUserProfilePreference("protocol_handler.excluded_schemes", new Dictionary { { "steam", true } }); return new ChromeDriver(chromeOptions); case BrowserType.Firefox: From e37d47c8910ad39bbbe286ba953a5b0bb2ca3d90 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Sun, 29 Mar 2026 17:36:41 +0200 Subject: [PATCH 37/42] added more info for allure reports --- .github/workflows/main.yml | 19 ++++++++++++++ API_Automation/API_Automation.csproj | 8 +++--- UI_Automation/Features/CopyAndPaste.feature | 5 ++++ .../Features/CopyAndPaste.feature.cs | 18 ++++++++++--- .../Features/SearchAndNavigate.feature | 12 ++++++--- .../Features/SearchAndNavigate.feature.cs | 20 +++++++++++--- UI_Automation/Pages/StorePage.cs | 7 +---- UI_Automation/Setup/TestSetup.cs | 26 ++++++++++++++----- UI_Automation/UI_Automation.csproj | 4 +-- 9 files changed, 90 insertions(+), 29 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b0e9ae0..8af9297 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -118,6 +118,17 @@ jobs: 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 @@ -146,3 +157,11 @@ jobs: 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/API_Automation/API_Automation.csproj b/API_Automation/API_Automation.csproj index adf5335..b224d24 100644 --- a/API_Automation/API_Automation.csproj +++ b/API_Automation/API_Automation.csproj @@ -2,6 +2,7 @@ net8.0 + true enable enable @@ -12,11 +13,12 @@ - + + - + @@ -51,7 +53,7 @@ - + PreserveNewest diff --git a/UI_Automation/Features/CopyAndPaste.feature b/UI_Automation/Features/CopyAndPaste.feature index a39e806..dc58665 100644 --- a/UI_Automation/Features/CopyAndPaste.feature +++ b/UI_Automation/Features/CopyAndPaste.feature @@ -3,6 +3,8 @@ @allure.feature:CopyPaste @allure.owner:Srdjan_Miljus @allure.severity:critical +@allure.tag:regression +@allure.link:https://store.steampowered.com Feature: Copy and paste As a user @@ -10,6 +12,7 @@ Feature: Copy and paste So that I can play games on the Steam platform @ui +@allure.story:Copy_to_clipboard Scenario: Copy Given I open Store page When I search for "FIFA" game @@ -26,6 +29,7 @@ Scenario: Copy And I should see that Playing Now gamers status are less than Online gamers status @ui +@allure.story:Paste_from_clipboard Scenario: Paste Given I open Store page When I search for "THE FINALS" game @@ -39,6 +43,7 @@ Scenario: Paste And I should see that Playing Now gamers status are less than Online gamers status @ui +@allure.story:Cut_to_clipboard Scenario: Cut Given I open Store page When I search for "THE FINALS" game diff --git a/UI_Automation/Features/CopyAndPaste.feature.cs b/UI_Automation/Features/CopyAndPaste.feature.cs index 9d56309..587b846 100644 --- a/UI_Automation/Features/CopyAndPaste.feature.cs +++ b/UI_Automation/Features/CopyAndPaste.feature.cs @@ -25,6 +25,8 @@ namespace UI_Automation.Features [global::NUnit.Framework.CategoryAttribute("allure.feature:CopyPaste")] [global::NUnit.Framework.CategoryAttribute("allure.owner:Srdjan_Miljus")] [global::NUnit.Framework.CategoryAttribute("allure.severity:critical")] + [global::NUnit.Framework.CategoryAttribute("allure.tag:regression")] + [global::NUnit.Framework.CategoryAttribute("allure.link:https://store.steampowered.com")] public partial class CopyAndPasteFeature { @@ -35,7 +37,9 @@ public partial class CopyAndPasteFeature "allure.epic:SteamWebInterface", "allure.feature:CopyPaste", "allure.owner:Srdjan_Miljus", - "allure.severity:critical"}; + "allure.severity:critical", + "allure.tag:regression", + "allure.link:https://store.steampowered.com"}; private static global::Reqnroll.FeatureInfo featureInfo = new global::Reqnroll.FeatureInfo(new global::System.Globalization.CultureInfo("en-US"), "Features", "Copy and paste", " As a user\r\n I want to search for game and navigate to the official Steam About" + " page\r\n So that I can play games on the Steam platform", global::Reqnroll.ProgrammingLanguage.CSharp, featureTags, InitializeCucumberMessages()); @@ -119,10 +123,12 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa [global::NUnit.Framework.TestAttribute()] [global::NUnit.Framework.DescriptionAttribute("Copy")] [global::NUnit.Framework.CategoryAttribute("ui")] + [global::NUnit.Framework.CategoryAttribute("allure.story:Copy_to_clipboard")] public async global::System.Threading.Tasks.Task Copy() { string[] tagsOfScenario = new string[] { - "ui"}; + "ui", + "allure.story:Copy_to_clipboard"}; global::System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new global::System.Collections.Specialized.OrderedDictionary(); string pickleIndex = "0"; global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("Copy", null, tagsOfScenario, argumentsOfScenario, featureTags, pickleIndex); @@ -156,10 +162,12 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa [global::NUnit.Framework.TestAttribute()] [global::NUnit.Framework.DescriptionAttribute("Paste")] [global::NUnit.Framework.CategoryAttribute("ui")] + [global::NUnit.Framework.CategoryAttribute("allure.story:Paste_from_clipboard")] public async global::System.Threading.Tasks.Task Paste() { string[] tagsOfScenario = new string[] { - "ui"}; + "ui", + "allure.story:Paste_from_clipboard"}; global::System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new global::System.Collections.Specialized.OrderedDictionary(); string pickleIndex = "1"; global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("Paste", null, tagsOfScenario, argumentsOfScenario, featureTags, pickleIndex); @@ -190,10 +198,12 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa [global::NUnit.Framework.TestAttribute()] [global::NUnit.Framework.DescriptionAttribute("Cut")] [global::NUnit.Framework.CategoryAttribute("ui")] + [global::NUnit.Framework.CategoryAttribute("allure.story:Cut_to_clipboard")] public async global::System.Threading.Tasks.Task Cut() { string[] tagsOfScenario = new string[] { - "ui"}; + "ui", + "allure.story:Cut_to_clipboard"}; global::System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new global::System.Collections.Specialized.OrderedDictionary(); string pickleIndex = "2"; global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("Cut", null, tagsOfScenario, argumentsOfScenario, featureTags, pickleIndex); diff --git a/UI_Automation/Features/SearchAndNavigate.feature b/UI_Automation/Features/SearchAndNavigate.feature index 8d42fa0..964a092 100644 --- a/UI_Automation/Features/SearchAndNavigate.feature +++ b/UI_Automation/Features/SearchAndNavigate.feature @@ -3,13 +3,17 @@ @allure.feature:Search @allure.owner:Srdjan_Miljus @allure.severity:critical +@allure.tag:regression +@allure.link:https://store.steampowered.com Feature: Search and navigate As a user I want to search for game and navigate to the official Steam About page So that I can play games on the Steam platform -@ui +@ui +@allure.story:Search_by_game_title +@allure.tag:smoke Scenario: Search for Steam game and navigate to the About page Given I open Store page When I search for "FIFA" game @@ -25,7 +29,8 @@ Scenario: Search for Steam game and navigate to the About page And I should see the Install Steam button is clickable And I should see that Playing Now gamers status are less than Online gamers status -@ui +@ui +@allure.story:Navigate_to_About_page Scenario: Navigate to the About page Given I open Store page When I search for "THE FINALS" game @@ -38,7 +43,8 @@ Scenario: Navigate to the About page And I should see the Install Steam button is clickable And I should see that Playing Now gamers status are less than Online gamers status -@ui +@ui +@allure.story:About_page_verification Scenario: About page Given I open Store page When I search for "THE FINALS" game diff --git a/UI_Automation/Features/SearchAndNavigate.feature.cs b/UI_Automation/Features/SearchAndNavigate.feature.cs index 83417cd..f9b7427 100644 --- a/UI_Automation/Features/SearchAndNavigate.feature.cs +++ b/UI_Automation/Features/SearchAndNavigate.feature.cs @@ -25,6 +25,8 @@ namespace UI_Automation.Features [global::NUnit.Framework.CategoryAttribute("allure.feature:Search")] [global::NUnit.Framework.CategoryAttribute("allure.owner:Srdjan_Miljus")] [global::NUnit.Framework.CategoryAttribute("allure.severity:critical")] + [global::NUnit.Framework.CategoryAttribute("allure.tag:regression")] + [global::NUnit.Framework.CategoryAttribute("allure.link:https://store.steampowered.com")] public partial class SearchAndNavigateFeature { @@ -35,7 +37,9 @@ public partial class SearchAndNavigateFeature "allure.epic:SteamWebInterface", "allure.feature:Search", "allure.owner:Srdjan_Miljus", - "allure.severity:critical"}; + "allure.severity:critical", + "allure.tag:regression", + "allure.link:https://store.steampowered.com"}; private static global::Reqnroll.FeatureInfo featureInfo = new global::Reqnroll.FeatureInfo(new global::System.Globalization.CultureInfo("en-US"), "Features", "Search and navigate", " As a user\r\n I want to search for game and navigate to the official Steam About" + " page\r\n So that I can play games on the Steam platform", global::Reqnroll.ProgrammingLanguage.CSharp, featureTags, InitializeCucumberMessages()); @@ -119,10 +123,14 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa [global::NUnit.Framework.TestAttribute()] [global::NUnit.Framework.DescriptionAttribute("Search for Steam game and navigate to the About page")] [global::NUnit.Framework.CategoryAttribute("ui")] + [global::NUnit.Framework.CategoryAttribute("allure.story:Search_by_game_title")] + [global::NUnit.Framework.CategoryAttribute("allure.tag:smoke")] public async global::System.Threading.Tasks.Task SearchForSteamGameAndNavigateToTheAboutPage() { string[] tagsOfScenario = new string[] { - "ui"}; + "ui", + "allure.story:Search_by_game_title", + "allure.tag:smoke"}; global::System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new global::System.Collections.Specialized.OrderedDictionary(); string pickleIndex = "0"; global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("Search for Steam game and navigate to the About page", null, tagsOfScenario, argumentsOfScenario, featureTags, pickleIndex); @@ -156,10 +164,12 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa [global::NUnit.Framework.TestAttribute()] [global::NUnit.Framework.DescriptionAttribute("Navigate to the About page")] [global::NUnit.Framework.CategoryAttribute("ui")] + [global::NUnit.Framework.CategoryAttribute("allure.story:Navigate_to_About_page")] public async global::System.Threading.Tasks.Task NavigateToTheAboutPage() { string[] tagsOfScenario = new string[] { - "ui"}; + "ui", + "allure.story:Navigate_to_About_page"}; global::System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new global::System.Collections.Specialized.OrderedDictionary(); string pickleIndex = "1"; global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("Navigate to the About page", null, tagsOfScenario, argumentsOfScenario, featureTags, pickleIndex); @@ -190,10 +200,12 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa [global::NUnit.Framework.TestAttribute()] [global::NUnit.Framework.DescriptionAttribute("About page")] [global::NUnit.Framework.CategoryAttribute("ui")] + [global::NUnit.Framework.CategoryAttribute("allure.story:About_page_verification")] public async global::System.Threading.Tasks.Task AboutPage() { string[] tagsOfScenario = new string[] { - "ui"}; + "ui", + "allure.story:About_page_verification"}; global::System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new global::System.Collections.Specialized.OrderedDictionary(); string pickleIndex = "2"; global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("About page", null, tagsOfScenario, argumentsOfScenario, featureTags, pickleIndex); diff --git a/UI_Automation/Pages/StorePage.cs b/UI_Automation/Pages/StorePage.cs index 9ae7678..2a3cdb4 100644 --- a/UI_Automation/Pages/StorePage.cs +++ b/UI_Automation/Pages/StorePage.cs @@ -114,12 +114,7 @@ public void ClickPlayGameButton() public void ClickNoINeedSteamButton() { - // Dismiss any browser-level protocol handler dialog - try { _driver.SwitchTo().Alert().Dismiss(); } catch { } - - var button = WaitClickable(NoINeedSteamButton); - _jsExecutor.ExecuteScript("arguments[0].scrollIntoView({block:'center'});", button); - _jsExecutor.ExecuteScript("arguments[0].click();", button); + WaitClickable(NoINeedSteamButton).Click(); } #endregion diff --git a/UI_Automation/Setup/TestSetup.cs b/UI_Automation/Setup/TestSetup.cs index 9160e6a..3d0393b 100644 --- a/UI_Automation/Setup/TestSetup.cs +++ b/UI_Automation/Setup/TestSetup.cs @@ -63,6 +63,24 @@ public static void GlobalSetup() "buildName": "UI_Automation" } """, Encoding.UTF8); + + File.WriteAllText(Path.Combine(resultsDir, "categories.json"), + """ + [ + { + "name": "Product defects", + "matchedStatuses": ["failed"] + }, + { + "name": "Test defects", + "matchedStatuses": ["broken"] + }, + { + "name": "Ignored tests", + "matchedStatuses": ["skipped"] + } + ] + """, Encoding.UTF8); } [BeforeScenario] @@ -117,13 +135,10 @@ private static IWebDriver CreateWebDriver(string browserName, bool headless, boo if (headless) options.AddArgument("--headless=new"); if (incognito) options.AddArgument("--incognito"); - // Optional stability options (safe for Docker/CI) + // Optional stability options (safe for Docker) options.AddArgument("--no-sandbox"); options.AddArgument("--disable-dev-shm-usage"); options.AddArgument("--window-size=1920,1080"); - options.AddArgument("--disable-popup-blocking"); - options.AddExcludedArgument("enable-automation"); - options.AddUserProfilePreference("protocol_handler.excluded_schemes", new Dictionary { { "steam", true } }); return new RemoteWebDriver(new Uri(remoteUrl), options); } @@ -139,9 +154,6 @@ private static IWebDriver CreateWebDriver(string browserName, bool headless, boo chromeOptions.AddArgument("--no-sandbox"); chromeOptions.AddArgument("--disable-dev-shm-usage"); chromeOptions.AddArgument("--window-size=1920,1080"); - chromeOptions.AddArgument("--disable-popup-blocking"); - chromeOptions.AddExcludedArgument("enable-automation"); - chromeOptions.AddUserProfilePreference("protocol_handler.excluded_schemes", new Dictionary { { "steam", true } }); return new ChromeDriver(chromeOptions); case BrowserType.Firefox: diff --git a/UI_Automation/UI_Automation.csproj b/UI_Automation/UI_Automation.csproj index 518c020..9157f33 100644 --- a/UI_Automation/UI_Automation.csproj +++ b/UI_Automation/UI_Automation.csproj @@ -16,8 +16,8 @@ - - + + From 6da3e7309a2b295b2f0c3001564243961a5c2ac4 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Thu, 11 Jun 2026 19:23:44 +0200 Subject: [PATCH 38/42] Update author title from Senior QA Automation Architect --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 22b3095..14e9ad6 100644 --- a/README.md +++ b/README.md @@ -136,4 +136,4 @@ Example snippet from workflow: --- -πŸ‘€ **Author:** *Srdjan Miljus β€” Senior QA Automation Architect +πŸ‘€ **Author:** *Srdjan Miljus β€” QA Automation Engineer From 0462fff64a7f1f21924a67a7061538ed508f7b79 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Thu, 11 Jun 2026 19:24:47 +0200 Subject: [PATCH 39/42] Revise README title and project description Updated project title and description in README. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 14e9ad6..e13ebfb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -ο»Ώ# TA_Csharp_Selenium_RestSharp +ο»Ώ# C# UI + API Test Automation Framework Modern **Test Automation Framework** built with **C# (.NET 8)** β€” combining **Selenium WebDriver** for UI testing, **RestSharp** for API testing, and **Reqnroll** (SpecFlow-style) for BDD scenarios. From f712dd86c9ce121bdf52d181010cc990d03558a4 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Thu, 11 Jun 2026 19:32:45 +0200 Subject: [PATCH 40/42] Fix project structure and solution file name Updated project structure and corrected solution file name in README. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e13ebfb..6bbf288 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ allure open allure-report ## πŸ— Project Structure ```text -Csharp_Automation_Task/ +CsharpUiApiAutomation β”œβ”€β”€ UI_Automation/ β”‚ β”œβ”€β”€ Features/ β”‚ β”œβ”€β”€ Pages/ @@ -105,7 +105,7 @@ Csharp_Automation_Task/ β”‚ └── workflows/ β”‚ └── automation-tests.yml β”‚ -β”œβ”€β”€ Csharp_Automation_Task.sln +β”œβ”€β”€ CCsharpUiApiAutomation.sln β”œβ”€β”€ README.md └── .gitignore ``` @@ -136,4 +136,4 @@ Example snippet from workflow: --- -πŸ‘€ **Author:** *Srdjan Miljus β€” QA Automation Engineer +πŸ‘€ **Author:** *Srdjan Miljus β€” QA Automation Engineer* From 55c91eb915f3fd75ea9a68a00e45b65d0b36a7b4 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Thu, 11 Jun 2026 19:34:25 +0200 Subject: [PATCH 41/42] Rename solution file to Csharp_Automation.sln --- Csharp_Automation_Task.sln => Csharp_Automation.sln | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Csharp_Automation_Task.sln => Csharp_Automation.sln (100%) diff --git a/Csharp_Automation_Task.sln b/Csharp_Automation.sln similarity index 100% rename from Csharp_Automation_Task.sln rename to Csharp_Automation.sln From 2fae6c04b0902cf5cae75daf19a877ae90a6c833 Mon Sep 17 00:00:00 2001 From: Srdjan Miljus Date: Thu, 11 Jun 2026 19:34:56 +0200 Subject: [PATCH 42/42] Rename Csharp_Automation_Task.slnx to Csharp_Automation.slnx --- Csharp_Automation_Task.slnx => Csharp_Automation.slnx | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Csharp_Automation_Task.slnx => Csharp_Automation.slnx (100%) diff --git a/Csharp_Automation_Task.slnx b/Csharp_Automation.slnx similarity index 100% rename from Csharp_Automation_Task.slnx rename to Csharp_Automation.slnx