From d0c19792793233bf285c183816fac521aecc5667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20Miale=C5=A1ka?= Date: Tue, 26 May 2020 16:18:34 +0200 Subject: [PATCH] Fix XPath generation in ElementFactory for non-xpath base locators +semver: feature (#184) * Update Aquality.Selenium.Core version * Add FindChildElements to Form to resolve #187 +semver: feature * Fix XPath generation in ElementFactory for non-xpath base locators * Add missed localization values * Handle some locator types to generate more productive XPath for multiple elements --- .../Aquality.Selenium.csproj | 2 +- .../Elements/ElementFactory.cs | 58 +++++++++++++++++- .../src/Aquality.Selenium/Forms/Form.cs | 53 ++++++++++++---- .../Resources/Localization/en.json | 5 +- .../Resources/Localization/ru.json | 5 +- .../Aquality.Selenium.Tests.csproj | 2 +- .../Integration/HiddenElementsTests.cs | 29 +++++++-- .../Forms/ProductTabContentForm.cs | 61 +++++++++++++++++++ .../AutomationPractice/Forms/SliderForm.cs | 7 --- 9 files changed, 192 insertions(+), 30 deletions(-) create mode 100644 Aquality.Selenium/tests/Aquality.Selenium.Tests/Integration/TestApp/AutomationPractice/Forms/ProductTabContentForm.cs diff --git a/Aquality.Selenium/src/Aquality.Selenium/Aquality.Selenium.csproj b/Aquality.Selenium/src/Aquality.Selenium/Aquality.Selenium.csproj index 662f36d1..ebe10df6 100644 --- a/Aquality.Selenium/src/Aquality.Selenium/Aquality.Selenium.csproj +++ b/Aquality.Selenium/src/Aquality.Selenium/Aquality.Selenium.csproj @@ -65,7 +65,7 @@ - + diff --git a/Aquality.Selenium/src/Aquality.Selenium/Elements/ElementFactory.cs b/Aquality.Selenium/src/Aquality.Selenium/Elements/ElementFactory.cs index ad0eccc2..dc0fc06d 100644 --- a/Aquality.Selenium/src/Aquality.Selenium/Elements/ElementFactory.cs +++ b/Aquality.Selenium/src/Aquality.Selenium/Elements/ElementFactory.cs @@ -1,11 +1,14 @@ -using Aquality.Selenium.Core.Elements; +using Aquality.Selenium.Browsers; +using Aquality.Selenium.Core.Elements; using Aquality.Selenium.Core.Elements.Interfaces; using Aquality.Selenium.Core.Localization; using Aquality.Selenium.Core.Waitings; using Aquality.Selenium.Elements.Interfaces; using OpenQA.Selenium; +using OpenQA.Selenium.Support.Extensions; using System; using System.Collections.Generic; +using System.Linq; using CoreFactory = Aquality.Selenium.Core.Elements.ElementFactory; using IElementFactory = Aquality.Selenium.Elements.Interfaces.IElementFactory; @@ -17,7 +20,14 @@ namespace Aquality.Selenium.Elements /// public class ElementFactory : CoreFactory, IElementFactory { - public ElementFactory(IConditionalWait conditionalWait, IElementFinder elementFinder, ILocalizationManager localizationManager) + private static readonly IDictionary LocatorToXPathTemplateMap = new Dictionary + { + { "By.ClassName", "//*[contains(@class,'{0}')]" }, + { "By.Name", "//*[@name='{0}']" }, + { "By.Id", "//*[@id='{0}']" } + }; + + public ElementFactory(IConditionalWait conditionalWait, IElementFinder elementFinder, ILocalizationManager localizationManager) : base(conditionalWait, elementFinder, localizationManager) { } @@ -78,5 +88,49 @@ protected override IDictionary ElementTypesMap }; } } + + /// + /// Generates xpath locator for target element + /// + /// locator of parent element + /// target element + /// index of target element + /// target element's locator + protected override By GenerateXpathLocator(By baseLocator, IWebElement webElement, int elementIndex) + { + return IsLocatorSupportedForXPathExtraction(baseLocator) + ? base.GenerateXpathLocator(baseLocator, webElement, elementIndex) + : By.XPath(ConditionalWait.WaitFor(driver => driver.ExecuteJavaScript( + JavaScript.GetElementXPath.GetScript(), webElement), message: "XPath generation failed")); + } + + /// + /// 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 override bool IsLocatorSupportedForXPathExtraction(By locator) + { + return LocatorToXPathTemplateMap.Keys.Any(locType => locator.ToString().StartsWith(locType)) + || base.IsLocatorSupportedForXPathExtraction(locator); + } + + /// + /// 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 override string ExtractXPathFromLocator(By locator) + { + var locatorString = locator.ToString(); + var supportedLocatorType = LocatorToXPathTemplateMap.Keys.FirstOrDefault(locType => locatorString.StartsWith(locType)); + return supportedLocatorType == null + ? base.ExtractXPathFromLocator(locator) + : string.Format(LocatorToXPathTemplateMap[supportedLocatorType], locatorString.Substring(locatorString.IndexOf(':') + 1).Trim()); + } } } diff --git a/Aquality.Selenium/src/Aquality.Selenium/Forms/Form.cs b/Aquality.Selenium/src/Aquality.Selenium/Forms/Form.cs index ccac756a..c78ac162 100644 --- a/Aquality.Selenium/src/Aquality.Selenium/Forms/Form.cs +++ b/Aquality.Selenium/src/Aquality.Selenium/Forms/Form.cs @@ -3,6 +3,8 @@ using System.Drawing; using Aquality.Selenium.Browsers; using Aquality.Selenium.Core.Localization; +using Aquality.Selenium.Core.Elements; +using System.Collections.Generic; namespace Aquality.Selenium.Forms { @@ -22,15 +24,7 @@ protected Form(By locator, string name) Name = name; } - /// - /// Locator of specified form. - /// - public By Locator { get; } - - /// - /// Name of specified form. - /// - public string Name { get; } + private ILabel FormLabel => ElementFactory.GetLabel(Locator, Name); /// /// Instance of logger @@ -44,6 +38,16 @@ protected Form(By locator, string name) /// Element factory. protected IElementFactory ElementFactory => AqualityServices.Get(); + /// + /// Locator of specified form. + /// + public By Locator { get; } + + /// + /// Name of specified form. + /// + public string Name { get; } + /// /// Return form state for form locator /// @@ -56,8 +60,6 @@ protected Form(By locator, string name) /// public Size Size => FormLabel.GetElement().Size; - private ILabel FormLabel => ElementFactory.GetLabel(Locator, Name); - /// /// Scroll form without scrolling entire page /// @@ -67,5 +69,34 @@ public void ScrollBy(int x, int y) { FormLabel.JsActions.ScrollBy(x, y); } + + /// + /// Finds child element of current form by its locator. + /// + /// Type of child element that has to implement IElement. + /// Locator of child element relative to form. + /// Child element name. + /// Delegate that defines constructor of child element in case of custom element. + /// Child element state. + /// Instance of child element. + protected T FindChildElement(By childLocator, string name = null, ElementSupplier supplier = null, ElementState state = ElementState.Displayed) where T : Core.Elements.Interfaces.IElement + { + return FormLabel.FindChildElement(childLocator, name, supplier, state); + } + + /// + /// Finds child elements of current form by their locator. + /// + /// Type of child elements that has to implement IElement. + /// Locator of child elements relative to form. + /// 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. + protected IList FindChildElements(By childLocator, string name = null, ElementSupplier supplier = null, ElementsCount expectedCount = ElementsCount.Any, ElementState state = ElementState.Displayed) where T : Core.Elements.Interfaces.IElement + { + return FormLabel.FindChildElements(childLocator, name, supplier, expectedCount, state); + } } } diff --git a/Aquality.Selenium/src/Aquality.Selenium/Resources/Localization/en.json b/Aquality.Selenium/src/Aquality.Selenium/Resources/Localization/en.json index d1f5c9fa..44c97a1e 100644 --- a/Aquality.Selenium/src/Aquality.Selenium/Resources/Localization/en.json +++ b/Aquality.Selenium/src/Aquality.Selenium/Resources/Localization/en.json @@ -57,7 +57,10 @@ "loc.waitinvisible": "Wait until element is not visible", "loc.waitnotexists": "Wait until element does not exist in DOM during {0} seconds", "loc.no.elements.found.in.state": "No elements with locator '{0}' found in {1} state", - "loc.elements.were.found.but.not.in.state": "Elements were found by locator '{0}'. But {1}", + "loc.no.elements.found.by.locator": "No elements were found by locator '{0}'", + "loc.elements.were.found.but.not.in.state": "Elements were found by locator '{0}' but not in desired state {1}", + "loc.elements.found.but.should.not": "No elements should be found by locator '{0}' in {1} state", + "loc.search.of.elements.failed": "Search of element by locator '{0}' failed", "loc.browser.switch.to.tab.handle": "Switching to tab by handle '{0}'", "loc.browser.switch.to.tab.index": "Switching to tab by index '{0}'", "loc.browser.switch.to.new.tab": "Switching to new tab", diff --git a/Aquality.Selenium/src/Aquality.Selenium/Resources/Localization/ru.json b/Aquality.Selenium/src/Aquality.Selenium/Resources/Localization/ru.json index 83361fc4..1f556fe7 100644 --- a/Aquality.Selenium/src/Aquality.Selenium/Resources/Localization/ru.json +++ b/Aquality.Selenium/src/Aquality.Selenium/Resources/Localization/ru.json @@ -56,7 +56,10 @@ "loc.waitinvisible": "Îæèäàåì ïîêà ýëåìåíò èñ÷åçíåò", "loc.waitnotexists": "Îæèäàåì èñ÷åçíîâåíèÿ ýëåìåíòà èç DOM â òå÷åíèè {0}", "loc.no.elements.found.in.state": "Íå óäàëîñü íàéòè ýëåìåíòîâ ïî ëîêàòîðó '{0}' â {1} ñîñòîÿíèè", - "loc.elements.were.found.but.not.in.state": "Óäàëîñü íàéòè ýëåìåíòû ïî ëîêàòîðó '{0}'. Íî {1}", + "loc.no.elements.found.by.locator": "Íå óäàëîñü íàéòè ýëåìåíòîâ ïî ëîêàòîðó '{0}'", + "loc.elements.were.found.but.not.in.state": "Óäàëîñü íàéòè ýëåìåíòû ïî ëîêàòîðó '{0}', íî îíè íå â æåëàåìîì ñîñòîÿíèè {1}", + "loc.elements.found.but.should.not": "Íå äîëæíî áûòü íàéäåíî ýëåìåíòîâ ïî ëîêàòîðó '{0}' â {1} ñîñòîÿíèè", + "loc.search.of.elements.failed": "Ïîèñê ýëåìåíòà ïî ëîêàòîðó '{0}' ïðîøåë íåóäà÷íî", "loc.browser.switch.to.tab.handle": "Ïåðåêëþ÷åíèå íà íîâóþ âêëàäêó ïî äåñêðèïòîðó '{0}'", "loc.browser.switch.to.tab.index": "Ïåðåêëþ÷åíèå íà íîâóþ âêëàäêó ïî èíäåêñó '{0}'", "loc.browser.switch.to.new.tab": "Ïåðåêëþ÷åíèå íà íîâóþ âêëàäêó", diff --git a/Aquality.Selenium/tests/Aquality.Selenium.Tests/Aquality.Selenium.Tests.csproj b/Aquality.Selenium/tests/Aquality.Selenium.Tests/Aquality.Selenium.Tests.csproj index b7ec5075..c0304843 100644 --- a/Aquality.Selenium/tests/Aquality.Selenium.Tests/Aquality.Selenium.Tests.csproj +++ b/Aquality.Selenium/tests/Aquality.Selenium.Tests/Aquality.Selenium.Tests.csproj @@ -30,7 +30,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Aquality.Selenium/tests/Aquality.Selenium.Tests/Integration/HiddenElementsTests.cs b/Aquality.Selenium/tests/Aquality.Selenium.Tests/Integration/HiddenElementsTests.cs index c9a30b30..0484566a 100644 --- a/Aquality.Selenium/tests/Aquality.Selenium.Tests/Integration/HiddenElementsTests.cs +++ b/Aquality.Selenium/tests/Aquality.Selenium.Tests/Integration/HiddenElementsTests.cs @@ -1,16 +1,31 @@ using Aquality.Selenium.Browsers; using Aquality.Selenium.Core.Elements; +using Aquality.Selenium.Elements; using Aquality.Selenium.Tests.Integration.TestApp; using Aquality.Selenium.Tests.Integration.TestApp.AutomationPractice.Forms; using NUnit.Framework; using System; +using System.Collections.Generic; using System.Linq; namespace Aquality.Selenium.Tests.Integration { internal class HiddenElementsTests : UITest { - private readonly SliderForm sliderForm = new SliderForm(); + private static readonly ProductTabContentForm productsForm = new ProductTabContentForm(); + + private static readonly Func>[] ElementListProviders + = new Func>[] + { + (state, count) => productsForm.GetListElements(state, count), + (state, count) => productsForm.GetListElementsById(state, count), + (state, count) => productsForm.GetListElementsByName(state, count), + (state, count) => productsForm.GetListElementsByClassName(state, count), + (state, count) => productsForm.GetListElementsByCss(state, count), + (state, count) => productsForm.GetListElementsByDottedXPath(state, count), + (state, count) => productsForm.GetChildElementsByDottedXPath(state, count), + (state, count) => new List