Skip to content

Commit 3157da0

Browse files
authored
[Selenium 4] ShadowRoot functionality wrapper (#211) +semver: feature
* Implemented ShadowRoot functionality wrapper +semver: feature
1 parent 123b866 commit 3157da0

File tree

15 files changed

+263
-29
lines changed

15 files changed

+263
-29
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
</PropertyGroup>
2525

2626
<ItemGroup>
27+
<None Remove="Resources\JavaScripts\ExpandShadowRoot.js" />
2728
<None Remove="Resources\Localization\be.json" />
2829
<None Remove="Resources\Localization\en.json" />
2930
<None Remove="Resources\Localization\ru.json" />
@@ -43,6 +44,7 @@
4344
<EmbeddedResource Include="Resources\JavaScripts\GetComboBoxSelectedText.js" />
4445
<EmbeddedResource Include="Resources\JavaScripts\GetComboBoxTexts.js" />
4546
<EmbeddedResource Include="Resources\JavaScripts\GetElementByXPath.js" />
47+
<EmbeddedResource Include="Resources\JavaScripts\ExpandShadowRoot.js" />
4648
<EmbeddedResource Include="Resources\JavaScripts\GetElementText.js" />
4749
<EmbeddedResource Include="Resources\JavaScripts\GetElementXPath.js" />
4850
<EmbeddedResource Include="Resources\JavaScripts\GetTextFirstChild.js" />

Aquality.Selenium/src/Aquality.Selenium/Aquality.Selenium.xml

Lines changed: 51 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Aquality.Selenium/src/Aquality.Selenium/Browsers/JavaScript.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ public enum JavaScript
3333
SetValue,
3434
GetViewPortCoordinates,
3535
OpenNewTab,
36-
OpenInNewTab
36+
OpenInNewTab,
37+
ExpandShadowRoot
3738
}
3839

3940
/// <summary>

Aquality.Selenium/src/Aquality.Selenium/Elements/Actions/JsActions.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
using System.Linq;
66
using Aquality.Selenium.Browsers;
77
using Aquality.Selenium.Configurations;
8+
using Aquality.Selenium.Core.Elements;
89
using Aquality.Selenium.Core.Localization;
910
using Aquality.Selenium.Core.Utilities;
1011
using Aquality.Selenium.Elements.Interfaces;
12+
using OpenQA.Selenium;
1113

