Skip to content

Commit d0c1979

Browse files
authored
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
1 parent cc03ffb commit d0c1979

File tree

9 files changed

+192
-30
lines changed

9 files changed

+192
-30
lines changed

Aquality.Selenium/src/Aquality.Selenium/Aquality.Selenium.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
</ItemGroup>
6666

6767
<ItemGroup>
68-
<PackageReference Include="Aquality.Selenium.Core" Version="1.0.1" />
68+
<PackageReference Include="Aquality.Selenium.Core" Version="1.1.0" />
6969
<PackageReference Include="NLog" Version="4.7.0" />
7070
<PackageReference Include="Selenium.Support" Version="3.141.0" />
7171
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />

Aquality.Selenium/src/Aquality.Selenium/Elements/ElementFactory.cs

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
using Aquality.Selenium.Core.Elements;
1+
using Aquality.Selenium.Browsers;
2+
using Aquality.Selenium.Core.Elements;
23
using Aquality.Selenium.Core.Elements.Interfaces;
34
using Aquality.Selenium.Core.Localization;
45
using Aquality.Selenium.Core.Waitings;
56
using Aquality.Selenium.Elements.Interfaces;
67
using OpenQA.Selenium;
8+
using OpenQA.Selenium.Support.Extensions;
79
using System;
810
using System.Collections.Generic;
11+
using System.Linq;
912
using CoreFactory = Aquality.Selenium.Core.Elements.ElementFactory;
1013
using IElementFactory = Aquality.Selenium.Elements.Interfaces.IElementFactory;
1114

@@ -17,7 +20,14 @@ namespace Aquality.Selenium.Elements
1720
/// </summary>
1821
public class ElementFactory : CoreFactory, IElementFactory
1922
{
20-
public ElementFactory(IConditionalWait conditionalWait, IElementFinder elementFinder, ILocalizationManager localizationManager)
23+
private static readonly IDictionary<string, string> LocatorToXPathTemplateMap = new Dictionary<string, string>
24+
{
25+
{ "By.ClassName", "//*[contains(@class,'{0}')]" },
26+
{ "By.Name", "//*[@name='{0}']" },
27+
{ "By.Id", "//*[@id='{0}']" }
28+
};
29+
30+
public ElementFactory(IConditionalWait conditionalWait, IElementFinder elementFinder, ILocalizationManager localizationManager)
2131
: base(conditionalWait, elementFinder, localizationManager)
2232
{
2333
}
@@ -78,5 +88,49 @@ protected override IDictionary<Type, Type> ElementTypesMap
7888
};
7989
}
8090
}
91+
92+
/// <summary>
93+
/// Generates xpath locator for target element
94+
/// </summary>
95+
/// <param name="baseLocator">locator of parent element</param>
96+
/// <param name="webElement">target element</param>
97+
/// <param name="elementIndex">index of target element</param>
98+
/// <returns>target element's locator</returns>
99+
protected override By GenerateXpathLocator(By baseLocator, IWebElement webElement, int elementIndex)
100+
{
101+
return IsLocatorSupportedForXPathExtraction(baseLocator)
102+
? base.GenerateXpathLocator(baseLocator, webElement, elementIndex)
103+
: By.XPath(ConditionalWait.WaitFor(driver => driver.ExecuteJavaScript<string>(
104+
JavaScript.GetElementXPath.GetScript(), webElement), message: "XPath generation failed"));
105+
}
106+
107+
/// <summary>
108+
/// Defines is the locator can be transformed to xpath or not.
109+
/// Current implementation works only with ByXPath.class and ByTagName locator types,
110+
/// but you can implement your own for the specific WebDriver type.
111+
/// </summary>
112+
/// <param name="locator">locator to transform</param>
113+
/// <returns>true if the locator can be transformed to xpath, false otherwise.</returns>
114+
protected override bool IsLocatorSupportedForXPathExtraction(By locator)
115+
{
116+
return LocatorToXPathTemplateMap.Keys.Any(locType => locator.ToString().StartsWith(locType))
117+
|| base.IsLocatorSupportedForXPathExtraction(locator);
118+
}
119+
120+
/// <summary>
121+
/// Extracts XPath from passed locator.
122+
/// Current implementation works only with ByXPath.class and ByTagName locator types,
123+
/// but you can implement your own for the specific WebDriver type.
124+
/// </summary>
125+
/// <param name="locator">locator to get xpath from.</param>
126+
/// <returns>extracted XPath.</returns>
127+
protected override string ExtractXPathFromLocator(By locator)
128+
{
129+
var locatorString = locator.ToString();
130+
var supportedLocatorType = LocatorToXPathTemplateMap.Keys.FirstOrDefault(locType => locatorString.StartsWith(locType));
131+
return supportedLocatorType == null
132+
? base.ExtractXPathFromLocator(locator)
133+
: string.Format(LocatorToXPathTemplateMap[supportedLocatorType], locatorString.Substring(locatorString.IndexOf(':') + 1).Trim());
134+
}
81135
}
82136
}

