Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/trunk' into py-nightly
Browse files Browse the repository at this point in the history
Signed-off-by: Viet Nguyen Duc <[email protected]>
  • Loading branch information
VietND96 committed Nov 20, 2024
2 parents 911245f + 673d2c7 commit 4c0944f
Show file tree
Hide file tree
Showing 14 changed files with 152 additions and 103 deletions.
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
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; }
}
}
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
12 changes: 11 additions & 1 deletion javascript/node/selenium-webdriver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,15 @@
},
"publishConfig": {
"registry": "https://registry.npmjs.org/"
}
},
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/SeleniumHQ"
},
{
"type": "opencollective",
"url": "https://opencollective.com/selenium"
}
]
}
52 changes: 47 additions & 5 deletions py/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,65 @@
requires = ["setuptools", "setuptools-rust"]
build-backend = "setuptools.build_meta"

[project]
name = "selenium"
version = "4.27.0.202410311942"
license = "Apache 2.0"
description = "Official Python bindings for Selenium WebDriver."
readme = "README.rst"
requires-python = "~=3.8"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Operating System :: POSIX",
"Operating System :: Microsoft :: Windows",
"Operating System :: MacOS :: MacOS X",
"Topic :: Software Development :: Testing",
"Topic :: Software Development :: Libraries",
"Programming Language :: Python",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
dependencies = [
"urllib3[socks]>=1.26,<3",
"trio~=0.17",
"trio-websocket~=0.9",
"certifi>=2021.10.8",
"typing_extensions~=4.9",
"websocket-client~=1.8",
]

[tool.setuptools]
zip-safe = false

[tool.setuptools.packages.find]
include = ["selenium*"]
exclude = ["test*"]
namespaces = false
namespace = false
# include-package-data is `true` by default in pyproject.toml

[project.urls]
Repository = "https://github.com/SeleniumHQ/selenium/"
BugTracker = "https://github.com/SeleniumHQ/selenium/issues"
Changelog = "https://github.com/SeleniumHQ/selenium/blob/trunk/py/CHANGES"
Documentation = "https://www.selenium.dev/documentation/overview/"
SourceCode = "https://github.com/SeleniumHQ/selenium/tree/trunk/py"

[tool.setuptools.package-data]
selenium_package = [
"*.py",
"*.py",
"*.rst",
"*.json",
"*.xpi",
"*.json",
"*.xpi",
"*.js",
"py.typed",
"prune*",
"selenium.egg-info*",
"selenium-manager",
"selenium-manager",
"selenium-manager.exe",
"CHANGES",
"LICENSE"
Expand Down
Loading

0 comments on commit 4c0944f

Please sign in to comment.