From 2e3e42665ed970cd7f94ca707a05438487e99f0b Mon Sep 17 00:00:00 2001 From: Michael Render Date: Mon, 3 Feb 2025 03:07:25 -0500 Subject: [PATCH 1/5] [dotnet] Fix `JavaScriptEngine.ScriptCallbackBindings` not containing new bindings --- dotnet/src/webdriver/JavaScriptEngine.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dotnet/src/webdriver/JavaScriptEngine.cs b/dotnet/src/webdriver/JavaScriptEngine.cs index 4bd4fe4676eb4..8ea7018530294 100644 --- a/dotnet/src/webdriver/JavaScriptEngine.cs +++ b/dotnet/src/webdriver/JavaScriptEngine.cs @@ -40,7 +40,7 @@ public class JavaScriptEngine : IJavaScriptEngine private Lazy session; private Dictionary initializationScripts = new Dictionary(); private Dictionary pinnedScripts = new Dictionary(); - private List bindings = new List(); + private HashSet bindings = new HashSet(); private bool isEnabled = false; private bool isDisposed = false; @@ -271,7 +271,7 @@ public async Task UnpinScript(PinnedScript script) /// A task that represents the asynchronous operation. public async Task AddScriptCallbackBinding(string bindingName) { - if (this.bindings.Contains(bindingName)) + if (!this.bindings.Add(bindingName)) { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "A binding named {0} has already been added", bindingName)); } @@ -288,7 +288,7 @@ public async Task AddScriptCallbackBinding(string bindingName) public async Task RemoveScriptCallbackBinding(string bindingName) { await this.session.Value.Domains.JavaScript.RemoveBinding(bindingName).ConfigureAwait(false); - this.bindings.Remove(bindingName); + _ = this.bindings.Remove(bindingName); } /// From 78cc853e26ee17974f95dde7c6fd0961030e8cf6 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Mon, 3 Feb 2025 03:10:14 -0500 Subject: [PATCH 2/5] Add tests --- dotnet/test/common/ExecutingJavascriptTest.cs | 103 +++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/dotnet/test/common/ExecutingJavascriptTest.cs b/dotnet/test/common/ExecutingJavascriptTest.cs index d5b239a9cf206..32cfbbccb08ef 100644 --- a/dotnet/test/common/ExecutingJavascriptTest.cs +++ b/dotnet/test/common/ExecutingJavascriptTest.cs @@ -480,7 +480,7 @@ public void ShouldBeAbleToExecuteABigChunkOfJavascriptCode() [IgnoreBrowser(Selenium.Browser.Safari, "Safari does not support Chrome DevTools Protocol")] public async Task ShouldBeAbleToPinJavascriptCodeAndExecuteRepeatedly() { - IJavaScriptEngine jsEngine = new JavaScriptEngine(driver); + using IJavaScriptEngine jsEngine = new JavaScriptEngine(driver); driver.Url = xhtmlTestPage; @@ -500,6 +500,107 @@ public async Task ShouldBeAbleToPinJavascriptCodeAndExecuteRepeatedly() Throws.TypeOf()); } + [Test] + [NeedsFreshDriver(IsCreatedAfterTest = true)] + [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 ShouldBeAbleToAddInitializationScriptAndExecuteOnNewDocument() + { + const string ScriptValue = "alert('notice')"; + const string ScriptName = "AlertScript"; + + using IJavaScriptEngine jsEngine = new JavaScriptEngine(driver); + + var initScript = await jsEngine.AddInitializationScript(ScriptName, ScriptValue); + + Assert.That(initScript, Is.Not.Null); + Assert.That(initScript.ScriptSource, Is.EqualTo(ScriptValue)); + Assert.That(initScript.ScriptName, Is.EqualTo(ScriptName)); + Assert.That(initScript.ScriptId, Is.Not.Null); + + await jsEngine.StartEventMonitoring(); + + driver.Navigate().Refresh(); + driver.SwitchTo().Alert().Accept(); + + Assert.That(jsEngine.InitializationScripts, Does.Contain(initScript)); + await jsEngine.RemoveInitializationScript(ScriptName); + + driver.Navigate().Refresh(); + Assert.That(() => driver.SwitchTo().Alert().Accept(), Throws.TypeOf()); + + Assert.That(jsEngine.InitializationScripts, Does.Not.Contain(initScript)); + + await jsEngine.AddInitializationScript(ScriptName, ScriptValue); + + driver.Navigate().Refresh(); + driver.SwitchTo().Alert().Accept(); + Assert.That(jsEngine.InitializationScripts, Does.Contain(initScript)); + + await jsEngine.ClearInitializationScripts(); + + driver.Navigate().Refresh(); + Assert.That(() => driver.SwitchTo().Alert().Accept(), Throws.TypeOf()); + Assert.That(jsEngine.InitializationScripts, Is.Empty); + + await jsEngine.AddInitializationScript(ScriptName, ScriptValue); + driver.Navigate().Refresh(); + driver.SwitchTo().Alert().Accept(); + + await jsEngine.ClearAll(); + driver.Navigate().Refresh(); + Assert.That(() => driver.SwitchTo().Alert().Accept(), Throws.TypeOf()); + Assert.That(jsEngine.InitializationScripts, Is.Empty); + } + + [Test] + [NeedsFreshDriver(IsCreatedAfterTest = true)] + [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 ShouldBeAbleToAddAndRemoveScriptCallbackBinding() + { + const string ScriptValue = "alert('Hello world')"; + const string ScriptName = "alert"; + + using IJavaScriptEngine jsEngine = new JavaScriptEngine(driver); + + var executedBindings = new List(); + jsEngine.JavaScriptCallbackExecuted += AddToList; + await jsEngine.AddInitializationScript(ScriptName, ScriptValue); + await jsEngine.StartEventMonitoring(); + + driver.Navigate().Refresh(); + driver.SwitchTo().Alert().Accept(); + + await jsEngine.AddScriptCallbackBinding(ScriptName); + + driver.Navigate().Refresh(); + Assert.That(() => driver.SwitchTo().Alert().Accept(), Throws.TypeOf()); + + Assert.That(executedBindings, Does.Contain(ScriptName)); + int oldCount = executedBindings.Count; + driver.Navigate().Refresh(); + + Assert.That(executedBindings, Has.Count.GreaterThan(oldCount)); + Assert.That(jsEngine.ScriptCallbackBindings, Does.Contain(ScriptName)); + oldCount = executedBindings.Count; + + await jsEngine.RemoveScriptCallbackBinding(ScriptName); + Assert.That(jsEngine.ScriptCallbackBindings, Is.Empty); + await jsEngine.AddScriptCallbackBinding(ScriptName); + Assert.That(jsEngine.ScriptCallbackBindings, Does.Contain(ScriptName)); + await jsEngine.ClearScriptCallbackBindings(); + Assert.That(jsEngine.ScriptCallbackBindings, Is.Empty); + + jsEngine.JavaScriptCallbackExecuted -= AddToList; + driver.Navigate().Refresh(); + Assert.That(executedBindings, Has.Count.EqualTo(oldCount)); + + void AddToList(object sender, JavaScriptCallbackExecutedEventArgs e) => executedBindings.Add(e.BindingName); + } + [Test] public void ShouldBeAbleToExecuteScriptAndReturnElementsList() { From 43182ff05311f750a14960d3d4b1ced503637db0 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Mon, 3 Feb 2025 12:42:20 -0500 Subject: [PATCH 3/5] Implement IEquatable on `InitializationScript` --- dotnet/src/webdriver/InitializationScript.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/dotnet/src/webdriver/InitializationScript.cs b/dotnet/src/webdriver/InitializationScript.cs index 0fa30bbfded79..80d805262876b 100644 --- a/dotnet/src/webdriver/InitializationScript.cs +++ b/dotnet/src/webdriver/InitializationScript.cs @@ -17,6 +17,7 @@ // under the License. // +using System; using System.Globalization; namespace OpenQA.Selenium @@ -24,7 +25,7 @@ namespace OpenQA.Selenium /// /// Represents a JavaScript script that is loaded and run on every document load. /// - public class InitializationScript + public class InitializationScript : IEquatable { /// /// Gets the internal ID of the initialization script. @@ -41,6 +42,16 @@ public class InitializationScript /// public string ScriptSource { get; internal set; } + /// + /// Indicates whether the current is equal to another of the same type. + /// + /// An to compare with this . + /// if the current is equal to the other parameter; otherwise, . + public bool Equals(InitializationScript other) + { + return other is not null && this.ScriptId == other.ScriptId && this.ScriptName == other.ScriptName && this.ScriptSource == other.ScriptSource; + } + /// /// Returns a string that represents the current object. /// From 316875085196df62ada7e401720d8b10bdcd68d3 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Mon, 3 Feb 2025 13:15:30 -0500 Subject: [PATCH 4/5] Remove implementation of IEquatable, override normal `Equals` --- dotnet/src/webdriver/InitializationScript.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dotnet/src/webdriver/InitializationScript.cs b/dotnet/src/webdriver/InitializationScript.cs index 80d805262876b..3fcf11d939442 100644 --- a/dotnet/src/webdriver/InitializationScript.cs +++ b/dotnet/src/webdriver/InitializationScript.cs @@ -17,7 +17,6 @@ // under the License. // -using System; using System.Globalization; namespace OpenQA.Selenium @@ -25,7 +24,7 @@ namespace OpenQA.Selenium /// /// Represents a JavaScript script that is loaded and run on every document load. /// - public class InitializationScript : IEquatable + public class InitializationScript { /// /// Gets the internal ID of the initialization script. @@ -47,9 +46,9 @@ public class InitializationScript : IEquatable /// /// An to compare with this . /// if the current is equal to the other parameter; otherwise, . - public bool Equals(InitializationScript other) + public override bool Equals(object obj) { - return other is not null && this.ScriptId == other.ScriptId && this.ScriptName == other.ScriptName && this.ScriptSource == other.ScriptSource; + return obj is InitializationScript other && this.ScriptId == other.ScriptId && this.ScriptName == other.ScriptName && this.ScriptSource == other.ScriptSource; } /// From d4605ef97e4928aff75bdd14ff0214c37957f819 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Mon, 3 Feb 2025 13:47:27 -0500 Subject: [PATCH 5/5] Override InitializationScript.GetHashCode --- dotnet/src/webdriver/InitializationScript.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/dotnet/src/webdriver/InitializationScript.cs b/dotnet/src/webdriver/InitializationScript.cs index 3fcf11d939442..eaf29bb9438c0 100644 --- a/dotnet/src/webdriver/InitializationScript.cs +++ b/dotnet/src/webdriver/InitializationScript.cs @@ -51,6 +51,18 @@ public override bool Equals(object obj) return obj is InitializationScript other && this.ScriptId == other.ScriptId && this.ScriptName == other.ScriptName && this.ScriptSource == other.ScriptSource; } + /// + /// Serves as a hash function for a particular . + /// + /// A hash code for the current . + public override int GetHashCode() + { + int result = this.ScriptId.GetHashCode(); + result = (31 * result) + this.ScriptName.GetHashCode(); + result = (31 * result) + this.ScriptSource.GetHashCode(); + return result; + } + /// /// Returns a string that represents the current object. ///