Skip to content

Commit f448555

Browse files
committed
Add input handling and server address properties
Updated UiaDriver.cs to include new using directives and methods for handling input scan codes and text inputs via HTTP requests. Added private classes for input data modeling. Added ServerAddress property to SimulatorCommandInvoker.cs and IWebDriverCommandInvoker.cs. Updated WebDriverCommandInvoker.cs to use the new property and removed the private _serverAddress field. Modified PublicExtensions.cs to update GetServerAddress method and added UiaExtensions class with methods for UI automation using WebDriver. Updated UiaDriverTests.cs with new using directives.
1 parent 4ada6cd commit f448555

File tree

7 files changed

+378
-11
lines changed

7 files changed

+378
-11
lines changed
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
using G4.WebDriver.Remote;
2+
3+
using System.Collections.Generic;
4+
using System.Net.Http;
5+
using System.Reflection;
6+
using System.Text.Json;
7+
8+
#pragma warning disable S3011 // Necessary to interact with internal fields for sending native actions.
9+
namespace G4.WebDriver.Extensions
10+
{
11+
/// <summary>
12+
/// Provides extension methods for UI automation using WebDriver.
13+
/// </summary>
14+
public static class UiaExtensions
15+
{
16+
// Initialize the static HttpClient instance for sending requests to the WebDriver server
17+
private static HttpClient HttpClient => new();
18+
19+
// Initialize the static JsonSerializerOptions instance for deserializing JSON responses
20+
private static JsonSerializerOptions JsonSerializerOptions => new()
21+
{
22+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
23+
PropertyNameCaseInsensitive = true
24+
};
25+
26+
/// <summary>
27+
/// Gets the UI Automation attribute value of a web element.
28+
/// </summary>
29+
/// <param name="element">The web element to get the attribute from.</param>
30+
/// <param name="name">The name of the attribute.</param>
31+
/// <returns>The attribute value as a string.</returns>
32+
public static string GetUiaAttribute(this IWebElement element, string name)
33+
{
34+
// Get the session ID and element ID from the web element
35+
var (sessionId, elementId) = GetRouteData(element);
36+
37+
// Get the WebDriver instance from the web element
38+
var driver = element.Driver;
39+
40+
// Get the remote server URI from the command executor
41+
var url = GetRemoteServerUri(driver);
42+
43+
// Construct the route for the attribute command
44+
var requestUri = $"{url}/session/{sessionId}/element/{elementId}/attribute/{name}";
45+
46+
// Create the HTTP request for the attribute
47+
var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
48+
49+
// Send the HTTP request
50+
var response = HttpClient.Send(request);
51+
52+
// Read the response content and deserialize the JSON response
53+
var responseContent = response.Content.ReadAsStringAsync().Result;
54+
var responseObject = JsonSerializer.Deserialize<Dictionary<string, object>>(responseContent, JsonSerializerOptions);
55+
56+
// Try to get the value from the response object
57+
var isValue = responseObject.TryGetValue("value", out var value);
58+
59+
// Ensure the response is successful
60+
response.EnsureSuccessStatusCode();
61+
62+
// Return the value as a string if it exists, otherwise return an empty string
63+
return isValue ? $"{value}" : string.Empty;
64+
}
65+
66+
/// <summary>
67+
/// Sends a native click command to a web element.
68+
/// </summary>
69+
/// <param name="element">The web element to click.</param>
70+
public static void SendNativeClick(this IWebElement element)
71+
{
72+
// Get the session ID and element ID from the web element
73+
var (sessionId, elementId) = GetRouteData(element);
74+
75+
// Get the WebDriver instance from the web element
76+
var driver = element.Driver;
77+
78+
// Get the remote server URI from the command executor
79+
var url = GetRemoteServerUri(driver);
80+
81+
// Construct the route for the native click command
82+
var requestUri = $"{url}/session/{sessionId}/user32/element/{elementId}/click";
83+
84+
// Create the HTTP request for the native click
85+
var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
86+
87+
// Send the HTTP request
88+
var response = HttpClient.Send(request);
89+
90+
// Ensure the response is successful
91+
response.EnsureSuccessStatusCode();
92+
}
93+
94+
/// <summary>
95+
/// Sends a native double-click command to a web element.
96+
/// </summary>
97+
/// <param name="element">The web element to double-click.</param>
98+
public static void SendNativeDoubleClick(this IWebElement element)
99+
{
100+
// Get the session ID and element ID from the web element
101+
var (sessionId, elementId) = GetRouteData(element);
102+
103+
// Get the WebDriver instance from the web element
104+
var driver = element.Driver;
105+
106+
// Get the remote server URI from the command executor
107+
var url = GetRemoteServerUri(driver);
108+
109+
// Construct the route for the native double-click command
110+
var requestUri = $"{url}/session/{sessionId}/user32/element/{elementId}/dclick";
111+
112+
// Create the HTTP request for the native double-click
113+
var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
114+
115+
// Send the HTTP request
116+
var response = HttpClient.Send(request);
117+
118+
// Ensure the response is successful
119+
response.EnsureSuccessStatusCode();
120+
}
121+
122+
/// <summary>
123+
/// Sets focus on a web element.
124+
/// </summary>
125+
/// <param name="element">The web element to set focus on.</param>
126+
public static void SetFocus(this IWebElement element)
127+
{
128+
// Get the session ID and element ID from the web element
129+
var (sessionId, elementId) = GetRouteData(element);
130+
131+
// Get the WebDriver instance from the web element
132+
var driver = element.Driver;
133+
134+
// Get the remote server URI from the command executor
135+
var url = GetRemoteServerUri(driver);
136+
137+
// Construct the route for the native focus command
138+
var requestUri = $"{url}/session/{sessionId}/user32/element/{elementId}/focus";
139+
140+
// Create the HTTP request for the native focus
141+
var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
142+
143+
// Send the HTTP request
144+
var response = HttpClient.Send(request);
145+
146+
// Ensure the response is successful
147+
response.EnsureSuccessStatusCode();
148+
}
149+
150+
// Gets the remote server URI from the WebDriver command executor.
151+
private static string GetRemoteServerUri(IWebDriver driver)
152+
{
153+
// Get the remote server URI from the command executor using reflection
154+
return driver.GetServerAddress().AbsoluteUri.Trim('/');
155+
}
156+
157+
// Gets the session ID and element ID from a web element.
158+
private static (string SessionId, string ElementId) GetRouteData(IWebElement element)
159+
{
160+
const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
161+
162+
// Get the WebDriver instance from the web element
163+
var driver = element.Driver;
164+
165+
// Get the session ID from the WebDriver
166+
var sessionId = $"{((IWebDriverSession)driver).Session}";
167+
168+
// Return the session ID and element ID as a tuple
169+
return (sessionId, element.Id);
170+
}
171+
}
172+
}

