Skip to content

Commit

Permalink
Merge pull request #47 from FlaUI/basic-windows-extensions
Browse files Browse the repository at this point in the history
Basic windows extensions
  • Loading branch information
aristotelos authored May 16, 2024
2 parents 7889ff4 + 837241e commit 76443af
Show file tree
Hide file tree
Showing 13 changed files with 347 additions and 9 deletions.
14 changes: 12 additions & 2 deletions src/FlaUI.WebDriver.UITests/ActionsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public void ReleaseActions_Default_ReleasesKeys()
}

[Test]
public void PerformActions_MoveToElement_Click()
public void PerformActions_MoveToElementAndClick_SelectsElement()
{
var element = _driver.FindElement(ExtendedBy.AccessibilityId("TextBox"));

Expand All @@ -60,7 +60,17 @@ public void PerformActions_MoveToElement_Click()
}

[Test]
public void PerformActions_MoveToElement_MoveByOffset_Click()
public void PerformActions_MoveToElement_IsSupported()
{
var element = _driver.FindElement(ExtendedBy.AccessibilityId("LabelWithHover"));

new Actions(_driver).MoveToElement(element).Perform();

Assert.That(element.Text, Is.EqualTo("Hovered!"));
}

[Test]
public void PerformActions_MoveToElementMoveByOffsetAndClick_SelectsElement()
{
var element = _driver.FindElement(ExtendedBy.AccessibilityId("TextBox"));

Expand Down
51 changes: 51 additions & 0 deletions src/FlaUI.WebDriver.UITests/ExecuteTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using FlaUI.WebDriver.UITests.TestUtil;
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Interactions;
using OpenQA.Selenium.Remote;
using System.Collections.Generic;

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

Assert.That(executeScriptResult, Is.EqualTo("2\r\n"));
}

[Test]
public void ExecuteScript_WindowsClickXY_IsSupported()
{
var driverOptions = FlaUIDriverOptions.TestApp();
using var driver = new RemoteWebDriver(WebDriverFixture.WebDriverUrl, driverOptions);
var element = driver.FindElement(ExtendedBy.AccessibilityId("TextBox"));

driver.ExecuteScript("windows: click", new Dictionary<string, object> { ["x"] = element.Location.X + element.Size.Width / 2, ["y"] = element.Location.Y + element.Size.Height / 2});

string activeElementText = driver.SwitchTo().ActiveElement().Text;
Assert.That(activeElementText, Is.EqualTo("Test TextBox"));
}

[Test]
public void ExecuteScript_WindowsHoverXY_IsSupported()
{
var driverOptions = FlaUIDriverOptions.TestApp();
using var driver = new RemoteWebDriver(WebDriverFixture.WebDriverUrl, driverOptions);
var element = driver.FindElement(ExtendedBy.AccessibilityId("LabelWithHover"));

driver.ExecuteScript("windows: hover", new Dictionary<string, object> {
["startX"] = element.Location.X + element.Size.Width / 2,
["startY"] = element.Location.Y + element.Size.Height / 2,
["endX"] = element.Location.X + element.Size.Width / 2,
["endY"] = element.Location.Y + element.Size.Height / 2
});

Assert.That(element.Text, Is.EqualTo("Hovered!"));
}

[Test]
public void ExecuteScript_WindowsKeys_IsSupported()
{
var driverOptions = FlaUIDriverOptions.TestApp();
using var driver = new RemoteWebDriver(WebDriverFixture.WebDriverUrl, driverOptions);
var element = driver.FindElement(ExtendedBy.AccessibilityId("TextBox"));
element.Click();

driver.ExecuteScript("windows: keys", new Dictionary<string, object> { ["actions"] = new[] {
new Dictionary<string, object> { ["virtualKeyCode"] = 0x11, ["down"]=true }, // CTRL
new Dictionary<string, object> { ["virtualKeyCode"] = 0x08, ["down"]=true }, // BACKSPACE
new Dictionary<string, object> { ["virtualKeyCode"] = 0x08, ["down"]=false },
new Dictionary<string, object> { ["virtualKeyCode"] = 0x11, ["down"]=false }
} });

string activeElementText = driver.SwitchTo().ActiveElement().Text;
Assert.That(activeElementText, Is.EqualTo("Test "));
}
}
}
2 changes: 1 addition & 1 deletion src/FlaUI.WebDriver.UITests/WindowTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public void GetWindowRect_Default_IsSupported()
Assert.That(position.X, Is.GreaterThanOrEqualTo(0));
Assert.That(position.Y, Is.GreaterThanOrEqualTo(0));
Assert.That(size.Width, Is.InRange(629 * scaling, 630 * scaling));
Assert.That(size.Height, Is.InRange(515 * scaling, 516 * scaling));
Assert.That(size.Height, Is.InRange(550 * scaling, 551 * scaling));
}

