Skip to content

Commit

Permalink
Add getClipboard and setClipboard extensions
Browse files Browse the repository at this point in the history
Add extensions that are also supported by appium-windows-driver.
  • Loading branch information
aristotelos committed Nov 22, 2024
1 parent d1650bd commit 7026514
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ const result = driver.executeScript("powerShell", [{ command: `1+1` }]);

## Windows extensions

To enable easy switching from appium-windows-driver, there is a rudimentary implementation of `windows: click`, `windows: hover`, `windows: scroll` and `windows: keys`.
To enable easy switching from appium-windows-driver, there is a rudimentary implementation of `windows: click`, `windows: hover`, `windows: scroll`, `windows: keys`, `windows: getClipboard` and `windows: setClipboard`.

## Supported WebDriver Commands

Expand Down
32 changes: 32 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 Down Expand Up @@ -32,6 +34,36 @@ public void ExecuteScript_WindowsClickXY_IsSupported()
Assert.That(activeElementText, Is.EqualTo("Test TextBox"));
}

[Test]
public void ExecuteScript_WindowsGetClipboard_IsSupported()
{
var driverOptions = FlaUIDriverOptions.TestApp();
using var driver = new RemoteWebDriver(WebDriverFixture.WebDriverUrl, driverOptions);
var element = driver.FindElement(ExtendedBy.AccessibilityId("TextBox"));
element.Click();
new Actions(driver).KeyDown(Keys.Control).SendKeys("a").KeyUp(Keys.Control).Perform();
new Actions(driver).KeyDown(Keys.Control).SendKeys("c").KeyUp(Keys.Control).Perform();

var result = driver.ExecuteScript("windows: getClipboard", new Dictionary<string, object> {});

Assert.That(result, Is.EqualTo("Test TextBox"));
}

[Test]
public void ExecuteScript_WindowsSetClipboard_IsSupported()
{
var driverOptions = FlaUIDriverOptions.TestApp();
using var driver = new RemoteWebDriver(WebDriverFixture.WebDriverUrl, driverOptions);

var result = driver.ExecuteScript("windows: setClipboard", new Dictionary<string, object> {
["b64Content"] = "Pasted!"});

var element = driver.FindElement(ExtendedBy.AccessibilityId("TextBox"));
element.Click();
new Actions(driver).KeyDown(Keys.Control).SendKeys("v").KeyUp(Keys.Control).Perform();
Assert.That(element.Text, Is.EqualTo("Test TextBoxPasted!"));
}

[Test]
public void ExecuteScript_WindowsHoverXY_IsSupported()
{
Expand Down
34 changes: 34 additions & 0 deletions src/FlaUI.WebDriver/Controllers/ExecuteController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ public async Task<ActionResult> ExecuteScript([FromRoute] string sessionId, [Fro
return await ExecuteWindowsHoverScript(session, executeScriptRequest);
case "windows: scroll":
return await ExecuteWindowsScrollScript(session, executeScriptRequest);
case "windows: setClipboard":
return await ExecuteWindowsSetClipboardScript(session, executeScriptRequest);
case "windows: getClipboard":
return await ExecuteWindowsGetClipboardScript(session, executeScriptRequest);
default:
throw WebDriverResponseException.UnsupportedOperation("Only 'powerShell', 'windows: keys', 'windows: click', 'windows: hover' scripts are supported");
}
Expand Down Expand Up @@ -90,6 +94,36 @@ private async Task<ActionResult> ExecutePowerShellScript(Session session, Execut
return WebDriverResult.Success(result);
}

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

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

private async Task<ActionResult> ExecuteWindowsClickScript(Session session, ExecuteScriptRequest executeScriptRequest)
{
if (executeScriptRequest.Args.Count != 1)
Expand Down
7 changes: 7 additions & 0 deletions src/FlaUI.WebDriver/Models/WindowsGetClipboardScript.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace FlaUI.WebDriver.Models
{
public class WindowsGetClipboardScript
{
public string? ContentType { get; set; }
}
}
8 changes: 8 additions & 0 deletions src/FlaUI.WebDriver/Models/WindowsSetClipboardScript.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace FlaUI.WebDriver.Models
{
public class WindowsSetClipboardScript
{
public string B64Content { get; set; } = "";
public string? ContentType { get; set; }
}
}
2 changes: 2 additions & 0 deletions src/FlaUI.WebDriver/Services/IWindowsExtensionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ public interface IWindowsExtensionService
Task ExecuteScrollScript(Session session, WindowsScrollScript action);
Task ExecuteHoverScript(Session session, WindowsHoverScript action);
Task ExecuteKeyScript(Session session, WindowsKeyScript action);
Task<string> ExecuteGetClipboardScript(Session session, WindowsGetClipboardScript action);
Task ExecuteSetClipboardScript(Session session, WindowsSetClipboardScript action);
}
}
64 changes: 64 additions & 0 deletions src/FlaUI.WebDriver/Services/WindowsExtensionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,70 @@ public WindowsExtensionService(ILogger<WindowsExtensionService> logger)
_logger = logger;
}

public Task<string> ExecuteGetClipboardScript(Session session, WindowsGetClipboardScript action)
{
switch(action.ContentType)
{
default:
case "plaintext":
return Task.FromResult(ExecuteOnClipboardThread(
() => System.Windows.Forms.Clipboard.GetText(System.Windows.Forms.TextDataFormat.UnicodeText)
));
case "image":
return Task.FromResult(ExecuteOnClipboardThread(() =>
{
using var image = System.Windows.Forms.Clipboard.GetImage();
if (image == null)
{
return "";
}
using var stream = new MemoryStream();
image.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
return Convert.ToBase64String(stream.ToArray());
}));
}
}

public Task ExecuteSetClipboardScript(Session session, WindowsSetClipboardScript action)
{
switch (action.ContentType)
{
default:
case "plaintext":
ExecuteOnClipboardThread(() => System.Windows.Forms.Clipboard.SetText(action.B64Content));
break;
case "image":
ExecuteOnClipboardThread(() =>
{
using var stream = new MemoryStream(Convert.FromBase64String(action.B64Content));
using var image = Image.FromStream(stream);
System.Windows.Forms.Clipboard.SetImage(image);
});
break;
}
return Task.CompletedTask;
}

private void ExecuteOnClipboardThread(System.Action action)
{
// See https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.clipboard?view=windowsdesktop-8.0#remarks
var thread = new Thread(() => action());
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
}

private string ExecuteOnClipboardThread(Func<string> method)
{
// See https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.clipboard?view=windowsdesktop-8.0#remarks
string result = "";
var thread = new Thread(() => { result = method(); });
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
return result;
}

public async Task ExecuteClickScript(Session session, WindowsClickScript action)
{
if (action.DurationMs.HasValue)
Expand Down

0 comments on commit 7026514

Please sign in to comment.