Browse Source

UI Test improvements

pull/240/head
Ionite 1 year ago
parent
commit
47b34cb60a
No known key found for this signature in database
  1. 12
      StabilityMatrix.UITests/Attributes/TestPriorityAttribute.cs
  2. 17
      StabilityMatrix.UITests/Extensions/VisualExtensions.cs
  3. 73
      StabilityMatrix.UITests/Extensions/WindowExtensions.cs
  4. 67
      StabilityMatrix.UITests/MainWindowTests.cs
  5. 2
      StabilityMatrix.UITests/ModuleInit.cs
  6. 57
      StabilityMatrix.UITests/PriorityOrderer.cs
  7. BIN
      StabilityMatrix.UITests/Snapshots/MainWindowTests.MainWindow_ShouldOpen.verified.png
  8. 1
      StabilityMatrix.UITests/Usings.cs
  9. 1
      StabilityMatrix.UITests/VerifyConfig.cs

12
StabilityMatrix.UITests/Attributes/TestPriorityAttribute.cs

@ -0,0 +1,12 @@
namespace StabilityMatrix.UITests.Attributes;
[AttributeUsage(AttributeTargets.Method)]
public class TestPriorityAttribute : Attribute
{
public int Priority { get; private set; }
public TestPriorityAttribute(int priority)
{
Priority = priority;
}
}

17
StabilityMatrix.UITests/Extensions/VisualExtensions.cs

@ -0,0 +1,17 @@
using Avalonia.Controls;
namespace StabilityMatrix.UITests.Extensions;
public static class VisualExtensions
{
public static Rect GetRelativeBounds(this Visual visual, TopLevel topLevel)
{
var origin =
visual.TranslatePoint(new Point(0, 0), topLevel)
?? throw new NullReferenceException("Origin is null");
var bounds = new Rect(origin, visual.Bounds.Size);
return bounds;
}
}

73
StabilityMatrix.UITests/Extensions/WindowExtensions.cs

@ -0,0 +1,73 @@
using Avalonia.Controls;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace StabilityMatrix.UITests.Extensions;
/// <summary>
/// Window extensions for UI tests
/// </summary>
public static class WindowExtensions
{
public static void ClickTarget(this TopLevel topLevel, Control target)
{
// Check target is part of the visual tree
var targetVisualRoot = target.GetVisualRoot();
if (targetVisualRoot is not TopLevel)
{
throw new ArgumentException("Target is not part of the visual tree");
}
if (targetVisualRoot.Equals(topLevel))
{
throw new ArgumentException(
"Target is not part of the same visual tree as the top level"
);
}
var point =
target.TranslatePoint(
new Point(target.Bounds.Width / 2, target.Bounds.Height / 2),
topLevel
) ?? throw new NullReferenceException("Point is null");
topLevel.MouseMove(point);
topLevel.MouseDown(point, MouseButton.Left);
topLevel.MouseUp(point, MouseButton.Left);
// Return mouse to outside of window
topLevel.MouseMove(new Point(-50, -50));
}
public static async Task ClickTargetAsync(this TopLevel topLevel, Control target)
{
// Check target is part of the visual tree
var targetVisualRoot = target.GetVisualRoot();
if (targetVisualRoot is not TopLevel)
{
throw new ArgumentException("Target is not part of the visual tree");
}
if (!targetVisualRoot.Equals(topLevel))
{
throw new ArgumentException(
"Target is not part of the same visual tree as the top level"
);
}
var point =
target.TranslatePoint(
new Point(target.Bounds.Width / 2, target.Bounds.Height / 2),
topLevel
) ?? throw new NullReferenceException("Point is null");
topLevel.MouseMove(point);
topLevel.MouseDown(point, MouseButton.Left);
topLevel.MouseUp(point, MouseButton.Left);
await Task.Delay(40);
// Return mouse to outside of window
topLevel.MouseMove(new Point(-50, -50));
Dispatcher.UIThread.Invoke(() => Dispatcher.UIThread.RunJobs());
}
}

67
StabilityMatrix.UITests/MainWindowTests.cs

