Skip to content

Commit

Permalink
[dotnet] Implementation of event wrapped shadow root element (#12073)
Browse files Browse the repository at this point in the history
* First implementation of events for shadow root

* Added unit test for EventFiringShadowRoot

---------

Co-authored-by: Diego Molina <diemol@users.noreply.github.com>
  • Loading branch information
nvborisenko and diemol authored Jun 8, 2023
1 parent fbb0996 commit 043bb18
Show file tree
Hide file tree
Showing 3 changed files with 267 additions and 1 deletion.
155 changes: 154 additions & 1 deletion dotnet/src/support/Events/EventFiringWebDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
using OpenQA.Selenium.Internal;

namespace OpenQA.Selenium.Support.Events
{
Expand Down Expand Up @@ -101,6 +100,16 @@ public EventFiringWebDriver(IWebDriver parentDriver)
/// </summary>
public event EventHandler<FindElementEventArgs> FindElementCompleted;

/// <summary>
/// Fires before the driver starts to get a shadow root.
/// </summary>
public event EventHandler<GetShadowRootEventArgs> GettingShadowRoot;

/// <summary>
/// Fires after the driver completes getting a shadow root.
/// </summary>
public event EventHandler<GetShadowRootEventArgs> GetShadowRootCompleted;

/// <summary>
/// Fires before a script is executed.
/// </summary>
Expand Down Expand Up @@ -728,6 +737,30 @@ protected virtual void OnFindElementCompleted(FindElementEventArgs e)
}
}

/// <summary>
/// Raises the <see cref="OnGettingShadowRoot"/> event.
/// </summary>
/// <param name="e">A <see cref="GetShadowRootEventArgs"/> that contains the event data.</param>
protected virtual void OnGettingShadowRoot(GetShadowRootEventArgs e)
{
if (this.GettingShadowRoot != null)
{
this.GettingShadowRoot(this, e);
}
}

/// <summary>
/// Raises the <see cref="OnGetShadowRootCompleted"/> event.
/// </summary>
/// <param name="e">A <see cref="GetShadowRootEventArgs"/> that contains the event data.</param>
protected virtual void OnGetShadowRootCompleted(GetShadowRootEventArgs e)
{
if (this.GetShadowRootCompleted != null)
{
this.GetShadowRootCompleted(this, e);
}
}

/// <summary>
/// Raises the <see cref="ScriptExecuting"/> event.
/// </summary>
Expand Down Expand Up @@ -1613,7 +1646,11 @@ public ISearchContext GetShadowRoot()
ISearchContext shadowRoot = null;
try
{
GetShadowRootEventArgs e = new GetShadowRootEventArgs(this.parentDriver.WrappedDriver, this.underlyingElement);
this.parentDriver.OnGettingShadowRoot(e);
shadowRoot = this.underlyingElement.GetShadowRoot();
this.parentDriver.OnGetShadowRootCompleted(e);
shadowRoot = new EventFiringShadowRoot(this.parentDriver, shadowRoot);
}
catch (Exception ex)
{
Expand Down Expand Up @@ -1726,5 +1763,121 @@ public override int GetHashCode()
return this.underlyingElement.GetHashCode();
}
}

