From 631901e1afb99b833751584bc3a0ad877d52666c Mon Sep 17 00:00:00 2001 From: Michael Render Date: Sun, 3 Nov 2024 14:50:34 -0500 Subject: [PATCH 1/4] [dotnet] Solidify nullability of `PinnedScript`, add test --- .../support/Events/EventFiringWebDriver.cs | 6 +++ dotnet/src/webdriver/IJavaScriptEngine.cs | 4 ++ dotnet/src/webdriver/IJavascriptExecutor.cs | 2 + dotnet/src/webdriver/ISearchContext.cs | 2 + dotnet/src/webdriver/JavaScriptEngine.cs | 25 ++++++++-- dotnet/src/webdriver/PinnedScript.cs | 49 +++++++------------ dotnet/src/webdriver/WebDriver.cs | 9 +++- dotnet/test/common/ExecutingJavascriptTest.cs | 25 ++++++++++ 8 files changed, 86 insertions(+), 36 deletions(-) diff --git a/dotnet/src/support/Events/EventFiringWebDriver.cs b/dotnet/src/support/Events/EventFiringWebDriver.cs index 77a3b5f3b50a5..faf851bf20a20 100644 --- a/dotnet/src/support/Events/EventFiringWebDriver.cs +++ b/dotnet/src/support/Events/EventFiringWebDriver.cs @@ -477,6 +477,7 @@ public object ExecuteScript(string script, params object[] args) /// A object containing the code to execute. /// The arguments to the script. /// The value returned by the script. + /// If is null. /// /// /// The ExecuteScript method executes JavaScript in the context of @@ -508,6 +509,11 @@ public object ExecuteScript(string script, params object[] args) /// 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) { diff --git a/dotnet/src/webdriver/IJavaScriptEngine.cs b/dotnet/src/webdriver/IJavaScriptEngine.cs index 79ff01eea5d5a..693414d18c304 100644 --- a/dotnet/src/webdriver/IJavaScriptEngine.cs +++ b/dotnet/src/webdriver/IJavaScriptEngine.cs @@ -86,6 +86,7 @@ public interface IJavaScriptEngine : IDisposable /// The friendly name by which to refer to this initialization script. /// The JavaScript to be loaded on every page. /// A task containing an object representing the script to be loaded on each page. + /// If is null. Task AddInitializationScript(string scriptName, string script); /// @@ -93,6 +94,7 @@ public interface IJavaScriptEngine : IDisposable /// /// The friendly name of the initialization script to be removed. /// A task that represents the asynchronous operation. + /// If is null. Task RemoveInitializationScript(string scriptName); /// @@ -108,6 +110,7 @@ public interface IJavaScriptEngine : IDisposable /// /// The JavaScript to pin /// A task containing a object to use to execute the script. + /// If is null. Task PinScript(string script); /// @@ -115,6 +118,7 @@ public interface IJavaScriptEngine : IDisposable /// /// The object to unpin. /// A task that represents the asynchronous operation. + /// If is null. Task UnpinScript(PinnedScript script); /// diff --git a/dotnet/src/webdriver/IJavascriptExecutor.cs b/dotnet/src/webdriver/IJavascriptExecutor.cs index 0835980f0c189..d850251614cc4 100644 --- a/dotnet/src/webdriver/IJavascriptExecutor.cs +++ b/dotnet/src/webdriver/IJavascriptExecutor.cs @@ -16,6 +16,7 @@ // limitations under the License. // +using System; using System.Collections.Generic; namespace OpenQA.Selenium @@ -97,6 +98,7 @@ public interface IJavaScriptExecutor /// variable, as if the function were called via "Function.apply" /// /// + /// If is null. object ExecuteScript(PinnedScript script, params object[] args); /// diff --git a/dotnet/src/webdriver/ISearchContext.cs b/dotnet/src/webdriver/ISearchContext.cs index 2483d0eaa3853..19d094147e8d4 100644 --- a/dotnet/src/webdriver/ISearchContext.cs +++ b/dotnet/src/webdriver/ISearchContext.cs @@ -16,6 +16,7 @@ // limitations under the License. // +using System; using System.Collections.ObjectModel; namespace OpenQA.Selenium @@ -30,6 +31,7 @@ public interface ISearchContext /// /// The locating mechanism to use. /// The first matching on the current context. + /// If is null. /// If no element matches the criteria. IWebElement FindElement(By by); diff --git a/dotnet/src/webdriver/JavaScriptEngine.cs b/dotnet/src/webdriver/JavaScriptEngine.cs index 76f89bdf391d5..32a7758cd03e7 100644 --- a/dotnet/src/webdriver/JavaScriptEngine.cs +++ b/dotnet/src/webdriver/JavaScriptEngine.cs @@ -218,14 +218,25 @@ public async Task ClearInitializationScripts() /// /// The JavaScript to pin /// A task containing a object to use to execute the script. + /// If is null. public async Task 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; } @@ -235,11 +246,17 @@ public async Task PinScript(string script) /// /// The object to unpin. /// A task that represents the asynchronous operation. + /// If is null. 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); } diff --git a/dotnet/src/webdriver/PinnedScript.cs b/dotnet/src/webdriver/PinnedScript.cs index fbc822c9135b9..8712b094c2642 100644 --- a/dotnet/src/webdriver/PinnedScript.cs +++ b/dotnet/src/webdriver/PinnedScript.cs @@ -16,83 +16,70 @@ // limitations under the License. // -using System; using System.Globalization; +#nullable enable + namespace OpenQA.Selenium { /// /// A class representing a pinned JavaScript function that can be repeatedly called /// without sending the entire script across the wire for every execution. /// - public class PinnedScript + public sealed class PinnedScript { - private string scriptSource; - private string scriptHandle; - private string scriptId; - /// /// Initializes a new instance of the class. /// /// The body of the JavaScript function to pin. + /// The unique handle for this pinned script. + /// The internal ID of this script. /// /// This constructor is explicitly internal. Creation of pinned script objects /// is strictly the perview of Selenium, and should not be required by external /// libraries. /// - 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; } /// /// Gets the unique handle for this pinned script. /// - public string Handle - { - get { return this.scriptHandle; } - } + public string Handle { get; } /// /// Gets the source representing the body of the function in the pinned script. /// - public string Source - { - get { return this.scriptSource; } - } + public string Source { get; } - /// - /// Gets the script to create the pinned script in the browser. - /// - 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); } /// /// Gets the script used to execute the pinned script in the browser. /// - 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); } /// /// Gets the script used to remove the pinned script from the browser. /// - 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); } /// /// Gets or sets the ID of this script. /// - internal string ScriptId - { - get { return this.scriptId; } - set { this.scriptId = value; } - } + internal string ScriptId { get; set; } } } diff --git a/dotnet/src/webdriver/WebDriver.cs b/dotnet/src/webdriver/WebDriver.cs index 6125490529a39..5a953aa75bfc9 100644 --- a/dotnet/src/webdriver/WebDriver.cs +++ b/dotnet/src/webdriver/WebDriver.cs @@ -279,9 +279,15 @@ public object ExecuteScript(string script, params object[] args) /// A object containing the JavaScript code to execute. /// The arguments to the script. /// The value returned by the script. + /// If is null. 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); } /// @@ -289,6 +295,7 @@ public object ExecuteScript(PinnedScript script, params object[] args) /// /// By mechanism to find the object /// IWebElement object so that you can interact with that object + /// If is null. /// /// /// IWebDriver driver = new InternetExplorerDriver(); diff --git a/dotnet/test/common/ExecutingJavascriptTest.cs b/dotnet/test/common/ExecutingJavascriptTest.cs index 74761c9af3667..e932c4156d72f 100644 --- a/dotnet/test/common/ExecutingJavascriptTest.cs +++ b/dotnet/test/common/ExecutingJavascriptTest.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Threading.Tasks; namespace OpenQA.Selenium { @@ -449,6 +450,30 @@ public void ShouldBeAbleToExecuteABigChunkOfJavascriptCode() } } + [Test] + 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()); + Assert.That(result, Is.EqualTo("XHTML Test Page")); + } + + await jsEngine.UnpinScript(script); + + Assert.That(() => + { + _ = ((IJavaScriptExecutor)driver).ExecuteScript(script); + }, Throws.TypeOf()); + } + [Test] public void ShouldBeAbleToExecuteScriptAndReturnElementsList() { From 3ccf4038c14f19ea1674c31a2703ee257865a3b8 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Tue, 12 Nov 2024 16:46:29 -0500 Subject: [PATCH 2/4] Use langword null --- dotnet/src/support/Events/EventFiringWebDriver.cs | 2 +- dotnet/src/webdriver/IJavaScriptEngine.cs | 8 ++++---- dotnet/src/webdriver/IJavascriptExecutor.cs | 2 +- dotnet/src/webdriver/ISearchContext.cs | 2 +- dotnet/src/webdriver/JavaScriptEngine.cs | 4 ++-- dotnet/src/webdriver/WebDriver.cs | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/dotnet/src/support/Events/EventFiringWebDriver.cs b/dotnet/src/support/Events/EventFiringWebDriver.cs index dd03ed7fa9982..e68b261e387c3 100644 --- a/dotnet/src/support/Events/EventFiringWebDriver.cs +++ b/dotnet/src/support/Events/EventFiringWebDriver.cs @@ -478,7 +478,7 @@ public object ExecuteScript(string script, params object[] args) /// A object containing the code to execute. /// The arguments to the script. /// The value returned by the script. - /// If is null. + /// If is . /// /// /// The ExecuteScript method executes JavaScript in the context of diff --git a/dotnet/src/webdriver/IJavaScriptEngine.cs b/dotnet/src/webdriver/IJavaScriptEngine.cs index 99422c6d4f36c..3445c59753d60 100644 --- a/dotnet/src/webdriver/IJavaScriptEngine.cs +++ b/dotnet/src/webdriver/IJavaScriptEngine.cs @@ -87,7 +87,7 @@ public interface IJavaScriptEngine : IDisposable /// The friendly name by which to refer to this initialization script. /// The JavaScript to be loaded on every page. /// A task containing an object representing the script to be loaded on each page. - /// If is null. + /// If is . Task AddInitializationScript(string scriptName, string script); /// @@ -95,7 +95,7 @@ public interface IJavaScriptEngine : IDisposable /// /// The friendly name of the initialization script to be removed. /// A task that represents the asynchronous operation. - /// If is null. + /// If is . Task RemoveInitializationScript(string scriptName); /// @@ -111,7 +111,7 @@ public interface IJavaScriptEngine : IDisposable /// /// The JavaScript to pin /// A task containing a object to use to execute the script. - /// If is null. + /// If is . Task PinScript(string script); /// @@ -119,7 +119,7 @@ public interface IJavaScriptEngine : IDisposable /// /// The object to unpin. /// A task that represents the asynchronous operation. - /// If is null. + /// If is . Task UnpinScript(PinnedScript script); /// diff --git a/dotnet/src/webdriver/IJavascriptExecutor.cs b/dotnet/src/webdriver/IJavascriptExecutor.cs index 95cb2a1fa7524..fcbf9080efd2c 100644 --- a/dotnet/src/webdriver/IJavascriptExecutor.cs +++ b/dotnet/src/webdriver/IJavascriptExecutor.cs @@ -99,7 +99,7 @@ public interface IJavaScriptExecutor /// variable, as if the function were called via "Function.apply" /// /// - /// If is null. + /// If is . object ExecuteScript(PinnedScript script, params object[] args); /// diff --git a/dotnet/src/webdriver/ISearchContext.cs b/dotnet/src/webdriver/ISearchContext.cs index 718cdbeb9cb7e..53c5f33ae36da 100644 --- a/dotnet/src/webdriver/ISearchContext.cs +++ b/dotnet/src/webdriver/ISearchContext.cs @@ -32,7 +32,7 @@ public interface ISearchContext /// /// The locating mechanism to use. /// The first matching on the current context. - /// If is null. + /// If is . /// If no element matches the criteria. IWebElement FindElement(By by); diff --git a/dotnet/src/webdriver/JavaScriptEngine.cs b/dotnet/src/webdriver/JavaScriptEngine.cs index 273fa37cb7973..4bd4fe4676eb4 100644 --- a/dotnet/src/webdriver/JavaScriptEngine.cs +++ b/dotnet/src/webdriver/JavaScriptEngine.cs @@ -219,7 +219,7 @@ public async Task ClearInitializationScripts() /// /// The JavaScript to pin /// A task containing a object to use to execute the script. - /// If is null. + /// If is . public async Task PinScript(string script) { if (script == null) @@ -247,7 +247,7 @@ public async Task PinScript(string script) /// /// The object to unpin. /// A task that represents the asynchronous operation. - /// If is null. + /// If is . public async Task UnpinScript(PinnedScript script) { if (script == null) diff --git a/dotnet/src/webdriver/WebDriver.cs b/dotnet/src/webdriver/WebDriver.cs index 0da35660975e2..e29ce29a12c79 100644 --- a/dotnet/src/webdriver/WebDriver.cs +++ b/dotnet/src/webdriver/WebDriver.cs @@ -280,7 +280,7 @@ public object ExecuteScript(string script, params object[] args) /// A object containing the JavaScript code to execute. /// The arguments to the script. /// The value returned by the script. - /// If is null. + /// If is . public object ExecuteScript(PinnedScript script, params object[] args) { if (script == null) @@ -296,7 +296,7 @@ public object ExecuteScript(PinnedScript script, params object[] args) /// /// By mechanism to find the object /// IWebElement object so that you can interact with that object - /// If is null. + /// If is . /// /// /// IWebDriver driver = new InternetExplorerDriver(); From 86855102f41de739ff03eb7d1b345e5ea33b8778 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Wed, 13 Nov 2024 17:55:52 -0500 Subject: [PATCH 3/4] Remove setter from `PinnedScript.ScriptId` --- dotnet/src/webdriver/PinnedScript.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/webdriver/PinnedScript.cs b/dotnet/src/webdriver/PinnedScript.cs index 5fa23178321bc..629c1bd7212f3 100644 --- a/dotnet/src/webdriver/PinnedScript.cs +++ b/dotnet/src/webdriver/PinnedScript.cs @@ -81,6 +81,6 @@ internal string MakeRemovalScript() /// /// Gets or sets the ID of this script. /// - internal string ScriptId { get; set; } + internal string ScriptId { get; } } } From 42f362095d0c3ec342c1e911226eade2c4fdbd6a Mon Sep 17 00:00:00 2001 From: Michael Render Date: Mon, 18 Nov 2024 17:27:10 -0500 Subject: [PATCH 4/4] disable `PinnedScript` tests on non-chromium --- dotnet/test/common/ExecutingJavascriptTest.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dotnet/test/common/ExecutingJavascriptTest.cs b/dotnet/test/common/ExecutingJavascriptTest.cs index f01680b780e52..048b712d3178d 100644 --- a/dotnet/test/common/ExecutingJavascriptTest.cs +++ b/dotnet/test/common/ExecutingJavascriptTest.cs @@ -470,6 +470,9 @@ 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);