Skip to content

Commit 043bb18

Browse files
nvborisenkodiemol
andauthored
[dotnet] Implementation of event wrapped shadow root element (#12073)
* First implementation of events for shadow root * Added unit test for EventFiringShadowRoot --------- Co-authored-by: Diego Molina <[email protected]>
1 parent fbb0996 commit 043bb18

3 files changed

Lines changed: 267 additions & 1 deletion

File tree

dotnet/src/support/Events/EventFiringWebDriver.cs

Lines changed: 154 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
using System.Collections.Generic;
2121
using System.Collections.ObjectModel;
2222
using System.Drawing;
23-
using OpenQA.Selenium.Internal;
2423

2524
namespace OpenQA.Selenium.Support.Events
2625
{
@@ -101,6 +100,16 @@ public EventFiringWebDriver(IWebDriver parentDriver)
101100
/// </summary>
102101
public event EventHandler<FindElementEventArgs> FindElementCompleted;
103102

103+
/// <summary>
104+
/// Fires before the driver starts to get a shadow root.
105+
/// </summary>
106+
public event EventHandler<GetShadowRootEventArgs> GettingShadowRoot;
107+
108+
/// <summary>
109+
/// Fires after the driver completes getting a shadow root.
110+
/// </summary>
111+
public event EventHandler<GetShadowRootEventArgs> GetShadowRootCompleted;
112+
104113
/// <summary>
105114
/// Fires before a script is executed.
106115
/// </summary>
@@ -728,6 +737,30 @@ protected virtual void OnFindElementCompleted(FindElementEventArgs e)
728737
}
729738
}
730739

