Skip to content
Open
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
17 changes: 16 additions & 1 deletion dotnet/src/support/Extensions/WebDriverExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
// under the License.
// </copyright>

using OpenQA.Selenium.Support.UI;
using System;
using System.Reflection;

Expand Down Expand Up @@ -117,7 +118,7 @@ public static void ExecuteJavaScript(this IWebDriver driver, string script, para

private static object? ExecuteJavaScriptInternal(IWebDriver driver, string script, object?[] args)
{
IJavaScriptExecutor? executor = GetDriverAs<IJavaScriptExecutor>(driver)
IJavaScriptExecutor executor = GetDriverAs<IJavaScriptExecutor>(driver)
?? throw new WebDriverException("Driver does not implement IJavaScriptExecutor");

return executor.ExecuteScript(script, args);
Expand All @@ -142,4 +143,18 @@ public static void ExecuteJavaScript(this IWebDriver driver, string script, para

return convertedDriver;
}

/// <summary>
/// Performs an action that opens a new browser window, and returns an object that allows to switch between
/// the new and the old windows easily.
/// </summary>
/// <param name="driver">The driver instance to extend.</param>
/// <param name="actionThatOpensNewWindow">The action delegate that should open the new browser window.</param>
/// <returns>An instance of <see cref="WindowSwitcher"/> that allows to switch between the old and new windows.</returns>
public static WindowSwitcher WithWindowOpenedBy(this IWebDriver driver, Action actionThatOpensNewWindow)
{
var finder = new PopupWindowFinder(driver);
var newHandle = finder.Invoke(actionThatOpensNewWindow);
return new WindowSwitcher(driver, newHandle);
}
}
132 changes: 132 additions & 0 deletions dotnet/src/support/UI/WindowSwitcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
using System;
using OpenQA.Selenium.Support.Extensions;

namespace OpenQA.Selenium.Support.UI;

/// <summary>
/// Provides a mechanism to easily switch between two browser windows.
/// </summary>
public class WindowSwitcher
{
private readonly IWebDriver driver;
private readonly string newWindowHandle;
private readonly string originalHandle;

/// <summary>
/// Initializes a new instance of the <see cref="WindowSwitcher"/> class.
/// </summary>
/// <param name="driver">The <see cref="IWebDriver"/> instance that controls the two windows.</param>
/// <param name="newWindowHandle">The handle of the new window.</param>
/// <exception cref="ArgumentNullException">Either <paramref name="driver"/> or <paramref name="newWindowHandle"/> is null.</exception>
/// <remarks>
/// <para>
/// It is recommended to use the <see cref="WebDriverExtensions.WithWindowOpenedBy"/> to instantiate this
/// class.
/// </para>
/// <para>
/// The current driver window handle is used to identify the existing window, while
/// <paramref name="newWindowHandle"/> should be the handle of another, newly opened, window.
/// </para>
/// </remarks>
public WindowSwitcher(IWebDriver driver, string newWindowHandle)
{
this.driver = driver ?? throw new ArgumentNullException(nameof(driver));
this.newWindowHandle = newWindowHandle ?? throw new ArgumentNullException(nameof(newWindowHandle));
originalHandle = this.driver.CurrentWindowHandle;
}

/// <summary>
/// Performs the provided action on the newly opened window, and returns to use the original window afterward.
/// </summary>
/// <param name="action">The action to perform on the newly opened window.</param>
/// <example>
/// <code>
/// driver.WithWindowOpenedBy(() => driver.FindElement(By.Id("openWindowId")).Click())
/// .Do(() => {
/// // Perform whatever you want with the new window, for example:
/// driver.FindElement(By.Id("anElementOnTheNewWindow").Click();
/// Assert.That(driver.Title, Is.EqualTo("The new window!"));
/// });
/// // Then continue to do stuff on the original window:
/// driver.FindElement(By.Id("anElementOnTheOriginalWindow").Click();
/// </code>
/// </example>
public void Do(Action action)
{
_ = Do(() =>
{
action();
return 0;
});
}

/// <summary>
/// Invokes the provided function on the newly opened window, and returns to use the original window afterward. This method returns whatever the function returns.
/// </summary>
/// <param name="func">The function to invoke on the newly opened window.</param>
/// <returns>Anything that <paramref name="func"/> returns.</returns>
/// <example>
/// <code>
/// const string expectedResult = "abc";
/// var result = driver.WithWindowOpenedBy(() => driver.FindElement(By.Id("openWindowId")).Click())
/// .Do(() => {
/// // Perform whatever you want with the new window, for example:
/// driver.FindElement(By.Id("anElementOnTheNewWindow").Click();
/// return driver.FindElement(By.Id("resultElement")).Text;
/// });
/// Assert.That(result, Is.EqualTo(expected));
/// // Then continue to do stuff on the original window:
/// driver.FindElement(By.Id("anElementOnTheOriginalWindow").Click();
/// </code>
/// </example>
public T Do<T>(Func<T> func)
{
SwitchToNewWindow();
try
{
return func();
}
finally
{
SwitchToOriginalWindow();
}
}

/// <summary>
/// Switches to the new window.
/// </summary>
/// <remarks>
/// The following example shows how to use <see cref="SwitchToNewWindow"/> and <see cref="SwitchToOriginalWindow"/>
/// to switch back and forth between the two windows:
/// <example>
/// <code>
/// var switcher = driver.WithWindowOpenedBy(() => driver.FindElement(By.Id("openWindowId")).Click());
/// // Do some stuff on the original window:
/// driver.FindElement(By.Id("aButtonOnTheOriginalWindow")).Click();
///
/// // Do some stuff on the new window:
/// switcher.SwitchToNewWindow();
/// driver.FindElement(By.Id("anInputOnTheNewWindow")).SendKeys("Hi");
///
/// // Do some more stuff on the original window:
/// switcher.SwitchToOriginalWindow();
/// driver.FindElement(By.Id("aButtonOnTheOriginalWindow")).Click();
///
/// // And you can continue switching between the windows as you need...
/// </code>
/// </example>
/// </remarks>
public void SwitchToNewWindow()
{
driver.SwitchTo().Window(newWindowHandle);
}

/// <summary>
/// Switches to the original window.
/// </summary>
/// <inheritdoc cref="SwitchToNewWindow" path="/remarks" />
public void SwitchToOriginalWindow()
{
driver.SwitchTo().Window(originalHandle);
}
}
49 changes: 49 additions & 0 deletions dotnet/test/support/UI/WindowSwitcherTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using NUnit.Framework;
using OpenQA.Selenium.Environment;
using System.Threading.Tasks;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.Extensions;

namespace Selenium.WebDriver.Support.Tests.UI;

internal class WindowSwitcherTest : DriverTestFixture
{
[OneTimeSetUp]
public async Task RunBeforeAnyTestAsync()
{
await EnvironmentManager.Instance.WebServer.StartAsync();
}

[OneTimeTearDown]
public async Task RunAfterAnyTestsAsync()
{
EnvironmentManager.Instance.CloseCurrentDriver();
await EnvironmentManager.Instance.WebServer.StopAsync();
}

[Test]
public void SwitchesToNewWindowAndBack()
{
driver.Url = xhtmlTestPage;
var originalWindowHandle = driver.CurrentWindowHandle;
var delegateCalled = false;
var openWindowLink = driver.FindElement(By.LinkText("Open new window"));
driver.WithWindowOpenedBy(() => openWindowLink.Click())
.Do(() =>
{
delegateCalled = true;
Assert.Multiple(() =>
{
Assert.That(driver.CurrentWindowHandle, Is.Not.EqualTo(originalWindowHandle));
Assert.That(driver.Title, Is.EqualTo("We Arrive Here"));
});
});

Assert.Multiple(() =>
{
Assert.That(delegateCalled);
Assert.That(driver.CurrentWindowHandle, Is.EqualTo(originalWindowHandle));
Assert.That(driver.Title, Is.EqualTo("XHTML Test Page"));
});
}
}