diff --git a/.github/workflows/nightly_build.yml b/.github/workflows/nightly_build.yml new file mode 100644 index 00000000..5b4104db --- /dev/null +++ b/.github/workflows/nightly_build.yml @@ -0,0 +1,540 @@ +name: Nightly Build + +on: + schedule: + - cron: "20 23 * * *" + repository_dispatch: + +jobs: + unit_tests: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install KVM and Android SDK (x86) + run: | + # Set up environment variables + export ANDROID_SDK_ROOT=$HOME/android-sdk + export ANDROID_HOME=$ANDROID_SDK_ROOT + export PATH=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/emulator:$PATH + + # Create necessary directories + mkdir -p $ANDROID_SDK_ROOT/cmdline-tools + + # Download and install command-line tools + wget -q https://dl.google.com/android/repository/commandlinetools-linux-10406996_latest.zip + unzip -q commandlinetools-linux-*.zip -d $ANDROID_SDK_ROOT/cmdline-tools + mv $ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools $ANDROID_SDK_ROOT/cmdline-tools/latest + chmod +x $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager + + # Accept licenses and install required components + yes | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager --licenses + # Install Android API level 35 and necessary system images + $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager "platform-tools" "emulator" "platforms;android-36" "system-images;android-36;google_apis;x86_64" + + # Export paths for future steps + echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" >> $GITHUB_ENV + echo "ANDROID_HOME=$ANDROID_SDK_ROOT" >> $GITHUB_ENV + echo "PATH=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/emulator:$PATH" >> $GITHUB_ENV + + - name: Install Android Build Tools + run: | + echo "Installing Android Build Tools..." + sdkmanager "build-tools;36.0.0" # Updated to use Android 34 build tools + sdkmanager --update + + - name: Install MAUI workloads + run: | + dotnet workload install maui-android + + - name: Build Code + run: dotnet build TransactionProcessor.Mobile/TransactionProcessor.Mobile.csproj --source ${{ secrets.PUBLICFEEDURL }} --source ${{ secrets.PRIVATEFEED_URL }} --source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6/nuget/v3/index.json -f net10.0-android -c Release #--no-restore + + - name: Run unit tests + run: dotnet test --project TransactionProcessor.Mobile.BusinessLogic.Tests/TransactionProcessor.Mobile.BusinessLogic.Tests.csproj --configuration Release + + windows_ui_tests: + runs-on: windows-2022 + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v1.1 + with: + vs-prerelease: true + + - name: Install MAUI workloads + run: | + dotnet workload install maui + + - name: Install Appium and Drivers + run: | + npm install -g appium --unsafe-perm=true --allow-root + appium driver install --source=npm appium-windows-driver + + - name: Start Appium Server + run: | + nohup appium --log appium.log & + + - name: Restore MAUI App for Windows + run: dotnet restore TransactionProcessor.Mobile.sln --source ${{ secrets.PUBLICFEEDURL }} --source ${{ secrets.PRIVATEFEED_URL }} --source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6/nuget/v3/index.json + + - name: Decrypt PFX File + run: | + echo "${{ secrets.WINDOWSSIGNINGCERT }}" > cert.pfx.asc + certutil -decode cert.pfx.asc cert.pfx + + - name: Add Cert to Store + run: certutil -user -q -p ${{ secrets.WINDOWSSIGNINGCERTPWD }} -importpfx cert.pfx NoRoot + + - name: Publish App + run: | + dotnet publish TransactionProcessor.Mobile/TransactionProcessor.Mobile.csproj -c Release -f net10.0-windows10.0.19041.0 /p:AppxPackageSigningEnabled=true /p:PackageCertificateThumbprint="${{ secrets.WINDOWSSIGNINGCERTTHUMBPRINT }}" + + - name: Debug AppPackages contents + shell: powershell + run: | + $candidatePaths = @( + (Join-Path $env:GITHUB_WORKSPACE "TransactionProcessor.Mobile\AppPackages"), + (Join-Path $env:GITHUB_WORKSPACE "TransactionProcessor.Mobile\bin\Release\net10.0-windows10.0.19041.0\win-x64\AppPackages") + ) + $appPackagesPath = $candidatePaths | Where-Object { Test-Path $_ } | Select-Object -First 1 + if ($null -eq $appPackagesPath) { + Write-Error "Unable to find AppPackages under: $($candidatePaths -join ', ')" + exit 1 + } + Write-Host "Listing AppPackages from $appPackagesPath" + Get-ChildItem -Path $appPackagesPath -Recurse | Format-List FullName, Length, Mode + + - name: Install App + shell: powershell + run: | + Import-Module Appx + $candidatePaths = @( + (Join-Path $env:GITHUB_WORKSPACE "TransactionProcessor.Mobile\AppPackages"), + (Join-Path $env:GITHUB_WORKSPACE "TransactionProcessor.Mobile\bin\Release\net10.0-windows10.0.19041.0\win-x64\AppPackages") + ) + $appPackagesPath = $candidatePaths | Where-Object { Test-Path $_ } | Select-Object -First 1 + if ($null -eq $appPackagesPath) { + throw "Unable to find AppPackages under: $($candidatePaths -join ', ')" + } + $installScript = Get-ChildItem -Path $appPackagesPath -Recurse -Filter Install.ps1 | Select-Object -First 1 + if ($null -ne $installScript) { + & $installScript.FullName -Force + } else { + $msixPackage = Get-ChildItem -Path $appPackagesPath -Recurse -Filter *.msix | Select-Object -First 1 + if ($null -eq $msixPackage) { + throw "Unable to find Install.ps1 or .msix package under $appPackagesPath." + } + Add-AppxPackage -Path $msixPackage.FullName -ForceApplicationShutdown + } + + - name: Resolve Windows UI test app id + id: windows_app + shell: powershell + run: | + $package = $null + for ($attempt = 1; $attempt -le 12 -and $null -eq $package; $attempt++) { + $package = Get-AppxPackage -AllUsers | + Where-Object { + $_.Name -like '*TransactionMobile*' -or + $_.Name -like '*TransactionProcessor.Mobile*' -or + $_.PackageFamilyName -like '*TransactionMobile*' -or + $_.PackageFamilyName -like '*TransactionProcessor.Mobile*' -or + $_.InstallLocation -like '*TransactionProcessor.Mobile*' + } | + Sort-Object InstallDate -Descending | + Select-Object -First 1 + + if ($null -eq $package) { + Write-Host ("Attempt {0}: installed app not visible yet, waiting 5 seconds." -f $attempt) + Start-Sleep -Seconds 5 + } + } + + if ($null -eq $package) { + Write-Error "Unable to find the installed TransactionProcessor.Mobile package." + Get-AppxPackage -AllUsers | Sort-Object Name | Select-Object Name, PackageFamilyName, InstallLocation | Format-Table -AutoSize + exit 1 + } + + $windowsAppId = "$($package.PackageFamilyName)!App" + Write-Host "Resolved Windows app id: $windowsAppId" + "windows_app_id=$windowsAppId" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 + + - name: Prepare Windows UI test settings + id: windows_settings + shell: powershell + run: | + $settingsPath = Join-Path $env:RUNNER_TEMP "windows-ui-tests.runsettings" + Copy-Item -Path "TransactionProcessor.Mobile.UITests.Windows.runsettings" -Destination $settingsPath -Force + [xml]$settingsXml = Get-Content -Raw $settingsPath + $nodes = $settingsXml.SelectNodes("//EnvironmentVariables/UITEST_WINDOWS_APP_ID") + if ($nodes.Count -ne 1) { + throw "Expected exactly one UITEST_WINDOWS_APP_ID entry in the runsettings template." + } + + $nodes.Item(0).InnerText = "${{ steps.windows_app.outputs.windows_app_id }}" + $settingsXml.Save($settingsPath) + "runsettings_path=$settingsPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 + + - name: Run Windows Navigation Tests + run: dotnet test --project TransactionProcessor.Mobile.UITests/TransactionProcessor.Mobile.UITests.csproj --filter "(Category=Windows)" --no-restore --settings "${{ steps.windows_settings.outputs.runsettings_path }}" + + - name: Upload Appium Logs on Failure + if: always() + uses: actions/upload-artifact@v4 + with: + name: windows-software_navigation_tests_appium + path: appium.log + + runs-on: windows-2022 + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v1.1 + with: + vs-prerelease: true + + - name: Install MAUI workloads + run: | + dotnet workload install maui + + - name: Install Appium and Drivers + run: | + npm install -g appium --unsafe-perm=true --allow-root + appium driver install --source=npm appium-windows-driver + + - name: Start Appium Server + run: | + nohup appium --log appium.log & + + - name: Restore MAUI App for Windows + run: dotnet restore TransactionProcessor.Mobile.sln --source ${{ secrets.PUBLICFEEDURL }} --source ${{ secrets.PRIVATEFEED_URL }} --source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6/nuget/v3/index.json + + - name: Decrypt PFX File + run: | + echo "${{ secrets.WINDOWSSIGNINGCERT }}" > cert.pfx.asc + certutil -decode cert.pfx.asc cert.pfx + + - name: Add Cert to Store + run: certutil -user -q -p ${{ secrets.WINDOWSSIGNINGCERTPWD }} -importpfx cert.pfx NoRoot + + - name: Publish App + run: | + dotnet publish TransactionProcessor.Mobile/TransactionProcessor.Mobile.csproj -c Release -f net10.0-windows10.0.19041.0 /p:AppxPackageSigningEnabled=true /p:PackageCertificateThumbprint="${{ secrets.WINDOWSSIGNINGCERTTHUMBPRINT }}" + + - name: Install App + shell: powershell + run: | + Import-Module Appx + $candidatePaths = @( + (Join-Path $env:GITHUB_WORKSPACE "TransactionProcessor.Mobile\AppPackages"), + (Join-Path $env:GITHUB_WORKSPACE "TransactionProcessor.Mobile\bin\Release\net10.0-windows10.0.19041.0\win-x64\AppPackages") + ) + $appPackagesPath = $candidatePaths | Where-Object { Test-Path $_ } | Select-Object -First 1 + if ($null -eq $appPackagesPath) { + throw "Unable to find AppPackages under: $($candidatePaths -join ', ')" + } + $installScript = Get-ChildItem -Path $appPackagesPath -Recurse -Filter Install.ps1 | Select-Object -First 1 + if ($null -ne $installScript) { + & $installScript.FullName -Force + } else { + $msixPackage = Get-ChildItem -Path $appPackagesPath -Recurse -Filter *.msix | Select-Object -First 1 + if ($null -eq $msixPackage) { + throw "Unable to find Install.ps1 or .msix package under $appPackagesPath." + } + Add-AppxPackage -Path $msixPackage.FullName -ForceApplicationShutdown + } + + - name: Resolve Windows UI test app id + id: windows_app + shell: powershell + run: | + $package = $null + for ($attempt = 1; $attempt -le 12 -and $null -eq $package; $attempt++) { + $package = Get-AppxPackage -AllUsers | + Where-Object { + $_.Name -like '*TransactionMobile*' -or + $_.Name -like '*TransactionProcessor.Mobile*' -or + $_.PackageFamilyName -like '*TransactionMobile*' -or + $_.PackageFamilyName -like '*TransactionProcessor.Mobile*' -or + $_.InstallLocation -like '*TransactionProcessor.Mobile*' + } | + Sort-Object InstallDate -Descending | + Select-Object -First 1 + + if ($null -eq $package) { + Write-Host ("Attempt {0}: installed app not visible yet, waiting 5 seconds." -f $attempt) + Start-Sleep -Seconds 5 + } + } + + if ($null -eq $package) { + Write-Error "Unable to find the installed TransactionProcessor.Mobile package." + Get-AppxPackage -AllUsers | Sort-Object Name | Select-Object Name, PackageFamilyName, InstallLocation | Format-Table -AutoSize + exit 1 + } + + $windowsAppId = "$($package.PackageFamilyName)!App" + Write-Host "Resolved Windows app id: $windowsAppId" + "windows_app_id=$windowsAppId" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 + + - name: Prepare Windows UI test settings + id: windows_settings + shell: powershell + run: | + $settingsPath = Join-Path $env:RUNNER_TEMP "windows-ui-tests.runsettings" + Copy-Item -Path "TransactionProcessor.Mobile.UITests.Windows.runsettings" -Destination $settingsPath -Force + [xml]$settingsXml = Get-Content -Raw $settingsPath + $nodes = $settingsXml.SelectNodes("//EnvironmentVariables/UITEST_WINDOWS_APP_ID") + if ($nodes.Count -ne 1) { + throw "Expected exactly one UITEST_WINDOWS_APP_ID entry in the runsettings template." + } + + $nodes.Item(0).InnerText = "${{ steps.windows_app.outputs.windows_app_id }}" + $settingsXml.Save($settingsPath) + "runsettings_path=$settingsPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 + + - name: Run Windows End To End Tests + run: dotnet test --project TransactionProcessor.Mobile.UITests/TransactionProcessor.Mobile.UITests.csproj --filter "Category=PRTest&Category=Windows" --no-restore --settings "${{ steps.windows_settings.outputs.runsettings_path }}" + #run: dotnet test TransactionProcessor.Mobile.UiTests/TransactionProcessor.Mobile.UiTests.csproj --list-tests -v:d + + - name: Upload Logs on Failure + if: always() + uses: actions/upload-artifact@v4 + with: + name: windows-e2e_tests + path: C:\\Users\\runneradmin\\runner + + - name: Upload Appium Logs on Failure + if: always() + uses: actions/upload-artifact@v4 + with: + name: windows-e2e_tests_appium + path: appium.log + + android_ui_tests: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up JDK + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + + - name: Enable KVM group perms + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Install KVM and Android SDK (x86) + run: | + # Set up environment variables + export ANDROID_SDK_ROOT=$HOME/android-sdk + export ANDROID_HOME=$ANDROID_SDK_ROOT + export PATH=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/emulator:$PATH + + # Create necessary directories + mkdir -p $ANDROID_SDK_ROOT/cmdline-tools + + # Download and install command-line tools + wget -q https://dl.google.com/android/repository/commandlinetools-linux-10406996_latest.zip + unzip -q commandlinetools-linux-*.zip -d $ANDROID_SDK_ROOT/cmdline-tools + mv $ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools $ANDROID_SDK_ROOT/cmdline-tools/latest + chmod +x $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager + + # Accept licenses and install required components + yes | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager --licenses + # Install Android API level 35 and necessary system images + $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager "platform-tools" "emulator" "platforms;android-36" "system-images;android-36;google_apis;x86_64" + + # Export paths for future steps + echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" >> $GITHUB_ENV + echo "ANDROID_HOME=$ANDROID_SDK_ROOT" >> $GITHUB_ENV + echo "PATH=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/emulator:$PATH" >> $GITHUB_ENV + + - name: Cache Emulator System Image (x86) + uses: actions/cache@v3 + with: + path: ~/.android/avd + key: android-emulator-x86-${{ runner.os }}-android-36 + + - name: Create Android Emulator (x86) + run: | + echo "no" | avdmanager create avd -n test_emulator -k "system-images;android-36;google_apis;x86_64" --device "pixel_4" + + - name: List AVDs + run: avdmanager list avd + + - name: Start Emulator + run: | + echo "Starting emulator..." + + export ANDROID_AVD_HOME=$HOME/.config/.android/avd + + nohup $ANDROID_HOME/emulator/emulator -avd test_emulator -no-window -no-audio -no-snapshot -gpu swiftshader_indirect -verbose > emulator.log 2>&1 & + + echo "Waiting for emulator process to start..." + sleep 10 # Allow time for initialization + + echo "Checking if emulator process is running..." + ps aux | grep emulator + + echo "Checking ADB devices..." + adb devices + + - name: Restart ADB + run: | + adb kill-server + adb start-server + adb devices + + - name: Wait for Emulator to Boot (Timeout 10 min with Debug) + run: | + # Start waiting for the emulator + echo "Waiting for emulator to boot..." + + # Check the list of devices connected (debugging) + adb wait-for-device + + # Wait for emulator boot completion + timeout 600 bash -c ' + while true; do + # Show the current list of devices (debugging) + adb devices + + # Check if the emulator is booting by checking the boot status + BOOT_COMPLETED=$(adb shell getprop sys.boot_completed 2>/dev/null) + echo "Boot status: $BOOT_COMPLETED" + + # If boot is completed, break the loop + if [ "$BOOT_COMPLETED" == "1" ]; then + echo "Emulator boot completed!" + break + fi + + # If boot is not completed, keep waiting + echo "Emulator still booting, waiting..." + sleep 5 + done + ' + + - name: Install Android Build Tools + run: | + echo "Installing Android Build Tools..." + sdkmanager "build-tools;36.0.0" # Updated to use Android 34 build tools + sdkmanager --update + + - name: Install MAUI workloads + run: | + dotnet workload install maui-android + + - name: Install Appium and Drivers + run: | + sudo npm install -g appium + appium driver install uiautomator2 + + - name: Start Appium Server + run: | + nohup appium --log appium.log & + + - name: Restore MAUI App for Android + run: dotnet restore TransactionProcessor.Mobile.sln --source ${{ secrets.PUBLICFEEDURL }} --source ${{ secrets.PRIVATEFEED_URL }} --source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6/nuget/v3/index.json + + - name: Build Code + run: dotnet build TransactionProcessor.Mobile/TransactionProcessor.Mobile.csproj -f net10.0-android -c Release --no-restore + + - name: Locate Android APK + id: android_apk + run: | + apk_path=$(find "$GITHUB_WORKSPACE/TransactionProcessor.Mobile/bin/Release/net10.0-android" -type f -name '*-Signed.apk' | head -n 1) + if [ -z "$apk_path" ]; then + echo "Unable to find a signed APK under TransactionProcessor.Mobile/bin/Release/net10.0-android." + find "$GITHUB_WORKSPACE/TransactionProcessor.Mobile/bin/Release/net10.0-android" -type f -name '*.apk' -print + exit 1 + fi + + echo "UITEST_ANDROID_APP_PATH=$apk_path" >> "$GITHUB_ENV" + echo "apk_path=$apk_path" >> "$GITHUB_OUTPUT" + + - name: Prepare Android UI test settings + id: android_settings + run: | + settings_path="$RUNNER_TEMP/android-ui-tests.runsettings" + cp TransactionProcessor.Mobile.UITests.Android.runsettings "$settings_path" + + python3 - "$settings_path" "${{ steps.android_apk.outputs.apk_path }}" <<'PY' + from pathlib import Path + import re + import sys + + settings_file = Path(sys.argv[1]) + apk_path = sys.argv[2] + content = settings_file.read_text() + updated_content, replacements = re.subn( + r".*?", + f"{apk_path}", + content, + count=1, + flags=re.DOTALL, + ) + + if replacements != 1: + raise SystemExit("Expected exactly one UITEST_ANDROID_APP_PATH entry in the runsettings template.") + + settings_file.write_text(updated_content) + PY + + echo "runsettings_path=$settings_path" >> "$GITHUB_OUTPUT" + + - name: Run Android Navigation Tests + run: dotnet test --project TransactionProcessor.Mobile.UITests/TransactionProcessor.Mobile.UITests.csproj --filter "(Category=Android)" --no-restore --settings "${{ steps.android_settings.outputs.runsettings_path }}" + + - name: Upload Logs on Failure + if: always() + uses: actions/upload-artifact@v4 + with: + name: android-e2e_tests + path: /home/runner/trace/ + + - name: Upload Appium Logs on Failure + if: always() + uses: actions/upload-artifact@v4 + with: + name: android-e2e_tests_appium + path: appium.log + + - name: Capture emulator logs + if: always() + run: adb logcat -d > emulator-log.txt + + - name: Upload emulator logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: android-emulator-logs + path: emulator-log.txt