740+
/// <summary>
741+
/// Raises the <see cref="OnGettingShadowRoot"/> event.
742+
/// </summary>
743+
/// <param name="e">A <see cref="GetShadowRootEventArgs"/> that contains the event data.</param>
744+
protected virtual void OnGettingShadowRoot(GetShadowRootEventArgs e)
745+
{
746+
if (this.GettingShadowRoot != null)
747+
{
748+
this.GettingShadowRoot(this, e);
749+
}
750+
}
751+
752+
/// <summary>
753+
/// Raises the <see cref="OnGetShadowRootCompleted"/> event.
754+
/// </summary>
755+
/// <param name="e">A <see cref="GetShadowRootEventArgs"/> that contains the event data.</param>
756+
protected virtual void OnGetShadowRootCompleted(GetShadowRootEventArgs e)
757+
{
758+
if (this.GetShadowRootCompleted != null)
759+
{
760+
this.GetShadowRootCompleted(this, e);
761+
}
762+
}
763+
731764
/// <summary>
732765
/// Raises the <see cref="ScriptExecuting"/> event.
733766
/// </summary>
@@ -1613,7 +1646,11 @@ public ISearchContext GetShadowRoot()
16131646
ISearchContext shadowRoot = null;
16141647
try
16151648
{
1649+
GetShadowRootEventArgs e = new GetShadowRootEventArgs(this.parentDriver.WrappedDriver, this.underlyingElement);
1650+
this.parentDriver.OnGettingShadowRoot(e);
16161651
shadowRoot = this.underlyingElement.GetShadowRoot();
1652+
this.parentDriver.OnGetShadowRootCompleted(e);
1653+
shadowRoot = new EventFiringShadowRoot(this.parentDriver, shadowRoot);
16171654
}
16181655
catch (Exception ex)
16191656
{
@@ -1726,5 +1763,121 @@ public override int GetHashCode()
17261763
return this.underlyingElement.GetHashCode();
17271764
}
17281765
}
1766+
1767+
/// <summary>
1768+
/// EventFiringShadowElement allows you to have access to specific shadow elements
1769+
/// </summary>
1770+
private class EventFiringShadowRoot : ISearchContext, IWrapsDriver
1771+
{
1772+
private ISearchContext underlyingSearchContext;
1773+
private EventFiringWebDriver parentDriver;
1774+
1775+
/// <summary>
1776+
/// Initializes a new instance of the <see cref="EventFiringShadowRoot"/> class.
1777+
/// </summary>
1778+
/// <param name="driver">The <see cref="EventFiringWebDriver"/> instance hosting this element.</param>
1779+
/// <param name="searchContext">The <see cref="ISearchContext"/> to wrap for event firing.</param>
1780+
public EventFiringShadowRoot(EventFiringWebDriver driver, ISearchContext searchContext)
1781+
{
1782+
this.underlyingSearchContext = searchContext;
1783+
this.parentDriver = driver;
1784+
}
1785+
1786+
/// <summary>
1787+
/// Gets the underlying wrapped <see cref="ISearchContext"/>.
1788+
/// </summary>
1789+
public ISearchContext WrappedSearchContext
1790+
{
1791+
get { return this.underlyingSearchContext; }
1792+
}
1793+
1794+
/// <summary>
1795+
/// Gets the underlying parent wrapped <see cref="IWebDriver"/>
1796+
/// </summary>
1797+
public IWebDriver WrappedDriver
1798+
{
1799+
get { return this.parentDriver; }
1800+
}
1801+
1802+
/// <summary>
1803+
/// Finds the first element in the page that matches the <see cref="By"/> object
1804+
/// </summary>
1805+
/// <param name="by">By mechanism to find the element</param>
1806+
/// <returns>IWebElement object so that you can interaction that object</returns>
1807+
public IWebElement FindElement(By by)
1808+
{
1809+
IWebElement wrappedElement = null;
1810+
try
1811+
{
1812+
GetShadowRootEventArgs e = new GetShadowRootEventArgs(this.parentDriver.WrappedDriver, this.underlyingSearchContext);
1813+
this.parentDriver.OnGettingShadowRoot(e);
1814+
IWebElement element = this.underlyingSearchContext.FindElement(by);
1815+
this.parentDriver.OnGetShadowRootCompleted(e);
1816+
wrappedElement = new EventFiringWebElement(this.parentDriver, element);
1817+
}
1818+
catch (Exception ex)
1819+
{
1820+
this.parentDriver.OnException(new WebDriverExceptionEventArgs(this.parentDriver, ex));
1821+
throw;
1822+
}
1823+
1824+
return wrappedElement;
1825+
}
1826+
1827+
/// <summary>
1828+
/// Finds the elements on the page by using the <see cref="By"/> object and returns a ReadOnlyCollection of the Elements on the page
1829+
/// </summary>
1830+
/// <param name="by">By mechanism to find the element</param>
1831+
/// <returns>ReadOnlyCollection of IWebElement</returns>
1832+
public ReadOnlyCollection<IWebElement> FindElements(By by)
1833+
{
1834+
List<IWebElement> wrappedElementList = new List<IWebElement>();
1835+
try
1836+
{
1837+
GetShadowRootEventArgs e = new GetShadowRootEventArgs(this.parentDriver.WrappedDriver, this.underlyingSearchContext);
1838+
this.parentDriver.OnGettingShadowRoot(e);
1839+
ReadOnlyCollection<IWebElement> elements = this.underlyingSearchContext.FindElements(by);
1840+
this.parentDriver.OnGetShadowRootCompleted(e);
1841+
foreach (IWebElement element in elements)
1842+
{
1843+
IWebElement wrappedElement = this.parentDriver.WrapElement(element);
1844+
wrappedElementList.Add(wrappedElement);
1845+
}
1846+
}
1847+
catch (Exception ex)
1848+
{
1849+
this.parentDriver.OnException(new WebDriverExceptionEventArgs(this.parentDriver, ex));
1850+
throw;
1851+
}
1852+
1853+
return wrappedElementList.AsReadOnly();
1854+
}
1855+
1856+
/// <summary>
1857+
/// Determines whether the specified <see cref="EventFiringShadowRoot"/> is equal to the current <see cref="EventFiringShadowRoot"/>.
1858+
/// </summary>
1859+
/// <param name="obj">The <see cref="EventFiringWebElement"/> to compare to the current <see cref="EventFiringShadowRoot"/>.</param>
1860+
/// <returns><see langword="true"/> if the specified <see cref="EventFiringShadowRoot"/> is equal to the current <see cref="EventFiringShadowRoot"/>; otherwise, <see langword="false"/>.</returns>
1861+
public override bool Equals(object obj)
1862+
{
1863+
ISearchContext other = obj as ISearchContext;
1864+
1865+
if (other == null)
1866+
{
1867+
return false;
1868+
}
1869+
1870+
return underlyingSearchContext.Equals(other);
1871+
}
1872+
1873+
/// <summary>
1874+
/// Return the hash code for this <see cref="EventFiringWebElement"/>.
1875+
/// </summary>
1876+
/// <returns>A 32-bit signed integer hash code.</returns>
1877+
public override int GetHashCode()
1878+
{
1879+
return this.underlyingSearchContext.GetHashCode();
1880+
}
1881+
}
17291882
}
17301883
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// <copyright file="FindElementEventArgs.cs" company="WebDriver Committers">
2+
// Licensed to the Software Freedom Conservancy (SFC) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The SFC licenses this file
6+
// to you under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
// </copyright>
18+
19+
using System;
20+
21+
namespace OpenQA.Selenium.Support.Events
22+
{
23+
/// <summary>
24+
/// Provides data for events related to getting shadow root of the web element.
25+
/// </summary>
26+
public class GetShadowRootEventArgs : EventArgs
27+
{
28+
private IWebDriver driver;
29+
private ISearchContext searchContext;
30+
31+
/// <summary>
32+
/// Initializes a new instance of the <see cref="GetShadowRootEventArgs"/> class.
33+
/// </summary>
34+
/// <param name="driver">The WebDriver instance used in the current context.</param>
35+
/// <param name="searchContext">The parent searc context used as the context for getting shadow root.</param>
36+
public GetShadowRootEventArgs(IWebDriver driver, ISearchContext searchContext)
37+
{
38+
this.driver = driver;
39+
this.searchContext = searchContext;
40+
}
41+
42+
/// <summary>
43+
/// Gets the WebDriver instance used in the current context.
44+
/// </summary>
45+
public IWebDriver Driver
46+
{
47+
get { return this.driver; }
48+
}
49+
50+
/// <summary>
51+
/// Gets the parent search context used as the context for getting shadow root.
52+
/// </summary>
53+
public ISearchContext SearchContext
54+
{
55+
get { return this.searchContext; }
56+
}
57+
}
58+
}

