From 5b874657cba381951794918481f09f17c7fb0b38 Mon Sep 17 00:00:00 2001 From: Gjorgji Slamkov Date: Tue, 17 Sep 2024 05:23:08 +0000 Subject: [PATCH] test(BRIDGE-133): Bridge E2E UI tests for Windows --- .gitignore | 3 + .../ProtonMailBridge.UI.Tests.csproj | 35 +++++++++++ .../ui_tests/windows_os/Results/HomeResult.cs | 29 ++++++++++ tests/e2e/ui_tests/windows_os/TestSession.cs | 43 ++++++++++++++ .../windows_os/Tests/LoginLogoutTests.cs | 51 ++++++++++++++++ .../windows_os/TestsHelper/TestData.cs | 16 +++++ .../windows_os/TestsHelper/TestUserData.cs | 58 +++++++++++++++++++ tests/e2e/ui_tests/windows_os/UIActions.cs | 14 +++++ .../ui_tests/windows_os/Windows/HomeWindow.cs | 34 +++++++++++ .../windows_os/Windows/LoginWindow.cs | 49 ++++++++++++++++ tests/e2e/ui_tests/windows_os/app.config | 35 +++++++++++ tests/e2e/ui_tests/windows_os/ui_tests.sln | 31 ++++++++++ 12 files changed, 398 insertions(+) create mode 100644 tests/e2e/ui_tests/windows_os/ProtonMailBridge.UI.Tests.csproj create mode 100644 tests/e2e/ui_tests/windows_os/Results/HomeResult.cs create mode 100644 tests/e2e/ui_tests/windows_os/TestSession.cs create mode 100644 tests/e2e/ui_tests/windows_os/Tests/LoginLogoutTests.cs create mode 100644 tests/e2e/ui_tests/windows_os/TestsHelper/TestData.cs create mode 100644 tests/e2e/ui_tests/windows_os/TestsHelper/TestUserData.cs create mode 100644 tests/e2e/ui_tests/windows_os/UIActions.cs create mode 100644 tests/e2e/ui_tests/windows_os/Windows/HomeWindow.cs create mode 100644 tests/e2e/ui_tests/windows_os/Windows/LoginWindow.cs create mode 100644 tests/e2e/ui_tests/windows_os/app.config create mode 100644 tests/e2e/ui_tests/windows_os/ui_tests.sln diff --git a/.gitignore b/.gitignore index 3ba5f170..dc8f0c02 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ *~ .idea .vscode +.vs # Test files godog.test @@ -35,6 +36,8 @@ cmd/Import-Export/deploy proton-bridge cmd/Desktop-Bridge/*.exe cmd/launcher/*.exe +bin/ +obj/ # Jetbrains (CLion, Golang) cmake build dirs cmake-build-*/ diff --git a/tests/e2e/ui_tests/windows_os/ProtonMailBridge.UI.Tests.csproj b/tests/e2e/ui_tests/windows_os/ProtonMailBridge.UI.Tests.csproj new file mode 100644 index 00000000..30fee0b2 --- /dev/null +++ b/tests/e2e/ui_tests/windows_os/ProtonMailBridge.UI.Tests.csproj @@ -0,0 +1,35 @@ + + + + net8.0-windows7.0 + enable + enable + + false + true + x64 + AnyCPU;x64 + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + 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 new file mode 100644 index 00000000..e3d4bd47 --- /dev/null +++ b/tests/e2e/ui_tests/windows_os/Results/HomeResult.cs @@ -0,0 +1,29 @@ +using FlaUI.Core.AutomationElements; +using FlaUI.Core.Definitions; + +namespace ProtonMailBridge.UI.Tests.Results +{ + public class HomeResult : UIActions + { + private Button SignOutButton => AccountView.FindFirstDescendant(cf => cf.ByControlType(ControlType.Button).And(cf.ByName("Sign out"))).AsButton(); + private AutomationElement NotificationWindow => Window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Window)); + private TextBox FreeAccountErrorText => NotificationWindow.FindFirstDescendant(cf => cf.ByControlType(ControlType.Text)).AsTextBox(); + private TextBox SignedOutAccount => AccountView.FindFirstDescendant(cf => cf.ByControlType(ControlType.Text)).AsTextBox(); + public HomeResult CheckIfLoggedIn() + { + Assert.That(SignOutButton.IsAvailable, Is.True); + return this; + } + + public HomeResult CheckIfFreeAccountErrorIsDisplayed(string ErrorText) + { + Assert.That(FreeAccountErrorText.Name == ErrorText, Is.True); + return this; + } + public HomeResult CheckIfAccountIsSignedOut() + { + Assert.That(SignedOutAccount.IsAvailable, Is.True); + return this; + } + } +} \ No newline at end of file diff --git a/tests/e2e/ui_tests/windows_os/TestSession.cs b/tests/e2e/ui_tests/windows_os/TestSession.cs new file mode 100644 index 00000000..a79ed274 --- /dev/null +++ b/tests/e2e/ui_tests/windows_os/TestSession.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading; +using FlaUI.Core.AutomationElements; +using FlaUI.Core; +using FlaUI.UIA3; +using ProtonMailBridge.UI.Tests.TestsHelper; +using FlaUI.Core.Input; + +namespace ProtonMailBridge.UI.Tests +{ + public class TestSession + { + + public static Application App; + protected static Application Service; + protected static Window Window; + + protected static void ClientCleanup() + { + App.Kill(); + App.Dispose(); + // Give some time to properly exit the app + Thread.Sleep(2000); + } + + public static void LaunchApp() + { + string appExecutable = TestData.AppExecutable; + Application.Launch(appExecutable); + Wait.UntilInputIsProcessed(TestData.FiveSecondsTimeout); + App = Application.Attach("bridge-gui.exe"); + + try + { + Window = App.GetMainWindow(new UIA3Automation(), TestData.ThirtySecondsTimeout); + } + catch (System.TimeoutException) + { + Assert.Fail("Failed to get window of application!"); + } + } + } +} \ No newline at end of file diff --git a/tests/e2e/ui_tests/windows_os/Tests/LoginLogoutTests.cs b/tests/e2e/ui_tests/windows_os/Tests/LoginLogoutTests.cs new file mode 100644 index 00000000..23ffe8cb --- /dev/null +++ b/tests/e2e/ui_tests/windows_os/Tests/LoginLogoutTests.cs @@ -0,0 +1,51 @@ +using NUnit.Framework; +using ProtonMailBridge.UI.Tests.TestsHelper; +using ProtonMailBridge.UI.Tests.Windows; +using ProtonMailBridge.UI.Tests.Results; + +namespace ProtonMailBridge.UI.Tests.Tests +{ + [TestFixture] + 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."; + + [Test] + public void LoginAsPaidUser() + { + _loginWindow.SignIn(TestUserData.GetPaidUser()); + _homeResult.CheckIfLoggedIn(); + } + + [Test] + public void LoginAsFreeUser() + { + _loginWindow.SignIn(TestUserData.GetFreeUser()); + _homeResult.CheckIfFreeAccountErrorIsDisplayed(FreeAccountErrorText); + } + + [Test] + public void SuccessfullLogout() + { + _loginWindow.SignIn(TestUserData.GetPaidUser()); + _mainWindow.SignOutAccount(); + _homeResult.CheckIfAccountIsSignedOut(); + } + + [SetUp] + public void TestInitialize() + { + LaunchApp(); + } + + [TearDown] + public void TestCleanup() + { + _mainWindow.RemoveAccount(); + ClientCleanup(); + } + } +} diff --git a/tests/e2e/ui_tests/windows_os/TestsHelper/TestData.cs b/tests/e2e/ui_tests/windows_os/TestsHelper/TestData.cs new file mode 100644 index 00000000..b7a4fae0 --- /dev/null +++ b/tests/e2e/ui_tests/windows_os/TestsHelper/TestData.cs @@ -0,0 +1,16 @@ +using System; +using System.Linq; +using System.IO; + +namespace ProtonMailBridge.UI.Tests.TestsHelper +{ + public static class TestData + { + public static TimeSpan FiveSecondsTimeout => TimeSpan.FromSeconds(5); + public static TimeSpan TenSecondsTimeout => TimeSpan.FromSeconds(10); + public static TimeSpan ThirtySecondsTimeout => TimeSpan.FromSeconds(30); + public static TimeSpan OneMinuteTimeout => TimeSpan.FromSeconds(60); + public static TimeSpan RetryInterval => TimeSpan.FromMilliseconds(1000); + public static string AppExecutable => "C:\\Program Files\\Proton AG\\Proton Mail Bridge\\bridge-gui.exe"; + } +} diff --git a/tests/e2e/ui_tests/windows_os/TestsHelper/TestUserData.cs b/tests/e2e/ui_tests/windows_os/TestsHelper/TestUserData.cs new file mode 100644 index 00000000..b34fa5d4 --- /dev/null +++ b/tests/e2e/ui_tests/windows_os/TestsHelper/TestUserData.cs @@ -0,0 +1,58 @@ +using System; + +namespace ProtonMailBridge.UI.Tests.TestsHelper +{ + public class TestUserData + { + public string Username { get; set; } + public string Password { get; set; } + + public TestUserData(string username, string password) + { + Username = username; + Password = password; + } + + public static TestUserData GetFreeUser() + { + (string username, string password) = GetusernameAndPassword("BRIDGE_FLAUI_FREE_USER"); + return new TestUserData(username, password); + } + + public static TestUserData GetPaidUser() + { + (string username, string password) = GetusernameAndPassword("BRIDGE_FLAUI_PAID_USER"); + return new TestUserData(username, password); + } + + public static TestUserData GetIncorrectCredentialsUser() + { + return new TestUserData("IncorrectUsername", "IncorrectPass"); + } + + private static (string, string) GetusernameAndPassword(string userType) + { + // Get the environment variable for the user and check if missing + // When changing or adding an environment variable, you must restart Visual Studio + // if you have it open while doing this + string? str = Environment.GetEnvironmentVariable(userType); + if (string.IsNullOrEmpty(str)) + { + throw new Exception($"Missing environment variable: {userType}"); + } + + // Check if the environment variable contains only one ':' + // The ':' character must be between the username/email and password + string ch = ":"; + if ((str.IndexOf(ch) != str.LastIndexOf(ch)) | (str.IndexOf(ch) == -1)) + { + throw new Exception( + $"Environment variable {str} must contain one ':' and it must be between username and password!" + ); + } + + string[] split = str.Split(':'); + return (split[0], split[1]); + } + } +} diff --git a/tests/e2e/ui_tests/windows_os/UIActions.cs b/tests/e2e/ui_tests/windows_os/UIActions.cs new file mode 100644 index 00000000..5adbbdda --- /dev/null +++ b/tests/e2e/ui_tests/windows_os/UIActions.cs @@ -0,0 +1,14 @@ +using System; +using FlaUI.Core.AutomationElements; +using FlaUI.Core.Definitions; +using FlaUI.Core.Input; +using FlaUI.Core.Tools; +using NUnit.Framework; + +namespace ProtonMailBridge.UI.Tests +{ + public class UIActions : TestSession + { + public AutomationElement AccountView => Window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Pane)); + } +} \ No newline at end of file diff --git a/tests/e2e/ui_tests/windows_os/Windows/HomeWindow.cs b/tests/e2e/ui_tests/windows_os/Windows/HomeWindow.cs new file mode 100644 index 00000000..bcc39f04 --- /dev/null +++ b/tests/e2e/ui_tests/windows_os/Windows/HomeWindow.cs @@ -0,0 +1,34 @@ +using FlaUI.Core.AutomationElements; +using FlaUI.Core.Definitions; +using System; + + +namespace ProtonMailBridge.UI.Tests.Windows +{ + public class HomeWindow : UIActions + { + private AutomationElement[] AccountViewButtons => AccountView.FindAllChildren(cf => cf.ByControlType(ControlType.Button)); + private Button RemoveAccountButton => AccountViewButtons[1].AsButton(); + private AutomationElement RemoveAccountConfirmModal => Window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Window)); + private Button ConfirmRemoveAccountButton => RemoveAccountConfirmModal.FindFirstDescendant(cf => cf.ByControlType(ControlType.Button).And(cf.ByName("Remove this account"))).AsButton(); + private Button SignOutButton => AccountView.FindFirstDescendant(cf => cf.ByControlType(ControlType.Button).And(cf.ByName("Sign out"))).AsButton(); + public HomeWindow RemoveAccount() + { + try + { + RemoveAccountButton.Click(); + ConfirmRemoveAccountButton.Click(); + } + catch (System.NullReferenceException) + { + ClientCleanup(); + } + return this; + } + public HomeWindow SignOutAccount() + { + SignOutButton.Click(); + return this; + } + } +} diff --git a/tests/e2e/ui_tests/windows_os/Windows/LoginWindow.cs b/tests/e2e/ui_tests/windows_os/Windows/LoginWindow.cs new file mode 100644 index 00000000..604f290a --- /dev/null +++ b/tests/e2e/ui_tests/windows_os/Windows/LoginWindow.cs @@ -0,0 +1,49 @@ +using FlaUI.Core.AutomationElements; +using FlaUI.Core.Input; +using FlaUI.Core.Definitions; +using ProtonMailBridge.UI.Tests.TestsHelper; + +namespace ProtonMailBridge.UI.Tests.Windows +{ + public class LoginWindow : UIActions + { + private AutomationElement[] InputFields => Window.FindAllDescendants(cf => cf.ByControlType(ControlType.Edit)); + private TextBox UsernameInput => InputFields[0].AsTextBox(); + private TextBox PasswordInput => InputFields[1].AsTextBox(); + private Button SignInButton => Window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Button).And(cf.ByName("Sign in"))).AsButton(); + private Button StartSetupButton => Window.FindFirstDescendant(cf => cf.ByName("Start setup")).AsButton(); + private Button SetUpLater => Window.FindFirstDescendant(cf => cf.ByName("Setup later")).AsButton(); + + public LoginWindow SignIn(TestUserData user) + { + ClickStartSetupButton(); + EnterCredentials(user); + Wait.UntilInputIsProcessed(TestData.TenSecondsTimeout); + SetUpLater?.Click(); + + return this; + } + + public LoginWindow SignIn(string username, string password) + { + TestUserData user = new TestUserData(username, password); + SignIn(user); + return this; + } + + public LoginWindow ClickStartSetupButton() + { + StartSetupButton?.Click(); + + return this; + } + + public LoginWindow EnterCredentials(TestUserData user) + { + UsernameInput.Text = user.Username; + PasswordInput.Text = user.Password; + SignInButton.Click(); + return this; + } + } +} \ No newline at end of file diff --git a/tests/e2e/ui_tests/windows_os/app.config b/tests/e2e/ui_tests/windows_os/app.config new file mode 100644 index 00000000..3c1fd93a --- /dev/null +++ b/tests/e2e/ui_tests/windows_os/app.config @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/e2e/ui_tests/windows_os/ui_tests.sln b/tests/e2e/ui_tests/windows_os/ui_tests.sln new file mode 100644 index 00000000..6a4690f7 --- /dev/null +++ b/tests/e2e/ui_tests/windows_os/ui_tests.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35208.52 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtonMailBridge.UI.Tests", "ProtonMailBridge.UI.Tests.csproj", "{027E5266-E353-4095-AF24-B3ED240EACAA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {027E5266-E353-4095-AF24-B3ED240EACAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {027E5266-E353-4095-AF24-B3ED240EACAA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {027E5266-E353-4095-AF24-B3ED240EACAA}.Debug|x64.ActiveCfg = Debug|x64 + {027E5266-E353-4095-AF24-B3ED240EACAA}.Debug|x64.Build.0 = Debug|x64 + {027E5266-E353-4095-AF24-B3ED240EACAA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {027E5266-E353-4095-AF24-B3ED240EACAA}.Release|Any CPU.Build.0 = Release|Any CPU + {027E5266-E353-4095-AF24-B3ED240EACAA}.Release|x64.ActiveCfg = Release|x64 + {027E5266-E353-4095-AF24-B3ED240EACAA}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {817DD45A-EA2C-4F16-A680-5810DADCE4E7} + EndGlobalSection +EndGlobal