D41197_Explore Issue Fixed#3935
Conversation
WalkthroughThe changes in this pull request focus on enhancing the Changes
Possibly related PRs
Suggested reviewers
Poem
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Outside diff range and nitpick comments (26)
Ginger/GingerCoreNET/Drivers/CoreDrivers/Web/Selenium/SeleniumDriver.cs (26)
Line range hint
1-77: Consider organizing imports and using statementsThe file starts with a large number of using statements. Consider organizing these into logical groups (e.g., System namespaces, Selenium namespaces, project-specific namespaces) and potentially using global using statements in a separate file to reduce clutter.
global using System; global using System.Collections.Generic; global using System.Linq; // ... other commonly used namespaces // In this file: using Amdocs.Ginger.Common; using OpenQA.Selenium; // ... other specific namespaces
Line range hint
79-85: Consider splitting large class into smaller, focused classesThe
SeleniumDriverclass is very large and seems to have multiple responsibilities. Consider splitting it into smaller, more focused classes to improve maintainability and readability.For example, you could create separate classes for:
- Browser management
- Element location and interaction
- JavaScript execution
- Network monitoring
Line range hint
87-185: Refactor enum declarationsThe enum declarations at the beginning of the class could be moved to separate files or a dedicated "Enums" class to improve organization.
Create a new file
SeleniumEnums.cs:public static class SeleniumEnums { public enum eBrowserType { ... } public enum ePageLoadStrategy { ... } public enum eUnhandledPromptBehavior { ... } public enum eBrowserLogLevel { ... } }Then use these enums in the
SeleniumDriverclass.
Line range hint
187-326: Consider using auto-implemented propertiesMany of the properties in this section could be simplified using auto-implemented properties. This would reduce code clutter and improve readability.
For example:
public string ProxyAutoConfigUrl { get; set; } public bool EnableNativeEvents { get; set; } = false; public bool AutoDetect { get; set; } = true; // ... other properties
Line range hint
328-372: Improve error handling in BrowserType propertyThe
BrowserTypeproperty uses a switch statement for both getter and setter. Consider using a dictionary or enum mapping to simplify this logic and make it more maintainable.private static readonly Dictionary<WebBrowserType, eBrowserType> BrowserTypeMapping = new() { { WebBrowserType.Chrome, eBrowserType.Chrome }, { WebBrowserType.FireFox, eBrowserType.FireFox }, // ... other mappings }; public override WebBrowserType BrowserType { get => BrowserTypeMapping.FirstOrDefault(x => x.Value == mBrowserType).Key; set { if (BrowserTypeMapping.TryGetValue(value, out var browserType)) { mBrowserType = browserType; } else { throw new ArgumentException($"Unsupported browser type: {value}"); } } }
Line range hint
374-403: Consider using dependency injection for DriverThe
Driverproperty is protected and seems to be a core component of this class. Consider using dependency injection to provide theIWebDriverinstance, which would improve testability and flexibility.public class SeleniumDriver { private readonly IWebDriver _driver; public SeleniumDriver(IWebDriver driver) { _driver = driver ?? throw new ArgumentNullException(nameof(driver)); } // Use _driver instead of Driver throughout the class }
Line range hint
405-456: Simplify InitDriver methodThe
InitDrivermethod could be simplified by using a switch expression instead of if-else statements.public override void InitDriver(Agent agent) { if (BrowserType != WebBrowserType.RemoteWebDriver) { return; } if (agent.DriverConfiguration == null) { agent.DriverConfiguration = new ObservableList<DriverConfigParam>(); } (RemoteGridHub, RemoteBrowserName, RemotePlatform, RemoteVersion) = ( agent.GetParamValue(RemoteGridHubParam), agent.GetParamValue(RemoteBrowserNameParam), agent.GetParamValue(RemotePlatformParam), agent.GetParamValue(RemoteVersionParam) ); if (WorkSpace.Instance.BetaFeatures.ShowHealenium) { (IsHealenium, HealeniumUrl) = (agent.Healenium, agent.HealeniumURL); } }
Line range hint
458-483: Consider using a factory pattern for browser creationThe
StartDrivermethod is quite long and complex. Consider using a factory pattern to create different browser instances based on themBrowserType.Create a
BrowserFactoryclass:public class BrowserFactory { public IWebDriver CreateBrowser(eBrowserType browserType, BrowserOptions options) { return browserType switch { eBrowserType.Chrome => CreateChromeDriver(options), eBrowserType.FireFox => CreateFirefoxDriver(options), // ... other browser types _ => throw new ArgumentException($"Unsupported browser type: {browserType}") }; } private IWebDriver CreateChromeDriver(BrowserOptions options) { ... } private IWebDriver CreateFirefoxDriver(BrowserOptions options) { ... } // ... other creation methods }Then use this factory in the
StartDrivermethod.
Line range hint
485-1037: Refactor StartDriver methodThe
StartDrivermethod is extremely long and complex. It should be broken down into smaller, more focused methods for each browser type and common setup steps.public override void StartDriver() { SetupProxy(); SetupImplicitWait(); SetupSeleniumArguments(); try { Driver = CreateDriverForBrowserType(); SetupBrowserWindow(); SetupTimeouts(); InitializeDefaultState(); } catch (Exception ex) { HandleDriverStartupException(ex); } } private IWebDriver CreateDriverForBrowserType() { return mBrowserType switch { eBrowserType.IE => CreateInternetExplorerDriver(), eBrowserType.FireFox => CreateFirefoxDriver(), eBrowserType.Chrome => CreateChromeDriver(), // ... other browser types _ => throw new NotSupportedException($"Browser type {mBrowserType} is not supported.") }; } private IWebDriver CreateInternetExplorerDriver() { ... } private IWebDriver CreateFirefoxDriver() { ... } private IWebDriver CreateChromeDriver() { ... } // ... other creation methods private void SetupBrowserWindow() { ... } private void SetupTimeouts() { ... } private void InitializeDefaultState() { ... } private void HandleDriverStartupException(Exception ex) { ... }
Line range hint
1039-1106: Simplify UpdateDriver methodThe
UpdateDrivermethod could be simplified by using pattern matching and reducing nested if statements.private string UpdateDriver(eBrowserType browserType) { try { Reporter.ToLog(eLogLevel.INFO, $"Attempting to update {browserType} driver to latest using System Proxy Settings...."); DriverOptions driverOptions = browserType switch { eBrowserType.Chrome => new ChromeOptions(), eBrowserType.Edge => new EdgeOptions(), eBrowserType.FireFox => new FirefoxOptions(), _ => throw new NotSupportedException($"Browser type {browserType} is not supported for driver update.") }; SetupProxyForDriverUpdate(driverOptions); SetBrowserVersion(driverOptions); var driverFinder = new DriverFinder(driverOptions); var driverPath = driverFinder.GetDriverPath(); Reporter.ToLog(eLogLevel.INFO, $"Updated {browserType} driver to latest and placed in {driverPath}."); return driverPath; } catch (Exception ex) { HandleDriverUpdateException(ex, browserType); throw; } } private void SetupProxyForDriverUpdate(DriverOptions driverOptions) { string systemProxy = OperatingSystemBase.GetSystemProxy(); if (!string.IsNullOrEmpty(systemProxy)) { var proxy = new Proxy { Kind = ProxyKind.Manual, HttpProxy = systemProxy, SslProxy = systemProxy }; driverOptions.Proxy = proxy; } } private void HandleDriverUpdateException(Exception ex, eBrowserType browserType) { if (!WorkSpace.Instance.RunningInExecutionMode && !WorkSpace.Instance.RunningFromUnitTest) { Reporter.ToUser(eUserMsgKey.FailedToDownloadDriver, browserType); } Reporter.ToLog(eLogLevel.ERROR, string.Format(Reporter.UserMsgsPool[eUserMsgKey.FailedToDownloadDriver].Message, browserType), ex); }
Line range hint
1108-1134: Simplify GetDriverPath methodThe
GetDriverPathmethod can be simplified to a one-liner.public string GetDriverPath(eBrowserType browserType) => UpdateDriver(browserType);
Line range hint
1136-1155: Improve error handling in CloseDriverProcessThe
CloseDriverProcessmethod swallows all exceptions. Consider logging the exception or handling specific exception types.private static void CloseDriverProcess(DriverService driverService) { if (driverService?.ProcessId == 0) { return; } try { Reporter.ToLog(eLogLevel.DEBUG, "Closing Driver process which was left open after failure to start driver."); var process = System.Diagnostics.Process.GetProcessById(driverService.ProcessId); process?.Kill(); } catch (ArgumentException ex) { Reporter.ToLog(eLogLevel.DEBUG, $"Process with ID {driverService.ProcessId} not found. It may have already been closed.", ex); } catch (Exception ex) { Reporter.ToLog(eLogLevel.ERROR, $"Failed to close driver process with ID {driverService.ProcessId}", ex); } }
Line range hint
1157-1186: Simplify GetDriversPathPerOS methodThe
GetDriversPathPerOSmethod can be simplified using pattern matching and removing redundant conditions.public string GetDriversPathPerOS() { string basePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); return Environment.OSVersion.Platform switch { PlatformID.Win32NT when Use64Bitbrowser && (mBrowserType == eBrowserType.IE || mBrowserType == eBrowserType.FireFox) => Path.Combine(basePath, "Win64"), PlatformID.Win32NT or PlatformID.Unix or PlatformID.MacOSX => basePath, _ => throw new PlatformNotSupportedException($"The '{RuntimeInformation.OSDescription}' OS is not supported by Ginger Selenium") }; }
Line range hint
1188-1218: Simplify DriverServiceFileName methodThe
DriverServiceFileNamemethod can be simplified using pattern matching.private string DriverServiceFileName(string fileName) { return Environment.OSVersion.Platform switch { PlatformID.Win32NT => $"{fileName}.exe", PlatformID.Unix or PlatformID.MacOSX => fileName, _ => throw new PlatformNotSupportedException($"The '{RuntimeInformation.OSDescription}' OS is not supported by Ginger Selenium") }; }
Line range hint
1220-1223: Simplify AddByPassAddress methodThe
AddByPassAddressmethod can be simplified to a one-liner.private string[] AddByPassAddress() => ByPassProxy.Split(';');
Line range hint
1225-1279: Refactor SetProxy methodThe
SetProxymethod can be simplified by using a switch expression and extracting the proxy configuration logic.private void SetProxy(dynamic options) { if (mProxy == null) { return; } var proxy = new Proxy { Kind = mProxy.Kind }; options.Proxy = proxy; ConfigureProxy(proxy); } private void ConfigureProxy(Proxy proxy) { switch (proxy.Kind) { case ProxyKind.Manual: proxy.HttpProxy = mProxy.HttpProxy; proxy.SslProxy = mProxy.SslProxy; if (!string.IsNullOrEmpty(ByPassProxy)) { proxy.AddBypassAddresses(AddByPassAddress()); } break; case ProxyKind.ProxyAutoConfigure: proxy.ProxyAutoConfigUrl = mProxy.ProxyAutoConfigUrl; break; case ProxyKind.AutoDetect: case ProxyKind.System: case ProxyKind.Direct: // No additional configuration needed break; default: proxy.Kind = ProxyKind.System; break; } }
Line range hint
1281-1314: Simplify CheckifPageLoaded methodThe
CheckifPageLoadedmethod can be simplified by using a more concise lambda expression.void CheckifPageLoaded() { WebDriverWait webDriverWait = new WebDriverWait(Driver, TimeSpan.FromSeconds(ImplicitWait)); webDriverWait.Until(driver => { var jQueryComplete = ((IJavaScriptExecutor)driver).ExecuteScript("return window.jQuery && jQuery.active == 0") as bool? ?? true; var documentReady = ((IJavaScriptExecutor)driver).ExecuteScript("return document.readyState").Equals("complete"); return documentReady && jQueryComplete; }); }
Line range hint
1316-1321: Simplify GetCurrentElement methodThe
GetCurrentElementmethod can be simplified by using pattern matching and removing redundant null checks.public override Act GetCurrentElement() { try { IWebElement currentElement = Driver.SwitchTo().ActiveElement(); return currentElement.TagName.ToLower() switch { "input" => HandleInputElement(currentElement), "a" => getActLink(currentElement), _ => null }; } catch (Exception ex) { Reporter.ToLog(eLogLevel.ERROR, "Exception occurred while getting current element", ex); return null; } } private Act HandleInputElement(IWebElement element) { return element.GetAttribute("type").ToLower() switch { "text" => getActTextBox(element), "button" or "submit" => getActButton(element), "password" => getActPassword(element), "checkbox" => getActCheckbox(element), "radio" => getActRadioButton(element), _ => null }; }
Line range hint
1323-1485: Refactor element handling methodsThe methods for handling different element types (e.g.,
getActButton,getActPassword, etc.) can be refactored to reduce code duplication and improve consistency.Create a base method for common element handling:
private Act CreateBaseAct<T>(IWebElement element) where T : Act, new() { var act = new T(); act.LocateBy = eLocateBy.ByID; act.LocateValue = element.GetAttribute("id"); return act; } private Act getActButton(IWebElement element) { return CreateBaseAct<ActButton>(element); } private Act getActPassword(IWebElement element) { var act = CreateBaseAct<ActPassword>(element); (act as ActPassword).PasswordAction = ActPassword.ePasswordAction.SetValue; act.AddOrUpdateInputParamValue("Value", element.GetAttribute("value")); act.AddOrUpdateReturnParamActual("Actual", $"Tag Name = {element.TagName}"); return act; } // Similar refactoring for other element handling methods
Line range hint
1487-1559: Refactor GotoURL methodThe
GotoURLmethod can be simplified and improved for better error handling and readability.private void GotoURL(Act act, string sURL) { if (string.IsNullOrEmpty(sURL)) { act.Error = "Error: Provided URL is empty. Please provide valid URL."; return; } try { Uri uri = new Uri(sURL.StartsWith("www", StringComparison.OrdinalIgnoreCase) ? $"http://{sURL}" : sURL); Driver.Navigate().GoToUrl(uri.AbsoluteUri); HandleCertificateError(); CheckifPageLoaded(); } catch (UriFormatException) { act.Error = "Error: Invalid URL. Please provide a valid URL (complete URL)."; } catch (Exception ex) { act.Status = Amdocs.Ginger.CoreNET.Execution.eRunStatus.Failed; act.Error = $"Error navigating to URL: {ex.Message}"; } } private void HandleCertificateError() { if (Driver is InternetExplorerDriver && Driver.Title.Contains("Certificate Error", StringComparison.OrdinalIgnoreCase)) { Thread.Sleep(100); try { Driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(1); Driver.Navigate().GoToUrl("javascript:document.getElementById('overridelink').click()"); } catch { // Ignore exceptions when handling certificate errors } finally { Driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds((int)ImplicitWait); } } }
Line range hint
1561-1566: Simplify GetURL methodThe
GetURLmethod can be simplified to a one-liner.public override string GetURL() => Driver.Url;
Line range hint
1568-2080: Refactor RunAction methodThe
RunActionmethod is very long and complex. It should be refactored into smaller, more focused methods for better maintainability and readability.public override void RunAction(Act act) { if (!ShouldRunActionDirectly(act)) { PrepareDriverForAction(act); } try { ExecuteAction(act); } catch (Exception ex) { HandleActionException(act, ex); } } private bool ShouldRunActionDirectly(Act act) { // Logic to determine if the action should be run directly } private void PrepareDriverForAction(Act act) { EnsureDriverIsAccessible(); SetImplicitWait(act); SetupBrowserMobProxy(act); } private void ExecuteAction(Act act) { switch (act) { case ActUIElement actUIElement: HandleActUIElement(actUIElement); break; case ActGotoURL actGotoURL: GotoURL(actGotoURL, actGotoURL.GetInputParamCalculatedValue("Value")); break; // ... other action types default: throw new NotSupportedException($"Action type {act.GetType()} is not supported."); } } private void HandleActionException(Act act, Exception ex) { act.Status = Amdocs.Ginger.CoreNET.Execution.eRunStatus.Failed; act.Error = ex.Message; Reporter.ToLog(eLogLevel.ERROR, $"Error executing action: {act.Description}", ex); }
Line range hint
2082-2156: Refactor element locating methodsThe methods for locating elements (e.g.,
LocateElement,LocateElementByLocators, etc.) are complex and could benefit from refactoring for better readability and maintainability.Create a separate
ElementLocatorclass to handle element location strategies:public class ElementLocator { private readonly IWebDriver _driver; private readonly ISearchContext _searchContext; public ElementLocator(IWebDriver driver, ISearchContext searchContext = null) { _driver = driver; _searchContext = searchContext ?? driver; } public IWebElement LocateElement(ElementInfo elementInfo) { if (elementInfo.LocateBy == eLocateBy.POMElement) { return LocatePOMElement(elementInfo); } return LocateElementByLocator(elementInfo.Locators.FirstOrDefault(l => l.Active)); } private IWebElement LocatePOMElement(ElementInfo elementInfo) { // POM element location logic } private IWebElement LocateElementByLocator(ElementLocator locator) { // Element location logic based on locator type } // Other helper methods for element location }Then use this class in the
SeleniumDriver:private ElementLocator _elementLocator; public SeleniumDriver() { // ... other initialization _elementLocator = new ElementLocator(Driver); } public IWebElement LocateElement(Act act) { return _elementLocator.LocateElement(act); }
Line range hint
2158-2280: Refactor GetElementTypeEnum methodThe
GetElementTypeEnummethod is complex and uses a lot of if-else statements. It can be simplified using pattern matching and a more structured approach.public static Tuple<string, eElementType> GetElementTypeEnum(IWebElement el = null, string jsType = null, HtmlNode htmlNode = null) { string elementTagName = GetElementTagName(el, jsType, htmlNode); string elementTypeAtt = GetElementTypeAttribute(el, htmlNode); eElementType elementType = GetElementType(elementTagName, elementTypeAtt); return new Tuple<string, eElementType>(elementTagName, elementType); } private static string GetElementTagName(IWebElement el, string jsType, HtmlNode htmlNode) { return el?.TagName?.ToUpper() ?? jsType ?? htmlNode?.Name ?? "INPUT"; } private static string GetElementTypeAttribute(IWebElement el, HtmlNode htmlNode) { return el?.GetAttribute("type") ?? htmlNode?.Attributes["type"]?.Value ?? string.Empty; } private static eElementType GetElementType(string elementTagName, string elementTypeAtt) { return (elementTagName, elementTypeAtt.ToUpper()) switch { ("INPUT", "TEXT") or ("INPUT", "PASSWORD") or ("INPUT", "EMAIL") or ("INPUT", "TEL") or ("INPUT", "SEARCH") or ("INPUT", "NUMBER") or ("INPUT", "URL") or ("INPUT", "DATE") => eElementType.TextBox, ("INPUT", "IMAGE") or ("INPUT", "SUBMIT") or ("INPUT", "BUTTON") => eElementType.Button, ("INPUT", "CHECKBOX") => eElementType.CheckBox, ("INPUT", "RADIO") => eElementType.RadioButton, ("TEXTAREA", _) or ("TEXT", _) => eElementType.TextBox, ("BUTTON", _) or ("RESET", _) or ("SUBMIT", _) => eElementType.Button, ("TD", _) or ("TH", _) or ("TR", _) => eElementType.TableItem, ("LINK", _) or ("A", _) or ("LI", _) => eElementType.HyperLink, ("LABEL", _) or ("TITLE", _) => eElementType.Label, ("SELECT", _) or ("SELECT-ONE", _) => eElementType.ComboBox, ("TABLE", _) or ("CAPTION", _) => eElementType.Table, ("JEDITOR.TABLE", _) => eElementType.EditorPane, ("DIV", _) => eElementType.Div, ("SPAN", _) => eElementType.Span, ("IMG", _) or ("MAP", _) => eElementType.Image, ("OPTGROUP", _) or ("OPTION", _) => eElementType.ComboBoxOption, ("IFRAME", _) or ("FRAME", _) or ("FRAMESET", _) => eElementType.Iframe, ("CANVAS", _) => eElementType.Canvas, ("FORM", _) => eElementType.Form, ("UL", _) or ("OL", _) or ("DL", _) => eElementType.List, ("LI", _) or ("DT", _) or ("DD", _) => eElementType.ListItem, ("MENU", _) => eElementType.MenuBar, ("H1", _) or ("H2", _) or ("H3", _) or ("H4", _) or ("H5", _) or ("H6", _) or ("P", _) => eElementType.Text, ("SVG", _) => eElementType.Svg, _ => eElementType.Unknown }; }
Line range hint
2282-2359: Refactor LearnElementInfoDetails methodThe
LearnElementInfoDetailsmethod is complex and performs multiple operations. It can be refactored into smaller, more focused methods for better readability and maintainability.ElementInfo IWindowExplorer.LearnElementInfoDetails(ElementInfo EI, PomSetting pomSetting) { UpdateElementType(EI); UpdateElementXPath(EI, pomSetting); UpdateElementName(EI); UpdateElementRelXPath(EI, pomSetting); UpdateElementLocators(EI, pomSetting); UpdateElementProperties(EI); return EI; } private void UpdateElementType(ElementInfo EI) { if (string.IsNullOrEmpty(EI.ElementType) || EI.ElementTypeEnum == eElementType.Unknown) { var elementTypeEnum = GetElementTypeEnum(EI.ElementObject as IWebElement); EI.ElementType = elementTypeEnum.Item1; EI.ElementTypeEnum = elementTypeEnum.Item2; } } private void UpdateElementXPath(ElementInfo EI, PomSetting pomSetting) { if (string.IsNullOrEmpty(EI.XPath) || EI.XPath == "/") { EI.XPath = GenerateXPathForElement(EI); } } private void UpdateElementName(ElementInfo EI) { EI.ElementName = GetElementName(EI as HTMLElementInfo); } private void UpdateElementRelXPath(ElementInfo EI, PomSetting pomSetting) { if (mXPathHelper == null) { InitXpathHelper(); } ((HTMLElementInfo)EI).RelXpath = mXPathHelper.GetElementRelXPath(EI, pomSetting); } private void UpdateElementLocators(ElementInfo EI, PomSetting pomSetting) { EI.Locators = ((IWindowExplorer)this).GetElementLocators(EI, pomSetting); if (EI.Locators.Any(x => x.EnableFriendlyLocator)) { EI.FriendlyLocators = ((IWindowExplorer)this).GetElementFriendlyLocators(EI, pomSetting); } } private void UpdateElementProperties(ElementInfo EI) { EI.Properties = ((IWindowExplorer)this).GetElementProperties(EI); }
Line range hint
2361-2424: Refactor GetElementName methodThe
GetElementNamemethod can be simplified and made more readable by using LINQ and reducing nested if statements.private string GetElementName(HTMLElementInfo EI) { if (EI.HTMLElementObject == null) { return string.IsNullOrEmpty(EI.Value) ? EI.ElementTypeEnumDescription : null; } var attributes = new[] { EI.HTMLElementObject.Name, EI.HTMLElementObject.Attributes["name"]?.Value, EI.HTMLElementObject.Attributes["title"]?.Value, E </blockquote></details> </blockquote></details> <details> <summary>:scroll: Review details</summary> **Configuration used: CodeRabbit UI** **Review profile: CHILL** <details> <summary>:inbox_tray: Commits</summary> Files that changed from the base of the PR and between 2304e9330db4f33dbc813e48d08846b7e8366f50 and 1ce2574381ed4d5c0eaf6274127276293807c901. </details> <details> <summary>:ledger: Files selected for processing (1)</summary> * Ginger/GingerCoreNET/Drivers/CoreDrivers/Web/Selenium/SeleniumDriver.cs (5 hunks) </details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
Thank you for your contribution.
Before submitting this PR, please make sure:
Summary by CodeRabbit
Bug Fixes
Refactor