@ -1,5 +1,6 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Threading;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Windowing; using FluentAvalonia.UI.Windowing;
@ -9,15 +10,19 @@ using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.ViewModels; using StabilityMatrix.Avalonia.ViewModels;
using StabilityMatrix.Avalonia.Views; using StabilityMatrix.Avalonia.Views;
using StabilityMatrix.Avalonia.Views.Dialogs; using StabilityMatrix.Avalonia.Views.Dialogs;
using StabilityMatrix.UITests.Extensions;
namespace StabilityMatrix.UITests; namespace StabilityMatrix.UITests;
[UsesVerify] [UsesVerify]
[Collection("TempDir")] [Collection("TempDir")]
[TestCaseOrderer("StabilityMatrix.UITests.PriorityOrderer", "StabilityMatrix.UITests")]
public class MainWindowTests public class MainWindowTests
{ {
private static IServiceProvider Services => App.Services; private static IServiceProvider Services => App.Services;
private static (AppWindow, MainWindowViewModel)? currentMainWindow;
private static VerifySettings Settings private static VerifySettings Settings
{ {
get get
@ -28,23 +33,32 @@ public class MainWindowTests
vm => vm.FooterPages, vm => vm.FooterPages,
vm => vm.CurrentPage vm => vm.CurrentPage
); );
settings.DisableDiff();
return settings; return settings;
} }
} }
private static (AppWindow, MainWindowViewModel) GetMainWindow() private static (AppWindow, MainWindowViewModel) GetMainWindow()
{ {
if (currentMainWindow is not null)
{
return currentMainWindow.Value;
}
var window = Services.GetRequiredService<MainWindow>(); var window = Services.GetRequiredService<MainWindow>();
var viewModel = Services.GetRequiredService<MainWindowViewModel>(); var viewModel = Services.GetRequiredService<MainWindowViewModel>();
window.DataContext = viewModel; window.DataContext = viewModel;
window.SetDefaultFonts(); window.SetDefaultFonts();
window.Width = 1400;
window.Height = 900;
App.VisualRoot = window; App.VisualRoot = window;
App.StorageProvider = window.StorageProvider; App.StorageProvider = window.StorageProvider;
App.Clipboard = window.Clipboard ?? throw new NullReferenceException("Clipboard is null"); App.Clipboard = window.Clipboard ?? throw new NullReferenceException("Clipboard is null");
return (window, viewModel); currentMainWindow = (window, viewModel);
return currentMainWindow.Value;
} }
private static BetterContentDialog? GetWindowDialog(Visual window) private static BetterContentDialog? GetWindowDialog(Visual window)
@ -58,22 +72,46 @@ public class MainWindowTests
?.FindDescendantOfType<BetterContentDialog>(); ?.FindDescendantOfType<BetterContentDialog>();
} }
[AvaloniaFact] private static IEnumerable<BetterContentDialog> EnumerateWindowDialogs(Visual window)
public Task MainWindowViewModel_ShouldOk()
{ {
var viewModel = Services.GetRequiredService<MainWindowViewModel>(); return window
.FindDescendantOfType<VisualLayerManager>()
?.FindDescendantOfType<OverlayLayer>()
?.FindDescendantOfType<DialogHost>()
?.FindDescendantOfType<LayoutTransformControl>()
?.FindDescendantOfType<VisualLayerManager>()
?.GetVisualDescendants()
.OfType<BetterContentDialog>() ?? Enumerable.Empty<BetterContentDialog>();
}
return Verify(viewModel, Settings); private async Task<(BetterContentDialog, T)> WaitForDialog<T>(Visual window)
where T : Control
{
var dialogs = await WaitHelper.WaitForConditionAsync(
() => EnumerateWindowDialogs(window).ToList(),
list => list.Any(dialog => dialog.Content is T)
);
if (dialogs.Count == 0)
{
throw new InvalidOperationException("No dialogs found");
}
var contentDialog = dialogs.First(dialog => dialog.Content is T);
return (contentDialog, contentDialog.Content as T)!;
} }
[AvaloniaFact] [AvaloniaFact, TestPriority(1)]
public async Task MainWindow_ShouldOpen() public async Task MainWindow_ShouldOpen()
{ {
var (window, vm) = GetMainWindow(); var (window, _) = GetMainWindow();
window.Show(); window.Show();
await Task.Delay(800); await Task.Delay(300);
Dispatcher.UIThread.RunJobs();
// Find the select data directory dialog // Find the select data directory dialog
var selectDataDirectoryDialog = await WaitHelper.WaitForNotNullAsync( var selectDataDirectoryDialog = await WaitHelper.WaitForNotNullAsync(
@ -86,7 +124,8 @@ public class MainWindowTests
.GetVisualDescendants() .GetVisualDescendants()
.OfType<Button>() .OfType<Button>()
.First(b => b.Content as string == "Continue"); .First(b => b.Content as string == "Continue");
continueButton.Command?.Execute(null);
await window.ClickTargetAsync(continueButton);
// Find the one click install dialog // Find the one click install dialog
var oneClickDialog = await WaitHelper.WaitForConditionAsync( var oneClickDialog = await WaitHelper.WaitForConditionAsync(
@ -95,8 +134,16 @@ public class MainWindowTests
); );
Assert.NotNull(oneClickDialog); Assert.NotNull(oneClickDialog);
await Task.Delay(1000); await Task.Delay(1800);
await Verify(window, Settings); await Verify(window, Settings);
} }
[AvaloniaFact, TestPriority(2)]
public async Task MainWindowViewModel_ShouldOk()
{
var viewModel = Services.GetRequiredService<MainWindowViewModel>();
await Verify(viewModel, Settings);
}
} }

