From fd709b0d085a51ea655ff3fb6b796e73d9b41081 Mon Sep 17 00:00:00 2001 From: Gjorgji Slamkov Date: Fri, 15 Aug 2025 09:25:10 +0200 Subject: [PATCH] test(BRIDGE-136): Download Bridge --- ci/test.yml | 34 +++- .../InstallerScripts/Get-BridgeInstaller.ps1 | 45 +++++ .../InstallerScripts/Remove-Bridge.ps1 | 156 ++++++++++++++++++ .../Remove-BridgeCredentials.ps1 | 34 ++++ .../ProtonMailBridge.UI.Tests.csproj | 11 +- .../ui_tests/windows_os/Results/HomeResult.cs | 38 ++--- .../Results/artifacts/Logs/.gitkeep | 0 .../windows_os/Results/artifacts/README.md | 1 + .../Results/artifacts/Screenshots/.gitkeep | 0 tests/e2e/ui_tests/windows_os/TestSession.cs | 152 ++++++++++++++++- .../windows_os/Tests/HelpMenuTests.cs | 8 +- .../windows_os/Tests/LoginLogoutTests.cs | 42 +++-- .../windows_os/Tests/SettingsMenuTests.cs | 18 +- .../windows_os/Tests/ZeroPercentUpdateTest.cs | 4 +- .../windows_os/TestsHelper/DebugTests.cs | 24 +++ .../windows_os/TestsHelper/RetryHelper.cs | 43 +++++ tests/e2e/ui_tests/windows_os/UIActions.cs | 46 ++++++ .../windows_os/Windows/HelpMenuWindow.cs | 2 +- .../ui_tests/windows_os/Windows/HomeWindow.cs | 5 +- .../windows_os/Windows/LoginWindow.cs | 45 ++++- .../windows_os/Windows/SettingsMenuWindow.cs | 119 ++++++------- 21 files changed, 697 insertions(+), 130 deletions(-) create mode 100644 tests/e2e/ui_tests/windows_os/InstallerScripts/Get-BridgeInstaller.ps1 create mode 100644 tests/e2e/ui_tests/windows_os/InstallerScripts/Remove-Bridge.ps1 create mode 100644 tests/e2e/ui_tests/windows_os/InstallerScripts/Remove-BridgeCredentials.ps1 create mode 100644 tests/e2e/ui_tests/windows_os/Results/artifacts/Logs/.gitkeep create mode 100644 tests/e2e/ui_tests/windows_os/Results/artifacts/README.md create mode 100644 tests/e2e/ui_tests/windows_os/Results/artifacts/Screenshots/.gitkeep create mode 100644 tests/e2e/ui_tests/windows_os/TestsHelper/DebugTests.cs create mode 100644 tests/e2e/ui_tests/windows_os/TestsHelper/RetryHelper.cs diff --git a/ci/test.yml b/ci/test.yml index 664f5b72..2f1e9baf 100644 --- a/ci/test.yml +++ b/ci/test.yml @@ -1,6 +1,4 @@ - --- - lint: stage: test extends: @@ -33,8 +31,6 @@ lint-bug-report-preview: paths: - coverage/** - - test-linux: extends: - .image-linux-test @@ -93,7 +89,6 @@ test-integration-race: paths: - integration-race-job.log - test-integration-nightly: extends: - test-integration @@ -131,12 +126,38 @@ test-coverage: paths: - coverage* - coverage/** - when: 'always' + when: "always" reports: coverage_report: coverage_format: cobertura path: coverage.xml +test-e2e-ui: + stage: test + extends: + - .rules-branch-and-MR-manual + tags: + - inbox-virt-windows-ui-v1 + variables: + REQUIRES_GRAPHICAL_CONSOLE: true + before_script: + - echo "Downloading dotnet dependencies" + - cd ./tests/e2e/ui_tests/windows_os/ + - dotnet restore ./ProtonMailBridge.UI.Tests.csproj + - dotnet list package + script: + - pwsh $CI_PROJECT_DIR/tests/e2e/ui_tests/windows_os/InstallerScripts/Get-BridgeInstaller.ps1 + - no_grpc_proxy=127.0.0.1 dotnet test ./ProtonMailBridge.UI.Tests.csproj -- NUnit.Where="cat != TemporarilyExcluded" + + after_script: + - cp /c/users/gitlab-runner/AppData/Roaming/protonmail/bridge-v3/logs/* $CI_PROJECT_DIR/tests/e2e/ui_tests/windows_os/Results/artifacts/Logs/ + - pwsh $CI_PROJECT_DIR/tests/e2e/ui_tests/windows_os/InstallerScripts/Remove-Bridge.ps1 + + artifacts: + paths: + - tests/e2e/ui_tests/windows_os/Results/artifacts/* + when: always + go-vuln-check: extends: - .image-linux-test @@ -150,4 +171,3 @@ go-vuln-check: when: always paths: - vulns* - diff --git a/tests/e2e/ui_tests/windows_os/InstallerScripts/Get-BridgeInstaller.ps1 b/tests/e2e/ui_tests/windows_os/InstallerScripts/Get-BridgeInstaller.ps1 new file mode 100644 index 00000000..ee6ccd8c --- /dev/null +++ b/tests/e2e/ui_tests/windows_os/InstallerScripts/Get-BridgeInstaller.ps1 @@ -0,0 +1,45 @@ +# Download the Bridge installer, and install it + +# Set variables with Bridge's download link and the download path +# to be used later on +$bridgeDownloadURL = $env:BRIDGE_DOWNLOAD_URL +$bridgeDownloadPath = "$env:CI_PROJECT_DIR/tests/e2e/ui_tests/windows_os/InstallerScripts/Bridge-Installer.exe" + +# Write the download link of Bridge to use it if manual re-tests are needed +Write-Output $bridgeDownloadURL + +# Download the Bridge-Installer.exe file +Invoke-WebRequest -Uri $bridgeDownloadURL -OutFile $bridgeDownloadPath + +if (Test-Path -Path $bridgeDownloadPath) { + Write-Output "Bridge Installer downloaded." + $file = Get-Item $bridgeDownloadPath | Select-Object Name, Length + $size = $file.Length + $sizeMB = "{0:N2}" -f ($size / 1MB) + Write-Output "File size in MB: $sizeMB" +} else { + Write-Output "Bridge installer NOT DOWNLOADED" +} +# Install the downloaded Bridge-Installer.exe file +# The installer is passive, meaning no user interaction is needed +# If the user does not have admin rights, it will still show the UAC prompt +# where a user needs to click on "Yes", +# but this will be not needed since the image in the pipeline will be an +# Admin account + +# Argument list for passive install +# $argList = "/passive INSTALLSHORTCUT=yes INSTALLDESKTOPSHORTCUT=yes" + +# Install Bridge +$process = Start-Process -Wait -ArgumentList "/passive INSTALLSHORTCUT=yes INSTALLDESKTOPSHORTCUT=yes" -PassThru -FilePath $bridgeDownloadPath + +# Check exit code of the installation process to confirm installation +if ($process.ExitCode -eq "0") { + Write-Output "Bridge installed successfully" +} else { + Write-Error "Bridge not installed successfully!" + Write-Error "Installer Exit Code: $($process.ExitCode)" +} + +# Delete the installer after installation to clean up the space +Remove-Item -Path $bridgeDownloadPath diff --git a/tests/e2e/ui_tests/windows_os/InstallerScripts/Remove-Bridge.ps1 b/tests/e2e/ui_tests/windows_os/InstallerScripts/Remove-Bridge.ps1 new file mode 100644 index 00000000..675cffee --- /dev/null +++ b/tests/e2e/ui_tests/windows_os/InstallerScripts/Remove-Bridge.ps1 @@ -0,0 +1,156 @@ +<# +PowerShell script for uninstalling Bridge +And removing all other files (vault, cache, updates, etc) +#> + + +# Define variables with path to Bridge files (vault, cache, startup entry etc) +$RoamProtonmail = "$env:APPDATA\protonmail" +$RoamProtonAG = "$env:APPDATA\Proton AG" +$LocalProtonmail = "$env:LOCALAPPDATA\protonmail" +$LocalProtonAG = "$env:LOCALAPPDATA\Proton AG" +$StartUpProtonBridge = "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup\Proton Mail Bridge.lnk" + +function Uninstall-PMBridge { + # Uninstalling REBRANDED version of Bridge + # Find the UninstallSTring in the registry (64bit & 32bit) + # Use the UninstallString with `msiexec.exe` to uninstall Bridge + + $registry64 = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" | ForEach-Object { Get-ItemProperty $_.PSPath } | Where-Object { $_ -match "Proton Mail Bridge" } | Select-Object UninstallString + + if ($registry64) { + $registry64 = $registry64 | Select-Object -Last 1 + $registry64 = $registry64.UninstallString -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X","" + $registry64 = $registry64.Trim() + Start-Process "msiexec.exe" -arg "/X $registry64 /passive" -Wait + } + + $registry32 = Get-ChildItem "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" | ForEach-Object { Get-ItemProperty $_.PSPath } | Where-Object { $_ -match "Proton Mail Bridge" } | Select-Object UninstallString + + if ($registry32) { + $registry32 = $registry32 | Select-Object -Last 1 + $registry32 = $registry32.UninstallString -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X","" + $registry32 = $registry32.Trim() + Start-Process "msiexec.exe" -arg "/X $registry32 /passive" -Wait + } + + + # Uninstalling PRE-REBRANDED version of Bridge + # Find the UninstallSTring in the registry (64bit & 32bit) + # Use the UninstallString with `msiexec.exe` to uninstall Bridge + + $preRebrandRegistry64 = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" | ForEach-Object { Get-ItemProperty $_.PSPath } | Where-Object { $_ -match "ProtonMail Bridge" } | Select-Object UninstallString + + if ($preRebrandRegistry64) { + $preRebrandRegistry64 = $preRebrandRegistry64 | Select-Object -Last 1 + $preRebrandRegistry64 = $preRebrandRegistry64.UninstallString -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X","" + $preRebrandRegistry64 = $preRebrandRegistry64.Trim() + Start-Process "msiexec.exe" -arg "/X $preRebrandRegistry64 /passive" -Wait + } + + $preRebrandRegistry32 = Get-ChildItem "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" | ForEach-Object { Get-ItemProperty $_.PSPath } | Where-Object { $_ -match "ProtonMail Bridge" } | Select-Object UninstallString + + if ($preRebrandRegistry32) { + $preRebrandRegistry32 = $preRebrandRegistry32 | Select-Object -Last 1 + $preRebrandRegistry32 = $preRebrandRegistry32.UninstallString -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X","" + $preRebrandRegistry32 = $preRebrandRegistry32.Trim() + Start-Process "msiexec.exe" -arg "/X $preRebrandRegistry32 /passive" -Wait + } + +} + + +function Stop-PMBridge { + # Stop the `bridge` process to completely quit Bridge + + $bridge = Get-Process "bridge" -ErrorAction SilentlyContinue + + if ($bridge){ + + $bridge | Stop-Process -Force + + } + +} + + +function Remove-PMBridgeResources { + # Delete all the Bridge resource folders + # They should be deleted with uninstalling Bridge + # But to just make sure do this again + + Remove-Item $RoamProtonmail -Force -Recurse -ErrorAction SilentlyContinue + Remove-Item $RoamProtonAG -Force -Recurse -ErrorAction SilentlyContinue + Remove-Item $LocalProtonmail -Force -Recurse -ErrorAction SilentlyContinue + Remove-Item $LocalProtonAG -Force -Recurse -ErrorAction SilentlyContinue + Remove-Item $StartUpProtonBridge -Force -Recurse -ErrorAction SilentlyContinue + +} + + +function Find-PMBridgeResources { + # Search and check if the Bridge resource folders + # Are deleted + # Write to Output the result + + $FolderExists = $false + + if ( Test-Path -Path $RoamProtonmail ){ + Write-Host "`r`n'$RoamProtonmail' is not deleted!" -ForegroundColor Red + $FolderExists = $true + } + + if ( Test-Path -Path $RoamProtonAG ) { + Write-Host "`r`n'$RoamProtonAG' is not deleted!" -ForegroundColor Red + $FolderExists = $true + } + + if ( Test-Path -Path $LocalProtonmail ) { + Write-Host "`r`n'$LocalProtonmail' is not deleted!" -ForegroundColor Red + $FolderExists = $true + } + + if ( Test-Path -Path $LocalProtonAG ) { + Write-Host "`r`n'$LocalProtonAG' is not deleted!" -ForegroundColor Red + $FolderExists = $true + } + + if ( Test-Path -Path $StartUpProtonBridge ) { + Write-Host "`r`n'$StartUpProtonBridge' is not deleted!" -ForegroundColor Red + $FolderExists = $true + } + + if ( $FolderExists ) { + Write-Host "`r`nSome directories were not deleted properly!`r`n" -ForegroundColor Red + } + + else { + Write-Host "`r`nAll Bridge resource folders deleted!`r`n" -ForegroundColor Green + } + +} + + +function Remove-PMBridgeCredentials { + # Delete the entries in the credential manager + + $CredentialsData = @((cmdkey /listall | Where-Object{$_ -like "*LegacyGeneric:target=protonmail*"}).replace("Target: ","")) + + for($i =0; $i -le ($CredentialsData.Count -1); $i++){ + [string]$DeleteData = $CredentialsData[$i].trim() + cmdkey /delete:$DeleteData + } + +} + + +function Invoke-BridgeFunctions { + Stop-PMBridge + Uninstall-PMBridge + Remove-PMBridgeResources + Find-PMBridgeResources + Remove-PMBridgeCredentials +} + + +Invoke-BridgeFunctions diff --git a/tests/e2e/ui_tests/windows_os/InstallerScripts/Remove-BridgeCredentials.ps1 b/tests/e2e/ui_tests/windows_os/InstallerScripts/Remove-BridgeCredentials.ps1 new file mode 100644 index 00000000..e8f2f011 --- /dev/null +++ b/tests/e2e/ui_tests/windows_os/InstallerScripts/Remove-BridgeCredentials.ps1 @@ -0,0 +1,34 @@ +<# +PowerShell script for removing Bridge credentials from +Microsoft Credentials manager +#> + +$Bridge = Get-Process "bridge" -ErrorAction SilentlyContinue +$CredentialsData = @((cmdkey /listall | Where-Object{$_ -like "*LegacyGeneric:target=protonmail*"}).replace("Target: ","")) + + +function Remove-BridgeCredentials { + # Delete the entries in the credential manager + + for($i=0; $i -le ($CredentialsData.Count -1); $i++){ + [string]$DeleteData = $CredentialsData[$i].trim() + cmdkey /delete:$DeleteData + } +} + +function Stop-PMBridge { + # Stop the `bridge` process to completely quit Bridge + + if ($Bridge){ + + $Bridge | Stop-Process -Force + + } +} + +function Invoke-Functions{ + Stop-PMBridge + Remove-BridgeCredentials +} + +Invoke-Functions diff --git a/tests/e2e/ui_tests/windows_os/ProtonMailBridge.UI.Tests.csproj b/tests/e2e/ui_tests/windows_os/ProtonMailBridge.UI.Tests.csproj index 30fee0b2..df924f52 100644 --- a/tests/e2e/ui_tests/windows_os/ProtonMailBridge.UI.Tests.csproj +++ b/tests/e2e/ui_tests/windows_os/ProtonMailBridge.UI.Tests.csproj @@ -18,14 +18,17 @@ - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + + + diff --git a/tests/e2e/ui_tests/windows_os/Results/HomeResult.cs b/tests/e2e/ui_tests/windows_os/Results/HomeResult.cs index a176b32e..bda9b122 100644 --- a/tests/e2e/ui_tests/windows_os/Results/HomeResult.cs +++ b/tests/e2e/ui_tests/windows_os/Results/HomeResult.cs @@ -16,42 +16,44 @@ namespace ProtonMailBridge.UI.Tests.Results private Button OkToAcknowledgeAccountAlreadySignedIn => NotificationWindow.FindFirstDescendant(cf => cf.ByControlType(ControlType.Button).And(cf.ByName("OK"))).AsButton(); private AutomationElement[] TextFields => Window.FindAllDescendants(cf => cf.ByControlType(ControlType.Text)); private TextBox SynchronizingField => TextFields[4].AsTextBox(); - private TextBox AccountDisabledErrorText => Window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Text).And(cf.ByName("failed to create new API client: 422 POST https://mail-api.proton.me/auth/v4: This account has been suspended due to a potential policy violation. If you believe this is in error, please contact us at https://proton.me/support/appeal-abuse (Code=10003, Status=422)"))).AsTextBox(); - private TextBox AccountDelinquentErrorText => Window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Text).And(cf.ByName("failed to create new API client: 422 POST https://mail-api.proton.me/auth/v4: Use of this client requires permissions not available to your account (Code=2011, Status=422)"))).AsTextBox(); + private TextBox AccountDisabledErrorText => Window.FindAllDescendants(cf => cf.ByControlType(ControlType.Text)).FirstOrDefault(e =>!string.IsNullOrEmpty(e.Name) && e.Name.IndexOf("This account has been suspended due to a potential policy violation.", StringComparison.OrdinalIgnoreCase) >= 0)?.AsTextBox(); private TextBox IncorrectLoginCredentialsErrorText => Window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Text).And(cf.ByName("Incorrect login credentials"))).AsTextBox(); private TextBox EnterEmailOrUsernameErrorText => Window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Text).And(cf.ByName("Enter email or username"))).AsTextBox(); private TextBox EnterPasswordErrorText => Window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Text).And(cf.ByName("Enter password"))).AsTextBox(); private TextBox ConnectedStateText => Window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Text).And(cf.ByName("Connected"))).AsTextBox(); private CheckBox SplitAddressesToggle => AccountView.FindFirstDescendant(cf => cf.ByControlType(ControlType.CheckBox).And(cf.ByName("Split addresses toggle"))).AsCheckBox(); + + public HomeResult CheckConnectedState() { - Assert.That(ConnectedStateText.IsAvailable, Is.True); + RetryHelper.Eventually(() => ConnectedStateText.IsAvailable); return this; } public HomeResult CheckIfLoggedIn() { - Assert.That(SignOutButton.IsAvailable, Is.True); + RetryHelper.Eventually(() => SignOutButton.IsAvailable); return this; } public HomeResult CheckIfSynchronizingBarIsShown() { - Assert.That(SynchronizingField.IsAvailable && SynchronizingField.Name.StartsWith("Synchronizing"), Is.True); + RetryHelper.Eventually(() => SynchronizingField.IsAvailable && SynchronizingField.Name.StartsWith("Synchronizing")); return this; } public HomeResult CheckIfFreeAccountErrorIsDisplayed(string ErrorText) { - Assert.That(FreeAccountErrorText.Name == ErrorText, Is.True); + RetryHelper.Eventually(() => FreeAccountErrorText.Name == ErrorText); return this; } public HomeResult CheckIfAccountIsSignedOut() { - Assert.That(SignedOutAccount.IsAvailable, Is.True); + RetryHelper.Eventually(() => SignedOutAccount.IsAvailable); return this; } public HomeResult CheckIfAccountAlreadySignedInIsDisplayed() { - Assert.That(AlreadySignedInText.IsAvailable, Is.True); + + RetryHelper.Eventually(() => AlreadySignedInText.IsAvailable); return this; } public HomeResult ClickOkToAcknowledgeAccountAlreadySignedIn () @@ -67,32 +69,30 @@ namespace ProtonMailBridge.UI.Tests.Results } public HomeResult CheckIfEnterUsernameAndEnterPasswordErrorMsgsAreDisplayed() - { - Assert.That(EnterEmailOrUsernameErrorText.IsAvailable && EnterPasswordErrorText.IsAvailable, Is.True); + { + RetryHelper.Eventually(() => EnterEmailOrUsernameErrorText.IsAvailable && EnterPasswordErrorText.IsAvailable); return this; } public HomeResult CheckIfDsabledAccountErrorIsDisplayed() { - Assert.That(AccountDisabledErrorText.IsAvailable, Is.True); - return this; - } - - public HomeResult CheckIfDelinquentAccountErrorIsDisplayed() - { - Assert.That(AccountDelinquentErrorText.IsAvailable, Is.True); + RetryHelper.Eventually(() => AccountDisabledErrorText.IsAvailable); return this; } public HomeResult CheckIfNotificationTextIsShown() { - Assert.That(AlreadySignedInText.IsAvailable, Is.True); + RetryHelper.Eventually(() => AlreadySignedInText.IsAvailable); return this; } public HomeResult CheckIfSplitAddressesIsDisabledByDefault() { - Assert.That(SplitAddressesToggle.IsToggled, Is.False); + RetryHelper.Eventually(() => + { + bool isNotToggled = SplitAddressesToggle.IsToggled == null || !(bool)SplitAddressesToggle.IsToggled; + return isNotToggled; + }); return this; } } diff --git a/tests/e2e/ui_tests/windows_os/Results/artifacts/Logs/.gitkeep b/tests/e2e/ui_tests/windows_os/Results/artifacts/Logs/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/e2e/ui_tests/windows_os/Results/artifacts/README.md b/tests/e2e/ui_tests/windows_os/Results/artifacts/README.md new file mode 100644 index 00000000..59f5c741 --- /dev/null +++ b/tests/e2e/ui_tests/windows_os/Results/artifacts/README.md @@ -0,0 +1 @@ +# This is a folder where all files that are needed for debugging purposes will be saved in regards to failing tests diff --git a/tests/e2e/ui_tests/windows_os/Results/artifacts/Screenshots/.gitkeep b/tests/e2e/ui_tests/windows_os/Results/artifacts/Screenshots/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/e2e/ui_tests/windows_os/TestSession.cs b/tests/e2e/ui_tests/windows_os/TestSession.cs index d2418e1a..fd0cb59a 100644 --- a/tests/e2e/ui_tests/windows_os/TestSession.cs +++ b/tests/e2e/ui_tests/windows_os/TestSession.cs @@ -1,11 +1,16 @@ using System; using System.Threading; +using System.Management.Automation; +using System.Management.Automation.Runspaces; using FlaUI.Core.AutomationElements; using FlaUI.Core; using FlaUI.UIA3; using ProtonMailBridge.UI.Tests.TestsHelper; using FlaUI.Core.Input; using System.Diagnostics; +using System.Drawing.Text; +using System.Collections.ObjectModel; +using FlaUI.Core.Tools; namespace ProtonMailBridge.UI.Tests { @@ -17,13 +22,57 @@ namespace ProtonMailBridge.UI.Tests protected static Window Window; protected static Window ChromeWindow; protected static Window FileExplorerWindow; + protected static Runspace? myRunSpace; + protected static string? bridgeDownloadURL; + protected static string downloadEnvVariable = "BRIDGE_DOWNLOAD_URL"; + private static readonly DebugTests debugTests = new(); + protected static string atlasEnvironment = "https://mail-api.proton.pink/"; + protected static void ClientCleanup() { - App.Kill(); - App.Dispose(); + var outcome = TestContext.CurrentContext.Result.Outcome.Status; + if (outcome == NUnit.Framework.Interfaces.TestStatus.Failed) + { + try + { + debugTests.TakeScreenshot(); + } + catch (Exception ex) + { + TestContext.Out.WriteLine(ex.ToString()); + } + } + + try + { + App.Kill(); + } + catch (Exception ex) + { + TestContext.Out.WriteLine(ex.ToString()); + } + + try + { + App.Dispose(); + } + catch (Exception ex) + { + TestContext.Out.WriteLine(ex.ToString()); + } + // Give some time to properly exit the app Thread.Sleep(10000); + try + { + RemoveBridgeCredentials(); + } + catch (Exception ex) + { + TestContext.Out.WriteLine($"Failed to remove Bridge credentials: {ex}"); + } + } public static void switchToFileExplorerWindow() @@ -60,28 +109,117 @@ namespace ProtonMailBridge.UI.Tests } // Cast the found element to a Window object - ChromeWindow = _chromeWindow.AsWindow(); + ChromeWindow = _chromeWindow.AsWindow(); // Focus on the Chrome window ChromeWindow.Focus(); } + protected static void RefreshWindow(TimeSpan? timeout = null) + { + Window = null; + TimeSpan refreshTimeout = timeout ?? TestData.ThirtySecondsTimeout; + RetryResult retry = Retry.WhileNull( + () => + { + try + { + Window = App.GetMainWindow(new UIA3Automation(), refreshTimeout); + } + catch (System.TimeoutException) + { + // Ignore + } + return Window; + }, + refreshTimeout, TestData.RetryInterval); + + if (!retry.Success) + { + Assert.Fail($"Failed to refresh window in {refreshTimeout.TotalSeconds} seconds."); + } + } + public static void LaunchApp() { + TestContext.Out.WriteLine($"[RUNNING TEST] {TestContext.CurrentContext.Test.FullName}"); + System.Environment.SetEnvironmentVariable("BRIDGE_HOST_URL", $"{atlasEnvironment}"); string appExecutable = TestData.AppExecutable; Application.Launch(appExecutable); Wait.UntilInputIsProcessed(TestData.FiveSecondsTimeout); App = Application.Attach("bridge-gui.exe"); + RefreshWindow(TestData.OneMinuteTimeout); + Window.Focus(); + } - try + private static RetryResult WaitUntilAppIsRunning() + { + RetryResult retry = Retry.WhileFalse( + () => + { + Process[] pname = Process.GetProcessesByName("Proton Mail Bridge"); + return pname.Length > 0; + }, + TimeSpan.FromSeconds(30), TestData.RetryInterval); + + return retry; + } + public static void CreateRunSpace() + { + bridgeDownloadURL = Environment.GetEnvironmentVariable($"{downloadEnvVariable}"); + myRunSpace = RunspaceFactory.CreateRunspace(); + myRunSpace.Open(); + Pipeline cmd = myRunSpace.CreatePipeline($"New-Item env:{downloadEnvVariable} -Value {bridgeDownloadURL} -Force"); + cmd.Invoke(); + cmd = myRunSpace.CreatePipeline(@"Set-Location $env:CI_PROJECT_DIR\tests\e2e\ui_tests\windows_os\InstallerScripts"); + cmd.Invoke(); + cmd = myRunSpace.CreatePipeline(@"Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope CurrentUser"); + cmd.Invoke(); + } + + public static Collection? InstallBridge() + { + CreateRunSpace(); + if (myRunSpace is not null) { - Window = App.GetMainWindow(new UIA3Automation(), TestData.ThirtySecondsTimeout); + Pipeline cmd = myRunSpace.CreatePipeline("Get-Location"); + cmd.Invoke(); + cmd = myRunSpace.CreatePipeline(@".\Get-BridgeInstaller.ps1"); + var objects = cmd.Invoke(); + + return objects; } - catch (System.TimeoutException) + + return null; + } + + public static Collection? UninstallBridge() + { + CreateRunSpace(); + if (myRunSpace is not null) { - Assert.Fail("Failed to get window of application!"); + Pipeline cmd = myRunSpace.CreatePipeline(@".\Remove-Bridge.ps1"); + var objects = cmd.Invoke(); + + return objects; } + + return null; + } + + public static Collection? RemoveBridgeCredentials() + { + CreateRunSpace(); + if (myRunSpace is not null) + { + Pipeline cmd = myRunSpace.CreatePipeline(@".\Remove-BridgeCredentials.ps1"); + var objects = cmd.Invoke(); + + return objects; + } + + return null; } } } \ No newline at end of file diff --git a/tests/e2e/ui_tests/windows_os/Tests/HelpMenuTests.cs b/tests/e2e/ui_tests/windows_os/Tests/HelpMenuTests.cs index fbfdb59f..b55c0048 100644 --- a/tests/e2e/ui_tests/windows_os/Tests/HelpMenuTests.cs +++ b/tests/e2e/ui_tests/windows_os/Tests/HelpMenuTests.cs @@ -33,7 +33,7 @@ namespace ProtonMailBridge.UI.Tests.Tests _homeResult.CheckIfLoggedIn(); } - [Test] + [Test, Category("TemporarilyExcluded")] public void OpenGoToHelpTopics() { _loginWindow.SignIn(TestUserData.GetPaidUser()); @@ -46,7 +46,7 @@ namespace ProtonMailBridge.UI.Tests.Tests _helpMenuWindow.ClickBackFromHelpMenu(); } - [Test] + [Test, Category("TemporarilyExcluded")] public void CheckForUpdates() { _loginWindow.SignIn(TestUserData.GetPaidUser()); @@ -58,7 +58,7 @@ namespace ProtonMailBridge.UI.Tests.Tests Wait.UntilInputIsProcessed(TimeSpan.FromSeconds(1)); _helpMenuWindow.ClickBackFromHelpMenu(); } - [Test] + [Test, Category("TemporarilyExcluded")] public void OpenLogs() { _loginWindow.SignIn(TestUserData.GetPaidUser()); @@ -134,7 +134,7 @@ namespace ProtonMailBridge.UI.Tests.Tests [TearDown] public void TestCleanup() { - _mainWindow.RemoveAccount(); + _mainWindow.RemoveAccountTestCleanup(); ClientCleanup(); } } diff --git a/tests/e2e/ui_tests/windows_os/Tests/LoginLogoutTests.cs b/tests/e2e/ui_tests/windows_os/Tests/LoginLogoutTests.cs index 3a4dcafe..3b076cbb 100644 --- a/tests/e2e/ui_tests/windows_os/Tests/LoginLogoutTests.cs +++ b/tests/e2e/ui_tests/windows_os/Tests/LoginLogoutTests.cs @@ -3,25 +3,39 @@ using ProtonMailBridge.UI.Tests.TestsHelper; using ProtonMailBridge.UI.Tests.Windows; using ProtonMailBridge.UI.Tests.Results; using FlaUI.Core.Input; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; namespace ProtonMailBridge.UI.Tests.Tests { [TestFixture] + [Category("LoginLogoutTests")] public class LoginLogoutTests : TestSession { private readonly LoginWindow _loginWindow = new(); private readonly HomeWindow _mainWindow = new(); private readonly HomeResult _homeResult = new(); private readonly string FreeAccountErrorText = "Bridge is exclusive to our mail paid plans. Upgrade your account to use Bridge."; + private bool removeAccount = true; [Test] + [Category("NOOP")] + public void Noop() + { + TestContext.Out.WriteLine("NoOP"); + removeAccount = false; + } + + [Test] + [Category("DebugTests")] public void LoginAsFreeUser() { _loginWindow.SignIn(TestUserData.GetFreeUser()); _homeResult.CheckIfFreeAccountErrorIsDisplayed(FreeAccountErrorText); + removeAccount = false; } [Test] + [Category("DebugTests")] public void LoginAsPaidUser() { _loginWindow.SignIn(TestUserData.GetPaidUser()); @@ -46,13 +60,8 @@ namespace ProtonMailBridge.UI.Tests.Tests [Test] public void AddAliasAddress() { - _loginWindow.SignIn(TestUserData.GetPaidUser()); - _homeResult.CheckIfLoggedIn(); - _mainWindow.AddNewAccount(); _loginWindow.SignIn(TestUserData.GetAliasUser()); - _homeResult.CheckIfAccountAlreadySignedInIsDisplayed(); - _homeResult.ClickOkToAcknowledgeAccountAlreadySignedIn(); - _loginWindow.ClickCancelToSignIn(); + _homeResult.CheckIfLoggedIn(); } [Test] @@ -83,6 +92,7 @@ namespace ProtonMailBridge.UI.Tests.Tests _loginWindow.SignIn(TestUserData.GetIncorrectCredentialsUser()); _homeResult.CheckIfIncorrectCredentialsErrorIsDisplayed(); _loginWindow.ClickCancelToSignIn(); + removeAccount = false; } [Test, Order (1)] @@ -91,8 +101,7 @@ namespace ProtonMailBridge.UI.Tests.Tests _loginWindow.SignIn(TestUserData.GetEmptyCredentialsUser()); _homeResult.CheckIfEnterUsernameAndEnterPasswordErrorMsgsAreDisplayed(); _loginWindow.ClickCancelToSignIn(); - _loginWindow.SignIn(TestUserData.GetPaidUser()); - _homeResult.CheckIfLoggedIn(); + removeAccount = false; } [Test] @@ -114,14 +123,7 @@ namespace ProtonMailBridge.UI.Tests.Tests _loginWindow.SignIn(TestUserData.GetDisabledUser()); _homeResult.CheckIfDsabledAccountErrorIsDisplayed(); _loginWindow.ClickCancelToSignIn(); - } - - [Test] - public void AddDeliquentAccount() - { - _loginWindow.SignIn(TestUserData.GetDeliquentUser()); - _homeResult.CheckIfDelinquentAccountErrorIsDisplayed(); - _loginWindow.ClickCancelToSignIn(); + removeAccount = false; } [Test] @@ -143,14 +145,18 @@ namespace ProtonMailBridge.UI.Tests.Tests [SetUp] public void TestInitialize() - { + { LaunchApp(); + Thread.Sleep(5000); } [TearDown] public void TestCleanup() { - _mainWindow.RemoveAccount(); + if (removeAccount) + { + _mainWindow.RemoveAccountTestCleanup(); + } ClientCleanup(); } } diff --git a/tests/e2e/ui_tests/windows_os/Tests/SettingsMenuTests.cs b/tests/e2e/ui_tests/windows_os/Tests/SettingsMenuTests.cs index 72e04aaa..f642cbd6 100644 --- a/tests/e2e/ui_tests/windows_os/Tests/SettingsMenuTests.cs +++ b/tests/e2e/ui_tests/windows_os/Tests/SettingsMenuTests.cs @@ -14,6 +14,7 @@ using FlaUI.UIA3; namespace ProtonMailBridge.UI.Tests.Tests { [TestFixture] + [Category("SettingsMenuTests")] public class SettingsMenuTests : TestSession { private readonly LoginWindow _loginWindow = new(); @@ -175,7 +176,6 @@ namespace ProtonMailBridge.UI.Tests.Tests _settingsMenuWindow.ClickSettingsButton(); _settingsMenuWindow.ExpandAdvancedSettings(); Mouse.Scroll(-20); - //Thread.Sleep(3000); _settingsMenuResults.CollectUsageDiagnosticsIsEnabledByDefault(); Mouse.Scroll(20); _settingsMenuWindow.CollapseAdvancedSettings(); @@ -189,8 +189,10 @@ namespace ProtonMailBridge.UI.Tests.Tests _settingsMenuWindow.ClickSettingsButton(); _settingsMenuWindow.ExpandAdvancedSettings(); Mouse.Scroll(-20); + Thread.Sleep(5000); _settingsMenuWindow.DisableAndEnableCollectUsageDiagnostics(); Mouse.Scroll(20); + Thread.Sleep(5000); _settingsMenuWindow.CollapseAdvancedSettings(); _settingsMenuWindow.ClickBackFromSettingsMenu(); } @@ -270,7 +272,7 @@ namespace ProtonMailBridge.UI.Tests.Tests _settingsMenuWindow.ClickBackFromSettingsMenu(); } - [Test] + [Test, Category("TemporarilyExcluded")] public void ChangeLocationSwitchBackToDefaultAndDeleteOldLocalCacheLocation() { _loginWindow.SignIn(TestUserData.GetPaidUser()); @@ -284,7 +286,7 @@ namespace ProtonMailBridge.UI.Tests.Tests _settingsMenuWindow.ClickBackFromSettingsMenu(); } - [Test] + [Test, Category("TemporarilyExcluded")] public void ExportTlsCertificatesVerifyExportAndDeleteTheExportFolder() { _loginWindow.SignIn(TestUserData.GetPaidUser()); @@ -325,7 +327,15 @@ namespace ProtonMailBridge.UI.Tests.Tests [TearDown] public void TestCleanup() { - _mainWindow.RemoveAccount(); + try + { + _mainWindow.RemoveAccountTestCleanup(); + } + catch (Exception ex) + { + TestContext.Out.WriteLine("Teardown error on test account cleanup: " + ex); + } + ClientCleanup(); } } diff --git a/tests/e2e/ui_tests/windows_os/Tests/ZeroPercentUpdateTest.cs b/tests/e2e/ui_tests/windows_os/Tests/ZeroPercentUpdateTest.cs index cb4a4e25..8641be2d 100644 --- a/tests/e2e/ui_tests/windows_os/Tests/ZeroPercentUpdateTest.cs +++ b/tests/e2e/ui_tests/windows_os/Tests/ZeroPercentUpdateTest.cs @@ -26,8 +26,7 @@ namespace ProtonMailBridge.UI.Tests.Tests LaunchApp(); } - [Test] - [Category("ZeroPercentUpdateRollout")] + [Test, Category("TemporarilyExcluded")] public void EnableBetaAccessVerifyBetaIsEnabledVerifyNotificationAndRestartBridge() { _zeroPercentWindow.ClickStartSetupButton(); @@ -52,6 +51,7 @@ namespace ProtonMailBridge.UI.Tests.Tests public void TestCleanup() { ClientCleanup(); + } } } diff --git a/tests/e2e/ui_tests/windows_os/TestsHelper/DebugTests.cs b/tests/e2e/ui_tests/windows_os/TestsHelper/DebugTests.cs new file mode 100644 index 00000000..ca0817f5 --- /dev/null +++ b/tests/e2e/ui_tests/windows_os/TestsHelper/DebugTests.cs @@ -0,0 +1,24 @@ +using FlaUI.Core.Capturing; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; + +namespace ProtonMailBridge.UI.Tests.TestsHelper +{ + internal class DebugTests + { + public void TakeScreenshot() + { + string testName = TestContext.CurrentContext.Test.Name; + string Timestamp = DateTime.Now.ToString("yyyyMMddHHmmss"); + string ScreenshotName = "Screenshot_" + testName + "_" + Timestamp + ".png"; + string Query = "%CI_PROJECT_DIR%\\tests\\e2e\\ui_tests\\windows_os\\Results\\artifacts\\Screenshots\\" + ScreenshotName; + string ScreenshotLocation = Environment.ExpandEnvironmentVariables(Query); + var ScreenshotFile = Capture.Screen(); + ScreenshotFile.ToFile(ScreenshotLocation); + } + } +} diff --git a/tests/e2e/ui_tests/windows_os/TestsHelper/RetryHelper.cs b/tests/e2e/ui_tests/windows_os/TestsHelper/RetryHelper.cs new file mode 100644 index 00000000..c386abd3 --- /dev/null +++ b/tests/e2e/ui_tests/windows_os/TestsHelper/RetryHelper.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading.Tasks; + +namespace ProtonMailBridge.UI.Tests.TestsHelper +{ + public class RetryHelper + { + public static void Eventually(Func condition, int retries = 10, int delaySeconds = 5) + { + for (int i = 0; i < retries; i++) + { + if (condition()) return; + + Thread.Sleep(TimeSpan.FromSeconds(delaySeconds)); + } + + Assert.Fail(); + } + + public static void EventuallyAction(Action action, int retries = 20, int delaySeconds = 2) + { + Exception? lastException = null; + for (int i = 0; i < retries; i++) + { + try + { + action(); + return; + } catch (Exception e) + { + lastException = e; + Thread.Sleep(TimeSpan.FromSeconds(delaySeconds)); + } + } + + throw new Exception("Eventually failed after retries", lastException); + } + } +} diff --git a/tests/e2e/ui_tests/windows_os/UIActions.cs b/tests/e2e/ui_tests/windows_os/UIActions.cs index 5adbbdda..1199778b 100644 --- a/tests/e2e/ui_tests/windows_os/UIActions.cs +++ b/tests/e2e/ui_tests/windows_os/UIActions.cs @@ -1,14 +1,60 @@ using System; +using System.Runtime.InteropServices; using FlaUI.Core.AutomationElements; using FlaUI.Core.Definitions; using FlaUI.Core.Input; using FlaUI.Core.Tools; using NUnit.Framework; +using ProtonMailBridge.UI.Tests.TestsHelper; namespace ProtonMailBridge.UI.Tests { public class UIActions : TestSession { public AutomationElement AccountView => Window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Pane)); + + protected dynamic WaitUntilElementExistsByName(string name, TimeSpan time) + { + WaitForElement(() => + { + RefreshWindow(); + return Window.FindFirstDescendant(cf => cf.ByName(name)) != null; + }, time, name); + + return this; + } + protected AutomationElement ElementByName(string name, TimeSpan? timeout = null) + { + WaitUntilElementExistsByName(name, timeout ?? TestData.TenSecondsTimeout); + return Window.FindFirstDescendant(cf => cf.ByName(name)); + } + private void WaitForElement(Func function, TimeSpan time, string selector, string customMessage = null) + { + RetryResult retry = Retry.WhileFalse( + () => { + try + { + App.WaitWhileBusy(); + return function(); + } + catch (COMException) + { + return false; + } + }, + time, TestData.RetryInterval); + + if (!retry.Success) + { + if(customMessage == null) + { + Assert.Fail($"Failed to get {selector} element within {time.TotalSeconds} seconds."); + } + else + { + Assert.Fail(customMessage); + } + } + } } } \ No newline at end of file diff --git a/tests/e2e/ui_tests/windows_os/Windows/HelpMenuWindow.cs b/tests/e2e/ui_tests/windows_os/Windows/HelpMenuWindow.cs index f4b67162..76bf352b 100644 --- a/tests/e2e/ui_tests/windows_os/Windows/HelpMenuWindow.cs +++ b/tests/e2e/ui_tests/windows_os/Windows/HelpMenuWindow.cs @@ -32,7 +32,7 @@ namespace ProtonMailBridge.UI.Tests.Windows private Button LogsButton => HomeButtons[9].AsButton(); private Button ReportProblemButton => HomeButtons[10].AsButton(); private Button ICannotFindEmailInClient => HomeButtons[7].AsButton(); - private TextBox DescriptionOnWhatHappened => Window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Edit)).AsTextBox(); + private TextBox DescriptionOnWhatHappened => Window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Pane)).FindFirstDescendant(cf => cf.ByControlType(ControlType.Pane)).FindFirstDescendant(cf => cf.ByControlType(ControlType.Edit)).AsTextBox(); private RadioButton MissingEmails => ReportProblemPane[0].FindFirstDescendant(cf => cf.ByControlType(ControlType.RadioButton).And(cf.ByName("Old emails are missing"))).AsRadioButton(); private RadioButton FindEmails => ReportProblemPane[0].FindFirstDescendant(cf => cf.ByControlType(ControlType.RadioButton).And(cf.ByName("Yes"))).AsRadioButton(); private CheckBox VPNSoftware => ReportProblemPane[0].FindFirstDescendant(cf => cf.ByControlType(ControlType.CheckBox).And(cf.ByName("VPN"))).AsCheckBox(); diff --git a/tests/e2e/ui_tests/windows_os/Windows/HomeWindow.cs b/tests/e2e/ui_tests/windows_os/Windows/HomeWindow.cs index 259ff0d8..1ef48863 100644 --- a/tests/e2e/ui_tests/windows_os/Windows/HomeWindow.cs +++ b/tests/e2e/ui_tests/windows_os/Windows/HomeWindow.cs @@ -11,6 +11,7 @@ namespace ProtonMailBridge.UI.Tests.Windows public class HomeWindow : UIActions { private AutomationElement[] AccountViewButtons => AccountView.FindAllChildren(cf => cf.ByControlType(ControlType.Button)); + private AutomationElement[] HomeButtons => Window.FindAllDescendants(cf => cf.ByControlType(ControlType.Button)); private Button AddNewAccountButton => HomeButtons[6].AsButton(); private Button RemoveAccountButton => AccountViewButtons[1].AsButton(); @@ -21,14 +22,14 @@ namespace ProtonMailBridge.UI.Tests.Windows private CheckBox SplitAddressesToggle => AccountView.FindFirstDescendant(cf => cf.ByControlType(ControlType.CheckBox).And(cf.ByName("Split addresses toggle"))).AsCheckBox(); private Button EnableSplitAddressButton => Window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Button).And(cf.ByName("Enable split mode"))).AsButton(); - public HomeWindow RemoveAccount() + public HomeWindow RemoveAccountTestCleanup() { try { RemoveAccountButton.Click(); ConfirmRemoveAccountButton.Click(); } - catch (System.NullReferenceException) + catch (System.IndexOutOfRangeException) { ClientCleanup(); } diff --git a/tests/e2e/ui_tests/windows_os/Windows/LoginWindow.cs b/tests/e2e/ui_tests/windows_os/Windows/LoginWindow.cs index 3ba8806d..a9535aab 100644 --- a/tests/e2e/ui_tests/windows_os/Windows/LoginWindow.cs +++ b/tests/e2e/ui_tests/windows_os/Windows/LoginWindow.cs @@ -4,6 +4,7 @@ using FlaUI.Core.Definitions; using ProtonMailBridge.UI.Tests.TestsHelper; using ProtonMailBridge.UI.Tests.Results; using System.Diagnostics; +using System.ComponentModel.DataAnnotations; namespace ProtonMailBridge.UI.Tests.Windows { @@ -11,7 +12,8 @@ namespace ProtonMailBridge.UI.Tests.Windows { private AutomationElement[] InputFields => Window.FindAllDescendants(cf => cf.ByControlType(ControlType.Edit)); private TextBox UsernameInput => InputFields[0].AsTextBox(); - private TextBox PasswordInput => InputFields[1].AsTextBox(); + private AutomationElement PasswordGroup => Window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Group).And(cf.ByName("Password"))); + private TextBox PasswordInput => PasswordGroup.FindFirstDescendant(cf => cf.ByControlType(ControlType.Edit)).AsTextBox(); private Button SignInButton => Window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Button).And(cf.ByName("Sign in"))).AsButton(); private Button SigningInButton => Window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Button).And(cf.ByName("Signing in"))).AsButton(); private Button StartSetupButton => Window.FindFirstDescendant(cf => cf.ByName("Start setup")).AsButton(); @@ -20,11 +22,17 @@ namespace ProtonMailBridge.UI.Tests.Windows private Button UnlockButton => Window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Button).And(cf.ByName("Unlock"))).AsButton(); private Button CancelSignIn => Window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Button).And(cf.ByName("Cancel"))).AsButton(); + private Button UnlockingButton => Window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Button).And(cf.ByName("Unlocking"))).AsButton(); + + private static readonly int loginTimeout = 500; + public LoginWindow SignIn(TestUserData user) { ClickStartSetupButton(); + + TestContext.Out.WriteLine($"Trying to login with '{user.Username}':'{user.Password}'. Attempt {i}."); EnterCredentials(user); - WaitForAuthorizationToComplete(60); + WaitForAuthorizationToComplete(loginTimeout); SetUpLater?.Click(); @@ -33,11 +41,9 @@ namespace ProtonMailBridge.UI.Tests.Windows public LoginWindow SignInMailbox(TestUserData user) { - ClickStartSetupButton(); - EnterCredentials(user); - Wait.UntilInputIsProcessed(TestData.TenSecondsTimeout); + SignIn(user); EnterMailboxPassword(user); - Wait.UntilInputIsProcessed(TestData.TenSecondsTimeout); + WaitForUnlockToComplete(loginTimeout); SetUpLater?.Click(); @@ -59,8 +65,14 @@ namespace ProtonMailBridge.UI.Tests.Windows public LoginWindow EnterCredentials(TestUserData user) { + for (int i = 0; i < InputFields.Length; i++) + { + Console.WriteLine($"---------- {InputFields[i].Name} ----------"); + } + UsernameInput.Text = user.Username; PasswordInput.Text = user.Password; + TestContext.Out.WriteLine($"Trying to sign in with username '{user.Username}' and password '{user.Password}'"); SignInButton.Click(); return this; } @@ -68,6 +80,7 @@ namespace ProtonMailBridge.UI.Tests.Windows public LoginWindow EnterMailboxPassword(TestUserData user) { MailboxPasswordInput.Text = user.MailboxPassword; + TestContext.Out.WriteLine($"Entering mailbox password '{user.MailboxPassword}'"); UnlockButton.Click(); return this; } @@ -97,5 +110,25 @@ namespace ProtonMailBridge.UI.Tests.Windows } } + + private void WaitForUnlockToComplete(int numOfSeconds) + { + TimeSpan timeout = TimeSpan.FromSeconds(numOfSeconds); + Stopwatch stopwatch = Stopwatch.StartNew(); + + + while (stopwatch.Elapsed < timeout) + { + //if Signing in button is not visible authorization process is finished + if (UnlockingButton == null) + { + return; + } + + Wait.UntilInputIsProcessed(); + Thread.Sleep(500); + } + + } } } \ No newline at end of file diff --git a/tests/e2e/ui_tests/windows_os/Windows/SettingsMenuWindow.cs b/tests/e2e/ui_tests/windows_os/Windows/SettingsMenuWindow.cs index 2176f448..908ec7c3 100644 --- a/tests/e2e/ui_tests/windows_os/Windows/SettingsMenuWindow.cs +++ b/tests/e2e/ui_tests/windows_os/Windows/SettingsMenuWindow.cs @@ -19,7 +19,6 @@ using ProtonMailBridge.UI.Tests.TestsHelper; using Keyboard = FlaUI.Core.Input.Keyboard; using Mouse = FlaUI.Core.Input.Mouse; using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window; -//using System.Windows.Forms; using CheckBox = FlaUI.Core.AutomationElements.CheckBox; using FlaUI.Core.Tools; using System.Diagnostics; @@ -87,125 +86,127 @@ namespace ProtonMailBridge.UI.Tests.Windows private Button ResetButton => Window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Button).And(cf.ByName("Reset Bridge button"))).AsButton(); private Button ResetAndRestartButtonInPopUp => NotificationWindow.FindFirstDescendant(cf => cf.ByControlType(ControlType.Button).And(cf.ByName("Reset and restart"))).AsButton(); private Button StartSetUpButton => Window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Button).And(cf.ByName("Start setup"))).AsButton(); - + public SettingsMenuWindow ClickSettingsButton() { - SettingsButton.Click(); + RetryHelper.EventuallyAction(() => SettingsButton.Click()); return this; } public SettingsMenuWindow ClickBackFromSettingsMenu() { - BackToAccountViewButton.Click(); + RetryHelper.EventuallyAction(() => BackToAccountViewButton.Click()); return this; } public SettingsMenuWindow DisableAndEnableAutomaticUpdates() { - AutomaticUpdates.Click(); - Assert.That(AutomaticUpdates.IsToggled, Is.False); + RetryHelper.EventuallyAction(() => AutomaticUpdates.Click()); + RetryHelper.EventuallyAction(() => Assert.That(AutomaticUpdates.IsToggled, Is.False)); Thread.Sleep(1000); - AutomaticUpdates.Click(); - Assert.That(AutomaticUpdates.IsToggled, Is.True); + RetryHelper.EventuallyAction(() => AutomaticUpdates.Click()); + RetryHelper.EventuallyAction(() => Assert.That(AutomaticUpdates.IsToggled, Is.True)); return this; } public SettingsMenuWindow DisableAndEnableOpenOnStartUp() { - OpenOnStartUp.Click(); - Assert.That(OpenOnStartUp.IsToggled, Is.False); + RetryHelper.EventuallyAction(() => OpenOnStartUp.Click()); + RetryHelper.EventuallyAction(() => Assert.That(OpenOnStartUp.IsToggled, Is.False)); Thread.Sleep(1000); - OpenOnStartUp.Click(); - Assert.That(OpenOnStartUp.IsToggled, Is.True); + RetryHelper.EventuallyAction(() => OpenOnStartUp.Click()); + RetryHelper.EventuallyAction(() => Assert.That(OpenOnStartUp.IsToggled, Is.True)); return this; } - public SettingsMenuWindow EnableAndDisableBetaAccess() { - BetaAccess.Click(); - EnableBetaAccessButtonInPopUp.Click(); + RetryHelper.EventuallyAction(() => BetaAccess.Click()); + RetryHelper.EventuallyAction(() => EnableBetaAccessButtonInPopUp.Click()); Thread.Sleep(1000); - Assert.That(BetaAccess.IsToggled, Is.True); - BetaAccess.Click(); - Assert.That(BetaAccess.IsToggled, Is.False); + RetryHelper.EventuallyAction(() => Assert.That(BetaAccess.IsToggled, Is.True)); + RetryHelper.EventuallyAction(() => BetaAccess.Click()); + RetryHelper.EventuallyAction(() => Assert.That(BetaAccess.IsToggled, Is.False)); return this; } public SettingsMenuWindow ExpandAdvancedSettings() { - AdvancedSettings.Click(); + RetryHelper.EventuallyAction(() => AdvancedSettings.Click()); Thread.Sleep(1000); - Assert.That(AlternativeRouting != null && AlternativeRouting.IsAvailable, Is.True); + RetryHelper.EventuallyAction(() => Assert.That(AlternativeRouting != null && AlternativeRouting.IsAvailable, Is.True)); return this; } public SettingsMenuWindow CollapseAdvancedSettings() { - AdvancedSettings.Click(); + RetryHelper.EventuallyAction(() => AdvancedSettings.Click()); return this; } + public SettingsMenuWindow EnableAndDisableAlternativeRouting() { - AlternativeRouting.Click(); - Assert.That(AlternativeRouting.IsToggled, Is.True); + RetryHelper.EventuallyAction(() => AlternativeRouting.Click()); + RetryHelper.EventuallyAction(() => Assert.That(AlternativeRouting.IsToggled, Is.True)); Thread.Sleep(1000); - AlternativeRouting.Click(); - Assert.That(AlternativeRouting?.IsToggled, Is.False); + RetryHelper.EventuallyAction(() => AlternativeRouting.Click()); + RetryHelper.EventuallyAction(() => Assert.That(AlternativeRouting?.IsToggled, Is.False)); return this; } public SettingsMenuWindow CheckEnableAndDisableDarkMode() { - DarkMode.Click(); - Assert.That(DarkMode.IsToggled, Is.True); + RetryHelper.EventuallyAction(() => DarkMode.Click()); + RetryHelper.EventuallyAction(() => Assert.That(DarkMode.IsToggled, Is.True)); Thread.Sleep(1000); - DarkMode.Click(); - Assert.That(DarkMode.IsToggled, Is.False); + RetryHelper.EventuallyAction(() => DarkMode.Click()); + RetryHelper.EventuallyAction(() => Assert.That(DarkMode.IsToggled, Is.False)); return this; } + public SettingsMenuWindow DisableAndEnableShowAllMail() { - ShowAllMail.Click(); - HideAllMailFolderInPopUp.Click(); - Assert.That(ShowAllMail.IsToggled, Is.False); + RetryHelper.EventuallyAction(() => ShowAllMail.Click()); + RetryHelper.EventuallyAction(() => HideAllMailFolderInPopUp.Click()); + RetryHelper.EventuallyAction(() => Assert.That(ShowAllMail.IsToggled, Is.False)); Thread.Sleep(1000); - ShowAllMail.Click(); + RetryHelper.EventuallyAction(() => ShowAllMail.Click()); Thread.Sleep(1000); - ShowAllMailFolderInPopUp.Click(); - Assert.That(ShowAllMail?.IsToggled, Is.True); + RetryHelper.EventuallyAction(() => ShowAllMailFolderInPopUp.Click()); + RetryHelper.EventuallyAction(() => Assert.That(ShowAllMail?.IsToggled, Is.True)); return this; } public SettingsMenuWindow DisableAndEnableCollectUsageDiagnostics() { - CollectUsageDiagnostics.Click(); + RetryHelper.EventuallyAction(() => CollectUsageDiagnostics.Click()); Thread.Sleep(3000); - Assert.That(CollectUsageDiagnostics.IsToggled, Is.False); + RetryHelper.EventuallyAction(() => Assert.That(CollectUsageDiagnostics.IsToggled, Is.False)); Thread.Sleep(1000); - CollectUsageDiagnostics.Click(); + RetryHelper.EventuallyAction(() => CollectUsageDiagnostics.Click()); Thread.Sleep(1000); - Assert.That(CollectUsageDiagnostics?.IsToggled, Is.True); + RetryHelper.EventuallyAction(() => Assert.That(CollectUsageDiagnostics?.IsToggled, Is.True)); return this; } - public SettingsMenuWindow OpenChangeDefaultPorts() { - ChangeDefaultPortsButton.Click(); + RetryHelper.EventuallyAction(() => ChangeDefaultPortsButton.Click()); return this; } public SettingsMenuWindow CancelChangingDefaultPorts() { - CancelDefaultPorts.Click(); + RetryHelper.EventuallyAction(() => CancelDefaultPorts.Click()); + return this; } private int GenerateUniqueRandomPort() { - return random.Next(MinPort, MaxPort +1); + return random.Next(MinPort, MaxPort + 1); } public SettingsMenuWindow ChangeDefaultPorts() { - ChangeDefaultPortsButton.Click(); + RetryHelper.EventuallyAction(() => ChangeDefaultPortsButton.Click()); + Thread.Sleep(2000); - ImapPort.Click(); + RetryHelper.EventuallyAction(() => ImapPort.Click()); int imapPort = GenerateUniqueRandomPort(); int smtpPort; @@ -226,7 +227,8 @@ namespace ProtonMailBridge.UI.Tests.Windows public SettingsMenuWindow SwitchBackToDefaultPorts() { - ChangeDefaultPortsButton.Click(); + RetryHelper.EventuallyAction(() => ChangeDefaultPortsButton.Click()); + Thread.Sleep(2000); ImapPort.Click(); ImapPort.Patterns.Value.Pattern.SetValue(""); @@ -241,17 +243,18 @@ namespace ProtonMailBridge.UI.Tests.Windows public SettingsMenuWindow OpenChangeConnectionMode() { - ChangeConnectionModeButton.Click(); + RetryHelper.EventuallyAction(() => ChangeConnectionModeButton.Click()); return this; } public SettingsMenuWindow CancelChangeConnectionMode() { - CancelChangeConnectionModeButton.Click(); + RetryHelper.EventuallyAction(() => CancelChangeConnectionModeButton.Click()); return this; } public SettingsMenuWindow ChangeConnectionMode() { - ImapSslMode.Click(); + RetryHelper.EventuallyAction(() => ImapSslMode.Click()); + SmtpSslMode.Click(); Thread.Sleep(2000); SaveChangedConnectionMode.Click(); @@ -259,7 +262,8 @@ namespace ProtonMailBridge.UI.Tests.Windows } public SettingsMenuWindow SwitchBackToDefaultConnectionMode() { - ImapStarttlsMode.Click(); + RetryHelper.EventuallyAction(() => ImapStarttlsMode.Click()); + SmtpStarttlsMode.Click(); Thread.Sleep(2000); SaveChangedConnectionMode.Click(); @@ -268,7 +272,7 @@ namespace ProtonMailBridge.UI.Tests.Windows public SettingsMenuWindow ConfigureLocalCache() { - ConfigureLocalCacheButton.Click(); + RetryHelper.EventuallyAction(() => ConfigureLocalCacheButton.Click()); return this; } public SettingsMenuWindow CancelToConfigureLocalCache() @@ -319,14 +323,15 @@ namespace ProtonMailBridge.UI.Tests.Windows public SettingsMenuWindow ChangeAndSwitchBackLocalCacheLocation() { string? userProfilePath = Environment.GetEnvironmentVariable("USERPROFILE"); - ChangeLocalCacheLocationButton.Click(); + RetryHelper.EventuallyAction(() => ChangeLocalCacheLocationButton.Click()); + Thread.Sleep(2000); FocusOnSelectCacheLocationWindow(); ClickNewFolder.Click(); Wait.UntilInputIsProcessed(TimeSpan.FromMilliseconds(2000)); Keyboard.TypeVirtualKeyCode(0x0D); AutomationElement pane = Window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Pane)); - AutomationElement pane2 = pane.FindFirstDescendant(cf => cf.ByControlType(ControlType.Pane).And(cf.ByName("Shell Folder View"))); + AutomationElement pane2 = pane.FindFirstDescendant(cf => cf.ByControlType(ControlType.Pane).And(cf.ByName("Shell Folder View"))); AutomationElement list = pane2.FindFirstDescendant(cf => cf.ByControlType(ControlType.List).And(cf.ByName("Items View"))); AutomationElement listItem = list.FindFirstDescendant(cf => cf.ByControlType(ControlType.ListItem).And(cf.ByName("New folder"))); TextBox folderName = listItem.FindFirstDescendant(cf => cf.ByControlType(ControlType.Edit)).AsTextBox(); @@ -409,7 +414,8 @@ namespace ProtonMailBridge.UI.Tests.Windows } public SettingsMenuWindow ExportAssertDeleteTLSCertificates() { - ExportTLSCertificatesButton.Click(); + RetryHelper.EventuallyAction(() => ExportTLSCertificatesButton.Click()); + Thread.Sleep(2000); ClickNewFolder.Click(); Wait.UntilInputIsProcessed(TimeSpan.FromMilliseconds(2000)); @@ -472,7 +478,8 @@ namespace ProtonMailBridge.UI.Tests.Windows } public SettingsMenuWindow VerifyRepairRestartsSync() { - RepairBridgeButton.Click(); + RetryHelper.EventuallyAction(() => RepairBridgeButton.Click()); + RepairButtonInPopUp.Click(); bool syncRestarted = WaitForCondition(() => { @@ -509,7 +516,7 @@ namespace ProtonMailBridge.UI.Tests.Windows public SettingsMenuWindow VerifyResetAndRestartBridge() { - ResetButton.Click(); + RetryHelper.EventuallyAction(() => ResetButton.Click()); ResetAndRestartButtonInPopUp.Click(); Thread.Sleep(5000); LaunchApp();