/* * Copyright (c) 2026 Proton AG * * This file is part of ProtonVPN. * * ProtonVPN is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ProtonVPN is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ProtonVPN. If not, see . */ using System; using System.IO; using System.Threading; using System.Threading.Tasks; using System.Diagnostics; using System.ComponentModel; using FlaUI.UIA3; using FlaUI.Core; using FlaUI.Core.Tools; using FlaUI.Core.AutomationElements; using NUnit.Framework; using NUnit.Framework.Interfaces; using ProtonVPN.UI.Tests.Robots; using ProtonVPN.UI.Tests.TestsHelper; using ProtonVPN.UI.Tests.UiTools; using static ProtonVPN.UI.Tests.Robots.TrayRobot; using TimeoutException = System.TimeoutException; namespace ProtonVPN.UI.Tests.TestBase; // Shared methods related to test session. public class BaseTest { public static Application? App; public static Window? Window; protected static LoginRobot LoginRobot { get; } = new(); protected static HomeRobot HomeRobot { get; } = new(); protected static NavigationRobot NavigationRobot { get; } = new(); protected static ProfileRobot ProfileRobot { get; } = new(); protected static SidebarRobot SidebarRobot { get; } = new(); protected static FeaturesRobot FeaturesRobot { get; } = new(); protected static SettingRobot SettingRobot { get; } = new(); protected static DesktopRobot DesktopRobot { get; } = new(); protected static TrayRobot TrayRobot { get; } = new(); protected static TrayAppWindow TrayApp => new(); protected static SupportRobot SupportRobot { get; } = new(() => Window); protected static AdvancedSettingsRobot AdvancedSettingsRobot { get; } = new(); protected static UpsellCarrouselRobot UpsellCarrouselRobot { get; } = new(); protected static SplitTunnelingRobot SplitTunnelingRobot { get; } = new(); protected static IpSelectorRobot IpSelectorRobot { get; } = new(); protected static AppSelectorRobot AppSelectorRobot { get; } = new(); protected static ConfirmationRobot ConfirmationRobot { get; } = new(); protected static TeachingTipRobot TeachingTipRobot { get; } = new(); private const string CLIENT_NAME = "ProtonVPN.Client.exe"; private static readonly bool _isDevelopmentModeEnabled = false; // Shared SetUp, TearDown actions that will be performed accross all the tests. [SetUp] public async Task GlobalSetUpAsync() { string testName = TestContext.CurrentContext.Test.MethodName ?? throw new Exception("Test method name is null."); ArtifactsHelper.ClearEventViewerLogs(); ArtifactsHelper.DeleteArtifactFolder(testName); await ArtifactsHelper.StartVideoCaptureAsync(testName); } [TearDown] public void GlobalTeardown() { string testName = TestContext.CurrentContext.Test.MethodName ?? throw new Exception("Test method name is null."); ArtifactsHelper.Recorder?.Stop(); ArtifactsHelper.Recorder?.Dispose(); ArtifactsHelper.SaveEventViewerLogs(testName); if (TestContext.CurrentContext.Result.Outcome.Status != TestStatus.Failed) { ArtifactsHelper.DeleteArtifactFolder(testName); } } public static void RefreshWindow(TimeSpan? timeout = null) { Window = null; TimeSpan refreshTimeout = timeout ?? TestConstants.ThirtySecondsTimeout; RetryResult retry = Retry.WhileNull(() => { try { Window = App?.GetMainWindow(new UIA3Automation(), refreshTimeout); } catch (TimeoutException) { //Ignore } catch (Win32Exception) { // Ignore } return Window; }, refreshTimeout, TestConstants.RetryInterval); if (!retry.Success) { Assert.Fail($"Failed to refresh window in {refreshTimeout.TotalSeconds:0} seconds."); } } protected static void DeleteProtonData() { try { Directory.Delete(TestConstants.UserStoragePath, true); File.Delete(TestEnvironment.GetServiceLogsPath()); } catch { } } protected static void Cleanup() { try { SaveScreenshotAndLogsIfFailed(); } catch { //Do nothing, since artifact collection shouldn't block cleanup. } try { AutomationElement? KebabMenu = Element.ByAutomationId("TitleBarMenuButton").TryGetElement(); if (KebabMenu == null) { HomeRobot.CloseClientViaCloseButton(); } else { HomeRobot .ExpandKebabMenuButton() .ExitViaKebabMenuWithConfirmation(); } Thread.Sleep(TestConstants.OneSecondTimeout); } catch (TimeoutException) { //Ignore } catch { } try { App?.Close(); } catch { } finally { App?.Dispose(); Thread.Sleep(TestConstants.OneSecondTimeout); } } public void KillVpnService() { Thread.Sleep(TestConstants.OneSecondTimeout); foreach (Process process in Process.GetProcessesByName("ProtonVPNService")) { try { process.Kill(true); } catch { } } Thread.Sleep(TestConstants.OneSecondTimeout); } protected static void LaunchClient(ClientLaunchParams? parameters = null) { parameters ??= ClientLaunchParams.FreshStartWithNoOnboarding; if (parameters.IsFreshStart) { DeleteProtonData(); } string installedClientPath = Path.Combine( _isDevelopmentModeEnabled ? TestEnvironment.GetDevProtonClientFolder() : TestEnvironment.GetProtonClientFolder(), CLIENT_NAME); ProcessStartInfo startInfo = new(installedClientPath) { Arguments = parameters.BuildArguments() }; App = Application.Launch(startInfo); RetryResult result = WaitUntilAppIsRunning(); if (!result.Success) { //Sometimes app fails to launch on first try due to CI issues. App = Application.Launch(installedClientPath); } if (parameters.ShouldRefreshWindow) { RefreshWindow(TestConstants.OneMinuteTimeout); Window?.Focus(); } } protected static void RestartApp(bool shouldRefreshWindow = true) { App?.Close(); App?.Dispose(); ClientLaunchParams parameters = shouldRefreshWindow ? ClientLaunchParams.StartWithNoOnboarding : ClientLaunchParams.StartWithNoOnboardingNoRefresh; LaunchClient(parameters); if (!shouldRefreshWindow) { //give it time to start Thread.Sleep(TestConstants.TenSecondsTimeout); } } protected static void SaveScreenshotAndLogsIfFailed() { if (!TestEnvironment.AreTestsRunningLocally() && TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Failed) { string testName = TestContext.CurrentContext.Test.MethodName ?? throw new Exception("Test method name is null."); ArtifactsHelper.SaveScreenshotAndLogs(testName, TestEnvironment.GetServiceLogsPath()); } } private static RetryResult WaitUntilAppIsRunning() { RetryResult retry = Retry.WhileFalse( () => { Process[] pname = Process.GetProcessesByName("ProtonVPN.Client"); return pname.Length > 0; }, TimeSpan.FromSeconds(30), TestConstants.RetryInterval); return retry; } }