src/G4.WebDriver.Remote.Uia/UiaDriver.cs

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
using G4.WebDriver.Models;
33

44
using System;
5+
using System.Collections.Generic;
6+
using System.ComponentModel.DataAnnotations;
7+
using System.Net.Http;
8+
using System.Text.Json;
9+
using System.Text;
10+
using System.Threading;
511

612
namespace G4.WebDriver.Remote.Uia
713
{
@@ -169,5 +175,164 @@ public UiaDriver(IWebDriverCommandInvoker invoker, SessionModel session)
169175
// Additional initialization logic can be added here if needed.
170176
}
171177
#endregion
178+
179+
#region *** Methods ***
180+
// Initialize the static HttpClient instance for sending requests to the WebDriver server
181+
private static HttpClient HttpClient => new();
182+
183+
// Initialize the static JsonSerializerOptions instance for deserializing JSON responses
184+
private static JsonSerializerOptions JsonSerializerOptions => new()
185+
{
186+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
187+
PropertyNameCaseInsensitive = true
188+
};
189+
190+
/// <summary>
191+
/// Sends input scan codes to the WebDriver server.
192+
/// </summary>
193+
/// <param name="codes">The scan codes to send.</param>
194+
public void SendInputs(params string[] codes)
195+
{
196+
SendInputs(1, codes);
197+
}
198+
199+
/// <summary>
200+
/// Sends input scan codes to the WebDriver server multiple times.
201+
/// </summary>
202+
/// <param name="repeat">The number of times to repeat the input.</param>
203+
/// <param name="codes">The scan codes to send.</param>
204+
public void SendInputs(int repeat, params string[] codes)
205+
{
206+
// Get the session ID from the WebDriver
207+
var sessionId = $"{Session}";
208+
209+
// Get the remote server URI from the command executor
210+
var url = GetRemoteServerUri(driver: this);
211+
212+
// Construct the route for the input simulation command
213+
var requestUri = $"{url}/session/{sessionId}/user32/inputs";
214+
215+
// Create the request body with the scan codes to send to the server for input simulation
216+
var requestBody = new ScanCodesInputModel
217+
{
218+
ScanCodes = codes
219+
};
220+
221+
// Send the HTTP request and ensure the response is successful for the specified number of repeats
222+
for (var i = 0; i < repeat; i++)
223+
{
224+
// Create the HTTP content for the request body
225+
var content = new StringContent(
226+
content: JsonSerializer.Serialize(requestBody, JsonSerializerOptions),
227+
encoding: Encoding.UTF8,
228+
mediaType: "application/json");
229+
230+
// Create the HTTP request for the input simulation
231+
var request = new HttpRequestMessage(HttpMethod.Post, requestUri)
232+
{
233+
Content = content
234+
};
235+
236+
// Send the HTTP request
237+
var response = HttpClient.Send(request);
238+
239+
// Ensure the response is successful
240+
response.EnsureSuccessStatusCode();
241+
242+
// Wait for a short interval before sending the next input
243+
Thread.Sleep(100);
244+
}
245+
}
246+
247+
/// <summary>
248+
/// Sends text input to the WebDriver server.
249+
/// </summary>
250+
/// <param name="text">The text to send.</param>
251+
public void SendKeys(string text)
252+
{
253+
SendKeys(repeat: 1, text);
254+
}
255+
256+
/// <summary>
257+
/// Sends text input to the WebDriver server multiple times.
258+
/// </summary>
259+
/// <param name="repeat">The number of times to repeat the input.</param>
260+
/// <param name="text">The text to send.</param>
261+
public void SendKeys(int repeat, string text)
262+
{
263+
// Get the session ID from the WebDriver
264+
var sessionId = $"{Session}";
265+
266+
// Get the remote server URI from the command executor
267+
var url = GetRemoteServerUri(driver: this);
268+
269+
// Construct the route for the text input command
270+
var requestUri = $"{url}/session/{sessionId}/user32/value";
271+
272+
// Create the request body with the text to send to the server for input simulation
273+
var requestBody = new TextInputModel
274+
{
275+
Text = text
276+
};
277+
278+
// Send the HTTP request and ensure the response is successful for the specified number of repeats
279+
for (var i = 0; i < repeat; i++)
280+
{
281+
// Create the HTTP content for the request body
282+
var content = new StringContent(
283+
content: JsonSerializer.Serialize(requestBody, JsonSerializerOptions),
284+
encoding: Encoding.UTF8,
285+
mediaType: "application/json");
286+
287+
// Create the HTTP request for the text input
288+
var request = new HttpRequestMessage(HttpMethod.Post, requestUri)
289+
{
290+
Content = content
291+
};
292+
293+
// Send the HTTP request
294+
var response = HttpClient.Send(request);
295+
296+
// Ensure the response is successful
297+
response.EnsureSuccessStatusCode();
298+
299+
// Wait for a short interval before sending the next input
300+
Thread.Sleep(100);
301+
}
302+
}
303+
304+
// Gets the remote server URI from the WebDriver command executor.
305+
private static string GetRemoteServerUri(IWebDriver driver)
306+
{
307+
// Get the remote server URI from the command executor using reflection
308+
return driver.GetServerAddress().AbsoluteUri.Trim('/');
309+
}
310+
311+
/// <summary>
312+
/// Model for sending scan codes as input.
313+
/// </summary>
314+
private sealed class ScanCodesInputModel
315+
{
316+
/// <summary>
317+
/// Gets or sets the collection of scan codes.
318+
/// </summary>
319+
/// <value>A collection of scan codes required for the operation.</value>
320+
[Required]
321+
public IEnumerable<string> ScanCodes { get; set; }
322+
}
323+
324+
/// <summary>
325+
/// Model for sending text input.
326+
/// </summary>
327+
private sealed class TextInputModel
328+
{
329+
/// <summary>
330+
/// Gets or sets the text to be input.
331+
/// </summary>
332+
/// <value>The text to be input.</value>
333+
[Required]
334+
public string Text { get; set; }
335+
}
336+
#endregion
172337
}
173338
}