dotnet/test/support/Events/EventFiringWebDriverTest.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public class EventFiringWebDriverTest
1313
{
1414
private Mock<IWebDriver> mockDriver;
1515
private Mock<IWebElement> mockElement;
16+
private Mock<ISearchContext> mockShadowRoot;
1617
private Mock<INavigation> mockNavigation;
1718
private IWebDriver stubDriver;
1819
private StringBuilder log;
@@ -22,6 +23,7 @@ public void Setup()
2223
{
2324
mockDriver = new Mock<IWebDriver>();
2425
mockElement = new Mock<IWebElement>();
26+
mockShadowRoot = new Mock<ISearchContext>();
2527
mockNavigation = new Mock<INavigation>();
2628
log = new StringBuilder();
2729
}
@@ -214,6 +216,59 @@ public void ShouldBeAbleToAccessWrappedInstanceFromEventCalls()
214216
testDriver.Url = "http://example.org";
215217
}
216218

219+
[Test]
220+
public void ShouldFireGetShadowRootEvents()
221+
{
222+
mockDriver.Setup(d => d.FindElement(It.IsAny<By>())).Returns(mockElement.Object);
223+
EventFiringWebDriver testDriver = new EventFiringWebDriver(mockDriver.Object);
224+
225+
GetShadowRootEventArgs gettingShadowRootArgs = null;
226+
GetShadowRootEventArgs getShadowRootCompletedArgs = null;
227+
testDriver.GettingShadowRoot += (d, e) => gettingShadowRootArgs = e;
228+
testDriver.GetShadowRootCompleted += (d, e) => getShadowRootCompletedArgs = e;
229+
230+
var abcElement = testDriver.FindElement(By.CssSelector(".abc"));
231+
232+
// act
233+
abcElement.GetShadowRoot();
234+
235+
Assert.IsNotNull(gettingShadowRootArgs);
236+
Assert.AreEqual(mockDriver.Object, gettingShadowRootArgs.Driver);
237+
Assert.AreEqual(mockElement.Object, gettingShadowRootArgs.SearchContext);
238+
239+
Assert.IsNotNull(getShadowRootCompletedArgs);
240+
Assert.AreEqual(mockDriver.Object, getShadowRootCompletedArgs.Driver);
241+
Assert.AreEqual(mockElement.Object, getShadowRootCompletedArgs.SearchContext);
242+
}
243+
244+
[Test]
245+
public void ShouldFireFindEventsInShadowRoot()
246+
{
247+
mockElement.Setup(e => e.GetShadowRoot()).Returns(mockShadowRoot.Object);
248+
mockElement.Setup(e => e.FindElement(It.IsAny<By>())).Returns(mockElement.Object);
249+
mockDriver.Setup(d => d.FindElement(It.IsAny<By>())).Returns(mockElement.Object);
250+
EventFiringWebDriver testDriver = new EventFiringWebDriver(mockDriver.Object);
251+
252+
FindElementEventArgs findingElementArgs = null;
253+
FindElementEventArgs findElementCompletedArgs = null;
254+
testDriver.FindingElement += (d, e) => findingElementArgs = e;
255+
testDriver.FindElementCompleted += (d, e) => findElementCompletedArgs = e;
256+
257+
var abcElement = testDriver.FindElement(By.CssSelector(".abc"));
258+
var shadowRoot = abcElement.GetShadowRoot();
259+
260+
// act
261+
var element = shadowRoot.FindElement(By.CssSelector(".abc"));
262+
263+
Assert.IsNotNull(findingElementArgs);
264+
Assert.AreEqual(mockDriver.Object, findingElementArgs.Driver);
265+
Assert.AreEqual(null, findingElementArgs.Element);
266+
267+
Assert.IsNotNull(findElementCompletedArgs);
268+
Assert.AreEqual(mockDriver.Object, findElementCompletedArgs.Driver);
269+
Assert.AreEqual(null, findElementCompletedArgs.Element);
270+
}
271+
217272
void testDriver_Navigating(object sender, WebDriverNavigationEventArgs e)
218273
{
219274
Assert.AreEqual(e.Driver, stubDriver);

0 commit comments

Comments
 (0)