Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[dotnet] Implementation of event wrapped shadow root element #12073

Merged
merged 6 commits into from
Jun 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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