Skip to content

Commit

Permalink
Merge pull request #1 from asimmon/readme-demo
Browse files Browse the repository at this point in the history
Added documentation and simpler API methods
  • Loading branch information
asimmon authored Jul 21, 2021
2 parents 767d942 + 2c141e7 commit c4b1703
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 28 deletions.
174 changes: 173 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,178 @@
# Askaiser.Marionette

Askaiser.Marionette is a **test automation framework based on image and text recognition**. It includes a C# source generator that allows you to quickly use image files from your solution or elsewhere.
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 properties generated by images from your project or elsewhere. The framework is built on top of **OpenCV** and **Tesseract OCR**.

[![NuGet version (Askaiser.Marionette)](https://img.shields.io/nuget/v/Askaiser.Marionette.svg?logo=nuget)](https://www.nuget.org/packages/Askaiser.Marionette/)
[![build](https://img.shields.io/github/workflow/status/asimmon/askaiser-marionette/CI%20Build?logo=github)](https://github.com/asimmon/askaiser-marionette/actions/workflows/ci.yml)

## Askaiser.Marionette in action

* `00:00` : 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 project,
* `00:30` : Use `ImageLibrary` to **automatically** generate properties from your screenshots,
* `01:06` : Use `MarionetteDriver` to interact with the generated properties (or even text recognized by the OCR)!

https://user-images.githubusercontent.com/14242083/126416123-aebd0fce-825f-4ece-90e9-762503dc4cab.mp4

## Getting started

```
dotnet add package Askaiser.Marionette
```

It supports **.NET Standard 2.0**, **.NET Standard 2.1** an **.NET 5**.

```csharp
using (var driver = MarionetteDriver.Create(/* optional DriverOptions */))
{
// insert magic here
}
```

The [sample project](https://github.com/asimmon/askaiser-marionette/tree/readme-demo/samples/Askaiser.Marionette.ConsoleApp) will show you the basics of using this library.

## Show me the APIs

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.

### Configuration and utilities

```csharp
static Create()
static Create(DriverOptions options)

GetScreenshot()
GetCurrentMonitor()
GetMonitors()
SetCurrentMonitor(int monitorIndex)
SetCurrentMonitor(MonitorDescription monitor)
SetMouseSpeed(MouseSpeed speed)
Sleep(int millisecondsDelay)
Sleep(TimeSpan delay)
```

### Basic methods

```csharp
WaitFor(IElement element, TimeSpan waitFor, Rectangle searchRect)
WaitForAll(IEnumerable<IElement> elements, TimeSpan waitFor, Rectangle searchRect)
WaitForAny(IEnumerable<IElement> elements, TimeSpan waitFor, Rectangle searchRect)
SingleClick(int x, int y)
DoubleClick(int x, int y)
TripleClick(int x, int y)
RightClick(int x, int y)
MoveTo(int x, int y)
DragFrom(int x, int y)
DropTo(int x, int y)
TypeText(string text, TimeSpan sleepAfter)
KeyPress(VirtualKeyCode[] keyCodes)
KeyDown(VirtualKeyCode[] keyCodes)
KeyUp(VirtualKeyCode[] keyCodes)
ScrollDown(int scrollTicks)
ScrollUp(int scrollTicks)
ScrollDownUntilVisible(IElement element, TimeSpan totalDuration, int scrollTicks, Rectangle searchRect)
ScrollUpUntilVisible(IElement element, TimeSpan totalDuration, int scrollTicks, Rectangle searchRect)
```

### Mouse interaction with an element

```csharp
MoveTo(IElement element, TimeSpan waitFor, Rectangle searchRect)
SingleClick(IElement element, TimeSpan waitFor, Rectangle searchRect)
DoubleClick(IElement element, TimeSpan waitFor, Rectangle searchRect)
TripleClick(IElement element, TimeSpan waitFor, Rectangle searchRect)
RightClick(IElement element, TimeSpan waitFor, Rectangle searchRect)
DragFrom(IElement element, TimeSpan waitFor, Rectangle searchRect)
DropTo(IElement element, TimeSpan waitFor, Rectangle searchRect)
```

### Check for element visibility

```csharp
IsVisible(IElement element, TimeSpan waitFor, Rectangle searchRect)
IsAnyVisible(IEnumerable<IElement> elements, TimeSpan waitFor, Rectangle searchRect)
AreAllVisible(IEnumerable<IElement> elements, TimeSpan waitFor, Rectangle searchRect)
```

### Mouse interaction with the first available element of a collection

```csharp
MoveToAny(IEnumerable<IElement> elements, TimeSpan waitFor, Rectangle searchRect)
SingleClickAny(IEnumerable<IElement> elements, TimeSpan waitFor, Rectangle searchRect)
DoubleClickAny(IEnumerable<IElement> elements, TimeSpan waitFor, Rectangle searchRect)
TripleClickAny(IEnumerable<IElement> elements, TimeSpan waitFor, Rectangle searchRect)
RightClickAny(IEnumerable<IElement> elements, TimeSpan waitFor, Rectangle searchRect)
DragFromAny(IEnumerable<IElement> elements, TimeSpan waitFor, Rectangle searchRect)
DropToAny(IEnumerable<IElement> elements, TimeSpan waitFor, Rectangle searchRect)
```

### Text-based actions

```csharp
WaitFor(string text, TimeSpan waitFor, Rectangle searchRect, TextOptions textOptions)
MoveTo(string text, TimeSpan waitFor, Rectangle searchRect, TextOptions textOptions)
SingleClick(string text, TimeSpan waitFor, Rectangle searchRect, TextOptions textOptions)
DoubleClick(string text, TimeSpan waitFor, Rectangle searchRect, TextOptions textOptions)
TripleClick(string text, TimeSpan waitFor, Rectangle searchRect, TextOptions textOptions)
RightClick(string text, TimeSpan waitFor, Rectangle searchRect, TextOptions textOptions)
DragFrom(string text, TimeSpan waitFor, Rectangle searchRect, TextOptions textOptions)
DropTo(string text, TimeSpan waitFor, Rectangle searchRect, TextOptions textOptions)
IsVisible(string text, TimeSpan waitFor, Rectangle searchRect, TextOptions textOptions)
```

### Mouse interaction with points

```csharp
MoveTo(Point coordinates)
SingleClick(Point coordinates)
DoubleClick(Point coordinates)
TripleClick(Point coordinates)
RightClick(Point coordinates)
DragFrom(Point coordinates)
DropTo(Point coordinates)
```

### Mouse interaction with `WaitFor` search result

```csharp
MoveTo(SearchResult searchResult)
SingleClick(SearchResult searchResult)
DoubleClick(SearchResult searchResult)
TripleClick(SearchResult searchResult)
RightClick(SearchResult searchResult)
DragFrom(SearchResult searchResult)
DropTo(SearchResult searchResult)
```

### Key press with single key code

```csharp
KeyPress(VirtualKeyCode keyCode, TimeSpan sleepAfter)
KeyDown(VirtualKeyCode keyCode, TimeSpan sleepAfter)
KeyUp(VirtualKeyCode keyCode, TimeSpan sleepAfter)
```

### `System.Drawing.Image`-based actions

```csharp
WaitFor(Image image, TimeSpan waitFor, Rectangle searchRect, decimal threshold, bool grayscale)
MoveTo(Image image, TimeSpan waitFor, Rectangle searchRect, decimal threshold, bool grayscale)
SingleClick(Image image, TimeSpan waitFor, Rectangle searchRect, decimal threshold, bool grayscale)
DoubleClick(Image image, TimeSpan waitFor, Rectangle searchRect, decimal threshold, bool grayscale)
TripleClick(Image image, TimeSpan waitFor, Rectangle searchRect, decimal threshold, bool grayscale)
RightClick(Image image, TimeSpan waitFor, Rectangle searchRect, decimal threshold, bool grayscale)
DragFrom(Image image, TimeSpan waitFor, Rectangle searchRect, decimal threshold, bool grayscale)
DropTo(Image image, TimeSpan waitFor, Rectangle searchRect, decimal threshold, bool grayscale)
IsVisible(Image image, TimeSpan waitFor, Rectangle searchRect, decimal threshold, bool grayscale)
```

### Finding elements locations without throwing not found exceptions or multiple element found exceptions

```csharp
FindLocations(IElement element, TimeSpan waitFor, Rectangle searchRect)
FindLocations(string text, TimeSpan waitFor, Rectangle searchRect, TextOptions textOptions)
FindLocations(Image image, TimeSpan waitFor, Rectangle searchRect, decimal threshold, bool grayscale)
```
Binary file added docs/marionette_demo_720p.mp4
Binary file not shown.
39 changes: 31 additions & 8 deletions src/Askaiser.Marionette/MarionetteDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,17 @@ private MarionetteDriver(DriverOptions options, IMonitorService monitorService,
this._mouseSpeed = options.MouseSpeed;
}

public static MarionetteDriver Create(DriverOptions options = default)
public static MarionetteDriver Create()
{
options ??= new DriverOptions();
return Create(new DriverOptions());
}

public static MarionetteDriver Create(DriverOptions options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}

var monitorService = new MonitorService(TimeSpan.FromMilliseconds(200));
var imageRecognizer = new ImageElementRecognizer();
Expand Down Expand Up @@ -260,22 +268,37 @@ public async Task TypeText(string text, TimeSpan sleepAfter = default)
}
}

public async Task KeyPress(params VirtualKeyCode[] keyCodes)
public async Task KeyPress(VirtualKeyCode[] keyCodes, TimeSpan sleepAfter = default)
{
EnsureNotNullOrEmpty(keyCodes);
await this._keyPressHandler.Execute(new KeyboardKeysCommand(keyCodes)).ConfigureAwait(false);

if (sleepAfter > TimeSpan.Zero)
{
await this.Sleep(sleepAfter).ConfigureAwait(false);
}
}

public async Task KeyDown(params VirtualKeyCode[] keyCodes)
public async Task KeyDown(VirtualKeyCode[] keyCodes, TimeSpan sleepAfter = default)
{
EnsureNotNullOrEmpty(keyCodes);
await this._keyDownHandler.Execute(new KeyboardKeysCommand(keyCodes)).ConfigureAwait(false);

if (sleepAfter > TimeSpan.Zero)
{
await this.Sleep(sleepAfter).ConfigureAwait(false);
}
}

public async Task KeyUp(params VirtualKeyCode[] keyCodes)
public async Task KeyUp(VirtualKeyCode[] keyCodes, TimeSpan sleepAfter = default)
{
EnsureNotNullOrEmpty(keyCodes);
await this._keyUpHandler.Execute(new KeyboardKeysCommand(keyCodes)).ConfigureAwait(false);

if (sleepAfter > TimeSpan.Zero)
{
await this.Sleep(sleepAfter).ConfigureAwait(false);
}
}

private static void EnsureNotNullOrEmpty(params VirtualKeyCode[] keyCodes)
Expand All @@ -297,7 +320,7 @@ public MarionetteDriver SetMouseSpeed(MouseSpeed speed)
return this;
}

public MarionetteDriver SetMonitor(int monitorIndex)
public MarionetteDriver SetCurrentMonitor(int monitorIndex)
{
if (monitorIndex < 0)
{
Expand All @@ -308,9 +331,9 @@ public MarionetteDriver SetMonitor(int monitorIndex)
return this;
}

public MarionetteDriver SetMonitor(MonitorDescription monitor)
public MarionetteDriver SetCurrentMonitor(MonitorDescription monitor)
{
return this.SetMonitor(monitor.Index);
return this.SetCurrentMonitor(monitor.Index);
}

[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "It looks more coherent to only use instance methods here.")]
Expand Down
26 changes: 7 additions & 19 deletions src/Askaiser.Marionette/MarionetteDriverExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,33 +208,21 @@ public static async Task<bool> AreAllVisible(this MarionetteDriver driver, IEnum

#endregion

#region Key press with sleep after
#region Key press with single key code

public static async Task KeyPress(this MarionetteDriver driver, VirtualKeyCode[] keyCodes, TimeSpan sleepAfter = default)
public static async Task KeyPress(this MarionetteDriver driver, VirtualKeyCode keyCode, TimeSpan sleepAfter = default)
{
await driver.KeyPress(keyCodes).ConfigureAwait(false);
if (sleepAfter > TimeSpan.Zero)
{
await driver.Sleep(sleepAfter).ConfigureAwait(false);
}
await driver.KeyPress(new[] { keyCode }, sleepAfter).ConfigureAwait(false);
}

public static async Task KeyDown(this MarionetteDriver driver, VirtualKeyCode[] keyCodes, TimeSpan sleepAfter = default)
public static async Task KeyDown(this MarionetteDriver driver, VirtualKeyCode keyCode, TimeSpan sleepAfter = default)
{
await driver.KeyDown(keyCodes).ConfigureAwait(false);
if (sleepAfter > TimeSpan.Zero)
{
await driver.Sleep(sleepAfter).ConfigureAwait(false);
}
await driver.KeyDown(new[] { keyCode }, sleepAfter).ConfigureAwait(false);
}

public static async Task KeyUp(this MarionetteDriver driver, VirtualKeyCode[] keyCodes, TimeSpan sleepAfter = default)
public static async Task KeyUp(this MarionetteDriver driver, VirtualKeyCode keyCode, TimeSpan sleepAfter = default)
{
await driver.KeyUp(keyCodes).ConfigureAwait(false);
if (sleepAfter > TimeSpan.Zero)
{
await driver.Sleep(sleepAfter).ConfigureAwait(false);
}
await driver.KeyUp(new[] { keyCode }, sleepAfter).ConfigureAwait(false);
}

#endregion
Expand Down

0 comments on commit c4b1703

Please sign in to comment.