diff --git a/SeleniumManager.ConsoleApp/Program.cs b/SeleniumManager.ConsoleApp/Program.cs index cffcfcd..a0ae0ae 100644 --- a/SeleniumManager.ConsoleApp/Program.cs +++ b/SeleniumManager.ConsoleApp/Program.cs @@ -10,7 +10,7 @@ static void Main(string[] args) { string jarName = "selenium-server-4.9.1.jar"; // Name of your JAR file string jarPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), jarName); // Path to your JAR file - string arguments = $" -jar {jarPath} standalone ";//--driver-implementation \"Chrome\""; + string arguments = $@" -jar {jarPath} standalone ";// --config D:\dev\C#\SeleniumManager\SeleniumManager.ConsoleApp\myconfig.toml";//--driver-implementation \"Chrome\""; ProcessStartInfo psi = new ProcessStartInfo("java", arguments); psi.CreateNoWindow = false; // Hide the console window psi.UseShellExecute = false; // Do not use the operating system shell to start the process diff --git a/SeleniumManager.Core/ConfigManager.cs b/SeleniumManager.Core/ConfigManager.cs index 988f31c..239b991 100644 --- a/SeleniumManager.Core/ConfigManager.cs +++ b/SeleniumManager.Core/ConfigManager.cs @@ -12,8 +12,18 @@ namespace SeleniumManager.Core { public class ConfigManager { + #region Declaration + public readonly ConfigurationSettings configSettings; + #endregion + + #region Public Functions + + /// + /// This function sets the config required for the Selenium Manager + /// + /// public ConfigManager(string? configFilePath = null) { if (string.IsNullOrEmpty(configFilePath)) @@ -28,6 +38,10 @@ public ConfigManager(string? configFilePath = null) } } + #endregion + + #region Private Functions + private ConfigurationSettings LoadConfigSettingsFromResource(string resourceName) { Assembly assembly = Assembly.GetExecutingAssembly(); @@ -68,5 +82,8 @@ private ConfigurationSettings LoadConfigSettingsFromFile(string configFilePath) return LoadConfigSettingsFromResource("SeleniumManager.Core.Configuration.config.json"); } } + + #endregion + } } diff --git a/SeleniumManager.Core/DataContract/Options.cs b/SeleniumManager.Core/DataContract/Options.cs index 9a50358..d1692a4 100644 --- a/SeleniumManager.Core/DataContract/Options.cs +++ b/SeleniumManager.Core/DataContract/Options.cs @@ -20,6 +20,7 @@ public class Options public EdgeOptions edgeOptions { get; set; } = GetEdgeOptions(); public InternetExplorerOptions internetExplorerOptions { get; set; } = GetInternetExplorerOptions(); public SafariOptions safariOptions { get; set; } = GetSafariOptions(); + public ChromeOptions operaOptions { get; set; } = GetChromeOptions(); public static ChromeOptions GetChromeOptions() { @@ -39,8 +40,8 @@ public static FirefoxOptions GetFirefoxOptions() #if !DEBUG firefoxOptions.AddArgument("headless"); #endif - firefoxOptions.AddArgument("disable-gpu"); - firefoxOptions.AddArgument("no-sandbox"); + //firefoxOptions.AddArgument("disable-gpu"); + //firefoxOptions.AddArgument("no-sandbox"); firefoxOptions.AddArgument("--blink-settings=imagesEnabled=false"); return firefoxOptions; } diff --git a/SeleniumManager.Core/Enum/AdjustType.cs b/SeleniumManager.Core/Enum/AdjustType.cs new file mode 100644 index 0000000..1e1b586 --- /dev/null +++ b/SeleniumManager.Core/Enum/AdjustType.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SeleniumManager.Core.Enum +{ + /// + /// Tells the selenium manager to Create or destroy a instance + /// + public enum AdjustType + { + Create = 1, + Destroy = 2 + } +} diff --git a/SeleniumManager.Core/Enum/WebDriverType.cs b/SeleniumManager.Core/Enum/WebDriverType.cs new file mode 100644 index 0000000..65d0758 --- /dev/null +++ b/SeleniumManager.Core/Enum/WebDriverType.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SeleniumManager.Core.Enum +{ + public enum WebDriverType + { + None = 0, + [Description("Chrome")] + Chrome = 1, + [Description("Microsoft Edge")] + Microsoft_Edge = 2, + [Description("Firefox")] + Firefox = 3, + [Description("Safari")] + Safari = 4, + [Description("InternetExplorer")] + InternetExplorer = 5, + [Description("Opera")] + Opera = 6, + [Description("Custom")] + Custom = 7, + } +} diff --git a/SeleniumManager.Core/Interface/ISeleniumManager.cs b/SeleniumManager.Core/Interface/ISeleniumManager.cs index 2f3f77d..4dc029a 100644 --- a/SeleniumManager.Core/Interface/ISeleniumManager.cs +++ b/SeleniumManager.Core/Interface/ISeleniumManager.cs @@ -1,4 +1,5 @@ using OpenQA.Selenium; +using SeleniumManager.Core.Enum; using System; using System.Collections.Generic; using System.Linq; @@ -9,6 +10,122 @@ namespace SeleniumManager.Core.Interface { public interface ISeleniumManager { + #region Properties + /// + /// Gets Max Session available + /// + int MaxSessions { get; } + + /// + /// Gets Free Session available + /// + int FreeSessions { get; } + + /// + /// Gets total count of session available + /// + int TotalSessions { get; } + + /// + /// Gets Total count of available sessions + /// + int AvailableSessions { get; } + + /// + /// Gets Total count of concurrent sessions which are in use + /// + int ConcurrentSessions { get; } + + /// + /// Gets When Last Hartbeat was taken. + /// + /// the string represents browser's name and long represents the count + DateTime LastSessionDetails { get; } + + /// + /// This dictionary has list of browsers with max count of instance available + /// + /// the string represents browser's name and long represents the count + Dictionary MaxStereotypes { get; } + + /// + /// This dictionary has list of browsers with available instances. + /// + /// the string represents browser's name and long represents the count + Dictionary AvailableStereotypes { get; } + + /// + /// This dictionary has list of browsers with parallel count of instance running + /// + /// the string represents browser's name and long represents the count + Dictionary ConcurrentStereotypes { get; } + + #endregion + + #region Methods + + /// + /// This function will try to execute next in line of queue + /// + /// + void TryExecuteNext(); + + /// + /// This function returns complete data of the status available from grid + /// the endpoint is mostly example.com/status + /// + /// task of dynamic + Task GetHeartBeat(); + + /// + /// This function returns number of available instance in a Task + /// + /// Task of int + Task GetAvailableInstances(); + + /// + /// + /// This overridable function returns the webdriver from the given browser name. + /// By default it will try to get best available browser from the configured statistics. + /// + /// + /// Name of the browser + /// IWebDriver + /// + IWebDriver CreateDriverInstance(string? browserName); + + /// + /// This Function Enqueues an function + /// in which has the first parameter supports IWebDriver + /// + /// Action + /// + /// manager = new SeleniumManager(); + /// manager.EnqueueAction(SomeFunction); + /// string SomeFunction(IWebDriver driver) { } + /// + /// TaskCompletionSource string + /// Task EnqueueAction(Func action); + + /// + /// This function checks if the browser is available or not. + /// + /// Name of the browser + /// + /// + string GetAvailableDriverName(string? browserName); + + /// + /// This Function override Enqueues an function + /// in which has the first parameter supports IWebDriver and the other is the brousername you want + /// + /// + /// Name of the brouser for example check WebDriverType enum + /// + /// + Task EnqueueAction(Func action, string browserName); + #endregion + } } diff --git a/SeleniumManager.Core/SeleniumManager.Core.csproj b/SeleniumManager.Core/SeleniumManager.Core.csproj index 133f9fe..65c7fc4 100644 --- a/SeleniumManager.Core/SeleniumManager.Core.csproj +++ b/SeleniumManager.Core/SeleniumManager.Core.csproj @@ -14,16 +14,17 @@ This include managing Queues, Parallel Tests, etc. MIT License True - 0.0.0.3 - 0.0.0.3-alpha + 0.0.0.4 + 0.0.0.4-alpha + True - 1701;1702;8618 + 1701;1702;8618;1591 - 1701;1702;8618 + 1701;1702;8618;1591 @@ -50,7 +51,7 @@ This include managing Queues, Parallel Tests, etc. - + diff --git a/SeleniumManager.Core/SeleniumManager.cs b/SeleniumManager.Core/SeleniumManager.cs index 8819550..a404c83 100644 --- a/SeleniumManager.Core/SeleniumManager.cs +++ b/SeleniumManager.Core/SeleniumManager.cs @@ -1,10 +1,7 @@ -using Newtonsoft.Json.Linq; -using OpenQA.Selenium; -using OpenQA.Selenium.Chrome; -using OpenQA.Selenium.Edge; -using OpenQA.Selenium.Firefox; +using OpenQA.Selenium; using OpenQA.Selenium.Remote; using SeleniumManager.Core.DataContract; +using SeleniumManager.Core.Enum; using SeleniumManager.Core.Interface; using SeleniumManager.Core.Utils; using System; @@ -12,8 +9,10 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Reflection; using System.Text; using System.Threading.Tasks; +using static SeleniumManager.Core.SeleniumManager; namespace SeleniumManager.Core { @@ -21,12 +20,15 @@ public class SeleniumManager: ISeleniumManager { #region Declerations + #region Private Properties private readonly SemaphoreSlim _semaphore; private readonly SemaphoreSlim _availableStereotypesSemaphore = new SemaphoreSlim(1, 1); - private readonly ConcurrentQueue> _queue; + private readonly ConcurrentQueue _queue; private readonly ConfigurationSettings _configSettings; private readonly HttpClient httpClient; + #endregion + #region Public Properties public int MaxSessions { get; private set; } = 0; public int FreeSessions { get; private set; } = 0; public int ConcurrentSessions { get; private set; } = 0; @@ -36,11 +38,9 @@ public class SeleniumManager: ISeleniumManager public Dictionary ConcurrentStereotypes { get; private set; } = new(); public Dictionary AvailableStereotypes { get; private set; } = new(); public DateTime LastSessionDetails { get; private set; } - enum AdjustType - { - Create = 1, - Destroy = 2 - } + public delegate void ActionWithBrowser(IWebDriver driver, string? browserName); + #endregion + #endregion #region Constructor @@ -49,7 +49,7 @@ public SeleniumManager(ConfigManager configManager) _configSettings = configManager.configSettings; httpClient = new HttpClient(); _semaphore = new SemaphoreSlim(GetAvailableInstances().Result,1000); - _queue = new ConcurrentQueue>(); + _queue = new ConcurrentQueue(); } #endregion @@ -60,7 +60,7 @@ public virtual Task EnqueueAction(Func action) { var tcs = new TaskCompletionSource(); - _queue.Enqueue(driver => + _queue.Enqueue((driver,n) => { try { @@ -86,21 +86,60 @@ public virtual Task EnqueueAction(Func action) TryExecuteNext(); return tcs.Task; } + + public virtual Task EnqueueAction(Func action, string browserName) + { + var tcs = new TaskCompletionSource(); + + _queue.Enqueue((driver,bn) => + { + try + { + bn = browserName; + + // Execute the action and get the result + var result = action(driver); + + // Set the result as the task completion result + tcs.SetResult(result); + } + catch (Exception ex) + { + // Set the exception as the task completion exception + tcs.SetException(ex); + throw new Exception("Error Occoured inside Action", ex); + } + finally + { + // Dispose the driver if not already done + driver?.Dispose(); + } + }); + + TryExecuteNext(); + return tcs.Task; + } + public async void TryExecuteNext() { await _semaphore.WaitAsync(); // Acquire the semaphore if (_queue.TryDequeue(out var action)) { - string browserName = ""; + string? browserName = null; + if (action.Target != null) + { + FieldInfo browserNameField = action.Target.GetType().GetField("browserName"); + browserName = (string?)browserNameField?.GetValue(action.Target); + } // TODO: make it like get the driver first and then process the action try { // for now only using chrome for testing - IWebDriver _driver = CreateDriverInstance(); + IWebDriver _driver = CreateDriverInstance(browserName); ICapabilities capabilities = ((RemoteWebDriver)_driver).Capabilities; browserName = capabilities.GetCapability("browserName").ToString(); - action(_driver); + action(_driver, browserName); // Release driver _driver.Dispose(); @@ -129,8 +168,11 @@ public async void TryExecuteNext() } } } + public virtual async Task GetAvailableInstances() { + LastSessionDetails = DateTime.Now; + var nodeStatus = await GetStatus(); if (nodeStatus == null) return 0; @@ -149,6 +191,7 @@ public virtual async Task GetAvailableInstances() return nodeStatus; } + public virtual IWebDriver CreateDriverInstance(string? browserName = null) { IWebDriver driver; @@ -156,27 +199,41 @@ public virtual IWebDriver CreateDriverInstance(string? browserName = null) switch (browserName.ToLower()) { case "firefox": + case "geko": driver = new RemoteWebDriver(new Uri(_configSettings.GridHost.ToString()), _configSettings.Options.firefoxOptions); - break; + case "chrome": driver = new RemoteWebDriver(new Uri(_configSettings.GridHost.ToString()), _configSettings.Options.chromeOptions); break; + case "microsoftedge": driver = new RemoteWebDriver(new Uri(_configSettings.GridHost.ToString()), _configSettings.Options.edgeOptions); break; + case "safari": driver = new RemoteWebDriver(new Uri(_configSettings.GridHost.ToString()), _configSettings.Options.safariOptions); break; + + // Not Tested case "ie": + case "internetexplorer": + case "internet explorer": driver = new RemoteWebDriver(new Uri(_configSettings.GridHost.ToString()), _configSettings.Options.internetExplorerOptions); break; + + // Not Tested + case "opera": + driver = new RemoteWebDriver(new Uri(_configSettings.GridHost.ToString()), _configSettings.Options.operaOptions); + break; + default: throw new ArgumentException("Browser not supported yet!"); } return driver; } + public string GetAvailableDriverName(string? browserName) { // check if last session was gotten in last 1 min get from config default 1 min @@ -256,7 +313,6 @@ private void getSessions(dynamic nodeStatus) _availableStereotypesSemaphore.Release(); ConcurrentSessions = TotalSessions - FreeSessions; AvailableSessions = MaxSessions - ConcurrentSessions; - LastSessionDetails = DateTime.Now; } private void ResetValues() @@ -298,7 +354,7 @@ private async Task FindBestAvailableBrowser() } // If no available browser is found, return the browser with the highest instances count - return statistics.OrderByDescending(x => x.Value).FirstOrDefault().Key ?? "Chrome"; + return statistics.OrderByDescending(x => x.Value).FirstOrDefault().Key ?? WebDriverType.Chrome.GetDescription(); } private void AdjustInstance(string key,AdjustType type) diff --git a/SeleniumManager.Core/Utils/WebDriverTypeExtensions.cs b/SeleniumManager.Core/Utils/WebDriverTypeExtensions.cs new file mode 100644 index 0000000..618e030 --- /dev/null +++ b/SeleniumManager.Core/Utils/WebDriverTypeExtensions.cs @@ -0,0 +1,46 @@ +using SeleniumManager.Core.Enum; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + + +public static class WebDriverTypeExtensions +{ + private static readonly Dictionary customDescriptions = new Dictionary(); + + public static string GetDescription(this WebDriverType value) + { + if (customDescriptions.TryGetValue(value, out string customDescription)) + { + return customDescription; + } + + Type enumType = value.GetType(); + string name = Enum.GetName(enumType, value); + + if (name != null) + { + FieldInfo field = enumType.GetField(name); + if (field != null) + { + DescriptionAttribute attr = field.GetCustomAttribute(); + if (attr != null) + { + return attr.Description; + } + } + } + + return value.ToString(); + } + + public static void SetCustomDescription(WebDriverType type, string description) + { + customDescriptions[type] = description; + } +} + diff --git a/SeleniumManager.Tests/BrowsingTest.cs b/SeleniumManager.Tests/BrowsingTest.cs index 3bdca2a..c92ff79 100644 --- a/SeleniumManager.Tests/BrowsingTest.cs +++ b/SeleniumManager.Tests/BrowsingTest.cs @@ -31,6 +31,16 @@ public async Task TestBrouse() } + [TestMethod] + public async Task TestBrouseChrome() + { + var data = await _seleniumManager.EnqueueAction(BrouseWebsite, "chrome"); + + // Start processing the actions + _seleniumManager.TryExecuteNext(); + + } + [TestMethod] public async Task ParallelTestBrouse() { diff --git a/SeleniumManager.Tests/SeleniumManager.Tests.csproj b/SeleniumManager.Tests/SeleniumManager.Tests.csproj index fb51eb8..9ac9041 100644 --- a/SeleniumManager.Tests/SeleniumManager.Tests.csproj +++ b/SeleniumManager.Tests/SeleniumManager.Tests.csproj @@ -1,4 +1,4 @@ - + net7.0 @@ -14,6 +14,7 @@ +