Aquality.Selenium/src/Aquality.Selenium/Forms/Form.cs

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using System.Drawing;
44
using Aquality.Selenium.Browsers;
55
using Aquality.Selenium.Core.Localization;
6+
using Aquality.Selenium.Core.Elements;
7+
using System.Collections.Generic;
68

79
namespace Aquality.Selenium.Forms
810
{
@@ -22,15 +24,7 @@ protected Form(By locator, string name)
2224
Name = name;
2325
}
2426

25-
/// <summary>
26-
/// Locator of specified form.
27-
/// </summary>
28-
public By Locator { get; }
29-
30-
/// <summary>
31-
/// Name of specified form.
32-
/// </summary>
33-
public string Name { get; }
27+
private ILabel FormLabel => ElementFactory.GetLabel(Locator, Name);
3428

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

41+
/// <summary>
42+
/// Locator of specified form.
43+
/// </summary>
44+
public By Locator { get; }
45+
46+
/// <summary>
47+
/// Name of specified form.
48+
/// </summary>
49+
public string Name { get; }
50+
4751
/// <summary>
4852
/// Return form state for form locator
4953
/// </summary>
@@ -56,8 +60,6 @@ protected Form(By locator, string name)
5660
/// </summary>
5761
public Size Size => FormLabel.GetElement().Size;
5862

59-
private ILabel FormLabel => ElementFactory.GetLabel(Locator, Name);
60-
6163
/// <summary>
6264
/// Scroll form without scrolling entire page
6365
/// </summary>
@@ -67,5 +69,34 @@ public void ScrollBy(int x, int y)
6769
{
6870
FormLabel.JsActions.ScrollBy(x, y);
6971
}
72+
73+
/// <summary>
74+
/// Finds child element of current form by its locator.
75+
/// </summary>
76+
/// <typeparam name="T">Type of child element that has to implement IElement.</typeparam>
77+
/// <param name="childLocator">Locator of child element relative to form.</param>
78+
/// <param name="name">Child element name.</param>
79+
/// <param name="supplier">Delegate that defines constructor of child element in case of custom element.</param>
80+
/// <param name="state">Child element state.</param>
81+
/// <returns>Instance of child element.</returns>
82+
protected T FindChildElement<T>(By childLocator, string name = null, ElementSupplier<T> supplier = null, ElementState state = ElementState.Displayed) where T : Core.Elements.Interfaces.IElement
83+
{
84+
return FormLabel.FindChildElement(childLocator, name, supplier, state);
85+
}
86+
87+
/// <summary>
88+
/// Finds child elements of current form by their locator.
89+
/// </summary>
90+
/// <typeparam name="T">Type of child elements that has to implement IElement.</typeparam>
91+
/// <param name="childLocator">Locator of child elements relative to form.</param>
92+
/// <param name="name">Child elements name.</param>
93+
/// <param name="supplier">Delegate that defines constructor of child element in case of custom element type.</param>
94+
/// <param name="expectedCount">Expected number of elements that have to be found (zero, more then zero, any).</param>
95+
/// <param name="state">Child elements state.</param>
96+
/// <returns>List of child elements.</returns>
97+
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
98+
{
99+
return FormLabel.FindChildElements(childLocator, name, supplier, expectedCount, state);
100+
}
70101
}
71102
}

Aquality.Selenium/src/Aquality.Selenium/Resources/Localization/en.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@
5757
"loc.waitinvisible": "Wait until element is not visible",
5858
"loc.waitnotexists": "Wait until element does not exist in DOM during {0} seconds",
5959
"loc.no.elements.found.in.state": "No elements with locator '{0}' found in {1} state",
60-
"loc.elements.were.found.but.not.in.state": "Elements were found by locator '{0}'. But {1}",
60+
"loc.no.elements.found.by.locator": "No elements were found by locator '{0}'",
61+
"loc.elements.were.found.but.not.in.state": "Elements were found by locator '{0}' but not in desired state {1}",
62+
"loc.elements.found.but.should.not": "No elements should be found by locator '{0}' in {1} state",
63+
"loc.search.of.elements.failed": "Search of element by locator '{0}' failed",
6164
"loc.browser.switch.to.tab.handle": "Switching to tab by handle '{0}'",
6265
"loc.browser.switch.to.tab.index": "Switching to tab by index '{0}'",
6366
"loc.browser.switch.to.new.tab": "Switching to new tab",

