/*
* 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;
}
}