Skip to content

Commit 76443af

Browse files
authored
Merge pull request #47 from FlaUI/basic-windows-extensions
Basic windows extensions
2 parents 7889ff4 + 837241e commit 76443af

File tree

13 files changed

+347
-9
lines changed

13 files changed

+347
-9
lines changed

src/FlaUI.WebDriver.UITests/ActionsTest.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public void ReleaseActions_Default_ReleasesKeys()
5050
}
5151

5252
[Test]
53-
public void PerformActions_MoveToElement_Click()
53+
public void PerformActions_MoveToElementAndClick_SelectsElement()
5454
{
5555
var element = _driver.FindElement(ExtendedBy.AccessibilityId("TextBox"));
5656

@@ -60,7 +60,17 @@ public void PerformActions_MoveToElement_Click()
6060
}
6161

6262
[Test]
63-
public void PerformActions_MoveToElement_MoveByOffset_Click()
63+
public void PerformActions_MoveToElement_IsSupported()
64+
{
65+
var element = _driver.FindElement(ExtendedBy.AccessibilityId("LabelWithHover"));
66+
67+
new Actions(_driver).MoveToElement(element).Perform();
68+
69+
Assert.That(element.Text, Is.EqualTo("Hovered!"));
70+
}
71+
72+
[Test]
73+
public void PerformActions_MoveToElementMoveByOffsetAndClick_SelectsElement()
6474
{
6575
var element = _driver.FindElement(ExtendedBy.AccessibilityId("TextBox"));
6676

src/FlaUI.WebDriver.UITests/ExecuteTests.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using FlaUI.WebDriver.UITests.TestUtil;
22
using NUnit.Framework;
3+
using OpenQA.Selenium;
4+
using OpenQA.Selenium.Interactions;
35
using OpenQA.Selenium.Remote;
46
using System.Collections.Generic;
57

@@ -18,5 +20,54 @@ public void ExecuteScript_PowerShellCommand_ReturnsResult()
1820

1921
Assert.That(executeScriptResult, Is.EqualTo("2\r\n"));
2022
}
23+
24+
[Test]
25+
public void ExecuteScript_WindowsClickXY_IsSupported()
26+
{
27+
var driverOptions = FlaUIDriverOptions.TestApp();
28+
using var driver = new RemoteWebDriver(WebDriverFixture.WebDriverUrl, driverOptions);
29+
var element = driver.FindElement(ExtendedBy.AccessibilityId("TextBox"));
30+
31+
driver.ExecuteScript("windows: click", new Dictionary<string, object> { ["x"] = element.Location.X + element.Size.Width / 2, ["y"] = element.Location.Y + element.Size.Height / 2});
32+
33+
string activeElementText = driver.SwitchTo().ActiveElement().Text;
34+
Assert.That(activeElementText, Is.EqualTo("Test TextBox"));
35+
}
36+
37+
[Test]
38+
public void ExecuteScript_WindowsHoverXY_IsSupported()
39+
{
40+
var driverOptions = FlaUIDriverOptions.TestApp();
41+
using var driver = new RemoteWebDriver(WebDriverFixture.WebDriverUrl, driverOptions);
42+
var element = driver.FindElement(ExtendedBy.AccessibilityId("LabelWithHover"));
43+
44+
driver.ExecuteScript("windows: hover", new Dictionary<string, object> {
45+
["startX"] = element.Location.X + element.Size.Width / 2,
46+
["startY"] = element.Location.Y + element.Size.Height / 2,
47+
["endX"] = element.Location.X + element.Size.Width / 2,
48+
["endY"] = element.Location.Y + element.Size.Height / 2
49+
});
50+
51+
Assert.That(element.Text, Is.EqualTo("Hovered!"));
52+
}
53+
54+
[Test]
55+
public void ExecuteScript_WindowsKeys_IsSupported()
56+
{
57+
var driverOptions = FlaUIDriverOptions.TestApp();
58+
using var driver = new RemoteWebDriver(WebDriverFixture.WebDriverUrl, driverOptions);
59+
var element = driver.FindElement(ExtendedBy.AccessibilityId("TextBox"));
60+
element.Click();
61+
62+
driver.ExecuteScript("windows: keys", new Dictionary<string, object> { ["actions"] = new[] {
63+
new Dictionary<string, object> { ["virtualKeyCode"] = 0x11, ["down"]=true }, // CTRL
64+
new Dictionary<string, object> { ["virtualKeyCode"] = 0x08, ["down"]=true }, // BACKSPACE
65+
new Dictionary<string, object> { ["virtualKeyCode"] = 0x08, ["down"]=false },
66+
new Dictionary<string, object> { ["virtualKeyCode"] = 0x11, ["down"]=false }
67+
} });
68+
69+
string activeElementText = driver.SwitchTo().ActiveElement().Text;
70+
Assert.That(activeElementText, Is.EqualTo("Test "));
71+
}
2172
}
2273
}

