diff --git a/README.md b/README.md
index d2ec1ac..730d41f 100644
--- a/README.md
+++ b/README.md
@@ -109,37 +109,56 @@ Placing this attribute on a test or test class will force the tests to run on th
This attribute configures the type of the pointer that is simulated when using helpers like `App.TapCoordinates()`. You can define the attribute more than once, the test will then be run for each configured type. When not defined, the test engine will use the common pointer type of the platform (touch for mobile OS like iOS and Android, mouse for browsers, skia and other desktop platforms).
## Placing tests in a separate assembly
-- In a separate assembly, add the following attributes code:
- ```csharp
- using System;
- using Windows.Devices.Input;
-
- namespace Uno.UI.RuntimeTests;
-
- public sealed class RequiresFullWindowAttribute : Attribute { }
-
- public sealed class RunsOnUIThreadAttribute : Attribute { }
-
- [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
- public sealed class InjectedPointerAttribute : Attribute
- {
- public PointerDeviceType Type { get; }
-
- public InjectedPointerAttribute(PointerDeviceType type)
- {
- Type = type;
- }
- }
- ```
-- Then define the following in your `csproj`:
- ```xml
-
- $(DefineConstants);UNO_RUNTIMETESTS_DISABLE_INJECTEDPOINTERATTRIBUTE
- $(DefineConstants);UNO_RUNTIMETESTS_DISABLE_REQUIRESFULLWINDOWATTRIBUTE
- $(DefineConstants);UNO_RUNTIMETESTS_DISABLE_RUNSONUITHREADATTRIBUTE
-
+- In your separated test assembly
+ - Add a reference to the "Uno.UI.RuntimeTests.Engine" package
+ - Then define the following in your `csproj`:
+ ```xml
+
+ $(DefineConstants);UNO_RUNTIMETESTS_DISABLE_UI
+
+ ```
+
+- In your test application
+ - Add a reference to the "Uno.UI.RuntimeTests.Engine" package
+ - Then define the following in your `csproj`:
+ ```xml
+
+ $(DefineConstants);UNO_RUNTIMETESTS_DISABLE_LIBRARY
+
+ ```
+
+### Alternative method
+Alternatively, if you have only limited needs, in your separated test assembly, add the following attributes code:
+ ```csharp
+ using System;
+ using Windows.Devices.Input;
+
+ namespace Uno.UI.RuntimeTests;
+
+ public sealed class RequiresFullWindowAttribute : Attribute { }
+
+ public sealed class RunsOnUIThreadAttribute : Attribute { }
+
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
+ public sealed class InjectedPointerAttribute : Attribute
+ {
+ public PointerDeviceType Type { get; }
+
+ public InjectedPointerAttribute(PointerDeviceType type)
+ {
+ Type = type;
+ }
+ }
```
-
+and define the following in your `csproj`:
+ ```xml
+
+ $(DefineConstants);UNO_RUNTIMETESTS_DISABLE_UI
+ $(DefineConstants);UNO_RUNTIMETESTS_DISABLE_INJECTEDPOINTERATTRIBUTE
+ $(DefineConstants);UNO_RUNTIMETESTS_DISABLE_REQUIRESFULLWINDOWATTRIBUTE
+ $(DefineConstants);UNO_RUNTIMETESTS_DISABLE_RUNSONUITHREADATTRIBUTE
+
+ ```
These attributes will ask for the runtime test engine to replace the ones defined by the `Uno.UI.RuntimeTests.Engine` package.
## Running the tests automatically during CI
diff --git a/src/TestApp/shared/PointersInjectionTests.cs b/src/TestApp/shared/PointersInjectionTests.cs
new file mode 100644
index 0000000..a26fca2
--- /dev/null
+++ b/src/TestApp/shared/PointersInjectionTests.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using Windows.Devices.Input;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+#if HAS_UNO_WINUI || WINDOWS_WINUI
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+#else
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+#endif
+
+namespace Uno.UI.RuntimeTests.Engine;
+
+[TestClass]
+[RunsOnUIThread]
+public class PointersInjectionTests
+{
+ [TestMethod]
+ [InjectedPointer(PointerDeviceType.Mouse)]
+ [InjectedPointer(PointerDeviceType.Touch)]
+#if !HAS_UNO_SKIA && !WINDOWS
+ [ExpectedException(typeof(NotSupportedException))]
+#endif
+ public async Task When_TapCoordinates()
+ {
+ var elt = new Button { Content = "Tap me" };
+ var clicked = false;
+ elt.Click += (snd, e) => clicked = true;
+
+ UnitTestsUIContentHelper.Content = elt;
+
+ await UnitTestsUIContentHelper.WaitForLoaded(elt);
+
+ InputInjectorHelper.Current.Tap(elt);
+
+ Assert.IsTrue(clicked);
+ }
+}
\ No newline at end of file
diff --git a/src/Uno.UI.RuntimeTests.Engine.Library/InjectedPointerAttribute.cs b/src/Uno.UI.RuntimeTests.Engine.Library/Library/InjectedPointerAttribute.cs
similarity index 92%
rename from src/Uno.UI.RuntimeTests.Engine.Library/InjectedPointerAttribute.cs
rename to src/Uno.UI.RuntimeTests.Engine.Library/Library/InjectedPointerAttribute.cs
index 4386ef6..ea67df5 100644
--- a/src/Uno.UI.RuntimeTests.Engine.Library/InjectedPointerAttribute.cs
+++ b/src/Uno.UI.RuntimeTests.Engine.Library/Library/InjectedPointerAttribute.cs
@@ -6,7 +6,7 @@ namespace Uno.UI.RuntimeTests;
using Windows.Devices.Input;
-#if !UNO_RUNTIMETESTS_DISABLE_INJECTEDPOINTERATTRIBUTE
+#if !UNO_RUNTIMETESTS_DISABLE_LIBRARY && !UNO_RUNTIMETESTS_DISABLE_INJECTEDPOINTERATTRIBUTE
///
/// Specify the type of pointer to use for that test.
/// WARNING: This has no effects on UI tests, cf. remarks.
@@ -30,4 +30,4 @@ public InjectedPointerAttribute(PointerDeviceType type)
Type = type;
}
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/Uno.UI.RuntimeTests.Engine.Library/Library/InputInjectorHelper.MouseHelper.cs b/src/Uno.UI.RuntimeTests.Engine.Library/Library/InputInjectorHelper.MouseHelper.cs
new file mode 100644
index 0000000..28f6eb5
--- /dev/null
+++ b/src/Uno.UI.RuntimeTests.Engine.Library/Library/InputInjectorHelper.MouseHelper.cs
@@ -0,0 +1,163 @@
+#if !UNO_RUNTIMETESTS_DISABLE_LIBRARY
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Windows.Foundation;
+using Windows.UI.Input;
+using Windows.UI.Input.Preview.Injection;
+
+namespace Uno.UI.RuntimeTests;
+
+public partial class InputInjectorHelper
+{
+#pragma warning disable CA1822 // Mark members as static
+ public class MouseHelper
+ {
+#if HAS_UNO
+ public MouseHelper(InputInjector input)
+ {
+ var getCurrent = typeof(InputInjector).GetProperty("Mouse", BindingFlags.Instance | BindingFlags.NonPublic)?.GetMethod
+ ?? throw new NotSupportedException("This version of uno is not supported for pointer injection.");
+ var currentType = getCurrent.Invoke(input, null)!.GetType();
+ var getCurrentPosition = currentType.GetProperty("Position", BindingFlags.Instance | BindingFlags.Public)?.GetMethod
+ ?? throw new NotSupportedException("This version of uno is not supported for pointer injection.");
+ var getCurrentProperties = currentType.GetProperty("Properties", BindingFlags.Instance | BindingFlags.Public)?.GetMethod
+ ?? throw new NotSupportedException("This version of uno is not supported for pointer injection.");
+
+ CurrentPosition = () => (Point)getCurrentPosition.Invoke(getCurrent.Invoke(input, null)!, null)!;
+ CurrentProperties = () => (PointerPointProperties)getCurrentProperties.Invoke(getCurrent.Invoke(input, null)!, null)!;
+ }
+
+ private Func CurrentProperties;
+
+ private Func CurrentPosition;
+#else
+ public MouseHelper(InputInjector input)
+ {
+ }
+
+ private Point CurrentPosition()
+ => Windows.UI.Core.CoreWindow.GetForCurrentThread().PointerPosition;
+#endif
+
+ ///
+ /// Create an injected pointer info which presses the left button
+ ///
+ public InjectedInputMouseInfo Press()
+ => new()
+ {
+ TimeOffsetInMilliseconds = 1,
+ MouseOptions = InjectedInputMouseOptions.LeftDown,
+ };
+
+ ///
+ /// Create an injected pointer info which release the left button
+ ///
+ public InjectedInputMouseInfo Release()
+ => new()
+ {
+ TimeOffsetInMilliseconds = 1,
+ MouseOptions = InjectedInputMouseOptions.LeftUp,
+ };
+
+ ///
+ /// Create an injected pointer info which releases any pressed button
+ ///
+ public InjectedInputMouseInfo? ReleaseAny()
+ {
+ var options = default(InjectedInputMouseOptions);
+
+#if HAS_UNO
+ var currentProps = CurrentProperties();
+ if (currentProps.IsLeftButtonPressed)
+ {
+ options |= InjectedInputMouseOptions.LeftUp;
+ }
+
+ if (currentProps.IsMiddleButtonPressed)
+ {
+ options |= InjectedInputMouseOptions.MiddleUp;
+ }
+
+ if (currentProps.IsRightButtonPressed)
+ {
+ options |= InjectedInputMouseOptions.RightUp;
+ }
+
+ if (currentProps.IsXButton1Pressed)
+ {
+ options |= InjectedInputMouseOptions.XUp;
+ }
+#else
+ options = InjectedInputMouseOptions.LeftUp
+ | InjectedInputMouseOptions.MiddleUp
+ | InjectedInputMouseOptions.RightUp
+ | InjectedInputMouseOptions.XUp;
+#endif
+
+ return options is default(InjectedInputMouseOptions)
+ ? null
+ : new()
+ {
+ TimeOffsetInMilliseconds = 1,
+ MouseOptions = options
+ };
+ }
+
+ ///
+ /// Create an injected pointer info which moves the mouse by the given offests
+ ///
+ public InjectedInputMouseInfo MoveBy(int deltaX, int deltaY)
+ => new()
+ {
+ DeltaX = deltaX,
+ DeltaY = deltaY,
+ TimeOffsetInMilliseconds = 1,
+ MouseOptions = InjectedInputMouseOptions.MoveNoCoalesce,
+ };
+
+ ///
+ /// Create some injected pointer infos which moves the mouse to the given coordinates
+ ///
+ /// The target x position
+ /// The traget y position
+ /// Number injected pointer infos to generate to simutale a smooth manipulation.
+ public IEnumerable MoveTo(double x, double y, int? steps = null)
+ {
+ var deltaX = x - CurrentPosition().X;
+ var deltaY = y - CurrentPosition().Y;
+
+ steps ??= (int)Math.Min(Math.Max(Math.Abs(deltaX), Math.Abs(deltaY)), 512);
+ if (steps is 0)
+ {
+ yield break;
+ }
+
+ var stepX = deltaX / steps.Value;
+ var stepY = deltaY / steps.Value;
+
+ stepX = stepX is > 0 ? Math.Ceiling(stepX) : Math.Floor(stepX);
+ stepY = stepY is > 0 ? Math.Ceiling(stepY) : Math.Floor(stepY);
+
+ for (var step = 0; step <= steps && (stepX is not 0 || stepY is not 0); step++)
+ {
+ yield return MoveBy((int)stepX, (int)stepY);
+
+ if (Math.Abs(CurrentPosition().X - x) < stepX)
+ {
+ stepX = 0;
+ }
+
+ if (Math.Abs(CurrentPosition().Y - y) < stepY)
+ {
+ stepY = 0;
+ }
+ }
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Uno.UI.RuntimeTests.Engine.Library/Library/InputInjectorHelper.cs b/src/Uno.UI.RuntimeTests.Engine.Library/Library/InputInjectorHelper.cs
new file mode 100644
index 0000000..7382f90
--- /dev/null
+++ b/src/Uno.UI.RuntimeTests.Engine.Library/Library/InputInjectorHelper.cs
@@ -0,0 +1,119 @@
+#if !UNO_RUNTIMETESTS_DISABLE_LIBRARY
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using Windows.Devices.Input;
+using Windows.UI;
+using Windows.UI.Core;
+using Windows.UI.Input.Preview.Injection;
+using PointerDeviceType = Windows.Devices.Input.PointerDeviceType;
+
+namespace Uno.UI.RuntimeTests;
+
+public partial class InputInjectorHelper
+{
+ private static InputInjectorHelper? _current;
+
+ ///
+ /// Gets the singleton input injector that can be used to simulate pointers interaction on the application.
+ ///
+ public static InputInjectorHelper Current => _current ??= new();
+
+ ///
+ /// Returns the or `null` if it has not yet been initialized.
+ ///
+ public static InputInjectorHelper? TryGetCurrent() => _current;
+
+ private InputInjectorHelper()
+ {
+ Injector = InputInjector.TryCreate() ?? throw new InvalidOperationException(
+ "Cannot create input injector. "
+ + "This usually means that the pointer injection API is not implemented for the current platform. "
+ + "Consider to disable tests that are using pointer injection for this platform.");
+
+ Mouse = new MouseHelper(Injector);
+ }
+
+ ///
+ /// Raw access to the input injector for custom raw input injection.
+ ///
+ public InputInjector Injector { get; }
+
+ ///
+ /// Factory for mouse injected inputs info.
+ ///
+ public MouseHelper Mouse { get; }
+
+ ///
+ /// Gets the default pointer type for the current platform
+ ///
+ public static PointerDeviceType DefaultPointerType
+#if __IOS__ || __ANDROID__
+ => PointerDeviceType.Touch;
+#else
+ => PointerDeviceType.Mouse;
+#endif
+
+ ///
+ /// Gets the current pointer type used to inject pointer.
+ ///
+ public PointerDeviceType CurrentPointerType { get; private set; } = DefaultPointerType;
+
+ ///
+ /// Sets the .
+ ///
+ /// Type of pointer to use.
+ /// A disposable that will restore the previous type on dispose.
+ public IDisposable SetPointerType(PointerDeviceType type)
+ {
+ var previous = CurrentPointerType;
+ CurrentPointerType = type;
+
+ return new PointerSubscription(this, previous, type);
+ }
+
+ ///
+ /// Make sure to release any pressed pointer.
+ ///
+ public void CleanupPointers()
+ {
+ InjectMouseInput(Mouse.ReleaseAny());
+ InjectMouseInput(Mouse.MoveTo(0, 0));
+ }
+
+ ///
+ /// Injects some touch infos to simulate finger interaction on the application
+ ///
+ public void InjectTouchInput(IEnumerable input)
+ => Injector.InjectTouchInput(input.Where(i => i is not null).Cast());
+
+ ///
+ /// Injects some touch infos to simulate finger interaction on the application
+ ///
+ public void InjectTouchInput(params InjectedInputTouchInfo?[] input)
+ => Injector.InjectTouchInput(input.Where(i => i is not null).Cast());
+
+ ///
+ /// Injects some mouse infos to simulate mouse interaction on the application
+ ///
+ public void InjectMouseInput(IEnumerable input)
+ => Injector.InjectMouseInput(input.Where(i => i is not null).Cast());
+
+ ///
+ /// Injects some mouse infos to simulate mouse interaction on the application
+ ///
+ public void InjectMouseInput(params InjectedInputMouseInfo?[] input)
+ => Injector.InjectMouseInput(input.Where(i => i is not null).Cast());
+
+ private record PointerSubscription(InputInjectorHelper Injector, PointerDeviceType Previous, PointerDeviceType Current) : IDisposable
+ {
+ ///
+ public void Dispose()
+ => Injector.CurrentPointerType = Previous;
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Uno.UI.RuntimeTests.Engine.Library/Library/InputInjectorHelperExtensions.cs b/src/Uno.UI.RuntimeTests.Engine.Library/Library/InputInjectorHelperExtensions.cs
new file mode 100644
index 0000000..8723d4d
--- /dev/null
+++ b/src/Uno.UI.RuntimeTests.Engine.Library/Library/InputInjectorHelperExtensions.cs
@@ -0,0 +1,160 @@
+#if !UNO_RUNTIMETESTS_DISABLE_LIBRARY
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using Windows.Foundation;
+using Windows.UI.Core;
+using Windows.UI.Input.Preview.Injection;
+using PointerDeviceType = Windows.Devices.Input.PointerDeviceType;
+
+#if HAS_UNO_WINUI || WINDOWS_WINUI
+using Microsoft.UI.Xaml;
+#else
+using Windows.UI.Xaml;
+#endif
+
+namespace Uno.UI.RuntimeTests;
+
+public static partial class InputInjectorHelperExtensions
+{
+ public static void Tap(this InputInjectorHelper injector, UIElement elt)
+ => injector.TapCoordinates(GetAbsoluteCenter(elt));
+
+ public static void TapCoordinates(this InputInjectorHelper injector, Point point)
+ => injector.TapCoordinates(point.X, point.Y);
+
+ public static void TapCoordinates(this InputInjectorHelper injector, double x, double y)
+ {
+ switch (injector.CurrentPointerType)
+ {
+ case PointerDeviceType.Touch:
+ injector.Injector.InitializeTouchInjection(InjectedInputVisualizationMode.Default);
+ injector.Injector.InjectTouchInput(new[]
+ {
+ new InjectedInputTouchInfo
+ {
+ PointerInfo = new()
+ {
+ PointerId = 42,
+ PixelLocation = new() { PositionX = (int)x, PositionY = (int)y },
+ PointerOptions = InjectedInputPointerOptions.New
+ | InjectedInputPointerOptions.FirstButton
+ | InjectedInputPointerOptions.PointerDown
+ | InjectedInputPointerOptions.InContact
+ }
+ },
+ new InjectedInputTouchInfo
+ {
+ PointerInfo = new()
+ {
+ PixelLocation = { PositionX = (int)x, PositionY = (int)y },
+ PointerOptions = InjectedInputPointerOptions.FirstButton
+ | InjectedInputPointerOptions.PointerUp
+ }
+ }
+ });
+ injector.Injector.UninitializeTouchInjection();
+ break;
+
+ case PointerDeviceType.Mouse:
+ injector.InjectMouseInput(injector.Mouse.ReleaseAny());
+ injector.InjectMouseInput(injector.Mouse.MoveTo(x, y));
+ injector.InjectMouseInput(injector.Mouse.Press());
+ injector.InjectMouseInput(injector.Mouse.Release());
+ break;
+
+ default:
+ throw NotSupported();
+ }
+ }
+
+ public static void Drag(this InputInjectorHelper injector, UIElement elt, Point to)
+ => injector.DragCoordinates(GetAbsoluteCenter(elt), to);
+
+ public static void Drag(this InputInjectorHelper injector, UIElement elt, double toX, double toY)
+ => injector.DragCoordinates(GetAbsoluteCenter(elt), new Point(toX, toY));
+
+ public static void DragCoordinates(this InputInjectorHelper injector, Point from, Point to)
+ => injector.DragCoordinates(from.X, from.Y, to.X, to.Y);
+
+ public static void DragCoordinates(this InputInjectorHelper injector, double fromX, double fromY, double toX, double toY)
+ {
+ switch (injector.CurrentPointerType)
+ {
+ case PointerDeviceType.Touch:
+ injector.Injector.InitializeTouchInjection(InjectedInputVisualizationMode.Default);
+ injector.Injector.InjectTouchInput(Inputs());
+ injector.Injector.UninitializeTouchInjection();
+
+ IEnumerable Inputs()
+ {
+ yield return new()
+ {
+ PointerInfo = new()
+ {
+ PointerId = 42,
+ PixelLocation = new() { PositionX = (int)fromX, PositionY = (int)fromY },
+ PointerOptions = InjectedInputPointerOptions.New
+ | InjectedInputPointerOptions.FirstButton
+ | InjectedInputPointerOptions.PointerDown
+ | InjectedInputPointerOptions.InContact
+ | InjectedInputPointerOptions.InRange
+ }
+ };
+
+ var steps = 10;
+ var stepX = (toX - fromX) / steps;
+ var stepY = (toY - fromY) / steps;
+ for (var step = 0; step <= steps; step++)
+ {
+ yield return new()
+ {
+ PointerInfo = new()
+ {
+ PixelLocation = new() { PositionX = (int)(fromX + step * stepX), PositionY = (int)(fromY + step * stepY) },
+ PointerOptions = InjectedInputPointerOptions.Update
+ | InjectedInputPointerOptions.FirstButton
+ | InjectedInputPointerOptions.InContact
+ | InjectedInputPointerOptions.InRange
+ }
+ };
+ }
+
+ yield return new()
+ {
+ PointerInfo = new()
+ {
+ PixelLocation = { PositionX = (int)toX, PositionY = (int)toY },
+ PointerOptions = InjectedInputPointerOptions.FirstButton
+ | InjectedInputPointerOptions.PointerUp
+ }
+ };
+ }
+
+ break;
+
+ case PointerDeviceType.Mouse:
+ injector.InjectMouseInput(injector.Mouse.ReleaseAny());
+ injector.InjectMouseInput(injector.Mouse.MoveTo(fromX, fromY));
+ injector.InjectMouseInput(injector.Mouse.Press());
+ injector.InjectMouseInput(injector.Mouse.MoveTo(toX, toY));
+ injector.InjectMouseInput(injector.Mouse.Release());
+ break;
+
+ default:
+ throw NotSupported();
+ }
+ }
+
+ private static Point GetAbsoluteCenter(UIElement elt)
+ => elt.TransformToVisual(null).TransformPoint(new Point(elt.ActualSize.X / 2.0, elt.ActualSize.Y / 2.0));
+
+ private static NotSupportedException NotSupported([CallerMemberName] string operation = "")
+ => new($"'{operation}' with type '{InputInjectorHelper.Current.CurrentPointerType}' is not supported yet on this platform. Feel free to contribute!");
+
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Uno.UI.RuntimeTests.Engine.Library/RequiresFullWindowAttribute.cs b/src/Uno.UI.RuntimeTests.Engine.Library/Library/RequiresFullWindowAttribute.cs
similarity index 71%
rename from src/Uno.UI.RuntimeTests.Engine.Library/RequiresFullWindowAttribute.cs
rename to src/Uno.UI.RuntimeTests.Engine.Library/Library/RequiresFullWindowAttribute.cs
index 1be98ae..e6d4c59 100644
--- a/src/Uno.UI.RuntimeTests.Engine.Library/RequiresFullWindowAttribute.cs
+++ b/src/Uno.UI.RuntimeTests.Engine.Library/Library/RequiresFullWindowAttribute.cs
@@ -4,7 +4,7 @@
namespace Uno.UI.RuntimeTests;
-#if !UNO_RUNTIMETESTS_DISABLE_REQUIRESFULLWINDOWATTRIBUTE
+#if !UNO_RUNTIMETESTS_DISABLE_LIBRARY && !UNO_RUNTIMETESTS_DISABLE_REQUIRESFULLWINDOWATTRIBUTE
///
/// Marks a test which sets its test UI to be full-screen (Window.Content).
///
diff --git a/src/Uno.UI.RuntimeTests.Engine.Library/RunsOnUIThreadAttribute.cs b/src/Uno.UI.RuntimeTests.Engine.Library/Library/RunsOnUIThreadAttribute.cs
similarity index 71%
rename from src/Uno.UI.RuntimeTests.Engine.Library/RunsOnUIThreadAttribute.cs
rename to src/Uno.UI.RuntimeTests.Engine.Library/Library/RunsOnUIThreadAttribute.cs
index b82a2d7..3b3f57c 100644
--- a/src/Uno.UI.RuntimeTests.Engine.Library/RunsOnUIThreadAttribute.cs
+++ b/src/Uno.UI.RuntimeTests.Engine.Library/Library/RunsOnUIThreadAttribute.cs
@@ -4,7 +4,7 @@
namespace Uno.UI.RuntimeTests;
-#if !UNO_RUNTIMETESTS_DISABLE_RUNSONUITHREADATTRIBUTE
+#if !UNO_RUNTIMETESTS_DISABLE_LIBRARY && !UNO_RUNTIMETESTS_DISABLE_RUNSONUITHREADATTRIBUTE
///
/// Indicates that the test should be run on the UI thread.
///
diff --git a/src/Uno.UI.RuntimeTests.Engine.Library/TestRun.cs b/src/Uno.UI.RuntimeTests.Engine.Library/TestRun.cs
deleted file mode 100644
index 667df23..0000000
--- a/src/Uno.UI.RuntimeTests.Engine.Library/TestRun.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace Uno.UI.RuntimeTests
-{
- public sealed partial class UnitTestsControl
- {
- private class TestRun
- {
- public int Run { get; set; }
- public int Ignored { get; set; }
- public int Succeeded { get; set; }
- public int Failed { get; set; }
-
- public int CurrentRepeatCount { get; set; }
- }
- }
-}
diff --git a/src/Uno.UI.RuntimeTests.Engine.Library/TestCase.cs b/src/Uno.UI.RuntimeTests.Engine.Library/UI/TestCase.cs
similarity index 92%
rename from src/Uno.UI.RuntimeTests.Engine.Library/TestCase.cs
rename to src/Uno.UI.RuntimeTests.Engine.Library/UI/TestCase.cs
index a62c2be..c341b90 100644
--- a/src/Uno.UI.RuntimeTests.Engine.Library/TestCase.cs
+++ b/src/Uno.UI.RuntimeTests.Engine.Library/UI/TestCase.cs
@@ -6,6 +6,8 @@
namespace Uno.UI.RuntimeTests;
+#if !UNO_RUNTIMETESTS_DISABLE_UI
+
internal record TestCase
{
public object[] Parameters { get; init; } = Array.Empty