[Test]
Expand Down
96 changes: 93 additions & 3 deletions src/FlaUI.WebDriver/Controllers/ExecuteController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
using FlaUI.WebDriver.Models;
using FlaUI.Core.Input;
using FlaUI.Core.WindowsAPI;
using FlaUI.WebDriver.Models;
using FlaUI.WebDriver.Services;
using Microsoft.AspNetCore.Authentication.OAuth.Claims;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
using System.Drawing;
using System.Text.Json;

namespace FlaUI.WebDriver.Controllers
{
Expand All @@ -10,10 +16,12 @@ public class ExecuteController : ControllerBase
{
private readonly ILogger<ExecuteController> _logger;
private readonly ISessionRepository _sessionRepository;
private readonly IWindowsExtensionService _windowsExtensionService;

public ExecuteController(ISessionRepository sessionRepository, ILogger<ExecuteController> logger)
public ExecuteController(ISessionRepository sessionRepository, IWindowsExtensionService windowsExtensionService, ILogger<ExecuteController> logger)
{
_sessionRepository = sessionRepository;
_windowsExtensionService = windowsExtensionService;
_logger = logger;
}

Expand All @@ -25,21 +33,37 @@ public async Task<ActionResult> ExecuteScript([FromRoute] string sessionId, [Fro
{
case "powerShell":
return await ExecutePowerShellScript(session, executeScriptRequest);
case "windows: keys":
return await ExecuteWindowsKeysScript(session, executeScriptRequest);
case "windows: click":
return await ExecuteWindowsClickScript(session, executeScriptRequest);
case "windows: hover":
return await ExecuteWindowsHoverScript(session, executeScriptRequest);
default:
throw WebDriverResponseException.UnsupportedOperation("Only 'powerShell' scripts are supported");
}
}

private async Task<ActionResult> ExecutePowerShellScript(Session session, ExecuteScriptRequest executeScriptRequest)
{
if (executeScriptRequest.Args.Count != 1)
{
throw WebDriverResponseException.InvalidArgument($"Expected an array of exactly 1 arguments for the PowerShell script, but got {executeScriptRequest.Args.Count} arguments");
}
var powerShellArgs = executeScriptRequest.Args[0];
if (!powerShellArgs.TryGetValue("command", out var powerShellCommand))
if (!powerShellArgs.TryGetProperty("command", out var powerShellCommandJson))
{
throw WebDriverResponseException.InvalidArgument("Expected a \"command\" property of the first argument for the PowerShell script");
}
if (powerShellCommandJson.ValueKind != JsonValueKind.String)
{
throw WebDriverResponseException.InvalidArgument($"Powershell \"command\" property must be a string");
}
string? powerShellCommand = powerShellCommandJson.GetString();
if (string.IsNullOrEmpty(powerShellCommand))
{
throw WebDriverResponseException.InvalidArgument($"Powershell \"command\" property must be non-empty");
}

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

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

private async Task<ActionResult> ExecuteWindowsClickScript(Session session, ExecuteScriptRequest executeScriptRequest)
{
if (executeScriptRequest.Args.Count != 1)
{
throw WebDriverResponseException.InvalidArgument($"Expected an array of exactly 1 arguments for the windows: click script, but got {executeScriptRequest.Args.Count} arguments");
}
var action = JsonSerializer.Deserialize<WindowsClickScript>(executeScriptRequest.Args[0], new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
if (action == null)
{
throw WebDriverResponseException.InvalidArgument("Action cannot be null");
}
await _windowsExtensionService.ExecuteClickScript(session, action);
return WebDriverResult.Success();
}

private async Task<ActionResult> ExecuteWindowsHoverScript(Session session, ExecuteScriptRequest executeScriptRequest)
{
if (executeScriptRequest.Args.Count != 1)
{
throw WebDriverResponseException.InvalidArgument($"Expected an array of exactly 1 arguments for the windows: hover script, but got {executeScriptRequest.Args.Count} arguments");
}
var action = JsonSerializer.Deserialize<WindowsHoverScript>(executeScriptRequest.Args[0], new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
if (action == null)
{
throw WebDriverResponseException.InvalidArgument("Action cannot be null");
}
await _windowsExtensionService.ExecuteHoverScript(session, action);
return WebDriverResult.Success();
}

private async Task<ActionResult> ExecuteWindowsKeysScript(Session session, ExecuteScriptRequest executeScriptRequest)
{
if (executeScriptRequest.Args.Count != 1)
{
throw WebDriverResponseException.InvalidArgument($"Expected an array of exactly 1 arguments for the windows: keys script, but got {executeScriptRequest.Args.Count} arguments");
}
var windowsKeysArgs = executeScriptRequest.Args[0];
if (!windowsKeysArgs.TryGetProperty("actions", out var actionsJson))
{
throw WebDriverResponseException.InvalidArgument("Expected a \"actions\" property of the first argument for the windows: keys script");
}
session.CurrentWindow.FocusNative();
if (actionsJson.ValueKind == JsonValueKind.Array)
{
var actions = JsonSerializer.Deserialize<List<WindowsKeyScript>>(actionsJson, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
if (actions == null)
{
throw WebDriverResponseException.InvalidArgument("Argument \"actions\" cannot be null");
}
foreach (var action in actions)
{
await _windowsExtensionService.ExecuteKeyScript(session, action);
}
}
else
{
var action = JsonSerializer.Deserialize<WindowsKeyScript>(actionsJson, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
if (action == null)
{
throw WebDriverResponseException.InvalidArgument("Action cannot be null");
}
await _windowsExtensionService.ExecuteKeyScript(session, action);
}
return WebDriverResult.Success();
}

private Session GetSession(string sessionId)
{
var session = _sessionRepository.FindById(sessionId);
Expand Down
6 changes: 4 additions & 2 deletions src/FlaUI.WebDriver/Models/ExecuteScriptRequest.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
namespace FlaUI.WebDriver.Models
using System.Text.Json;

namespace FlaUI.WebDriver.Models
{
public class ExecuteScriptRequest
{
public string Script { get; set; } = null!;
public List<Dictionary<string, string>> Args { get; set; } = new List<Dictionary<string, string>>();
public List<JsonElement> Args { get; set; } = new List<JsonElement>();
}
}
10 changes: 10 additions & 0 deletions src/FlaUI.WebDriver/Models/WindowsClickScript.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace FlaUI.WebDriver.Models
{
public class WindowsClickScript
{
public string? ElementId { get; set; }
public int? X { get; set; }
public int? Y { get; set; }
public string? Button { get; set; }
}
}
13 changes: 13 additions & 0 deletions src/FlaUI.WebDriver/Models/WindowsHoverScript.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace FlaUI.WebDriver.Models
{
public class WindowsHoverScript
{
public string? StartElementId { get; set; }
public int? StartX { get; set; }
public int? StartY { get; set; }
public int? EndX { get; set; }
public int? EndY { get; set; }
public string? EndElementId { get; set; }
public int? DurationMs { get; set; }
}
}
10 changes: 10 additions & 0 deletions src/FlaUI.WebDriver/Models/WindowsKeyScript.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace FlaUI.WebDriver.Models
{
public class WindowsKeyScript
{
public int? Pause { get; set; }
public string? Text { get; set; }
public ushort? VirtualKeyCode { get; set; }
public bool? Down { get; set; }
}
}
1 change: 1 addition & 0 deletions src/FlaUI.WebDriver/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

builder.Services.AddSingleton<ISessionRepository, SessionRepository>();
builder.Services.AddScoped<IActionsDispatcher, ActionsDispatcher>();
builder.Services.AddScoped<IWindowsExtensionService, WindowsExtensionService>();

builder.Services.Configure<RouteOptions>(options => options.LowercaseUrls = true);
builder.Services.AddControllers(options =>
Expand Down
11 changes: 11 additions & 0 deletions src/FlaUI.WebDriver/Services/IWindowsExtensionService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using FlaUI.WebDriver.Models;

namespace FlaUI.WebDriver.Services
{
public interface IWindowsExtensionService
{
Task ExecuteClickScript(Session session, WindowsClickScript action);
Task ExecuteHoverScript(Session session, WindowsHoverScript action);
Task ExecuteKeyScript(Session session, WindowsKeyScript action);
}
}
Loading

0 comments on commit 76443af

Please sign in to comment.