Skip to content

Commit

Permalink
Merge pull request #115 from nowsprinting/feature/pluggable_operators
Browse files Browse the repository at this point in the history
Pluggable operators
  • Loading branch information
nowsprinting authored Apr 9, 2024
2 parents 2b8260c + 6531552 commit 95d8ad8
Show file tree
Hide file tree
Showing 42 changed files with 1,445 additions and 677 deletions.
42 changes: 28 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,20 @@ Configurations in `MonkeyConfig`:
- **Lifetime**: Running time
- **DelayMillis**: Delay time between operations
- **SecondsToErrorForNoInteractiveComponent**: Seconds to determine that an error has occurred when an object that can be interacted with does not exist
- **TouchAndHoldDelayMillis**: Delay time for touch-and-hold
- **Random**: Random generator
- **Logger**: Logger
- **Gizmos**: Show Gizmos on `GameView` during running monkey test if true
- **Screenshots**: Take screenshots during running the monkey test if set a `ScreenshotOptions` instance.
- **Directory**: Directory to save screenshots. If omitted, the directory specified by command line argument "-testHelperScreenshotDirectory" is used. If the command line argument is also omitted, `Application.persistentDataPath` + "/TestHelper/Screenshots/" is used.
- **FilenameStrategy**: Strategy for file paths of screenshot images. Default is test case name and four digit sequential number.
- **SuperSize**: The factor to increase resolution with. Default is 1.
- **StereoCaptureMode**: The eye texture to capture when stereo rendering is enabled. Default is `LeftEye`.

Configurations in `ScreenshotOptions`:
More customize for your project:

- **Directory**: Directory to save screenshots. If omitted, the directory specified by command line argument "-testHelperScreenshotDirectory" is used. If the command line argument is also omitted, `Application.persistentDataPath` + "/TestHelper/Screenshots/" is used.
- **FilenameStrategy**: Strategy for file paths of screenshot images. Default is test case name and four digit sequential number.
- **SuperSize**: The factor to increase resolution with. Default is 1.
- **StereoCaptureMode**: The eye texture to capture when stereo rendering is enabled. Default is `LeftEye`.
- **IsReachable**: Function returns the `GameObject` is reachable from user or not. Default implementation is using Raycaster and includes ScreenPointStrategy (GetScreenPoint function).
- **IsInteractable**: Function returns the `Component` is interactable or not. The default implementation is support for standard Unity UI (uGUI) components.
- **Operators**: Operators that the monkey invokes. Default is ClickOperator, ClickAndHoldOperator, and TextInputOperator. There is support for standard Unity UI (uGUI) components.


### Annotations for Monkey's behavior
Expand Down Expand Up @@ -151,9 +153,13 @@ public class MyIntegrationTest
}
```

#### InteractiveComponent.CreateInteractableComponent
#### InteractiveComponent and Operators

Returns new InteractableComponent instance from GameObject. If GameObject is not interactable so, return null.
##### InteractiveComponent
Returns new `InteractableComponent` instance from GameObject. If GameObject is not interactable so, return null.

##### Operators
Operators implements `IOperator` interface. It has `OperateAsync` method that operates on the component.

Usage:

Expand All @@ -168,10 +174,11 @@ public class MyIntegrationTest
public void MyTestMethod()
{
var finder = new GameObjectFinder();
var button = await finder.FindByNameAsync("Button", interactable: true);
var button = await finder.FindByNameAsync("StartButton", interactable: true);

var interactableComponent = InteractiveComponent.CreateInteractableComponent(button);
interactableComponent.Click();
var clickOperator = interactableComponent.GetOperatorsByType(OperatorType.Click).First();
clickOperator.OperateAsync(interactableComponent.component);
}
}
```
Expand All @@ -183,8 +190,10 @@ Returns interactable uGUI components.
Usage:

```csharp
using System.Linq;
using NUnit.Framework;
using TestHelper.Monkey;
using TestHelper.Monkey.Operators;

[TestFixture]
public class MyIntegrationTest
Expand All @@ -193,6 +202,10 @@ public class MyIntegrationTest
public void MyTestMethod()
{
var components = InteractiveComponentCollector.FindInteractableComponents();

var firstComponent = components.First();
var clickAndHoldOperator = firstComponent.GetOperatorsByType(OperatorType.ClickAndHold).First();
await clickAndHoldOperator.OperateAsync(firstComponent.component);
}
}
```
Expand All @@ -208,18 +221,19 @@ Usage:
using System.Linq;
using NUnit.Framework;
using TestHelper.Monkey;
using TestHelper.Monkey.Operators;