Aquality.Selenium/src/Aquality.Selenium/Resources/Localization/ru.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@
5656
"loc.waitinvisible": "Ожидаем пока элемент исчезнет",
5757
"loc.waitnotexists": "Ожидаем исчезновения элемента из DOM в течении {0}",
5858
"loc.no.elements.found.in.state": "Не удалось найти элементов по локатору '{0}' в {1} состоянии",
59-
"loc.elements.were.found.but.not.in.state": "Удалось найти элементы по локатору '{0}'. Но {1}",
59+
"loc.no.elements.found.by.locator": "Не удалось найти элементов по локатору '{0}'",
60+
"loc.elements.were.found.but.not.in.state": "Удалось найти элементы по локатору '{0}', но они не в желаемом состоянии {1}",
61+
"loc.elements.found.but.should.not": "Не должно быть найдено элементов по локатору '{0}' в {1} состоянии",
62+
"loc.search.of.elements.failed": "Поиск элемента по локатору '{0}' прошел неудачно",
6063
"loc.browser.switch.to.tab.handle": "Переключение на новую вкладку по дескриптору '{0}'",
6164
"loc.browser.switch.to.tab.index": "Переключение на новую вкладку по индексу '{0}'",
6265
"loc.browser.switch.to.new.tab": "Переключение на новую вкладку",

Aquality.Selenium/tests/Aquality.Selenium.Tests/Aquality.Selenium.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
<PrivateAssets>all</PrivateAssets>
3131
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
3232
</PackageReference>
33-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
33+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
3434
</ItemGroup>
3535

3636
<ItemGroup>

Aquality.Selenium/tests/Aquality.Selenium.Tests/Integration/HiddenElementsTests.cs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,31 @@
11
using Aquality.Selenium.Browsers;
22
using Aquality.Selenium.Core.Elements;
3+
using Aquality.Selenium.Elements;
34
using Aquality.Selenium.Tests.Integration.TestApp;
45
using Aquality.Selenium.Tests.Integration.TestApp.AutomationPractice.Forms;
56
using NUnit.Framework;
67
using System;
8+
using System.Collections.Generic;
79
using System.Linq;
810

