Skip to content

Commit

Permalink
Fix XPath generation in ElementFactory for non-xpath base locators +s…
Browse files Browse the repository at this point in the history
…emver: 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
  • Loading branch information
mialeska authored May 26, 2020
1 parent cc03ffb commit d0c1979
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Aquality.Selenium.Core" Version="1.0.1" />
<PackageReference Include="Aquality.Selenium.Core" Version="1.1.0" />
<PackageReference Include="NLog" Version="4.7.0" />
<PackageReference Include="Selenium.Support" Version="3.141.0" />
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
Expand Down
58 changes: 56 additions & 2 deletions Aquality.Selenium/src/Aquality.Selenium/Elements/ElementFactory.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -17,7 +20,14 @@ namespace Aquality.Selenium.Elements
/// </summary>
public class ElementFactory : CoreFactory, IElementFactory
{
public ElementFactory(IConditionalWait conditionalWait, IElementFinder elementFinder, ILocalizationManager localizationManager)
private static readonly IDictionary<string, string> LocatorToXPathTemplateMap = new Dictionary<string, string>
{
{ "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)
{
}
Expand Down Expand Up @@ -78,5 +88,49 @@ protected override IDictionary<Type, Type> ElementTypesMap
};
}
}

/// <summary>
/// Generates xpath locator for target element
/// </summary>
/// <param name="baseLocator">locator of parent element</param>
/// <param name="webElement">target element</param>
/// <param name="elementIndex">index of target element</param>
/// <returns>target element's locator</returns>
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<string>(
JavaScript.GetElementXPath.GetScript(), webElement), message: "XPath generation failed"));
}

/// <summary>
/// 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.
/// </summary>
/// <param name="locator">locator to transform</param>
/// <returns>true if the locator can be transformed to xpath, false otherwise.</returns>
protected override bool IsLocatorSupportedForXPathExtraction(By locator)
{
return LocatorToXPathTemplateMap.Keys.Any(locType => locator.ToString().StartsWith(locType))
|| base.IsLocatorSupportedForXPathExtraction(locator);
}

/// <summary>
/// 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.
/// </summary>
/// <param name="locator">locator to get xpath from.</param>
/// <returns>extracted XPath.</returns>
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());
}
}
}
53 changes: 42 additions & 11 deletions Aquality.Selenium/src/Aquality.Selenium/Forms/Form.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -22,15 +24,7 @@ protected Form(By locator, string name)
Name = name;
}

/// <summary>
/// Locator of specified form.
/// </summary>
public By Locator { get; }

/// <summary>
/// Name of specified form.
/// </summary>
public string Name { get; }
private ILabel FormLabel => ElementFactory.GetLabel(Locator, Name);

/// <summary>
/// Instance of logger <see cref="Logging.Logger">
Expand All @@ -44,6 +38,16 @@ protected Form(By locator, string name)
/// <value>Element factory.</value>
protected IElementFactory ElementFactory => AqualityServices.Get<IElementFactory>();

/// <summary>
/// Locator of specified form.
/// </summary>
public By Locator { get; }

/// <summary>
/// Name of specified form.
/// </summary>
public string Name { get; }

/// <summary>
/// Return form state for form locator
/// </summary>
Expand All @@ -56,8 +60,6 @@ protected Form(By locator, string name)
/// </summary>
public Size Size => FormLabel.GetElement().Size;

private ILabel FormLabel => ElementFactory.GetLabel(Locator, Name);

/// <summary>
/// Scroll form without scrolling entire page
/// </summary>
Expand All @@ -67,5 +69,34 @@ public void ScrollBy(int x, int y)
{
FormLabel.JsActions.ScrollBy(x, y);
}

/// <summary>
/// Finds child element of current form by its locator.
/// </summary>
/// <typeparam name="T">Type of child element that has to implement IElement.</typeparam>
/// <param name="childLocator">Locator of child element relative to form.</param>
/// <param name="name">Child element name.</param>
/// <param name="supplier">Delegate that defines constructor of child element in case of custom element.</param>
/// <param name="state">Child element state.</param>
/// <returns>Instance of child element.</returns>
protected T FindChildElement<T>(By childLocator, string name = null, ElementSupplier<T> supplier = null, ElementState state = ElementState.Displayed) where T : Core.Elements.Interfaces.IElement
{
return FormLabel.FindChildElement(childLocator, name, supplier, state);
}