1214
namespace Aquality.Selenium.Elements.Actions
1315
{
@@ -34,6 +36,34 @@ public JsActions(IElement element, string elementType, ILocalizedLogger logger,
3436

3537
protected ILocalizedLogger Logger { get; }
3638

39+
/// <summary>
40+
/// Expands shadow root.
41+
/// </summary>
42+
/// <returns><see cref="ShadowRoot"/> search context.</returns>
43+
public ShadowRoot ExpandShadowRoot()
44+
{
45+
LogElementAction("loc.shadowroot.expand.js");
46+
return ExecuteScript<ShadowRoot>(JavaScript.ExpandShadowRoot);
47+
}
48+
49+
/// <summary>
50+
/// Finds element in the shadow root of the current element.
51+
/// </summary>
52+
/// <typeparam name="T">Type of the target element that has to implement <see cref="IElement"/>.</typeparam>
53+
/// <param name="locator">Locator of the target element.
54+
/// Note that some browsers don't support XPath locator for shadow elements (e.g. Chrome).</param>
55+
/// <param name="name">Name of the target element.</param>
56+
/// <param name="supplier">Delegate that defines constructor of element.</param>
57+
/// <param name="state">State of the target element.</param>
58+
/// <returns>Instance of element.</returns>
59+
public T FindElementInShadowRoot<T>(By locator, string name, ElementSupplier<T> supplier = null, ElementState state = ElementState.Displayed)
60+
where T : IElement
61+
{
62+
var shadowRootRelativeFinder = new RelativeElementFinder(Logger, AqualityServices.ConditionalWait, ExpandShadowRoot);
63+
var shadowRootFactory = new ElementFactory(AqualityServices.ConditionalWait, shadowRootRelativeFinder, AqualityServices.Get<ILocalizationManager>());
64+
return shadowRootFactory.Get(locator, name, supplier, state);
65+
}
66+
3767
/// <summary>
3868
/// Perfroms click on element and waits for page is loaded.
3969
/// </summary>

Aquality.Selenium/src/Aquality.Selenium/Elements/Element.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ protected Element(By locator, string name, ElementState state) : base(locator, n
4646

4747
protected virtual IElementFactory CustomFactory => AqualityServices.Get<IElementFactory>();
4848

49-
protected override ICoreElementFinder Finder => AqualityServices.Get<ICoreElementFinder>();
49+
protected internal virtual ICoreElementFinder CustomFinder { get; internal set; } = AqualityServices.Get<ICoreElementFinder>();
50+
51+
protected override ICoreElementFinder Finder => CustomFinder;
5052

5153
protected override IElementCacheConfiguration CacheConfiguration => AqualityServices.Get<IElementCacheConfiguration>();
5254

@@ -125,5 +127,20 @@ public void SendKey(Key key)
125127
.FirstOrDefault(field => field.Name == key.ToString())?.GetValue(null).ToString();
126128
DoWithRetry(() => GetElement().SendKeys(keysString));
127129
}
130+
131+
public ShadowRoot ExpandShadowRoot()
132+
{
133+
LogElementAction("loc.shadowroot.expand");
134+
var shadowRoot = (ShadowRoot)GetElement().GetShadowRoot();
135+
return shadowRoot;
136+
}
137+
138+
public T FindElementInShadowRoot<T>(By locator, string name, ElementSupplier<T> supplier = null, ElementState state = ElementState.Displayed)
139+
where T : IElement
140+
{
141+
var shadowRootRelativeFinder = new RelativeElementFinder(LocalizedLogger, ConditionalWait, ExpandShadowRoot);
142+
var shadowRootFactory = new ElementFactory(ConditionalWait, shadowRootRelativeFinder, LocalizationManager);
143+
return shadowRootFactory.Get(locator, name, supplier, state);
144+
}
128145
}
129146
}

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

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,37 +34,61 @@ public ElementFactory(IConditionalWait conditionalWait, IElementFinder elementFi
3434

3535
public IButton GetButton(By locator, string name, ElementState state = ElementState.Displayed)
3636
{
37-
return new Button(locator, name, state);
37+
return ResolveSupplier<IButton>()(locator, name, state);
3838
}
3939

4040
public ICheckBox GetCheckBox(By locator, string name, ElementState state = ElementState.Displayed)
4141
{
42-
return new CheckBox(locator, name, state);
42+
return ResolveSupplier<ICheckBox>()(locator, name, state);
4343
}
4444

4545
public IComboBox GetComboBox(By locator, string name, ElementState state = ElementState.Displayed)
4646
{
47-
return new ComboBox(locator, name, state);
47+
return ResolveSupplier<IComboBox>()(locator, name, state);
4848
}
4949

5050
public ILabel GetLabel(By locator, string name, ElementState state = ElementState.Displayed)
5151
{
52-
return new Label(locator, name, state);
52+
return ResolveSupplier<ILabel>()(locator, name, state);
5353
}
5454

5555
public ILink GetLink(By locator, string name, ElementState state = ElementState.Displayed)
5656
{
57-
return new Link(locator, name, state);
57+
return ResolveSupplier<ILink>()(locator, name, state);
5858
}
5959

6060
public IRadioButton GetRadioButton(By locator, string name, ElementState state = ElementState.Displayed)
6161
{
62-
return new RadioButton(locator, name, state);
62+
return ResolveSupplier<IRadioButton>()(locator, name, state);
6363
}
6464

6565
public ITextBox GetTextBox(By locator, string name, ElementState state = ElementState.Displayed)
6666
{
67-
return new TextBox(locator, name, state);
67+
return ResolveSupplier<ITextBox>()(locator, name, state);
68+
}
69+
70+
public T Get<T>(By locator, string name, ElementSupplier<T> supplier = null, ElementState state = ElementState.Displayed) where T : Interfaces.IElement
71+
{
72+
return ResolveSupplier(supplier)(locator, name, state);
73+
}
74+
75+
private ElementSupplier<T> ResolveSupplier<T>() where T : Interfaces.IElement
76+
{
77+
return ResolveSupplier<T>(null);
78+
}
79+
80+
protected override ElementSupplier<T> ResolveSupplier<T>(ElementSupplier<T> supplier)
81+
{
82+
var baseSupplier = base.ResolveSupplier(supplier);
83+
return (loc, name, state) =>
84+
{
85+
var element = baseSupplier.Invoke(loc, name, state);
86+
if (element is Element baseElement)
87+
{
88+
baseElement.CustomFinder = ElementFinder;
89+
}
90+
return element;
91+
};
6892
}
6993

7094
/// <summary>

Aquality.Selenium/src/Aquality.Selenium/Elements/Interfaces/IElement.cs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using Aquality.Selenium.Elements.Actions;
1+
using Aquality.Selenium.Core.Elements;
2+
using Aquality.Selenium.Elements.Actions;
3+
using OpenQA.Selenium;
24
using ICoreElement = Aquality.Selenium.Core.Elements.Interfaces.IElement;
35

46
namespace Aquality.Selenium.Elements.Interfaces
@@ -23,16 +25,16 @@ public interface IElement : ICoreElement
2325
/// <summary>
2426
/// Gets element text.
2527
/// </summary>
26-
/// <param name="highlightState">Should the element be hightlighted or not.
28+
/// <param name="highlightState">Should the element be highlighted or not.
2729
/// Default value is from configuration: <seealso cref="Configurations.IBrowserProfile.IsElementHighlightEnabled"/></param>
2830
/// <returns>String representation of element text.</returns>
2931
string GetText(HighlightState highlightState = HighlightState.Default);
3032

3133
/// <summary>
3234
/// Gets element attribute value by its name.
3335
/// </summary>
34-
/// <param name="attr">Name of attrbiute</param>
35-
/// <param name="highlightState">Should the element be hightlighted or not.
36+
/// <param name="attr">Name of attribute</param>
37+
/// <param name="highlightState">Should the element be highlighted or not.
3638
/// Default value is from configuration: <seealso cref="Configurations.IBrowserProfile.IsElementHighlightEnabled"/></param>
3739
/// <returns>Value of element attribute.</returns>
3840
string GetAttribute(string attr, HighlightState highlightState = HighlightState.Default);
@@ -41,7 +43,7 @@ public interface IElement : ICoreElement
4143
/// Gets css value of the element.
4244
/// </summary>
4345
/// <param name="propertyName">Name of css property</param>
44-
/// <param name="highlightState">Should the element be hightlighted or not.
46+
/// <param name="highlightState">Should the element be highlighted or not.
4547
/// Default value is from configuration: <seealso cref="Configurations.IBrowserProfile.IsElementHighlightEnabled"/></param>
4648
/// <returns>Value of element attribute.</returns>
4749
string GetCssValue(string propertyName, HighlightState highlightState = HighlightState.Default);
@@ -72,5 +74,24 @@ public interface IElement : ICoreElement
7274
/// </summary>
7375
/// <param name="key"> Key for sending.</param>
7476
void SendKey(Key key);
77+
78+
/// <summary>
79+
/// Expands shadow root.
80+
/// </summary>
81+
/// <returns><see cref="ShadowRoot"/> search context.</returns>
82+
ShadowRoot ExpandShadowRoot();
83+
84+
/// <summary>
85+
/// Finds element in the shadow root of the current element.
86+
/// </summary>
87+
/// <typeparam name="T">Type of the target element that has to implement <see cref="IElement"/>.</typeparam>
88+
/// <param name="locator">Locator of the target element.
89+
/// Note that some browsers don't support XPath locator for shadow elements.</param>
90+
/// <param name="name">Name of the target element.</param>
91+
/// <param name="supplier">Delegate that defines constructor of element.</param>
92+
/// <param name="state">State of the target element.</param>
93+
/// <returns>Instance of element.</returns>
94+
T FindElementInShadowRoot<T>(By locator, string name, ElementSupplier<T> supplier = null, ElementState state = ElementState.Displayed)
95+
where T : IElement;
7596
}
7697
}

