diff --git a/.github/workflows/ios_navigation_tests.yml b/.github/workflows/ios_navigation_tests.yml new file mode 100644 index 000000000..5eebf5f6e --- /dev/null +++ b/.github/workflows/ios_navigation_tests.yml @@ -0,0 +1,76 @@ +name: Build and Run iOS Navigation Tests + +on: + pull_request: + branches: + - main + +jobs: + software_navigation_tests: + runs-on: macos-14 + env: + PLATFORM_VERSION: "17.2" + DEVICE_NAME: "iPhone 15" + APP_PATH: "./MyTestApp.app" + + steps: + - name: ๐Ÿงพ Checkout repo + uses: actions/checkout@v4 + + - name: ๐Ÿ”ง Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: ๐Ÿ”ง Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: ๐Ÿ“ฆ Install Appium + XCUITest driver + run: | + npm install -g appium + appium driver install xcuitest + + - name: ๐Ÿงน Clean DerivedData and WDA cache + run: | + rm -rf ~/Library/Developer/Xcode/DerivedData + rm -rf ~/.appium/node_modules/appium-webdriveragent/Build + + - name: ๐Ÿงช Start Appium Server + run: | + nohup appium --log appium.log & + + - name: ๐Ÿ“ฑ Create iOS Simulator (if needed) + run: | + SIMULATOR_NAME="ci-sim-$RANDOM" + xcrun simctl create "$SIMULATOR_NAME" "$DEVICE_NAME" "com.apple.CoreSimulator.SimRuntime.iOS-${PLATFORM_VERSION//./-}" + echo "SIMULATOR_NAME=$SIMULATOR_NAME" >> $GITHUB_ENV + + - name: ๐Ÿš€ Boot simulator + run: | + xcrun simctl boot "$SIMULATOR_NAME" + xcrun simctl list | grep Booted + + - name: Install MAUI Workloads + run: | + dotnet workload install ios --ignore-failed-sources + dotnet workload install maui --ignore-failed-sources + + - name: Restore MAUI App for iOS + run: dotnet restore TransactionMobile.Maui.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 TransactionMobile.Maui/TransactionMobile.Maui.csproj -f net8.0-ios -c Release --no-restore + + - name: Run iOS Navigation Tests + run: dotnet test TransactionMobile.Maui.UiTests/TransactionMobile.Maui.UiTests.csproj --filter "(Category=PRNavTest)&(Category=iOS)" --no-restore + + - name: Upload Appium Logs on Failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: ios-software_navigation_tests_appium + path: appium.log + + \ No newline at end of file diff --git a/TransactionMobile.Maui.BusinessLogic/TransactionMobile.Maui.BusinessLogic.csproj b/TransactionMobile.Maui.BusinessLogic/TransactionMobile.Maui.BusinessLogic.csproj index d196f715f..15818a5e2 100644 --- a/TransactionMobile.Maui.BusinessLogic/TransactionMobile.Maui.BusinessLogic.csproj +++ b/TransactionMobile.Maui.BusinessLogic/TransactionMobile.Maui.BusinessLogic.csproj @@ -52,9 +52,12 @@ + + + + - diff --git a/TransactionMobile.Maui.BusinessLogic/ViewModels/HomePageViewModel.cs b/TransactionMobile.Maui.BusinessLogic/ViewModels/HomePageViewModel.cs index 4d464f2d6..de2bc28a8 100644 --- a/TransactionMobile.Maui.BusinessLogic/ViewModels/HomePageViewModel.cs +++ b/TransactionMobile.Maui.BusinessLogic/ViewModels/HomePageViewModel.cs @@ -3,8 +3,10 @@ using System.Diagnostics.CodeAnalysis; using Logging; using Maui.UIServices; +#if !IOS using Microsoft.AppCenter; using Microsoft.AppCenter.Distribute; +#endif using Models; using Services; using UIServices; @@ -36,6 +38,7 @@ public async Task ShowDebugMessage(String message) { #region Methods +#if !IOS public async Task Initialise(CancellationToken cancellationToken) { Configuration configuration = this.ApplicationCache.GetConfiguration(); @@ -59,13 +62,14 @@ public async Task Initialise(CancellationToken cancellationToken) { Logger.LogError("Error during initialise", ex); } } +#endif private async void NoReleaseAvailable() { await this.DialogService.ShowDialog("Software Update", "No Release Available","OK"); } - //private Boolean IsIOS() => this.DeviceService.IsIOS//DeviceInfo.Current.Platform == DevicePlatform.iOS; +#if !IOS private Boolean OnReleaseAvailable(ReleaseDetails releaseDetails) { Logger.LogInformation("In OnReleaseAvailable"); // Look at releaseDetails public properties to get version information, release notes text or release notes URL @@ -111,6 +115,7 @@ private Boolean OnReleaseAvailable(ReleaseDetails releaseDetails) { // Return true if you're using your own dialog, false otherwise return true; } +#endif - #endregion +#endregion } \ No newline at end of file diff --git a/TransactionMobile.Maui.UiTests/Drivers/AppiumDriver.cs b/TransactionMobile.Maui.UiTests/Drivers/AppiumDriver.cs index 4a4329c8e..05ed07423 100644 --- a/TransactionMobile.Maui.UiTests/Drivers/AppiumDriver.cs +++ b/TransactionMobile.Maui.UiTests/Drivers/AppiumDriver.cs @@ -8,14 +8,17 @@ using System.Reflection; using System.Text; using System.Threading.Tasks; +using Google.Protobuf.WellKnownTypes; namespace TransactionMobile.Maui.UiTests.Drivers { + using Microsoft.Testing.Platform.Capabilities; + using OpenQA.Selenium; + using OpenQA.Selenium.Appium.Service.Options; + using OpenQA.Selenium.Appium.Windows; using System.Collections.ObjectModel; using System.Diagnostics; using System.Threading; - using OpenQA.Selenium; - using OpenQA.Selenium.Appium.Windows; public enum MobileTestPlatform { @@ -30,8 +33,11 @@ public class AppiumDriverWrapper public static MobileTestPlatform MobileTestPlatform; public static AppiumDriver Driver; - public void StartApp(){ + public void StartApp() { + //OptionCollector o = new OptionCollector(); + //o.AddArguments(GeneralOptionList.BasePath("/wd/hub")); AppiumLocalService appiumService = new AppiumServiceBuilder().UsingPort(4723) + //.WithArguments(o) .Build(); if (appiumService.IsRunning == false){ @@ -70,25 +76,78 @@ private static void SetupWindowsDriver(AppiumLocalService appiumService){ } private static void SetupiOSDriver(AppiumLocalService appiumService) { - var driverOptions = new AppiumOptions(); - driverOptions.AutomationName = "XCUITest"; - driverOptions.PlatformName = "iOS"; - driverOptions.PlatformVersion = "15.4"; - driverOptions.DeviceName = "iPhone 11"; - String assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - String binariesFolder = Path.Combine(assemblyFolder, "..", "..", "..", "..", @"TransactionMobile.Maui/bin/Release/net8.0-ios/iossimulator-x64/"); + String binariesFolder = Path.Combine(assemblyFolder, "..", "..", "..", "..", @"TransactionMobile.Maui/bin/Release/net8.0-ios/iossimulator-arm64/"); var apkPath = Path.Combine(binariesFolder, "TransactionMobile.Maui.app"); - driverOptions.App = apkPath; - driverOptions.AddAdditionalAppiumOption(MobileCapabilityType.NewCommandTimeout, 6000); - //driverOptions.AddAdditionalAppiumOption(MobileCapabilityType.FullReset, true); - //driverOptions.AddAdditionalAppiumOption("useNewWDA", true); - //driverOptions.AddAdditionalAppiumOption("wdaLaunchTimeout", 999999999); - //driverOptions.AddAdditionalAppiumOption("wdaConnectionTimeout", 999999999); - //driverOptions.AddAdditionalAppiumOption("restart", true); - //driverOptions.AddAdditionalAppiumOption("simulatorStartupTimeout", 5 * 60 * 1000); - - AppiumDriverWrapper.Driver = new OpenQA.Selenium.Appium.iOS.IOSDriver(appiumService, driverOptions, TimeSpan.FromMinutes(10)); + + var caps = new AppiumOptions(); + caps.PlatformName = "iOS"; + caps.PlatformVersion = "17.4"; + caps.DeviceName = "iPhone 15"; + caps.AutomationName = "XCUITest"; + caps.App = apkPath; + caps.AddAdditionalAppiumOption("fullReset", true); + caps.AddAdditionalAppiumOption("noReset", false); + caps.AddAdditionalAppiumOption("useNewWDA", true); + + //var driverOptions = new AppiumOptions(); + //driverOptions.AutomationName = "XCUITest"; + ////driverOptions.PlatformName = "iOS"; + ////driverOptions.PlatformVersion = "17.4"; + ////driverOptions.DeviceName = "iPhone 11"; + //driverOptions.AutomationName = "XCUITest"; + //driverOptions.PlatformName = "iOS"; + //driverOptions.PlatformVersion = "17.2"; + //driverOptions.AddAdditionalAppiumOption("udid", Environment.GetEnvironmentVariable("UDID")); // Corrected capability. + + //String assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + //String binariesFolder = Path.Combine(assemblyFolder, "..", "..", "..", "..", @"TransactionMobile.Maui/bin/Release/net8.0-ios/iossimulator-arm64/"); + //var apkPath = Path.Combine(binariesFolder, "TransactionMobile.Maui.app"); + //driverOptions.App = apkPath; + //driverOptions.AddAdditionalAppiumOption(MobileCapabilityType.NewCommandTimeout, 6000); + //driverOptions.AddAdditionalAppiumOption("waitForQuiescence", false); + //driverOptions.AddAdditionalAppiumOption("shouldWaitForQuiescence", false); + //driverOptions.AddAdditionalAppiumOption("showXcodeLog", true); + ////driverOptions.AddAdditionalAppiumOption("useNewWDA", true); + ////driverOptions.AddAdditionalAppiumOption("wdaLaunchTimeout", 60000); + ////driverOptions.AddAdditionalAppiumOption("wdaConnectionTimeout", 60000); + ////driverOptions.AddAdditionalAppiumOption("wdaLaunchTimeout", 120000); + ////driverOptions.AddAdditionalAppiumOption("wdaConnectionTimeout", 120000); + ////driverOptions.AddAdditionalAppiumOption("useNewWDA", false); + ////driverOptions.AddAdditionalAppiumOption("wdaStartupRetryInterval", 10000); + ////driverOptions.AddAdditionalAppiumOption("wdaStartupRetries", 4); + //driverOptions.AddAdditionalAppiumOption("usePrebuiltWDA", true); + //driverOptions.AddAdditionalAppiumOption("skipServerInstallation", true); + //driverOptions.AddAdditionalAppiumOption("skipProvisioningDeviceDetection", true); + //driverOptions.AddAdditionalAppiumOption("wdaLocalPort", Environment.GetEnvironmentVariable("WDA_PATH")); + //driverOptions.AddAdditionalAppiumOption("updatedWDABundleId", "WebDriverAgent/build"); + // Tell Appium to use the prebuilt WDA + //driverOptions.AddAdditionalAppiumOption("usePrebuiltWDA", true); + //driverOptions.AddAdditionalAppiumOption("useNewWDA", false); + //driverOptions.AddAdditionalAppiumOption("shouldUseSingletonTestManager", true); + + //driverOptions.AddAdditionalAppiumOption("derivedDataPath", "/Users/runner/work/WebDriverAgent/build/Build/Products/Debug-iphonesimulator"); + //driverOptions.AddAdditionalAppiumOption("wdaLocalPort", 8100); + + + //// Avoid unnecessary WDA rebuild attempts and add resilience + //driverOptions.AddAdditionalAppiumOption("wdaLaunchTimeout", 60000); + //driverOptions.AddAdditionalAppiumOption("wdaStartupRetries", 3); + //driverOptions.AddAdditionalAppiumOption("wdaStartupRetryInterval", 10000); + + //// (Optional but often helpful) + //driverOptions.AddAdditionalAppiumOption("waitForQuiescence", false); + //driverOptions.AddAdditionalAppiumOption("startIWDP", true); // if you want to inspect webviews + //driverOptions.AddAdditionalAppiumOption(MobileCapabilityType.FullReset, false); + //driverOptions.AddAdditionalAppiumOption(MobileCapabilityType.NoReset, true); + //driverOptions.AddAdditionalAppiumOption("usePrebuiltWDA", true); + //driverOptions.AddAdditionalAppiumOption("wdaLocalPort", 8100); // optional, but helpful + //driverOptions.AddAdditionalAppiumOption("shouldUseSingletonTestManager", true); // avoids extra processes + //driverOptions.AddAdditionalAppiumOption("showXcodeLog", true); // shows build errors in logs + //driverOptions.AddAdditionalAppiumOption("bundleId", "com.appium.WebDriverAgentRunner"); // match WDA bundle ID + + + AppiumDriverWrapper.Driver = new OpenQA.Selenium.Appium.iOS.IOSDriver(appiumService, caps, TimeSpan.FromMinutes(10)); } private static void SetupAndroidDriver(AppiumLocalService appiumService) { diff --git a/TransactionMobile.Maui.UiTests/Pages/LoginPage.cs b/TransactionMobile.Maui.UiTests/Pages/LoginPage.cs index 7ae04288a..cd9b2eb18 100644 --- a/TransactionMobile.Maui.UiTests/Pages/LoginPage.cs +++ b/TransactionMobile.Maui.UiTests/Pages/LoginPage.cs @@ -107,14 +107,17 @@ public async Task GetEmailAddress() public async Task EnterPassword(String password) { IWebElement element = await this.WaitForElementByAccessibilityId(this.PasswordEntry); - - element.SendKeys(password); } public async Task ClickLoginButton(){ await Retry.For(async () => { IWebElement element = await this.WaitForElementByAccessibilityId(this.LoginButton); + if (element.Displayed == false) + { + this.HideKeyboard(); + } + //element.Displayed.ShouldBeTrue(); element.Click(); }); diff --git a/TransactionMobile.Maui.UiTests/Steps/LoginSteps.cs b/TransactionMobile.Maui.UiTests/Steps/LoginSteps.cs index 7f394e0ca..a276983e9 100644 --- a/TransactionMobile.Maui.UiTests/Steps/LoginSteps.cs +++ b/TransactionMobile.Maui.UiTests/Steps/LoginSteps.cs @@ -56,7 +56,6 @@ public async Task GivenMyDeviceIsRegistered() { [Given(@"the application is in training mode")] public async Task GivenTheApplicationIsInTrainingMode() { var isTrainingModeOn = await this.loginPage.IsTrainingModeOn(); - if (isTrainingModeOn == false) await this.loginPage.SetTrainingModeOn(); } @@ -90,7 +89,14 @@ public async Task WhenITapOnLogin() { [Then(@"the Merchant Home Page is displayed")] public async Task ThenTheMerchantHomePageIsDisplayed() { - await this.mainPage.AssertOnPage(); + try { + await this.mainPage.AssertOnPage(); + } + catch(Exception ex) + { + String pageSource = await this.mainPage.GetPageSource(); + throw new Exception($"Unable to verify on page: {this.mainPage.GetType().Name} {Environment.NewLine} Page Source: {pageSource}", ex); + } } [Then(@"the available balance is shown as (.*)")] diff --git a/TransactionMobile.Maui/TransactionMobile.Maui.csproj b/TransactionMobile.Maui/TransactionMobile.Maui.csproj index 8700726a0..88342da7f 100644 --- a/TransactionMobile.Maui/TransactionMobile.Maui.csproj +++ b/TransactionMobile.Maui/TransactionMobile.Maui.csproj @@ -58,9 +58,12 @@ + + + + -