/// <summary>
/// EventFiringShadowElement allows you to have access to specific shadow elements
/// </summary>
private class EventFiringShadowRoot : ISearchContext, IWrapsDriver
{
private ISearchContext underlyingSearchContext;
private EventFiringWebDriver parentDriver;

/// <summary>
/// Initializes a new instance of the <see cref="EventFiringShadowRoot"/> class.
/// </summary>
/// <param name="driver">The <see cref="EventFiringWebDriver"/> instance hosting this element.</param>
/// <param name="searchContext">The <see cref="ISearchContext"/> to wrap for event firing.</param>
public EventFiringShadowRoot(EventFiringWebDriver driver, ISearchContext searchContext)
{
this.underlyingSearchContext = searchContext;
this.parentDriver = driver;
}

/// <summary>
/// Gets the underlying wrapped <see cref="ISearchContext"/>.
/// </summary>
public ISearchContext WrappedSearchContext
{
get { return this.underlyingSearchContext; }
}

/// <summary>
/// Gets the underlying parent wrapped <see cref="IWebDriver"/>
/// </summary>
public IWebDriver WrappedDriver
{
get { return this.parentDriver; }
}

/// <summary>
/// Finds the first element in the page that matches the <see cref="By"/> object
/// </summary>
/// <param name="by">By mechanism to find the element</param>
/// <returns>IWebElement object so that you can interaction that object</returns>
public IWebElement FindElement(By by)
{
IWebElement wrappedElement = null;
try
{
GetShadowRootEventArgs e = new GetShadowRootEventArgs(this.parentDriver.WrappedDriver, this.underlyingSearchContext);
this.parentDriver.OnGettingShadowRoot(e);
IWebElement element = this.underlyingSearchContext.FindElement(by);
this.parentDriver.OnGetShadowRootCompleted(e);
wrappedElement = new EventFiringWebElement(this.parentDriver, element);
}
catch (Exception ex)
{
this.parentDriver.OnException(new WebDriverExceptionEventArgs(this.parentDriver, ex));
throw;
}

return wrappedElement;
}

/// <summary>
/// Finds the elements on the page by using the <see cref="By"/> object and returns a ReadOnlyCollection of the Elements on the page
/// </summary>
/// <param name="by">By mechanism to find the element</param>
/// <returns>ReadOnlyCollection of IWebElement</returns>
public ReadOnlyCollection<IWebElement> FindElements(By by)
{
List<IWebElement> wrappedElementList = new List<IWebElement>();
try
{
GetShadowRootEventArgs e = new GetShadowRootEventArgs(this.parentDriver.WrappedDriver, this.underlyingSearchContext);
this.parentDriver.OnGettingShadowRoot(e);
ReadOnlyCollection<IWebElement> elements = this.underlyingSearchContext.FindElements(by);
this.parentDriver.OnGetShadowRootCompleted(e);
foreach (IWebElement element in elements)
{
IWebElement wrappedElement = this.parentDriver.WrapElement(element);
wrappedElementList.Add(wrappedElement);
}
}
catch (Exception ex)
{
this.parentDriver.OnException(new WebDriverExceptionEventArgs(this.parentDriver, ex));
throw;
}

return wrappedElementList.AsReadOnly();
}

/// <summary>
/// Determines whether the specified <see cref="EventFiringShadowRoot"/> is equal to the current <see cref="EventFiringShadowRoot"/>.
/// </summary>
/// <param name="obj">The <see cref="EventFiringWebElement"/> to compare to the current <see cref="EventFiringShadowRoot"/>.</param>
/// <returns><see langword="true"/> if the specified <see cref="EventFiringShadowRoot"/> is equal to the current <see cref="EventFiringShadowRoot"/>; otherwise, <see langword="false"/>.</returns>
public override bool Equals(object obj)
{
ISearchContext other = obj as ISearchContext;

if (other == null)
{
return false;
}

return underlyingSearchContext.Equals(other);
}

/// <summary>
/// Return the hash code for this <see cref="EventFiringWebElement"/>.
/// </summary>
/// <returns>A 32-bit signed integer hash code.</returns>
public override int GetHashCode()
{
return this.underlyingSearchContext.GetHashCode();
}
}
}
}
58 changes: 58 additions & 0 deletions dotnet/src/support/Events/GetShadowRootEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// <copyright file="FindElementEventArgs.cs" company="WebDriver Committers">
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System;

