Skip to content

Commit

Permalink
Merge branch 'trunk' into rb_bidi_nav
Browse files Browse the repository at this point in the history
  • Loading branch information
aguspe authored Nov 20, 2024
2 parents 7381ddb + 789e8d4 commit a863363
Show file tree
Hide file tree
Showing 18 changed files with 156 additions and 106 deletions.
5 changes: 3 additions & 2 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -582,10 +582,11 @@ namespace :py do
desc 'Update Python version'
task :version, [:version] do |_task, arguments|
old_version = python_version
nightly = ".dev#{Time.now.strftime('%Y%m%d%H%M')}"
nightly = ".#{Time.now.strftime('%Y%m%d%H%M')}"
new_version = updated_version(old_version, arguments[:version], nightly)

['py/setup.py',
'py/pyproject.toml',
'py/BUILD.bazel',
'py/selenium/__init__.py',
'py/selenium/webdriver/__init__.py',
Expand Down Expand Up @@ -1138,7 +1139,7 @@ def updated_version(current, desired = nil, nightly = nil)
desired.split('.').tap { |v| v << 0 while v.size < 3 }.join('.')
elsif current.split(/\.|-/).size > 3
# if current version is already nightly, just need to bump it; this will be noop for some languages
pattern = /-?\.?(nightly|SNAPSHOT|dev)\d*$/
pattern = /-?\.?(nightly|SNAPSHOT|dev|\d{12})\d*$/
current.gsub(pattern, nightly)
elsif current.split(/\.|-/).size == 3
# if current version is not nightly, need to bump the version and make nightly
Expand Down
6 changes: 6 additions & 0 deletions dotnet/src/support/Events/EventFiringWebDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,7 @@ public object ExecuteScript(string script, params object[] args)
/// <param name="script">A <see cref="PinnedScript"/> object containing the code to execute.</param>
/// <param name="args">The arguments to the script.</param>
/// <returns>The value returned by the script.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="script"/> is <see langword="null"/>.</exception>
/// <remarks>
/// <para>
/// The ExecuteScript method executes JavaScript in the context of
Expand Down Expand Up @@ -509,6 +510,11 @@ public object ExecuteScript(string script, params object[] args)
/// </remarks>
public object ExecuteScript(PinnedScript script, params object[] args)
{
if (script == null)
{
throw new ArgumentNullException(nameof(script));
}

IJavaScriptExecutor javascriptDriver = this.driver as IJavaScriptExecutor;
if (javascriptDriver == null)
{
Expand Down
1 change: 1 addition & 0 deletions dotnet/src/webdriver/Firefox/FirefoxDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ public DevToolsSession GetDevToolsSession(int devToolsProtocolVersion)
/// Creates a session to communicate with a browser using a Developer Tools debugging protocol.
/// </summary>
/// <returns>The active session to use to communicate with the Developer Tools debugging protocol.</returns>
[Obsolete("CDP support for Firefox is deprecated and will be removed in future versions. Please switch to WebDriver BiDi.")]
public DevToolsSession GetDevToolsSession(DevToolsOptions options)
{
if (this.devToolsSession == null)
Expand Down
4 changes: 4 additions & 0 deletions dotnet/src/webdriver/IJavaScriptEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,15 @@ public interface IJavaScriptEngine : IDisposable
/// <param name="scriptName">The friendly name by which to refer to this initialization script.</param>
/// <param name="script">The JavaScript to be loaded on every page.</param>
/// <returns>A task containing an <see cref="InitializationScript"/> object representing the script to be loaded on each page.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="scriptName"/> is <see langword="null"/>.</exception>
Task<InitializationScript> AddInitializationScript(string scriptName, string script);

/// <summary>
/// Asynchronously removes JavaScript from being loaded on every document load.
/// </summary>
/// <param name="scriptName">The friendly name of the initialization script to be removed.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="scriptName"/> is <see langword="null"/>.</exception>
Task RemoveInitializationScript(string scriptName);

/// <summary>
Expand All @@ -109,13 +111,15 @@ public interface IJavaScriptEngine : IDisposable
/// </summary>
/// <param name="script">The JavaScript to pin</param>
/// <returns>A task containing a <see cref="PinnedScript"/> object to use to execute the script.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="script"/> is <see langword="null"/>.</exception>
Task<PinnedScript> PinScript(string script);

/// <summary>
/// Unpins a previously pinned script from the browser.
/// </summary>
/// <param name="script">The <see cref="PinnedScript"/> object to unpin.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="script"/> is <see langword="null"/>.</exception>
Task UnpinScript(PinnedScript script);

/// <summary>
Expand Down
2 changes: 2 additions & 0 deletions dotnet/src/webdriver/IJavascriptExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
// under the License.
// </copyright>

using System;
using System.Collections.Generic;

namespace OpenQA.Selenium
Expand Down Expand Up @@ -98,6 +99,7 @@ public interface IJavaScriptExecutor
/// variable, as if the function were called via "Function.apply"
/// </para>
/// </remarks>
/// <exception cref="ArgumentNullException">If <paramref name="script" /> is <see langword="null"/>.</exception>
object ExecuteScript(PinnedScript script, params object[] args);

/// <summary>
Expand Down
2 changes: 2 additions & 0 deletions dotnet/src/webdriver/ISearchContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
// under the License.
// </copyright>

using System;
using System.Collections.ObjectModel;

namespace OpenQA.Selenium
Expand All @@ -31,6 +32,7 @@ public interface ISearchContext
/// </summary>
/// <param name="by">The locating mechanism to use.</param>
/// <returns>The first matching <see cref="IWebElement"/> on the current context.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="by" /> is <see langword="null"/>.</exception>
/// <exception cref="NoSuchElementException">If no element matches the criteria.</exception>
IWebElement FindElement(By by);

Expand Down
25 changes: 21 additions & 4 deletions dotnet/src/webdriver/JavaScriptEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -219,14 +219,25 @@ public async Task ClearInitializationScripts()
/// </summary>
/// <param name="script">The JavaScript to pin</param>
/// <returns>A task containing a <see cref="PinnedScript"/> object to use to execute the script.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="script"/> is <see langword="null"/>.</exception>
public async Task<PinnedScript> PinScript(string script)
{
if (script == null)
{
throw new ArgumentNullException(nameof(script));
}

string newScriptHandle = Guid.NewGuid().ToString("N");

// We do an "Evaluate" first so as to immediately create the script on the loaded
// page, then will add it to the initialization of future pages.
PinnedScript pinnedScript = new PinnedScript(script);
await this.EnableDomains().ConfigureAwait(false);
await this.session.Value.Domains.JavaScript.Evaluate(pinnedScript.CreationScript).ConfigureAwait(false);
pinnedScript.ScriptId = await this.session.Value.Domains.JavaScript.AddScriptToEvaluateOnNewDocument(pinnedScript.CreationScript).ConfigureAwait(false);

string creationScript = PinnedScript.MakeCreationScript(newScriptHandle, script);
await this.session.Value.Domains.JavaScript.Evaluate(creationScript).ConfigureAwait(false);
string scriptId = await this.session.Value.Domains.JavaScript.AddScriptToEvaluateOnNewDocument(creationScript).ConfigureAwait(false);

PinnedScript pinnedScript = new PinnedScript(script, newScriptHandle, scriptId);
this.pinnedScripts[pinnedScript.Handle] = pinnedScript;
return pinnedScript;
}
Expand All @@ -236,11 +247,17 @@ public async Task<PinnedScript> PinScript(string script)
/// </summary>
/// <param name="script">The <see cref="PinnedScript"/> object to unpin.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="script"/> is <see langword="null"/>.</exception>
public async Task UnpinScript(PinnedScript script)
{
if (script == null)
{
throw new ArgumentNullException(nameof(script));
}

if (this.pinnedScripts.ContainsKey(script.Handle))
{
await this.session.Value.Domains.JavaScript.Evaluate(script.RemovalScript).ConfigureAwait(false);
await this.session.Value.Domains.JavaScript.Evaluate(script.MakeRemovalScript()).ConfigureAwait(false);
await this.session.Value.Domains.JavaScript.RemoveScriptToEvaluateOnNewDocument(script.ScriptId).ConfigureAwait(false);
this.pinnedScripts.Remove(script.Handle);
}
Expand Down
49 changes: 18 additions & 31 deletions dotnet/src/webdriver/PinnedScript.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,83 +17,70 @@
// under the License.
// </copyright>

using System;
using System.Globalization;

#nullable enable

namespace OpenQA.Selenium
{
/// <summary>
/// A class representing a pinned JavaScript function that can be repeatedly called
/// without sending the entire script across the wire for every execution.
/// </summary>
public class PinnedScript
public sealed class PinnedScript
{
private string scriptSource;
private string scriptHandle;
private string scriptId;

/// <summary>
/// Initializes a new instance of the <see cref="PinnedScript"/> class.
/// </summary>
/// <param name="script">The body of the JavaScript function to pin.</param>
/// <param name="stringHandle">The unique handle for this pinned script.</param>
/// <param name="scriptId">The internal ID of this script.</param>
/// <remarks>
/// This constructor is explicitly internal. Creation of pinned script objects
/// is strictly the perview of Selenium, and should not be required by external
/// libraries.
/// </remarks>
internal PinnedScript(string script)
internal PinnedScript(string script, string stringHandle, string scriptId)
{
this.scriptSource = script;
this.scriptHandle = Guid.NewGuid().ToString("N");
this.Source = script;
this.Handle = stringHandle;
this.ScriptId = scriptId;
}

/// <summary>
/// Gets the unique handle for this pinned script.
/// </summary>
public string Handle
{
get { return this.scriptHandle; }
}
public string Handle { get; }

/// <summary>
/// Gets the source representing the body of the function in the pinned script.
/// </summary>
public string Source
{
get { return this.scriptSource; }
}
public string Source { get; }

/// <summary>
/// Gets the script to create the pinned script in the browser.
/// </summary>
internal string CreationScript
internal static string MakeCreationScript(string scriptHandle, string scriptSource)
{
get { return string.Format(CultureInfo.InvariantCulture, "function __webdriver_{0}(arguments) {{ {1} }}", this.scriptHandle, this.scriptSource); }
return string.Format(CultureInfo.InvariantCulture, "function __webdriver_{0}(arguments) {{ {1} }}", scriptHandle, scriptSource);
}

/// <summary>
/// Gets the script used to execute the pinned script in the browser.
/// </summary>
internal string ExecutionScript
internal string MakeExecutionScript()
{
get { return string.Format(CultureInfo.InvariantCulture, "return __webdriver_{0}(arguments)", this.scriptHandle); }
return string.Format(CultureInfo.InvariantCulture, "return __webdriver_{0}(arguments)", this.Handle);
}

/// <summary>
/// Gets the script used to remove the pinned script from the browser.
/// </summary>
internal string RemovalScript
internal string MakeRemovalScript()
{
get { return string.Format(CultureInfo.InvariantCulture, "__webdriver_{0} = undefined", this.scriptHandle); }
return string.Format(CultureInfo.InvariantCulture, "__webdriver_{0} = undefined", this.Handle);
}

/// <summary>
/// Gets or sets the ID of this script.
/// </summary>
internal string ScriptId
{
get { return this.scriptId; }
set { this.scriptId = value; }
}
internal string ScriptId { get; }
}
}
11 changes: 11 additions & 0 deletions dotnet/src/webdriver/Remote/RemoteWebDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
// under the License.
// </copyright>

using OpenQA.Selenium.Internal.Logging;
using OpenQA.Selenium.DevTools;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -63,6 +64,8 @@ namespace OpenQA.Selenium.Remote
/// </example>
public class RemoteWebDriver : WebDriver, IDevTools, IHasDownloads
{
private static readonly ILogger _logger = OpenQA.Selenium.Internal.Logging.Log.GetLogger(typeof(RemoteWebDriver));

/// <summary>
/// The name of the Selenium grid remote DevTools end point capability.
/// </summary>
Expand Down Expand Up @@ -425,6 +428,14 @@ public ReadOnlyCollection<IWebElement> FindElementsByCssSelector(string cssSelec
/// <returns>The active session to use to communicate with the Developer Tools debugging protocol.</returns>
public DevToolsSession GetDevToolsSession()
{
if (this.Capabilities.GetCapability(CapabilityType.BrowserName) == "firefox")
{
if (_logger.IsEnabled(LogEventLevel.Warn))
{
_logger.Warn("CDP support for Firefox is deprecated and will be removed in future versions. Please switch to WebDriver BiDi.");
}
}

return GetDevToolsSession(new DevToolsOptions() { ProtocolVersion = DevToolsSession.AutoDetectDevToolsProtocolVersion });
}

Expand Down
9 changes: 8 additions & 1 deletion dotnet/src/webdriver/WebDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,16 +280,23 @@ public object ExecuteScript(string script, params object[] args)
/// <param name="script">A <see cref="PinnedScript"/> object containing 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="ArgumentNullException">If <paramref name="script" /> is <see langword="null"/>.</exception>
public object ExecuteScript(PinnedScript script, params object[] args)
{
return this.ExecuteScript(script.ExecutionScript, args);
if (script == null)
{
throw new ArgumentNullException(nameof(script));
}

return this.ExecuteScript(script.MakeExecutionScript(), args);
}

/// <summary>
/// Finds the first element in the page that matches the <see cref="By"/> object
/// </summary>
/// <param name="by">By mechanism to find the object</param>
/// <returns>IWebElement object so that you can interact with that object</returns>
/// <exception cref="ArgumentNullException">If <paramref name="by" /> is <see langword="null"/>.</exception>
/// <example>
/// <code>
/// IWebDriver driver = new InternetExplorerDriver();
Expand Down
28 changes: 28 additions & 0 deletions dotnet/test/common/ExecutingJavascriptTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;

namespace OpenQA.Selenium
{
Expand Down Expand Up @@ -468,6 +469,33 @@ public void ShouldBeAbleToExecuteABigChunkOfJavascriptCode()
}
}

[Test]
[IgnoreBrowser(Selenium.Browser.IE, "IE does not support Chrome DevTools Protocol")]
[IgnoreBrowser(Selenium.Browser.Firefox, "Firefox does not support Chrome DevTools Protocol")]
[IgnoreBrowser(Selenium.Browser.Safari, "Safari does not support Chrome DevTools Protocol")]
public async Task ShouldBeAbleToPinJavascriptCodeAndExecuteRepeatedly()
{
IJavaScriptEngine jsEngine = new JavaScriptEngine(driver);

driver.Url = xhtmlTestPage;

PinnedScript script = await jsEngine.PinScript("return document.title;");
for (int i = 0; i < 5; i++)
{
object result = ((IJavaScriptExecutor)driver).ExecuteScript(script);

Assert.That(result, Is.InstanceOf<string>());
Assert.That(result, Is.EqualTo("XHTML Test Page"));
}

await jsEngine.UnpinScript(script);

Assert.That(() =>
{
_ = ((IJavaScriptExecutor)driver).ExecuteScript(script);
}, Throws.TypeOf<JavaScriptException>());
}

[Test]
public void ShouldBeAbleToExecuteScriptAndReturnElementsList()
{
Expand Down
2 changes: 1 addition & 1 deletion py/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ compile_pip_requirements(
],
)

SE_VERSION = "4.27.0.dev202410311942"
SE_VERSION = "4.27.0.202410311942"

BROWSER_VERSIONS = [
"v85",
Expand Down
2 changes: 1 addition & 1 deletion py/docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
# The short X.Y version.
version = '4.27'
# The full version, including alpha/beta/rc tags.
release = '4.27.0.dev202410311942'
release = '4.27.0.202410311942'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
Loading

0 comments on commit a863363

Please sign in to comment.