From c5cd7e74104552d6e71ec6ae6638c1b259d3ee22 Mon Sep 17 00:00:00 2001 From: Diego Martinez Gilabert Date: Thu, 17 Aug 2023 10:54:28 +0200 Subject: [PATCH 01/14] Feature: Selenoid implementation --- .../Configuration/Capabilities.cs | 31 ++++++++ .../Factory/BrowserFactory.cs | 66 +++++++++++++++++ .../Factory/ConfigurationTags.cs | 8 +++ .../Factory/IBrowserDriver.cs | 5 ++ .../Factory/SupportedBrowsers.cs | 10 +++ TestWare.Engines.Selenoid/SelenoidManager.cs | 70 +++++++++++++++++++ .../TestWare.Engines.Selenoid.csproj | 17 +++++ TestWare.Samples.Selenoid.Web/Program.cs | 2 + .../TestConfiguration.Web.json | 24 +++++++ .../TestWare.Samples.Selenoid.Web.csproj | 10 +++ TestWare.sln | 14 ++++ .../Configuration/Capabilities.cs | 34 ++++----- .../Factory/ConfigurationTags.cs | 8 +-- 13 files changed, 275 insertions(+), 24 deletions(-) create mode 100644 TestWare.Engines.Selenoid/Configuration/Capabilities.cs create mode 100644 TestWare.Engines.Selenoid/Factory/BrowserFactory.cs create mode 100644 TestWare.Engines.Selenoid/Factory/ConfigurationTags.cs create mode 100644 TestWare.Engines.Selenoid/Factory/IBrowserDriver.cs create mode 100644 TestWare.Engines.Selenoid/Factory/SupportedBrowsers.cs create mode 100644 TestWare.Engines.Selenoid/SelenoidManager.cs create mode 100644 TestWare.Engines.Selenoid/TestWare.Engines.Selenoid.csproj create mode 100644 TestWare.Samples.Selenoid.Web/Program.cs create mode 100644 TestWare.Samples.Selenoid.Web/TestConfiguration.Web.json create mode 100644 TestWare.Samples.Selenoid.Web/TestWare.Samples.Selenoid.Web.csproj diff --git a/TestWare.Engines.Selenoid/Configuration/Capabilities.cs b/TestWare.Engines.Selenoid/Configuration/Capabilities.cs new file mode 100644 index 00000000..21a1dcfe --- /dev/null +++ b/TestWare.Engines.Selenoid/Configuration/Capabilities.cs @@ -0,0 +1,31 @@ +using TestWare.Engines.Selenoid.Factory; + +namespace TestWare.Engines.Selenium.Configuration; + +internal class Capabilities +{ + public string Name { get; set; } + + public string Uri { get; set; } + + public string BrowserType { get; set; } + + public string BrowserVersion { get; set; } + + public string Resolution { get; set; } + + public int CommandTimeOutInMinutes { get; set; } + + public bool EnableLog { get; set; } + + public bool EnableVnc { get; set; } + + public bool EnableVideo { get; set; } + + public IEnumerable Arguments { get; set; } = Enumerable.Empty(); + + public SupportedBrowsers GetDriver() + { + return Enum.Parse(BrowserType, true); + } +} \ No newline at end of file diff --git a/TestWare.Engines.Selenoid/Factory/BrowserFactory.cs b/TestWare.Engines.Selenoid/Factory/BrowserFactory.cs new file mode 100644 index 00000000..3e779d2d --- /dev/null +++ b/TestWare.Engines.Selenoid/Factory/BrowserFactory.cs @@ -0,0 +1,66 @@ +using OpenQA.Selenium.Chrome; +using OpenQA.Selenium.Edge; +using OpenQA.Selenium.Firefox; +using OpenQA.Selenium.IE; +using OpenQA.Selenium.Remote; +using TestWare.Engines.Selenium.Configuration; + +namespace TestWare.Engines.Selenoid.Factory; + +internal static class BrowserFactory +{ + public static IBrowserDriver Create(Capabilities capabilities) + { + IBrowserDriver result = capabilities.GetDriver() switch + { + SupportedBrowsers.Chrome => CreateChromeDriver(capabilities), + SupportedBrowsers.Firefox => CreateFirefoxDriver(capabilities), + SupportedBrowsers.InternetExplorer => CreateInternetExplorerDriver(capabilities), + SupportedBrowsers.Edge => CreateEdgeDriver(capabilities), + SupportedBrowsers.Invalid => throw new NotImplementedException(), + _ => throw new NotSupportedException($"Browser type is invalid."), + }; + return result; + } + private static IBrowserDriver CreateChromeDriver(Capabilities capabilities) + { + ChromeOptions options = new(); + options.AddAdditionalOption("selenoid:options", GenerateSelenoidCapabilities(capabilities)); + + return (IBrowserDriver) new RemoteWebDriver(new Uri(capabilities.Uri), options); + } + + private static IBrowserDriver CreateFirefoxDriver(Capabilities capabilities) + { + FirefoxOptions options = new(); + options.AddAdditionalOption("selenoid:options", GenerateSelenoidCapabilities(capabilities)); + + return (IBrowserDriver)new RemoteWebDriver(new Uri(capabilities.Uri), options); + } + + private static IBrowserDriver CreateInternetExplorerDriver(Capabilities capabilities) + { + InternetExplorerOptions options = new(); + options.AddAdditionalOption("selenoid:options", GenerateSelenoidCapabilities(capabilities)); + + return (IBrowserDriver)new RemoteWebDriver(new Uri(capabilities.Uri), options); + } + + private static IBrowserDriver CreateEdgeDriver(Capabilities capabilities) + { + EdgeOptions options = new(); + options.AddAdditionalOption("selenoid:options", GenerateSelenoidCapabilities(capabilities)); + + return (IBrowserDriver)new RemoteWebDriver(new Uri(capabilities.Uri), options); + } + + private static Dictionary GenerateSelenoidCapabilities(Capabilities capabilities) + { + return new Dictionary + { + ["enableLog"] = capabilities.EnableLog, + ["enableVnc"] = capabilities.EnableVnc, + ["enableVideo"] = capabilities.EnableVideo + }; + } +} diff --git a/TestWare.Engines.Selenoid/Factory/ConfigurationTags.cs b/TestWare.Engines.Selenoid/Factory/ConfigurationTags.cs new file mode 100644 index 00000000..c0c30056 --- /dev/null +++ b/TestWare.Engines.Selenoid/Factory/ConfigurationTags.cs @@ -0,0 +1,8 @@ +namespace TestWare.Engines.Selenoid.Factory; + +public enum ConfigurationTags +{ + none = 0, + remotedriver = 1, + multiwebdriver = 2 +} \ No newline at end of file diff --git a/TestWare.Engines.Selenoid/Factory/IBrowserDriver.cs b/TestWare.Engines.Selenoid/Factory/IBrowserDriver.cs new file mode 100644 index 00000000..a5e333d9 --- /dev/null +++ b/TestWare.Engines.Selenoid/Factory/IBrowserDriver.cs @@ -0,0 +1,5 @@ +namespace TestWare.Engines.Selenoid.Factory; + +public interface IBrowserDriver : OpenQA.Selenium.IWebDriver +{ +} diff --git a/TestWare.Engines.Selenoid/Factory/SupportedBrowsers.cs b/TestWare.Engines.Selenoid/Factory/SupportedBrowsers.cs new file mode 100644 index 00000000..30affa3f --- /dev/null +++ b/TestWare.Engines.Selenoid/Factory/SupportedBrowsers.cs @@ -0,0 +1,10 @@ +namespace TestWare.Engines.Selenoid.Factory; + +public enum SupportedBrowsers +{ + Invalid = 0, + Chrome = 1, + Firefox = 2, + InternetExplorer = 3, + Edge = 4 +} \ No newline at end of file diff --git a/TestWare.Engines.Selenoid/SelenoidManager.cs b/TestWare.Engines.Selenoid/SelenoidManager.cs new file mode 100644 index 00000000..d6105374 --- /dev/null +++ b/TestWare.Engines.Selenoid/SelenoidManager.cs @@ -0,0 +1,70 @@ +using TestWare.Core; +using TestWare.Core.Configuration; +using TestWare.Core.Interfaces; +using TestWare.Engines.Selenium.Configuration; +using TestWare.Engines.Selenoid.Factory; + +namespace TestWare.Engines.Selenoid +{ + public class SelenoidManager : EngineManagerBase, IEngineManager + { + private const string _name = "Selenoid"; + + private static void RegisterSingle(IEnumerable tags, TestConfiguration testConfiguration) + { + var configName = Enum.GetName(ConfigurationTags.remotedriver).ToUpperInvariant(); + var capabilities = ConfigurationManager.GetCapabilities(testConfiguration, configName); + var singleCapability = capabilities.FirstOrDefault(x => tags.Contains(x.Name.ToUpperInvariant())); + if (!ContainerManager.ExistsType(singleCapability.GetType())) + { + var driver = BrowserFactory.Create(singleCapability); + ContainerManager.RegisterType(singleCapability.Name, driver); + } + } + + private static void RegisterMultiple(IEnumerable tags, TestConfiguration testConfiguration) + { + var configName = Enum.GetName(ConfigurationTags.multiwebdriver).ToUpperInvariant(); + var capabilities = ConfigurationManager.GetCapabilities(testConfiguration, configName); + var multipleCapabilities = capabilities.Where(x => tags.Contains(x.Name.ToUpperInvariant())); + + foreach (var capability in multipleCapabilities) + { + var driver = BrowserFactory.Create(capability); + ContainerManager.RegisterType(capability.Name, driver); + } + } + + public string CollectEvidence(string destinationPath, string evidenceName) + { + throw new NotImplementedException(); + } + + public void Destroy() + { + throw new NotImplementedException(); + } + + public string GetEngineName() + { + return _name; + } + + public void Initialize(IEnumerable tags, TestConfiguration testConfiguration) + { + var normalizedTags = tags.Select(x => x.ToUpperInvariant()).ToArray(); + var foundConfiguration = ConfigurationManager.GetValidConfiguration(tags); + + switch (foundConfiguration) + { + case ConfigurationTags.remotedriver: + RegisterSingle(normalizedTags, testConfiguration); + break; + + case ConfigurationTags.multiwebdriver: + RegisterMultiple(normalizedTags, testConfiguration); + break; + } + } + } +} \ No newline at end of file diff --git a/TestWare.Engines.Selenoid/TestWare.Engines.Selenoid.csproj b/TestWare.Engines.Selenoid/TestWare.Engines.Selenoid.csproj new file mode 100644 index 00000000..aea4f2bb --- /dev/null +++ b/TestWare.Engines.Selenoid/TestWare.Engines.Selenoid.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + diff --git a/TestWare.Samples.Selenoid.Web/Program.cs b/TestWare.Samples.Selenoid.Web/Program.cs new file mode 100644 index 00000000..3751555c --- /dev/null +++ b/TestWare.Samples.Selenoid.Web/Program.cs @@ -0,0 +1,2 @@ +// See https://aka.ms/new-console-template for more information +Console.WriteLine("Hello, World!"); diff --git a/TestWare.Samples.Selenoid.Web/TestConfiguration.Web.json b/TestWare.Samples.Selenoid.Web/TestConfiguration.Web.json new file mode 100644 index 00000000..d76c94ed --- /dev/null +++ b/TestWare.Samples.Selenoid.Web/TestConfiguration.Web.json @@ -0,0 +1,24 @@ +{ + "Configurations": [ + { + "Tag": "RemoteDriver", + "Capabilities": [ + { + "Name": "Chrome", + "Uri": "http://selenoid:4444/wd/hub", + "BrowserType": "Chrome", + "BrowserVersion": "111", + "Resolution": "1920x1080", + "EnableLog": false, + "EnableVnc": true, + "EnableVideo": false, + "CommandTimeOutInMinutes": 5, + "Arguments": [ + "--start-maximized" + ] + } + ] + } + ], + "TestResultPath": "C:\\workspace\\ERNI\\results\\" +} \ No newline at end of file diff --git a/TestWare.Samples.Selenoid.Web/TestWare.Samples.Selenoid.Web.csproj b/TestWare.Samples.Selenoid.Web/TestWare.Samples.Selenoid.Web.csproj new file mode 100644 index 00000000..74abf5c9 --- /dev/null +++ b/TestWare.Samples.Selenoid.Web/TestWare.Samples.Selenoid.Web.csproj @@ -0,0 +1,10 @@ + + + + Exe + net6.0 + enable + enable + + + diff --git a/TestWare.sln b/TestWare.sln index 3c7ff8cc..8385a342 100644 --- a/TestWare.sln +++ b/TestWare.sln @@ -38,6 +38,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestWare.ExtentReport", "sr EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Reporting", "Reporting", "{DCCEF363-0EBE-46EA-B02B-CD59010626F6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestWare.Engines.Selenoid", "TestWare.Engines.Selenoid\TestWare.Engines.Selenoid.csproj", "{0B7E3DFE-AB55-4C0C-81C6-6A6F517475EE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestWare.Samples.Selenoid.Web", "TestWare.Samples.Selenoid.Web\TestWare.Samples.Selenoid.Web.csproj", "{1E2E8373-84D1-4F9E-BE57-D340ECABBA5A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -84,6 +88,14 @@ Global {94025C6B-DF0D-4AC4-BBC5-A94A9FA3AB0A}.Debug|Any CPU.Build.0 = Debug|Any CPU {94025C6B-DF0D-4AC4-BBC5-A94A9FA3AB0A}.Release|Any CPU.ActiveCfg = Release|Any CPU {94025C6B-DF0D-4AC4-BBC5-A94A9FA3AB0A}.Release|Any CPU.Build.0 = Release|Any CPU + {0B7E3DFE-AB55-4C0C-81C6-6A6F517475EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B7E3DFE-AB55-4C0C-81C6-6A6F517475EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B7E3DFE-AB55-4C0C-81C6-6A6F517475EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0B7E3DFE-AB55-4C0C-81C6-6A6F517475EE}.Release|Any CPU.Build.0 = Release|Any CPU + {1E2E8373-84D1-4F9E-BE57-D340ECABBA5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E2E8373-84D1-4F9E-BE57-D340ECABBA5A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E2E8373-84D1-4F9E-BE57-D340ECABBA5A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E2E8373-84D1-4F9E-BE57-D340ECABBA5A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -102,6 +114,8 @@ Global {71120454-DD7A-484C-93F2-699B4C363297} = {58B1446D-98A3-46A2-A668-305F0170B39F} {94025C6B-DF0D-4AC4-BBC5-A94A9FA3AB0A} = {DCCEF363-0EBE-46EA-B02B-CD59010626F6} {DCCEF363-0EBE-46EA-B02B-CD59010626F6} = {4678C707-68DB-4E06-9184-A07FB398832C} + {0B7E3DFE-AB55-4C0C-81C6-6A6F517475EE} = {A2E4E1F7-CB61-48DC-AA38-A74F7DC74CA2} + {1E2E8373-84D1-4F9E-BE57-D340ECABBA5A} = {58B1446D-98A3-46A2-A668-305F0170B39F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4F88CB24-23C8-4E0E-8B83-29023C1642B8} diff --git a/src/Engines/TestWare.Engines.Selenium/Configuration/Capabilities.cs b/src/Engines/TestWare.Engines.Selenium/Configuration/Capabilities.cs index e5ac7275..6a216de6 100644 --- a/src/Engines/TestWare.Engines.Selenium/Configuration/Capabilities.cs +++ b/src/Engines/TestWare.Engines.Selenium/Configuration/Capabilities.cs @@ -1,20 +1,20 @@ -using TestWare.Engines.Selenium.Factory; - -namespace TestWare.Engines.Selenium.Configuration; +using TestWare.Engines.Selenium.Factory; + +namespace TestWare.Engines.Selenium.Configuration; + +internal class Capabilities +{ + public string Name { get; set; } + + public string Path { get; set; } + + public string Driver { get; set; } + public int CommandTimeOutInMinutes { get; set; } -internal class Capabilities -{ - public string Name { get; set; } - - public string Path { get; set; } - - public string Driver { get; set; } - public int CommandTimeOutInMinutes { get; set; } - public IEnumerable Arguments { get; set; } = Enumerable.Empty(); - - public SupportedBrowsers GetDriver() - { - return Enum.Parse(Driver, true); + + public SupportedBrowsers GetDriver() + { + return Enum.Parse(Driver, true); } -} +} diff --git a/src/Engines/TestWare.Engines.Selenium/Factory/ConfigurationTags.cs b/src/Engines/TestWare.Engines.Selenium/Factory/ConfigurationTags.cs index 680cc5df..26a67ffa 100644 --- a/src/Engines/TestWare.Engines.Selenium/Factory/ConfigurationTags.cs +++ b/src/Engines/TestWare.Engines.Selenium/Factory/ConfigurationTags.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace TestWare.Engines.Selenium.Factory; +namespace TestWare.Engines.Selenium.Factory; public enum ConfigurationTags { From 92fea69c0cf02cb82f9af626d707e5b9bf444a72 Mon Sep 17 00:00:00 2001 From: mg-diego Date: Thu, 17 Aug 2023 14:12:12 +0200 Subject: [PATCH 02/14] Fix paths --- TestWare.sln | 4 ++-- .../TestWare.Samples.Selenoid.Web}/Program.cs | 0 .../TestWare.Samples.Selenoid.Web}/TestConfiguration.Web.json | 0 .../TestWare.Samples.Selenoid.Web.csproj | 0 .../TestWare.Engines.Selenoid}/Configuration/Capabilities.cs | 0 .../TestWare.Engines.Selenoid}/Factory/BrowserFactory.cs | 0 .../TestWare.Engines.Selenoid}/Factory/ConfigurationTags.cs | 0 .../TestWare.Engines.Selenoid}/Factory/IBrowserDriver.cs | 0 .../TestWare.Engines.Selenoid}/Factory/SupportedBrowsers.cs | 0 .../Engines/TestWare.Engines.Selenoid}/SelenoidManager.cs | 0 .../TestWare.Engines.Selenoid.csproj | 0 11 files changed, 2 insertions(+), 2 deletions(-) rename {TestWare.Samples.Selenoid.Web => samples/TestWare.Samples.Selenoid.Web}/Program.cs (100%) rename {TestWare.Samples.Selenoid.Web => samples/TestWare.Samples.Selenoid.Web}/TestConfiguration.Web.json (100%) rename {TestWare.Samples.Selenoid.Web => samples/TestWare.Samples.Selenoid.Web}/TestWare.Samples.Selenoid.Web.csproj (100%) rename {TestWare.Engines.Selenoid => src/Engines/TestWare.Engines.Selenoid}/Configuration/Capabilities.cs (100%) rename {TestWare.Engines.Selenoid => src/Engines/TestWare.Engines.Selenoid}/Factory/BrowserFactory.cs (100%) rename {TestWare.Engines.Selenoid => src/Engines/TestWare.Engines.Selenoid}/Factory/ConfigurationTags.cs (100%) rename {TestWare.Engines.Selenoid => src/Engines/TestWare.Engines.Selenoid}/Factory/IBrowserDriver.cs (100%) rename {TestWare.Engines.Selenoid => src/Engines/TestWare.Engines.Selenoid}/Factory/SupportedBrowsers.cs (100%) rename {TestWare.Engines.Selenoid => src/Engines/TestWare.Engines.Selenoid}/SelenoidManager.cs (100%) rename {TestWare.Engines.Selenoid => src/Engines/TestWare.Engines.Selenoid}/TestWare.Engines.Selenoid.csproj (100%) diff --git a/TestWare.sln b/TestWare.sln index 8385a342..dec01a45 100644 --- a/TestWare.sln +++ b/TestWare.sln @@ -38,9 +38,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestWare.ExtentReport", "sr EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Reporting", "Reporting", "{DCCEF363-0EBE-46EA-B02B-CD59010626F6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestWare.Engines.Selenoid", "TestWare.Engines.Selenoid\TestWare.Engines.Selenoid.csproj", "{0B7E3DFE-AB55-4C0C-81C6-6A6F517475EE}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestWare.Engines.Selenoid", "src\Engines\TestWare.Engines.Selenoid\TestWare.Engines.Selenoid.csproj", "{0B7E3DFE-AB55-4C0C-81C6-6A6F517475EE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestWare.Samples.Selenoid.Web", "TestWare.Samples.Selenoid.Web\TestWare.Samples.Selenoid.Web.csproj", "{1E2E8373-84D1-4F9E-BE57-D340ECABBA5A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestWare.Samples.Selenoid.Web", "samples\TestWare.Samples.Selenoid.Web\TestWare.Samples.Selenoid.Web.csproj", "{1E2E8373-84D1-4F9E-BE57-D340ECABBA5A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/TestWare.Samples.Selenoid.Web/Program.cs b/samples/TestWare.Samples.Selenoid.Web/Program.cs similarity index 100% rename from TestWare.Samples.Selenoid.Web/Program.cs rename to samples/TestWare.Samples.Selenoid.Web/Program.cs diff --git a/TestWare.Samples.Selenoid.Web/TestConfiguration.Web.json b/samples/TestWare.Samples.Selenoid.Web/TestConfiguration.Web.json similarity index 100% rename from TestWare.Samples.Selenoid.Web/TestConfiguration.Web.json rename to samples/TestWare.Samples.Selenoid.Web/TestConfiguration.Web.json diff --git a/TestWare.Samples.Selenoid.Web/TestWare.Samples.Selenoid.Web.csproj b/samples/TestWare.Samples.Selenoid.Web/TestWare.Samples.Selenoid.Web.csproj similarity index 100% rename from TestWare.Samples.Selenoid.Web/TestWare.Samples.Selenoid.Web.csproj rename to samples/TestWare.Samples.Selenoid.Web/TestWare.Samples.Selenoid.Web.csproj diff --git a/TestWare.Engines.Selenoid/Configuration/Capabilities.cs b/src/Engines/TestWare.Engines.Selenoid/Configuration/Capabilities.cs similarity index 100% rename from TestWare.Engines.Selenoid/Configuration/Capabilities.cs rename to src/Engines/TestWare.Engines.Selenoid/Configuration/Capabilities.cs diff --git a/TestWare.Engines.Selenoid/Factory/BrowserFactory.cs b/src/Engines/TestWare.Engines.Selenoid/Factory/BrowserFactory.cs similarity index 100% rename from TestWare.Engines.Selenoid/Factory/BrowserFactory.cs rename to src/Engines/TestWare.Engines.Selenoid/Factory/BrowserFactory.cs diff --git a/TestWare.Engines.Selenoid/Factory/ConfigurationTags.cs b/src/Engines/TestWare.Engines.Selenoid/Factory/ConfigurationTags.cs similarity index 100% rename from TestWare.Engines.Selenoid/Factory/ConfigurationTags.cs rename to src/Engines/TestWare.Engines.Selenoid/Factory/ConfigurationTags.cs diff --git a/TestWare.Engines.Selenoid/Factory/IBrowserDriver.cs b/src/Engines/TestWare.Engines.Selenoid/Factory/IBrowserDriver.cs similarity index 100% rename from TestWare.Engines.Selenoid/Factory/IBrowserDriver.cs rename to src/Engines/TestWare.Engines.Selenoid/Factory/IBrowserDriver.cs diff --git a/TestWare.Engines.Selenoid/Factory/SupportedBrowsers.cs b/src/Engines/TestWare.Engines.Selenoid/Factory/SupportedBrowsers.cs similarity index 100% rename from TestWare.Engines.Selenoid/Factory/SupportedBrowsers.cs rename to src/Engines/TestWare.Engines.Selenoid/Factory/SupportedBrowsers.cs diff --git a/TestWare.Engines.Selenoid/SelenoidManager.cs b/src/Engines/TestWare.Engines.Selenoid/SelenoidManager.cs similarity index 100% rename from TestWare.Engines.Selenoid/SelenoidManager.cs rename to src/Engines/TestWare.Engines.Selenoid/SelenoidManager.cs diff --git a/TestWare.Engines.Selenoid/TestWare.Engines.Selenoid.csproj b/src/Engines/TestWare.Engines.Selenoid/TestWare.Engines.Selenoid.csproj similarity index 100% rename from TestWare.Engines.Selenoid/TestWare.Engines.Selenoid.csproj rename to src/Engines/TestWare.Engines.Selenoid/TestWare.Engines.Selenoid.csproj From fba60ea926257303400a8074b63a5e55785e726d Mon Sep 17 00:00:00 2001 From: mg-diego Date: Thu, 17 Aug 2023 20:46:25 +0200 Subject: [PATCH 03/14] Update versions --- TestWare.sln | 14 +- .../TestWare.Samples.API.csproj | 6 +- .../TestWare.Samples.Appium.Mobile.csproj | 6 +- .../TestWare.Samples.Selenium.Web.csproj | 6 +- .../Features/SwagLabs/Login.feature | 32 + samples/TestWare.Samples.Selenoid.Web/Hook.cs | 110 ++++ .../ImplicitUsings.cs | 3 + .../LifeCycle.cs | 36 ++ .../POM/SwagLabs/Inventory/IInventoryPage.cs | 9 + .../POM/SwagLabs/Inventory/InventoryPage.cs | 27 + .../POM/SwagLabs/Login/ILoginPage.cs | 31 + .../POM/SwagLabs/Login/LoginPage.cs | 54 ++ .../POM/SwagLabs/Menu/IMenuPage.cs | 13 + .../POM/SwagLabs/Menu/MenuPage.cs | 30 + .../TestWare.Samples.Selenoid.Web/Program.cs | 2 - .../SwagLabs/InventorySteps.cs | 25 + .../StepDefinitions/SwagLabs/LoginSteps.cs | 53 ++ .../StepDefinitions/SwagLabs/MenuSteps.cs | 26 + .../SwagLabs/MultiplePageSteps.cs | 67 ++ .../TestConfiguration.Web.json | 36 +- .../TestWare.Samples.Selenoid.Web.csproj | 41 +- ...stWare.Samples.WinAppDriver.Desktop.csproj | 6 +- .../Configuration/Capabilities.cs | 8 +- .../Extras/AbstractFindsByAttribute.cs | 170 +++++ .../TestWare.Engines.Selenoid/Extras/ByAll.cs | 109 ++++ .../Extras/ByChained.cs | 111 ++++ .../Extras/ByFactory.cs | 64 ++ .../Extras/ByIdOrName.cs | 72 +++ .../Extras/CacheLookupAttribute.cs | 10 + .../Extras/DefaultElementLocator.cs | 79 +++ .../DefaultPageObjectMemberDecorator.cs | 154 +++++ .../Extras/ExpectedConditions.cs | 602 ++++++++++++++++++ .../Extras/FindsByAllAttribute.cs | 30 + .../Extras/FindsByAttribute.cs | 94 +++ .../Extras/FindsBySequenceAttribute.cs | 30 + .../TestWare.Engines.Selenoid/Extras/How.cs | 53 ++ .../Extras/IElementLocator.cs | 35 + .../Extras/IPageObjectMemberDecorator.cs | 22 + .../Extras/MemberBuilders/IMemberBuilder.cs | 34 + .../MemberBuilders/WebElementBuilder.cs | 41 ++ .../MemberBuilders/WebElementListBuilder.cs | 23 + .../MemberBuilders/WrappedElementBuilder.cs | 21 + .../WrappedElementListBuilder.cs | 25 + .../Extras/PageFactory.cs | 202 ++++++ .../Extras/RetryingElementLocator.cs | 133 ++++ .../Extras/WebDriverObjectProxy.cs | 38 ++ .../Extras/WebElementEnumerable.cs | 28 + .../Extras/WebElementListProxy.cs | 65 ++ .../Extras/WebElementProxy.cs | 96 +++ .../Extras/WrapsElementFactory.cs | 35 + .../Extras/WrapsElementListProxy.cs | 71 +++ .../Factory/BrowserFactory.cs | 48 +- .../Factory/IBrowserDriver.cs | 5 - .../Factory/SupportedBrowsers.cs | 3 +- .../Pages/PageBase.cs | 125 ++++ .../Pages/WebPage.cs | 32 + .../SelenoidManager.cs | 65 +- .../TestWare.Engines.Selenoid.csproj | 4 +- 58 files changed, 3287 insertions(+), 83 deletions(-) create mode 100644 samples/TestWare.Samples.Selenoid.Web/Features/SwagLabs/Login.feature create mode 100644 samples/TestWare.Samples.Selenoid.Web/Hook.cs create mode 100644 samples/TestWare.Samples.Selenoid.Web/ImplicitUsings.cs create mode 100644 samples/TestWare.Samples.Selenoid.Web/LifeCycle.cs create mode 100644 samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Inventory/IInventoryPage.cs create mode 100644 samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Inventory/InventoryPage.cs create mode 100644 samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Login/ILoginPage.cs create mode 100644 samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Login/LoginPage.cs create mode 100644 samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Menu/IMenuPage.cs create mode 100644 samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Menu/MenuPage.cs delete mode 100644 samples/TestWare.Samples.Selenoid.Web/Program.cs create mode 100644 samples/TestWare.Samples.Selenoid.Web/StepDefinitions/SwagLabs/InventorySteps.cs create mode 100644 samples/TestWare.Samples.Selenoid.Web/StepDefinitions/SwagLabs/LoginSteps.cs create mode 100644 samples/TestWare.Samples.Selenoid.Web/StepDefinitions/SwagLabs/MenuSteps.cs create mode 100644 samples/TestWare.Samples.Selenoid.Web/StepDefinitions/SwagLabs/MultiplePageSteps.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/AbstractFindsByAttribute.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/ByAll.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/ByChained.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/ByFactory.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/ByIdOrName.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/CacheLookupAttribute.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/DefaultElementLocator.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/DefaultPageObjectMemberDecorator.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/ExpectedConditions.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/FindsByAllAttribute.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/FindsByAttribute.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/FindsBySequenceAttribute.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/How.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/IElementLocator.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/IPageObjectMemberDecorator.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/IMemberBuilder.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WebElementBuilder.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WebElementListBuilder.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WrappedElementBuilder.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WrappedElementListBuilder.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/PageFactory.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/RetryingElementLocator.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/WebDriverObjectProxy.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/WebElementEnumerable.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/WebElementListProxy.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/WebElementProxy.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/WrapsElementFactory.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/WrapsElementListProxy.cs delete mode 100644 src/Engines/TestWare.Engines.Selenoid/Factory/IBrowserDriver.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Pages/PageBase.cs create mode 100644 src/Engines/TestWare.Engines.Selenoid/Pages/WebPage.cs diff --git a/TestWare.sln b/TestWare.sln index dec01a45..88a2539a 100644 --- a/TestWare.sln +++ b/TestWare.sln @@ -38,9 +38,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestWare.ExtentReport", "sr EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Reporting", "Reporting", "{DCCEF363-0EBE-46EA-B02B-CD59010626F6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestWare.Engines.Selenoid", "src\Engines\TestWare.Engines.Selenoid\TestWare.Engines.Selenoid.csproj", "{0B7E3DFE-AB55-4C0C-81C6-6A6F517475EE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestWare.Engines.Selenoid", "src\Engines\TestWare.Engines.Selenoid\TestWare.Engines.Selenoid.csproj", "{0B7E3DFE-AB55-4C0C-81C6-6A6F517475EE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestWare.Samples.Selenoid.Web", "samples\TestWare.Samples.Selenoid.Web\TestWare.Samples.Selenoid.Web.csproj", "{1E2E8373-84D1-4F9E-BE57-D340ECABBA5A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestWare.Samples.Selenoid.Web", "samples\TestWare.Samples.Selenoid.Web\TestWare.Samples.Selenoid.Web.csproj", "{672C5D48-DD50-4AA3-8974-50B95746EA07}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -92,10 +92,10 @@ Global {0B7E3DFE-AB55-4C0C-81C6-6A6F517475EE}.Debug|Any CPU.Build.0 = Debug|Any CPU {0B7E3DFE-AB55-4C0C-81C6-6A6F517475EE}.Release|Any CPU.ActiveCfg = Release|Any CPU {0B7E3DFE-AB55-4C0C-81C6-6A6F517475EE}.Release|Any CPU.Build.0 = Release|Any CPU - {1E2E8373-84D1-4F9E-BE57-D340ECABBA5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1E2E8373-84D1-4F9E-BE57-D340ECABBA5A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1E2E8373-84D1-4F9E-BE57-D340ECABBA5A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1E2E8373-84D1-4F9E-BE57-D340ECABBA5A}.Release|Any CPU.Build.0 = Release|Any CPU + {672C5D48-DD50-4AA3-8974-50B95746EA07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {672C5D48-DD50-4AA3-8974-50B95746EA07}.Debug|Any CPU.Build.0 = Debug|Any CPU + {672C5D48-DD50-4AA3-8974-50B95746EA07}.Release|Any CPU.ActiveCfg = Release|Any CPU + {672C5D48-DD50-4AA3-8974-50B95746EA07}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -115,7 +115,7 @@ Global {94025C6B-DF0D-4AC4-BBC5-A94A9FA3AB0A} = {DCCEF363-0EBE-46EA-B02B-CD59010626F6} {DCCEF363-0EBE-46EA-B02B-CD59010626F6} = {4678C707-68DB-4E06-9184-A07FB398832C} {0B7E3DFE-AB55-4C0C-81C6-6A6F517475EE} = {A2E4E1F7-CB61-48DC-AA38-A74F7DC74CA2} - {1E2E8373-84D1-4F9E-BE57-D340ECABBA5A} = {58B1446D-98A3-46A2-A668-305F0170B39F} + {672C5D48-DD50-4AA3-8974-50B95746EA07} = {58B1446D-98A3-46A2-A668-305F0170B39F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4F88CB24-23C8-4E0E-8B83-29023C1642B8} diff --git a/samples/TestWare.Samples.API/TestWare.Samples.API.csproj b/samples/TestWare.Samples.API/TestWare.Samples.API.csproj index 73379e5a..3dd1aa9f 100644 --- a/samples/TestWare.Samples.API/TestWare.Samples.API.csproj +++ b/samples/TestWare.Samples.API/TestWare.Samples.API.csproj @@ -6,13 +6,13 @@ + - - + - + diff --git a/samples/TestWare.Samples.Appium.Mobile/TestWare.Samples.Appium.Mobile.csproj b/samples/TestWare.Samples.Appium.Mobile/TestWare.Samples.Appium.Mobile.csproj index 59c4be80..12be5a98 100644 --- a/samples/TestWare.Samples.Appium.Mobile/TestWare.Samples.Appium.Mobile.csproj +++ b/samples/TestWare.Samples.Appium.Mobile/TestWare.Samples.Appium.Mobile.csproj @@ -7,12 +7,12 @@ + - - + - + diff --git a/samples/TestWare.Samples.Selenium.Web/TestWare.Samples.Selenium.Web.csproj b/samples/TestWare.Samples.Selenium.Web/TestWare.Samples.Selenium.Web.csproj index 07ae10b2..28d3abf8 100644 --- a/samples/TestWare.Samples.Selenium.Web/TestWare.Samples.Selenium.Web.csproj +++ b/samples/TestWare.Samples.Selenium.Web/TestWare.Samples.Selenium.Web.csproj @@ -19,13 +19,13 @@ + - - - + + diff --git a/samples/TestWare.Samples.Selenoid.Web/Features/SwagLabs/Login.feature b/samples/TestWare.Samples.Selenoid.Web/Features/SwagLabs/Login.feature new file mode 100644 index 00000000..4ac466ba --- /dev/null +++ b/samples/TestWare.Samples.Selenoid.Web/Features/SwagLabs/Login.feature @@ -0,0 +1,32 @@ +@RemoteDriver +Feature: Login + +Scenario Outline: Login + Given the user enters username '' + And the user enters password '' + When the user clicks submit + Then the user can login + + @Chrome + Examples: + | Example Description | username | password | + | standard | standard_user | secret_sauce | + | problem | problem_user | secret_sauce | + + @Firefox + Examples: + | Example Description | username | password | + | standard | standard_user | secret_sauce | + | problem | problem_user | secret_sauce | + + @Edge + Examples: + | Example Description | username | password | + | standard | standard_user | secret_sauce | + | problem | problem_user | secret_sauce | + +@Chrome +Scenario: Logout + Given user 'standard_user' is logged with 'secret_sauce' into SwagLabs + When the user clicks Logout button + Then the user is at Login page diff --git a/samples/TestWare.Samples.Selenoid.Web/Hook.cs b/samples/TestWare.Samples.Selenoid.Web/Hook.cs new file mode 100644 index 00000000..742d855d --- /dev/null +++ b/samples/TestWare.Samples.Selenoid.Web/Hook.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using TestWare.Reporting.ExtentReport; + +namespace TestWare.Samples.Selenoid.Web; + +[Binding] +public sealed class Hook +{ + private readonly TestContext _testContext; + private int _stepCounter; + private static readonly LifeCycle _lifeCycle = new(); + private static ExtentReport _testReport; + + public Hook(TestContext testContext) + { + _testContext = testContext; + } + + [BeforeFeature] + public static void BeforeFeature(FeatureContext featureContext) + { + var name = featureContext.FeatureInfo.Title; + var tags = featureContext.FeatureInfo.Tags; + + _lifeCycle.BeginTestSuite(name); + _testReport.CreateFeature(name, tags); + } + + [AfterFeature] + public static void AfterFeature(FeatureContext featureContext) + { + _lifeCycle.EndTestSuite(); + } + + [BeforeScenario] + public void BeforeScenario(FeatureContext featureContext, ScenarioContext scenarioContext) + { + var name = scenarioContext.ScenarioInfo.Arguments.Count > 0 + ? $"{DateTime.UtcNow.ToString("yyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture)} - {scenarioContext.ScenarioInfo.Title}" + : scenarioContext.ScenarioInfo.Title; + + var description = scenarioContext.ScenarioInfo.Description ?? ""; + var scenarioTags = scenarioContext.ScenarioInfo.Tags; + _testReport.CreateTestCase(name, description, scenarioTags); + + _testContext.WriteLine("----------------------------------------- \r\n"); + _testContext.WriteLine($"Feature: {featureContext.FeatureInfo.Title}"); + _testContext.WriteLine($" Scenario: {scenarioContext.ScenarioInfo.Title} \r\n"); + + _stepCounter = 1; + var tags = GetTags(featureContext, scenarioContext); + _lifeCycle.BeginTestCase(name, tags); + } + + [AfterScenario] + public void AfterScenario() + { + _testReport.SetTestcaseOutcome(_testContext.CurrentTestOutcome); + _lifeCycle.EndTestCase(); + } + + [BeforeTestRun] + public static void BeforeTestRun() + { + _lifeCycle.BeginTestExecution(); + _testReport = new ExtentReport(_lifeCycle.GetCurrentResultsDirectory()); + } + + [AfterTestRun] + public static void AfterTestRun() + { + _lifeCycle.EndTestExecution(); + _testReport.CreateTestReportFile(); + } + + [BeforeStep] + public void BeforeStep(ScenarioContext scenarioContext) + { + var name = scenarioContext.CurrentScenarioBlock.ToString(); + var description = scenarioContext.StepContext.StepInfo.Text; + _testReport.CreateStep(name, description); + + var stepId = $"{_stepCounter:00} {description}"; + _stepCounter++; + _lifeCycle.BeginTestStep(stepId); + } + + [AfterStep] + public void AfterStep(ScenarioContext scenarioContext) + { + _lifeCycle.EndTestStep(); + var evidencesPath = _lifeCycle.GetStepEvidences(); + + foreach (var evidence in evidencesPath) + { + _testReport.AddScreenshotToStep(evidence); + _testContext.AddResultFile(evidence); + } + } + + private static List GetTags(FeatureContext featureContext, ScenarioContext scenarioContext) + { + var tags = featureContext.FeatureInfo.Tags.ToList(); + tags.AddRange(scenarioContext.ScenarioInfo.Tags.ToList()); + return tags; + } +} diff --git a/samples/TestWare.Samples.Selenoid.Web/ImplicitUsings.cs b/samples/TestWare.Samples.Selenoid.Web/ImplicitUsings.cs new file mode 100644 index 00000000..26bc47f3 --- /dev/null +++ b/samples/TestWare.Samples.Selenoid.Web/ImplicitUsings.cs @@ -0,0 +1,3 @@ +global using FluentAssertions; +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using TechTalk.SpecFlow; diff --git a/samples/TestWare.Samples.Selenoid.Web/LifeCycle.cs b/samples/TestWare.Samples.Selenoid.Web/LifeCycle.cs new file mode 100644 index 00000000..ed161129 --- /dev/null +++ b/samples/TestWare.Samples.Selenoid.Web/LifeCycle.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Reflection; +using TestWare.Core; +using TestWare.Core.Configuration; +using TestWare.Core.Interfaces; +using TestWare.Engines.Selenoid; + +namespace TestWare.Samples.Selenoid.Web; + +internal class LifeCycle : AutomationLifeCycleBase +{ + protected override IEnumerable GetTestWareComponentAssemblies() + { + IEnumerable assemblies = new[] + { + typeof(Hook).Assembly + }; + + return assemblies; + } + + protected override IEnumerable GetTestWareEngines() + { + IEnumerable engines = new[] + { + new SelenoidManager() + }; + + return engines; + } + + protected override TestConfiguration GetConfiguration() + { + return ConfigurationManager.ReadConfigurationFile("TestConfiguration.Web.json"); + } +} diff --git a/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Inventory/IInventoryPage.cs b/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Inventory/IInventoryPage.cs new file mode 100644 index 00000000..7cfcdb44 --- /dev/null +++ b/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Inventory/IInventoryPage.cs @@ -0,0 +1,9 @@ +using TestWare.Core.Interfaces; + +namespace TestWare.Samples.Selenoid.Web.POM.Inventory; + +public interface IInventoryPage : ITestWareComponent +{ + void CheckUserIsAtInventory(); +} + diff --git a/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Inventory/InventoryPage.cs b/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Inventory/InventoryPage.cs new file mode 100644 index 00000000..54260bd1 --- /dev/null +++ b/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Inventory/InventoryPage.cs @@ -0,0 +1,27 @@ +using OpenQA.Selenium; +using TestWare.Core.Libraries; +using TestWare.Engines.Selenoid.Factory; +using TestWare.Engines.Selenoid.Pages; + +namespace TestWare.Samples.Selenoid.Web.POM.Inventory; + +public class InventoryPage : WebPage, IInventoryPage +{ + private const string InventoryUrl = "https://www.saucedemo.com/inventory.html"; + + public InventoryPage(IWebDriver driver) : base(driver) + { + } + + /// + public void CheckUserIsAtInventory() + { + RetryPolicies.ExecuteActionWithRetries( + () => + { + this.Driver.Url.Should().Be(InventoryUrl); + }); + } + +} + diff --git a/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Login/ILoginPage.cs b/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Login/ILoginPage.cs new file mode 100644 index 00000000..49d6c09c --- /dev/null +++ b/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Login/ILoginPage.cs @@ -0,0 +1,31 @@ +using TestWare.Core.Interfaces; + +namespace TestWare.Samples.Selenoid.Web.POM.Login; + +/// +/// Encapsulate all Loging busines logic +/// +public interface ILoginPage : ITestWareComponent +{ + /// + /// Script to send User Name + /// + /// + void EnterUserName(string name); + + /// + /// Script to send User Password + /// + /// + void EnterUserPassword(string password); + + /// + /// Click in Login button + /// + void ClickLoginButton(); + + /// + /// Check that the user is at Login Page + /// + void CheckUserIsAtLoginpage(); +} diff --git a/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Login/LoginPage.cs b/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Login/LoginPage.cs new file mode 100644 index 00000000..7fbffaaa --- /dev/null +++ b/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Login/LoginPage.cs @@ -0,0 +1,54 @@ +using OpenQA.Selenium; +using TestWare.Core.Libraries; +using TestWare.Engines.Selenoid.Extras; +using TestWare.Engines.Selenoid.Factory; +using TestWare.Engines.Selenoid.Pages; + +namespace TestWare.Samples.Selenoid.Web.POM.Login; + +public class LoginPage : WebPage, ILoginPage +{ + private const string LoginUrl = "https://www.saucedemo.com/"; + + [FindsBy(How = How.Name, Using = "user-name")] + private IWebElement UserIdTextBox { get; set; } + + [FindsBy(How = How.Name, Using = "password")] + private IWebElement UserPasswordTextBox { get; set; } + + [FindsBy(How = How.Name, Using = "login-button")] + private IWebElement LoginButton { get; set; } + + public LoginPage(IWebDriver driver) : base(driver) + { + Url = LoginUrl; + NavigateToUrl(); + } + + /// + public void EnterUserName(string name) + => SendKeysElement(UserIdTextBox, name); + + /// + public void ConfirmLogoutPopup() + => this.Driver.SwitchTo().Alert().Accept(); + + /// + public void EnterUserPassword(string password) + => SendKeysElement(UserPasswordTextBox, password); + + /// + + public void ClickLoginButton() + => ClickElement(LoginButton); + + /// + public void CheckUserIsAtLoginpage() + { + RetryPolicies.ExecuteActionWithRetries( + () => + { + this.Driver.Url.Should().Be(LoginUrl); + }); + } +} diff --git a/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Menu/IMenuPage.cs b/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Menu/IMenuPage.cs new file mode 100644 index 00000000..6d145167 --- /dev/null +++ b/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Menu/IMenuPage.cs @@ -0,0 +1,13 @@ +using TestWare.Core.Interfaces; + +namespace TestWare.Samples.Selenoid.Web.POM.Menu; + +/// +/// Initializes a new instance of the interface class that contains +/// all the methods that can be used at Steps level. +/// +public interface IMenuPage : ITestWareComponent +{ + void ClickLogoutButton(); + void ClickOpenMenuButton(); +} diff --git a/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Menu/MenuPage.cs b/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Menu/MenuPage.cs new file mode 100644 index 00000000..8bb35b25 --- /dev/null +++ b/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Menu/MenuPage.cs @@ -0,0 +1,30 @@ +using OpenQA.Selenium; +using TestWare.Engines.Selenoid.Extras; +using TestWare.Engines.Selenoid.Factory; +using TestWare.Engines.Selenoid.Pages; + +namespace TestWare.Samples.Selenoid.Web.POM.Menu; + +/// +/// Initializes a new instance of the class that contains +/// all the elements and the interaction methods that will be exposed at it's Interface class. +/// +public class MenuPage : WebPage, IMenuPage +{ + [FindsBy(How = How.Id, Using = "react-burger-menu-btn")] + private IWebElement OpenMenuButton { get; set; } + + [FindsBy(How = How.Id, Using = "logout_sidebar_link")] + private IWebElement LogoutButton { get; set; } + + public MenuPage(IWebDriver driver) + : base(driver) + { + } + + public void ClickOpenMenuButton() + => ClickElement(OpenMenuButton); + + public void ClickLogoutButton() + => ClickElement(LogoutButton); +} diff --git a/samples/TestWare.Samples.Selenoid.Web/Program.cs b/samples/TestWare.Samples.Selenoid.Web/Program.cs deleted file mode 100644 index 3751555c..00000000 --- a/samples/TestWare.Samples.Selenoid.Web/Program.cs +++ /dev/null @@ -1,2 +0,0 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); diff --git a/samples/TestWare.Samples.Selenoid.Web/StepDefinitions/SwagLabs/InventorySteps.cs b/samples/TestWare.Samples.Selenoid.Web/StepDefinitions/SwagLabs/InventorySteps.cs new file mode 100644 index 00000000..18f44adf --- /dev/null +++ b/samples/TestWare.Samples.Selenoid.Web/StepDefinitions/SwagLabs/InventorySteps.cs @@ -0,0 +1,25 @@ +using TestWare.Core; +using TestWare.Samples.Selenoid.Web.POM.Inventory; + +namespace TestWare.Samples.Selenoid.Web.StepDefinitions; + +/// +/// Initializes a new instance of the class that contains +/// all the step action methods that can be used at Feature level. +/// +[Binding] +public sealed class InventorySteps +{ + private readonly IInventoryPage inventoryPage; + + public InventorySteps() + { + inventoryPage = ContainerManager.GetTestWareComponent(); + } + + [Then(@"the user can login")] + public void ThenTheUserCanLogin() + { + inventoryPage.CheckUserIsAtInventory(); + } +} diff --git a/samples/TestWare.Samples.Selenoid.Web/StepDefinitions/SwagLabs/LoginSteps.cs b/samples/TestWare.Samples.Selenoid.Web/StepDefinitions/SwagLabs/LoginSteps.cs new file mode 100644 index 00000000..c43b759d --- /dev/null +++ b/samples/TestWare.Samples.Selenoid.Web/StepDefinitions/SwagLabs/LoginSteps.cs @@ -0,0 +1,53 @@ +using TestWare.Core; +using TestWare.Samples.Selenoid.Web.POM.Login; + +namespace TestWare.Samples.Selenoid.Web.StepDefinitions; + +/// +/// Initializes a new instance of the class that contains +/// all the step action methods that can be used at Feature level. +/// +[Binding] +public sealed class LoginSteps +{ + private readonly ILoginPage loginPage; + + public LoginSteps() + { + loginPage = ContainerManager.GetTestWareComponent(); + } + + [Given(@"the user enters username '([^']*)'")] + public void GivenTheUserEntersUsername(string userName) + { + loginPage.EnterUserName(userName); + } + + [Given(@"the user enters password '([^']*)'")] + public void GivenTheUserEntersValidPassword(string password) + { + loginPage.EnterUserPassword(password); + } + + [Given(@"user '([^']*)' is logged with '([^']*)' into SwagLabs")] + public void GivenUserIsLogedWithIntoSwagLabs(string userName, string password) + { + GivenTheUserEntersUsername(userName); + GivenTheUserEntersValidPassword(password); + WhenTheUserClicksSubmit(); + } + + + [Given(@"the user clicks submit")] + [When(@"the user clicks submit")] + public void WhenTheUserClicksSubmit() + { + loginPage.ClickLoginButton(); + } + + [Then(@"the user is at Login page")] + public void UserIsAtLoginPage() + { + loginPage.CheckUserIsAtLoginpage(); + } +} diff --git a/samples/TestWare.Samples.Selenoid.Web/StepDefinitions/SwagLabs/MenuSteps.cs b/samples/TestWare.Samples.Selenoid.Web/StepDefinitions/SwagLabs/MenuSteps.cs new file mode 100644 index 00000000..492ec89a --- /dev/null +++ b/samples/TestWare.Samples.Selenoid.Web/StepDefinitions/SwagLabs/MenuSteps.cs @@ -0,0 +1,26 @@ +using TestWare.Core; +using TestWare.Samples.Selenoid.Web.POM.Menu; + +namespace TestWare.Samples.Selenoid.Web.StepDefinitions; + +/// +/// Initializes a new instance of the class that contains +/// all the step action methods that can be used at Feature level. +/// +[Binding] +public sealed class MenuSteps +{ + private readonly IMenuPage menuPage; + + public MenuSteps() + { + menuPage = ContainerManager.GetTestWareComponent(); + } + + [When(@"the user clicks Logout button")] + public void WhenTheUserClicksLogoutButton() + { + menuPage.ClickOpenMenuButton(); + menuPage.ClickLogoutButton(); + } +} diff --git a/samples/TestWare.Samples.Selenoid.Web/StepDefinitions/SwagLabs/MultiplePageSteps.cs b/samples/TestWare.Samples.Selenoid.Web/StepDefinitions/SwagLabs/MultiplePageSteps.cs new file mode 100644 index 00000000..35fd511e --- /dev/null +++ b/samples/TestWare.Samples.Selenoid.Web/StepDefinitions/SwagLabs/MultiplePageSteps.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using System.Linq; +using TestWare.Core; +using TestWare.Samples.Selenoid.Web.POM.Inventory; +using TestWare.Samples.Selenoid.Web.POM.Login; +using TestWare.Samples.Selenoid.Web.POM.Menu; + +namespace TestWare.Samples.Selenoid.Web.StepDefinitions; + +/// +/// Initializes a new instance of the class that contains +/// all the step action methods that can be used at Feature level. +/// +[Binding] +public sealed class MultiplePageSteps +{ + private readonly IEnumerable loginPages; + private readonly IEnumerable inventoryPages; + + public MultiplePageSteps(FeatureContext featureContext, ScenarioContext scenarioContext) + { + var tags = featureContext.FeatureInfo.Tags.Concat(scenarioContext.ScenarioInfo.Tags); + loginPages = ContainerManager.GetTestWareComponents(tags); + inventoryPages = ContainerManager.GetTestWareComponents(tags); + } + + [Given(@"the user enters username '([^']*)' on all")] + public void GivenTheUserEntersUsernameOnAll(string userName) + { + loginPages.ToList().ForEach(x => x.EnterUserName(userName)); + } + + [Given(@"the user enters password '([^']*)' on all")] + public void GivenTheUserEntersValidPasswordOnAll(string password) + { + loginPages.ToList().ForEach(x => x.EnterUserPassword(password)); + } + + [Given(@"the user clicks submit on all")] + [When(@"the user clicks submit on all")] + public void WhenTheUserClicksSubmitOnAll() + { + loginPages.ToList().ForEach(x => x.ClickLoginButton()); + } + + [Given(@"the user can login on all")] + [Then(@"the user can login on all")] + public void ThenTheUserCanLoginOnAll() + { + inventoryPages.ToList().ForEach(x => x.CheckUserIsAtInventory()); + } + + [When(@"the user clicks Logout button on '([^']*)'")] + public static void WhenTheUserClicksLogoutButtonOn(string browser) + { + var menuPage = ContainerManager.GetTestWareComponent(browser); + menuPage.ClickOpenMenuButton(); + menuPage.ClickLogoutButton(); + } + + [Then(@"the user is at Login page on '([^']*)'")] + public static void ThenTheUserIsAtLoginPageOn(string browser) + { + var loginPage = ContainerManager.GetTestWareComponent(browser); + loginPage.CheckUserIsAtLoginpage(); + } +} diff --git a/samples/TestWare.Samples.Selenoid.Web/TestConfiguration.Web.json b/samples/TestWare.Samples.Selenoid.Web/TestConfiguration.Web.json index d76c94ed..d96ad277 100644 --- a/samples/TestWare.Samples.Selenoid.Web/TestConfiguration.Web.json +++ b/samples/TestWare.Samples.Selenoid.Web/TestConfiguration.Web.json @@ -5,10 +5,38 @@ "Capabilities": [ { "Name": "Chrome", - "Uri": "http://selenoid:4444/wd/hub", - "BrowserType": "Chrome", - "BrowserVersion": "111", - "Resolution": "1920x1080", + "Uri": "http://localhost:4444/wd/hub", + "BrowserName": "Chrome", + "BrowserVersion": "111.0", + "Resolution": "1920x1080x24", + "EnableLog": false, + "EnableVnc": true, + "EnableVideo": false, + "CommandTimeOutInMinutes": 5, + "Arguments": [ + "--start-maximized" + ] + }, + { + "Name": "Firefox", + "Uri": "http://localhost:4444/wd/hub", + "BrowserName": "Firefox", + "BrowserVersion": "110.0", + "Resolution": "1920x1080x24", + "EnableLog": false, + "EnableVnc": true, + "EnableVideo": false, + "CommandTimeOutInMinutes": 5, + "Arguments": [ + "--start-maximized" + ] + }, + { + "Name": "Edge", + "Uri": "http://localhost:4444/wd/hub", + "BrowserName": "Edge", + "BrowserVersion": "111.0", + "Resolution": "1920x1080x24", "EnableLog": false, "EnableVnc": true, "EnableVideo": false, diff --git a/samples/TestWare.Samples.Selenoid.Web/TestWare.Samples.Selenoid.Web.csproj b/samples/TestWare.Samples.Selenoid.Web/TestWare.Samples.Selenoid.Web.csproj index 74abf5c9..26ee5272 100644 --- a/samples/TestWare.Samples.Selenoid.Web/TestWare.Samples.Selenoid.Web.csproj +++ b/samples/TestWare.Samples.Selenoid.Web/TestWare.Samples.Selenoid.Web.csproj @@ -1,10 +1,31 @@ - - - - Exe - net6.0 - enable - enable - - - + + + + net6.0 + enable + + false + + + + + + + + + + + + + + + + + + + + Always + + + + diff --git a/samples/TestWare.Samples.WinAppDriver/TestWare.Samples.WinAppDriver.Desktop.csproj b/samples/TestWare.Samples.WinAppDriver/TestWare.Samples.WinAppDriver.Desktop.csproj index 9e03f0a5..f678d77a 100644 --- a/samples/TestWare.Samples.WinAppDriver/TestWare.Samples.WinAppDriver.Desktop.csproj +++ b/samples/TestWare.Samples.WinAppDriver/TestWare.Samples.WinAppDriver.Desktop.csproj @@ -6,13 +6,13 @@ + - - + - + diff --git a/src/Engines/TestWare.Engines.Selenoid/Configuration/Capabilities.cs b/src/Engines/TestWare.Engines.Selenoid/Configuration/Capabilities.cs index 21a1dcfe..6517fb02 100644 --- a/src/Engines/TestWare.Engines.Selenoid/Configuration/Capabilities.cs +++ b/src/Engines/TestWare.Engines.Selenoid/Configuration/Capabilities.cs @@ -1,6 +1,6 @@ using TestWare.Engines.Selenoid.Factory; -namespace TestWare.Engines.Selenium.Configuration; +namespace TestWare.Engines.Selenoid.Configuration; internal class Capabilities { @@ -8,11 +8,11 @@ internal class Capabilities public string Uri { get; set; } - public string BrowserType { get; set; } + public string BrowserName { get; set; } public string BrowserVersion { get; set; } - public string Resolution { get; set; } + public string ScreenResolution { get; set; } public int CommandTimeOutInMinutes { get; set; } @@ -26,6 +26,6 @@ internal class Capabilities public SupportedBrowsers GetDriver() { - return Enum.Parse(BrowserType, true); + return Enum.Parse(BrowserName, true); } } \ No newline at end of file diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/AbstractFindsByAttribute.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/AbstractFindsByAttribute.cs new file mode 100644 index 00000000..e8a7075f --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/AbstractFindsByAttribute.cs @@ -0,0 +1,170 @@ +using System.ComponentModel; +using OpenQA.Selenium; + +namespace TestWare.Engines.Selenoid.Extras; + +/// +/// Base class for attributes to mark elements with methods by which to find a corresponding element on the page. +/// In order to define custom FindsBy attribute, inherit from this class and implement Finder property. +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] +public abstract class AbstractFindsByAttribute : Attribute, IComparable +{ + /// + /// Gets an explicit object to find by. + /// + public abstract By Finder { get; } + + /// + /// Gets or sets a value indicating where this attribute should be evaluated relative to other instances + /// of this attribute decorating the same class member. + /// + [DefaultValue(0)] + public int Priority { get; set; } + + /// + /// Determines if two instances are equal. + /// + /// One instance to compare. + /// The other instance to compare. + /// if the two instances are equal; otherwise, . + public static bool operator ==(AbstractFindsByAttribute one, AbstractFindsByAttribute two) + { + // If both are null, or both are same instance, return true. + if (ReferenceEquals(one, two)) + { + return true; + } + + // If one is null, but not both, return false. + if (one is null || two is null) + { + return false; + } + + return one.Equals(two); + } + + /// + /// Determines if two instances are unequal. + /// s + /// One instance to compare. + /// The other instance to compare. + /// if the two instances are not equal; otherwise, . + public static bool operator !=(AbstractFindsByAttribute one, AbstractFindsByAttribute two) + { + return !(one == two); + } + + /// + /// Determines if one instance is greater than another. + /// + /// One instance to compare. + /// The other instance to compare. + /// if the first instance is greater than the second; otherwise, . + + public static bool operator >(AbstractFindsByAttribute one, AbstractFindsByAttribute two) + { + return one.CompareTo(two) > 0; + } + + public static bool operator >=(AbstractFindsByAttribute one, AbstractFindsByAttribute two) + { + return one.CompareTo(two) >= 0; + } + + public static bool operator <=(AbstractFindsByAttribute one, AbstractFindsByAttribute two) + { + return one.CompareTo(two) <= 0; + } + + /// + /// Determines if one instance is less than another. + /// + /// One instance to compare. + /// The other instance to compare. + /// if the first instance is less than the second; otherwise, . + public static bool operator <(AbstractFindsByAttribute one, AbstractFindsByAttribute two) + { + return one.CompareTo(two) < 0; + } + + /// + /// Compares the current instance with another object of the same type and returns an + /// integer that indicates whether the current instance precedes, follows, or occurs + /// in the same position in the sort order as the other object. + /// + /// An object to compare with this instance. + /// A value that indicates the relative order of the objects being compared. The return value has these meanings: + /// + /// ValueMeaning + /// Less than zeroThis instance precedes in the sort order. + /// ZeroThis instance occurs in the same position in the sort order as . + /// Greater than zeroThis instance follows in the sort order. + /// + /// + public int CompareTo(object obj) + { + if (obj == null) + { + throw new ArgumentNullException(nameof(obj), "Object to compare cannot be null"); + } + + AbstractFindsByAttribute other = obj as AbstractFindsByAttribute; + if (other == null) + { + throw new ArgumentException("Object to compare must be a AbstractFindsByAttribute", nameof(obj)); + } + + if (Priority != other.Priority) + { + return Priority - other.Priority; + } + + return 0; + } + + /// + /// Determines whether the specified Object is equal + /// to the current Object. + /// + /// The Object to compare with the + /// current Object. + /// if the specified Object + /// is equal to the current Object; otherwise, + /// . + public override bool Equals(object obj) + { + if (obj == null) + { + return false; + } + + AbstractFindsByAttribute other = obj as AbstractFindsByAttribute; + if (other == null) + { + return false; + } + + if (other.Priority != Priority) + { + return false; + } + + if (other.Finder != Finder) + { + return false; + } + + return true; + } + + /// + /// Serves as a hash function for a particular type. + /// + /// A hash code for the current Object. + public override int GetHashCode() + { + return Finder.GetHashCode(); + } +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/ByAll.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/ByAll.cs new file mode 100644 index 00000000..8316c44a --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/ByAll.cs @@ -0,0 +1,109 @@ +#nullable enable + +using System.Collections.ObjectModel; +using System.Globalization; +using System.Text; +using OpenQA.Selenium; + +namespace TestWare.Engines.Selenoid.Extras; + +/// +/// Mechanism used to locate elements within a document using a series of lookups. This class will +/// find all DOM elements that matches all of the locators in sequence, e.g. +/// +/// +/// The following code will find all elements that match by1 and then all elements that also match by2. +/// +/// driver.findElements(new ByAll(by1, by2)) +/// +/// This means that the list of elements returned may not be in document order. +/// > +public class ByAll : By +{ + private readonly By[] bys; + + /// + /// Initializes a new instance of the class with one or more objects. + /// + /// One or more references + public ByAll(params By[] bys) + { + this.bys = bys; + } + + /// + /// Find a single element. + /// + /// Context used to find the element. + /// The element that matches + public override IWebElement FindElement(ISearchContext context) + { + var elements = this.FindElements(context); + if (elements.Count == 0) + { + throw new NoSuchElementException("Cannot locate an element using " + this.ToString()); + } + + return elements[0]; + } + + /// + /// Finds many elements + /// + /// Context used to find the element. + /// A readonly collection of elements that match. + public override ReadOnlyCollection FindElements(ISearchContext context) + { + if (this.bys.Length == 0) + { + return new List().AsReadOnly(); + } + + IEnumerable? elements = null; + foreach (By by in this.bys) + { + ReadOnlyCollection foundElements = by.FindElements(context); + if (foundElements.Count == 0) + { + // Optimization: If at any time a find returns no elements, the + // only possible result for find-all is an empty collection. + return new List().AsReadOnly(); + } + + if (elements == null) + { + elements = foundElements; + } + else + { + elements = elements.Intersect(by.FindElements(context)); + } + } + if (elements == null) + { + elements = new List().AsReadOnly(); + } + + return elements.ToList().AsReadOnly(); + } + + /// + /// Writes out a comma separated list of the objects used in the chain. + /// + /// Converts the value of this instance to a + public override string ToString() + { + StringBuilder stringBuilder = new StringBuilder(); + foreach (By by in this.bys) + { + if (stringBuilder.Length > 0) + { + stringBuilder.Append(','); + } + + stringBuilder.Append(by); + } + + return string.Format(CultureInfo.InvariantCulture, "By.All([{0}])", stringBuilder.ToString()); + } +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/ByChained.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/ByChained.cs new file mode 100644 index 00000000..a7dd1a72 --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/ByChained.cs @@ -0,0 +1,111 @@ +#nullable enable + +using System.Collections.ObjectModel; +using System.Globalization; +using System.Text; +using OpenQA.Selenium; + +namespace TestWare.Engines.Selenoid.Extras; + +/// +/// Mechanism used to locate elements within a document using a series of other lookups. This class +/// will find all DOM elements that matches each of the locators in sequence +/// +/// +/// The following code will will find all elements that match by2 and appear under an element that matches +/// by1. +/// +/// driver.findElements(new ByChained(by1, by2)) +/// +/// +public class ByChained : By +{ + private readonly By[] bys; + + /// + /// Initializes a new instance of the class with one or more objects. + /// + /// One or more references + public ByChained(params By[] bys) + { + this.bys = bys; + } + + /// + /// Find a single element. + /// + /// Context used to find the element. + /// The element that matches + public override IWebElement FindElement(ISearchContext context) + { + ReadOnlyCollection elements = this.FindElements(context); + if (elements.Count == 0) + { + throw new NoSuchElementException("Cannot locate an element using " + this.ToString()); + } + + return elements[0]; + } + + /// + /// Finds many elements + /// + /// Context used to find the element. + /// A readonly collection of elements that match. + public override ReadOnlyCollection FindElements(ISearchContext context) + { + if (this.bys.Length == 0) + { + return new List().AsReadOnly(); + } + + List? elems = null; + foreach (By by in this.bys) + { + List newElems = new List(); + + if (elems == null) + { + newElems.AddRange(by.FindElements(context)); + } + else + { + foreach (IWebElement elem in elems) + { + try + { + newElems.AddRange(elem.FindElements(by)); + } + catch (StaleElementReferenceException) + { + // Elements has gone stale during search. + } + } + } + + elems = newElems; + } + + return elems!.AsReadOnly(); + } + + /// + /// Writes out a comma separated list of the objects used in the chain. + /// + /// Converts the value of this instance to a + public override string ToString() + { + StringBuilder stringBuilder = new StringBuilder(); + foreach (By by in this.bys) + { + if (stringBuilder.Length > 0) + { + stringBuilder.Append(','); + } + + stringBuilder.Append(by); + } + + return string.Format(CultureInfo.InvariantCulture, "By.Chained([{0}])", stringBuilder.ToString()); + } +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/ByFactory.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/ByFactory.cs new file mode 100644 index 00000000..b37ad2fb --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/ByFactory.cs @@ -0,0 +1,64 @@ +#nullable enable +using System.Globalization; +using System.Reflection; +using OpenQA.Selenium; + +namespace TestWare.Engines.Selenoid.Extras; + +/// +/// Provides instances of the object to the attributes. +/// +internal static class ByFactory +{ + /// + /// Gets an instance of the class based on the specified attribute. + /// + /// The describing how to find the element. + /// An instance of the class. + public static By From(FindsByAttribute attribute) + { + var how = attribute.How; + var usingValue = attribute.Using; + switch (how) + { + case How.Id: + return By.Id(usingValue); + case How.Name: + return By.Name(usingValue); + case How.TagName: + return By.TagName(usingValue); + case How.ClassName: + return By.ClassName(usingValue); + case How.CssSelector: + return By.CssSelector(usingValue); + case How.LinkText: + return By.LinkText(usingValue); + case How.PartialLinkText: + return By.PartialLinkText(usingValue); + case How.XPath: + return By.XPath(usingValue); + case How.Custom: + if (attribute.CustomFinderType == null) + { + throw new ArgumentException("Cannot use How.Custom without supplying a custom finder type"); + } + + if (!attribute.CustomFinderType.IsSubclassOf(typeof(By))) + { + throw new ArgumentException("Custom finder type must be a descendent of the By class"); + } + + ConstructorInfo? ctor = attribute.CustomFinderType.GetConstructor(new Type[] { typeof(string) }); + if (ctor == null) + { + throw new ArgumentException("Custom finder type must expose a public constructor with a string argument"); + } + + By finder = (By)ctor.Invoke(new object?[] { usingValue }); + + return finder; + } + + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Did not know how to construct How from how {0}, using {1}", how, usingValue)); + } +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/ByIdOrName.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/ByIdOrName.cs new file mode 100644 index 00000000..1816e508 --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/ByIdOrName.cs @@ -0,0 +1,72 @@ + +using System.Collections.ObjectModel; +using System.Globalization; +using OpenQA.Selenium; + +namespace TestWare.Engines.Selenoid.Extras; + +/// +/// Finds element when the id or the name attribute has the specified value. +/// +public class ByIdOrName : By +{ + private readonly string elementIdentifier = string.Empty; + private readonly By idFinder; + private readonly By nameFinder; + + /// + /// Initializes a new instance of the class. + /// + /// The ID or Name to use in finding the element. + public ByIdOrName(string elementIdentifier) + { + if (string.IsNullOrEmpty(elementIdentifier)) + { + throw new ArgumentException("element identifier cannot be null or the empty string", nameof(elementIdentifier)); + } + + this.elementIdentifier = elementIdentifier; + this.idFinder = By.Id(this.elementIdentifier); + this.nameFinder = By.Name(this.elementIdentifier); + } + + /// + /// Find a single element. + /// + /// Context used to find the element. + /// The element that matches + public override IWebElement FindElement(ISearchContext context) + { + try + { + return this.idFinder.FindElement(context); + } + catch (NoSuchElementException) + { + return this.nameFinder.FindElement(context); + } + } + + /// + /// Finds many elements + /// + /// Context used to find the element. + /// A readonly collection of elements that match. + public override ReadOnlyCollection FindElements(ISearchContext context) + { + List elements = new List(); + elements.AddRange(this.idFinder.FindElements(context)); + elements.AddRange(this.nameFinder.FindElements(context)); + + return elements.AsReadOnly(); + } + + /// + /// Writes out a description of this By object. + /// + /// Converts the value of this instance to a + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "ByIdOrName([{0}])", this.elementIdentifier); + } +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/CacheLookupAttribute.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/CacheLookupAttribute.cs new file mode 100644 index 00000000..7d9be172 --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/CacheLookupAttribute.cs @@ -0,0 +1,10 @@ + +namespace TestWare.Engines.Selenoid.Extras; + +/// +/// Marks the element so that lookups to the browser page are cached. This class cannot be inherited. +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = false)] +public sealed class CacheLookupAttribute : Attribute +{ +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/DefaultElementLocator.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/DefaultElementLocator.cs new file mode 100644 index 00000000..33e735a6 --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/DefaultElementLocator.cs @@ -0,0 +1,79 @@ +#nullable enable + +using System.Collections.ObjectModel; +using OpenQA.Selenium; + +namespace TestWare.Engines.Selenoid.Extras; + +/// +/// A default locator for elements for use with the . This locator +/// implements no retry logic for elements not being found, nor for elements being stale. +/// +public class DefaultElementLocator : IElementLocator +{ + + /// + /// Initializes a new instance of the class. + /// + /// The used by this locator + /// to locate elements. + public DefaultElementLocator(ISearchContext searchContext) + { + this.SearchContext = searchContext; + } + + /// + /// Gets the to be used in locating elements. + /// + public ISearchContext SearchContext { get; } + + /// + /// Locates an element using the given list of criteria. + /// + /// The list of methods by which to search for the element. + /// An which is the first match under the desired criteria. + public IWebElement LocateElement(IEnumerable bys) + { + if (bys == null) + { + throw new ArgumentNullException(nameof(bys), "List of criteria may not be null"); + } + + string? errorString = null; + foreach (var by in bys) + { + try + { + return this.SearchContext.FindElement(by); + } + catch (NoSuchElementException) + { + errorString = (errorString == null ? "Could not find element by: " : errorString + ", or: ") + by; + } + } + + throw new NoSuchElementException(errorString); + } + + /// + /// Locates a list of elements using the given list of criteria. + /// + /// The list of methods by which to search for the elements. + /// A list of all elements which match the desired criteria. + public ReadOnlyCollection LocateElements(IEnumerable bys) + { + if (bys == null) + { + throw new ArgumentNullException(nameof(bys), "List of criteria may not be null"); + } + + List collection = new List(); + foreach (var by in bys) + { + ReadOnlyCollection list = this.SearchContext.FindElements(by); + collection.AddRange(list); + } + + return collection.AsReadOnly(); + } +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/DefaultPageObjectMemberDecorator.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/DefaultPageObjectMemberDecorator.cs new file mode 100644 index 00000000..78dd05fe --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/DefaultPageObjectMemberDecorator.cs @@ -0,0 +1,154 @@ +#nullable enable + +using System.Collections.ObjectModel; +using System.Reflection; +using OpenQA.Selenium; +using TestWare.Engines.Selenoid.Extras.MemberBuilders; + +namespace TestWare.Engines.Selenoid.Extras; + +/// +/// Default decorator determining how members of a class which represent elements in a Page Object +/// are detected. +/// +public class DefaultPageObjectMemberDecorator : IPageObjectMemberDecorator +{ + private static readonly List _memberBuilders = new List { + new WebElementBuilder(), + new WebElementListBuilder(), + new WrappedElementBuilder(), + new WrappedElementListBuilder() + }; + + /// + /// Locates an element or list of elements for a Page Object member, and returns a + /// proxy object for the element or list of elements. + /// + /// The containing information about + /// a class's member. + /// The used to locate elements. + /// A transparent proxy to the WebDriver element object. + public virtual object? Decorate(MemberInfo member, IElementLocator locator) + { + FieldInfo? field = member as FieldInfo; + PropertyInfo? property = member as PropertyInfo; + + Type? targetType = null; + if (field != null) + { + targetType = field.FieldType; + } + + if (property != null && property.CanWrite) + { + targetType = property.PropertyType; + } + + if (targetType == null) + { + return null; + } + + IList bys = CreateLocatorList(member); + if (bys.Count > 0) + { + bool cache = ShouldCacheLookup(member); + return CreateObject(targetType, locator, bys, cache); + } + + return null; + } + + public virtual object CreateObject(Type memberType, IElementLocator locator, IEnumerable bys, bool cache) + { + foreach (var builder in _memberBuilders) + { + if (builder.CreateObject(memberType, locator, bys, cache, out object? createdObject)) + { + return createdObject; + } + } + + throw new ArgumentException($"Type of member '{memberType.Name}' is not IWebElement or IList"); + } + + /// + /// Determines whether lookups on this member should be cached. + /// + /// The containing information about + /// the member of the Page Object class. + /// if lookups are to be cached; otherwise, . + protected static bool ShouldCacheLookup(MemberInfo member) + { + if (member == null) + { + throw new ArgumentNullException(nameof(member), "member cannot be null"); + } + + var cacheAttributeType = typeof(CacheLookupAttribute); + bool cache = member.GetCustomAttributes(cacheAttributeType, true).Length > 0 + || member.DeclaringType?.GetCustomAttributes(cacheAttributeType, true).Length > 0; + + return cache; + } + + /// + /// Creates a list of locators based on the attributes of this member. + /// + /// The containing information about + /// the member of the Page Object class. + /// A list of locators based on the attributes of this member. + protected static ReadOnlyCollection CreateLocatorList(MemberInfo member) + { + if (member == null) + { + throw new ArgumentNullException(nameof(member), "member cannot be null"); + } + + var useSequenceAttributes = Attribute.GetCustomAttributes(member, typeof(FindsBySequenceAttribute), true); + bool useSequence = useSequenceAttributes.Length > 0; + + var useFindAllAttributes = Attribute.GetCustomAttributes(member, typeof(FindsByAllAttribute), true); + bool useAll = useFindAllAttributes.Length > 0; + + if (useSequence && useAll) + { + throw new ArgumentException("Cannot specify FindsBySequence and FindsByAll on the same member"); + } + + List bys = new List(); + var attributes = Attribute.GetCustomAttributes(member, typeof(AbstractFindsByAttribute), true); + if (attributes.Length > 0) + { + Array.Sort(attributes); + foreach (var attribute in attributes) + { + var castedAttribute = (AbstractFindsByAttribute)attribute; + + var findsByAttribute = attribute as FindsByAttribute; + if (findsByAttribute != null && findsByAttribute.Using == null) + { + findsByAttribute.Using = member.Name; + } + + bys.Add(castedAttribute.Finder); + } + + if (useSequence) + { + ByChained chained = new ByChained(bys.ToArray()); + bys.Clear(); + bys.Add(chained); + } + + if (useAll) + { + ByAll all = new ByAll(bys.ToArray()); + bys.Clear(); + bys.Add(all); + } + } + + return bys.AsReadOnly(); + } +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/ExpectedConditions.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/ExpectedConditions.cs new file mode 100644 index 00000000..501844cd --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/ExpectedConditions.cs @@ -0,0 +1,602 @@ + +using System.Collections.ObjectModel; +using System.Text.RegularExpressions; +using OpenQA.Selenium; + +namespace TestWare.Engines.Selenoid.Extras; + +/// +/// Supplies a set of common conditions that can be waited for using . +/// +/// +/// +/// IWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(3)) +/// IWebElement element = wait.Until(ExpectedConditions.ElementExists(By.Id("foo"))); +/// +/// +public sealed class ExpectedConditions +{ + /// + /// Prevents a default instance of the class from being created. + /// + private ExpectedConditions() + { + } + + /// + /// An expectation for checking the title of a page. + /// + /// The expected title, which must be an exact match. + /// when the title matches; otherwise, . + public static Func TitleIs(string title) + { + return (driver) => { return title == driver.Title; }; + } + + /// + /// An expectation for checking that the title of a page contains a case-sensitive substring. + /// + /// The fragment of title expected. + /// when the title matches; otherwise, . + public static Func TitleContains(string title) + { + return (driver) => { return driver.Title.Contains(title); }; + } + + /// + /// An expectation for the URL of the current page to be a specific URL. + /// + /// The URL that the page should be on + /// when the URL is what it should be; otherwise, . + public static Func UrlToBe(string url) + { + return (driver) => { return driver.Url.ToLowerInvariant().Equals(url.ToLowerInvariant()); }; + } + + /// + /// An expectation for the URL of the current page to be a specific URL. + /// + /// The fraction of the url that the page should be on + /// when the URL contains the text; otherwise, . + public static Func UrlContains(string fraction) + { + return (driver) => { return driver.Url.ToLowerInvariant().Contains(fraction.ToLowerInvariant()); }; + } + + /// + /// An expectation for the URL of the current page to be a specific URL. + /// + /// The regular expression that the URL should match + /// if the URL matches the specified regular expression; otherwise, . + public static Func UrlMatches(string regex) + { + return (driver) => + { + var currentUrl = driver.Url; + var pattern = new Regex(regex, RegexOptions.IgnoreCase); + var match = pattern.Match(currentUrl); + return match.Success; + }; + } + + /// + /// An expectation for checking that an element is present on the DOM of a + /// page. This does not necessarily mean that the element is visible. + /// + /// The locator used to find the element. + /// The once it is located. + public static Func ElementExists(By locator) + { + return (driver) => { return driver.FindElement(locator); }; + } + + /// + /// An expectation for checking that an element is present on the DOM of a page + /// and visible. Visibility means that the element is not only displayed but + /// also has a height and width that is greater than 0. + /// + /// The locator used to find the element. + /// The once it is located and visible. + public static Func ElementIsVisible(By locator) + { + return (driver) => + { + try + { + return ElementIfVisible(driver.FindElement(locator)); + } + catch (StaleElementReferenceException) + { + return null; + } + }; + } + + /// + /// An expectation for checking that all elements present on the web page that + /// match the locator are visible. Visibility means that the elements are not + /// only displayed but also have a height and width that is greater than 0. + /// + /// The locator used to find the element. + /// The list of once it is located and visible. + public static Func> VisibilityOfAllElementsLocatedBy(By locator) + { + return (driver) => + { + try + { + var elements = driver.FindElements(locator); + if (elements.Any(element => !element.Displayed)) + { + return null; + } + + return elements.Any() ? elements : null; + } + catch (StaleElementReferenceException) + { + return null; + } + }; + } + + /// + /// An expectation for checking that all elements present on the web page that + /// match the locator are visible. Visibility means that the elements are not + /// only displayed but also have a height and width that is greater than 0. + /// + /// list of WebElements + /// The list of once it is located and visible. + public static Func> VisibilityOfAllElementsLocatedBy(ReadOnlyCollection elements) + { + return (driver) => + { + try + { + if (elements.Any(element => !element.Displayed)) + { + return null; + } + + return elements.Any() ? elements : null; + } + catch (StaleElementReferenceException) + { + return null; + } + }; + } + + /// + /// An expectation for checking that all elements present on the web page that + /// match the locator. + /// + /// The locator used to find the element. + /// The list of once it is located. + public static Func> PresenceOfAllElementsLocatedBy(By locator) + { + return (driver) => + { + try + { + var elements = driver.FindElements(locator); + return elements.Any() ? elements : null; + } + catch (StaleElementReferenceException) + { + return null; + } + }; + } + + /// + /// An expectation for checking if the given text is present in the specified element. + /// + /// The WebElement + /// Text to be present in the element + /// once the element contains the given text; otherwise, . + public static Func TextToBePresentInElement(IWebElement element, string text) + { + return (driver) => + { + try + { + var elementText = element.Text; + return elementText.Contains(text); + } + catch (StaleElementReferenceException) + { + return false; + } + }; + } + + /// + /// An expectation for checking if the given text is present in the element that matches the given locator. + /// + /// The locator used to find the element. + /// Text to be present in the element + /// once the element contains the given text; otherwise, . + public static Func TextToBePresentInElementLocated(By locator, string text) + { + return (driver) => + { + try + { + var element = driver.FindElement(locator); + var elementText = element.Text; + return elementText.Contains(text); + } + catch (StaleElementReferenceException) + { + return false; + } + }; + } + + /// + /// An expectation for checking if the given text is present in the specified elements value attribute. + /// + /// The WebElement + /// Text to be present in the element + /// once the element contains the given text; otherwise, . + public static Func TextToBePresentInElementValue(IWebElement element, string text) + { + return (driver) => + { + try + { + var elementValue = element.GetAttribute("value"); + if (elementValue != null) + { + return elementValue.Contains(text); + } + else + { + return false; + } + } + catch (StaleElementReferenceException) + { + return false; + } + }; + } + + /// + /// An expectation for checking if the given text is present in the specified elements value attribute. + /// + /// The locator used to find the element. + /// Text to be present in the element + /// once the element contains the given text; otherwise, . + public static Func TextToBePresentInElementValue(By locator, string text) + { + return (driver) => + { + try + { + var element = driver.FindElement(locator); + var elementValue = element.GetAttribute("value"); + if (elementValue != null) + { + return elementValue.Contains(text); + } + else + { + return false; + } + } + catch (StaleElementReferenceException) + { + return false; + } + }; + } + + /// + /// An expectation for checking whether the given frame is available to switch + /// to. If the frame is available it switches the given driver to the + /// specified frame. + /// + /// Used to find the frame (id or name) + /// + public static Func FrameToBeAvailableAndSwitchToIt(string frameLocator) + { + return (driver) => + { + try + { + return driver.SwitchTo().Frame(frameLocator); + } + catch (NoSuchFrameException) + { + return null; + } + }; + } + + /// + /// An expectation for checking whether the given frame is available to switch + /// to. If the frame is available it switches the given driver to the + /// specified frame. + /// + /// Locator for the Frame + /// + public static Func FrameToBeAvailableAndSwitchToIt(By locator) + { + return (driver) => + { + try + { + var frameElement = driver.FindElement(locator); + return driver.SwitchTo().Frame(frameElement); + } + catch (NoSuchFrameException) + { + return null; + } + }; + } + + /// + /// An expectation for checking that an element is either invisible or not present on the DOM. + /// + /// The locator used to find the element. + /// if the element is not displayed; otherwise, . + public static Func InvisibilityOfElementLocated(By locator) + { + return (driver) => + { + try + { + var element = driver.FindElement(locator); + return !element.Displayed; + } + catch (NoSuchElementException) + { + // Returns true because the element is not present in DOM. The + // try block checks if the element is present but is invisible. + return true; + } + catch (StaleElementReferenceException) + { + // Returns true because stale element reference implies that element + // is no longer visible. + return true; + } + }; + } + + /// + /// An expectation for checking that an element with text is either invisible or not present on the DOM. + /// + /// The locator used to find the element. + /// Text of the element + /// if the element is not displayed; otherwise, . + public static Func InvisibilityOfElementWithText(By locator, string text) + { + return (driver) => + { + try + { + var element = driver.FindElement(locator); + var elementText = element.Text; + if (string.IsNullOrEmpty(elementText)) + { + return true; + } + + return !elementText.Equals(text); + } + catch (NoSuchElementException) + { + // Returns true because the element with text is not present in DOM. The + // try block checks if the element is present but is invisible. + return true; + } + catch (StaleElementReferenceException) + { + // Returns true because stale element reference implies that element + // is no longer visible. + return true; + } + }; + } + + /// + /// An expectation for checking an element is visible and enabled such that you + /// can click it. + /// + /// The locator used to find the element. + /// The once it is located and clickable (visible and enabled). + public static Func ElementToBeClickable(By locator) + { + return (driver) => + { + var element = ElementIfVisible(driver.FindElement(locator)); + try + { + if (element != null && element.Enabled) + { + return element; + } + else + { + return null; + } + } + catch (StaleElementReferenceException) + { + return null; + } + }; + } + + /// + /// An expectation for checking an element is visible and enabled such that you + /// can click it. + /// + /// The element. + /// The once it is clickable (visible and enabled). + public static Func ElementToBeClickable(IWebElement element) + { + return (driver) => + { + try + { + if (element != null && element.Displayed && element.Enabled) + { + return element; + } + else + { + return null; + } + } + catch (StaleElementReferenceException) + { + return null; + } + }; + } + + /// + /// Wait until an element is no longer attached to the DOM. + /// + /// The element. + /// is the element is still attached to the DOM; otherwise, . + public static Func StalenessOf(IWebElement element) + { + return (driver) => + { + try + { + // Calling any method forces a staleness check + return element == null || !element.Enabled; + } + catch (StaleElementReferenceException) + { + return true; + } + }; + } + + /// + /// An expectation for checking if the given element is selected. + /// + /// The element. + /// given element is selected.; otherwise, . + public static Func ElementToBeSelected(IWebElement element) + { + return ElementSelectionStateToBe(element, true); + } + + /// + /// An expectation for checking if the given element is in correct state. + /// + /// The element. + /// selected or not selected + /// given element is in correct state.; otherwise, . + public static Func ElementToBeSelected(IWebElement element, bool selected) + { + return (driver) => + { + return element.Selected == selected; + }; + } + + /// + /// An expectation for checking if the given element is selected. + /// + /// The locator used to find the element. + /// given element is selected.; otherwise, . + public static Func ElementToBeSelected(By locator) + { + return ElementSelectionStateToBe(locator, true); + } + + + /// + /// An expectation for checking if the given element is in correct state. + /// + /// The element. + /// selected or not selected + /// given element is in correct state.; otherwise, . + public static Func ElementSelectionStateToBe(IWebElement element, bool selected) + { + return (driver) => + { + return element.Selected == selected; + }; + } + + /// + /// An expectation for checking if the given element is in correct state. + /// + /// The locator used to find the element. + /// selected or not selected + /// given element is in correct state.; otherwise, . + public static Func ElementSelectionStateToBe(By locator, bool selected) + { + return (driver) => + { + try + { + var element = driver.FindElement(locator); + return element.Selected == selected; + } + catch (StaleElementReferenceException) + { + return false; + } + }; + } + + /// + /// An expectation for checking the AlterIsPresent + /// + /// Alert + public static Func AlertIsPresent() + { + return (driver) => + { + try + { + return driver.SwitchTo().Alert(); + } + catch (NoAlertPresentException) + { + return null; + } + }; + } + + /// + /// An expectation for checking the Alert State + /// + /// A value indicating whether or not an alert should be displayed in order to meet this condition. + /// alert is in correct state present or not present; otherwise, . + public static Func AlertState(bool state) + { + return (driver) => + { + var alertState = false; + try + { + driver.SwitchTo().Alert(); + alertState = true; + return alertState == state; + } + catch (NoAlertPresentException) + { + alertState = false; + return alertState == state; + } + }; + } + + private static IWebElement ElementIfVisible(IWebElement element) + { + return element.Displayed ? element : null; + } +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/FindsByAllAttribute.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/FindsByAllAttribute.cs new file mode 100644 index 00000000..c63e00f6 --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/FindsByAllAttribute.cs @@ -0,0 +1,30 @@ + +namespace TestWare.Engines.Selenoid.Extras; + +/// +/// Marks elements to indicate that found elements should match the criteria of +/// all on the field or property. +/// +/// +/// +/// When used with a set of , all criteria must be +/// matched to be returned. The criteria are used in sequence according to the +/// Priority property. Note that the behavior when setting multiple +/// Priority properties to the same value, or not +/// specifying a Priority value, is undefined. +/// +/// +/// +/// // Will find the element with the tag name "input" that also has an ID +/// // attribute matching "elementId". +/// [FindsByAll] +/// [FindsBy(How = How.TagName, Using = "input", Priority = 0)] +/// [FindsBy(How = How.Id, Using = "elementId", Priority = 1)] +/// public IWebElement thisElement; +/// +/// +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] +public sealed class FindsByAllAttribute : Attribute +{ +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/FindsByAttribute.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/FindsByAttribute.cs new file mode 100644 index 00000000..092d5119 --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/FindsByAttribute.cs @@ -0,0 +1,94 @@ +#nullable enable + +using System.ComponentModel; +using OpenQA.Selenium; + +namespace TestWare.Engines.Selenoid.Extras; + +/// +/// Marks program elements with methods by which to find a corresponding element on the page. Used +/// in conjunction with the , it allows you to quickly create Page Objects. +/// +/// +/// +/// You can use this attribute by specifying the and properties +/// to indicate how to find the elements. This attribute can be used to decorate fields and properties +/// in your Page Object classes. The of the field or property must be either +/// or IList{IWebElement}. Any other type will throw an +/// when is called. +/// +/// +/// +/// [FindsBy(How = How.Name, Using = "myElementName")] +/// public IWebElement foundElement; +/// +/// [FindsBy(How = How.TagName, Using = "a")] +/// public IList{IWebElement} allLinks; +/// +/// +/// +/// You can also use multiple instances of this attribute to find an element that may meet +/// one of multiple criteria. When using multiple instances, you can specify the order in +/// which the criteria is matched by using the property. +/// +/// +/// +/// // Will find the element with the name attribute matching the first of "anElementName" +/// // or "differentElementName". +/// [FindsBy(How = How.Name, Using = "anElementName", Priority = 0)] +/// [FindsBy(How = How.Name, Using = "differentElementName", Priority = 1)] +/// public IWebElement thisElement; +/// +/// +/// +public class FindsByAttribute : AbstractFindsByAttribute +{ + protected By? finder; + + public FindsByAttribute() { } + + /// + /// Create instance + /// + /// Method used to look up the element + /// Value to lookup by + public FindsByAttribute(How how, string @using) + { + How = how; + Using = @using; + } + + /// + /// Gets or sets the method used to look up the element + /// + [DefaultValue(How.Id)] + public How How { get; set; } + + /// + /// Gets or sets the value to lookup by (i.e. for How.Name, the actual name to look up) + /// + public string? Using { get; set; } + + /// + /// Gets or sets a value indicating the of the custom finder. The custom finder must + /// descend from the class, and expose a public constructor that takes a + /// argument. + /// + public Type? CustomFinderType { get; set; } + + /// + /// Gets an explicit object to find by. + /// + public override By Finder + { + get + { + if (this.finder == null) + { + this.finder = ByFactory.From(this); + } + + return this.finder; + } + } +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/FindsBySequenceAttribute.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/FindsBySequenceAttribute.cs new file mode 100644 index 00000000..23097957 --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/FindsBySequenceAttribute.cs @@ -0,0 +1,30 @@ + + +namespace TestWare.Engines.Selenoid.Extras; + +/// +/// Marks elements to indicate that each on the field or +/// property should be used in sequence to find the appropriate element. +/// +/// +/// +/// When used with a set of , the criteria are used +/// in sequence according to the Priority property to find child elements. Note that +/// the behavior when setting multiple Priority +/// properties to the same value, or not specifying a Priority value, is undefined. +/// +/// +/// +/// // Will find the element with the ID attribute matching "elementId", then will find +/// // a child element with the ID attribute matching "childElementId". +/// [FindsBySequence] +/// [FindsBy(How = How.Id, Using = "elementId", Priority = 0)] +/// [FindsBy(How = How.Id, Using = "childElementId", Priority = 1)] +/// public IWebElement thisElement; +/// +/// +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] +public sealed class FindsBySequenceAttribute : Attribute +{ +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/How.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/How.cs new file mode 100644 index 00000000..25e59507 --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/How.cs @@ -0,0 +1,53 @@ + +namespace TestWare.Engines.Selenoid.Extras; + +/// +/// Provides the lookup methods for the FindsBy attribute (for using in PageObjects) +/// +public enum How +{ + /// + /// Finds by + /// + Id, + + /// + /// Finds by + /// + Name, + + /// + /// Finds by + /// + TagName, + + /// + /// Finds by + /// + ClassName, + + /// + /// Finds by + /// + CssSelector, + + /// + /// Finds by + /// + LinkText, + + /// + /// Finds by + /// + PartialLinkText, + + /// + /// Finds by + /// + XPath, + + /// + /// Finds by a custom implementation. + /// + Custom +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/IElementLocator.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/IElementLocator.cs new file mode 100644 index 00000000..446e6b69 --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/IElementLocator.cs @@ -0,0 +1,35 @@ + +using System.Collections.ObjectModel; +using OpenQA.Selenium; + +namespace TestWare.Engines.Selenoid.Extras; + +/// +/// Interface describing how elements are to be located by a . +/// +/// +/// A locator must always contain a way to retrieve the to +/// use in locating elements. In practice, this will usually be implemented by passing +/// the context in via a constructor. +/// +public interface IElementLocator +{ + /// + /// Gets the to be used in locating elements. + /// + ISearchContext SearchContext { get; } + + /// + /// Locates an element using the given list of criteria. + /// + /// The list of methods by which to search for the element. + /// An which is the first match under the desired criteria. + IWebElement LocateElement(IEnumerable bys); + + /// + /// Locates a list of elements using the given list of criteria. + /// + /// The list of methods by which to search for the elements. + /// A list of all elements which match the desired criteria. + ReadOnlyCollection LocateElements(IEnumerable bys); +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/IPageObjectMemberDecorator.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/IPageObjectMemberDecorator.cs new file mode 100644 index 00000000..d1fb93ce --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/IPageObjectMemberDecorator.cs @@ -0,0 +1,22 @@ +#nullable enable + +using System.Reflection; + +namespace TestWare.Engines.Selenoid.Extras; + +/// +/// Interface describing how members of a class which represent elements in a Page Object +/// are detected. +/// +public interface IPageObjectMemberDecorator +{ + /// + /// Locates an element or list of elements for a Page Object member, and returns a + /// proxy object for the element or list of elements. + /// + /// The containing information about + /// a class's member. + /// The used to locate elements. + /// A transparent proxy to the WebDriver element object. + object? Decorate(MemberInfo member, IElementLocator locator); +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/IMemberBuilder.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/IMemberBuilder.cs new file mode 100644 index 00000000..4c8960ae --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/IMemberBuilder.cs @@ -0,0 +1,34 @@ +// +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Diagnostics.CodeAnalysis; +using OpenQA.Selenium; + +namespace TestWare.Engines.Selenoid.Extras.MemberBuilders; + +/// +/// Interface for page object members creation +/// +internal interface IMemberBuilder +{ + /// + /// Create page object member + /// + bool CreateObject(Type memberType, IElementLocator locator, IEnumerable bys, bool cache, + [NotNullWhen(true)] out object createdObject); +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WebElementBuilder.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WebElementBuilder.cs new file mode 100644 index 00000000..3afe823c --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WebElementBuilder.cs @@ -0,0 +1,41 @@ +// +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Diagnostics.CodeAnalysis; +using OpenQA.Selenium; + +namespace TestWare.Engines.Selenoid.Extras.MemberBuilders; + +/// +/// Creates member of type +/// +internal class WebElementBuilder : IMemberBuilder +{ + public bool CreateObject(Type memberType, IElementLocator locator, IEnumerable bys, bool cache, [NotNullWhen(true)] out object createdObject) + { + createdObject = null; + + if (memberType == typeof(IWebElement)) + { + createdObject = new WebElementProxy(locator, bys, cache); + return true; + } + + return false; + } +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WebElementListBuilder.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WebElementListBuilder.cs new file mode 100644 index 00000000..b53937eb --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WebElementListBuilder.cs @@ -0,0 +1,23 @@ +using System.Diagnostics.CodeAnalysis; +using OpenQA.Selenium; + +namespace TestWare.Engines.Selenoid.Extras.MemberBuilders; + +/// +/// Creates member of type +/// +internal class WebElementListBuilder : IMemberBuilder +{ + public bool CreateObject(Type memberType, IElementLocator locator, IEnumerable bys, bool cache, [NotNullWhen(true)] out object createdObject) + { + createdObject = null; + + if (memberType == typeof(IList)) + { + createdObject = new WebElementListProxy(locator, bys, cache); + return true; + } + + return false; + } +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WrappedElementBuilder.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WrappedElementBuilder.cs new file mode 100644 index 00000000..642b1018 --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WrappedElementBuilder.cs @@ -0,0 +1,21 @@ +using System.Diagnostics.CodeAnalysis; +using OpenQA.Selenium; + +namespace TestWare.Engines.Selenoid.Extras.MemberBuilders; + +internal class WrappedElementBuilder : IMemberBuilder +{ + public bool CreateObject(Type memberType, IElementLocator locator, IEnumerable bys, bool cache, [NotNullWhen(true)] out object createdObject) + { + createdObject = null; + + if (typeof(IWrapsElement).IsAssignableFrom(memberType)) + { + var webElement = new WebElementProxy(locator, bys, cache); + createdObject = WrapsElementFactory.Wrap(memberType, webElement); + return true; + } + + return false; + } +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WrappedElementListBuilder.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WrappedElementListBuilder.cs new file mode 100644 index 00000000..32524227 --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WrappedElementListBuilder.cs @@ -0,0 +1,25 @@ +using System.Diagnostics.CodeAnalysis; +using OpenQA.Selenium; + +namespace TestWare.Engines.Selenoid.Extras.MemberBuilders; + +internal class WrappedElementListBuilder : IMemberBuilder +{ + public bool CreateObject(Type memberType, IElementLocator locator, IEnumerable bys, bool cache, [NotNullWhen(true)] out object createdObject) + { + createdObject = null; + + if (memberType.IsGenericType && memberType.GetGenericTypeDefinition() == typeof(IList<>)) + { + var elementType = memberType.GetGenericArguments()[0]; + + if (typeof(IWrapsElement).IsAssignableFrom(elementType)) + { + var listType = typeof(WrapsElementListProxy<>).MakeGenericType(memberType.GetGenericArguments()[0]); + createdObject = Activator.CreateInstance(listType, new object[] { locator, bys, cache }); + } + } + + return createdObject != null; + } +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/PageFactory.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/PageFactory.cs new file mode 100644 index 00000000..be394019 --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/PageFactory.cs @@ -0,0 +1,202 @@ +#nullable enable + +using System.Reflection; +using OpenQA.Selenium; + +namespace TestWare.Engines.Selenoid.Extras; + +/// +/// Provides the ability to produce Page Objects modeling a page. This class cannot be inherited. +/// +public class PageFactory +{ + /// + /// Initializes a new instance of the class. + /// Private constructor prevents a default instance from being created. + /// + protected PageFactory() + { + } + + /// + /// Initializes the elements in the Page Object with the given type. + /// + /// The of the Page Object class. + /// The instance used to populate the page. + /// An instance of the Page Object class with the elements initialized. + /// + /// The class used in the argument must have a public constructor + /// that takes a single argument of type . This helps to enforce + /// best practices of the Page Object pattern, and encapsulates the driver into the Page + /// Object so that it can have no external WebDriver dependencies. + /// + /// + /// thrown if no constructor to the class can be found with a single IWebDriver argument + /// -or- + /// if a field or property decorated with the is not of type + /// or IList{IWebElement}. + /// + public static T InitElements(IWebDriver driver) + { + return InitElements(new DefaultElementLocator(driver)); + } + + /// + /// Initializes the elements in the Page Object with the given type. + /// + /// The of the Page Object class. + /// The implementation that + /// determines how elements are located. + /// An instance of the Page Object class with the elements initialized. + /// + /// The class used in the argument must have a public constructor + /// that takes a single argument of type . This helps to enforce + /// best practices of the Page Object pattern, and encapsulates the driver into the Page + /// Object so that it can have no external WebDriver dependencies. + /// + /// + /// thrown if no constructor to the class can be found with a single IWebDriver argument + /// -or- + /// if a field or property decorated with the is not of type + /// or IList{IWebElement}. + /// + public static T InitElements(IElementLocator locator) + { + Type pageClassType = typeof(T); + ConstructorInfo? ctor = pageClassType.GetConstructor(new Type[] { typeof(IWebDriver) }); + if (ctor == null) + { + throw new ArgumentException("No constructor for the specified class containing a single argument of type IWebDriver can be found"); + } + + if (locator == null) + { + throw new ArgumentNullException(nameof(locator), "locator cannot be null"); + } + + if (!(locator.SearchContext is IWebDriver driver)) + { + throw new ArgumentException("The search context of the element locator must implement IWebDriver", nameof(locator)); + } + + var page = (T)ctor.Invoke(new object[] { driver }); + InitElements(page, locator); + return page; + } + + /// + /// Initializes the elements in the Page Object. + /// + /// The driver used to find elements on the page. + /// The Page Object to be populated with elements. + /// + /// thrown if a field or property decorated with the is not of type + /// or IList{IWebElement}. + /// + public static void InitElements(ISearchContext driver, object page) + { + InitElements(page, new DefaultElementLocator(driver)); + } + + /// + /// Initializes the elements in the Page Object. + /// + /// The driver used to find elements on the page. + /// The Page Object to be populated with elements. + /// The implementation that + /// determines how Page Object members representing elements are discovered and populated. + /// + /// thrown if a field or property decorated with the is not of type + /// or IList{IWebElement}. + /// + public static void InitElements(ISearchContext driver, object page, IPageObjectMemberDecorator decorator) + { + InitElements(page, new DefaultElementLocator(driver), decorator); + } + + /// + /// Initializes the elements in the Page Object. + /// + /// The Page Object to be populated with elements. + /// The implementation that + /// determines how elements are located. + /// + /// thrown if a field or property decorated with the is not of type + /// or IList{IWebElement}. + /// + public static void InitElements(object page, IElementLocator locator) + { + InitElements(page, locator, new DefaultPageObjectMemberDecorator()); + } + + /// + /// Initializes the elements in the Page Object. + /// + /// The Page Object to be populated with elements. + /// The implementation that + /// determines how elements are located. + /// The implementation that + /// determines how Page Object members representing elements are discovered and populated. + /// + /// thrown if a field or property decorated with the is not of type + /// or IList{IWebElement}. + /// + public static void InitElements(object page, IElementLocator locator, IPageObjectMemberDecorator decorator) + { + if (page == null) + { + throw new ArgumentNullException(nameof(page), "page cannot be null"); + } + + if (locator == null) + { + throw new ArgumentNullException(nameof(locator), "locator cannot be null"); + } + + if (decorator == null) + { + throw new ArgumentNullException(nameof(locator), "decorator cannot be null"); + } + + if (locator.SearchContext == null) + { + throw new ArgumentException("The SearchContext of the locator object cannot be null", nameof(locator)); + } + + const BindingFlags PublicBindingOptions = BindingFlags.Instance | BindingFlags.Public; + const BindingFlags NonPublicBindingOptions = BindingFlags.Instance | BindingFlags.NonPublic; + + // Get a list of all of the fields and properties (public and non-public [private, protected, etc.]) + // in the passed-in page object. Note that we walk the inheritance tree to get superclass members. + Type? type = page.GetType(); + var members = new List(); + members.AddRange(type.GetFields(PublicBindingOptions)); + members.AddRange(type.GetProperties(PublicBindingOptions)); + while (type != null) + { + members.AddRange(type.GetFields(NonPublicBindingOptions)); + members.AddRange(type.GetProperties(NonPublicBindingOptions)); + type = type.BaseType; + } + + foreach (var member in members) + { + // Examine each member, and if the decorator returns a non-null object, + // set the value of that member to the decorated object. + object? decoratedValue = decorator.Decorate(member, locator); + if (decoratedValue == null) + { + continue; + } + + if (member is FieldInfo field) + { + field.SetValue(page, decoratedValue); + } + else if (member is PropertyInfo property && property.CanWrite) + { + property.SetValue(page, decoratedValue, null); + } + } + } +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/RetryingElementLocator.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/RetryingElementLocator.cs new file mode 100644 index 00000000..3f7b503d --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/RetryingElementLocator.cs @@ -0,0 +1,133 @@ +#nullable enable + +using System.Collections.ObjectModel; +using OpenQA.Selenium; + +namespace TestWare.Engines.Selenoid.Extras; + +/// +/// A locator for elements for use with the that retries locating +/// the element up to a timeout if the element is not found. +/// +public class RetryingElementLocator : IElementLocator +{ + private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(5); + private static readonly TimeSpan DefaultPollingInterval = TimeSpan.FromMilliseconds(500); + private TimeSpan timeout; + private TimeSpan pollingInterval; + + /// + /// Initializes a new instance of the class. + /// + /// The object that the + /// locator uses for locating elements. + public RetryingElementLocator(ISearchContext searchContext) + : this(searchContext, DefaultTimeout) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The object that the + /// locator uses for locating elements. + /// The indicating how long the locator should + /// retry before timing out. + public RetryingElementLocator(ISearchContext searchContext, TimeSpan timeout) + : this(searchContext, timeout, DefaultPollingInterval) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The object that the + /// locator uses for locating elements. + /// The indicating how long the locator should + /// retry before timing out. + /// The indicating how often to poll + /// for the existence of the element. + public RetryingElementLocator(ISearchContext searchContext, TimeSpan timeout, TimeSpan pollingInterval) + { + this.SearchContext = searchContext; + this.timeout = timeout; + this.pollingInterval = pollingInterval; + } + + /// + /// Gets the to be used in locating elements. + /// + public ISearchContext SearchContext { get; } + + /// + /// Locates an element using the given list of criteria. + /// + /// The list of methods by which to search for the element. + /// An which is the first match under the desired criteria. + public IWebElement LocateElement(IEnumerable bys) + { + if (bys == null) + { + throw new ArgumentNullException(nameof(bys), "List of criteria may not be null"); + } + + string? errorString = null; + DateTime endTime = DateTime.Now.Add(this.timeout); + bool timeoutReached = DateTime.Now > endTime; + while (!timeoutReached) + { + foreach (var by in bys) + { + try + { + return this.SearchContext.FindElement(by); + } + catch (NoSuchElementException) + { + errorString = (errorString == null ? "Could not find element by: " : errorString + ", or: ") + by; + } + } + + timeoutReached = DateTime.Now > endTime; + if (!timeoutReached) + { + Thread.Sleep(this.pollingInterval); + } + } + + throw new NoSuchElementException(errorString); + } + + /// + /// Locates a list of elements using the given list of criteria. + /// + /// The list of methods by which to search for the elements. + /// A list of all elements which match the desired criteria. + public ReadOnlyCollection LocateElements(IEnumerable bys) + { + if (bys == null) + { + throw new ArgumentNullException(nameof(bys), "List of criteria may not be null"); + } + + List collection = new List(); + DateTime endTime = DateTime.Now.Add(this.timeout); + bool timeoutReached = DateTime.Now > endTime; + while (!timeoutReached) + { + foreach (var by in bys) + { + ReadOnlyCollection list = this.SearchContext.FindElements(by); + collection.AddRange(list); + } + + timeoutReached = collection.Count != 0 || DateTime.Now > endTime; + if (!timeoutReached) + { + Thread.Sleep(this.pollingInterval); + } + } + + return collection.AsReadOnly(); + } +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/WebDriverObjectProxy.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/WebDriverObjectProxy.cs new file mode 100644 index 00000000..f31d4ebd --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/WebDriverObjectProxy.cs @@ -0,0 +1,38 @@ +using OpenQA.Selenium; + +namespace TestWare.Engines.Selenoid.Extras; + +/// +/// Represents a base proxy class for objects used with the PageFactory. +/// +internal abstract class WebDriverObjectProxy +{ + /// + /// Create WebDriverObjectProxy + /// + /// The implementation that + /// determines how elements are located. + /// The list of methods by which to search for the elements. + /// to cache the lookup to the element; otherwise, . + protected WebDriverObjectProxy(IElementLocator locator, IEnumerable bys, bool cache) + { + this.Locator = locator; + this.Bys = bys; + this.Cache = cache; + } + + /// + /// Gets the implementation that determines how elements are located. + /// + protected IElementLocator Locator { get; } + + /// + /// Gets the list of methods by which to search for the elements. + /// + protected IEnumerable Bys { get; } + + /// + /// Gets a value indicating whether element search results should be cached. + /// + protected bool Cache { get; } +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/WebElementEnumerable.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/WebElementEnumerable.cs new file mode 100644 index 00000000..f6c46a92 --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/WebElementEnumerable.cs @@ -0,0 +1,28 @@ +using TestWare.Engines.Selenoid.Extras; + +namespace TestWare.Engines.Selenoid.Extras; + +/// +/// Due to Linq optimized execution in dotnet core for IList, some methods lead to multiple elements retrieval. +/// In this class IList is wrapped in IEnumerable to disable that 'optimized' evaluation. +/// +public static class WebElementEnumerable +{ + public static IEnumerable Select(this IList webElements, Func selector) + => webElements.ToEnumerable().Select(selector); + + public static IEnumerable Where(this IList webElements, Func selector) + => webElements.ToEnumerable().Where(selector); + + public static List ToList(this IList webElements) + => webElements.ToEnumerable().ToList(); + + public static TElement[] ToArray(this IList webElements) + => webElements.ToEnumerable().ToArray(); + + private static IEnumerable ToEnumerable(this IList enumerable) + { + foreach (var element in enumerable) + yield return element; + } +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/WebElementListProxy.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/WebElementListProxy.cs new file mode 100644 index 00000000..b7d6f68d --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/WebElementListProxy.cs @@ -0,0 +1,65 @@ +#nullable enable + +using System.Collections; +using OpenQA.Selenium; + +namespace TestWare.Engines.Selenoid.Extras; + +/// +/// Represents a proxy class for a list of elements to be used with the PageFactory. +/// +internal class WebElementListProxy : WebDriverObjectProxy, IList +{ + private IList? _items; + + public WebElementListProxy(IElementLocator locator, IEnumerable bys, bool cache) + : base(locator, bys, cache) + { + } + + private IList Items + { + get + { + if (_items == null || !Cache) + { + _items = Locator.LocateElements(Bys); + } + return _items; + } + } + + #region Forwarded Items calls + + public IWebElement this[int index] + { + get { return Items[index]; } + set { Items[index] = value; } + } + + public int Count => Items.Count; + + public bool IsReadOnly => Items.IsReadOnly; + + public void Add(IWebElement item) => Items.Add(item); + + public void Clear() => Items.Clear(); + + public bool Contains(IWebElement item) => Items.Contains(item); + + public void CopyTo(IWebElement[] array, int arrayIndex) => Items.CopyTo(array, arrayIndex); + + public IEnumerator GetEnumerator() => Items.GetEnumerator(); + + public int IndexOf(IWebElement item) => Items.IndexOf(item); + + public void Insert(int index, IWebElement item) => Items.Insert(index, item); + + public bool Remove(IWebElement item) => Items.Remove(item); + + public void RemoveAt(int index) => Items.RemoveAt(index); + + IEnumerator IEnumerable.GetEnumerator() => Items.GetEnumerator(); + + #endregion +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/WebElementProxy.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/WebElementProxy.cs new file mode 100644 index 00000000..96ec06b8 --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/WebElementProxy.cs @@ -0,0 +1,96 @@ +#nullable enable +using System.Collections.ObjectModel; +using System.Drawing; +using OpenQA.Selenium; +using OpenQA.Selenium.Interactions.Internal; +using OpenQA.Selenium.Internal; + +namespace TestWare.Engines.Selenoid.Extras; + +/// +/// Intercepts the request to a single +/// +internal class WebElementProxy : WebDriverObjectProxy, IWrapsElement, IWebElement, ILocatable, IFindsElement +{ + private IWebElement? cachedElement; + + public WebElementProxy(IElementLocator locator, IEnumerable bys, bool cache) + : base(locator, bys, cache) + { + } + + /// + /// Gets the IWebElement object this proxy represents, returning a cached one if requested. + /// + public IWebElement WrappedElement + { + get + { + if (!this.Cache || this.cachedElement == null) + { + this.cachedElement = this.Locator.LocateElement(this.Bys); + } + + return this.cachedElement; + } + } + + #region Forwarded WrappedElement calls + + public string TagName => WrappedElement.TagName; + + public string Text => WrappedElement.Text; + + public bool Enabled => WrappedElement.Enabled; + + public bool Selected => WrappedElement.Selected; + + public Point Location => WrappedElement.Location; + + public Size Size => WrappedElement.Size; + + public bool Displayed => WrappedElement.Displayed; + + public void Clear() => WrappedElement.Clear(); + + public void Click() => WrappedElement.Click(); + + public IWebElement FindElement(By by) => WrappedElement.FindElement(by); + + public ReadOnlyCollection FindElements(By by) => WrappedElement.FindElements(by); + + public string GetAttribute(string attributeName) => WrappedElement.GetAttribute(attributeName); + + public string GetCssValue(string propertyName) => WrappedElement.GetCssValue(propertyName); + + [Obsolete ("Deprecated on IWebElement")] + public string GetProperty(string propertyName) => WrappedElement.GetProperty(propertyName); + + public void SendKeys(string text) => WrappedElement.SendKeys(text); + + public void Submit() => WrappedElement.Submit(); + + public Point LocationOnScreenOnceScrolledIntoView + => ((ILocatable)WrappedElement).LocationOnScreenOnceScrolledIntoView; + + public ICoordinates Coordinates + => ((ILocatable)WrappedElement).Coordinates; + + public override int GetHashCode() => WrappedElement.GetHashCode(); + + public override bool Equals(object? obj) => WrappedElement.Equals(obj); + + public IWebElement FindElement(string mechanism, string value) + => ((IFindsElement)WrappedElement).FindElement(mechanism, value); + + public ReadOnlyCollection FindElements(string mechanism, string value) + => ((IFindsElement)WrappedElement).FindElements(mechanism, value); + + public string GetDomAttribute(string attributeName) => WrappedElement.GetDomAttribute(attributeName); + + public string GetDomProperty(string propertyName) => WrappedElement.GetDomProperty(propertyName); + + public ISearchContext GetShadowRoot() => WrappedElement.GetShadowRoot(); + + #endregion Forwarded WrappedElement calls +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/WrapsElementFactory.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/WrapsElementFactory.cs new file mode 100644 index 00000000..00c286a9 --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/WrapsElementFactory.cs @@ -0,0 +1,35 @@ +using OpenQA.Selenium; + +namespace TestWare.Engines.Selenoid.Extras; + +internal static class WrapsElementFactory +{ + public static T Wrap(IWebElement webElement) where T : IWrapsElement + { + return (T)Wrap(typeof(T), webElement); + } + + public static object Wrap(Type wrapsElementType, IWebElement webElement) + { + var iWebElementConstructor = wrapsElementType.GetConstructor(new[] { typeof(IWebElement) }); + + // Option 1 - T has constructor with IWebElement parameter + if (iWebElementConstructor != null) + { + return iWebElementConstructor.Invoke(new object[] { webElement }); + } + + // Option 2 - T has parameterless constructor, and setter on WrappedElement property + var parameterlessConstructor = wrapsElementType.GetConstructor(Array.Empty()); + var wrappedElementProperty = wrapsElementType.GetProperty(nameof(IWrapsElement.WrappedElement)); + + if (parameterlessConstructor != null && wrappedElementProperty?.CanWrite == true) + { + var wrappedInstance = parameterlessConstructor.Invoke(Array.Empty()); + wrappedElementProperty.SetValue(wrappedInstance, webElement); + return wrappedInstance; + } + + throw new NotSupportedException($"Cannot create instance of type '{wrapsElementType.FullName}'"); + } +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/WrapsElementListProxy.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/WrapsElementListProxy.cs new file mode 100644 index 00000000..1eb83214 --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Extras/WrapsElementListProxy.cs @@ -0,0 +1,71 @@ +#nullable enable + +using System.Collections; +using OpenQA.Selenium; +using TestWare.Engines.Selenoid.Extras; + +namespace TestWare.Engines.Selenoid.Extras; + +/// +/// Represents a proxy class for a list of elements to be used with the PageFactory. +/// +internal class WrapsElementListProxy : WebDriverObjectProxy, IList where T : IWrapsElement +{ + private IList? _items; + + public WrapsElementListProxy(IElementLocator locator, IEnumerable bys, bool cache) + : base(locator, bys, cache) + { + } + + private IList Items + { + get + { + // Find elements, and wrap them in IWrapsElement instances. + // If caching enabled - use previously found elements, if any. + if (_items == null || !Cache) + { + _items = Locator + .LocateElements(Bys) + .Select(WrapsElementFactory.Wrap) + .ToList(); + } + return _items; + } + } + + #region Forwarded Items calls + + public T this[int index] + { + get { return Items[index]; } + set { Items[index] = value; } + } + + public int Count => Items.Count; + + public bool IsReadOnly => Items.IsReadOnly; + + public void Add(T item) => Items.Add(item); + + public void Clear() => Items.Clear(); + + public bool Contains(T item) => Items.Contains(item); + + public void CopyTo(T[] array, int arrayIndex) => Items.CopyTo(array, arrayIndex); + + public IEnumerator GetEnumerator() => Items.GetEnumerator(); + + public int IndexOf(T item) => Items.IndexOf(item); + + public void Insert(int index, T item) => Items.Insert(index, item); + + public bool Remove(T item) => Items.Remove(item); + + public void RemoveAt(int index) => Items.RemoveAt(index); + + IEnumerator IEnumerable.GetEnumerator() => Items.GetEnumerator(); + + #endregion +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Factory/BrowserFactory.cs b/src/Engines/TestWare.Engines.Selenoid/Factory/BrowserFactory.cs index 3e779d2d..92919939 100644 --- a/src/Engines/TestWare.Engines.Selenoid/Factory/BrowserFactory.cs +++ b/src/Engines/TestWare.Engines.Selenoid/Factory/BrowserFactory.cs @@ -1,63 +1,65 @@ -using OpenQA.Selenium.Chrome; +using OpenQA.Selenium; +using OpenQA.Selenium.Chrome; using OpenQA.Selenium.Edge; using OpenQA.Selenium.Firefox; using OpenQA.Selenium.IE; using OpenQA.Selenium.Remote; -using TestWare.Engines.Selenium.Configuration; +using TestWare.Engines.Selenoid.Configuration; namespace TestWare.Engines.Selenoid.Factory; internal static class BrowserFactory { - public static IBrowserDriver Create(Capabilities capabilities) + private static string SELENOID_OPTIONS_KEY = "selenoid:options"; + + public static IWebDriver Create(Capabilities capabilities) { - IBrowserDriver result = capabilities.GetDriver() switch + IWebDriver result = capabilities.GetDriver() switch { SupportedBrowsers.Chrome => CreateChromeDriver(capabilities), SupportedBrowsers.Firefox => CreateFirefoxDriver(capabilities), - SupportedBrowsers.InternetExplorer => CreateInternetExplorerDriver(capabilities), SupportedBrowsers.Edge => CreateEdgeDriver(capabilities), SupportedBrowsers.Invalid => throw new NotImplementedException(), _ => throw new NotSupportedException($"Browser type is invalid."), }; return result; } - private static IBrowserDriver CreateChromeDriver(Capabilities capabilities) + private static IWebDriver CreateChromeDriver(Capabilities capabilities) { ChromeOptions options = new(); - options.AddAdditionalOption("selenoid:options", GenerateSelenoidCapabilities(capabilities)); + options.AddArguments(capabilities.Arguments); + options.AddAdditionalOption(SELENOID_OPTIONS_KEY, GenerateSelenoidCapabilities(capabilities)); - return (IBrowserDriver) new RemoteWebDriver(new Uri(capabilities.Uri), options); + return new RemoteWebDriver(new Uri(capabilities.Uri), options.ToCapabilities()); } - - private static IBrowserDriver CreateFirefoxDriver(Capabilities capabilities) + + private static IWebDriver CreateFirefoxDriver(Capabilities capabilities) { FirefoxOptions options = new(); - options.AddAdditionalOption("selenoid:options", GenerateSelenoidCapabilities(capabilities)); - - return (IBrowserDriver)new RemoteWebDriver(new Uri(capabilities.Uri), options); - } - - private static IBrowserDriver CreateInternetExplorerDriver(Capabilities capabilities) - { - InternetExplorerOptions options = new(); - options.AddAdditionalOption("selenoid:options", GenerateSelenoidCapabilities(capabilities)); + options.AddArguments(capabilities.Arguments); + options.AddAdditionalOption(SELENOID_OPTIONS_KEY, GenerateSelenoidCapabilities(capabilities)); - return (IBrowserDriver)new RemoteWebDriver(new Uri(capabilities.Uri), options); + return new RemoteWebDriver(new Uri(capabilities.Uri), options.ToCapabilities()); } - private static IBrowserDriver CreateEdgeDriver(Capabilities capabilities) + private static IWebDriver CreateEdgeDriver(Capabilities capabilities) { EdgeOptions options = new(); - options.AddAdditionalOption("selenoid:options", GenerateSelenoidCapabilities(capabilities)); + options.AddArguments(capabilities.Arguments); + options.AddAdditionalOption(SELENOID_OPTIONS_KEY, GenerateSelenoidCapabilities(capabilities)); - return (IBrowserDriver)new RemoteWebDriver(new Uri(capabilities.Uri), options); + return new RemoteWebDriver(new Uri(capabilities.Uri), options.ToCapabilities()); } private static Dictionary GenerateSelenoidCapabilities(Capabilities capabilities) { return new Dictionary { + ["browserName"] = capabilities.BrowserName.ToLower(), + ["browserVersion"] = capabilities.BrowserVersion, + ["screenResolution"] = capabilities.ScreenResolution, + ["name"] = capabilities.BrowserName, + ["sessionTimeout"] = capabilities.CommandTimeOutInMinutes + "m", ["enableLog"] = capabilities.EnableLog, ["enableVnc"] = capabilities.EnableVnc, ["enableVideo"] = capabilities.EnableVideo diff --git a/src/Engines/TestWare.Engines.Selenoid/Factory/IBrowserDriver.cs b/src/Engines/TestWare.Engines.Selenoid/Factory/IBrowserDriver.cs deleted file mode 100644 index a5e333d9..00000000 --- a/src/Engines/TestWare.Engines.Selenoid/Factory/IBrowserDriver.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace TestWare.Engines.Selenoid.Factory; - -public interface IBrowserDriver : OpenQA.Selenium.IWebDriver -{ -} diff --git a/src/Engines/TestWare.Engines.Selenoid/Factory/SupportedBrowsers.cs b/src/Engines/TestWare.Engines.Selenoid/Factory/SupportedBrowsers.cs index 30affa3f..aa02787b 100644 --- a/src/Engines/TestWare.Engines.Selenoid/Factory/SupportedBrowsers.cs +++ b/src/Engines/TestWare.Engines.Selenoid/Factory/SupportedBrowsers.cs @@ -5,6 +5,5 @@ public enum SupportedBrowsers Invalid = 0, Chrome = 1, Firefox = 2, - InternetExplorer = 3, - Edge = 4 + Edge = 3 } \ No newline at end of file diff --git a/src/Engines/TestWare.Engines.Selenoid/Pages/PageBase.cs b/src/Engines/TestWare.Engines.Selenoid/Pages/PageBase.cs new file mode 100644 index 00000000..fa102502 --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Pages/PageBase.cs @@ -0,0 +1,125 @@ +using OpenQA.Selenium; +using OpenQA.Selenium.Interactions; +using OpenQA.Selenium.Support.UI; +using TestWare.Core.Libraries; +using TestWare.Engines.Selenoid.Extras; + +namespace TestWare.Engines.Selenoid.Pages; + +public abstract class PageBase +{ + protected const int TimeToWait = 15; + protected const int NumberOfRetries = 5; + + public IWebDriver Driver { get; protected set; } + + private readonly TimeSpan RetryAttemp = TimeSpan.FromMilliseconds(200); + + protected void ClickElement(IWebElement element) + { + element = element ?? throw new ArgumentNullException(nameof(element), "Element to be clicked was null"); + + RetryPolicies.ExecuteActionWithRetries( + () => + { + this.WaitUntilElementIsClickable(element); + element.Click(); + }, + numberOfRetries: NumberOfRetries, + retryAttemp: RetryAttemp); + } + + protected void ClickInnerElement(IWebElement element) + { + element = element ?? throw new ArgumentNullException(nameof(element), "Element to be clicked was null"); + + RetryPolicies.ExecuteActionWithRetries( + () => + { + this.WaitUntilElementIsClickable(element); + var action = new Actions(Driver); + action.MoveToElement(element).Click().Perform(); + }, + numberOfRetries: NumberOfRetries, + retryAttemp: RetryAttemp); + } + + protected void DoubleClickElement(IWebElement element) + { + element = element ?? throw new ArgumentNullException(nameof(element), "Element to be double clicked was null"); + + RetryPolicies.ExecuteActionWithRetries( + () => + { + this.WaitUntilElementIsClickable(element); + new Actions(Driver).DoubleClick(element).Perform(); + }, + numberOfRetries: NumberOfRetries, + retryAttemp: RetryAttemp); + } + + protected void SendKeysElement(IWebElement element, string text) + => this.SendKeysElement(element, text, TimeToWait); + + protected void SendKeysElement(IWebElement element, string text, int timeToWait) + { + element = element ?? throw new ArgumentNullException(nameof(element), "Element to send keys was null"); + + RetryPolicies.ExecuteActionWithRetries( + () => + { + this.WaitUntilElementIsClickable(element, timeToWait); + element.SendKeys(text); + }, + numberOfRetries: NumberOfRetries, + retryAttemp: RetryAttemp); + } + + protected void ClearElementText(IWebElement element) + => this.ClearElementText(element, TimeToWait); + + protected void ClearElementText(IWebElement element, int timeToWait) + { + element = element ?? throw new ArgumentNullException(nameof(element), "Element to clear was null"); + + RetryPolicies.ExecuteActionWithRetries( + () => + { + this.WaitUntilElementIsClickable(element, timeToWait); + element.Clear(); + }, + numberOfRetries: NumberOfRetries, + retryAttemp: RetryAttemp); + } + + protected void WaitUntilElementIsClickable(IWebElement element) + => this.WaitUntilElementIsClickable(element, TimeToWait); + + protected void WaitUntilElementIsClickable(IWebElement element, int timeToWait) + { + var webDriverWait = new WebDriverWait(Driver, TimeSpan.FromSeconds(timeToWait)); + webDriverWait.Until(ExpectedConditions.ElementToBeClickable(element)); + } + + protected void WaitUntilElementIsVisible(By locator) + => this.WaitUntilElementIsVisible(locator, TimeToWait); + + protected void WaitUntilElementIsVisible(By locator, int timeToWait) + { + var webDriverWait = new WebDriverWait(Driver, TimeSpan.FromSeconds(timeToWait)); + webDriverWait.Until(ExpectedConditions.VisibilityOfAllElementsLocatedBy(locator)); + } + + protected void WaitUntilElementNotVisible(By locator, int secondsToWait) + { + Thread.Sleep(1000); + var webDriverWait = new WebDriverWait(Driver, TimeSpan.FromSeconds(secondsToWait)); + webDriverWait.Until(ExpectedConditions.InvisibilityOfElementLocated(locator)); + } + + protected static void ExecuteActionWithDelay(Action action, int secondsToDelayAction) + { + Thread.Sleep(secondsToDelayAction * 1000); + action.Invoke(); + } +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Pages/WebPage.cs b/src/Engines/TestWare.Engines.Selenoid/Pages/WebPage.cs new file mode 100644 index 00000000..02c47b50 --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Pages/WebPage.cs @@ -0,0 +1,32 @@ +using OpenQA.Selenium; +using TestWare.Engines.Selenoid.Extras; +using TestWare.Engines.Selenoid.Factory; + +namespace TestWare.Engines.Selenoid.Pages; + +public abstract class WebPage : PageBase +{ + protected string Url { get; set; } + + protected WebPage(IWebDriver driver) + { + Driver = driver; + PageFactory.InitElements(Driver, this); + } + + public void NavigateToUrl() + { + Driver.Navigate().GoToUrl(new Uri(Url)); + } + + protected string AcceptDialog() + => this.AcceptDialog(TimeToWait); + + protected string AcceptDialog(int timeToWait) + { + IAlert alert = ExpectedConditions.AlertIsPresent().Invoke(Driver); + var content = alert.Text; + alert.Accept(); + return content; + } +} diff --git a/src/Engines/TestWare.Engines.Selenoid/SelenoidManager.cs b/src/Engines/TestWare.Engines.Selenoid/SelenoidManager.cs index d6105374..5cfa8691 100644 --- a/src/Engines/TestWare.Engines.Selenoid/SelenoidManager.cs +++ b/src/Engines/TestWare.Engines.Selenoid/SelenoidManager.cs @@ -1,7 +1,10 @@ -using TestWare.Core; +using Autofac; +using Autofac.Core.Registration; +using OpenQA.Selenium; +using TestWare.Core; using TestWare.Core.Configuration; using TestWare.Core.Interfaces; -using TestWare.Engines.Selenium.Configuration; +using TestWare.Engines.Selenoid.Configuration; using TestWare.Engines.Selenoid.Factory; namespace TestWare.Engines.Selenoid @@ -33,16 +36,54 @@ private static void RegisterMultiple(IEnumerable tags, TestConfiguration var driver = BrowserFactory.Create(capability); ContainerManager.RegisterType(capability.Name, driver); } - } - - public string CollectEvidence(string destinationPath, string evidenceName) - { - throw new NotImplementedException(); - } - - public void Destroy() - { - throw new NotImplementedException(); + } + + public string CollectEvidence(string destinationPath, string evidenceName) + { + var screenshotPath = string.Empty; + + IEnumerable webDrivers; + using (var scope = ContainerManager.Container.BeginLifetimeScope()) + { + webDrivers = scope.Resolve>(); + } + foreach (var webDriver in webDrivers) + { + try + { + webDriver.SwitchTo().Alert(); + // No screenshot because an Alert is present + } + catch (NoAlertPresentException) + { + var instanceName = ContainerManager.GetNameFromInstance(webDriver); + var ss = ((ITakesScreenshot)webDriver).GetScreenshot(); + ss.SaveAsFile(Path.Combine(destinationPath, $"{evidenceName} - {instanceName}.png"), ScreenshotImageFormat.Png); + } + catch (WebDriverException) { } + + } + + return screenshotPath; + } + + public void Destroy() + { + try + { + IEnumerable webDrivers; + using (var scope = ContainerManager.Container.BeginLifetimeScope()) + { + webDrivers = scope.Resolve>(); + } + + foreach (var webDriver in webDrivers) + { + webDriver.Close(); + webDriver.Dispose(); + } + } + catch (ComponentNotRegisteredException) { } } public string GetEngineName() diff --git a/src/Engines/TestWare.Engines.Selenoid/TestWare.Engines.Selenoid.csproj b/src/Engines/TestWare.Engines.Selenoid/TestWare.Engines.Selenoid.csproj index aea4f2bb..7706752c 100644 --- a/src/Engines/TestWare.Engines.Selenoid/TestWare.Engines.Selenoid.csproj +++ b/src/Engines/TestWare.Engines.Selenoid/TestWare.Engines.Selenoid.csproj @@ -7,11 +7,11 @@ - + - + From 2520580b01ed4474996ecb66d3e79c07231494d5 Mon Sep 17 00:00:00 2001 From: mg-diego Date: Fri, 18 Aug 2023 10:06:29 +0200 Subject: [PATCH 04/14] Add BrowserNameAttribute --- .../Factory/BrowserFactory.cs | 4 ++-- .../Factory/BrowserNameAttribute.cs | 23 ++++++++++++++++++ .../Factory/SupportedBrowsers.cs | 24 ++++++++++++++++--- 3 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 src/Engines/TestWare.Engines.Selenoid/Factory/BrowserNameAttribute.cs diff --git a/src/Engines/TestWare.Engines.Selenoid/Factory/BrowserFactory.cs b/src/Engines/TestWare.Engines.Selenoid/Factory/BrowserFactory.cs index 92919939..2e2ec8fa 100644 --- a/src/Engines/TestWare.Engines.Selenoid/Factory/BrowserFactory.cs +++ b/src/Engines/TestWare.Engines.Selenoid/Factory/BrowserFactory.cs @@ -2,7 +2,6 @@ using OpenQA.Selenium.Chrome; using OpenQA.Selenium.Edge; using OpenQA.Selenium.Firefox; -using OpenQA.Selenium.IE; using OpenQA.Selenium.Remote; using TestWare.Engines.Selenoid.Configuration; @@ -53,9 +52,10 @@ private static IWebDriver CreateEdgeDriver(Capabilities capabilities) private static Dictionary GenerateSelenoidCapabilities(Capabilities capabilities) { + var browser = (SupportedBrowsers)Enum.Parse(typeof(SupportedBrowsers), capabilities.BrowserName); return new Dictionary { - ["browserName"] = capabilities.BrowserName.ToLower(), + ["browserName"] = SupportedBrowsersHelper.GetBrowserName(browser), ["browserVersion"] = capabilities.BrowserVersion, ["screenResolution"] = capabilities.ScreenResolution, ["name"] = capabilities.BrowserName, diff --git a/src/Engines/TestWare.Engines.Selenoid/Factory/BrowserNameAttribute.cs b/src/Engines/TestWare.Engines.Selenoid/Factory/BrowserNameAttribute.cs new file mode 100644 index 00000000..9fb01c33 --- /dev/null +++ b/src/Engines/TestWare.Engines.Selenoid/Factory/BrowserNameAttribute.cs @@ -0,0 +1,23 @@ +namespace TestWare.Engines.Selenoid.Factory +{ + [AttributeUsage(AttributeTargets.Field)] + internal class BrowserNameAttribute : Attribute + { + private readonly string Name; + + public BrowserNameAttribute(string name) + { + this.Name = name; + } + + public static string GetPropertyName() + { + return "BrowserName"; + } + + public string GetValue() + { + return this.Name; + } + } +} diff --git a/src/Engines/TestWare.Engines.Selenoid/Factory/SupportedBrowsers.cs b/src/Engines/TestWare.Engines.Selenoid/Factory/SupportedBrowsers.cs index aa02787b..e720ab90 100644 --- a/src/Engines/TestWare.Engines.Selenoid/Factory/SupportedBrowsers.cs +++ b/src/Engines/TestWare.Engines.Selenoid/Factory/SupportedBrowsers.cs @@ -1,9 +1,27 @@ -namespace TestWare.Engines.Selenoid.Factory; +using System.Reflection; + +namespace TestWare.Engines.Selenoid.Factory; public enum SupportedBrowsers -{ +{ Invalid = 0, + [BrowserName("chrome")] Chrome = 1, + [BrowserName("firefox")] Firefox = 2, + [BrowserName("MicrosoftEdge")] Edge = 3 -} \ No newline at end of file +} + +static class SupportedBrowsersHelper +{ + public static string? GetBrowserName(this SupportedBrowsers enumValue) + { + return enumValue + .GetType() + .GetMember(enumValue.ToString()) + .First()? + .GetCustomAttribute()? + .GetValue(); + } +} From 0c347804915218d77ead638d459bd204ef0fad2c Mon Sep 17 00:00:00 2001 From: mg-diego Date: Fri, 18 Aug 2023 10:15:53 +0200 Subject: [PATCH 05/14] Update README.md --- README.md | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ea1a81e1..9227369f 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,11 @@ Testware provides capabilities to automate: Firefox IE Edge - +- **Websites** (using Selenoid) + - Supported Browsers: + Chrome + Firefox + Edge - **Mobile Applications** (using Appium) - **Windows Desktop applications** (using WinAppDriver) - **API Rest** (using Restsharp) @@ -155,6 +159,53 @@ Evidence collection: } ] }, + { + "Tag": "RemoteDriver", + "Capabilities": [ + { + "Name": "Chrome", + "Uri": "http://localhost:4444/wd/hub", + "BrowserName": "Chrome", + "BrowserVersion": "111.0", + "Resolution": "1920x1080x24", + "EnableLog": false, + "EnableVnc": true, + "EnableVideo": false, + "CommandTimeOutInMinutes": 5, + "Arguments": [ + "--start-maximized" + ] + }, + { + "Name": "Firefox", + "Uri": "http://localhost:4444/wd/hub", + "BrowserName": "Firefox", + "BrowserVersion": "110.0", + "Resolution": "1920x1080x24", + "EnableLog": false, + "EnableVnc": true, + "EnableVideo": false, + "CommandTimeOutInMinutes": 5, + "Arguments": [ + "--start-maximized" + ] + }, + { + "Name": "Edge", + "Uri": "http://localhost:4444/wd/hub", + "BrowserName": "Edge", + "BrowserVersion": "111.0", + "Resolution": "1920x1080x24", + "EnableLog": false, + "EnableVnc": true, + "EnableVideo": false, + "CommandTimeOutInMinutes": 5, + "Arguments": [ + "--start-maximized" + ] + } + ] + }, { "Name": "Appiumdriver", "AppiumUrl": "http://127.0.0.1:4723/wd/hub", From 93af9c9b65f3b9c6e8e0a60ff30734dec89737ae Mon Sep 17 00:00:00 2001 From: mg-diego Date: Fri, 18 Aug 2023 10:19:17 +0200 Subject: [PATCH 06/14] Fix edge icon url --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9227369f..731fc0eb 100644 --- a/README.md +++ b/README.md @@ -33,12 +33,12 @@ Testware provides capabilities to automate: Chrome Firefox IE - Edge + Edge - **Websites** (using Selenoid) - Supported Browsers: Chrome Firefox - Edge + Edge - **Mobile Applications** (using Appium) - **Windows Desktop applications** (using WinAppDriver) - **API Rest** (using Restsharp) From 3ef7dd6244e53a7c3b119f3bdc7aaa1d1a5d655d Mon Sep 17 00:00:00 2001 From: mg-diego Date: Sat, 19 Aug 2023 10:33:44 +0200 Subject: [PATCH 07/14] Add selenoid scripts --- .../Selenoid/browsers.json | 65 +++++++++++++++++++ .../Selenoid/docker-compose.yml | 28 ++++++++ .../Selenoid/start-selenoid-docker.ps1 | 13 ++++ 3 files changed, 106 insertions(+) create mode 100644 samples/TestWare.Samples.Selenoid.Web/Selenoid/browsers.json create mode 100644 samples/TestWare.Samples.Selenoid.Web/Selenoid/docker-compose.yml create mode 100644 samples/TestWare.Samples.Selenoid.Web/Selenoid/start-selenoid-docker.ps1 diff --git a/samples/TestWare.Samples.Selenoid.Web/Selenoid/browsers.json b/samples/TestWare.Samples.Selenoid.Web/Selenoid/browsers.json new file mode 100644 index 00000000..c4fb8d4c --- /dev/null +++ b/samples/TestWare.Samples.Selenoid.Web/Selenoid/browsers.json @@ -0,0 +1,65 @@ +{ + "chrome": { + "default": "111.0", + "versions": { + "111.0": { + "image": "selenoid/chrome:111.0", + "port": "4444", + "volumes": ["/c/temp/downloads:/home/selenium/Downloads"], + "path": "/", + "tmpfs": { + "/tmp": "size=128m" + } + }, + "94.0": { + "image": "selenoid/chrome:94.0", + "port": "4444", + "volumes": ["/c/temp/downloads:/home/selenium/Downloads"], + "path": "/", + "tmpfs": { + "/tmp": "size=128m" + } + } + } + }, + "firefox": { + "default": "110.0", + "versions": { + "110.0": { + "image": "selenoid/firefox:110.0", + "port": "4444", + "volumes": ["/c/temp/downloads:/home/selenium/Downloads"], + "path": "/wd/hub", + "tmpfs": { + "/tmp": "size=128m" + } + }, + "92.0": { + "image": "selenoid/firefox:92.0", + "port": "4444", + "volumes": ["/c/temp/downloads:/home/selenium/Downloads"], + "path": "/wd/hub", + "tmpfs": { + "/tmp": "size=128m" + } + } + } + }, + "MicrosoftEdge": { + "default": "111.0", + "versions": { + "111.0": { + "image": "browsers/edge:111.0", + "port": "4444", + "volumes": ["/c/temp/downloads:/home/selenium/Downloads"], + "path": "/" + }, + "94.0": { + "image": "browsers/edge:94.0", + "port": "4444", + "volumes": ["/c/temp/downloads:/home/selenium/Downloads"], + "path": "/" + } + } + } +} diff --git a/samples/TestWare.Samples.Selenoid.Web/Selenoid/docker-compose.yml b/samples/TestWare.Samples.Selenoid.Web/Selenoid/docker-compose.yml new file mode 100644 index 00000000..9daccaf3 --- /dev/null +++ b/samples/TestWare.Samples.Selenoid.Web/Selenoid/docker-compose.yml @@ -0,0 +1,28 @@ +version: '3' +services: + selenoid: + restart: always + network_mode: bridge + image: aerokube/selenoid:latest-release + volumes: + - ".:/etc/selenoid" + - "/var/run/docker.sock:/var/run/docker.sock" + - "./video:/opt/selenoid/video" + - "./logs:/opt/selenoid/logs" + environment: + - OVERRIDE_VIDEO_OUTPUT_DIR=./logs + command: ["-conf", "/etc/selenoid/browsers.json", "-video-output-dir", "/opt/selenoid/video", "-log-output-dir", "/opt/selenoid/logs"] + ports: + - "4444:4444" + + selenoid-ui: + restart: always + image: "aerokube/selenoid-ui:latest-release" + network_mode: bridge + depends_on: + - selenoid + links: + - selenoid + ports: + - "8888:8080" + command: ["--selenoid-uri", "http://selenoid:4444"] \ No newline at end of file diff --git a/samples/TestWare.Samples.Selenoid.Web/Selenoid/start-selenoid-docker.ps1 b/samples/TestWare.Samples.Selenoid.Web/Selenoid/start-selenoid-docker.ps1 new file mode 100644 index 00000000..ef53fd45 --- /dev/null +++ b/samples/TestWare.Samples.Selenoid.Web/Selenoid/start-selenoid-docker.ps1 @@ -0,0 +1,13 @@ +Write-Host "Stopping selenoid and selenoid-ui containers" -ForegroundColor DarkGreen +docker-compose down + +Write-Host "Downloading browser images...." -ForegroundColor DarkGreen +$browsers = Get-Content .\browsers.json | ConvertFrom-Json +foreach($browser in $browsers.PsObject.Properties.Value) { + foreach($version in $browser.versions.PsObject.Properties.Value) { + docker pull $version.image + } +} + +Write-Host "Starting selenoid and selenoid-ui containers" -ForegroundColor DarkGreen +docker-compose up -d \ No newline at end of file From c8345276ab19be1e68daab4ae11279d87d5e44f0 Mon Sep 17 00:00:00 2001 From: mg-diego Date: Fri, 6 Oct 2023 18:30:23 +0200 Subject: [PATCH 08/14] Solve merge conflict --- .../Configuration/Capabilities.cs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/Engines/TestWare.Engines.Selenium/Configuration/Capabilities.cs b/src/Engines/TestWare.Engines.Selenium/Configuration/Capabilities.cs index 27a4b729..5c01917f 100644 --- a/src/Engines/TestWare.Engines.Selenium/Configuration/Capabilities.cs +++ b/src/Engines/TestWare.Engines.Selenium/Configuration/Capabilities.cs @@ -4,17 +4,6 @@ namespace TestWare.Engines.Selenium.Configuration; internal class Capabilities { - public string Name { get; set; } - - public string Path { get; set; } - - public string Driver { get; set; } - public int CommandTimeOutInMinutes { get; set; } - -<<<<<<< HEAD -======= -internal class Capabilities -{ public string Name { get; set; } public string Path { get; set; } @@ -24,7 +13,7 @@ internal class Capabilities public string BaseUrl { get; set; } public int CommandTimeOutInMinutes { get; set; } ->>>>>>> main + public IEnumerable Arguments { get; set; } = Enumerable.Empty(); public SupportedBrowsers GetDriver() From e120df1cf2e2be3e74340bfd282a4a0fc31f00c9 Mon Sep 17 00:00:00 2001 From: mg-diego Date: Fri, 6 Oct 2023 18:41:13 +0200 Subject: [PATCH 09/14] Solve PR comments --- .../Factory/BrowserFactory.cs | 11 ++++++++++- .../TestWare.Engines.Selenoid/Pages/PageBase.cs | 6 +++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Engines/TestWare.Engines.Selenoid/Factory/BrowserFactory.cs b/src/Engines/TestWare.Engines.Selenoid/Factory/BrowserFactory.cs index 2e2ec8fa..22e87359 100644 --- a/src/Engines/TestWare.Engines.Selenoid/Factory/BrowserFactory.cs +++ b/src/Engines/TestWare.Engines.Selenoid/Factory/BrowserFactory.cs @@ -29,6 +29,7 @@ private static IWebDriver CreateChromeDriver(Capabilities capabilities) options.AddArguments(capabilities.Arguments); options.AddAdditionalOption(SELENOID_OPTIONS_KEY, GenerateSelenoidCapabilities(capabilities)); + if (capabilities.Uri == null) throw new ArgumentNullException(nameof(capabilities.Uri)); return new RemoteWebDriver(new Uri(capabilities.Uri), options.ToCapabilities()); } @@ -38,6 +39,7 @@ private static IWebDriver CreateFirefoxDriver(Capabilities capabilities) options.AddArguments(capabilities.Arguments); options.AddAdditionalOption(SELENOID_OPTIONS_KEY, GenerateSelenoidCapabilities(capabilities)); + if (capabilities.Uri == null) throw new ArgumentNullException(nameof(capabilities.Uri)); return new RemoteWebDriver(new Uri(capabilities.Uri), options.ToCapabilities()); } @@ -47,15 +49,22 @@ private static IWebDriver CreateEdgeDriver(Capabilities capabilities) options.AddArguments(capabilities.Arguments); options.AddAdditionalOption(SELENOID_OPTIONS_KEY, GenerateSelenoidCapabilities(capabilities)); + if (capabilities.Uri == null) throw new ArgumentNullException(nameof(capabilities.Uri)); return new RemoteWebDriver(new Uri(capabilities.Uri), options.ToCapabilities()); } private static Dictionary GenerateSelenoidCapabilities(Capabilities capabilities) { + if (capabilities.BrowserName == null || capabilities.BrowserVersion == null || capabilities.ScreenResolution == null) + { + throw new ArgumentNullException(nameof(capabilities)); + } var browser = (SupportedBrowsers)Enum.Parse(typeof(SupportedBrowsers), capabilities.BrowserName); + var supportedBrowser = SupportedBrowsersHelper.GetBrowserName(browser) ?? throw new ArgumentNullException(nameof(browser)); + return new Dictionary { - ["browserName"] = SupportedBrowsersHelper.GetBrowserName(browser), + ["browserName"] = supportedBrowser, ["browserVersion"] = capabilities.BrowserVersion, ["screenResolution"] = capabilities.ScreenResolution, ["name"] = capabilities.BrowserName, diff --git a/src/Engines/TestWare.Engines.Selenoid/Pages/PageBase.cs b/src/Engines/TestWare.Engines.Selenoid/Pages/PageBase.cs index fa102502..2199ae4d 100644 --- a/src/Engines/TestWare.Engines.Selenoid/Pages/PageBase.cs +++ b/src/Engines/TestWare.Engines.Selenoid/Pages/PageBase.cs @@ -11,7 +11,7 @@ public abstract class PageBase protected const int TimeToWait = 15; protected const int NumberOfRetries = 5; - public IWebDriver Driver { get; protected set; } + public IWebDriver? Driver { get; protected set; } private readonly TimeSpan RetryAttemp = TimeSpan.FromMilliseconds(200); @@ -32,6 +32,7 @@ protected void ClickElement(IWebElement element) protected void ClickInnerElement(IWebElement element) { element = element ?? throw new ArgumentNullException(nameof(element), "Element to be clicked was null"); + if (Driver == null) throw new ArgumentNullException(nameof(Driver)); RetryPolicies.ExecuteActionWithRetries( () => @@ -97,6 +98,7 @@ protected void WaitUntilElementIsClickable(IWebElement element) protected void WaitUntilElementIsClickable(IWebElement element, int timeToWait) { + if (Driver == null) throw new ArgumentNullException(nameof(Driver)); var webDriverWait = new WebDriverWait(Driver, TimeSpan.FromSeconds(timeToWait)); webDriverWait.Until(ExpectedConditions.ElementToBeClickable(element)); } @@ -106,12 +108,14 @@ protected void WaitUntilElementIsVisible(By locator) protected void WaitUntilElementIsVisible(By locator, int timeToWait) { + if (Driver == null) throw new ArgumentNullException(nameof(Driver)); var webDriverWait = new WebDriverWait(Driver, TimeSpan.FromSeconds(timeToWait)); webDriverWait.Until(ExpectedConditions.VisibilityOfAllElementsLocatedBy(locator)); } protected void WaitUntilElementNotVisible(By locator, int secondsToWait) { + if (Driver == null) throw new ArgumentNullException(nameof(Driver)); Thread.Sleep(1000); var webDriverWait = new WebDriverWait(Driver, TimeSpan.FromSeconds(secondsToWait)); webDriverWait.Until(ExpectedConditions.InvisibilityOfElementLocated(locator)); From da0a0bbeebac62d1f009224514ceaad61e09c7ec Mon Sep 17 00:00:00 2001 From: mg-diego Date: Fri, 6 Oct 2023 18:47:13 +0200 Subject: [PATCH 10/14] Solve PR comments --- src/Engines/TestWare.Engines.Selenoid/Pages/WebPage.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Engines/TestWare.Engines.Selenoid/Pages/WebPage.cs b/src/Engines/TestWare.Engines.Selenoid/Pages/WebPage.cs index a0dd7fb0..f04d6d70 100644 --- a/src/Engines/TestWare.Engines.Selenoid/Pages/WebPage.cs +++ b/src/Engines/TestWare.Engines.Selenoid/Pages/WebPage.cs @@ -15,10 +15,8 @@ protected WebPage(IWebDriver driver) public void NavigateToUrl() { - if (Url == null) { - throw new NullReferenceException("Url variable was null"); - } - Driver.Navigate().GoToUrl(new Uri(Url)); + if (Url == null) throw new NullReferenceException("Url variable was null"); + Driver?.Navigate().GoToUrl(new Uri(Url)); } protected string AcceptDialog() @@ -26,6 +24,7 @@ protected string AcceptDialog() protected string AcceptDialog(int timeToWait) { + if (Driver == null) throw new NullReferenceException("Driver variable was null"); IAlert alert = ExpectedConditions.AlertIsPresent().Invoke(Driver); var content = alert.Text; alert.Accept(); From 9525f8b2da627860cc229e928c1192e82ece44ca Mon Sep 17 00:00:00 2001 From: Diego Martinez Date: Thu, 23 May 2024 15:00:22 +0200 Subject: [PATCH 11/14] Fix conflicts --- TestWare.sln | 13 +- .../TestWare.Samples.MongoDB.csproj | 2 +- .../TestWare.Engines.Appium/AppiumManager.cs | 2 +- .../Extras/AbstractFindsByAttribute.cs | 2 +- .../TestWare.Engines.Common}/Extras/ByAll.cs | 2 +- .../Extras/ByChained.cs | 2 +- .../Extras/ByFactory.cs | 2 +- .../Extras/ByIdOrName.cs | 2 +- .../Extras/CacheLookupAttribute.cs | 2 +- .../Extras/DefaultElementLocator.cs | 2 +- .../DefaultPageObjectMemberDecorator.cs | 2 +- .../Extras/ExpectedConditions.cs | 4 +- .../Extras/FindsByAllAttribute.cs | 2 +- .../Extras/FindsByAttribute.cs | 2 +- .../Extras/FindsBySequenceAttribute.cs | 2 +- .../TestWare.Engines.Common}/Extras/How.cs | 2 +- .../Extras/IElementLocator.cs | 2 +- .../Extras/IPageObjectMemberDecorator.cs | 2 +- .../Extras/MemberBuilders/IMemberBuilder.cs | 1 + .../MemberBuilders/WebElementBuilder.cs | 1 + .../MemberBuilders/WebElementListBuilder.cs | 0 .../MemberBuilders/WrappedElementBuilder.cs | 1 + .../WrappedElementListBuilder.cs | 1 + .../Extras/PageFactory.cs | 2 +- .../Extras/RetryingElementLocator.cs | 2 +- .../Extras/WebDriverObjectProxy.cs | 2 +- .../Extras/WebElementEnumerable.cs | 2 +- .../Extras/WebElementListProxy.cs | 2 +- .../Extras/WebElementProxy.cs | 5 +- .../Extras/WrapsElementFactory.cs | 2 +- .../Extras/WrapsElementListProxy.cs | 2 +- .../TestWare.Engines.Common.csproj | 14 + .../TestWare.Engines.Selenium/Extras/ByAll.cs | 109 ---- .../Extras/ByChained.cs | 111 ---- .../Extras/ByFactory.cs | 64 -- .../Extras/DefaultElementLocator.cs | 79 --- .../TestWare.Engines.Selenium/Extras/How.cs | 53 -- .../Extras/IElementLocator.cs | 35 - .../Extras/PageFactory.cs | 202 ------ .../Extras/WebElementProxy.cs | 96 --- .../SeleniumManager.cs | 2 +- .../TestWare.Engines.Selenium.csproj | 3 +- .../Extras/AbstractFindsByAttribute.cs | 166 ----- .../Extras/ByIdOrName.cs | 72 --- .../Extras/CacheLookupAttribute.cs | 10 - .../DefaultPageObjectMemberDecorator.cs | 154 ----- .../Extras/ExpectedConditions.cs | 602 ------------------ .../Extras/FindsByAllAttribute.cs | 30 - .../Extras/FindsByAttribute.cs | 94 --- .../Extras/FindsBySequenceAttribute.cs | 30 - .../Extras/IPageObjectMemberDecorator.cs | 22 - .../Extras/MemberBuilders/IMemberBuilder.cs | 34 - .../MemberBuilders/WebElementBuilder.cs | 41 -- .../MemberBuilders/WebElementListBuilder.cs | 23 - .../MemberBuilders/WrappedElementBuilder.cs | 21 - .../WrappedElementListBuilder.cs | 25 - .../Extras/RetryingElementLocator.cs | 133 ---- .../Extras/WebDriverObjectProxy.cs | 38 -- .../Extras/WebElementEnumerable.cs | 28 - .../Extras/WebElementListProxy.cs | 65 -- .../Extras/WrapsElementFactory.cs | 35 - .../Extras/WrapsElementListProxy.cs | 71 --- .../Pages/PageBase.cs | 2 +- .../Pages/WebPage.cs | 2 +- .../TestWare.Engines.Selenoid.csproj | 3 +- 65 files changed, 61 insertions(+), 2480 deletions(-) rename src/Engines/{TestWare.Engines.Selenium => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/AbstractFindsByAttribute.cs (99%) rename src/Engines/{TestWare.Engines.Selenoid => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/ByAll.cs (98%) rename src/Engines/{TestWare.Engines.Selenoid => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/ByChained.cs (98%) rename src/Engines/{TestWare.Engines.Selenoid => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/ByFactory.cs (98%) rename src/Engines/{TestWare.Engines.Selenium => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/ByIdOrName.cs (98%) rename src/Engines/{TestWare.Engines.Selenium => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/CacheLookupAttribute.cs (87%) rename src/Engines/{TestWare.Engines.Selenoid => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/DefaultElementLocator.cs (98%) rename src/Engines/{TestWare.Engines.Selenium => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/DefaultPageObjectMemberDecorator.cs (99%) rename src/Engines/{TestWare.Engines.Selenium => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/ExpectedConditions.cs (99%) rename src/Engines/{TestWare.Engines.Selenium => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/FindsByAllAttribute.cs (95%) rename src/Engines/{TestWare.Engines.Selenium => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/FindsByAttribute.cs (98%) rename src/Engines/{TestWare.Engines.Selenium => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/FindsBySequenceAttribute.cs (96%) rename src/Engines/{TestWare.Engines.Selenoid => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/How.cs (96%) rename src/Engines/{TestWare.Engines.Selenoid => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/IElementLocator.cs (96%) rename src/Engines/{TestWare.Engines.Selenium => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/IPageObjectMemberDecorator.cs (94%) rename src/Engines/{TestWare.Engines.Selenium => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/MemberBuilders/IMemberBuilder.cs (97%) rename src/Engines/{TestWare.Engines.Selenium => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/MemberBuilders/WebElementBuilder.cs (97%) rename src/Engines/{TestWare.Engines.Selenium => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/MemberBuilders/WebElementListBuilder.cs (100%) rename src/Engines/{TestWare.Engines.Selenium => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/MemberBuilders/WrappedElementBuilder.cs (94%) rename src/Engines/{TestWare.Engines.Selenium => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/MemberBuilders/WrappedElementListBuilder.cs (96%) rename src/Engines/{TestWare.Engines.Selenoid => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/PageFactory.cs (99%) rename src/Engines/{TestWare.Engines.Selenium => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/RetryingElementLocator.cs (99%) rename src/Engines/{TestWare.Engines.Selenium => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/WebDriverObjectProxy.cs (96%) rename src/Engines/{TestWare.Engines.Selenium => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/WebElementEnumerable.cs (96%) rename src/Engines/{TestWare.Engines.Selenium => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/WebElementListProxy.cs (97%) rename src/Engines/{TestWare.Engines.Selenoid => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/WebElementProxy.cs (94%) rename src/Engines/{TestWare.Engines.Selenium => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/WrapsElementFactory.cs (96%) rename src/Engines/{TestWare.Engines.Selenium => TestWare.Engines.Common/TestWare.Engines.Common}/Extras/WrapsElementListProxy.cs (97%) create mode 100644 src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/TestWare.Engines.Common.csproj delete mode 100644 src/Engines/TestWare.Engines.Selenium/Extras/ByAll.cs delete mode 100644 src/Engines/TestWare.Engines.Selenium/Extras/ByChained.cs delete mode 100644 src/Engines/TestWare.Engines.Selenium/Extras/ByFactory.cs delete mode 100644 src/Engines/TestWare.Engines.Selenium/Extras/DefaultElementLocator.cs delete mode 100644 src/Engines/TestWare.Engines.Selenium/Extras/How.cs delete mode 100644 src/Engines/TestWare.Engines.Selenium/Extras/IElementLocator.cs delete mode 100644 src/Engines/TestWare.Engines.Selenium/Extras/PageFactory.cs delete mode 100644 src/Engines/TestWare.Engines.Selenium/Extras/WebElementProxy.cs delete mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/AbstractFindsByAttribute.cs delete mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/ByIdOrName.cs delete mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/CacheLookupAttribute.cs delete mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/DefaultPageObjectMemberDecorator.cs delete mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/ExpectedConditions.cs delete mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/FindsByAllAttribute.cs delete mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/FindsByAttribute.cs delete mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/FindsBySequenceAttribute.cs delete mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/IPageObjectMemberDecorator.cs delete mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/IMemberBuilder.cs delete mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WebElementBuilder.cs delete mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WebElementListBuilder.cs delete mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WrappedElementBuilder.cs delete mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WrappedElementListBuilder.cs delete mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/RetryingElementLocator.cs delete mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/WebDriverObjectProxy.cs delete mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/WebElementEnumerable.cs delete mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/WebElementListProxy.cs delete mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/WrapsElementFactory.cs delete mode 100644 src/Engines/TestWare.Engines.Selenoid/Extras/WrapsElementListProxy.cs diff --git a/TestWare.sln b/TestWare.sln index 94e8bd89..56ee7b42 100644 --- a/TestWare.sln +++ b/TestWare.sln @@ -40,13 +40,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Reporting", "Reporting", "{ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestWare.Engines.Selenoid", "src\Engines\TestWare.Engines.Selenoid\TestWare.Engines.Selenoid.csproj", "{0B7E3DFE-AB55-4C0C-81C6-6A6F517475EE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestWare.Samples.Selenoid.Web", "samples\TestWare.Samples.Selenoid.Web\TestWare.Samples.Selenoid.Web.csproj", "{672C5D48-DD50-4AA3-8974-50B95746EA07}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestWare.Samples.Selenoid.Web", "samples\TestWare.Samples.Selenoid.Web\TestWare.Samples.Selenoid.Web.csproj", "{672C5D48-DD50-4AA3-8974-50B95746EA07}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestWare.Engines.MongoDB", "src\Engines\TestWare.Engines.MongoDB\TestWare.Engines.MongoDB.csproj", "{4DFC2BE5-D3EF-4F39-AAD4-B8213DE92A11}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestWare.Samples.MongoDB", "samples\TestWare.Samples.MongoDB\TestWare.Samples.MongoDB.csproj", "{5378DE68-675E-440D-AAA9-7D3AF8AA680E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestWare.Samples.MongoDB", "samples\TestWare.Samples.MongoDB\TestWare.Samples.MongoDB.csproj", "{5378DE68-675E-440D-AAA9-7D3AF8AA680E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestWare.AllureReport", "src\Core\TestWare.AllureReport\TestWare.AllureReport.csproj", "{91F2B723-A68B-4711-969B-89897B3C6161}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestWare.AllureReport", "src\Core\TestWare.AllureReport\TestWare.AllureReport.csproj", "{91F2B723-A68B-4711-969B-89897B3C6161}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestWare.Engines.Common", "src\Engines\TestWare.Engines.Common\TestWare.Engines.Common\TestWare.Engines.Common.csproj", "{FFD6A200-D2F1-4CBE-8FDC-320C7839AAB9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -114,6 +116,10 @@ Global {91F2B723-A68B-4711-969B-89897B3C6161}.Debug|Any CPU.Build.0 = Debug|Any CPU {91F2B723-A68B-4711-969B-89897B3C6161}.Release|Any CPU.ActiveCfg = Release|Any CPU {91F2B723-A68B-4711-969B-89897B3C6161}.Release|Any CPU.Build.0 = Release|Any CPU + {FFD6A200-D2F1-4CBE-8FDC-320C7839AAB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FFD6A200-D2F1-4CBE-8FDC-320C7839AAB9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FFD6A200-D2F1-4CBE-8FDC-320C7839AAB9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FFD6A200-D2F1-4CBE-8FDC-320C7839AAB9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -137,6 +143,7 @@ Global {4DFC2BE5-D3EF-4F39-AAD4-B8213DE92A11} = {A2E4E1F7-CB61-48DC-AA38-A74F7DC74CA2} {5378DE68-675E-440D-AAA9-7D3AF8AA680E} = {58B1446D-98A3-46A2-A668-305F0170B39F} {91F2B723-A68B-4711-969B-89897B3C6161} = {DCCEF363-0EBE-46EA-B02B-CD59010626F6} + {FFD6A200-D2F1-4CBE-8FDC-320C7839AAB9} = {A2E4E1F7-CB61-48DC-AA38-A74F7DC74CA2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4F88CB24-23C8-4E0E-8B83-29023C1642B8} diff --git a/samples/TestWare.Samples.MongoDB/TestWare.Samples.MongoDB.csproj b/samples/TestWare.Samples.MongoDB/TestWare.Samples.MongoDB.csproj index 5f7c44b4..4319e2de 100644 --- a/samples/TestWare.Samples.MongoDB/TestWare.Samples.MongoDB.csproj +++ b/samples/TestWare.Samples.MongoDB/TestWare.Samples.MongoDB.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/Engines/TestWare.Engines.Appium/AppiumManager.cs b/src/Engines/TestWare.Engines.Appium/AppiumManager.cs index b044a99a..637b7b25 100644 --- a/src/Engines/TestWare.Engines.Appium/AppiumManager.cs +++ b/src/Engines/TestWare.Engines.Appium/AppiumManager.cs @@ -68,7 +68,7 @@ public string CollectEvidence(string destinationPath, string evidenceName) } var screenshot = ((ITakesScreenshot)appiumDriver).GetScreenshot(); - screenshot.SaveAsFile(Path.Combine(destinationPath, $"{evidenceName}.png"), ScreenshotImageFormat.Png); + screenshot.SaveAsFile(Path.Combine(destinationPath, $"{evidenceName}.png")); } catch { diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/AbstractFindsByAttribute.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/AbstractFindsByAttribute.cs similarity index 99% rename from src/Engines/TestWare.Engines.Selenium/Extras/AbstractFindsByAttribute.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/AbstractFindsByAttribute.cs index 0f1f81da..828a5701 100644 --- a/src/Engines/TestWare.Engines.Selenium/Extras/AbstractFindsByAttribute.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/AbstractFindsByAttribute.cs @@ -1,7 +1,7 @@ using System.ComponentModel; using OpenQA.Selenium; -namespace TestWare.Engines.Selenium.Extras; +namespace TestWare.Engines.Common.Extras; /// /// Base class for attributes to mark elements with methods by which to find a corresponding element on the page. diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/ByAll.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/ByAll.cs similarity index 98% rename from src/Engines/TestWare.Engines.Selenoid/Extras/ByAll.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/ByAll.cs index 8316c44a..2790fa5c 100644 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/ByAll.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/ByAll.cs @@ -5,7 +5,7 @@ using System.Text; using OpenQA.Selenium; -namespace TestWare.Engines.Selenoid.Extras; +namespace TestWare.Engines.Common.Extras; /// /// Mechanism used to locate elements within a document using a series of lookups. This class will diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/ByChained.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/ByChained.cs similarity index 98% rename from src/Engines/TestWare.Engines.Selenoid/Extras/ByChained.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/ByChained.cs index a7dd1a72..e3acc07e 100644 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/ByChained.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/ByChained.cs @@ -5,7 +5,7 @@ using System.Text; using OpenQA.Selenium; -namespace TestWare.Engines.Selenoid.Extras; +namespace TestWare.Engines.Common.Extras; /// /// Mechanism used to locate elements within a document using a series of other lookups. This class diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/ByFactory.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/ByFactory.cs similarity index 98% rename from src/Engines/TestWare.Engines.Selenoid/Extras/ByFactory.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/ByFactory.cs index b37ad2fb..b2f2c9f7 100644 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/ByFactory.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/ByFactory.cs @@ -3,7 +3,7 @@ using System.Reflection; using OpenQA.Selenium; -namespace TestWare.Engines.Selenoid.Extras; +namespace TestWare.Engines.Common.Extras; /// /// Provides instances of the object to the attributes. diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/ByIdOrName.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/ByIdOrName.cs similarity index 98% rename from src/Engines/TestWare.Engines.Selenium/Extras/ByIdOrName.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/ByIdOrName.cs index 36e875e1..9d2b2343 100644 --- a/src/Engines/TestWare.Engines.Selenium/Extras/ByIdOrName.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/ByIdOrName.cs @@ -3,7 +3,7 @@ using System.Globalization; using OpenQA.Selenium; -namespace TestWare.Engines.Selenium.Extras; +namespace TestWare.Engines.Common.Extras; /// /// Finds element when the id or the name attribute has the specified value. diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/CacheLookupAttribute.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/CacheLookupAttribute.cs similarity index 87% rename from src/Engines/TestWare.Engines.Selenium/Extras/CacheLookupAttribute.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/CacheLookupAttribute.cs index 95e1a73f..07c5edf0 100644 --- a/src/Engines/TestWare.Engines.Selenium/Extras/CacheLookupAttribute.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/CacheLookupAttribute.cs @@ -1,5 +1,5 @@  -namespace TestWare.Engines.Selenium.Extras; +namespace TestWare.Engines.Common.Extras; /// /// Marks the element so that lookups to the browser page are cached. This class cannot be inherited. diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/DefaultElementLocator.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/DefaultElementLocator.cs similarity index 98% rename from src/Engines/TestWare.Engines.Selenoid/Extras/DefaultElementLocator.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/DefaultElementLocator.cs index 33e735a6..d422341d 100644 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/DefaultElementLocator.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/DefaultElementLocator.cs @@ -3,7 +3,7 @@ using System.Collections.ObjectModel; using OpenQA.Selenium; -namespace TestWare.Engines.Selenoid.Extras; +namespace TestWare.Engines.Common.Extras; /// /// A default locator for elements for use with the . This locator diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/DefaultPageObjectMemberDecorator.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/DefaultPageObjectMemberDecorator.cs similarity index 99% rename from src/Engines/TestWare.Engines.Selenium/Extras/DefaultPageObjectMemberDecorator.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/DefaultPageObjectMemberDecorator.cs index 7fd57faf..4ab22a84 100644 --- a/src/Engines/TestWare.Engines.Selenium/Extras/DefaultPageObjectMemberDecorator.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/DefaultPageObjectMemberDecorator.cs @@ -5,7 +5,7 @@ using OpenQA.Selenium; using TestWare.Engines.Selenium.Extras.MemberBuilders; -namespace TestWare.Engines.Selenium.Extras; +namespace TestWare.Engines.Common.Extras; /// /// Default decorator determining how members of a class which represent elements in a Page Object diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/ExpectedConditions.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/ExpectedConditions.cs similarity index 99% rename from src/Engines/TestWare.Engines.Selenium/Extras/ExpectedConditions.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/ExpectedConditions.cs index 7a80173a..040bfa97 100644 --- a/src/Engines/TestWare.Engines.Selenium/Extras/ExpectedConditions.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/ExpectedConditions.cs @@ -3,7 +3,7 @@ using System.Text.RegularExpressions; using OpenQA.Selenium; -namespace TestWare.Engines.Selenium.Extras; +namespace TestWare.Engines.Common.Extras; /// /// Supplies a set of common conditions that can be waited for using . @@ -73,7 +73,7 @@ public static Func UrlMatches(string regex) return (driver) => { var currentUrl = driver.Url; - var pattern = new Regex(regex, RegexOptions.IgnoreCase); + var pattern = new Regex(regex, RegexOptions.IgnoreCase, TimeSpan.FromSeconds(10)); var match = pattern.Match(currentUrl); return match.Success; }; diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/FindsByAllAttribute.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/FindsByAllAttribute.cs similarity index 95% rename from src/Engines/TestWare.Engines.Selenium/Extras/FindsByAllAttribute.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/FindsByAllAttribute.cs index 168bb0f2..bd88807c 100644 --- a/src/Engines/TestWare.Engines.Selenium/Extras/FindsByAllAttribute.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/FindsByAllAttribute.cs @@ -1,5 +1,5 @@  -namespace TestWare.Engines.Selenium.Extras; +namespace TestWare.Engines.Common.Extras; /// /// Marks elements to indicate that found elements should match the criteria of diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/FindsByAttribute.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/FindsByAttribute.cs similarity index 98% rename from src/Engines/TestWare.Engines.Selenium/Extras/FindsByAttribute.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/FindsByAttribute.cs index 8853dc6e..cb3467b8 100644 --- a/src/Engines/TestWare.Engines.Selenium/Extras/FindsByAttribute.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/FindsByAttribute.cs @@ -3,7 +3,7 @@ using System.ComponentModel; using OpenQA.Selenium; -namespace TestWare.Engines.Selenium.Extras; +namespace TestWare.Engines.Common.Extras; /// /// Marks program elements with methods by which to find a corresponding element on the page. Used diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/FindsBySequenceAttribute.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/FindsBySequenceAttribute.cs similarity index 96% rename from src/Engines/TestWare.Engines.Selenium/Extras/FindsBySequenceAttribute.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/FindsBySequenceAttribute.cs index 92831391..ed1b14c0 100644 --- a/src/Engines/TestWare.Engines.Selenium/Extras/FindsBySequenceAttribute.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/FindsBySequenceAttribute.cs @@ -1,6 +1,6 @@  -namespace TestWare.Engines.Selenium.Extras; +namespace TestWare.Engines.Common.Extras; /// /// Marks elements to indicate that each on the field or diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/How.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/How.cs similarity index 96% rename from src/Engines/TestWare.Engines.Selenoid/Extras/How.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/How.cs index 25e59507..e3034062 100644 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/How.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/How.cs @@ -1,5 +1,5 @@  -namespace TestWare.Engines.Selenoid.Extras; +namespace TestWare.Engines.Common.Extras; /// /// Provides the lookup methods for the FindsBy attribute (for using in PageObjects) diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/IElementLocator.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/IElementLocator.cs similarity index 96% rename from src/Engines/TestWare.Engines.Selenoid/Extras/IElementLocator.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/IElementLocator.cs index 446e6b69..8fe81455 100644 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/IElementLocator.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/IElementLocator.cs @@ -2,7 +2,7 @@ using System.Collections.ObjectModel; using OpenQA.Selenium; -namespace TestWare.Engines.Selenoid.Extras; +namespace TestWare.Engines.Common.Extras; /// /// Interface describing how elements are to be located by a . diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/IPageObjectMemberDecorator.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/IPageObjectMemberDecorator.cs similarity index 94% rename from src/Engines/TestWare.Engines.Selenium/Extras/IPageObjectMemberDecorator.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/IPageObjectMemberDecorator.cs index 9c45442d..14e3246b 100644 --- a/src/Engines/TestWare.Engines.Selenium/Extras/IPageObjectMemberDecorator.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/IPageObjectMemberDecorator.cs @@ -2,7 +2,7 @@ using System.Reflection; -namespace TestWare.Engines.Selenium.Extras; +namespace TestWare.Engines.Common.Extras; /// /// Interface describing how members of a class which represent elements in a Page Object diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/MemberBuilders/IMemberBuilder.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/MemberBuilders/IMemberBuilder.cs similarity index 97% rename from src/Engines/TestWare.Engines.Selenium/Extras/MemberBuilders/IMemberBuilder.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/MemberBuilders/IMemberBuilder.cs index 59ad7312..fd0678ea 100644 --- a/src/Engines/TestWare.Engines.Selenium/Extras/MemberBuilders/IMemberBuilder.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/MemberBuilders/IMemberBuilder.cs @@ -18,6 +18,7 @@ using System.Diagnostics.CodeAnalysis; using OpenQA.Selenium; +using TestWare.Engines.Common.Extras; namespace TestWare.Engines.Selenium.Extras.MemberBuilders; diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/MemberBuilders/WebElementBuilder.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/MemberBuilders/WebElementBuilder.cs similarity index 97% rename from src/Engines/TestWare.Engines.Selenium/Extras/MemberBuilders/WebElementBuilder.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/MemberBuilders/WebElementBuilder.cs index 770b365c..e6e6d462 100644 --- a/src/Engines/TestWare.Engines.Selenium/Extras/MemberBuilders/WebElementBuilder.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/MemberBuilders/WebElementBuilder.cs @@ -18,6 +18,7 @@ using System.Diagnostics.CodeAnalysis; using OpenQA.Selenium; +using TestWare.Engines.Common.Extras; namespace TestWare.Engines.Selenium.Extras.MemberBuilders; diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/MemberBuilders/WebElementListBuilder.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/MemberBuilders/WebElementListBuilder.cs similarity index 100% rename from src/Engines/TestWare.Engines.Selenium/Extras/MemberBuilders/WebElementListBuilder.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/MemberBuilders/WebElementListBuilder.cs diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/MemberBuilders/WrappedElementBuilder.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/MemberBuilders/WrappedElementBuilder.cs similarity index 94% rename from src/Engines/TestWare.Engines.Selenium/Extras/MemberBuilders/WrappedElementBuilder.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/MemberBuilders/WrappedElementBuilder.cs index 406245ea..750aa20e 100644 --- a/src/Engines/TestWare.Engines.Selenium/Extras/MemberBuilders/WrappedElementBuilder.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/MemberBuilders/WrappedElementBuilder.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using OpenQA.Selenium; +using TestWare.Engines.Common.Extras; namespace TestWare.Engines.Selenium.Extras.MemberBuilders; diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/MemberBuilders/WrappedElementListBuilder.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/MemberBuilders/WrappedElementListBuilder.cs similarity index 96% rename from src/Engines/TestWare.Engines.Selenium/Extras/MemberBuilders/WrappedElementListBuilder.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/MemberBuilders/WrappedElementListBuilder.cs index 8b9f088c..175bfa9a 100644 --- a/src/Engines/TestWare.Engines.Selenium/Extras/MemberBuilders/WrappedElementListBuilder.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/MemberBuilders/WrappedElementListBuilder.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using OpenQA.Selenium; +using TestWare.Engines.Common.Extras; namespace TestWare.Engines.Selenium.Extras.MemberBuilders; diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/PageFactory.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/PageFactory.cs similarity index 99% rename from src/Engines/TestWare.Engines.Selenoid/Extras/PageFactory.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/PageFactory.cs index be394019..4fc46dfb 100644 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/PageFactory.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/PageFactory.cs @@ -3,7 +3,7 @@ using System.Reflection; using OpenQA.Selenium; -namespace TestWare.Engines.Selenoid.Extras; +namespace TestWare.Engines.Common.Extras; /// /// Provides the ability to produce Page Objects modeling a page. This class cannot be inherited. diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/RetryingElementLocator.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/RetryingElementLocator.cs similarity index 99% rename from src/Engines/TestWare.Engines.Selenium/Extras/RetryingElementLocator.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/RetryingElementLocator.cs index 3acbf4a1..86859b52 100644 --- a/src/Engines/TestWare.Engines.Selenium/Extras/RetryingElementLocator.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/RetryingElementLocator.cs @@ -3,7 +3,7 @@ using System.Collections.ObjectModel; using OpenQA.Selenium; -namespace TestWare.Engines.Selenium.Extras; +namespace TestWare.Engines.Common.Extras; /// /// A locator for elements for use with the that retries locating diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/WebDriverObjectProxy.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/WebDriverObjectProxy.cs similarity index 96% rename from src/Engines/TestWare.Engines.Selenium/Extras/WebDriverObjectProxy.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/WebDriverObjectProxy.cs index 30885e29..28f46984 100644 --- a/src/Engines/TestWare.Engines.Selenium/Extras/WebDriverObjectProxy.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/WebDriverObjectProxy.cs @@ -1,6 +1,6 @@ using OpenQA.Selenium; -namespace TestWare.Engines.Selenium.Extras; +namespace TestWare.Engines.Common.Extras; /// /// Represents a base proxy class for objects used with the PageFactory. diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/WebElementEnumerable.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/WebElementEnumerable.cs similarity index 96% rename from src/Engines/TestWare.Engines.Selenium/Extras/WebElementEnumerable.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/WebElementEnumerable.cs index 0e27ede9..2841bf49 100644 --- a/src/Engines/TestWare.Engines.Selenium/Extras/WebElementEnumerable.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/WebElementEnumerable.cs @@ -1,6 +1,6 @@ using TestWare.Engines.Selenium.Extras; -namespace TestWare.Engines.Selenium.Extras; +namespace TestWare.Engines.Common.Extras; /// /// Due to Linq optimized execution in dotnet core for IList, some methods lead to multiple elements retrieval. diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/WebElementListProxy.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/WebElementListProxy.cs similarity index 97% rename from src/Engines/TestWare.Engines.Selenium/Extras/WebElementListProxy.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/WebElementListProxy.cs index 9d3376ba..92834ab6 100644 --- a/src/Engines/TestWare.Engines.Selenium/Extras/WebElementListProxy.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/WebElementListProxy.cs @@ -3,7 +3,7 @@ using System.Collections; using OpenQA.Selenium; -namespace TestWare.Engines.Selenium.Extras; +namespace TestWare.Engines.Common.Extras; /// /// Represents a proxy class for a list of elements to be used with the PageFactory. diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/WebElementProxy.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/WebElementProxy.cs similarity index 94% rename from src/Engines/TestWare.Engines.Selenoid/Extras/WebElementProxy.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/WebElementProxy.cs index 96ec06b8..5c71fde4 100644 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/WebElementProxy.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/WebElementProxy.cs @@ -5,7 +5,7 @@ using OpenQA.Selenium.Interactions.Internal; using OpenQA.Selenium.Internal; -namespace TestWare.Engines.Selenoid.Extras; +namespace TestWare.Engines.Common.Extras; /// /// Intercepts the request to a single @@ -63,9 +63,6 @@ public IWebElement WrappedElement public string GetCssValue(string propertyName) => WrappedElement.GetCssValue(propertyName); - [Obsolete ("Deprecated on IWebElement")] - public string GetProperty(string propertyName) => WrappedElement.GetProperty(propertyName); - public void SendKeys(string text) => WrappedElement.SendKeys(text); public void Submit() => WrappedElement.Submit(); diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/WrapsElementFactory.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/WrapsElementFactory.cs similarity index 96% rename from src/Engines/TestWare.Engines.Selenium/Extras/WrapsElementFactory.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/WrapsElementFactory.cs index 55906ade..5a663dbd 100644 --- a/src/Engines/TestWare.Engines.Selenium/Extras/WrapsElementFactory.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/WrapsElementFactory.cs @@ -1,6 +1,6 @@ using OpenQA.Selenium; -namespace TestWare.Engines.Selenium.Extras; +namespace TestWare.Engines.Common.Extras; internal static class WrapsElementFactory { diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/WrapsElementListProxy.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/WrapsElementListProxy.cs similarity index 97% rename from src/Engines/TestWare.Engines.Selenium/Extras/WrapsElementListProxy.cs rename to src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/WrapsElementListProxy.cs index 8e9cc2e3..fc9736d0 100644 --- a/src/Engines/TestWare.Engines.Selenium/Extras/WrapsElementListProxy.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/WrapsElementListProxy.cs @@ -4,7 +4,7 @@ using OpenQA.Selenium; using TestWare.Engines.Selenium.Extras; -namespace TestWare.Engines.Selenium.Extras; +namespace TestWare.Engines.Common.Extras; /// /// Represents a proxy class for a list of elements to be used with the PageFactory. diff --git a/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/TestWare.Engines.Common.csproj b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/TestWare.Engines.Common.csproj new file mode 100644 index 00000000..bec319ca --- /dev/null +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/TestWare.Engines.Common.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + + + + + + + + diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/ByAll.cs b/src/Engines/TestWare.Engines.Selenium/Extras/ByAll.cs deleted file mode 100644 index 7dda4a55..00000000 --- a/src/Engines/TestWare.Engines.Selenium/Extras/ByAll.cs +++ /dev/null @@ -1,109 +0,0 @@ -#nullable enable - -using System.Collections.ObjectModel; -using System.Globalization; -using System.Text; -using OpenQA.Selenium; - -namespace TestWare.Engines.Selenium.Extras; - -/// -/// Mechanism used to locate elements within a document using a series of lookups. This class will -/// find all DOM elements that matches all of the locators in sequence, e.g. -/// -/// -/// The following code will find all elements that match by1 and then all elements that also match by2. -/// -/// driver.findElements(new ByAll(by1, by2)) -/// -/// This means that the list of elements returned may not be in document order. -/// > -public class ByAll : By -{ - private readonly By[] bys; - - /// - /// Initializes a new instance of the class with one or more objects. - /// - /// One or more references - public ByAll(params By[] bys) - { - this.bys = bys; - } - - /// - /// Find a single element. - /// - /// Context used to find the element. - /// The element that matches - public override IWebElement FindElement(ISearchContext context) - { - var elements = this.FindElements(context); - if (elements.Count == 0) - { - throw new NoSuchElementException("Cannot locate an element using " + this.ToString()); - } - - return elements[0]; - } - - /// - /// Finds many elements - /// - /// Context used to find the element. - /// A readonly collection of elements that match. - public override ReadOnlyCollection FindElements(ISearchContext context) - { - if (this.bys.Length == 0) - { - return new List().AsReadOnly(); - } - - IEnumerable? elements = null; - foreach (By by in this.bys) - { - ReadOnlyCollection foundElements = by.FindElements(context); - if (foundElements.Count == 0) - { - // Optimization: If at any time a find returns no elements, the - // only possible result for find-all is an empty collection. - return new List().AsReadOnly(); - } - - if (elements == null) - { - elements = foundElements; - } - else - { - elements = elements.Intersect(by.FindElements(context)); - } - } - if (elements == null) - { - elements = new List().AsReadOnly(); - } - - return elements.ToList().AsReadOnly(); - } - - /// - /// Writes out a comma separated list of the objects used in the chain. - /// - /// Converts the value of this instance to a - public override string ToString() - { - StringBuilder stringBuilder = new StringBuilder(); - foreach (By by in this.bys) - { - if (stringBuilder.Length > 0) - { - stringBuilder.Append(','); - } - - stringBuilder.Append(by); - } - - return string.Format(CultureInfo.InvariantCulture, "By.All([{0}])", stringBuilder.ToString()); - } -} diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/ByChained.cs b/src/Engines/TestWare.Engines.Selenium/Extras/ByChained.cs deleted file mode 100644 index d302c713..00000000 --- a/src/Engines/TestWare.Engines.Selenium/Extras/ByChained.cs +++ /dev/null @@ -1,111 +0,0 @@ -#nullable enable - -using System.Collections.ObjectModel; -using System.Globalization; -using System.Text; -using OpenQA.Selenium; - -namespace TestWare.Engines.Selenium.Extras; - -/// -/// Mechanism used to locate elements within a document using a series of other lookups. This class -/// will find all DOM elements that matches each of the locators in sequence -/// -/// -/// The following code will will find all elements that match by2 and appear under an element that matches -/// by1. -/// -/// driver.findElements(new ByChained(by1, by2)) -/// -/// -public class ByChained : By -{ - private readonly By[] bys; - - /// - /// Initializes a new instance of the class with one or more objects. - /// - /// One or more references - public ByChained(params By[] bys) - { - this.bys = bys; - } - - /// - /// Find a single element. - /// - /// Context used to find the element. - /// The element that matches - public override IWebElement FindElement(ISearchContext context) - { - ReadOnlyCollection elements = this.FindElements(context); - if (elements.Count == 0) - { - throw new NoSuchElementException("Cannot locate an element using " + this.ToString()); - } - - return elements[0]; - } - - /// - /// Finds many elements - /// - /// Context used to find the element. - /// A readonly collection of elements that match. - public override ReadOnlyCollection FindElements(ISearchContext context) - { - if (this.bys.Length == 0) - { - return new List().AsReadOnly(); - } - - List? elems = null; - foreach (By by in this.bys) - { - List newElems = new List(); - - if (elems == null) - { - newElems.AddRange(by.FindElements(context)); - } - else - { - foreach (IWebElement elem in elems) - { - try - { - newElems.AddRange(elem.FindElements(by)); - } - catch (StaleElementReferenceException) - { - // Elements has gone stale during search. - } - } - } - - elems = newElems; - } - - return elems!.AsReadOnly(); - } - - /// - /// Writes out a comma separated list of the objects used in the chain. - /// - /// Converts the value of this instance to a - public override string ToString() - { - StringBuilder stringBuilder = new StringBuilder(); - foreach (By by in this.bys) - { - if (stringBuilder.Length > 0) - { - stringBuilder.Append(','); - } - - stringBuilder.Append(by); - } - - return string.Format(CultureInfo.InvariantCulture, "By.Chained([{0}])", stringBuilder.ToString()); - } -} diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/ByFactory.cs b/src/Engines/TestWare.Engines.Selenium/Extras/ByFactory.cs deleted file mode 100644 index 3cda7f11..00000000 --- a/src/Engines/TestWare.Engines.Selenium/Extras/ByFactory.cs +++ /dev/null @@ -1,64 +0,0 @@ -#nullable enable -using System.Globalization; -using System.Reflection; -using OpenQA.Selenium; - -namespace TestWare.Engines.Selenium.Extras; - -/// -/// Provides instances of the object to the attributes. -/// -internal static class ByFactory -{ - /// - /// Gets an instance of the class based on the specified attribute. - /// - /// The describing how to find the element. - /// An instance of the class. - public static By From(FindsByAttribute attribute) - { - var how = attribute.How; - var usingValue = attribute.Using; - switch (how) - { - case How.Id: - return By.Id(usingValue); - case How.Name: - return By.Name(usingValue); - case How.TagName: - return By.TagName(usingValue); - case How.ClassName: - return By.ClassName(usingValue); - case How.CssSelector: - return By.CssSelector(usingValue); - case How.LinkText: - return By.LinkText(usingValue); - case How.PartialLinkText: - return By.PartialLinkText(usingValue); - case How.XPath: - return By.XPath(usingValue); - case How.Custom: - if (attribute.CustomFinderType == null) - { - throw new ArgumentException("Cannot use How.Custom without supplying a custom finder type"); - } - - if (!attribute.CustomFinderType.IsSubclassOf(typeof(By))) - { - throw new ArgumentException("Custom finder type must be a descendent of the By class"); - } - - ConstructorInfo? ctor = attribute.CustomFinderType.GetConstructor(new Type[] { typeof(string) }); - if (ctor == null) - { - throw new ArgumentException("Custom finder type must expose a public constructor with a string argument"); - } - - By finder = (By)ctor.Invoke(new object?[] { usingValue }); - - return finder; - } - - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Did not know how to construct How from how {0}, using {1}", how, usingValue)); - } -} diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/DefaultElementLocator.cs b/src/Engines/TestWare.Engines.Selenium/Extras/DefaultElementLocator.cs deleted file mode 100644 index cb9cd5fe..00000000 --- a/src/Engines/TestWare.Engines.Selenium/Extras/DefaultElementLocator.cs +++ /dev/null @@ -1,79 +0,0 @@ -#nullable enable - -using System.Collections.ObjectModel; -using OpenQA.Selenium; - -namespace TestWare.Engines.Selenium.Extras; - -/// -/// A default locator for elements for use with the . This locator -/// implements no retry logic for elements not being found, nor for elements being stale. -/// -public class DefaultElementLocator : IElementLocator -{ - - /// - /// Initializes a new instance of the class. - /// - /// The used by this locator - /// to locate elements. - public DefaultElementLocator(ISearchContext searchContext) - { - this.SearchContext = searchContext; - } - - /// - /// Gets the to be used in locating elements. - /// - public ISearchContext SearchContext { get; } - - /// - /// Locates an element using the given list of criteria. - /// - /// The list of methods by which to search for the element. - /// An which is the first match under the desired criteria. - public IWebElement LocateElement(IEnumerable bys) - { - if (bys == null) - { - throw new ArgumentNullException(nameof(bys), "List of criteria may not be null"); - } - - string? errorString = null; - foreach (var by in bys) - { - try - { - return this.SearchContext.FindElement(by); - } - catch (NoSuchElementException) - { - errorString = (errorString == null ? "Could not find element by: " : errorString + ", or: ") + by; - } - } - - throw new NoSuchElementException(errorString); - } - - /// - /// Locates a list of elements using the given list of criteria. - /// - /// The list of methods by which to search for the elements. - /// A list of all elements which match the desired criteria. - public ReadOnlyCollection LocateElements(IEnumerable bys) - { - if (bys == null) - { - throw new ArgumentNullException(nameof(bys), "List of criteria may not be null"); - } - - List collection = new List(); - foreach (var by in bys) - { - ReadOnlyCollection list = this.SearchContext.FindElements(by); - collection.AddRange(list); - } - - return collection.AsReadOnly(); - } -} diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/How.cs b/src/Engines/TestWare.Engines.Selenium/Extras/How.cs deleted file mode 100644 index 4ff44fce..00000000 --- a/src/Engines/TestWare.Engines.Selenium/Extras/How.cs +++ /dev/null @@ -1,53 +0,0 @@ - -namespace TestWare.Engines.Selenium.Extras; - -/// -/// Provides the lookup methods for the FindsBy attribute (for using in PageObjects) -/// -public enum How -{ - /// - /// Finds by - /// - Id, - - /// - /// Finds by - /// - Name, - - /// - /// Finds by - /// - TagName, - - /// - /// Finds by - /// - ClassName, - - /// - /// Finds by - /// - CssSelector, - - /// - /// Finds by - /// - LinkText, - - /// - /// Finds by - /// - PartialLinkText, - - /// - /// Finds by - /// - XPath, - - /// - /// Finds by a custom implementation. - /// - Custom -} diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/IElementLocator.cs b/src/Engines/TestWare.Engines.Selenium/Extras/IElementLocator.cs deleted file mode 100644 index bbd210b5..00000000 --- a/src/Engines/TestWare.Engines.Selenium/Extras/IElementLocator.cs +++ /dev/null @@ -1,35 +0,0 @@ - -using System.Collections.ObjectModel; -using OpenQA.Selenium; - -namespace TestWare.Engines.Selenium.Extras; - -/// -/// Interface describing how elements are to be located by a . -/// -/// -/// A locator must always contain a way to retrieve the to -/// use in locating elements. In practice, this will usually be implemented by passing -/// the context in via a constructor. -/// -public interface IElementLocator -{ - /// - /// Gets the to be used in locating elements. - /// - ISearchContext SearchContext { get; } - - /// - /// Locates an element using the given list of criteria. - /// - /// The list of methods by which to search for the element. - /// An which is the first match under the desired criteria. - IWebElement LocateElement(IEnumerable bys); - - /// - /// Locates a list of elements using the given list of criteria. - /// - /// The list of methods by which to search for the elements. - /// A list of all elements which match the desired criteria. - ReadOnlyCollection LocateElements(IEnumerable bys); -} diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/PageFactory.cs b/src/Engines/TestWare.Engines.Selenium/Extras/PageFactory.cs deleted file mode 100644 index 391fda02..00000000 --- a/src/Engines/TestWare.Engines.Selenium/Extras/PageFactory.cs +++ /dev/null @@ -1,202 +0,0 @@ -#nullable enable - -using System.Reflection; -using OpenQA.Selenium; - -namespace TestWare.Engines.Selenium.Extras; - -/// -/// Provides the ability to produce Page Objects modeling a page. This class cannot be inherited. -/// -public class PageFactory -{ - /// - /// Initializes a new instance of the class. - /// Private constructor prevents a default instance from being created. - /// - protected PageFactory() - { - } - - /// - /// Initializes the elements in the Page Object with the given type. - /// - /// The of the Page Object class. - /// The instance used to populate the page. - /// An instance of the Page Object class with the elements initialized. - /// - /// The class used in the argument must have a public constructor - /// that takes a single argument of type . This helps to enforce - /// best practices of the Page Object pattern, and encapsulates the driver into the Page - /// Object so that it can have no external WebDriver dependencies. - /// - /// - /// thrown if no constructor to the class can be found with a single IWebDriver argument - /// -or- - /// if a field or property decorated with the is not of type - /// or IList{IWebElement}. - /// - public static T InitElements(IWebDriver driver) - { - return InitElements(new DefaultElementLocator(driver)); - } - - /// - /// Initializes the elements in the Page Object with the given type. - /// - /// The of the Page Object class. - /// The implementation that - /// determines how elements are located. - /// An instance of the Page Object class with the elements initialized. - /// - /// The class used in the argument must have a public constructor - /// that takes a single argument of type . This helps to enforce - /// best practices of the Page Object pattern, and encapsulates the driver into the Page - /// Object so that it can have no external WebDriver dependencies. - /// - /// - /// thrown if no constructor to the class can be found with a single IWebDriver argument - /// -or- - /// if a field or property decorated with the is not of type - /// or IList{IWebElement}. - /// - public static T InitElements(IElementLocator locator) - { - Type pageClassType = typeof(T); - ConstructorInfo? ctor = pageClassType.GetConstructor(new Type[] { typeof(IWebDriver) }); - if (ctor == null) - { - throw new ArgumentException("No constructor for the specified class containing a single argument of type IWebDriver can be found"); - } - - if (locator == null) - { - throw new ArgumentNullException(nameof(locator), "locator cannot be null"); - } - - if (!(locator.SearchContext is IWebDriver driver)) - { - throw new ArgumentException("The search context of the element locator must implement IWebDriver", nameof(locator)); - } - - var page = (T)ctor.Invoke(new object[] { driver }); - InitElements(page, locator); - return page; - } - - /// - /// Initializes the elements in the Page Object. - /// - /// The driver used to find elements on the page. - /// The Page Object to be populated with elements. - /// - /// thrown if a field or property decorated with the is not of type - /// or IList{IWebElement}. - /// - public static void InitElements(ISearchContext driver, object page) - { - InitElements(page, new DefaultElementLocator(driver)); - } - - /// - /// Initializes the elements in the Page Object. - /// - /// The driver used to find elements on the page. - /// The Page Object to be populated with elements. - /// The implementation that - /// determines how Page Object members representing elements are discovered and populated. - /// - /// thrown if a field or property decorated with the is not of type - /// or IList{IWebElement}. - /// - public static void InitElements(ISearchContext driver, object page, IPageObjectMemberDecorator decorator) - { - InitElements(page, new DefaultElementLocator(driver), decorator); - } - - /// - /// Initializes the elements in the Page Object. - /// - /// The Page Object to be populated with elements. - /// The implementation that - /// determines how elements are located. - /// - /// thrown if a field or property decorated with the is not of type - /// or IList{IWebElement}. - /// - public static void InitElements(object page, IElementLocator locator) - { - InitElements(page, locator, new DefaultPageObjectMemberDecorator()); - } - - /// - /// Initializes the elements in the Page Object. - /// - /// The Page Object to be populated with elements. - /// The implementation that - /// determines how elements are located. - /// The implementation that - /// determines how Page Object members representing elements are discovered and populated. - /// - /// thrown if a field or property decorated with the is not of type - /// or IList{IWebElement}. - /// - public static void InitElements(object page, IElementLocator locator, IPageObjectMemberDecorator decorator) - { - if (page == null) - { - throw new ArgumentNullException(nameof(page), "page cannot be null"); - } - - if (locator == null) - { - throw new ArgumentNullException(nameof(locator), "locator cannot be null"); - } - - if (decorator == null) - { - throw new ArgumentNullException(nameof(locator), "decorator cannot be null"); - } - - if (locator.SearchContext == null) - { - throw new ArgumentException("The SearchContext of the locator object cannot be null", nameof(locator)); - } - - const BindingFlags PublicBindingOptions = BindingFlags.Instance | BindingFlags.Public; - const BindingFlags NonPublicBindingOptions = BindingFlags.Instance | BindingFlags.NonPublic; - - // Get a list of all of the fields and properties (public and non-public [private, protected, etc.]) - // in the passed-in page object. Note that we walk the inheritance tree to get superclass members. - Type? type = page.GetType(); - var members = new List(); - members.AddRange(type.GetFields(PublicBindingOptions)); - members.AddRange(type.GetProperties(PublicBindingOptions)); - while (type != null) - { - members.AddRange(type.GetFields(NonPublicBindingOptions)); - members.AddRange(type.GetProperties(NonPublicBindingOptions)); - type = type.BaseType; - } - - foreach (var member in members) - { - // Examine each member, and if the decorator returns a non-null object, - // set the value of that member to the decorated object. - object? decoratedValue = decorator.Decorate(member, locator); - if (decoratedValue == null) - { - continue; - } - - if (member is FieldInfo field) - { - field.SetValue(page, decoratedValue); - } - else if (member is PropertyInfo property && property.CanWrite) - { - property.SetValue(page, decoratedValue, null); - } - } - } -} diff --git a/src/Engines/TestWare.Engines.Selenium/Extras/WebElementProxy.cs b/src/Engines/TestWare.Engines.Selenium/Extras/WebElementProxy.cs deleted file mode 100644 index 3448a478..00000000 --- a/src/Engines/TestWare.Engines.Selenium/Extras/WebElementProxy.cs +++ /dev/null @@ -1,96 +0,0 @@ -#nullable enable -using System.Collections.ObjectModel; -using System.Drawing; -using OpenQA.Selenium; -using OpenQA.Selenium.Interactions.Internal; -using OpenQA.Selenium.Internal; - -namespace TestWare.Engines.Selenium.Extras; - -/// -/// Intercepts the request to a single -/// -internal class WebElementProxy : WebDriverObjectProxy, IWrapsElement, IWebElement, ILocatable, IFindsElement -{ - private IWebElement? cachedElement; - - public WebElementProxy(IElementLocator locator, IEnumerable bys, bool cache) - : base(locator, bys, cache) - { - } - - /// - /// Gets the IWebElement object this proxy represents, returning a cached one if requested. - /// - public IWebElement WrappedElement - { - get - { - if (!this.Cache || this.cachedElement == null) - { - this.cachedElement = this.Locator.LocateElement(this.Bys); - } - - return this.cachedElement; - } - } - - #region Forwarded WrappedElement calls - - public string TagName => WrappedElement.TagName; - - public string Text => WrappedElement.Text; - - public bool Enabled => WrappedElement.Enabled; - - public bool Selected => WrappedElement.Selected; - - public Point Location => WrappedElement.Location; - - public Size Size => WrappedElement.Size; - - public bool Displayed => WrappedElement.Displayed; - - public void Clear() => WrappedElement.Clear(); - - public void Click() => WrappedElement.Click(); - - public IWebElement FindElement(By by) => WrappedElement.FindElement(by); - - public ReadOnlyCollection FindElements(By by) => WrappedElement.FindElements(by); - - public string GetAttribute(string attributeName) => WrappedElement.GetAttribute(attributeName); - - public string GetCssValue(string propertyName) => WrappedElement.GetCssValue(propertyName); - - [Obsolete ("Deprecated on IWebElement")] - public string GetProperty(string propertyName) => WrappedElement.GetProperty(propertyName); - - public void SendKeys(string text) => WrappedElement.SendKeys(text); - - public void Submit() => WrappedElement.Submit(); - - public Point LocationOnScreenOnceScrolledIntoView - => ((ILocatable)WrappedElement).LocationOnScreenOnceScrolledIntoView; - - public ICoordinates Coordinates - => ((ILocatable)WrappedElement).Coordinates; - - public override int GetHashCode() => WrappedElement.GetHashCode(); - - public override bool Equals(object? obj) => WrappedElement.Equals(obj); - - public IWebElement FindElement(string mechanism, string value) - => ((IFindsElement)WrappedElement).FindElement(mechanism, value); - - public ReadOnlyCollection FindElements(string mechanism, string value) - => ((IFindsElement)WrappedElement).FindElements(mechanism, value); - - public string GetDomAttribute(string attributeName) => WrappedElement.GetDomAttribute(attributeName); - - public string GetDomProperty(string propertyName) => WrappedElement.GetDomProperty(propertyName); - - public ISearchContext GetShadowRoot() => WrappedElement.GetShadowRoot(); - - #endregion Forwarded WrappedElement calls -} diff --git a/src/Engines/TestWare.Engines.Selenium/SeleniumManager.cs b/src/Engines/TestWare.Engines.Selenium/SeleniumManager.cs index 082e6029..164bfbb1 100644 --- a/src/Engines/TestWare.Engines.Selenium/SeleniumManager.cs +++ b/src/Engines/TestWare.Engines.Selenium/SeleniumManager.cs @@ -94,7 +94,7 @@ public string CollectEvidence(string destinationPath, string evidenceName) { var instanceName = ContainerManager.GetNameFromInstance(webDriver); var ss = ((ITakesScreenshot)webDriver).GetScreenshot(); - ss.SaveAsFile(Path.Combine(destinationPath, $"{evidenceName} - {instanceName}.png"), ScreenshotImageFormat.Png); + ss.SaveAsFile(Path.Combine(destinationPath, $"{evidenceName} - {instanceName}.png")); } catch (WebDriverException) { } diff --git a/src/Engines/TestWare.Engines.Selenium/TestWare.Engines.Selenium.csproj b/src/Engines/TestWare.Engines.Selenium/TestWare.Engines.Selenium.csproj index 36441f58..c0914bb9 100644 --- a/src/Engines/TestWare.Engines.Selenium/TestWare.Engines.Selenium.csproj +++ b/src/Engines/TestWare.Engines.Selenium/TestWare.Engines.Selenium.csproj @@ -7,11 +7,12 @@ - + + diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/AbstractFindsByAttribute.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/AbstractFindsByAttribute.cs deleted file mode 100644 index cb7f20f2..00000000 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/AbstractFindsByAttribute.cs +++ /dev/null @@ -1,166 +0,0 @@ -using System.ComponentModel; -using OpenQA.Selenium; - -namespace TestWare.Engines.Selenoid.Extras; - -/// -/// Base class for attributes to mark elements with methods by which to find a corresponding element on the page. -/// In order to define custom FindsBy attribute, inherit from this class and implement Finder property. -/// -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] -public abstract class AbstractFindsByAttribute : Attribute, IComparable -{ - /// - /// Gets an explicit object to find by. - /// - public abstract By Finder { get; } - - /// - /// Gets or sets a value indicating where this attribute should be evaluated relative to other instances - /// of this attribute decorating the same class member. - /// - [DefaultValue(0)] - public int Priority { get; set; } - - /// - /// Determines if two instances are equal. - /// - /// One instance to compare. - /// The other instance to compare. - /// if the two instances are equal; otherwise, . - public static bool operator ==(AbstractFindsByAttribute one, AbstractFindsByAttribute two) - { - // If both are null, or both are same instance, return true. - if (ReferenceEquals(one, two)) - { - return true; - } - - // If one is null, but not both, return false. - if (one is null || two is null) - { - return false; - } - - return one.Equals(two); - } - - /// - /// Determines if two instances are unequal. - /// s - /// One instance to compare. - /// The other instance to compare. - /// if the two instances are not equal; otherwise, . - public static bool operator !=(AbstractFindsByAttribute one, AbstractFindsByAttribute two) - { - return !(one == two); - } - - /// - /// Determines if one instance is greater than another. - /// - /// One instance to compare. - /// The other instance to compare. - /// if the first instance is greater than the second; otherwise, . - - public static bool operator >(AbstractFindsByAttribute one, AbstractFindsByAttribute two) - { - return one.CompareTo(two) > 0; - } - - public static bool operator >=(AbstractFindsByAttribute one, AbstractFindsByAttribute two) - { - return one.CompareTo(two) >= 0; - } - - public static bool operator <=(AbstractFindsByAttribute one, AbstractFindsByAttribute two) - { - return one.CompareTo(two) <= 0; - } - - /// - /// Determines if one instance is less than another. - /// - /// One instance to compare. - /// The other instance to compare. - /// if the first instance is less than the second; otherwise, . - public static bool operator <(AbstractFindsByAttribute one, AbstractFindsByAttribute two) - { - return one.CompareTo(two) < 0; - } - - /// - /// Compares the current instance with another object of the same type and returns an - /// integer that indicates whether the current instance precedes, follows, or occurs - /// in the same position in the sort order as the other object. - /// - /// An object to compare with this instance. - /// A value that indicates the relative order of the objects being compared. The return value has these meanings: - /// - /// ValueMeaning - /// Less than zeroThis instance precedes in the sort order. - /// ZeroThis instance occurs in the same position in the sort order as . - /// Greater than zeroThis instance follows in the sort order. - /// - /// - public int CompareTo(object obj) - { - if (obj == null) - { - throw new ArgumentNullException(nameof(obj), "Object to compare cannot be null"); - } - - AbstractFindsByAttribute? other = obj as AbstractFindsByAttribute ?? throw new ArgumentException("Object to compare must be a AbstractFindsByAttribute", nameof(obj)); - - if (Priority != other.Priority) - { - return Priority - other.Priority; - } - - return 0; - } - - /// - /// Determines whether the specified Object is equal - /// to the current Object. - /// - /// The Object to compare with the - /// current Object. - /// if the specified Object - /// is equal to the current Object; otherwise, - /// . - public override bool Equals(object obj) - { - if (obj == null) - { - return false; - } - - AbstractFindsByAttribute? other = obj as AbstractFindsByAttribute; - if (other == null) - { - return false; - } - - if (other.Priority != Priority) - { - return false; - } - - if (other.Finder != Finder) - { - return false; - } - - return true; - } - - /// - /// Serves as a hash function for a particular type. - /// - /// A hash code for the current Object. - public override int GetHashCode() - { - return Finder.GetHashCode(); - } -} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/ByIdOrName.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/ByIdOrName.cs deleted file mode 100644 index 1816e508..00000000 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/ByIdOrName.cs +++ /dev/null @@ -1,72 +0,0 @@ - -using System.Collections.ObjectModel; -using System.Globalization; -using OpenQA.Selenium; - -namespace TestWare.Engines.Selenoid.Extras; - -/// -/// Finds element when the id or the name attribute has the specified value. -/// -public class ByIdOrName : By -{ - private readonly string elementIdentifier = string.Empty; - private readonly By idFinder; - private readonly By nameFinder; - - /// - /// Initializes a new instance of the class. - /// - /// The ID or Name to use in finding the element. - public ByIdOrName(string elementIdentifier) - { - if (string.IsNullOrEmpty(elementIdentifier)) - { - throw new ArgumentException("element identifier cannot be null or the empty string", nameof(elementIdentifier)); - } - - this.elementIdentifier = elementIdentifier; - this.idFinder = By.Id(this.elementIdentifier); - this.nameFinder = By.Name(this.elementIdentifier); - } - - /// - /// Find a single element. - /// - /// Context used to find the element. - /// The element that matches - public override IWebElement FindElement(ISearchContext context) - { - try - { - return this.idFinder.FindElement(context); - } - catch (NoSuchElementException) - { - return this.nameFinder.FindElement(context); - } - } - - /// - /// Finds many elements - /// - /// Context used to find the element. - /// A readonly collection of elements that match. - public override ReadOnlyCollection FindElements(ISearchContext context) - { - List elements = new List(); - elements.AddRange(this.idFinder.FindElements(context)); - elements.AddRange(this.nameFinder.FindElements(context)); - - return elements.AsReadOnly(); - } - - /// - /// Writes out a description of this By object. - /// - /// Converts the value of this instance to a - public override string ToString() - { - return string.Format(CultureInfo.InvariantCulture, "ByIdOrName([{0}])", this.elementIdentifier); - } -} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/CacheLookupAttribute.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/CacheLookupAttribute.cs deleted file mode 100644 index 7d9be172..00000000 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/CacheLookupAttribute.cs +++ /dev/null @@ -1,10 +0,0 @@ - -namespace TestWare.Engines.Selenoid.Extras; - -/// -/// Marks the element so that lookups to the browser page are cached. This class cannot be inherited. -/// -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = false)] -public sealed class CacheLookupAttribute : Attribute -{ -} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/DefaultPageObjectMemberDecorator.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/DefaultPageObjectMemberDecorator.cs deleted file mode 100644 index 78dd05fe..00000000 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/DefaultPageObjectMemberDecorator.cs +++ /dev/null @@ -1,154 +0,0 @@ -#nullable enable - -using System.Collections.ObjectModel; -using System.Reflection; -using OpenQA.Selenium; -using TestWare.Engines.Selenoid.Extras.MemberBuilders; - -namespace TestWare.Engines.Selenoid.Extras; - -/// -/// Default decorator determining how members of a class which represent elements in a Page Object -/// are detected. -/// -public class DefaultPageObjectMemberDecorator : IPageObjectMemberDecorator -{ - private static readonly List _memberBuilders = new List { - new WebElementBuilder(), - new WebElementListBuilder(), - new WrappedElementBuilder(), - new WrappedElementListBuilder() - }; - - /// - /// Locates an element or list of elements for a Page Object member, and returns a - /// proxy object for the element or list of elements. - /// - /// The containing information about - /// a class's member. - /// The used to locate elements. - /// A transparent proxy to the WebDriver element object. - public virtual object? Decorate(MemberInfo member, IElementLocator locator) - { - FieldInfo? field = member as FieldInfo; - PropertyInfo? property = member as PropertyInfo; - - Type? targetType = null; - if (field != null) - { - targetType = field.FieldType; - } - - if (property != null && property.CanWrite) - { - targetType = property.PropertyType; - } - - if (targetType == null) - { - return null; - } - - IList bys = CreateLocatorList(member); - if (bys.Count > 0) - { - bool cache = ShouldCacheLookup(member); - return CreateObject(targetType, locator, bys, cache); - } - - return null; - } - - public virtual object CreateObject(Type memberType, IElementLocator locator, IEnumerable bys, bool cache) - { - foreach (var builder in _memberBuilders) - { - if (builder.CreateObject(memberType, locator, bys, cache, out object? createdObject)) - { - return createdObject; - } - } - - throw new ArgumentException($"Type of member '{memberType.Name}' is not IWebElement or IList"); - } - - /// - /// Determines whether lookups on this member should be cached. - /// - /// The containing information about - /// the member of the Page Object class. - /// if lookups are to be cached; otherwise, . - protected static bool ShouldCacheLookup(MemberInfo member) - { - if (member == null) - { - throw new ArgumentNullException(nameof(member), "member cannot be null"); - } - - var cacheAttributeType = typeof(CacheLookupAttribute); - bool cache = member.GetCustomAttributes(cacheAttributeType, true).Length > 0 - || member.DeclaringType?.GetCustomAttributes(cacheAttributeType, true).Length > 0; - - return cache; - } - - /// - /// Creates a list of locators based on the attributes of this member. - /// - /// The containing information about - /// the member of the Page Object class. - /// A list of locators based on the attributes of this member. - protected static ReadOnlyCollection CreateLocatorList(MemberInfo member) - { - if (member == null) - { - throw new ArgumentNullException(nameof(member), "member cannot be null"); - } - - var useSequenceAttributes = Attribute.GetCustomAttributes(member, typeof(FindsBySequenceAttribute), true); - bool useSequence = useSequenceAttributes.Length > 0; - - var useFindAllAttributes = Attribute.GetCustomAttributes(member, typeof(FindsByAllAttribute), true); - bool useAll = useFindAllAttributes.Length > 0; - - if (useSequence && useAll) - { - throw new ArgumentException("Cannot specify FindsBySequence and FindsByAll on the same member"); - } - - List bys = new List(); - var attributes = Attribute.GetCustomAttributes(member, typeof(AbstractFindsByAttribute), true); - if (attributes.Length > 0) - { - Array.Sort(attributes); - foreach (var attribute in attributes) - { - var castedAttribute = (AbstractFindsByAttribute)attribute; - - var findsByAttribute = attribute as FindsByAttribute; - if (findsByAttribute != null && findsByAttribute.Using == null) - { - findsByAttribute.Using = member.Name; - } - - bys.Add(castedAttribute.Finder); - } - - if (useSequence) - { - ByChained chained = new ByChained(bys.ToArray()); - bys.Clear(); - bys.Add(chained); - } - - if (useAll) - { - ByAll all = new ByAll(bys.ToArray()); - bys.Clear(); - bys.Add(all); - } - } - - return bys.AsReadOnly(); - } -} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/ExpectedConditions.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/ExpectedConditions.cs deleted file mode 100644 index 501844cd..00000000 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/ExpectedConditions.cs +++ /dev/null @@ -1,602 +0,0 @@ - -using System.Collections.ObjectModel; -using System.Text.RegularExpressions; -using OpenQA.Selenium; - -namespace TestWare.Engines.Selenoid.Extras; - -/// -/// Supplies a set of common conditions that can be waited for using . -/// -/// -/// -/// IWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(3)) -/// IWebElement element = wait.Until(ExpectedConditions.ElementExists(By.Id("foo"))); -/// -/// -public sealed class ExpectedConditions -{ - /// - /// Prevents a default instance of the class from being created. - /// - private ExpectedConditions() - { - } - - /// - /// An expectation for checking the title of a page. - /// - /// The expected title, which must be an exact match. - /// when the title matches; otherwise, . - public static Func TitleIs(string title) - { - return (driver) => { return title == driver.Title; }; - } - - /// - /// An expectation for checking that the title of a page contains a case-sensitive substring. - /// - /// The fragment of title expected. - /// when the title matches; otherwise, . - public static Func TitleContains(string title) - { - return (driver) => { return driver.Title.Contains(title); }; - } - - /// - /// An expectation for the URL of the current page to be a specific URL. - /// - /// The URL that the page should be on - /// when the URL is what it should be; otherwise, . - public static Func UrlToBe(string url) - { - return (driver) => { return driver.Url.ToLowerInvariant().Equals(url.ToLowerInvariant()); }; - } - - /// - /// An expectation for the URL of the current page to be a specific URL. - /// - /// The fraction of the url that the page should be on - /// when the URL contains the text; otherwise, . - public static Func UrlContains(string fraction) - { - return (driver) => { return driver.Url.ToLowerInvariant().Contains(fraction.ToLowerInvariant()); }; - } - - /// - /// An expectation for the URL of the current page to be a specific URL. - /// - /// The regular expression that the URL should match - /// if the URL matches the specified regular expression; otherwise, . - public static Func UrlMatches(string regex) - { - return (driver) => - { - var currentUrl = driver.Url; - var pattern = new Regex(regex, RegexOptions.IgnoreCase); - var match = pattern.Match(currentUrl); - return match.Success; - }; - } - - /// - /// An expectation for checking that an element is present on the DOM of a - /// page. This does not necessarily mean that the element is visible. - /// - /// The locator used to find the element. - /// The once it is located. - public static Func ElementExists(By locator) - { - return (driver) => { return driver.FindElement(locator); }; - } - - /// - /// An expectation for checking that an element is present on the DOM of a page - /// and visible. Visibility means that the element is not only displayed but - /// also has a height and width that is greater than 0. - /// - /// The locator used to find the element. - /// The once it is located and visible. - public static Func ElementIsVisible(By locator) - { - return (driver) => - { - try - { - return ElementIfVisible(driver.FindElement(locator)); - } - catch (StaleElementReferenceException) - { - return null; - } - }; - } - - /// - /// An expectation for checking that all elements present on the web page that - /// match the locator are visible. Visibility means that the elements are not - /// only displayed but also have a height and width that is greater than 0. - /// - /// The locator used to find the element. - /// The list of once it is located and visible. - public static Func> VisibilityOfAllElementsLocatedBy(By locator) - { - return (driver) => - { - try - { - var elements = driver.FindElements(locator); - if (elements.Any(element => !element.Displayed)) - { - return null; - } - - return elements.Any() ? elements : null; - } - catch (StaleElementReferenceException) - { - return null; - } - }; - } - - /// - /// An expectation for checking that all elements present on the web page that - /// match the locator are visible. Visibility means that the elements are not - /// only displayed but also have a height and width that is greater than 0. - /// - /// list of WebElements - /// The list of once it is located and visible. - public static Func> VisibilityOfAllElementsLocatedBy(ReadOnlyCollection elements) - { - return (driver) => - { - try - { - if (elements.Any(element => !element.Displayed)) - { - return null; - } - - return elements.Any() ? elements : null; - } - catch (StaleElementReferenceException) - { - return null; - } - }; - } - - /// - /// An expectation for checking that all elements present on the web page that - /// match the locator. - /// - /// The locator used to find the element. - /// The list of once it is located. - public static Func> PresenceOfAllElementsLocatedBy(By locator) - { - return (driver) => - { - try - { - var elements = driver.FindElements(locator); - return elements.Any() ? elements : null; - } - catch (StaleElementReferenceException) - { - return null; - } - }; - } - - /// - /// An expectation for checking if the given text is present in the specified element. - /// - /// The WebElement - /// Text to be present in the element - /// once the element contains the given text; otherwise, . - public static Func TextToBePresentInElement(IWebElement element, string text) - { - return (driver) => - { - try - { - var elementText = element.Text; - return elementText.Contains(text); - } - catch (StaleElementReferenceException) - { - return false; - } - }; - } - - /// - /// An expectation for checking if the given text is present in the element that matches the given locator. - /// - /// The locator used to find the element. - /// Text to be present in the element - /// once the element contains the given text; otherwise, . - public static Func TextToBePresentInElementLocated(By locator, string text) - { - return (driver) => - { - try - { - var element = driver.FindElement(locator); - var elementText = element.Text; - return elementText.Contains(text); - } - catch (StaleElementReferenceException) - { - return false; - } - }; - } - - /// - /// An expectation for checking if the given text is present in the specified elements value attribute. - /// - /// The WebElement - /// Text to be present in the element - /// once the element contains the given text; otherwise, . - public static Func TextToBePresentInElementValue(IWebElement element, string text) - { - return (driver) => - { - try - { - var elementValue = element.GetAttribute("value"); - if (elementValue != null) - { - return elementValue.Contains(text); - } - else - { - return false; - } - } - catch (StaleElementReferenceException) - { - return false; - } - }; - } - - /// - /// An expectation for checking if the given text is present in the specified elements value attribute. - /// - /// The locator used to find the element. - /// Text to be present in the element - /// once the element contains the given text; otherwise, . - public static Func TextToBePresentInElementValue(By locator, string text) - { - return (driver) => - { - try - { - var element = driver.FindElement(locator); - var elementValue = element.GetAttribute("value"); - if (elementValue != null) - { - return elementValue.Contains(text); - } - else - { - return false; - } - } - catch (StaleElementReferenceException) - { - return false; - } - }; - } - - /// - /// An expectation for checking whether the given frame is available to switch - /// to. If the frame is available it switches the given driver to the - /// specified frame. - /// - /// Used to find the frame (id or name) - /// - public static Func FrameToBeAvailableAndSwitchToIt(string frameLocator) - { - return (driver) => - { - try - { - return driver.SwitchTo().Frame(frameLocator); - } - catch (NoSuchFrameException) - { - return null; - } - }; - } - - /// - /// An expectation for checking whether the given frame is available to switch - /// to. If the frame is available it switches the given driver to the - /// specified frame. - /// - /// Locator for the Frame - /// - public static Func FrameToBeAvailableAndSwitchToIt(By locator) - { - return (driver) => - { - try - { - var frameElement = driver.FindElement(locator); - return driver.SwitchTo().Frame(frameElement); - } - catch (NoSuchFrameException) - { - return null; - } - }; - } - - /// - /// An expectation for checking that an element is either invisible or not present on the DOM. - /// - /// The locator used to find the element. - /// if the element is not displayed; otherwise, . - public static Func InvisibilityOfElementLocated(By locator) - { - return (driver) => - { - try - { - var element = driver.FindElement(locator); - return !element.Displayed; - } - catch (NoSuchElementException) - { - // Returns true because the element is not present in DOM. The - // try block checks if the element is present but is invisible. - return true; - } - catch (StaleElementReferenceException) - { - // Returns true because stale element reference implies that element - // is no longer visible. - return true; - } - }; - } - - /// - /// An expectation for checking that an element with text is either invisible or not present on the DOM. - /// - /// The locator used to find the element. - /// Text of the element - /// if the element is not displayed; otherwise, . - public static Func InvisibilityOfElementWithText(By locator, string text) - { - return (driver) => - { - try - { - var element = driver.FindElement(locator); - var elementText = element.Text; - if (string.IsNullOrEmpty(elementText)) - { - return true; - } - - return !elementText.Equals(text); - } - catch (NoSuchElementException) - { - // Returns true because the element with text is not present in DOM. The - // try block checks if the element is present but is invisible. - return true; - } - catch (StaleElementReferenceException) - { - // Returns true because stale element reference implies that element - // is no longer visible. - return true; - } - }; - } - - /// - /// An expectation for checking an element is visible and enabled such that you - /// can click it. - /// - /// The locator used to find the element. - /// The once it is located and clickable (visible and enabled). - public static Func ElementToBeClickable(By locator) - { - return (driver) => - { - var element = ElementIfVisible(driver.FindElement(locator)); - try - { - if (element != null && element.Enabled) - { - return element; - } - else - { - return null; - } - } - catch (StaleElementReferenceException) - { - return null; - } - }; - } - - /// - /// An expectation for checking an element is visible and enabled such that you - /// can click it. - /// - /// The element. - /// The once it is clickable (visible and enabled). - public static Func ElementToBeClickable(IWebElement element) - { - return (driver) => - { - try - { - if (element != null && element.Displayed && element.Enabled) - { - return element; - } - else - { - return null; - } - } - catch (StaleElementReferenceException) - { - return null; - } - }; - } - - /// - /// Wait until an element is no longer attached to the DOM. - /// - /// The element. - /// is the element is still attached to the DOM; otherwise, . - public static Func StalenessOf(IWebElement element) - { - return (driver) => - { - try - { - // Calling any method forces a staleness check - return element == null || !element.Enabled; - } - catch (StaleElementReferenceException) - { - return true; - } - }; - } - - /// - /// An expectation for checking if the given element is selected. - /// - /// The element. - /// given element is selected.; otherwise, . - public static Func ElementToBeSelected(IWebElement element) - { - return ElementSelectionStateToBe(element, true); - } - - /// - /// An expectation for checking if the given element is in correct state. - /// - /// The element. - /// selected or not selected - /// given element is in correct state.; otherwise, . - public static Func ElementToBeSelected(IWebElement element, bool selected) - { - return (driver) => - { - return element.Selected == selected; - }; - } - - /// - /// An expectation for checking if the given element is selected. - /// - /// The locator used to find the element. - /// given element is selected.; otherwise, . - public static Func ElementToBeSelected(By locator) - { - return ElementSelectionStateToBe(locator, true); - } - - - /// - /// An expectation for checking if the given element is in correct state. - /// - /// The element. - /// selected or not selected - /// given element is in correct state.; otherwise, . - public static Func ElementSelectionStateToBe(IWebElement element, bool selected) - { - return (driver) => - { - return element.Selected == selected; - }; - } - - /// - /// An expectation for checking if the given element is in correct state. - /// - /// The locator used to find the element. - /// selected or not selected - /// given element is in correct state.; otherwise, . - public static Func ElementSelectionStateToBe(By locator, bool selected) - { - return (driver) => - { - try - { - var element = driver.FindElement(locator); - return element.Selected == selected; - } - catch (StaleElementReferenceException) - { - return false; - } - }; - } - - /// - /// An expectation for checking the AlterIsPresent - /// - /// Alert - public static Func AlertIsPresent() - { - return (driver) => - { - try - { - return driver.SwitchTo().Alert(); - } - catch (NoAlertPresentException) - { - return null; - } - }; - } - - /// - /// An expectation for checking the Alert State - /// - /// A value indicating whether or not an alert should be displayed in order to meet this condition. - /// alert is in correct state present or not present; otherwise, . - public static Func AlertState(bool state) - { - return (driver) => - { - var alertState = false; - try - { - driver.SwitchTo().Alert(); - alertState = true; - return alertState == state; - } - catch (NoAlertPresentException) - { - alertState = false; - return alertState == state; - } - }; - } - - private static IWebElement ElementIfVisible(IWebElement element) - { - return element.Displayed ? element : null; - } -} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/FindsByAllAttribute.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/FindsByAllAttribute.cs deleted file mode 100644 index c63e00f6..00000000 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/FindsByAllAttribute.cs +++ /dev/null @@ -1,30 +0,0 @@ - -namespace TestWare.Engines.Selenoid.Extras; - -/// -/// Marks elements to indicate that found elements should match the criteria of -/// all on the field or property. -/// -/// -/// -/// When used with a set of , all criteria must be -/// matched to be returned. The criteria are used in sequence according to the -/// Priority property. Note that the behavior when setting multiple -/// Priority properties to the same value, or not -/// specifying a Priority value, is undefined. -/// -/// -/// -/// // Will find the element with the tag name "input" that also has an ID -/// // attribute matching "elementId". -/// [FindsByAll] -/// [FindsBy(How = How.TagName, Using = "input", Priority = 0)] -/// [FindsBy(How = How.Id, Using = "elementId", Priority = 1)] -/// public IWebElement thisElement; -/// -/// -/// -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] -public sealed class FindsByAllAttribute : Attribute -{ -} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/FindsByAttribute.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/FindsByAttribute.cs deleted file mode 100644 index 092d5119..00000000 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/FindsByAttribute.cs +++ /dev/null @@ -1,94 +0,0 @@ -#nullable enable - -using System.ComponentModel; -using OpenQA.Selenium; - -namespace TestWare.Engines.Selenoid.Extras; - -/// -/// Marks program elements with methods by which to find a corresponding element on the page. Used -/// in conjunction with the , it allows you to quickly create Page Objects. -/// -/// -/// -/// You can use this attribute by specifying the and properties -/// to indicate how to find the elements. This attribute can be used to decorate fields and properties -/// in your Page Object classes. The of the field or property must be either -/// or IList{IWebElement}. Any other type will throw an -/// when is called. -/// -/// -/// -/// [FindsBy(How = How.Name, Using = "myElementName")] -/// public IWebElement foundElement; -/// -/// [FindsBy(How = How.TagName, Using = "a")] -/// public IList{IWebElement} allLinks; -/// -/// -/// -/// You can also use multiple instances of this attribute to find an element that may meet -/// one of multiple criteria. When using multiple instances, you can specify the order in -/// which the criteria is matched by using the property. -/// -/// -/// -/// // Will find the element with the name attribute matching the first of "anElementName" -/// // or "differentElementName". -/// [FindsBy(How = How.Name, Using = "anElementName", Priority = 0)] -/// [FindsBy(How = How.Name, Using = "differentElementName", Priority = 1)] -/// public IWebElement thisElement; -/// -/// -/// -public class FindsByAttribute : AbstractFindsByAttribute -{ - protected By? finder; - - public FindsByAttribute() { } - - /// - /// Create instance - /// - /// Method used to look up the element - /// Value to lookup by - public FindsByAttribute(How how, string @using) - { - How = how; - Using = @using; - } - - /// - /// Gets or sets the method used to look up the element - /// - [DefaultValue(How.Id)] - public How How { get; set; } - - /// - /// Gets or sets the value to lookup by (i.e. for How.Name, the actual name to look up) - /// - public string? Using { get; set; } - - /// - /// Gets or sets a value indicating the of the custom finder. The custom finder must - /// descend from the class, and expose a public constructor that takes a - /// argument. - /// - public Type? CustomFinderType { get; set; } - - /// - /// Gets an explicit object to find by. - /// - public override By Finder - { - get - { - if (this.finder == null) - { - this.finder = ByFactory.From(this); - } - - return this.finder; - } - } -} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/FindsBySequenceAttribute.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/FindsBySequenceAttribute.cs deleted file mode 100644 index 23097957..00000000 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/FindsBySequenceAttribute.cs +++ /dev/null @@ -1,30 +0,0 @@ - - -namespace TestWare.Engines.Selenoid.Extras; - -/// -/// Marks elements to indicate that each on the field or -/// property should be used in sequence to find the appropriate element. -/// -/// -/// -/// When used with a set of , the criteria are used -/// in sequence according to the Priority property to find child elements. Note that -/// the behavior when setting multiple Priority -/// properties to the same value, or not specifying a Priority value, is undefined. -/// -/// -/// -/// // Will find the element with the ID attribute matching "elementId", then will find -/// // a child element with the ID attribute matching "childElementId". -/// [FindsBySequence] -/// [FindsBy(How = How.Id, Using = "elementId", Priority = 0)] -/// [FindsBy(How = How.Id, Using = "childElementId", Priority = 1)] -/// public IWebElement thisElement; -/// -/// -/// -[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] -public sealed class FindsBySequenceAttribute : Attribute -{ -} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/IPageObjectMemberDecorator.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/IPageObjectMemberDecorator.cs deleted file mode 100644 index d1fb93ce..00000000 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/IPageObjectMemberDecorator.cs +++ /dev/null @@ -1,22 +0,0 @@ -#nullable enable - -using System.Reflection; - -namespace TestWare.Engines.Selenoid.Extras; - -/// -/// Interface describing how members of a class which represent elements in a Page Object -/// are detected. -/// -public interface IPageObjectMemberDecorator -{ - /// - /// Locates an element or list of elements for a Page Object member, and returns a - /// proxy object for the element or list of elements. - /// - /// The containing information about - /// a class's member. - /// The used to locate elements. - /// A transparent proxy to the WebDriver element object. - object? Decorate(MemberInfo member, IElementLocator locator); -} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/IMemberBuilder.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/IMemberBuilder.cs deleted file mode 100644 index 4c8960ae..00000000 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/IMemberBuilder.cs +++ /dev/null @@ -1,34 +0,0 @@ -// -// Licensed to the Software Freedom Conservancy (SFC) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The SFC licenses this file -// to you under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System.Diagnostics.CodeAnalysis; -using OpenQA.Selenium; - -namespace TestWare.Engines.Selenoid.Extras.MemberBuilders; - -/// -/// Interface for page object members creation -/// -internal interface IMemberBuilder -{ - /// - /// Create page object member - /// - bool CreateObject(Type memberType, IElementLocator locator, IEnumerable bys, bool cache, - [NotNullWhen(true)] out object createdObject); -} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WebElementBuilder.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WebElementBuilder.cs deleted file mode 100644 index 3afe823c..00000000 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WebElementBuilder.cs +++ /dev/null @@ -1,41 +0,0 @@ -// -// Licensed to the Software Freedom Conservancy (SFC) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The SFC licenses this file -// to you under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System.Diagnostics.CodeAnalysis; -using OpenQA.Selenium; - -namespace TestWare.Engines.Selenoid.Extras.MemberBuilders; - -/// -/// Creates member of type -/// -internal class WebElementBuilder : IMemberBuilder -{ - public bool CreateObject(Type memberType, IElementLocator locator, IEnumerable bys, bool cache, [NotNullWhen(true)] out object createdObject) - { - createdObject = null; - - if (memberType == typeof(IWebElement)) - { - createdObject = new WebElementProxy(locator, bys, cache); - return true; - } - - return false; - } -} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WebElementListBuilder.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WebElementListBuilder.cs deleted file mode 100644 index b53937eb..00000000 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WebElementListBuilder.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using OpenQA.Selenium; - -namespace TestWare.Engines.Selenoid.Extras.MemberBuilders; - -/// -/// Creates member of type -/// -internal class WebElementListBuilder : IMemberBuilder -{ - public bool CreateObject(Type memberType, IElementLocator locator, IEnumerable bys, bool cache, [NotNullWhen(true)] out object createdObject) - { - createdObject = null; - - if (memberType == typeof(IList)) - { - createdObject = new WebElementListProxy(locator, bys, cache); - return true; - } - - return false; - } -} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WrappedElementBuilder.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WrappedElementBuilder.cs deleted file mode 100644 index 642b1018..00000000 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WrappedElementBuilder.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using OpenQA.Selenium; - -namespace TestWare.Engines.Selenoid.Extras.MemberBuilders; - -internal class WrappedElementBuilder : IMemberBuilder -{ - public bool CreateObject(Type memberType, IElementLocator locator, IEnumerable bys, bool cache, [NotNullWhen(true)] out object createdObject) - { - createdObject = null; - - if (typeof(IWrapsElement).IsAssignableFrom(memberType)) - { - var webElement = new WebElementProxy(locator, bys, cache); - createdObject = WrapsElementFactory.Wrap(memberType, webElement); - return true; - } - - return false; - } -} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WrappedElementListBuilder.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WrappedElementListBuilder.cs deleted file mode 100644 index 32524227..00000000 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/MemberBuilders/WrappedElementListBuilder.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using OpenQA.Selenium; - -namespace TestWare.Engines.Selenoid.Extras.MemberBuilders; - -internal class WrappedElementListBuilder : IMemberBuilder -{ - public bool CreateObject(Type memberType, IElementLocator locator, IEnumerable bys, bool cache, [NotNullWhen(true)] out object createdObject) - { - createdObject = null; - - if (memberType.IsGenericType && memberType.GetGenericTypeDefinition() == typeof(IList<>)) - { - var elementType = memberType.GetGenericArguments()[0]; - - if (typeof(IWrapsElement).IsAssignableFrom(elementType)) - { - var listType = typeof(WrapsElementListProxy<>).MakeGenericType(memberType.GetGenericArguments()[0]); - createdObject = Activator.CreateInstance(listType, new object[] { locator, bys, cache }); - } - } - - return createdObject != null; - } -} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/RetryingElementLocator.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/RetryingElementLocator.cs deleted file mode 100644 index 3f7b503d..00000000 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/RetryingElementLocator.cs +++ /dev/null @@ -1,133 +0,0 @@ -#nullable enable - -using System.Collections.ObjectModel; -using OpenQA.Selenium; - -namespace TestWare.Engines.Selenoid.Extras; - -/// -/// A locator for elements for use with the that retries locating -/// the element up to a timeout if the element is not found. -/// -public class RetryingElementLocator : IElementLocator -{ - private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(5); - private static readonly TimeSpan DefaultPollingInterval = TimeSpan.FromMilliseconds(500); - private TimeSpan timeout; - private TimeSpan pollingInterval; - - /// - /// Initializes a new instance of the class. - /// - /// The object that the - /// locator uses for locating elements. - public RetryingElementLocator(ISearchContext searchContext) - : this(searchContext, DefaultTimeout) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The object that the - /// locator uses for locating elements. - /// The indicating how long the locator should - /// retry before timing out. - public RetryingElementLocator(ISearchContext searchContext, TimeSpan timeout) - : this(searchContext, timeout, DefaultPollingInterval) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The object that the - /// locator uses for locating elements. - /// The indicating how long the locator should - /// retry before timing out. - /// The indicating how often to poll - /// for the existence of the element. - public RetryingElementLocator(ISearchContext searchContext, TimeSpan timeout, TimeSpan pollingInterval) - { - this.SearchContext = searchContext; - this.timeout = timeout; - this.pollingInterval = pollingInterval; - } - - /// - /// Gets the to be used in locating elements. - /// - public ISearchContext SearchContext { get; } - - /// - /// Locates an element using the given list of criteria. - /// - /// The list of methods by which to search for the element. - /// An which is the first match under the desired criteria. - public IWebElement LocateElement(IEnumerable bys) - { - if (bys == null) - { - throw new ArgumentNullException(nameof(bys), "List of criteria may not be null"); - } - - string? errorString = null; - DateTime endTime = DateTime.Now.Add(this.timeout); - bool timeoutReached = DateTime.Now > endTime; - while (!timeoutReached) - { - foreach (var by in bys) - { - try - { - return this.SearchContext.FindElement(by); - } - catch (NoSuchElementException) - { - errorString = (errorString == null ? "Could not find element by: " : errorString + ", or: ") + by; - } - } - - timeoutReached = DateTime.Now > endTime; - if (!timeoutReached) - { - Thread.Sleep(this.pollingInterval); - } - } - - throw new NoSuchElementException(errorString); - } - - /// - /// Locates a list of elements using the given list of criteria. - /// - /// The list of methods by which to search for the elements. - /// A list of all elements which match the desired criteria. - public ReadOnlyCollection LocateElements(IEnumerable bys) - { - if (bys == null) - { - throw new ArgumentNullException(nameof(bys), "List of criteria may not be null"); - } - - List collection = new List(); - DateTime endTime = DateTime.Now.Add(this.timeout); - bool timeoutReached = DateTime.Now > endTime; - while (!timeoutReached) - { - foreach (var by in bys) - { - ReadOnlyCollection list = this.SearchContext.FindElements(by); - collection.AddRange(list); - } - - timeoutReached = collection.Count != 0 || DateTime.Now > endTime; - if (!timeoutReached) - { - Thread.Sleep(this.pollingInterval); - } - } - - return collection.AsReadOnly(); - } -} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/WebDriverObjectProxy.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/WebDriverObjectProxy.cs deleted file mode 100644 index f31d4ebd..00000000 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/WebDriverObjectProxy.cs +++ /dev/null @@ -1,38 +0,0 @@ -using OpenQA.Selenium; - -namespace TestWare.Engines.Selenoid.Extras; - -/// -/// Represents a base proxy class for objects used with the PageFactory. -/// -internal abstract class WebDriverObjectProxy -{ - /// - /// Create WebDriverObjectProxy - /// - /// The implementation that - /// determines how elements are located. - /// The list of methods by which to search for the elements. - /// to cache the lookup to the element; otherwise, . - protected WebDriverObjectProxy(IElementLocator locator, IEnumerable bys, bool cache) - { - this.Locator = locator; - this.Bys = bys; - this.Cache = cache; - } - - /// - /// Gets the implementation that determines how elements are located. - /// - protected IElementLocator Locator { get; } - - /// - /// Gets the list of methods by which to search for the elements. - /// - protected IEnumerable Bys { get; } - - /// - /// Gets a value indicating whether element search results should be cached. - /// - protected bool Cache { get; } -} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/WebElementEnumerable.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/WebElementEnumerable.cs deleted file mode 100644 index f6c46a92..00000000 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/WebElementEnumerable.cs +++ /dev/null @@ -1,28 +0,0 @@ -using TestWare.Engines.Selenoid.Extras; - -namespace TestWare.Engines.Selenoid.Extras; - -/// -/// Due to Linq optimized execution in dotnet core for IList, some methods lead to multiple elements retrieval. -/// In this class IList is wrapped in IEnumerable to disable that 'optimized' evaluation. -/// -public static class WebElementEnumerable -{ - public static IEnumerable Select(this IList webElements, Func selector) - => webElements.ToEnumerable().Select(selector); - - public static IEnumerable Where(this IList webElements, Func selector) - => webElements.ToEnumerable().Where(selector); - - public static List ToList(this IList webElements) - => webElements.ToEnumerable().ToList(); - - public static TElement[] ToArray(this IList webElements) - => webElements.ToEnumerable().ToArray(); - - private static IEnumerable ToEnumerable(this IList enumerable) - { - foreach (var element in enumerable) - yield return element; - } -} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/WebElementListProxy.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/WebElementListProxy.cs deleted file mode 100644 index b7d6f68d..00000000 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/WebElementListProxy.cs +++ /dev/null @@ -1,65 +0,0 @@ -#nullable enable - -using System.Collections; -using OpenQA.Selenium; - -namespace TestWare.Engines.Selenoid.Extras; - -/// -/// Represents a proxy class for a list of elements to be used with the PageFactory. -/// -internal class WebElementListProxy : WebDriverObjectProxy, IList -{ - private IList? _items; - - public WebElementListProxy(IElementLocator locator, IEnumerable bys, bool cache) - : base(locator, bys, cache) - { - } - - private IList Items - { - get - { - if (_items == null || !Cache) - { - _items = Locator.LocateElements(Bys); - } - return _items; - } - } - - #region Forwarded Items calls - - public IWebElement this[int index] - { - get { return Items[index]; } - set { Items[index] = value; } - } - - public int Count => Items.Count; - - public bool IsReadOnly => Items.IsReadOnly; - - public void Add(IWebElement item) => Items.Add(item); - - public void Clear() => Items.Clear(); - - public bool Contains(IWebElement item) => Items.Contains(item); - - public void CopyTo(IWebElement[] array, int arrayIndex) => Items.CopyTo(array, arrayIndex); - - public IEnumerator GetEnumerator() => Items.GetEnumerator(); - - public int IndexOf(IWebElement item) => Items.IndexOf(item); - - public void Insert(int index, IWebElement item) => Items.Insert(index, item); - - public bool Remove(IWebElement item) => Items.Remove(item); - - public void RemoveAt(int index) => Items.RemoveAt(index); - - IEnumerator IEnumerable.GetEnumerator() => Items.GetEnumerator(); - - #endregion -} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/WrapsElementFactory.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/WrapsElementFactory.cs deleted file mode 100644 index 00c286a9..00000000 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/WrapsElementFactory.cs +++ /dev/null @@ -1,35 +0,0 @@ -using OpenQA.Selenium; - -namespace TestWare.Engines.Selenoid.Extras; - -internal static class WrapsElementFactory -{ - public static T Wrap(IWebElement webElement) where T : IWrapsElement - { - return (T)Wrap(typeof(T), webElement); - } - - public static object Wrap(Type wrapsElementType, IWebElement webElement) - { - var iWebElementConstructor = wrapsElementType.GetConstructor(new[] { typeof(IWebElement) }); - - // Option 1 - T has constructor with IWebElement parameter - if (iWebElementConstructor != null) - { - return iWebElementConstructor.Invoke(new object[] { webElement }); - } - - // Option 2 - T has parameterless constructor, and setter on WrappedElement property - var parameterlessConstructor = wrapsElementType.GetConstructor(Array.Empty()); - var wrappedElementProperty = wrapsElementType.GetProperty(nameof(IWrapsElement.WrappedElement)); - - if (parameterlessConstructor != null && wrappedElementProperty?.CanWrite == true) - { - var wrappedInstance = parameterlessConstructor.Invoke(Array.Empty()); - wrappedElementProperty.SetValue(wrappedInstance, webElement); - return wrappedInstance; - } - - throw new NotSupportedException($"Cannot create instance of type '{wrapsElementType.FullName}'"); - } -} diff --git a/src/Engines/TestWare.Engines.Selenoid/Extras/WrapsElementListProxy.cs b/src/Engines/TestWare.Engines.Selenoid/Extras/WrapsElementListProxy.cs deleted file mode 100644 index 1eb83214..00000000 --- a/src/Engines/TestWare.Engines.Selenoid/Extras/WrapsElementListProxy.cs +++ /dev/null @@ -1,71 +0,0 @@ -#nullable enable - -using System.Collections; -using OpenQA.Selenium; -using TestWare.Engines.Selenoid.Extras; - -namespace TestWare.Engines.Selenoid.Extras; - -/// -/// Represents a proxy class for a list of elements to be used with the PageFactory. -/// -internal class WrapsElementListProxy : WebDriverObjectProxy, IList where T : IWrapsElement -{ - private IList? _items; - - public WrapsElementListProxy(IElementLocator locator, IEnumerable bys, bool cache) - : base(locator, bys, cache) - { - } - - private IList Items - { - get - { - // Find elements, and wrap them in IWrapsElement instances. - // If caching enabled - use previously found elements, if any. - if (_items == null || !Cache) - { - _items = Locator - .LocateElements(Bys) - .Select(WrapsElementFactory.Wrap) - .ToList(); - } - return _items; - } - } - - #region Forwarded Items calls - - public T this[int index] - { - get { return Items[index]; } - set { Items[index] = value; } - } - - public int Count => Items.Count; - - public bool IsReadOnly => Items.IsReadOnly; - - public void Add(T item) => Items.Add(item); - - public void Clear() => Items.Clear(); - - public bool Contains(T item) => Items.Contains(item); - - public void CopyTo(T[] array, int arrayIndex) => Items.CopyTo(array, arrayIndex); - - public IEnumerator GetEnumerator() => Items.GetEnumerator(); - - public int IndexOf(T item) => Items.IndexOf(item); - - public void Insert(int index, T item) => Items.Insert(index, item); - - public bool Remove(T item) => Items.Remove(item); - - public void RemoveAt(int index) => Items.RemoveAt(index); - - IEnumerator IEnumerable.GetEnumerator() => Items.GetEnumerator(); - - #endregion -} diff --git a/src/Engines/TestWare.Engines.Selenoid/Pages/PageBase.cs b/src/Engines/TestWare.Engines.Selenoid/Pages/PageBase.cs index 2199ae4d..56e21892 100644 --- a/src/Engines/TestWare.Engines.Selenoid/Pages/PageBase.cs +++ b/src/Engines/TestWare.Engines.Selenoid/Pages/PageBase.cs @@ -2,7 +2,7 @@ using OpenQA.Selenium.Interactions; using OpenQA.Selenium.Support.UI; using TestWare.Core.Libraries; -using TestWare.Engines.Selenoid.Extras; +using TestWare.Engines.Common.Extras; namespace TestWare.Engines.Selenoid.Pages; diff --git a/src/Engines/TestWare.Engines.Selenoid/Pages/WebPage.cs b/src/Engines/TestWare.Engines.Selenoid/Pages/WebPage.cs index f04d6d70..78a4fb9d 100644 --- a/src/Engines/TestWare.Engines.Selenoid/Pages/WebPage.cs +++ b/src/Engines/TestWare.Engines.Selenoid/Pages/WebPage.cs @@ -1,5 +1,5 @@ using OpenQA.Selenium; -using TestWare.Engines.Selenoid.Extras; +using TestWare.Engines.Common.Extras; namespace TestWare.Engines.Selenoid.Pages; diff --git a/src/Engines/TestWare.Engines.Selenoid/TestWare.Engines.Selenoid.csproj b/src/Engines/TestWare.Engines.Selenoid/TestWare.Engines.Selenoid.csproj index 7706752c..1c51a306 100644 --- a/src/Engines/TestWare.Engines.Selenoid/TestWare.Engines.Selenoid.csproj +++ b/src/Engines/TestWare.Engines.Selenoid/TestWare.Engines.Selenoid.csproj @@ -7,11 +7,12 @@ - + + From a6c4895aa39911a4b19ae8f7930c87a83f23aaed Mon Sep 17 00:00:00 2001 From: Diego Martinez Date: Thu, 23 May 2024 15:05:07 +0200 Subject: [PATCH 12/14] Add missing files --- .../Extras/MemberBuilders/WebElementListBuilder.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/MemberBuilders/WebElementListBuilder.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/MemberBuilders/WebElementListBuilder.cs index 7ac927e4..decff291 100644 --- a/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/MemberBuilders/WebElementListBuilder.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/MemberBuilders/WebElementListBuilder.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using OpenQA.Selenium; +using TestWare.Engines.Common.Extras; namespace TestWare.Engines.Selenium.Extras.MemberBuilders; From 1772057be0449b60bae0c9896ee91bc1608bd31b Mon Sep 17 00:00:00 2001 From: Diego Martinez Date: Thu, 23 May 2024 15:08:17 +0200 Subject: [PATCH 13/14] Add missing references --- src/Engines/TestWare.Engines.Selenium/Pages/PageBase.cs | 2 +- src/Engines/TestWare.Engines.Selenium/Pages/WebPage.cs | 2 +- src/Engines/TestWare.Engines.Selenoid/SelenoidManager.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Engines/TestWare.Engines.Selenium/Pages/PageBase.cs b/src/Engines/TestWare.Engines.Selenium/Pages/PageBase.cs index d601ca2c..fd0eb089 100644 --- a/src/Engines/TestWare.Engines.Selenium/Pages/PageBase.cs +++ b/src/Engines/TestWare.Engines.Selenium/Pages/PageBase.cs @@ -2,7 +2,7 @@ using OpenQA.Selenium.Interactions; using OpenQA.Selenium.Support.UI; using TestWare.Core.Libraries; -using TestWare.Engines.Selenium.Extras; +using TestWare.Engines.Common.Extras; namespace TestWare.Engines.Selenium.Pages; diff --git a/src/Engines/TestWare.Engines.Selenium/Pages/WebPage.cs b/src/Engines/TestWare.Engines.Selenium/Pages/WebPage.cs index 3a5d7f86..2ed6ad7f 100644 --- a/src/Engines/TestWare.Engines.Selenium/Pages/WebPage.cs +++ b/src/Engines/TestWare.Engines.Selenium/Pages/WebPage.cs @@ -1,5 +1,5 @@ using OpenQA.Selenium; -using TestWare.Engines.Selenium.Extras; +using TestWare.Engines.Common.Extras; using TestWare.Engines.Selenium.Factory; namespace TestWare.Engines.Selenium.Pages; diff --git a/src/Engines/TestWare.Engines.Selenoid/SelenoidManager.cs b/src/Engines/TestWare.Engines.Selenoid/SelenoidManager.cs index 2993a698..43a9b5da 100644 --- a/src/Engines/TestWare.Engines.Selenoid/SelenoidManager.cs +++ b/src/Engines/TestWare.Engines.Selenoid/SelenoidManager.cs @@ -66,7 +66,7 @@ public string CollectEvidence(string destinationPath, string evidenceName) { var instanceName = ContainerManager.GetNameFromInstance(webDriver); var ss = ((ITakesScreenshot)webDriver).GetScreenshot(); - ss.SaveAsFile(Path.Combine(destinationPath, $"{evidenceName} - {instanceName}.png"), ScreenshotImageFormat.Png); + ss.SaveAsFile(Path.Combine(destinationPath, $"{evidenceName} - {instanceName}.png")); } catch (WebDriverException) { } From d37eb54265541a88d08d5132ac611a5637d608d9 Mon Sep 17 00:00:00 2001 From: Diego Martinez Date: Thu, 23 May 2024 15:30:33 +0200 Subject: [PATCH 14/14] Move all references to Extras common --- .../POM/Cart/CartPage.cs | 2 +- .../POM/Login/LoginPage.cs | 2 +- .../POM/Menu/MenuPage.cs | 2 +- .../POM/Product/ProductPage.cs | 2 +- .../POM/Stinto/Chat/ChatPage.cs | 2 +- .../POM/Stinto/CreateChat/CreateChatPage.cs | 2 +- .../POM/Stinto/Home/HomePage.cs | 2 +- .../POM/SwagLabs/Login/LoginPage.cs | 2 +- .../POM/SwagLabs/Menu/MenuPage.cs | 2 +- .../POM/SwagLabs/Login/LoginPage.cs | 2 +- .../POM/SwagLabs/Menu/MenuPage.cs | 2 +- .../POM/Calculator/CalculatorPage.cs | 2 +- .../POM/Notepad/NotepadPage.cs | 2 +- .../WindowsCalculatorPage.cs | 2 +- .../Extras/ByFactory.cs | 67 ------------------- .../Extras/FindsByAttribute.cs | 23 ------- .../TestWare.Engines.Appium/Extras/How.cs | 57 ---------------- .../Extras/PageFactory.cs | 5 -- .../Pages/MobilePage.cs | 2 +- .../Extras/ByFactory.cs | 5 +- .../TestWare.Engines.Common/Extras/How.cs | 7 +- .../TestWare.Engines.Common.csproj | 1 + .../Pages/WinAppDriverPage.cs | 4 +- ...estWare.Engines.Appium.WinAppDriver.csproj | 2 +- 24 files changed, 29 insertions(+), 172 deletions(-) delete mode 100644 src/Engines/TestWare.Engines.Appium/Extras/ByFactory.cs delete mode 100644 src/Engines/TestWare.Engines.Appium/Extras/FindsByAttribute.cs delete mode 100644 src/Engines/TestWare.Engines.Appium/Extras/How.cs delete mode 100644 src/Engines/TestWare.Engines.Appium/Extras/PageFactory.cs diff --git a/samples/TestWare.Samples.Appium.Mobile/POM/Cart/CartPage.cs b/samples/TestWare.Samples.Appium.Mobile/POM/Cart/CartPage.cs index 117292ee..1e9bd672 100644 --- a/samples/TestWare.Samples.Appium.Mobile/POM/Cart/CartPage.cs +++ b/samples/TestWare.Samples.Appium.Mobile/POM/Cart/CartPage.cs @@ -1,6 +1,6 @@ using OpenQA.Selenium; using TestWare.Engines.Appium.Pages; -using TestWare.Engines.Appium.Extras; +using TestWare.Engines.Common.Extras; using TestWare.Engines.Appium.Factory; using TestWare.Core.Libraries; diff --git a/samples/TestWare.Samples.Appium.Mobile/POM/Login/LoginPage.cs b/samples/TestWare.Samples.Appium.Mobile/POM/Login/LoginPage.cs index 1ec030bb..d07a842d 100644 --- a/samples/TestWare.Samples.Appium.Mobile/POM/Login/LoginPage.cs +++ b/samples/TestWare.Samples.Appium.Mobile/POM/Login/LoginPage.cs @@ -1,5 +1,5 @@ using OpenQA.Selenium; -using TestWare.Engines.Appium.Extras; +using TestWare.Engines.Common.Extras; using TestWare.Engines.Appium.Factory; using TestWare.Engines.Appium.Pages; diff --git a/samples/TestWare.Samples.Appium.Mobile/POM/Menu/MenuPage.cs b/samples/TestWare.Samples.Appium.Mobile/POM/Menu/MenuPage.cs index 3a5a060b..dd3d687b 100644 --- a/samples/TestWare.Samples.Appium.Mobile/POM/Menu/MenuPage.cs +++ b/samples/TestWare.Samples.Appium.Mobile/POM/Menu/MenuPage.cs @@ -1,6 +1,6 @@ using OpenQA.Selenium; using TestWare.Engines.Appium.Pages; -using TestWare.Engines.Appium.Extras; +using TestWare.Engines.Common.Extras; using TestWare.Engines.Appium.Factory; namespace TestWare.Samples.Appium.Mobile.POM.Menu; diff --git a/samples/TestWare.Samples.Appium.Mobile/POM/Product/ProductPage.cs b/samples/TestWare.Samples.Appium.Mobile/POM/Product/ProductPage.cs index 8e018e54..a81fdfc2 100644 --- a/samples/TestWare.Samples.Appium.Mobile/POM/Product/ProductPage.cs +++ b/samples/TestWare.Samples.Appium.Mobile/POM/Product/ProductPage.cs @@ -1,7 +1,7 @@ using OpenQA.Selenium; using OpenQA.Selenium.Appium; using TestWare.Core.Libraries; -using TestWare.Engines.Appium.Extras; +using TestWare.Engines.Common.Extras; using TestWare.Engines.Appium.Factory; using TestWare.Engines.Appium.Pages; diff --git a/samples/TestWare.Samples.Selenium.Web/POM/Stinto/Chat/ChatPage.cs b/samples/TestWare.Samples.Selenium.Web/POM/Stinto/Chat/ChatPage.cs index cfed90a4..a2646dbf 100644 --- a/samples/TestWare.Samples.Selenium.Web/POM/Stinto/Chat/ChatPage.cs +++ b/samples/TestWare.Samples.Selenium.Web/POM/Stinto/Chat/ChatPage.cs @@ -1,6 +1,6 @@ using OpenQA.Selenium; using TestWare.Core.Libraries; -using TestWare.Engines.Selenium.Extras; +using TestWare.Engines.Common.Extras; using TestWare.Engines.Selenium.Factory; using TestWare.Engines.Selenium.Pages; diff --git a/samples/TestWare.Samples.Selenium.Web/POM/Stinto/CreateChat/CreateChatPage.cs b/samples/TestWare.Samples.Selenium.Web/POM/Stinto/CreateChat/CreateChatPage.cs index 2b788a26..8f9c02c1 100644 --- a/samples/TestWare.Samples.Selenium.Web/POM/Stinto/CreateChat/CreateChatPage.cs +++ b/samples/TestWare.Samples.Selenium.Web/POM/Stinto/CreateChat/CreateChatPage.cs @@ -1,5 +1,5 @@ using OpenQA.Selenium; -using TestWare.Engines.Selenium.Extras; +using TestWare.Engines.Common.Extras; using TestWare.Engines.Selenium.Factory; using TestWare.Engines.Selenium.Pages; diff --git a/samples/TestWare.Samples.Selenium.Web/POM/Stinto/Home/HomePage.cs b/samples/TestWare.Samples.Selenium.Web/POM/Stinto/Home/HomePage.cs index 9fb3e451..38ddb933 100644 --- a/samples/TestWare.Samples.Selenium.Web/POM/Stinto/Home/HomePage.cs +++ b/samples/TestWare.Samples.Selenium.Web/POM/Stinto/Home/HomePage.cs @@ -1,5 +1,5 @@ using OpenQA.Selenium; -using TestWare.Engines.Selenium.Extras; +using TestWare.Engines.Common.Extras; using TestWare.Engines.Selenium.Factory; using TestWare.Engines.Selenium.Pages; diff --git a/samples/TestWare.Samples.Selenium.Web/POM/SwagLabs/Login/LoginPage.cs b/samples/TestWare.Samples.Selenium.Web/POM/SwagLabs/Login/LoginPage.cs index 82547164..8b1b1dc6 100644 --- a/samples/TestWare.Samples.Selenium.Web/POM/SwagLabs/Login/LoginPage.cs +++ b/samples/TestWare.Samples.Selenium.Web/POM/SwagLabs/Login/LoginPage.cs @@ -1,6 +1,6 @@ using OpenQA.Selenium; using TestWare.Core.Libraries; -using TestWare.Engines.Selenium.Extras; +using TestWare.Engines.Common.Extras; using TestWare.Engines.Selenium.Factory; using TestWare.Engines.Selenium.Pages; diff --git a/samples/TestWare.Samples.Selenium.Web/POM/SwagLabs/Menu/MenuPage.cs b/samples/TestWare.Samples.Selenium.Web/POM/SwagLabs/Menu/MenuPage.cs index 715dab7d..dc246ee6 100644 --- a/samples/TestWare.Samples.Selenium.Web/POM/SwagLabs/Menu/MenuPage.cs +++ b/samples/TestWare.Samples.Selenium.Web/POM/SwagLabs/Menu/MenuPage.cs @@ -1,5 +1,5 @@ using OpenQA.Selenium; -using TestWare.Engines.Selenium.Extras; +using TestWare.Engines.Common.Extras; using TestWare.Engines.Selenium.Factory; using TestWare.Engines.Selenium.Pages; diff --git a/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Login/LoginPage.cs b/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Login/LoginPage.cs index 7fbffaaa..e5eceb6d 100644 --- a/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Login/LoginPage.cs +++ b/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Login/LoginPage.cs @@ -1,6 +1,6 @@ using OpenQA.Selenium; using TestWare.Core.Libraries; -using TestWare.Engines.Selenoid.Extras; +using TestWare.Engines.Common.Extras; using TestWare.Engines.Selenoid.Factory; using TestWare.Engines.Selenoid.Pages; diff --git a/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Menu/MenuPage.cs b/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Menu/MenuPage.cs index 8bb35b25..3c953f28 100644 --- a/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Menu/MenuPage.cs +++ b/samples/TestWare.Samples.Selenoid.Web/POM/SwagLabs/Menu/MenuPage.cs @@ -1,5 +1,5 @@ using OpenQA.Selenium; -using TestWare.Engines.Selenoid.Extras; +using TestWare.Engines.Common.Extras; using TestWare.Engines.Selenoid.Factory; using TestWare.Engines.Selenoid.Pages; diff --git a/samples/TestWare.Samples.WinAppDriver/POM/Calculator/CalculatorPage.cs b/samples/TestWare.Samples.WinAppDriver/POM/Calculator/CalculatorPage.cs index 8f597e1d..1b758ba6 100644 --- a/samples/TestWare.Samples.WinAppDriver/POM/Calculator/CalculatorPage.cs +++ b/samples/TestWare.Samples.WinAppDriver/POM/Calculator/CalculatorPage.cs @@ -1,6 +1,6 @@ using OpenQA.Selenium; using OpenQA.Selenium.Appium; -using TestWare.Engines.Appium.Extras; +using TestWare.Engines.Common.Extras; using TestWare.Engines.Appium.WinAppDriver.Factory; using TestWare.Engines.Appium.WinAppDriver.Pages; diff --git a/samples/TestWare.Samples.WinAppDriver/POM/Notepad/NotepadPage.cs b/samples/TestWare.Samples.WinAppDriver/POM/Notepad/NotepadPage.cs index c33e6cee..c8e7c05a 100644 --- a/samples/TestWare.Samples.WinAppDriver/POM/Notepad/NotepadPage.cs +++ b/samples/TestWare.Samples.WinAppDriver/POM/Notepad/NotepadPage.cs @@ -1,5 +1,5 @@ using OpenQA.Selenium; -using TestWare.Engines.Appium.Extras; +using TestWare.Engines.Common.Extras; using TestWare.Engines.Appium.WinAppDriver.Factory; using TestWare.Engines.Appium.WinAppDriver.Pages; diff --git a/samples/TestWare.Samples.WinAppDriver/POM/WindowsCalculator/WindowsCalculatorPage.cs b/samples/TestWare.Samples.WinAppDriver/POM/WindowsCalculator/WindowsCalculatorPage.cs index f0c2e66e..91df494a 100644 --- a/samples/TestWare.Samples.WinAppDriver/POM/WindowsCalculator/WindowsCalculatorPage.cs +++ b/samples/TestWare.Samples.WinAppDriver/POM/WindowsCalculator/WindowsCalculatorPage.cs @@ -1,6 +1,6 @@ using OpenQA.Selenium.Appium; using OpenQA.Selenium; -using TestWare.Engines.Appium.Extras; +using TestWare.Engines.Common.Extras; using TestWare.Engines.Appium.WinAppDriver.Factory; using TestWare.Engines.Appium.WinAppDriver.Pages; using Humanizer; diff --git a/src/Engines/TestWare.Engines.Appium/Extras/ByFactory.cs b/src/Engines/TestWare.Engines.Appium/Extras/ByFactory.cs deleted file mode 100644 index 10fc83ca..00000000 --- a/src/Engines/TestWare.Engines.Appium/Extras/ByFactory.cs +++ /dev/null @@ -1,67 +0,0 @@ -#nullable enable -using System.Globalization; -using System.Reflection; -using OpenQA.Selenium; -using OpenQA.Selenium.Appium; - -namespace TestWare.Engines.Appium.Extras; - -/// -/// Provides instances of the object to the attributes. -/// -internal static class ByFactory -{ - /// - /// Gets an instance of the class based on the specified attribute. - /// - /// The describing how to find the element. - /// An instance of the class. - public static By From(FindsByAttribute attribute) - { - var how = attribute.How; - var usingValue = attribute.Using; - switch (how) - { - case How.Id: - return MobileBy.Id(usingValue); - case How.Name: - return MobileBy.Name(usingValue); - case How.TagName: - return MobileBy.TagName(usingValue); - case How.ClassName: - return MobileBy.ClassName(usingValue); - case How.CssSelector: - return By.CssSelector(usingValue); - case How.LinkText: - return By.LinkText(usingValue); - case How.PartialLinkText: - return By.PartialLinkText(usingValue); - case How.XPath: - return By.XPath(usingValue); - case How.AccessibilityId: - return MobileBy.AccessibilityId(usingValue); - case How.Custom: - if (attribute.CustomFinderType == null) - { - throw new ArgumentException("Cannot use How.Custom without supplying a custom finder type"); - } - - if (!attribute.CustomFinderType.IsSubclassOf(typeof(By))) - { - throw new ArgumentException("Custom finder type must be a descendent of the By class"); - } - - ConstructorInfo? ctor = attribute.CustomFinderType.GetConstructor(new Type[] { typeof(string) }); - if (ctor == null) - { - throw new ArgumentException("Custom finder type must expose a public constructor with a string argument"); - } - - By finder = (By)ctor.Invoke(new object?[] { usingValue }); - - return finder; - } - - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Did not know how to construct How from how {0}, using {1}", how, usingValue)); - } -} diff --git a/src/Engines/TestWare.Engines.Appium/Extras/FindsByAttribute.cs b/src/Engines/TestWare.Engines.Appium/Extras/FindsByAttribute.cs deleted file mode 100644 index f0a2bcd9..00000000 --- a/src/Engines/TestWare.Engines.Appium/Extras/FindsByAttribute.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.ComponentModel; -using OpenQA.Selenium; - -namespace TestWare.Engines.Appium.Extras; - -public class FindsByAttribute : TestWare.Engines.Selenium.Extras.FindsByAttribute -{ - [DefaultValue(How.Id)] - new public How How { get; set; } - - public override By Finder - { - get - { - if (this.finder == null) - { - this.finder = ByFactory.From(this); - } - - return this.finder; - } - } -} diff --git a/src/Engines/TestWare.Engines.Appium/Extras/How.cs b/src/Engines/TestWare.Engines.Appium/Extras/How.cs deleted file mode 100644 index aa0424b2..00000000 --- a/src/Engines/TestWare.Engines.Appium/Extras/How.cs +++ /dev/null @@ -1,57 +0,0 @@ -namespace TestWare.Engines.Appium.Extras; - -/// -/// Provides the lookup methods for the FindsBy attribute (for using in PageObjects) -/// -public enum How -{ - /// - /// Finds by - /// - Id, - - /// - /// Finds by - /// - Name, - - /// - /// Finds by - /// - TagName, - - /// - /// Finds by - /// - ClassName, - - /// - /// Finds by - /// - CssSelector, - - /// - /// Finds by - /// - LinkText, - - /// - /// Finds by - /// - PartialLinkText, - - /// - /// Finds by - /// - XPath, - - /// - /// Finds by a custom implementation. - /// - Custom, - - /// - /// Finds by - /// - AccessibilityId -} diff --git a/src/Engines/TestWare.Engines.Appium/Extras/PageFactory.cs b/src/Engines/TestWare.Engines.Appium/Extras/PageFactory.cs deleted file mode 100644 index f4b74bae..00000000 --- a/src/Engines/TestWare.Engines.Appium/Extras/PageFactory.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace TestWare.Engines.Appium.Extras; - -public class PageFactory : Selenium.Extras.PageFactory -{ -} diff --git a/src/Engines/TestWare.Engines.Appium/Pages/MobilePage.cs b/src/Engines/TestWare.Engines.Appium/Pages/MobilePage.cs index 59e89a33..b1a479a6 100644 --- a/src/Engines/TestWare.Engines.Appium/Pages/MobilePage.cs +++ b/src/Engines/TestWare.Engines.Appium/Pages/MobilePage.cs @@ -2,7 +2,7 @@ using OpenQA.Selenium.Appium; using OpenQA.Selenium.Appium.MultiTouch; using System.Drawing; -using TestWare.Engines.Appium.Extras; +using TestWare.Engines.Common.Extras; using TestWare.Engines.Appium.Factory; using TestWare.Engines.Selenium.Pages; diff --git a/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/ByFactory.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/ByFactory.cs index b2f2c9f7..cb5174f6 100644 --- a/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/ByFactory.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/ByFactory.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.Reflection; using OpenQA.Selenium; +using OpenQA.Selenium.Appium; namespace TestWare.Engines.Common.Extras; @@ -36,7 +37,9 @@ public static By From(FindsByAttribute attribute) case How.PartialLinkText: return By.PartialLinkText(usingValue); case How.XPath: - return By.XPath(usingValue); + return By.XPath(usingValue); + case How.AccessibilityId: + return MobileBy.AccessibilityId(usingValue); case How.Custom: if (attribute.CustomFinderType == null) { diff --git a/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/How.cs b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/How.cs index e3034062..a3d52fda 100644 --- a/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/How.cs +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/Extras/How.cs @@ -49,5 +49,10 @@ public enum How /// /// Finds by a custom implementation. /// - Custom + Custom, + + /// + /// Finds by + /// + AccessibilityId } diff --git a/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/TestWare.Engines.Common.csproj b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/TestWare.Engines.Common.csproj index bec319ca..3797629f 100644 --- a/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/TestWare.Engines.Common.csproj +++ b/src/Engines/TestWare.Engines.Common/TestWare.Engines.Common/TestWare.Engines.Common.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Engines/TestWare.Engines.WinAppDriver/Pages/WinAppDriverPage.cs b/src/Engines/TestWare.Engines.WinAppDriver/Pages/WinAppDriverPage.cs index 9462ecac..a126d808 100644 --- a/src/Engines/TestWare.Engines.WinAppDriver/Pages/WinAppDriverPage.cs +++ b/src/Engines/TestWare.Engines.WinAppDriver/Pages/WinAppDriverPage.cs @@ -1,5 +1,5 @@ using TestWare.Engines.Selenium.Pages; -using TestWare.Engines.Appium.Extras; +using TestWare.Engines.Common.Extras; using TestWare.Engines.Appium.WinAppDriver.Factory; namespace TestWare.Engines.Appium.WinAppDriver.Pages; @@ -10,6 +10,6 @@ public abstract class WinAppDriverPage : PageBase protected WinAppDriverPage(IWindowsDriver driver) { Driver = driver; - Selenium.Extras.PageFactory.InitElements(Driver, this); + PageFactory.InitElements(Driver, this); } } diff --git a/src/Engines/TestWare.Engines.WinAppDriver/TestWare.Engines.Appium.WinAppDriver.csproj b/src/Engines/TestWare.Engines.WinAppDriver/TestWare.Engines.Appium.WinAppDriver.csproj index 678a14c9..46b59d91 100644 --- a/src/Engines/TestWare.Engines.WinAppDriver/TestWare.Engines.Appium.WinAppDriver.csproj +++ b/src/Engines/TestWare.Engines.WinAppDriver/TestWare.Engines.Appium.WinAppDriver.csproj @@ -7,7 +7,7 @@ - +