[TestFixture]
public class MyIntegrationTest
{
[Test]
public void MyTestMethod()
{
var component = InteractiveComponentCollector.FindReachableInteractableComponents()
.First();
var components = InteractiveComponentCollector.FindReachableInteractableComponents();

Assume.That(component.CanClick(), Is.True);
component.Click();
var firstComponent = components.First();
var textInputOperator = firstComponent.GetOperatorsByType(OperatorType.TextInput).First();
textInputOperator.OperateAsync(firstComponent.component); // input random text
}
}
```
Expand Down
9 changes: 3 additions & 6 deletions Runtime/DefaultStrategies/DefaultReachableStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,22 @@ namespace TestHelper.Monkey.DefaultStrategies
/// </summary>
public static class DefaultReachableStrategy
{
private static Func<GameObject, Vector2> GetScreenPoint => DefaultScreenPointStrategy.GetScreenPoint;

/// <summary>
/// Make sure the <c>GameObject</c> is reachable from user.
/// Hit test using raycaster
/// </summary>
/// <param name="gameObject"></param>
/// <param name="getScreenPoint">The function returns the screen position where raycast for the found <c>GameObject</c>.
/// Default is <c>DefaultScreenPointStrategy.GetScreenPoint</c>.</param>
/// <param name="eventData">Specify if avoid GC memory allocation</param>
/// <param name="results">Specify if avoid GC memory allocation</param>
/// <returns>True if this GameObject is reachable from user</returns>
public static bool IsReachable(GameObject gameObject,
Func<GameObject, Vector2> getScreenPoint = null,
PointerEventData eventData = null,
List<RaycastResult> results = null)
{
getScreenPoint = getScreenPoint ?? DefaultScreenPointStrategy.GetScreenPoint;

eventData = eventData ?? new PointerEventData(EventSystem.current);
eventData.position = getScreenPoint.Invoke(gameObject);
eventData.position = GetScreenPoint.Invoke(gameObject);

results = results ?? new List<RaycastResult>();
results.Clear();
Expand Down
15 changes: 3 additions & 12 deletions Runtime/GameObjectFinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using Cysharp.Threading.Tasks;
using TestHelper.Monkey.DefaultStrategies;
using TestHelper.Monkey.Extensions;
using TestHelper.Monkey.ScreenPointStrategies;
using UnityEngine;
using UnityEngine.EventSystems;

Expand All @@ -20,11 +19,7 @@ namespace TestHelper.Monkey
public class GameObjectFinder
{
private readonly double _timeoutSeconds;
private readonly Func<GameObject, Vector2> _getScreenPoint;

private readonly Func<GameObject, Func<GameObject, Vector2>, PointerEventData, List<RaycastResult>, bool>
_isReachable;

private readonly Func<GameObject, PointerEventData, List<RaycastResult>, bool> _isReachable;
private readonly Func<Component, bool> _isComponentInteractable;
private readonly PointerEventData _eventData = new PointerEventData(EventSystem.current);
private readonly List<RaycastResult> _results = new List<RaycastResult>();
Expand All @@ -33,19 +28,15 @@ private readonly Func<GameObject, Func<GameObject, Vector2>, PointerEventData, L
/// Constructor.
/// </summary>
/// <param name="timeoutSeconds">Seconds to wait until <c>GameObject</c> appear.</param>
/// <param name="getScreenPoint">The function returns the screen position where raycast for the found <c>GameObject</c>.
/// Default is <c>DefaultScreenPointStrategy.GetScreenPoint</c>.</param>
/// <param name="isReachable">The function returns the <c>GameObject</c> is reachable from user or not.
/// Default is <c>DefaultReachableStrategy.IsReachable</c>.</param>
/// <param name="isComponentInteractable">The function returns the <c>Component</c> is interactable or not.
/// Default is <c>DefaultComponentInteractableStrategy.IsInteractable</c>.</param>
public GameObjectFinder(double timeoutSeconds = 1.0d,
Func<GameObject, Vector2> getScreenPoint = null,
Func<GameObject, Func<GameObject, Vector2>, PointerEventData, List<RaycastResult>, bool> isReachable = null,
Func<GameObject, PointerEventData, List<RaycastResult>, bool> isReachable = null,
Func<Component, bool> isComponentInteractable = null)
{
_timeoutSeconds = timeoutSeconds;
_getScreenPoint = getScreenPoint ?? DefaultScreenPointStrategy.GetScreenPoint;
_isReachable = isReachable ?? DefaultReachableStrategy.IsReachable;
_isComponentInteractable = isComponentInteractable ?? DefaultComponentInteractableStrategy.IsInteractable;
}
Expand All @@ -70,7 +61,7 @@ private enum Reason

if (reachable)
{
if (!_isReachable.Invoke(foundObject, _getScreenPoint, _eventData, _results))
if (!_isReachable.Invoke(foundObject, _eventData, _results))
{
return (null, Reason.NotReachable);
}
Expand Down
2 changes: 1 addition & 1 deletion Runtime/Hints/InteractiveComponentHint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ private void Refresh()
{
Clear();

var interactiveComponentCollector = new InteractiveComponentCollector(getScreenPoint: GetScreenPoint);
var interactiveComponentCollector = new InteractiveComponentCollector();
foreach (var component in interactiveComponentCollector.FindInteractableComponents())
{
var dst = component.IsReachable()
Expand Down
Loading

0 comments on commit 95d8ad8

Please sign in to comment.