Skip to content

Commit 2548da3

Browse files
committed
Possible resolution to #281 - Needs cross-browser testing
This might also need some tidy-up!
1 parent 0203161 commit 2548da3

7 files changed

Lines changed: 94 additions & 3 deletions

File tree

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using CSF.Screenplay.Selenium.Elements;
5+
using OpenQA.Selenium;
6+
using static CSF.Screenplay.Selenium.PerformableBuilder;
7+
8+
namespace CSF.Screenplay.Selenium.Actions
9+
{
10+
/// <summary>
11+
/// An action that sets the value of a web element using JavaScript.
12+
/// </summary>
13+
public class SetTheElementValue : ISingleElementPerformable
14+
{
15+
readonly object value;
16+
17+
/// <inheritdoc/>
18+
public ReportFragment GetReportFragment(Actor actor, Lazy<SeleniumElement> element, IFormatsReportFragment formatter)
19+
=> formatter.Format("{Actor} uses JavaScript to set the value of {Element} to {Value}", actor, element.Value, value);
20+
21+
/// <inheritdoc/>
22+
public ValueTask PerformAsAsync(ICanPerform actor, IWebDriver webDriver, Lazy<SeleniumElement> element, CancellationToken cancellationToken = default)
23+
{
24+
return actor.PerformAsync(ExecuteAScript(Scripts.SetElementValue, element.Value, value), cancellationToken);
25+
}
26+
27+
/// <summary>
28+
/// Initializes a new instance of the <see cref="SetTheElementValue"/> class.
29+
/// </summary>
30+
/// <param name="value">The value to set on the element.</param>
31+
public SetTheElementValue(object value)
32+
{
33+
this.value = value;
34+
}
35+
}
36+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using CSF.Screenplay.Selenium.Actions;
2+
using CSF.Screenplay.Selenium.Elements;
3+
4+
namespace CSF.Screenplay.Selenium
5+
{
6+
/// <summary>
7+
/// Builds actions for setting values in Selenium web elements.
8+
/// </summary>
9+
public class SetTheValueBuilder
10+
{
11+
readonly ITarget target;
12+
13+
/// <summary>
14+
/// Gets a performable, to set the target element to the value specified in this method.
15+
/// </summary>
16+
/// <param name="value">The new value for the element</param>
17+
/// <returns>A performable</returns>
18+
public IPerformable To(object value) => SingleElementPerformableAdapter.From(new SetTheElementValue(value), target);
19+
20+
/// <summary>
21+
/// Initializes a new instance of the <see cref="SetTheValueBuilder"/> class with the specified target.
22+
/// </summary>
23+
/// <param name="target">The target web element for which to build value-setting actions.</param>
24+
public SetTheValueBuilder(ITarget target)
25+
{
26+
this.target = target ?? throw new System.ArgumentNullException(nameof(target));
27+
}
28+
}
29+
}

CSF.Screenplay.Selenium/PerformableBuilder.elementPerformables.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,5 +160,19 @@ public static FromTargetActionBuilder SelectTheOptionWithValue(string optionValu
160160
/// <param name="target">The target element whose contents will be cleared.</param>
161161
/// <returns>A performable action</returns>
162162
public static IPerformable ClearTheContentsOf(ITarget target) => SingleElementPerformableAdapter.From(new ClearTheContents(), target);
163+
164+
/// <summary>
165+
/// Gets a builder for an action which sets the value of an element programatically, using JavaScript.
166+
/// </summary>
167+
/// <remarks>
168+
/// <para>
169+
/// It is advised to use this technique and others like it sparingly, particularly when using Screenplay/Selenium for testing. Whilst
170+
/// it is possible to set values and update the web page's state via JavaScript, this does not properly mimic the manner in which a real
171+
/// human being would interact with the page.
172+
/// </para>
173+
/// </remarks>
174+
/// <param name="target">The target HTML element, to have its value updated.</param>
175+
/// <returns>A builder with which to choose the new value</returns>
176+
public static SetTheValueBuilder SetTheValueOf(ITarget target) => new SetTheValueBuilder(target);
163177
}
164178
}

CSF.Screenplay.Selenium/Resources/ScriptResources.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ static class ScriptResources
1212
/// <summary>Gets a short JavaScript for <see cref="Actions.ClearLocalStorage"/>.</summary>
1313
internal static string ClearLocalStorage => resourceManager.GetString("ClearLocalStorage");
1414

15+
/// <summary>Gets a short JavaScript that returns the value of <c>document.readyState</c>.</summary>
1516
internal static string GetDocReadyState => resourceManager.GetString("GetDocReadyState");
17+
18+
/// <summary>Gets a short JavaScript which sets the <c>value</c> of an HTML element.</summary>
19+
internal static string SetElementValue = resourceManager.GetString("SetElementValue");
1620
}
1721
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
ClearLocalStorage = localStorage.clear()
2-
GetDocReadyState = return document.readyState
2+
GetDocReadyState = return document.readyState
3+
SetElementValue = arguments[0].value = arguments[1]

CSF.Screenplay.Selenium/Scripts.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using CSF.Screenplay.Selenium.Elements;
2+
13
namespace CSF.Screenplay.Selenium
24
{
35
/// <summary>
@@ -15,7 +17,6 @@ public static class Scripts
1517
/// <summary>
1618
/// Gets a <see cref="NamedScript"/> which clears the Local Storage of the browser, for the current domain.
1719
/// </summary>
18-
/// <returns>A named script.</returns>
1920
public static NamedScript ClearLocalStorage
2021
=> new NamedScript(Resources.ScriptResources.ClearLocalStorage, "clear the local storage");
2122

@@ -28,5 +29,11 @@ public static NamedScript ClearLocalStorage
2829
/// </remarks>
2930
public static NamedScriptWithResult<string> GetTheDocumentReadyState
3031
=> new NamedScriptWithResult<string>(Resources.ScriptResources.GetDocReadyState, "get the readiness of the current page");
32+
33+
/// <summary>
34+
/// Gets a <see cref="NamedScript{T1, T2}"/> which sets the <c>value</c> of a specified HTML element.
35+
/// </summary>
36+
public static NamedScript<SeleniumElement, object> SetElementValue
37+
=> new NamedScript<SeleniumElement, object>(Resources.ScriptResources.SetElementValue, "set the element's value");
3138
}
3239
}

CSF.Screenplay.Selenium/Tasks/EnterTheDate.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public ValueTask PerformAsAsync(ICanPerform actor, CancellationToken cancellatio
5151
{
5252
var browseTheWeb = actor.GetAbility<BrowseTheWeb>();
5353
if(browseTheWeb.WebDriver.HasQuirk(BrowserQuirks.CannotSetInputTypeDateWithSendKeys))
54-
throw new NotImplementedException("This has yet to be written, see #281");
54+
return actor.PerformAsync(SetTheValueOf(target).To(date.HasValue ? date.Value.ToString("yyyy-MM-dd") : null));
5555

5656
if(!date.HasValue)
5757
return actor.PerformAsync(ClearTheContentsOf(target), cancellationToken);

0 commit comments

Comments
 (0)