Aquality.Selenium/src/Aquality.Selenium/Elements/Interfaces/IElementFactory.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,5 +71,16 @@ public interface IElementFactory : ICoreElementFactory
7171
/// <param name="state">Element state</param>
7272
/// <returns>Instance of element that implements ITextBox interface</returns>
7373
ITextBox GetTextBox(By locator, string name, ElementState state = ElementState.Displayed);
74+
75+
/// <summary>
76+
/// Creates element that implements <typeparamref name="T"/> interface.
77+
/// </summary>
78+
/// <typeparam name="T">Type of child element that has to implement IElement.</typeparam>
79+
/// <param name="locator">Base elements locator.</param>
80+
/// <param name="name">Elements name.</param>
81+
/// <param name="supplier">Delegate that defines constructor of element in case of custom element.</param>
82+
/// <param name="state">Elements state.</param>
83+
/// <returns></returns>
84+
T Get<T>(By locator, string name, ElementSupplier<T> supplier = null, ElementState state = ElementState.Displayed) where T : IElement;
7485
}
7586
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
return arguments[0].shadowRoot;

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,5 +84,7 @@
8484
"loc.browser.network.handler.response.add": "Дадаем апрацоўшчык сеткавых адказаў",
8585
"loc.browser.network.handler.response.clear": "Ачышчаем апрацоўшчыкі сеткавых адказаў",
8686
"loc.browser.network.monitoring.start": "Пачынаем сеткавы маніторынг",
87-
"loc.browser.network.monitoring.stop": "Спыняем сеткавы маніторынг"
87+
"loc.browser.network.monitoring.stop": "Спыняем сеткавы маніторынг",
88+
"loc.shadowroot.expand": "Разварочваем дрэва схаваных элементаў",
89+
"loc.shadowroot.expand.js": "Разварочваем дрэва схаваных элементаў праз JavaScript"
8890
}

0 commit comments

Comments
 (0)