src/FlaUI.WebDriver.UITests/WindowTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public void GetWindowRect_Default_IsSupported()
2222
Assert.That(position.X, Is.GreaterThanOrEqualTo(0));
2323
Assert.That(position.Y, Is.GreaterThanOrEqualTo(0));
2424
Assert.That(size.Width, Is.InRange(629 * scaling, 630 * scaling));
25-
Assert.That(size.Height, Is.InRange(515 * scaling, 516 * scaling));
25+
Assert.That(size.Height, Is.InRange(550 * scaling, 551 * scaling));
2626
}
2727

2828
[Test]

src/FlaUI.WebDriver/Controllers/ExecuteController.cs

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1-
using FlaUI.WebDriver.Models;
1+
using FlaUI.Core.Input;
2+
using FlaUI.Core.WindowsAPI;
3+
using FlaUI.WebDriver.Models;
4+
using FlaUI.WebDriver.Services;
5+
using Microsoft.AspNetCore.Authentication.OAuth.Claims;
26
using Microsoft.AspNetCore.Mvc;
37
using System.Diagnostics;
8+
using System.Drawing;
9+
using System.Text.Json;
410

511
namespace FlaUI.WebDriver.Controllers
612
{
@@ -10,10 +16,12 @@ public class ExecuteController : ControllerBase
1016
{
1117
private readonly ILogger<ExecuteController> _logger;
1218
private readonly ISessionRepository _sessionRepository;
19+
private readonly IWindowsExtensionService _windowsExtensionService;
1320

14-
public ExecuteController(ISessionRepository sessionRepository, ILogger<ExecuteController> logger)
21+
public ExecuteController(ISessionRepository sessionRepository, IWindowsExtensionService windowsExtensionService, ILogger<ExecuteController> logger)
1522
{
1623
_sessionRepository = sessionRepository;
24+
_windowsExtensionService = windowsExtensionService;
1725
_logger = logger;
1826
}
1927

@@ -25,21 +33,37 @@ public async Task<ActionResult> ExecuteScript([FromRoute] string sessionId, [Fro
2533
{
2634
case "powerShell":
2735
return await ExecutePowerShellScript(session, executeScriptRequest);
36+
case "windows: keys":
37+
return await ExecuteWindowsKeysScript(session, executeScriptRequest);
38+
case "windows: click":
39+
return await ExecuteWindowsClickScript(session, executeScriptRequest);
40+
case "windows: hover":
41+
return await ExecuteWindowsHoverScript(session, executeScriptRequest);
2842
default:
2943
throw WebDriverResponseException.UnsupportedOperation("Only 'powerShell' scripts are supported");
3044
}
3145
}
46+
3247
private async Task<ActionResult> ExecutePowerShellScript(Session session, ExecuteScriptRequest executeScriptRequest)
3348
{
3449
if (executeScriptRequest.Args.Count != 1)
3550
{
3651
throw WebDriverResponseException.InvalidArgument($"Expected an array of exactly 1 arguments for the PowerShell script, but got {executeScriptRequest.Args.Count} arguments");
3752
}
3853
var powerShellArgs = executeScriptRequest.Args[0];
39-
if (!powerShellArgs.TryGetValue("command", out var powerShellCommand))
54+
if (!powerShellArgs.TryGetProperty("command", out var powerShellCommandJson))
4055
{
4156
throw WebDriverResponseException.InvalidArgument("Expected a \"command\" property of the first argument for the PowerShell script");
4257
}
58+
if (powerShellCommandJson.ValueKind != JsonValueKind.String)
59+
{
60+
throw WebDriverResponseException.InvalidArgument($"Powershell \"command\" property must be a string");
61+
}
62+
string? powerShellCommand = powerShellCommandJson.GetString();
63+
if (string.IsNullOrEmpty(powerShellCommand))
64+
{
65+
throw WebDriverResponseException.InvalidArgument($"Powershell \"command\" property must be non-empty");
66+
}
4367

4468
_logger.LogInformation("Executing PowerShell command {Command} (session {SessionId})", powerShellCommand, session.SessionId);
4569

@@ -68,6 +92,72 @@ private async Task<ActionResult> ExecutePowerShellScript(Session session, Execut
6892
return WebDriverResult.Success(result);
6993
}
7094

95+
private async Task<ActionResult> ExecuteWindowsClickScript(Session session, ExecuteScriptRequest executeScriptRequest)
96+
{
97+
if (executeScriptRequest.Args.Count != 1)
98+
{
99+
throw WebDriverResponseException.InvalidArgument($"Expected an array of exactly 1 arguments for the windows: click script, but got {executeScriptRequest.Args.Count} arguments");
100+
}
101+
var action = JsonSerializer.Deserialize<WindowsClickScript>(executeScriptRequest.Args[0], new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
102+
if (action == null)
103+
{
104+
throw WebDriverResponseException.InvalidArgument("Action cannot be null");
105+
}
106+
await _windowsExtensionService.ExecuteClickScript(session, action);
107+
return WebDriverResult.Success();
108+
}
109+
110+
private async Task<ActionResult> ExecuteWindowsHoverScript(Session session, ExecuteScriptRequest executeScriptRequest)
111+
{
112+
if (executeScriptRequest.Args.Count != 1)
113+
{
114+
throw WebDriverResponseException.InvalidArgument($"Expected an array of exactly 1 arguments for the windows: hover script, but got {executeScriptRequest.Args.Count} arguments");
115+
}
116+
var action = JsonSerializer.Deserialize<WindowsHoverScript>(executeScriptRequest.Args[0], new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
117+
if (action == null)
118+
{
119+
throw WebDriverResponseException.InvalidArgument("Action cannot be null");
120+
}
121+
await _windowsExtensionService.ExecuteHoverScript(session, action);
122+
return WebDriverResult.Success();
123+
}
124+
125+
private async Task<ActionResult> ExecuteWindowsKeysScript(Session session, ExecuteScriptRequest executeScriptRequest)
126+
{
127+
if (executeScriptRequest.Args.Count != 1)
128+
{
129+
throw WebDriverResponseException.InvalidArgument($"Expected an array of exactly 1 arguments for the windows: keys script, but got {executeScriptRequest.Args.Count} arguments");
130+
}
131+
var windowsKeysArgs = executeScriptRequest.Args[0];
132+
if (!windowsKeysArgs.TryGetProperty("actions", out var actionsJson))
133+
{
134+
throw WebDriverResponseException.InvalidArgument("Expected a \"actions\" property of the first argument for the windows: keys script");
135+
}
136+
session.CurrentWindow.FocusNative();
137+
if (actionsJson.ValueKind == JsonValueKind.Array)
138+
{
139+
var actions = JsonSerializer.Deserialize<List<WindowsKeyScript>>(actionsJson, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
140+
if (actions == null)
141+
{
142+
throw WebDriverResponseException.InvalidArgument("Argument \"actions\" cannot be null");
143+
}
144+
foreach (var action in actions)
145+
{
146+
await _windowsExtensionService.ExecuteKeyScript(session, action);
147+
}
148+
}
149+
else
150+
{
151+
var action = JsonSerializer.Deserialize<WindowsKeyScript>(actionsJson, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
152+
if (action == null)
153+
{
154+
throw WebDriverResponseException.InvalidArgument("Action cannot be null");
155+
}
156+
await _windowsExtensionService.ExecuteKeyScript(session, action);
157+
}
158+
return WebDriverResult.Success();
159+
}
160+
71161
private Session GetSession(string sessionId)
72162
{
73163
var session = _sessionRepository.FindById(sessionId);
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
namespace FlaUI.WebDriver.Models
1+
using System.Text.Json;
2+
3+
namespace FlaUI.WebDriver.Models
24
{
35
public class ExecuteScriptRequest
46
{
57
public string Script { get; set; } = null!;
6-
public List<Dictionary<string, string>> Args { get; set; } = new List<Dictionary<string, string>>();
8+
public List<JsonElement> Args { get; set; } = new List<JsonElement>();
79
}
810
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace FlaUI.WebDriver.Models
2+
{
3+
public class WindowsClickScript
4+
{
5+
public string? ElementId { get; set; }
6+
public int? X { get; set; }
7+
public int? Y { get; set; }
8+
public string? Button { get; set; }
9+
}
10+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace FlaUI.WebDriver.Models
2+
{
3+
public class WindowsHoverScript
4+
{
5+
public string? StartElementId { get; set; }
6+
public int? StartX { get; set; }
7+
public int? StartY { get; set; }
8+
public int? EndX { get; set; }
9+
public int? EndY { get; set; }
10+
public string? EndElementId { get; set; }
11+
public int? DurationMs { get; set; }
12+
}
13+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace FlaUI.WebDriver.Models
2+
{
3+
public class WindowsKeyScript
4+
{
5+
public int? Pause { get; set; }
6+
public string? Text { get; set; }
7+
public ushort? VirtualKeyCode { get; set; }
8+
public bool? Down { get; set; }
9+
}
10+
}

src/FlaUI.WebDriver/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
builder.Services.AddSingleton<ISessionRepository, SessionRepository>();
88
builder.Services.AddScoped<IActionsDispatcher, ActionsDispatcher>();
9+
builder.Services.AddScoped<IWindowsExtensionService, WindowsExtensionService>();
910

1011
builder.Services.Configure<RouteOptions>(options => options.LowercaseUrls = true);
1112
builder.Services.AddControllers(options =>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using FlaUI.WebDriver.Models;
2+
3+
namespace FlaUI.WebDriver.Services
4+
{
5+
public interface IWindowsExtensionService
6+
{
7+
Task ExecuteClickScript(Session session, WindowsClickScript action);
8+
Task ExecuteHoverScript(Session session, WindowsHoverScript action);
9+
Task ExecuteKeyScript(Session session, WindowsKeyScript action);
10+
}
11+
}

0 commit comments

Comments
 (0)