diff --git a/.github/workflows/NewsReader.CI.yml b/.github/workflows/NewsReader.CI.yml index 98c8e3d3..6af7295c 100644 --- a/.github/workflows/NewsReader.CI.yml +++ b/.github/workflows/NewsReader.CI.yml @@ -180,7 +180,15 @@ jobs: - name: 🛠️ Build run: | cd src/NewsReader/NewsReader.MauiSystem - dotnet build -f net10.0-ios -c:Release /p:packageApp=false /p:buildForSimulator=true /p:ArchiveOnBuild=false -p:ApplicationVersion=${{ needs.GetVersion.outputs.version }} -p:ApplicationDisplayVersion=${{ needs.GetVersion.outputs.version }} + dotnet build -f net10.0-ios -c:Release -p:RuntimeIdentifier=iossimulator-arm64 -p:ApplicationVersion=${{ needs.GetVersion.outputs.version }} -p:ApplicationDisplayVersion=${{ needs.GetVersion.outputs.version }} + + - name: 📦 Upload iOS App for simulator + uses: actions/upload-artifact@v6 + with: + name: NewsReaderiOSSimulator + if-no-files-found: error + path: | + ./src/NewsReader/NewsReader.MauiSystem/bin/Release/*/iossimulator-arm64/NewsReader.MauiSystem.app/** Test: runs-on: windows-2025 diff --git a/.github/workflows/NewsReader.UITestAndroid.yml b/.github/workflows/NewsReader.UITestAndroid.yml index 791745be..0a938c42 100644 --- a/.github/workflows/NewsReader.UITestAndroid.yml +++ b/.github/workflows/NewsReader.UITestAndroid.yml @@ -1,6 +1,11 @@ name: NewsReader.UITestAndroid on: + push: + paths: + - '**/NewsReader.UITestAndroid.yml' + - 'src/NewsReader.UITest/**' + workflow_run: workflows: ["NewsReader.CI"] types: diff --git a/.github/workflows/NewsReader.UITestIOS.yml b/.github/workflows/NewsReader.UITestIOS.yml new file mode 100644 index 00000000..05a576be --- /dev/null +++ b/.github/workflows/NewsReader.UITestIOS.yml @@ -0,0 +1,83 @@ +name: NewsReader.UITestIOS + +on: + push: + paths: + - '**/NewsReader.UITestIOS.yml' + - 'src/NewsReader.UITest/**' + + workflow_run: + workflows: ["NewsReader.CI"] + types: + - completed + + workflow_dispatch: + +jobs: + UITest: + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} + runs-on: macos-26 + + steps: + - name: 🔖 Check-out + uses: actions/checkout@v6 + + - name: 📜 Authenticate GitHub CLI + run: echo "${{ secrets.GITHUB_TOKEN }}" | gh auth login --with-token + + - name: ⚙️ Get latest successful run ID + id: get_run + run: | + RUN_ID=$(gh run list \ + --workflow="NewsReader.CI.yml" \ + --branch=master \ + --json databaseId,status,conclusion \ + --jq '.[] | select(.conclusion=="success") | .databaseId' \ + | head -n 1) + echo "run_id=$RUN_ID" >> $GITHUB_OUTPUT + echo "run_id=$RUN_ID" + + - name: 📦 Download Simulator App + run: | + gh run download ${{ steps.get_run.outputs.run_id }} --name="NewsReaderiOSSimulator" --dir=./NewsReaderiOSSimulator + + - name: ⚙️ Show available simulators + run: xcrun simctl list devices available + + - name: ⚙️ Create and boot simulator and install app + run: | + DEVICE_TYPE="com.apple.CoreSimulator.SimDeviceType.iPhone-17" + RUNTIME=$(xcrun simctl list runtimes | grep 'iOS 26.4' | grep -o 'com.apple.CoreSimulator.SimRuntime.iOS-[0-9-]*' | head -1) + UDID=$(xcrun simctl create "CI iPhone" "$DEVICE_TYPE" "$RUNTIME") + echo "UITestDeviceId=$UDID" >> "$GITHUB_ENV" + xcrun simctl boot "$UDID" + xcrun simctl bootstatus "$UDID" -b + xcrun simctl install "$UDID" NewsReaderiOSSimulator/net10.0-ios/iossimulator-arm64/NewsReader.MauiSystem.app + + - name: ⚙️ Install Appium Server + run: | + npm install appium -g + appium driver install xcuitest + appium --version + + - name: ⚙️ Start Appium Server + run: | + mkdir -p $GITHUB_WORKSPACE/out/NewsReader.UITest + appium > $GITHUB_WORKSPACE/out/NewsReader.UITest/appium.log 2>&1 & + + - name: 🖥️ UI Test + env: + UITestApp: NewsReaderiOSSimulator/net10.0-ios/iossimulator-arm64/NewsReader.MauiSystem.app + run: | + echo "## 🕵️ Test Results - NewsReader.UITest.sln" >> $Env:GITHUB_STEP_SUMMARY + cd ./src/NewsReader.UITest + dotnet test --solution ./NewsReader.UITest.slnx --filter-trait DevicePlatform=IOS + + - name: 📦 Upload UI Test results + uses: actions/upload-artifact@v6 + if: always() + with: + name: NewsReaderUITestResults + if-no-files-found: ignore + path: | + out/NewsReader.UITest/ diff --git a/src/NewsReader.UITest/NewsReader.UITest/UITest.cs b/src/NewsReader.UITest/NewsReader.UITest/UITest.cs index 42b4e4b8..51631941 100644 --- a/src/NewsReader.UITest/NewsReader.UITest/UITest.cs +++ b/src/NewsReader.UITest/NewsReader.UITest/UITest.cs @@ -2,11 +2,14 @@ namespace UITest.NewsReader; -public class UITest() : UITestBase("Waf.NewsReader", +public class UITest() : UITestBase( + deviceId: Environment.GetEnvironmentVariable("UITestDeviceId"), + appId: "Waf.NewsReader", androidApkFile: Environment.GetEnvironmentVariable("UITestApkFile") ?? "src/NewsReader/NewsReader.MauiSystem/bin/Release/net10.0-android/Waf.NewsReader-Signed.apk", androidAppActivity: "Waf.NewsReader.MainActivity", + iosApp: Environment.GetEnvironmentVariable("UITestApp"), windowsAppId: "Waf.NewsReader_a8txtqew917ny!App", - Environment.GetEnvironmentVariable("UITestOutputPath") ?? "out/NewsReader.UITest/") + testOutputPath: Environment.GetEnvironmentVariable("UITestOutputPath") ?? "out/NewsReader.UITest/") { public ShellWindow GetShellWindow() => new(Driver); } diff --git a/src/NewsReader.UITest/UITest.Core/UITestBase.cs b/src/NewsReader.UITest/UITest.Core/UITestBase.cs index 0662a9fa..ca70a267 100644 --- a/src/NewsReader.UITest/UITest.Core/UITestBase.cs +++ b/src/NewsReader.UITest/UITest.Core/UITestBase.cs @@ -10,16 +10,17 @@ namespace UITest; public abstract class UITestBase : IDisposable { + private readonly string? deviceId; private readonly string appId; private readonly string app; private readonly string androidAppActivity; private readonly string rootPath; private readonly string testOutPath; - protected UITestBase(string appId, string androidApkFile, string androidAppActivity, string windowsAppId, string testOutputPath) + protected UITestBase(string? deviceId, string appId, string androidApkFile, string androidAppActivity, string? iosApp, string windowsAppId, string testOutputPath) { var devicePlatform = DeviceManager.GetDevicePlatform(GetType()); - + this.deviceId = deviceId; this.appId = appId; this.androidAppActivity = androidAppActivity; var assemblyPath = Assembly.GetAssembly(typeof(UITestBase))!.Location; @@ -29,11 +30,12 @@ protected UITestBase(string appId, string androidApkFile, string androidAppActiv app = devicePlatform switch { DevicePlatform.Android => androidApkFile, - DevicePlatform.IOS => "", + DevicePlatform.IOS => iosApp ?? "", DevicePlatform.Windows => windowsAppId, _ => throw new NotSupportedException() }; Log.WriteLine(("DevicePlatform:", $"{devicePlatform}")); + Log.WriteLine(("DeviceId:", $"{deviceId}")); Log.WriteLine(("AppId:", $"{appId}")); Log.WriteLine(("App:", $"{app}")); Log.WriteLine(("AndroidAppActivity:", $"{androidAppActivity}")); @@ -89,7 +91,15 @@ private IOSDriver SetupIOS(Uri serverUri) AutomationName = AutomationName.iOSXcuiTest, PlatformName = MobilePlatform.IOS, }; + if (!string.IsNullOrEmpty(app)) + { + var appPath = Path.GetFullPath(Path.IsPathFullyQualified(app) ? app : Path.Combine(rootPath, app)); + driverOptions.App = appPath; + } + if (!string.IsNullOrEmpty(deviceId)) driverOptions.AddAdditionalAppiumOption(MobileCapabilityType.Udid, deviceId); driverOptions.AddAdditionalAppiumOption("bundleId", appId); + driverOptions.AddAdditionalAppiumOption("simulatorStartupTimeout", 300_000); // Increased timeouts for CI, as simulator startup can be slow + driverOptions.AddAdditionalAppiumOption("wdaLaunchTimeout", 300_000); // Increased timeouts for CI, as WebDriverAgent startup can be slow // TODO: Use this if you have a physical device //driverOptions.AddAdditionalAppiumOption(MobileCapabilityType.Udid, ""); @@ -98,7 +108,7 @@ private IOSDriver SetupIOS(Uri serverUri) //driverOptions.AddAdditionalAppiumOption("xcodeOrgId", ""); //driverOptions.AddAdditionalAppiumOption("xcodeSigningId", ""); - return new(serverUri, driverOptions, TimeSpan.FromMinutes(3)); + return new(serverUri, driverOptions, TimeSpan.FromMinutes(10)); // Increased timeout for CI, as simulator and WebDriverAgent startup can be slow } private WindowsDriver SetupWindows(Uri serverUri) diff --git a/src/NewsReader/NewsReader.MauiSystem/NewsReader.MauiSystem.csproj b/src/NewsReader/NewsReader.MauiSystem/NewsReader.MauiSystem.csproj index 50d4faa8..4bb68bc0 100644 --- a/src/NewsReader/NewsReader.MauiSystem/NewsReader.MauiSystem.csproj +++ b/src/NewsReader/NewsReader.MauiSystem/NewsReader.MauiSystem.csproj @@ -21,9 +21,6 @@ true True - - - SdkOnly