/// <summary>
/// Finds child elements of current form by their locator.
/// </summary>
/// <typeparam name="T">Type of child elements that has to implement IElement.</typeparam>
/// <param name="childLocator">Locator of child elements relative to form.</param>
/// <param name="name">Child elements name.</param>
/// <param name="supplier">Delegate that defines constructor of child element in case of custom element type.</param>
/// <param name="expectedCount">Expected number of elements that have to be found (zero, more then zero, any).</param>
/// <param name="state">Child elements state.</param>
/// <returns>List of child elements.</returns>
protected IList<T> FindChildElements<T>(By childLocator, string name = null, ElementSupplier<T> supplier = null, ElementsCount expectedCount = ElementsCount.Any, ElementState state = ElementState.Displayed) where T : Core.Elements.Interfaces.IElement
{
return FormLabel.FindChildElements(childLocator, name, supplier, expectedCount, state);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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": "Переключение на новую вкладку",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ElementState, ElementsCount, IList<Label>>[] ElementListProviders
= new Func<ElementState, ElementsCount, IList<Label>>[]
{
(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<Label> { productsForm.GetChildElementByNonXPath(state) }
};

[SetUp]
public void BeforeTest()
Expand All @@ -21,13 +36,14 @@ public void BeforeTest()
[Test]
public void Should_BePossibleTo_CheckThatHiddenElementExists()
{
Assert.IsTrue(sliderForm.GetAddToCartBtn(ElementState.ExistsInAnyState).State.IsExist);
Assert.IsTrue(new SliderForm().GetAddToCartBtn(ElementState.ExistsInAnyState).State.IsExist);
}

[Test]
public void Should_BePossibleTo_CheckThatHiddenElementsExist()
public void Should_BePossibleTo_CheckThatHiddenElementsExist(
[ValueSource(nameof(ElementListProviders))] Func<ElementState, ElementsCount, IList<Label>> elementListProvider)
{
var elements = sliderForm.GetListElements(ElementState.ExistsInAnyState, ElementsCount.MoreThenZero);
var elements = elementListProvider(ElementState.ExistsInAnyState, ElementsCount.MoreThenZero);
Assert.Multiple(() =>
{
Assert.IsTrue(elements.Any());
Expand All @@ -36,9 +52,10 @@ public void Should_BePossibleTo_CheckThatHiddenElementsExist()
}

[Test]
public void Should_BePossibleTo_CheckThatHiddenElementsNotDisplayed()
public void Should_BePossibleTo_CheckThatHiddenElementsNotDisplayed(
[ValueSource(nameof(ElementListProviders))] Func<ElementState, ElementsCount, IList<Label>> elementListProvider)
{
var elements = sliderForm.GetListElements(ElementState.ExistsInAnyState, ElementsCount.MoreThenZero);
var elements = elementListProvider(ElementState.ExistsInAnyState, ElementsCount.MoreThenZero);
Assert.Multiple(() =>
{
Assert.IsTrue(elements.Any());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using Aquality.Selenium.Core.Elements;
using Aquality.Selenium.Elements;
using Aquality.Selenium.Forms;
using OpenQA.Selenium;
using System.Collections.Generic;

namespace Aquality.Selenium.Tests.Integration.TestApp.AutomationPractice.Forms
{
internal sealed class ProductTabContentForm : Form
{
private static readonly By DottedXPath = By.XPath(".//ul[@id='blockbestsellers']//li[not(@style='display:none')]");
private static readonly By BestSellersById = By.Id("blockbestsellers");
private static readonly By InputByName = By.Name("controller");
private static readonly By ItemByCssSelector = By.CssSelector(".submenu-container");
private static readonly By ItemByClassName = By.ClassName("submenu-container");

public ProductTabContentForm() : base(By.ClassName("tab-content"), "Product tab content")
{
}

public IList<Label> GetListElements(ElementState state, ElementsCount count)
{
return ElementFactory.FindElements<Label>(By.XPath("//ul[@id='blockbestsellers']//li"), state: state, expectedCount: count);
}

public IList<Label> GetListElementsById(ElementState state, ElementsCount count)
{
return ElementFactory.FindElements<Label>(BestSellersById, state: state, expectedCount: count);
}

public IList<Label> GetListElementsByName(ElementState state, ElementsCount count)
{
return ElementFactory.FindElements<Label>(InputByName, state: state, expectedCount: count);
}

public IList<Label> GetListElementsByClassName(ElementState state, ElementsCount count)
{
return ElementFactory.FindElements<Label>(ItemByClassName, state: state, expectedCount: count);
}

public IList<Label> GetListElementsByCss(ElementState state, ElementsCount count)
{
return ElementFactory.FindElements<Label>(ItemByCssSelector, state: state, expectedCount: count);
}

public Label GetChildElementByNonXPath(ElementState state)
{
return FindChildElement<Label>(BestSellersById, state: state);
}

public IList<Label> GetListElementsByDottedXPath(ElementState state, ElementsCount count)
{
return ElementFactory.FindElements<Label>(DottedXPath, state: state, expectedCount: count);
}

public IList<Label> GetChildElementsByDottedXPath(ElementState state, ElementsCount count)
{
return FindChildElements<Label>(DottedXPath, state: state, expectedCount: count);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
using Aquality.Selenium.Core.Elements;
using Aquality.Selenium.Elements;
using Aquality.Selenium.Elements.Interfaces;
using Aquality.Selenium.Forms;
using OpenQA.Selenium;
using System.Collections.Generic;
using System.Drawing;

namespace Aquality.Selenium.Tests.Integration.TestApp.AutomationPractice.Forms
Expand All @@ -23,11 +21,6 @@ public IButton GetAddToCartBtn(ElementState elementState)
return ElementFactory.GetButton(By.XPath("//ul[@id='blockbestsellers']//li[last()]//a[contains(@class, 'add_to_cart')]"), "Add to cart", elementState);
}

public IList<Label> GetListElements(ElementState state, ElementsCount count)
{
return ElementFactory.FindElements<Label>(By.XPath("//ul[@id='blockbestsellers']//li"), state: state, expectedCount: count);
}

public void ClickNextButton()
{
NextButton.ClickAndWait();
Expand Down

0 comments on commit d0c1979

Please sign in to comment.