Selenium Tests

This commit is contained in:
natan
2024-04-22 10:15:56 +02:00
parent f9f6dfee6b
commit 0fefb316eb
17 changed files with 333 additions and 109 deletions
@@ -0,0 +1,6 @@
namespace Duplicati.Library.RestAPI.Abstractions;
public interface IModernHttpRequestAccess
{
public string GetQueryParam(string name);
}
@@ -25,6 +25,7 @@ using System.IO;
using System.Linq;
using Duplicati.Server.Serialization.Interface;
using Duplicati.Library.RestAPI;
using Duplicati.Library.RestAPI.Abstractions;
namespace Duplicati.Server.WebServer.RESTMethods
{
@@ -636,7 +637,7 @@ namespace Duplicati.Server.WebServer.RESTMethods
return;
}
var delete_remote_files = Library.Utility.Utility.ParseBool(info.Request.Param["delete-remote-files"].Value, false);
var delete_remote_files = info.Request is IModernHttpRequestAccess request && Library.Utility.Utility.ParseBool(request.GetQueryParam("delete-remote-files"), false);
if (delete_remote_files)
{
@@ -224,8 +224,19 @@ namespace Duplicati.Server.WebServer.RESTMethods
{
string url;
using(var sr = new System.IO.StreamReader(info.Request.Body, System.Text.Encoding.UTF8, true))
url = sr.ReadToEnd();
using (var sr = new System.IO.StreamReader(info.Request.Body, System.Text.Encoding.UTF8, true))
{
try
{
url = sr.ReadToEnd();
}
catch (InvalidOperationException)
{
url = sr.ReadLineAsync().Result;
}
}
Console.WriteLine(url);
switch (key)
{
@@ -1,50 +1,133 @@
using System;
using System.Threading;
using OpenQA.Selenium;
using OpenQA.Selenium.Edge;
namespace Duplicati.Browser.Test.Drivers
namespace Duplicati.Browser.Test.Drivers;
/// <summary>
/// Manages a browser instance using Selenium
/// </summary>
public class BrowserDriver : IDisposable
{
private readonly Lazy<IWebDriver> _currentWebDriverLazy = new(CreateWebDriver);
private bool _isDisposed;
/// <summary>
/// Manages a browser instance using Selenium
/// The Selenium IWebDriver instance
/// </summary>
public class BrowserDriver : IDisposable
public IWebDriver Current => _currentWebDriverLazy.Value;
/// <summary>
/// Creates the Selenium web driver (opens a browser)
/// </summary>
/// <returns></returns>
private static EdgeDriver CreateWebDriver()
{
private readonly Lazy<IWebDriver> _currentWebDriverLazy = new(CreateWebDriver);
private bool _isDisposed;
//We use the Chrome browser
var chromeDriverService = EdgeDriverService.CreateDefaultService(".", "msedgedriver");
/// <summary>
/// The Selenium IWebDriver instance
/// </summary>
public IWebDriver Current => _currentWebDriverLazy.Value;
return new EdgeDriver(chromeDriverService, new EdgeOptions());
}
/// <summary>
/// Creates the Selenium web driver (opens a browser)
/// </summary>
/// <returns></returns>
private static EdgeDriver CreateWebDriver()
/// <summary>
/// Disposes the Selenium web driver (closing the browser)
/// </summary>
public void Dispose()
{
if (_isDisposed)
{
//We use the Chrome browser
var chromeDriverService = EdgeDriverService.CreateDefaultService(".", "msedgedriver");
return new EdgeDriver(chromeDriverService, new EdgeOptions());
return;
}
/// <summary>
/// Disposes the Selenium web driver (closing the browser)
/// </summary>
public void Dispose()
if (_currentWebDriverLazy.IsValueCreated)
{
if (_isDisposed)
{
return;
}
if (_currentWebDriverLazy.IsValueCreated)
{
Current.Quit();
}
_isDisposed = true;
Current.Quit();
}
_isDisposed = true;
}
}
public static class DriverExtensions
{
public static int SingleLoopWait = 100;
public static TimeSpan DefaultMaxWait = TimeSpan.FromSeconds(30);
public static IWebElement WaitForElement(this ISearchContext driver, By selector, TimeSpan? maxWait = null)
{
maxWait ??= DefaultMaxWait;
var i = 0;
while (i < maxWait.Value.TotalMilliseconds)
{
try
{
return driver.FindElement(selector);
}
catch (NoSuchElementException)
{
//swallow and wait for another loop
}
i += SingleLoopWait;
Thread.Sleep(TimeSpan.FromMilliseconds(SingleLoopWait));
}
return driver.FindElement(selector);
}
public static bool WaitForElementBeing(this IWebElement element, Func<IWebElement, bool> predicate,
TimeSpan? maxWait = null)
{
maxWait ??= DefaultMaxWait;
var i = 0;
while (i < maxWait.Value.TotalMilliseconds)
{
try
{
if (predicate.Invoke(element))
{
return true;
}
}
catch (NoSuchElementException)
{
//swallow and wait for another loop
}
i += SingleLoopWait;
Thread.Sleep(TimeSpan.FromMilliseconds(SingleLoopWait));
}
throw new Exception($"Timout of waiting for element to satisfy predicate: {predicate}!");
}
public static void SendKeysAndCheck(this IWebElement element, string text, TimeSpan? maxWait = null)
{
maxWait ??= DefaultMaxWait;
var i = 0;
while (i < maxWait.Value.TotalMilliseconds)
{
try
{
if (element.GetAttribute("value") != text)
{
element.SendKeys(text);
}
else
{
return;
}
}
catch (NoSuchElementException)
{
//swallow and wait for another loop
}
i += SingleLoopWait;
Thread.Sleep(TimeSpan.FromMilliseconds(SingleLoopWait));
}
throw new Exception("Could not set text value of element!");
}
}
@@ -19,11 +19,12 @@
<ItemGroup>
<Folder Include="Drivers\" />
<Folder Include="test-backup-source\" />
</ItemGroup>
<ItemGroup>
<None Update="msedgedriver">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
@@ -4,9 +4,8 @@ Link to a feature: [Calculator](Duplicati.Browser.Test/Features/LocalBackup.feat
***Further read***: **[Learn more about how to generate Living Documentation](https://docs.specflow.org/projects/specflow-livingdoc/en/latest/LivingDocGenerator/Generating-Documentation.html)**
Scenario: Configure Backup
Given the first number is 50
And the second number is 70
When the two numbers are added
Given there is local backup defined
When you run the backup
Then the result should be 120
@@ -105,15 +105,12 @@ this.ScenarioInitialize(scenarioInfo);
{
this.ScenarioStart();
#line 7
testRunner.Given("the first number is 50", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given ");
testRunner.Given("there is local backup defined", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given ");
#line hidden
#line 8
testRunner.And("the second number is 70", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
testRunner.When("you run the backup", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When ");
#line hidden
#line 9
testRunner.When("the two numbers are added", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When ");
#line hidden
#line 10
testRunner.Then("the result should be 120", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then ");
#line hidden
}
@@ -133,7 +130,7 @@ this.ScenarioInitialize(scenarioInfo);
argumentsOfScenario.Add("Second number", secondNumber);
argumentsOfScenario.Add("Expected result", expectedResult);
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Add two numbers permutations", null, tagsOfScenario, argumentsOfScenario, this._featureTags);
#line 13
#line 12
this.ScenarioInitialize(scenarioInfo);
#line hidden
bool isScenarioIgnored = default(bool);
@@ -153,16 +150,16 @@ this.ScenarioInitialize(scenarioInfo);
else
{
this.ScenarioStart();
#line 14
#line 13
testRunner.Given(string.Format("the first number is {0}", firstNumber), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given ");
#line hidden
#line 15
#line 14
testRunner.And(string.Format("the second number is {0}", secondNumber), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
#line hidden
#line 16
#line 15
testRunner.When("the two numbers are added", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When ");
#line hidden
#line 17
#line 16
testRunner.Then(string.Format("the result should be {0}", expectedResult), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then ");
#line hidden
}
@@ -0,0 +1,147 @@
using System.Threading;
using Duplicati.Browser.Test.Drivers;
using OpenQA.Selenium;
namespace Duplicati.Browser.Test.PageObjects;
public class AddBackupPage(IWebDriver webDriver)
{
private IWebElement NextButtonElement => webDriver.WaitForElement(By.ClassName("submit"));
public GeneralBackupSettingsPage ToGeneralSettings()
{
NextButtonElement.Click();
return new GeneralBackupSettingsPage(webDriver);
}
}
public class GeneralBackupSettingsPage(IWebDriver webDriver)
{
private IWebElement NameTextboxElement => webDriver.WaitForElement(By.Id("name"));
private IWebElement NextButtonElement => webDriver.WaitForElement(By.ClassName("submit"));
private IWebElement GeneratePasswordLink => webDriver.WaitForElement(By.PartialLinkText("Generuj"));
public GeneralBackupSettingsPage SetName(string name)
{
if (NameTextboxElement.WaitForElementBeing(e => e.GetAttribute("placeholder") is "Moje Zdjęcia" or "My Photos"))
{
NameTextboxElement.Clear();
NameTextboxElement.SendKeysAndCheck(name);
Thread.Sleep(1000);
NameTextboxElement.SendKeysAndCheck(name);
}
return this;
}
public GeneralBackupSettingsPage GenerateRandomPassword()
{
GeneratePasswordLink.Click();
return this;
}
public BackupTargetSettingsPage ToBackupTarget()
{
NextButtonElement.Click();
return new BackupTargetSettingsPage(webDriver);
}
}
public class BackupTargetSettingsPage(IWebDriver webDriver)
{
private IWebElement NextButtonElement => webDriver.WaitForElement(By.CssSelector("#nextStep2.submit"));
private IWebElement ManualPathLink => webDriver.WaitForElement(By.LinkText("Podaj ścieżkę ręcznie"));
private IWebElement FilePathTextbox => webDriver.WaitForElement(By.Id("file_path"));
public BackupTargetSettingsPage ChoosePathManually()
{
ManualPathLink.Click();
return this;
}
public BackupTargetSettingsPage SetManualPath(string path)
{
if (FilePathTextbox.WaitForElementBeing(e => e.GetAttribute("placeholder") is "Wprowadź ścieżkę docelową"))
{
FilePathTextbox.Clear();
FilePathTextbox.SendKeysAndCheck(path);
}
return this;
}
public BackupSourceSettingsPage ToBackupSource()
{
NextButtonElement.Click();
return new BackupSourceSettingsPage(webDriver);
}
}
public class BackupSourceSettingsPage(IWebDriver webDriver)
{
private IWebElement NextButtonElement => webDriver.WaitForElement(By.CssSelector("#nextStep3.submit"));
private IWebElement FilePathTextbox => webDriver.WaitForElement(By.Id("sourcePath"));
private IWebElement AddFilePathButton => webDriver.WaitForElement(By.Id("sourceFolderPathAdd"));
private IWebElement ConfirmAddPathButton => webDriver.WaitForElement(By.PartialLinkText("Tak"));
public BackupSourceSettingsPage AddSourceManually(string path)
{
if (FilePathTextbox.WaitForElementBeing(e => e.GetAttribute("placeholder") is "Dodaj ścieżkę bezpośrednio"))
{
FilePathTextbox.Clear();
FilePathTextbox.SendKeysAndCheck(path);
}
AddFilePathButton.Click();
ConfirmAddPathButton.Click();
return this;
}
public ScheduleSettingsPage ToSchedule()
{
Thread.Sleep(20000);
NextButtonElement.Click();
return new ScheduleSettingsPage(webDriver);
}
}
public class ScheduleSettingsPage(IWebDriver webDriver)
{
private IWebElement NextButtonElement => webDriver.WaitForElement(By.CssSelector("#nextStep4.submit"));
private IWebElement AutoRunCheckbox => webDriver.WaitForElement(By.Id("useScheduleRun"));
public ScheduleSettingsPage DisableAutorun()
{
if (AutoRunCheckbox.Selected)
{
AutoRunCheckbox.Click();
}
return this;
}
public ToOptionsSettingsPage ToOptions()
{
NextButtonElement.Click();
return new ToOptionsSettingsPage(webDriver);
}
}
public class ToOptionsSettingsPage(IWebDriver webDriver)
{
private IWebElement SaveButtonElement => webDriver.WaitForElement(By.CssSelector("#save"));
public ToOptionsSettingsPage Save()
{
SaveButtonElement.Click();
return this;
}
}
@@ -1,4 +1,5 @@
using System;
using Duplicati.Browser.Test.Drivers;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
@@ -18,23 +19,15 @@ namespace Duplicati.Browser.Test.PageObjects
public const int DefaultWaitInSeconds = 5;
//Finding elements by ID
private IWebElement AddBackupElement => webDriver.FindElement(By.ClassName("add"));
private IWebElement SecondNumberElement => webDriver.FindElement(By.Id("second-number"));
private IWebElement AddButtonElement => webDriver.FindElement(By.Id("add-button"));
private IWebElement ResultElement => webDriver.FindElement(By.Id("result"));
private IWebElement ResetButtonElement => webDriver.FindElement(By.Id("reset-button"));
private IWebElement AddBackupElement => webDriver.WaitForElement(By.ClassName("add"));
private IWebElement AddButtonElement => webDriver.WaitForElement(By.Id("add-button"));
private IWebElement ResultElement => webDriver.WaitForElement(By.Id("result"));
private IWebElement ResetButtonElement => webDriver.WaitForElement(By.Id("reset-button"));
public void NavigateToBackupCreation()
public AddBackupPage NavigateToBackupCreation()
{
AddBackupElement.Click();
}
public void EnterSecondNumber(string number)
{
//Clear text box
SecondNumberElement.Clear();
//Enter text
SecondNumberElement.SendKeys(number);
return new AddBackupPage(webDriver);
}
public void ClickAdd()
@@ -1,6 +1,8 @@
using Duplicati.Browser.Test.Drivers;
using System.IO;
using Duplicati.Browser.Test.Drivers;
using Duplicati.Browser.Test.PageObjects;
using FluentAssertions;
using OpenQA.Selenium.Support.Extensions;
using TechTalk.SpecFlow;
namespace Duplicati.Browser.Test.Steps
@@ -11,22 +13,29 @@ namespace Duplicati.Browser.Test.Steps
//Page Object for Calculator
private readonly DuplicatiPageObject _duplicatiPageObject = new(browserDriver.Current);
[Given("the first number is (.*)")]
public void GivenTheFirstNumberIs(int number)
[Given("there is local backup defined")]
public void GivenLocalBackupDefined()
{
browserDriver.Current.TakeScreenshot();
//delegate to Page Object
_duplicatiPageObject.NavigateToBackupCreation();
_duplicatiPageObject.NavigateToBackupCreation()
.ToGeneralSettings()
.SetName("Local Backup")
.GenerateRandomPassword()
.ToBackupTarget()
.ChoosePathManually()
.SetManualPath(Path.Combine(Directory.GetCurrentDirectory(), "test-backup-target"))
.ToBackupSource()
.AddSourceManually(Path.Combine(Directory.GetCurrentDirectory(), "test-backup-source")+"/")
.ToSchedule()
.DisableAutorun()
.ToOptions()
.Save()
;
}
[Given("the second number is (.*)")]
public void GivenTheSecondNumberIs(int number)
{
//delegate to Page Object
_duplicatiPageObject.EnterSecondNumber(number.ToString());
}
[When("the two numbers are added")]
public void WhenTheTwoNumbersAreAdded()
[When("you run the backup")]
public void WhenBackupIsRun()
{
//delegate to Page Object
_duplicatiPageObject.ClickAdd();
@@ -1,6 +0,0 @@
namespace Duplicati.Library.AutoUpdater;
public interface IUpdateManagerAccessor
{
bool HasUpdateInstalled { get; }
}
@@ -1,6 +0,0 @@
namespace Duplicati.Library.AutoUpdater;
public class UpdateManagerAccessor : IUpdateManagerAccessor
{
public bool HasUpdateInstalled => UpdaterManager.HasUpdateInstalled;
}
-12
View File
@@ -203,18 +203,6 @@ namespace Duplicati.Server
[STAThread]
public static int Main(string[] _args)
{
// var methodInfo = typeof(TemporaryIoCAccessor).Assembly.EntryPoint;
// var program = Activator.CreateInstance(methodInfo!.DeclaringType!);
// methodInfo.Invoke(program, [Array.Empty<string>()]);
return Duplicati.Library.AutoUpdater.UpdaterManager.RunFromMostRecent(typeof(Program).GetMethod("RealMain"), args, Duplicati.Library.AutoUpdater.AutoUpdateStrategy.Never);
}
public static int RealMain(string[] _args)
{
#if DEBUG
System.Diagnostics.Debugger.Launch();
#endif
//If we are on Windows, append the bundled "win-tools" programs to the search path
//We add it last, to allow the user to override with other versions
if (Platform.IsClientWindows)
+1 -1
View File
@@ -19,6 +19,6 @@ public class ServerStatusDto : IServerStatus
public long LastNotificationUpdateID { get; init; }
public string? UpdatedVersion { get; init; }
public UpdatePollerStates UpdaterState { get; init; }
public bool UpdateReady { get; init; }
public string UpdateDownloadLink { get; set; }
public double UpdateDownloadProgress { get; init; }
}
@@ -26,7 +26,6 @@ public static class ServiceCollectionsExtensions
services.AddSingleton<EventPollNotify>();
//transitional part - services that act as a proxy to old part for various reasons (not accessible in assembly i.e.)
services.AddSingleton<IUpdateManagerAccessor, UpdateManagerAccessor>();
services.AddSingleton<ILiveControls>(c => c.GetRequiredService<LiveControls>());
services.AddSingleton<IBoolParser, BoolParser>();
@@ -1,6 +1,7 @@
using System.Collections.Specialized;
using System.Globalization;
using System.Net;
using Duplicati.Library.RestAPI.Abstractions;
using HttpServer;
using HttpServer.FormDecoders;
using Microsoft.AspNetCore.Http.Extensions;
@@ -8,7 +9,7 @@ using HttpRequest = Microsoft.AspNetCore.Http.HttpRequest;
namespace Duplicati.WebserverCore;
class LegacyHttpRequestShim : HttpServer.IHttpRequest
class LegacyHttpRequestShim : HttpServer.IHttpRequest, IModernHttpRequestAccess
{
HttpRequest request;
public LegacyHttpRequestShim(HttpRequest request) { this.request = request; }
@@ -147,4 +148,9 @@ class LegacyHttpRequestShim : HttpServer.IHttpRequest
{
throw new NotImplementedException();
}
public string? GetQueryParam(string name)
{
return request.Query[name].FirstOrDefault();
}
}
@@ -1,4 +1,3 @@
using Duplicati.Library.AutoUpdater;
using Duplicati.Library.RestAPI;
using Duplicati.Library.RestAPI.Abstractions;
using Duplicati.Server;
@@ -14,7 +13,6 @@ public class StatusService : IStatusService
private readonly LiveControls m_liveControls;
private readonly UpdatePollThread m_updatePollThread;
private readonly IUpdateService m_updateService;
private readonly IUpdateManagerAccessor m_updateManager;
private readonly IWorkerThreadsManager m_workerThreadsManager;
private readonly ISettingsService m_settingsService;
private readonly IScheduler m_scheduler;
@@ -24,7 +22,6 @@ public class StatusService : IStatusService
public StatusService(LiveControls liveControls,
UpdatePollThread updatePollThread,
IUpdateService updateService,
IUpdateManagerAccessor updateManager,
IWorkerThreadsManager workerThreadsManager,
ISettingsService settingsService,
IScheduler scheduler,
@@ -35,7 +32,6 @@ public class StatusService : IStatusService
m_liveControls = liveControls;
m_updatePollThread = updatePollThread;
m_updateService = updateService;
m_updateManager = updateManager;
m_workerThreadsManager = workerThreadsManager;
m_settingsService = settingsService;
m_scheduler = scheduler;
@@ -52,7 +48,6 @@ public class StatusService : IStatusService
UpdatedVersion = GetUpdatedVersion(),
UpdaterState = m_updatePollThread.ThreadState,
UpdateDownloadProgress = m_updatePollThread.DownloadProgess,
UpdateReady = m_updateManager.HasUpdateInstalled,
ActiveTask = m_workerThreadsManager.CurrentTask,
SchedulerQueueIds = m_scheduler.GetSchedulerQueueIds(),
LastEventID = m_eventPollNotify.EventNo,
@@ -98,6 +93,7 @@ public class StatusService : IStatusService
{
status.HasError = m_settingsService.GetSettings().UnackedError;
status.HasWarning = m_settingsService.GetSettings().UnackedWarning;
status.UpdateDownloadLink = m_settingsService.GetSettings().UpdateCheckNewVersion;
}
private string? GetUpdatedVersion()