namespace OpenQA.Selenium.Support.Events
{
/// <summary>
/// Provides data for events related to getting shadow root of the web element.
/// </summary>
public class GetShadowRootEventArgs : EventArgs
{
private IWebDriver driver;
private ISearchContext searchContext;

/// <summary>
/// Initializes a new instance of the <see cref="GetShadowRootEventArgs"/> class.
/// </summary>
/// <param name="driver">The WebDriver instance used in the current context.</param>
/// <param name="searchContext">The parent searc context used as the context for getting shadow root.</param>
public GetShadowRootEventArgs(IWebDriver driver, ISearchContext searchContext)
{
this.driver = driver;
this.searchContext = searchContext;
}

/// <summary>
/// Gets the WebDriver instance used in the current context.
/// </summary>
public IWebDriver Driver
{
get { return this.driver; }
}

/// <summary>
/// Gets the parent search context used as the context for getting shadow root.
/// </summary>
public ISearchContext SearchContext
{
get { return this.searchContext; }
}
}
}
55 changes: 55 additions & 0 deletions dotnet/test/support/Events/EventFiringWebDriverTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class EventFiringWebDriverTest
{
private Mock<IWebDriver> mockDriver;
private Mock<IWebElement> mockElement;
private Mock<ISearchContext> mockShadowRoot;
private Mock<INavigation> mockNavigation;
private IWebDriver stubDriver;
private StringBuilder log;
Expand All @@ -22,6 +23,7 @@ public void Setup()
{
mockDriver = new Mock<IWebDriver>();
mockElement = new Mock<IWebElement>();
mockShadowRoot = new Mock<ISearchContext>();
mockNavigation = new Mock<INavigation>();
log = new StringBuilder();
}
Expand Down Expand Up @@ -214,6 +216,59 @@ public void ShouldBeAbleToAccessWrappedInstanceFromEventCalls()
testDriver.Url = "http://example.org";
}

[Test]
public void ShouldFireGetShadowRootEvents()
{
mockDriver.Setup(d => d.FindElement(It.IsAny<By>())).Returns(mockElement.Object);
EventFiringWebDriver testDriver = new EventFiringWebDriver(mockDriver.Object);

GetShadowRootEventArgs gettingShadowRootArgs = null;
GetShadowRootEventArgs getShadowRootCompletedArgs = null;
testDriver.GettingShadowRoot += (d, e) => gettingShadowRootArgs = e;
testDriver.GetShadowRootCompleted += (d, e) => getShadowRootCompletedArgs = e;

var abcElement = testDriver.FindElement(By.CssSelector(".abc"));

// act
abcElement.GetShadowRoot();

Assert.IsNotNull(gettingShadowRootArgs);
Assert.AreEqual(mockDriver.Object, gettingShadowRootArgs.Driver);
Assert.AreEqual(mockElement.Object, gettingShadowRootArgs.SearchContext);

Assert.IsNotNull(getShadowRootCompletedArgs);
Assert.AreEqual(mockDriver.Object, getShadowRootCompletedArgs.Driver);
Assert.AreEqual(mockElement.Object, getShadowRootCompletedArgs.SearchContext);
}

[Test]
public void ShouldFireFindEventsInShadowRoot()
{
mockElement.Setup(e => e.GetShadowRoot()).Returns(mockShadowRoot.Object);
mockElement.Setup(e => e.FindElement(It.IsAny<By>())).Returns(mockElement.Object);
mockDriver.Setup(d => d.FindElement(It.IsAny<By>())).Returns(mockElement.Object);
EventFiringWebDriver testDriver = new EventFiringWebDriver(mockDriver.Object);

FindElementEventArgs findingElementArgs = null;
FindElementEventArgs findElementCompletedArgs = null;
testDriver.FindingElement += (d, e) => findingElementArgs = e;
testDriver.FindElementCompleted += (d, e) => findElementCompletedArgs = e;

var abcElement = testDriver.FindElement(By.CssSelector(".abc"));
var shadowRoot = abcElement.GetShadowRoot();

// act
var element = shadowRoot.FindElement(By.CssSelector(".abc"));

Assert.IsNotNull(findingElementArgs);
Assert.AreEqual(mockDriver.Object, findingElementArgs.Driver);
Assert.AreEqual(null, findingElementArgs.Element);

Assert.IsNotNull(findElementCompletedArgs);
Assert.AreEqual(mockDriver.Object, findElementCompletedArgs.Driver);
Assert.AreEqual(null, findElementCompletedArgs.Element);
}

void testDriver_Navigating(object sender, WebDriverNavigationEventArgs e)
{
Assert.AreEqual(e.Driver, stubDriver);
Expand Down

0 comments on commit 043bb18

Please sign in to comment.