From 6ea110f21f36e4b93e5246157d069898f4a37e72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20Miale=C5=A1ka?= Date: Fri, 22 May 2020 18:55:40 +0200 Subject: [PATCH] Implement FindChildElements for IElement +semver: feature (#72) * Implement FindChildElements in IElementsFactory, resolves #67. * Fixes #71 (dotted xpath locator split issue). * Fix GenerateXPathLocator method for ByChained type of locator. * Implement XPath extraction logic --- .../Aquality.Selenium.Core.xml | 136 ++++++++++++++---- .../Elements/Element.cs | 6 + .../Elements/ElementFactory.cs | 69 ++++++++- .../Elements/Interfaces/IElementFactory.cs | 16 ++- .../Elements/Interfaces/IParent.cs | 13 ++ .../Applications/Browser/AqualityServices.cs | 13 +- .../Browser/Elements/WebElementFactory.cs | 25 ++++ .../Browser/FindChildElementsTests.cs | 76 ++++++++++ .../Applications/Browser/FindElementsTests.cs | 45 ++++-- .../Applications/Browser/TestWithBrowser.cs | 17 ++- .../Aquality.Selenium.Core.Tests.csproj | 2 + .../Resources/GetElementXPath.js | 25 ++++ 12 files changed, 396 insertions(+), 47 deletions(-) create mode 100644 Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/Browser/Elements/WebElementFactory.cs create mode 100644 Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/Browser/FindChildElementsTests.cs create mode 100644 Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Resources/GetElementXPath.js diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Aquality.Selenium.Core.xml b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Aquality.Selenium.Core.xml index 5b02df2..dbcfe4c 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Aquality.Selenium.Core.xml +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Aquality.Selenium.Core.xml @@ -225,6 +225,32 @@ index of target element target element's locator + + + Generates absolute child locator for target element. + + parent locator + child locator relative to parent + absolute locator of the child + + + + Extracts XPath from passed locator. + Current implementation works only with ByXPath.class and ByTagName locator types, + but you can implement your own for the specific WebDriver type. + + locator to get xpath from. + extracted XPath. + + + + Defines is the locator can be transformed to xpath or not. + Current implementation works only with ByXPath.class and ByTagName locator types, + but you can implement your own for the specific WebDriver type. + + locator to transform + true if the locator can be transformed to xpath, false otherwise. + Resolves element supplier or return itself if it is not null @@ -366,11 +392,25 @@ Thrown when the supplier is null, and no constructor with required arguments was found. Instance of child element + + + Finds list of child elements by their locator relative to parent element. + + Type of child elements that has to implement IElement. + Parent element. + Locator of child elements relative to their parent. + Delegate that defines constructor of element in case of custom element type. + Expected number of elements that have to be found (zero, more then zero, any). + Child elements state. + Child elements name. + Thrown when the supplier is null, and no constructor with required arguments was found. + List of child elements. + Finds list of elements by base locator. - Type of elements that have to implement IElement. + Type of elements that has to implement IElement. Base elements locator. Delegate that defines constructor of element in case of custom elements. Expected number of elements that have to be found (zero, more then zero, any). @@ -525,6 +565,18 @@ Child element state. Instance of child element. + + + Finds child elements of current element by its locator. + + Type of child elements that has to implement IElement. + Locator of child elements relative to their parent. + Child elements name. + Delegate that defines constructor of child element in case of custom element type. + Expected number of elements that have to be found (zero, more then zero, any). + Child elements state. + List of child elements. + Implementation of for a relative supplier @@ -650,45 +702,69 @@ Message Exception + + + Instantiates the class using retry configuration. + + Retry configuration. + + + + Retries the action when one of the handledExceptions occures. + + Action to be applied. + Exceptions to be handled. + + + + Retries the action when one of the handledExceptions occures. + + Return type of function. + Function to be applied. + Exceptions to be handled. + Result of the function. + + + + Decides should the occured exception be handled (ignored during the retry) or not. + + Exceptions to be handled. + Exception to proceed. + True if the exception should be ignored, false otherwise. + Retries an action or function when occures. - + Instantiates the class using retry configuration. Retry configuration. - Exceptions to be handled. - If set to null, and will be handled. Exceptions to be ignored during action retrying. Set by the constructor parameter. - If were not passed to constructor, and will be handled. + If were not passed to constructor, + and will be handled. - - - Decides should the occured exception be handled (ignored during the retry) or not. - - Exception to proceed. - True if the exception should be ignored, false otherwise. - - + Retries the action when the handled exception occures. Action to be applied. + Exceptions to be handled. - + Retries the function when occures. Return type of function. Function to be applied. + Exceptions to be handled. Result of the function. @@ -762,31 +838,39 @@ Required file info. Text of the file. - - - Retries an action or function when occures. - - - + - Exceptions to be ignored during action retrying. - By the default implementation, and are handled. + Retries an action or function when handledExceptions occurs. - + - Retries the action when the handled exception occures. + Retries the action when one of the handledExceptions occures. Action to be applied. + Exceptions to be handled. - + - Retries the function when the handled exception occures. + Retries the action when one of the handledExceptions occures. Return type of function. Function to be applied. + Exceptions to be handled. Result of the function. + + + Retries an action or function when one of occures. + + + + + Exceptions to be ignored during action retrying. + By the default implementation, + and are handled. + + Provides methods to get info from JSON files. diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Element.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Element.cs index 928f3fb..8830206 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Element.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Element.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Aquality.Selenium.Core.Applications; using Aquality.Selenium.Core.Configurations; using Aquality.Selenium.Core.Elements.Interfaces; @@ -76,6 +77,11 @@ public T FindChildElement(By childLocator, string name = null, ElementSupplie return Factory.FindChildElement(this, childLocator, name, supplier, state); } + public IList FindChildElements(By childLocator, string name = null, ElementSupplier supplier = null, ElementsCount expectedCount = ElementsCount.Any, ElementState state = ElementState.Displayed) where T : IElement + { + return Factory.FindChildElements(this, childLocator, name, supplier, expectedCount, state); + } + public string GetAttribute(string attr) { LogElementAction("loc.el.getattr", attr); diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/ElementFactory.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/ElementFactory.cs index ed88028..51b2e4a 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/ElementFactory.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/ElementFactory.cs @@ -16,6 +16,8 @@ namespace Aquality.Selenium.Core.Elements public class ElementFactory : IElementFactory { private const string ByXpathIdentifier = "By.XPath"; + private const string ByTagNameIdentifier = "By.TagName"; + private const string TagNameXPathPrefix = "//"; public ElementFactory(IConditionalWait conditionalWait, IElementFinder elementFinder, ILocalizationManager localizationManager) { @@ -40,7 +42,12 @@ public ElementFactory(IConditionalWait conditionalWait, IElementFinder elementFi public virtual T FindChildElement(IElement parentElement, By childLocator, string name = null, ElementSupplier supplier = null, ElementState state = ElementState.Displayed) where T : IElement { var elementSupplier = ResolveSupplier(supplier); - return elementSupplier(new ByChained(parentElement.Locator, childLocator), name ?? $"Child element of {parentElement.Name}", state); + return elementSupplier(GenerateAbsoluteChildLocator(parentElement.Locator, childLocator), name ?? $"Child element of {parentElement.Name}", state); + } + + public virtual IList FindChildElements(IElement parentElement, By childLocator, string name = null, ElementSupplier supplier = null, ElementsCount expectedCount = ElementsCount.Any, ElementState state = ElementState.Displayed) where T : IElement + { + return FindElements(GenerateAbsoluteChildLocator(parentElement.Locator, childLocator), name ?? $"Child element of {parentElement.Name}", supplier, expectedCount, state); } public virtual IList FindElements(By locator, string name = null, ElementSupplier supplier = null, ElementsCount expectedCount = ElementsCount.Any, ElementState state = ElementState.Displayed) where T : IElement @@ -87,13 +94,65 @@ public virtual T GetCustomElement(ElementSupplier elementSupplier, By loca /// target element's locator protected virtual By GenerateXpathLocator(By baseLocator, IWebElement webElement, int elementIndex) { - var strBaseLocator = baseLocator.ToString(); - var elementLocator = strBaseLocator.Contains(ByXpathIdentifier) - ? $"({strBaseLocator.Split(':')[1].Trim()})[{elementIndex}]" - : throw new NotSupportedException($"Multiple elements' locator {baseLocator} is not {ByXpathIdentifier}, and is not supported yet"); + var elementLocator = IsLocatorSupportedForXPathExtraction(baseLocator) + ? $"({ExtractXPathFromLocator(baseLocator)})[{elementIndex}]" + : throw new NotSupportedException($"Multiple elements' baseLocator type {baseLocator} is not supported yet"); return By.XPath(elementLocator); } + /// + /// Generates absolute child locator for target element. + /// + /// parent locator + /// child locator relative to parent + /// absolute locator of the child + protected virtual By GenerateAbsoluteChildLocator(By parentLocator, By childLocator) + { + if (IsLocatorSupportedForXPathExtraction(parentLocator) && IsLocatorSupportedForXPathExtraction(childLocator)) + { + var childLocatorString = ExtractXPathFromLocator(childLocator); + var parentLocatorString = ExtractXPathFromLocator(parentLocator); + return By.XPath(parentLocatorString + + $"{(childLocatorString.StartsWith(".") ? childLocatorString.Substring(1) : childLocatorString)}"); + } + return new ByChained(parentLocator, childLocator); + } + + /// + /// Extracts XPath from passed locator. + /// Current implementation works only with ByXPath.class and ByTagName locator types, + /// but you can implement your own for the specific WebDriver type. + /// + /// locator to get xpath from. + /// extracted XPath. + protected virtual string ExtractXPathFromLocator(By locator) + { + var stringLocator = locator.ToString(); + string getLocatorWithoutPrefix() => stringLocator.Substring(stringLocator.IndexOf(':') + 1).Trim(); + if (stringLocator.StartsWith(ByXpathIdentifier)) + { + return getLocatorWithoutPrefix(); + } + if (stringLocator.StartsWith(ByTagNameIdentifier)) + { + return $"{TagNameXPathPrefix}{getLocatorWithoutPrefix()}"; + } + + throw new NotSupportedException($"Cannot define xpath from locator {stringLocator}. Locator type is not supported yet"); + } + + /// + /// Defines is the locator can be transformed to xpath or not. + /// Current implementation works only with ByXPath.class and ByTagName locator types, + /// but you can implement your own for the specific WebDriver type. + /// + /// locator to transform + /// true if the locator can be transformed to xpath, false otherwise. + protected virtual bool IsLocatorSupportedForXPathExtraction(By locator) + { + return locator.ToString().StartsWith(ByXpathIdentifier) || locator.ToString().StartsWith(ByTagNameIdentifier); + } + /// /// Resolves element supplier or return itself if it is not null /// diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Interfaces/IElementFactory.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Interfaces/IElementFactory.cs index 9def40f..9e40ac8 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Interfaces/IElementFactory.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Interfaces/IElementFactory.cs @@ -33,10 +33,24 @@ public interface IElementFactory /// Instance of child element T FindChildElement(IElement parentElement, By childLocator, string name = null, ElementSupplier supplier = null, ElementState state = ElementState.Displayed) where T : IElement; + /// + /// Finds list of child elements by their locator relative to parent element. + /// + /// Type of child elements that has to implement IElement. + /// Parent element. + /// Locator of child elements relative to their parent. + /// Delegate that defines constructor of element in case of custom element type. + /// Expected number of elements that have to be found (zero, more then zero, any). + /// Child elements state. + /// Child elements name. + /// Thrown when the supplier is null, and no constructor with required arguments was found. + /// List of child elements. + IList FindChildElements(IElement parentElement, By childLocator, string name = null, ElementSupplier supplier = null, ElementsCount expectedCount = ElementsCount.Any, ElementState state = ElementState.Displayed) where T : IElement; + /// /// Finds list of elements by base locator. /// - /// Type of elements that have to implement IElement. + /// Type of elements that has to implement IElement. /// Base elements locator. /// Delegate that defines constructor of element in case of custom elements. /// Expected number of elements that have to be found (zero, more then zero, any). diff --git a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Interfaces/IParent.cs b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Interfaces/IParent.cs index 856535f..e59e360 100644 --- a/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Interfaces/IParent.cs +++ b/Aquality.Selenium.Core/src/Aquality.Selenium.Core/Elements/Interfaces/IParent.cs @@ -1,4 +1,5 @@ using OpenQA.Selenium; +using System.Collections.Generic; namespace Aquality.Selenium.Core.Elements.Interfaces { @@ -17,5 +18,17 @@ public interface IParent /// Child element state. /// Instance of child element. T FindChildElement(By childLocator, string name = null, ElementSupplier supplier = null, ElementState state = ElementState.Displayed) where T : IElement; + + /// + /// Finds child elements of current element by its locator. + /// + /// Type of child elements that has to implement IElement. + /// Locator of child elements relative to their parent. + /// Child elements name. + /// Delegate that defines constructor of child element in case of custom element type. + /// Expected number of elements that have to be found (zero, more then zero, any). + /// Child elements state. + /// List of child elements. + IList FindChildElements(By childLocator, string name = null, ElementSupplier supplier = null, ElementsCount expectedCount = ElementsCount.Any, ElementState state = ElementState.Displayed) where T : IElement; } } diff --git a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/Browser/AqualityServices.cs b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/Browser/AqualityServices.cs index 6e3ac6e..554ae48 100644 --- a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/Browser/AqualityServices.cs +++ b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/Browser/AqualityServices.cs @@ -16,7 +16,18 @@ public class AqualityServices : AqualityServices public static ChromeApplication Application => GetApplication(services => StartChrome(services)); - public static IServiceProvider ServiceProvider => GetServiceProvider(services => Application); + public static IServiceProvider ServiceProvider + { + get + { + return GetServiceProvider(services => Application); + } + set + { + SetServiceProvider(value); + } + } + private static ChromeApplication StartChrome(IServiceProvider services) { diff --git a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/Browser/Elements/WebElementFactory.cs b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/Browser/Elements/WebElementFactory.cs new file mode 100644 index 0000000..e88d49c --- /dev/null +++ b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/Browser/Elements/WebElementFactory.cs @@ -0,0 +1,25 @@ +using Aquality.Selenium.Core.Elements; +using Aquality.Selenium.Core.Elements.Interfaces; +using Aquality.Selenium.Core.Localization; +using Aquality.Selenium.Core.Utilities; +using Aquality.Selenium.Core.Waitings; +using OpenQA.Selenium; +using OpenQA.Selenium.Support.Extensions; + +namespace Aquality.Selenium.Core.Tests.Applications.Browser.Elements +{ + internal class WebElementFactory : ElementFactory + { + public WebElementFactory(IConditionalWait conditionalWait, IElementFinder elementFinder, ILocalizationManager localizationManager) : base(conditionalWait, elementFinder, localizationManager) + { + } + + protected override By GenerateXpathLocator(By baseLocator, IWebElement webElement, int elementIndex) + { + return baseLocator.ToString().StartsWith("By.XPath") + ? base.GenerateXpathLocator(baseLocator, webElement, elementIndex) + : By.XPath(AqualityServices.Application.Driver.ExecuteJavaScript( + FileReader.GetTextFromEmbeddedResource("Resources.GetElementXPath.js"), webElement)); + } + } +} diff --git a/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/Browser/FindChildElementsTests.cs b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/Browser/FindChildElementsTests.cs new file mode 100644 index 0000000..ae7eabc --- /dev/null +++ b/Aquality.Selenium.Core/tests/Aquality.Selenium.Core.Tests/Applications/Browser/FindChildElementsTests.cs @@ -0,0 +1,76 @@ +using Aquality.Selenium.Core.Elements; +using Aquality.Selenium.Core.Tests.Applications.WindowsApp.Elements; +using NUnit.Framework; +using OpenQA.Selenium; +using System.Collections.Generic; +using System.Linq; + +namespace Aquality.Selenium.Core.Tests.Applications.Browser +{ + public class FindChildElementsTests : FindElementsTests + { + private readonly Label customParent = new Label(By.XPath("//div[contains(@class,'figure')]"), + "custom parent", ElementState.ExistsInAnyState); + protected override By HiddenElementsLoc => By.XPath(".//h5"); + protected override By DisplayedElementsLoc => By.XPath(".//img[@alt='User Avatar']"); + protected override By NotExistElementLoc => By.XPath(".//div[@class='testtest']"); + + private static readonly By[] SupportedLocators = new By[] + { + By.XPath("//img"), + By.XPath(".//img"), + By.TagName("img") + }; + + protected override IList FindElements(By locator, string name = null, ElementSupplier supplier = null, + ElementsCount expectedCount = ElementsCount.Any, ElementState state = ElementState.Displayed) + { + return ParentElement.FindChildElements(locator, name, supplier, expectedCount, state); + } + + [Test] + public void Should_GetCorrectNumberOfChilds_ForRelativeChildLocator( + [ValueSource(nameof(SupportedLocators))] By childRelativeLocator) + { + var expectedCount = 3; + var elementsCount = ElementFactory.FindChildElements