![]() |
VOOZH | about |
dotnet add package Askaiser.Marionette --version 1.5.0
NuGet\Install-Package Askaiser.Marionette -Version 1.5.0
<PackageReference Include="Askaiser.Marionette" Version="1.5.0" />
<PackageVersion Include="Askaiser.Marionette" Version="1.5.0" />Directory.Packages.props
<PackageReference Include="Askaiser.Marionette" />Project file
paket add Askaiser.Marionette --version 1.5.0
#r "nuget: Askaiser.Marionette, 1.5.0"
#:package Askaiser.Marionette@1.5.0
#addin nuget:?package=Askaiser.Marionette&version=1.5.0Install as a Cake Addin
#tool nuget:?package=Askaiser.Marionette&version=1.5.0Install as a Cake Tool
Askaiser.Marionette is a test automation framework based on image and text recognition. It includes a C# source generator that allows you to quickly interact with C# properties generated from images in your project or elsewhere. The framework is built on top of OpenCV and Tesseract OCR and only works on Windows for now.
Table of contents
This short video (1m40s) shows how to interact with an existing application with no prior knowledge of its source code, in a matter of minutes.
00:00 : Quickly capture screenshots of the app you're testing,00:08 : Rename and organize your screenshots in a meaningful way,00:22 : Drop your screenshots in your C# project,00:30 : Use ImageLibraryAttribute to automatically generate C# properties from your screenshots,01:06 : Use MarionetteDriver to interact with the generated properties (or text recognized by the OCR)!dotnet add package Askaiser.Marionette
It supports .NET Standard 2.0, .NET Standard 2.1 an .NET 6, but only on Windows for now.
[ImageLibrary("path/to/your/images")]
public partial class MyLibrary
{
// In the "path/to/your/images" directory, we assume here that there are multiple small screenshots of UI elements
// with these relative paths: "pages/login/title.png", "pages/login/email.png", "pages/login/password.png" and "pages/login/submit.png".
// These file names actually control the behavior of the C# source generator.
// This behavior is explained in the next section.
}
using (var driver = MarionetteDriver.Create(/* optional DriverOptions */))
{
// in this exemple, we enter a username and password in a login page
await driver.WaitForAsync(MyLibrary.Instance.Pages.Login.Title, waitFor: TimeSpan.FromSeconds(5));
await driver.SingleClickAsync(MyLibrary.Instance.Pages.Login.Email);
await driver.TypeTextAsync("much@automated.foo", sleepAfter: TimeSpan.FromSeconds(0.5));
await driver.SingleClickAsync(MyLibrary.Instance.Pages.Login.Password);
await driver.TypeTextAsync("V3ry5ecre7!", sleepAfter: TimeSpan.FromSeconds(0.5));
await driver.SingleClickAsync(MyLibrary.Instance.Pages.Login.Submit);
// insert more magic here
}
The sample project shows the basics of using this library.
Given the following partial image library class:
[ImageLibrary("path/to/your/images/directory")]
public partial class MyLibrary
{
}
var library = new MyLibrary(); // or use MyLibrary.Instance generated singleton
The source generator will behave differently depending on the name of the screenshots/images added to the ImageLibraryAttribute's directory path.
Use lowercase characters. Dashes (
-) and underscores (_) are considered as special characters that will change the generated C# code as shown below.
okbutton.png will create an image property library.Okbutton,ok-button.png will create an image property library.OkButton (here the dash is a word separator),my-feature\ok-button.png will create an image property library.MyFeature.OkButton,my-feature--ok-button.png simulates a subdirectory and will create an image property library.MyFeature.OkButton.Adding multiple images suffixed with a zero-based incremental number such as ok-button_0.png, ok-button_1.png, ok-button_2.png, etc. will create a single array property library.OkButton that can be interacted with methods that accept an array of elements (MoveToAnyAsync, WaitForAnyAsync, SingleClickAnyAsync, etc.).
It is very convenient when you have screenshots of an element in multiple states, for instance a button in its normal, pressed, focus state and you want to click on the button no matter what its current state is.
Underscores _ can be also used to specify image recognition options:
foo_gs.png will create an image property library.Foo which will be grayscaled during image recognition,foo_0.8 will create an image property library.Foo that will use a search threshold of 0.8 instead of the default 0.95,You can mix these modifiers. Here we will create an single array property library.Header with these images:
header_gs_0.85_0.png (first item of the array, grayscaled with a 0.85 threshold),header_gs_0.9_1.png (second item of the array, grayscaled with a 0.9 threshold),header_2.png (third and last item of the array, keep original colors with and use default threshold).// Instead of relying on the source generator that works with image files, you can create an ImageElement manually
var bytes = await File.ReadAllBytesAsync("path/to/your/image.png");
var image = new ImageElement(name: "sidebar-close-button", content: bytes, threshold: 0.95m, grayscale: false);
ImageElement.Threshold is a floating number between 0 and 1. It defines the accuracy of the image search process. 0.95 is the default value.ImageElement.Grayscale defines whether or not the engine will apply grayscaling preprocessing. Image search is faster with grayscaling.Image recognition works best with PNG images.
// Although many methods accept a simple string as an element, you can manually create a TextElement
var text = new TextElement("Save changes", options: TextOptions.BlackAndWhite | TextOptions.Negative);
Text options are flags that define the preprocessing behavior of your monitor's screenshots before executing the OCR.
TextOptions.None : do not use preprocessing,TextOptions.Grayscale : Use grayscaling,TextOptions.BlackAndWhite : Use grayscaling and binarization (this is the default value),TextOptions.Negative : Use negative preprocessing, very helpful with white text on dark background.Many parameters are optional. Most methods that look for an element (image or text) expect to find only one occurrence of this element. ElementNotFoundException and MultipleElementFoundException can be thrown.
You can use DriverOptions.FailureScreenshotPath to automatically save screenshots when these exceptions occur.
static Create()
static Create(DriverOptions options)
GetScreenshotAsync()
SaveScreenshotAsync(Stream destinationStream)
SaveScreenshotAsync(string destinationPath)
GetCurrentMonitorAsync()
GetMonitorsAsync()
SetCurrentMonitor(int monitorIndex)
SetCurrentMonitor(MonitorDescription monitor)
SetMouseSpeed(MouseSpeed speed)
SleepAsync(int millisecondsDelay)
SleepAsync(TimeSpan delay)
WaitForAsync(IElement element, TimeSpan? waitFor, Rectangle? searchRect)
WaitForAllAsync(IEnumerable<IElement> elements, TimeSpan? waitFor, Rectangle? searchRect)
WaitForAnyAsync(IEnumerable<IElement> elements, TimeSpan? waitFor, Rectangle? searchRect)
SingleClickAsync(int x, int y)
DoubleClickAsync(int x, int y)
TripleClickAsync(int x, int y)
RightClickAsync(int x, int y)
MoveToAsync(int x, int y)
DragFromAsync(int x, int y)
DropToAsync(int x, int y)
TypeTextAsync(string text, TimeSpan? sleepAfter)
KeyPressAsync(VirtualKeyCode[] keyCodes, TimeSpan? sleepAfter)
KeyDownAsync(VirtualKeyCode[] keyCodes, TimeSpan? sleepAfter)
KeyUpAsync(VirtualKeyCode[] keyCodes, TimeSpan? sleepAfter)
ScrollDownAsync(int scrollTicks)
ScrollUpAsync(int scrollTicks)
MoveToAsync(IElement element, TimeSpan? waitFor, Rectangle? searchRect)
SingleClickAsync(IElement element, TimeSpan? waitFor, Rectangle? searchRect)
DoubleClickAsync(IElement element, TimeSpan? waitFor, Rectangle? searchRect)
TripleClickAsync(IElement element, TimeSpan? waitFor, Rectangle? searchRect)
RightClickAsync(IElement element, TimeSpan? waitFor, Rectangle? searchRect)
DragFromAsync(IElement element, TimeSpan? waitFor, Rectangle? searchRect)
DropToAsync(IElement element, TimeSpan? waitFor, Rectangle? searchRect)
ScrollDownUntilVisibleAsync(IElement element, TimeSpan totalDuration, int scrollTicks, Rectangle? searchRect)
ScrollUpUntilVisibleAsync(IElement element, TimeSpan totalDuration, int scrollTicks, Rectangle? searchRect)
IsVisibleAsync(IElement element, TimeSpan? waitFor, Rectangle? searchRect)
IsAnyVisibleAsync(IEnumerable<IElement> elements, TimeSpan? waitFor, Rectangle? searchRect)
AreAllVisibleAsync(IEnumerable<IElement> elements, TimeSpan? waitFor, Rectangle? searchRect)
MoveToAnyAsync(IEnumerable<IElement> elements, TimeSpan? waitFor, Rectangle? searchRect)
SingleClickAnyAsync(IEnumerable<IElement> elements, TimeSpan? waitFor, Rectangle? searchRect)
DoubleClickAnyAsync(IEnumerable<IElement> elements, TimeSpan? waitFor, Rectangle? searchRect)
TripleClickAnyAsync(IEnumerable<IElement> elements, TimeSpan? waitFor, Rectangle? searchRect)
RightClickAnyAsync(IEnumerable<IElement> elements, TimeSpan? waitFor, Rectangle? searchRect)
DragFromAnyAsync(IEnumerable<IElement> elements, TimeSpan? waitFor, Rectangle? searchRect)
DropToAnyAsync(IEnumerable<IElement> elements, TimeSpan? waitFor, Rectangle? searchRect)
WaitForAsync(string text, TimeSpan? waitFor, Rectangle? searchRect, TextOptions? textOptions)
MoveToAsync(string text, TimeSpan? waitFor, Rectangle? searchRect, TextOptions? textOptions)
SingleClickAsync(string text, TimeSpan? waitFor, Rectangle? searchRect, TextOptions? textOptions)
DoubleClickAsync(string text, TimeSpan? waitFor, Rectangle? searchRect, TextOptions? textOptions)
TripleClickAsync(string text, TimeSpan? waitFor, Rectangle? searchRect, TextOptions? textOptions)
RightClickAsync(string text, TimeSpan? waitFor, Rectangle? searchRect, TextOptions? textOptions)
DragFromAsync(string text, TimeSpan? waitFor, Rectangle? searchRect, TextOptions? textOptions)
DropToAsync(string text, TimeSpan? waitFor, Rectangle? searchRect, TextOptions? textOptions)
IsVisibleAsync(string text, TimeSpan? waitFor, Rectangle? searchRect, TextOptions? textOptions)
MoveToAsync(Point coordinates)
SingleClickAsync(Point coordinates)
DoubleClickAsync(Point coordinates)
TripleClickAsync(Point coordinates)
RightClickAsync(Point coordinates)
DragFromAsync(Point coordinates)
DropToAsync(Point coordinates)
WaitFor search resultMoveToAsync(SearchResult searchResult)
SingleClickAsync(SearchResult searchResult)
DoubleClickAsync(SearchResult searchResult)
TripleClickAsync(SearchResult searchResult)
RightClickAsync(SearchResult searchResult)
DragFromAsync(SearchResult searchResult)
DropToAsync(SearchResult searchResult)
KeyPressAsync(VirtualKeyCode keyCode, TimeSpan? sleepAfter)
KeyDownAsync(VirtualKeyCode keyCode, TimeSpan? sleepAfter)
KeyUpAsync(VirtualKeyCode keyCode, TimeSpan? sleepAfter)
System.Drawing.Image-based interactionWaitForAsync(Image image, TimeSpan? waitFor, Rectangle? searchRect, decimal? threshold, bool? grayscale)
MoveToAsync(Image image, TimeSpan? waitFor, Rectangle? searchRect, decimal? threshold, bool? grayscale)
SingleClickAsync(Image image, TimeSpan? waitFor, Rectangle? searchRect, decimal? threshold, bool? grayscale)
DoubleClickAsync(Image image, TimeSpan? waitFor, Rectangle? searchRect, decimal? threshold, bool? grayscale)
TripleClickAsync(Image image, TimeSpan? waitFor, Rectangle? searchRect, decimal? threshold, bool? grayscale)
RightClickAsync(Image image, TimeSpan? waitFor, Rectangle? searchRect, decimal? threshold, bool? grayscale)
DragFromAsync(Image image, TimeSpan? waitFor, Rectangle? searchRect, decimal? threshold, bool? grayscale)
DropToAsync(Image image, TimeSpan? waitFor, Rectangle? searchRect, decimal? threshold, bool? grayscale)
IsVisibleAsync(Image image, TimeSpan? waitFor, Rectangle? searchRect, decimal? threshold, bool? grayscale)
FindLocationsAsync(IElement element, TimeSpan? waitFor, Rectangle? searchRect)
FindLocationsAsync(string text, TimeSpan? waitFor, Rectangle? searchRect, TextOptions? textOptions)
FindLocationsAsync(Image image, TimeSpan? waitFor, Rectangle? searchRect, decimal? threshold, bool? grayscale)
👷♂️ Work in progress
This section will soon show how Askaiser.Marionette can be used in an Azure Pipelines continuous integration environment:
There seems to be an equivalent for GitHub actions. If you use another CI environment, please search its documentation for an equivalent behavior.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 net5.0 was computed. net5.0-windows net5.0-windows was computed. net6.0 net6.0 was computed. net6.0-android net6.0-android was computed. net6.0-ios net6.0-ios was computed. net6.0-maccatalyst net6.0-maccatalyst was computed. net6.0-macos net6.0-macos was computed. net6.0-tvos net6.0-tvos was computed. net6.0-windows net6.0-windows was computed. net6.0-windows7.0 net6.0-windows7.0 is compatible. net7.0 net7.0 was computed. net7.0-android net7.0-android was computed. net7.0-ios net7.0-ios was computed. net7.0-maccatalyst net7.0-maccatalyst was computed. net7.0-macos net7.0-macos was computed. net7.0-tvos net7.0-tvos was computed. net7.0-windows net7.0-windows was computed. net8.0 net8.0 was computed. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 was computed. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 was computed. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 netcoreapp2.0 was computed. netcoreapp2.1 netcoreapp2.1 was computed. netcoreapp2.2 netcoreapp2.2 was computed. netcoreapp3.0 netcoreapp3.0 was computed. netcoreapp3.1 netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 netstandard2.0 is compatible. netstandard2.1 netstandard2.1 is compatible. |
| .NET Framework | net45 net45 is compatible. net451 net451 was computed. net452 net452 was computed. net46 net46 was computed. net461 net461 was computed. net462 net462 was computed. net463 net463 was computed. net47 net47 was computed. net471 net471 was computed. net472 net472 was computed. net48 net48 was computed. net481 net481 was computed. |
| MonoAndroid | monoandroid monoandroid was computed. |
| MonoMac | monomac monomac was computed. |
| MonoTouch | monotouch monotouch was computed. |
| Tizen | tizen40 tizen40 was computed. tizen60 tizen60 was computed. |
| Xamarin.iOS | xamarinios xamarinios was computed. |
| Xamarin.Mac | xamarinmac xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos xamarinwatchos was computed. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.