Skip to content

Commit 631901e

Browse files
committed
[dotnet] Solidify nullability of PinnedScript, add test
1 parent 0219666 commit 631901e

File tree

8 files changed

+86
-36
lines changed

8 files changed

+86
-36
lines changed

dotnet/src/support/Events/EventFiringWebDriver.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,7 @@ public object ExecuteScript(string script, params object[] args)
477477
/// <param name="script">A <see cref="PinnedScript"/> object containing the code to execute.</param>
478478
/// <param name="args">The arguments to the script.</param>
479479
/// <returns>The value returned by the script.</returns>
480+
/// <exception cref="ArgumentNullException">If <paramref name="script"/> is null.</exception>
480481
/// <remarks>
481482
/// <para>
482483
/// The ExecuteScript method executes JavaScript in the context of
@@ -508,6 +509,11 @@ public object ExecuteScript(string script, params object[] args)
508509
/// </remarks>
509510
public object ExecuteScript(PinnedScript script, params object[] args)
510511
{
512+
if (script == null)
513+
{
514+
throw new ArgumentNullException(nameof(script));
515+
}
516+
511517
IJavaScriptExecutor javascriptDriver = this.driver as IJavaScriptExecutor;
512518
if (javascriptDriver == null)
513519
{

dotnet/src/webdriver/IJavaScriptEngine.cs

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

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

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

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

120124
/// <summary>

dotnet/src/webdriver/IJavascriptExecutor.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
// limitations under the License.
1717
// </copyright>
1818

19+
using System;
1920
using System.Collections.Generic;
2021

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

102104
/// <summary>

dotnet/src/webdriver/ISearchContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
// limitations under the License.
1717
// </copyright>
1818

19+
using System;
1920
using System.Collections.ObjectModel;
2021

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

dotnet/src/webdriver/JavaScriptEngine.cs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -218,14 +218,25 @@ public async Task ClearInitializationScripts()
218218
/// </summary>
219219
/// <param name="script">The JavaScript to pin</param>
220220
/// <returns>A task containing a <see cref="PinnedScript"/> object to use to execute the script.</returns>
221+
/// <exception cref="ArgumentNullException">If <paramref name="script"/> is null.</exception>
221222
public async Task<PinnedScript> PinScript(string script)
222223
{
224+
if (script == null)
225+
{
226+
throw new ArgumentNullException(nameof(script));
227+
}
228+
229+
string newScriptHandle = Guid.NewGuid().ToString("N");
230+
223231
// We do an "Evaluate" first so as to immediately create the script on the loaded
224232
// page, then will add it to the initialization of future pages.
225-
PinnedScript pinnedScript = new PinnedScript(script);
226233
await this.EnableDomains().ConfigureAwait(false);
227-
await this.session.Value.Domains.JavaScript.Evaluate(pinnedScript.CreationScript).ConfigureAwait(false);
228-
pinnedScript.ScriptId = await this.session.Value.Domains.JavaScript.AddScriptToEvaluateOnNewDocument(pinnedScript.CreationScript).ConfigureAwait(false);
234+
235+
string creationScript = PinnedScript.MakeCreationScript(newScriptHandle, script);
236+
await this.session.Value.Domains.JavaScript.Evaluate(creationScript).ConfigureAwait(false);
237+
string scriptId = await this.session.Value.Domains.JavaScript.AddScriptToEvaluateOnNewDocument(creationScript).ConfigureAwait(false);
238+
239+
PinnedScript pinnedScript = new PinnedScript(script, newScriptHandle, scriptId);
229240
this.pinnedScripts[pinnedScript.Handle] = pinnedScript;
230241
return pinnedScript;
231242
}
@@ -235,11 +246,17 @@ public async Task<PinnedScript> PinScript(string script)
235246
/// </summary>
236247
/// <param name="script">The <see cref="PinnedScript"/> object to unpin.</param>
237248
/// <returns>A task that represents the asynchronous operation.</returns>
249+
/// <exception cref="ArgumentNullException">If <paramref name="script"/> is null.</exception>
238250
public async Task UnpinScript(PinnedScript script)
239251
{
252+
if (script == null)
253+
{
254+
throw new ArgumentNullException(nameof(script));
255+
}
256+
240257
if (this.pinnedScripts.ContainsKey(script.Handle))
241258
{
242-
await this.session.Value.Domains.JavaScript.Evaluate(script.RemovalScript).ConfigureAwait(false);
259+
await this.session.Value.Domains.JavaScript.Evaluate(script.MakeRemovalScript()).ConfigureAwait(false);
243260
await this.session.Value.Domains.JavaScript.RemoveScriptToEvaluateOnNewDocument(script.ScriptId).ConfigureAwait(false);
244261
this.pinnedScripts.Remove(script.Handle);
245262
}

dotnet/src/webdriver/PinnedScript.cs

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,83 +16,70 @@
1616
// limitations under the License.
1717
// </copyright>
1818

19-
using System;
2019
using System.Globalization;
2120

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

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

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

65-
/// <summary>
66-
/// Gets the script to create the pinned script in the browser.
67-
/// </summary>
68-
internal string CreationScript
59+
internal static string MakeCreationScript(string scriptHandle, string scriptSource)
6960
{
70-
get { return string.Format(CultureInfo.InvariantCulture, "function __webdriver_{0}(arguments) {{ {1} }}", this.scriptHandle, this.scriptSource); }
61+
return string.Format(CultureInfo.InvariantCulture, "function __webdriver_{0}(arguments) {{ {1} }}", scriptHandle, scriptSource);
7162
}
7263

7364
/// <summary>
7465
/// Gets the script used to execute the pinned script in the browser.
7566
/// </summary>
76-
internal string ExecutionScript
67+
internal string MakeExecutionScript()
7768
{
78-
get { return string.Format(CultureInfo.InvariantCulture, "return __webdriver_{0}(arguments)", this.scriptHandle); }
69+
return string.Format(CultureInfo.InvariantCulture, "return __webdriver_{0}(arguments)", this.Handle);
7970
}
8071

8172
/// <summary>
8273
/// Gets the script used to remove the pinned script from the browser.
8374
/// </summary>
84-
internal string RemovalScript
75+
internal string MakeRemovalScript()
8576
{
86-
get { return string.Format(CultureInfo.InvariantCulture, "__webdriver_{0} = undefined", this.scriptHandle); }
77+
return string.Format(CultureInfo.InvariantCulture, "__webdriver_{0} = undefined", this.Handle);
8778
}
8879

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

dotnet/src/webdriver/WebDriver.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,16 +279,23 @@ public object ExecuteScript(string script, params object[] args)
279279
/// <param name="script">A <see cref="PinnedScript"/> object containing the JavaScript code to execute.</param>
280280
/// <param name="args">The arguments to the script.</param>
281281
/// <returns>The value returned by the script.</returns>
282+
/// <exception cref="ArgumentNullException">If <paramref name="script" /> is null.</exception>
282283
public object ExecuteScript(PinnedScript script, params object[] args)
283284
{
284-
return this.ExecuteScript(script.ExecutionScript, args);
285+
if (script == null)
286+
{
287+
throw new ArgumentNullException(nameof(script));
288+
}
289+
290+
return this.ExecuteScript(script.MakeExecutionScript(), args);
285291
}
286292

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

dotnet/test/common/ExecutingJavascriptTest.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System;
33
using System.Collections.Generic;
44
using System.Collections.ObjectModel;
5+
using System.Threading.Tasks;
56

67
namespace OpenQA.Selenium
78
{
@@ -449,6 +450,30 @@ public void ShouldBeAbleToExecuteABigChunkOfJavascriptCode()
449450
}
450451
}
451452

453+
[Test]
454+
public async Task ShouldBeAbleToPinJavascriptCodeAndExecuteRepeatedly()
455+
{
456+
IJavaScriptEngine jsEngine = new JavaScriptEngine(driver);
457+
458+
driver.Url = xhtmlTestPage;
459+
460+
PinnedScript script = await jsEngine.PinScript("return document.title;");
461+
for (int i = 0; i < 5; i++)
462+
{
463+
object result = ((IJavaScriptExecutor)driver).ExecuteScript(script);
464+
465+
Assert.That(result, Is.InstanceOf<string>());
466+
Assert.That(result, Is.EqualTo("XHTML Test Page"));
467+
}
468+
469+
await jsEngine.UnpinScript(script);
470+
471+
Assert.That(() =>
472+
{
473+
_ = ((IJavaScriptExecutor)driver).ExecuteScript(script);
474+
}, Throws.TypeOf<JavaScriptException>());
475+
}
476+
452477
[Test]
453478
public void ShouldBeAbleToExecuteScriptAndReturnElementsList()
454479
{

0 commit comments

Comments
 (0)