2
StabilityMatrix.UITests/ModuleInit.cs

@ -1,5 +1,7 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
[assembly: CollectionBehavior(DisableTestParallelization = true)]
namespace StabilityMatrix.UITests; namespace StabilityMatrix.UITests;
public static class ModuleInit public static class ModuleInit

57
StabilityMatrix.UITests/PriorityOrderer.cs

@ -0,0 +1,57 @@
using StabilityMatrix.UITests.Attributes;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace StabilityMatrix.UITests;
public class PriorityOrderer : ITestCaseOrderer
{
public IEnumerable<TTestCase> OrderTestCases<TTestCase>(IEnumerable<TTestCase> testCases)
where TTestCase : ITestCase
{
var sortedMethods = new SortedDictionary<int, List<TTestCase>>();
foreach (var testCase in testCases)
{
var priority = 0;
foreach (
var attr in testCase.TestMethod.Method.GetCustomAttributes(
typeof(TestPriorityAttribute).AssemblyQualifiedName
)
)
{
priority = attr.GetNamedArgument<int>("Priority");
}
GetOrCreate(sortedMethods, priority).Add(testCase);
}
foreach (var list in sortedMethods.Keys.Select(priority => sortedMethods[priority]))
{
list.Sort(
(x, y) =>
StringComparer.OrdinalIgnoreCase.Compare(
x.TestMethod.Method.Name,
y.TestMethod.Method.Name
)
);
foreach (var testCase in list)
{
yield return testCase;
}
}
}
private static TValue GetOrCreate<TKey, TValue>(IDictionary<TKey, TValue> dictionary, TKey key)
where TValue : new()
{
if (dictionary.TryGetValue(key, out var result))
return result;
result = new TValue();
dictionary[key] = result;
return result;
}
}

BIN
StabilityMatrix.UITests/Snapshots/MainWindowTests.MainWindow_ShouldOpen.verified.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 202 KiB

1
StabilityMatrix.UITests/Usings.cs

@ -3,3 +3,4 @@ global using Avalonia;
global using Avalonia.Headless; global using Avalonia.Headless;
global using Avalonia.Headless.XUnit; global using Avalonia.Headless.XUnit;
global using Avalonia.Input; global using Avalonia.Input;
global using StabilityMatrix.UITests.Attributes;

1
StabilityMatrix.UITests/VerifyConfig.cs

@ -10,5 +10,6 @@ internal static class VerifyConfig
{ {
Default = new VerifySettings(); Default = new VerifySettings();
Default.IgnoreMembersWithType<WeakEventManager>(); Default.IgnoreMembersWithType<WeakEventManager>();
Default.DisableDiff();
} }
} }

Loading…
Cancel
Save