blob: 64b81bdc62caadcabbd8ccea3ced60818ab280e7 [file] [log] [blame]
// <copyright file="WebDriverExtensions.cs" company="Selenium 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;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using OpenQA.Selenium.Internal;
namespace OpenQA.Selenium.Support.Extensions;
/// <summary>
/// Provides extension methods for convenience in using WebDriver.
/// </summary>
public static class WebDriverExtensions
{
/// <summary>
/// Gets a <see cref="Screenshot"/> object representing the image of the page on the screen.
/// </summary>
/// <param name="driver">The driver instance to extend.</param>
/// <returns>A <see cref="Screenshot"/> object containing the image.</returns>
/// <exception cref="WebDriverException">Thrown if this <see cref="IWebDriver"/> instance
/// does not implement <see cref="ITakesScreenshot"/>, or the capabilities of the driver
/// indicate that it cannot take screenshots.</exception>
public static Screenshot TakeScreenshot(this IWebDriver driver)
{
ITakesScreenshot? screenshotDriver = GetDriverAs<ITakesScreenshot>(driver);
if (screenshotDriver is null)
{
IHasCapabilities capabilitiesDriver = driver as IHasCapabilities
?? throw new WebDriverException("Driver does not implement ITakesScreenshot or IHasCapabilities");
if (capabilitiesDriver.Capabilities.GetCapability(CapabilityType.TakesScreenshot) is not true)
{
throw new WebDriverException("Driver capabilities do not support taking screenshots");
}
MethodInfo executeMethod = driver.GetType().GetMethod("Execute", BindingFlags.Instance | BindingFlags.NonPublic)!;
object? responseObject = executeMethod.Invoke(driver, new object?[] { DriverCommand.Screenshot, null });
if (responseObject is not Response screenshotResponse)
{
throw new WebDriverException($"Unexpected failure getting screenshot; response was not in the proper format: {responseObject}");
}
string screenshotResult = screenshotResponse.Value!.ToString();
return new Screenshot(screenshotResult);
}
return screenshotDriver.GetScreenshot();
}
/// <summary>
/// Executes JavaScript in the context of the currently selected frame or window
/// </summary>
/// <param name="driver">The driver instance to extend.</param>
/// <param name="script">The JavaScript code to execute.</param>
/// <param name="args">The arguments to the script.</param>
/// <exception cref="WebDriverException">Thrown if this <see cref="IWebDriver"/> instance
/// does not implement <see cref="IJavaScriptExecutor"/></exception>
public static void ExecuteJavaScript(this IWebDriver driver, [StringSyntax(StringSyntaxConstants.JavaScript)] string script, params object?[] args)
{
ExecuteJavaScriptInternal(driver, script, args);
}
/// <summary>
/// Executes JavaScript in the context of the currently selected frame or window
/// </summary>
/// <typeparam name="T">Expected return type of the JavaScript execution.</typeparam>
/// <param name="driver">The driver instance to extend.</param>
/// <param name="script">The JavaScript code to execute.</param>
/// <param name="args">The arguments to the script.</param>
/// <returns>The value returned by the script.</returns>
/// <exception cref="WebDriverException">Thrown if this <see cref="IWebDriver"/> instance
/// does not implement <see cref="IJavaScriptExecutor"/>, or if the actual return type
/// of the JavaScript execution does not match the expected type.</exception>
public static T? ExecuteJavaScript<T>(this IWebDriver driver, [StringSyntax(StringSyntaxConstants.JavaScript)] string script, params object?[] args)
{
var value = ExecuteJavaScriptInternal(driver, script, args);
if (value == null)
{
if (default(T) != null)
{
throw new WebDriverException("Script returned null, but desired type is a non-nullable value type");
}
return default;
}
if (value is T t)
{
return t;
}
try
{
return (T)Convert.ChangeType(value, typeof(T));
}
catch (Exception exp)
{
throw new WebDriverException("Script returned a value, but the result could not be cast to the desired type", exp);
}
}
private static object? ExecuteJavaScriptInternal(IWebDriver driver, string script, object?[] args)
{
IJavaScriptExecutor? executor = GetDriverAs<IJavaScriptExecutor>(driver)
?? throw new WebDriverException("Driver does not implement IJavaScriptExecutor");
return executor.ExecuteScript(script, args);
}
private static T? GetDriverAs<T>(IWebDriver driver) where T : class
{
T? convertedDriver = driver as T;
if (convertedDriver == null)
{
// If the driver doesn't directly implement the desired interface, but does
// implement IWrapsDriver, walk up the hierarchy of wrapped drivers until
// either we find a class that does implement the desired interface, or is
// no longer wrapping a driver.
IWrapsDriver? driverWrapper = driver as IWrapsDriver;
while (convertedDriver == null && driverWrapper != null)
{
convertedDriver = driverWrapper.WrappedDriver as T;
driverWrapper = driverWrapper.WrappedDriver as IWrapsDriver;
}
}
return convertedDriver;
}
}