Skip to content

Commit d9149ac

Browse files
[dotnet] Solidify nullability of PinnedScript (#14708)
1 parent 751bacb commit d9149ac

8 files changed

+89
-36
lines changed

dotnet/src/support/Events/EventFiringWebDriver.cs

+6
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,7 @@ public object ExecuteScript(string script, params object[] args)
478478
/// <param name="script">A <see cref="PinnedScript"/> object containing the code to execute.</param>
479479
/// <param name="args">The arguments to the script.</param>
480480
/// <returns>The value returned by the script.</returns>
481+
/// <exception cref="ArgumentNullException">If <paramref name="script"/> is <see langword="null"/>.</exception>
481482
/// <remarks>
482483
/// <para>
483484
/// The ExecuteScript method executes JavaScript in the context of
@@ -509,6 +510,11 @@ public object ExecuteScript(string script, params object[] args)
509510
/// </remarks>
510511
public object ExecuteScript(PinnedScript script, params object[] args)
511512
{
513+
if (script == null)
514+
{
515+
throw new ArgumentNullException(nameof(script));
516+
}
517+
512518
IJavaScriptExecutor javascriptDriver = this.driver as IJavaScriptExecutor;
513519
if (javascriptDriver == null)
514520
{

dotnet/src/webdriver/IJavaScriptEngine.cs

+4
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,15 @@ public interface IJavaScriptEngine : IDisposable
8787
/// <param name="scriptName">The friendly name by which to refer to this initialization script.</param>
8888
/// <param name="script">The JavaScript to be loaded on every page.</param>
8989
/// <returns>A task containing an <see cref="InitializationScript"/> object representing the script to be loaded on each page.</returns>
90+
/// <exception cref="ArgumentNullException">If <paramref name="scriptName"/> is <see langword="null"/>.</exception>
9091
Task<InitializationScript> AddInitializationScript(string scriptName, string script);
9192

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

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

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

121125
/// <summary>

dotnet/src/webdriver/IJavascriptExecutor.cs

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
// under the License.
1818
// </copyright>
1919

20+
using System;
2021
using System.Collections.Generic;
2122

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

103105
/// <summary>

dotnet/src/webdriver/ISearchContext.cs

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
// under the License.
1818
// </copyright>
1919

20+
using System;
2021
using System.Collections.ObjectModel;
2122

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

dotnet/src/webdriver/JavaScriptEngine.cs

+21-4
Original file line numberDiff line numberDiff line change
@@ -219,14 +219,25 @@ public async Task ClearInitializationScripts()
219219
/// </summary>
220220
/// <param name="script">The JavaScript to pin</param>
221221
/// <returns>A task containing a <see cref="PinnedScript"/> object to use to execute the script.</returns>
222+
/// <exception cref="ArgumentNullException">If <paramref name="script"/> is <see langword="null"/>.</exception>
222223
public async Task<PinnedScript> PinScript(string script)
223224
{
225+
if (script == null)
226+
{
227+
throw new ArgumentNullException(nameof(script));
228+
}
229+
230+
string newScriptHandle = Guid.NewGuid().ToString("N");
231+
224232
// We do an "Evaluate" first so as to immediately create the script on the loaded
225233
// page, then will add it to the initialization of future pages.
226-
PinnedScript pinnedScript = new PinnedScript(script);
227234
await this.EnableDomains().ConfigureAwait(false);
228-
await this.session.Value.Domains.JavaScript.Evaluate(pinnedScript.CreationScript).ConfigureAwait(false);
229-
pinnedScript.ScriptId = await this.session.Value.Domains.JavaScript.AddScriptToEvaluateOnNewDocument(pinnedScript.CreationScript).ConfigureAwait(false);
235+
236+
string creationScript = PinnedScript.MakeCreationScript(newScriptHandle, script);
237+
await this.session.Value.Domains.JavaScript.Evaluate(creationScript).ConfigureAwait(false);
238+
string scriptId = await this.session.Value.Domains.JavaScript.AddScriptToEvaluateOnNewDocument(creationScript).ConfigureAwait(false);
239+
240+
PinnedScript pinnedScript = new PinnedScript(script, newScriptHandle, scriptId);
230241
this.pinnedScripts[pinnedScript.Handle] = pinnedScript;
231242
return pinnedScript;
232243
}
@@ -236,11 +247,17 @@ public async Task<PinnedScript> PinScript(string script)
236247
/// </summary>
237248
/// <param name="script">The <see cref="PinnedScript"/> object to unpin.</param>
238249
/// <returns>A task that represents the asynchronous operation.</returns>
250+
/// <exception cref="ArgumentNullException">If <paramref name="script"/> is <see langword="null"/>.</exception>
239251
public async Task UnpinScript(PinnedScript script)
240252
{
253+
if (script == null)
254+
{
255+
throw new ArgumentNullException(nameof(script));
256+
}
257+
241258
if (this.pinnedScripts.ContainsKey(script.Handle))
242259
{
243-
await this.session.Value.Domains.JavaScript.Evaluate(script.RemovalScript).ConfigureAwait(false);
260+
await this.session.Value.Domains.JavaScript.Evaluate(script.MakeRemovalScript()).ConfigureAwait(false);
244261
await this.session.Value.Domains.JavaScript.RemoveScriptToEvaluateOnNewDocument(script.ScriptId).ConfigureAwait(false);
245262
this.pinnedScripts.Remove(script.Handle);
246263
}

dotnet/src/webdriver/PinnedScript.cs

+18-31
Original file line numberDiff line numberDiff line change
@@ -17,83 +17,70 @@
1717
// under the License.
1818
// </copyright>
1919

20-
using System;
2120
using System.Globalization;
2221

22+
#nullable enable
23+
2324
namespace OpenQA.Selenium
2425
{
2526
/// <summary>
2627
/// A class representing a pinned JavaScript function that can be repeatedly called
2728
/// without sending the entire script across the wire for every execution.
2829
/// </summary>
29-
public class PinnedScript
30+
public sealed class PinnedScript
3031
{
31-
private string scriptSource;
32-
private string scriptHandle;
33-
private string scriptId;
34-
3532
/// <summary>
3633
/// Initializes a new instance of the <see cref="PinnedScript"/> class.
3734
/// </summary>
3835
/// <param name="script">The body of the JavaScript function to pin.</param>
36+
/// <param name="stringHandle">The unique handle for this pinned script.</param>
37+
/// <param name="scriptId">The internal ID of this script.</param>
3938
/// <remarks>
4039
/// This constructor is explicitly internal. Creation of pinned script objects
4140
/// is strictly the perview of Selenium, and should not be required by external
4241
/// libraries.
4342
/// </remarks>
44-
internal PinnedScript(string script)
43+
internal PinnedScript(string script, string stringHandle, string scriptId)
4544
{
46-
this.scriptSource = script;
47-
this.scriptHandle = Guid.NewGuid().ToString("N");
45+
this.Source = script;
46+
this.Handle = stringHandle;
47+
this.ScriptId = scriptId;
4848
}
4949

5050
/// <summary>
5151
/// Gets the unique handle for this pinned script.
5252
/// </summary>
53-
public string Handle
54-
{
55-
get { return this.scriptHandle; }
56-
}
53+
public string Handle { get; }
5754

5855
/// <summary>
5956
/// Gets the source representing the body of the function in the pinned script.
6057
/// </summary>
61-
public string Source
62-
{
63-
get { return this.scriptSource; }
64-
}
58+
public string Source { get; }
6559

66-
/// <summary>
67-
/// Gets the script to create the pinned script in the browser.
68-
/// </summary>
69-
internal string CreationScript
60+
internal static string MakeCreationScript(string scriptHandle, string scriptSource)
7061
{
71-
get { return string.Format(CultureInfo.InvariantCulture, "function __webdriver_{0}(arguments) {{ {1} }}", this.scriptHandle, this.scriptSource); }
62+
return string.Format(CultureInfo.InvariantCulture, "function __webdriver_{0}(arguments) {{ {1} }}", scriptHandle, scriptSource);
7263
}
7364

7465
/// <summary>
7566
/// Gets the script used to execute the pinned script in the browser.
7667
/// </summary>
77-
internal string ExecutionScript
68+
internal string MakeExecutionScript()
7869
{
79-
get { return string.Format(CultureInfo.InvariantCulture, "return __webdriver_{0}(arguments)", this.scriptHandle); }
70+
return string.Format(CultureInfo.InvariantCulture, "return __webdriver_{0}(arguments)", this.Handle);
8071
}
8172

8273
/// <summary>
8374
/// Gets the script used to remove the pinned script from the browser.
8475
/// </summary>
85-
internal string RemovalScript
76+
internal string MakeRemovalScript()
8677
{
87-
get { return string.Format(CultureInfo.InvariantCulture, "__webdriver_{0} = undefined", this.scriptHandle); }
78+
return string.Format(CultureInfo.InvariantCulture, "__webdriver_{0} = undefined", this.Handle);
8879
}
8980

9081
/// <summary>
9182
/// Gets or sets the ID of this script.
9283
/// </summary>
93-
internal string ScriptId
94-
{
95-
get { return this.scriptId; }
96-
set { this.scriptId = value; }
97-
}
84+
internal string ScriptId { get; }
9885
}
9986
}

dotnet/src/webdriver/WebDriver.cs

+8-1
Original file line numberDiff line numberDiff line change
@@ -280,16 +280,23 @@ public object ExecuteScript(string script, params object[] args)
280280
/// <param name="script">A <see cref="PinnedScript"/> object containing the JavaScript code to execute.</param>
281281
/// <param name="args">The arguments to the script.</param>
282282
/// <returns>The value returned by the script.</returns>
283+
/// <exception cref="ArgumentNullException">If <paramref name="script" /> is <see langword="null"/>.</exception>
283284
public object ExecuteScript(PinnedScript script, params object[] args)
284285
{
285-
return this.ExecuteScript(script.ExecutionScript, args);
286+
if (script == null)
287+
{
288+
throw new ArgumentNullException(nameof(script));
289+
}
290+
291+
return this.ExecuteScript(script.MakeExecutionScript(), args);
286292
}
287293

288294
/// <summary>
289295
/// Finds the first element in the page that matches the <see cref="By"/> object
290296
/// </summary>
291297
/// <param name="by">By mechanism to find the object</param>
292298
/// <returns>IWebElement object so that you can interact with that object</returns>
299+
/// <exception cref="ArgumentNullException">If <paramref name="by" /> is <see langword="null"/>.</exception>
293300
/// <example>
294301
/// <code>
295302
/// IWebDriver driver = new InternetExplorerDriver();

dotnet/test/common/ExecutingJavascriptTest.cs

+28
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
using System;
2222
using System.Collections.Generic;
2323
using System.Collections.ObjectModel;
24+
using System.Threading.Tasks;
2425

2526
namespace OpenQA.Selenium
2627
{
@@ -468,6 +469,33 @@ public void ShouldBeAbleToExecuteABigChunkOfJavascriptCode()
468469
}
469470
}
470471

472+
[Test]
473+
[IgnoreBrowser(Selenium.Browser.IE, "IE does not support Chrome DevTools Protocol")]
474+
[IgnoreBrowser(Selenium.Browser.Firefox, "Firefox does not support Chrome DevTools Protocol")]
475+
[IgnoreBrowser(Selenium.Browser.Safari, "Safari does not support Chrome DevTools Protocol")]
476+
public async Task ShouldBeAbleToPinJavascriptCodeAndExecuteRepeatedly()
477+
{
478+
IJavaScriptEngine jsEngine = new JavaScriptEngine(driver);
479+
480+
driver.Url = xhtmlTestPage;
481+
482+
PinnedScript script = await jsEngine.PinScript("return document.title;");
483+
for (int i = 0; i < 5; i++)
484+
{
485+
object result = ((IJavaScriptExecutor)driver).ExecuteScript(script);
486+
487+
Assert.That(result, Is.InstanceOf<string>());
488+
Assert.That(result, Is.EqualTo("XHTML Test Page"));
489+
}
490+
491+
await jsEngine.UnpinScript(script);
492+
493+
Assert.That(() =>
494+
{
495+
_ = ((IJavaScriptExecutor)driver).ExecuteScript(script);
496+
}, Throws.TypeOf<JavaScriptException>());
497+
}
498+
471499
[Test]
472500
public void ShouldBeAbleToExecuteScriptAndReturnElementsList()
473501
{

0 commit comments

Comments
 (0)