911
namespace Aquality.Selenium.Tests.Integration
1012
{
1113
internal class HiddenElementsTests : UITest
1214
{
13-
private readonly SliderForm sliderForm = new SliderForm();
15+
private static readonly ProductTabContentForm productsForm = new ProductTabContentForm();
16+
17+
private static readonly Func<ElementState, ElementsCount, IList<Label>>[] ElementListProviders
18+
= new Func<ElementState, ElementsCount, IList<Label>>[]
19+
{
20+
(state, count) => productsForm.GetListElements(state, count),
21+
(state, count) => productsForm.GetListElementsById(state, count),
22+
(state, count) => productsForm.GetListElementsByName(state, count),
23+
(state, count) => productsForm.GetListElementsByClassName(state, count),
24+
(state, count) => productsForm.GetListElementsByCss(state, count),
25+
(state, count) => productsForm.GetListElementsByDottedXPath(state, count),
26+
(state, count) => productsForm.GetChildElementsByDottedXPath(state, count),
27+
(state, count) => new List<Label> { productsForm.GetChildElementByNonXPath(state) }
28+
};
1429

1530
[SetUp]
1631
public void BeforeTest()
@@ -21,13 +36,14 @@ public void BeforeTest()
2136
[Test]
2237
public void Should_BePossibleTo_CheckThatHiddenElementExists()
2338
{
24-
Assert.IsTrue(sliderForm.GetAddToCartBtn(ElementState.ExistsInAnyState).State.IsExist);
39+
Assert.IsTrue(new SliderForm().GetAddToCartBtn(ElementState.ExistsInAnyState).State.IsExist);
2540
}
2641

2742
[Test]
28-
public void Should_BePossibleTo_CheckThatHiddenElementsExist()
43+
public void Should_BePossibleTo_CheckThatHiddenElementsExist(
44+
[ValueSource(nameof(ElementListProviders))] Func<ElementState, ElementsCount, IList<Label>> elementListProvider)
2945
{
30-
var elements = sliderForm.GetListElements(ElementState.ExistsInAnyState, ElementsCount.MoreThenZero);
46+
var elements = elementListProvider(ElementState.ExistsInAnyState, ElementsCount.MoreThenZero);
3147
Assert.Multiple(() =>
3248
{
3349
Assert.IsTrue(elements.Any());
@@ -36,9 +52,10 @@ public void Should_BePossibleTo_CheckThatHiddenElementsExist()
3652
}
3753

3854
[Test]
39-
public void Should_BePossibleTo_CheckThatHiddenElementsNotDisplayed()
55+
public void Should_BePossibleTo_CheckThatHiddenElementsNotDisplayed(
56+
[ValueSource(nameof(ElementListProviders))] Func<ElementState, ElementsCount, IList<Label>> elementListProvider)
4057
{
41-
var elements = sliderForm.GetListElements(ElementState.ExistsInAnyState, ElementsCount.MoreThenZero);
58+
var elements = elementListProvider(ElementState.ExistsInAnyState, ElementsCount.MoreThenZero);
4259
Assert.Multiple(() =>
4360
{
4461
Assert.IsTrue(elements.Any());
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using Aquality.Selenium.Core.Elements;
2+
using Aquality.Selenium.Elements;
3+
using Aquality.Selenium.Forms;
4+
using OpenQA.Selenium;
5+
using System.Collections.Generic;
6+
7+
namespace Aquality.Selenium.Tests.Integration.TestApp.AutomationPractice.Forms
8+
{
9+
internal sealed class ProductTabContentForm : Form
10+
{
11+
private static readonly By DottedXPath = By.XPath(".//ul[@id='blockbestsellers']//li[not(@style='display:none')]");
12+
private static readonly By BestSellersById = By.Id("blockbestsellers");
13+
private static readonly By InputByName = By.Name("controller");
14+
private static readonly By ItemByCssSelector = By.CssSelector(".submenu-container");
15+
private static readonly By ItemByClassName = By.ClassName("submenu-container");
16+
17+
public ProductTabContentForm() : base(By.ClassName("tab-content"), "Product tab content")
18+
{
19+
}
20+
21+
public IList<Label> GetListElements(ElementState state, ElementsCount count)
22+
{
23+
return ElementFactory.FindElements<Label>(By.XPath("//ul[@id='blockbestsellers']//li"), state: state, expectedCount: count);
24+
}
25+
26+
public IList<Label> GetListElementsById(ElementState state, ElementsCount count)
27+
{
28+
return ElementFactory.FindElements<Label>(BestSellersById, state: state, expectedCount: count);
29+
}
30+
31+
public IList<Label> GetListElementsByName(ElementState state, ElementsCount count)
32+
{
33+
return ElementFactory.FindElements<Label>(InputByName, state: state, expectedCount: count);
34+
}
35+
36+
public IList<Label> GetListElementsByClassName(ElementState state, ElementsCount count)
37+
{
38+
return ElementFactory.FindElements<Label>(ItemByClassName, state: state, expectedCount: count);
39+
}
40+
41+
public IList<Label> GetListElementsByCss(ElementState state, ElementsCount count)
42+
{
43+
return ElementFactory.FindElements<Label>(ItemByCssSelector, state: state, expectedCount: count);
44+
}
45+
46+
public Label GetChildElementByNonXPath(ElementState state)
47+
{
48+
return FindChildElement<Label>(BestSellersById, state: state);
49+
}
50+
51+
public IList<Label> GetListElementsByDottedXPath(ElementState state, ElementsCount count)
52+
{
53+
return ElementFactory.FindElements<Label>(DottedXPath, state: state, expectedCount: count);
54+
}
55+
56+
public IList<Label> GetChildElementsByDottedXPath(ElementState state, ElementsCount count)
57+
{
58+
return FindChildElements<Label>(DottedXPath, state: state, expectedCount: count);
59+
}
60+
}
61+
}

Aquality.Selenium/tests/Aquality.Selenium.Tests/Integration/TestApp/AutomationPractice/Forms/SliderForm.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
using Aquality.Selenium.Core.Elements;
2-
using Aquality.Selenium.Elements;
32
using Aquality.Selenium.Elements.Interfaces;
43
using Aquality.Selenium.Forms;
54
using OpenQA.Selenium;
6-
using System.Collections.Generic;
75
using System.Drawing;
86

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

26-
public IList<Label> GetListElements(ElementState state, ElementsCount count)
27-
{
28-
return ElementFactory.FindElements<Label>(By.XPath("//ul[@id='blockbestsellers']//li"), state: state, expectedCount: count);
29-
}
30-
3124
public void ClickNextButton()
3225
{
3326
NextButton.ClickAndWait();

0 commit comments

Comments
 (0)