src/G4.WebDriver.Simulator/SimulatorCommandInvoker.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ public SimulatorCommandInvoker()
3636
/// </summary>
3737
public IDictionary<string, WebDriverCommandModel> Commands { get; }
3838

39+
/// <summary>
40+
/// Gets the address of the WebDriver server.
41+
/// </summary>
42+
public Uri ServerAddress => new("http://simulator.local.io");
43+
3944
/// <summary>
4045
/// Gets or sets the session identifier model.
4146
/// </summary>

src/G4.WebDriver.Tests/UiaDriverTests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
using G4.WebDriver.Models;
2+
using G4.WebDriver.Extensions;
23
using G4.WebDriver.Remote.Uia;
34

45
using Microsoft.VisualStudio.TestTools.UnitTesting;
56

7+
using System;
68
using System.IO;
9+
using System.Threading;
710

811
namespace G4.WebDriver.Tests
912
{

src/G4.WebDriver/Extensions/PublicExtensions.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,17 @@ public static IEnumerable<IWebElement> GetSelectedElements(this IWebDriver drive
514514
/// </summary>
515515
/// <param name="driver">The instance of the IWebDriver from which to retrieve the server address.</param>
516516
/// <returns>The URI representing the server address of the WebDriver service.</returns>
517-
public static Uri GetServerAddress(this IWebDriver driver) => driver.Invoker.WebDriverService.ServerAddress;
517+
public static Uri GetServerAddress(this IWebDriver driver)
518+
{
519+
// Get the WebDriver service and server address from the driver instance.
520+
var webDriverService = driver.Invoker.WebDriverService;
521+
var serverAddress = driver.Invoker.ServerAddress;
522+
523+
// Return the server address from the WebDriver service if available, or the default server address.
524+
return webDriverService == null || webDriverService.ServerAddress == default
525+
? serverAddress
526+
: webDriverService.ServerAddress;
527+
}
518528

519529
/// <summary>
520530
/// Retrieves the current session ID associated with the WebDriver instance.

0 commit comments

Comments
 (0)