Skip to content

Commit

Permalink
WIP: UI Tests (#1866)
Browse files Browse the repository at this point in the history
* Starting work on UI Testing

* Working test

* More work getting UI tests rolling.

* Working base test

* Work on startup to make it more robust

* Starting work to coding demo app.

* Updates with pipeline starting WinAppDriver and output

* Turning off winapp driver starting.

* Automatically start and stop SUT application

* WIP

* Getting UI Tests running from UITestCases App

* Updated UITestCases to load tests via reflection

DialogTests properly reset each time they are used.

* Updating build configuration.

* Fixing arguments to dotnet test

* Updating timeout

* Clean up of UI tests to perform explicit waits

* Switching back to managing the process

* Waiting for condition to be true rather than a single assert.

* Adding screenshot.

* Attempted publishing of UI tests results.

* Increasing timeouts on failing tests

* Fixing overload resolution.

* Fixing screen resolution to avoid clicking in the wrong spot.

* Renaming solution

Creating a solution filter for backwards compatibility

* Adding automatic saving of screenshots

Updating build pipeline to only run UI tests on merges to master

* Removing un-used test project

* Fixing colors unit test project

* Warnings cleanup
  • Loading branch information
Keboo committed Jun 16, 2020
1 parent dbe4dde commit 82d7706
Show file tree
Hide file tree
Showing 35 changed files with 1,001 additions and 70 deletions.
8 changes: 4 additions & 4 deletions .editorconfig
Expand Up @@ -58,13 +58,13 @@ csharp_preserve_single_line_statements = true
#Style - expression bodied member options

#prefer block bodies for accessors
csharp_style_expression_bodied_accessors = false:suggestion
csharp_style_expression_bodied_accessors = when_on_single_line:suggestion
#prefer block bodies for constructors
csharp_style_expression_bodied_constructors = false:suggestion
csharp_style_expression_bodied_constructors = when_on_single_line:suggestion
#prefer block bodies for methods
csharp_style_expression_bodied_methods = when_on_single_line:suggestion
#prefer block bodies for properties
csharp_style_expression_bodied_properties = false:suggestion
csharp_style_expression_bodied_properties = when_on_single_line:suggestion

#Style - expression level options

Expand All @@ -76,7 +76,7 @@ dotnet_style_predefined_type_for_member_access = true:suggestion
#Style - implicit and explicit types

#prefer var is used to declare variables with built-in system types such as int
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_for_built_in_types = false:suggestion
#prefer var when the type is already mentioned on the right-hand side of a declaration expression
csharp_style_var_when_type_is_apparent = true:suggestion

Expand Down
5 changes: 5 additions & 0 deletions MahMaterialDragablzMashUp/MahAppsDragablzDemo.csproj
Expand Up @@ -23,5 +23,10 @@
<ItemGroup>
<Resource Include="Resources\ProfilePic.jpg" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="Dragablz">
<NoWarn>NU1701</NoWarn>
</PackageReference>
</ItemGroup>
<Import Project="..\.paket\Paket.Restore.targets" />
</Project>
1 change: 0 additions & 1 deletion MainDemo.Wpf/App.xaml
Expand Up @@ -28,7 +28,6 @@
<ResourceDictionary Source="pack://application:,,,/ShowMeTheXAML.AvalonEdit;component/Themes/xamldisplayer.xaml" />
</ResourceDictionary.MergedDictionaries>


<Style TargetType="smtx:XamlDisplay" BasedOn="{StaticResource {x:Type smtx:XamlDisplay}}">
<Style.Resources>
<ResourceDictionary>
Expand Down
16 changes: 10 additions & 6 deletions MainDemo.Wpf/MainWindow.xaml
Expand Up @@ -6,7 +6,9 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:domain1="clr-namespace:MaterialDesignDemo.Domain"
WindowStartupLocation="CenterScreen"
Title="Material Design in XAML" Height="800" Width="1100"
Title="Material Design in XAML"
AutomationProperties.Name="{Binding Title, RelativeSource={RelativeSource Self}}"
Height="800" Width="1100"
TextElement.Foreground="{DynamicResource MaterialDesignBody}"
TextElement.FontWeight="Regular"
TextElement.FontSize="13"
Expand Down Expand Up @@ -68,27 +70,29 @@
SelectedIndex="0"
SelectedItem="{Binding SelectedItem, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding DemoItems}"
PreviewMouseLeftButtonUp="UIElement_OnPreviewMouseLeftButtonUp">
PreviewMouseLeftButtonUp="UIElement_OnPreviewMouseLeftButtonUp"
AutomationProperties.Name="DemoPagesListBox">
<ListBox.Resources>
<Style TargetType="ScrollBar" BasedOn="{StaticResource MaterialDesignScrollBarMinimal}"/>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate DataType="domain:DemoItem">
<TextBlock Text="{Binding Name}" Margin="32 0 32 0" />
<TextBlock Text="{Binding Name}" Margin="32 0 32 0" AutomationProperties.AutomationId="DemoItemPage" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</materialDesign:DrawerHost.LeftDrawerContent>
<DockPanel>
<materialDesign:ColorZone Padding="16" materialDesign:ShadowAssist.ShadowDepth="Depth2"
Mode="PrimaryMid" DockPanel.Dock="Top">
Mode="PrimaryMid" DockPanel.Dock="Top">
<DockPanel>
<ToggleButton
x:Name="MenuToggleButton"
Style="{StaticResource MaterialDesignHamburgerToggleButton}"
IsChecked="False"
Click="MenuToggleButton_OnClick"/>
Click="MenuToggleButton_OnClick"
AutomationProperties.Name="HamburgerToggleButton"/>
<materialDesign:PopupBox DockPanel.Dock="Right" PlacementMode="BottomAndAlignRightEdges" StaysOpen="False">
<StackPanel>
<StackPanel Orientation="Horizontal"
Expand All @@ -109,7 +113,7 @@
<Button Content="Goodbye" Click="MenuPopupButton_OnClick"/>
</StackPanel>
</materialDesign:PopupBox>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="22">Material Design In XAML Toolkit</TextBlock>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="22" AutomationProperties.Name="Material Design In XAML Toolkit">Material Design In XAML Toolkit</TextBlock>
</DockPanel>
</materialDesign:ColorZone>
<Grid>
Expand Down
4 changes: 2 additions & 2 deletions MainDemo.Wpf/PaletteSelector.xaml
Expand Up @@ -45,7 +45,7 @@
<SolidColorBrush Color="{Binding ExemplarHue.Color, Mode=OneTime}" />
</Border.Background>
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:PaletteSelector}}, Path=DataContext.ApplyPrimaryCommand, Mode=OneTime}"
CommandParameter="{Binding}">
CommandParameter="{Binding}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Primary" />
<Viewbox Width="16" Height="16">
Expand All @@ -63,7 +63,7 @@
<SolidColorBrush Color="{Binding AccentExemplarHue.Color, Mode=OneTime}" />
</Border.Background>
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:PaletteSelector}}, Path=DataContext.ApplyAccentCommand, Mode=OneTime}"
CommandParameter="{Binding}">
CommandParameter="{Binding}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Accent" />
<Viewbox Width="16" Height="16">
Expand Down
@@ -1,12 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>MaterialDesignColors.Wpf.Fixture</RootNamespace>
<AssemblyName>MaterialDesignColors.Wpf.Fixture</AssemblyName>
<TargetFrameworks>net45;netcoreapp3.1</TargetFrameworks>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<AssemblyTitle>MaterialDesignColors.Wpf.Fixture</AssemblyTitle>
<Product>MaterialDesignColors.Wpf.Fixture</Product>
<TargetFrameworks>net472;netcoreapp3.1</TargetFrameworks>
<AssemblyTitle>MaterialDesignColors.Wpf.Tests</AssemblyTitle>
<Product>MaterialDesignColors.Wpf.Tests</Product>
</PropertyGroup>
<Import Project="..\packages\xunit.core\build\xunit.core.props" Condition="Exists('..\packages\xunit.core\build\xunit.core.props')" Label="Paket" />
<Import Project="..\packages\xunit.runner.visualstudio\build\$(__paket__xunit_runner_visualstudio_props).props" Condition="Exists('..\packages\xunit.runner.visualstudio\build\$(__paket__xunit_runner_visualstudio_props).props')" Label="Paket" />
Expand Down

This file was deleted.

13 changes: 0 additions & 13 deletions MaterialDesignThemes.MahApps.Tests/UnitTest1.cs

This file was deleted.

7 changes: 0 additions & 7 deletions MaterialDesignThemes.MahApps.Tests/paket.references

This file was deleted.

20 changes: 20 additions & 0 deletions MaterialDesignThemes.UITests/App.cs
@@ -0,0 +1,20 @@
using System.IO;
using Xunit;

[assembly: CollectionBehavior(DisableTestParallelization = true)]

namespace MaterialDesignThemes.UITests
{
public static class App
{
#if DEBUG
private const string Configuration = "Debug";
#else
private const string Configuration = "Release";
#endif

public static string DemoAppPath => Path.GetFullPath(@$"..\..\..\..\MainDemo.Wpf\bin\{Configuration}\netcoreapp3.1\MaterialDesignDemo.exe");

public static string UITestsAppPath => Path.GetFullPath(@$"..\..\..\..\UITestCases\bin\{Configuration}\netcoreapp3.1\UITestCases.exe");
}
}
114 changes: 114 additions & 0 deletions MaterialDesignThemes.UITests/AppiumHelpers.cs
@@ -0,0 +1,114 @@
using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium.Windows;
using OpenQA.Selenium.Support.UI;
using Xunit;
using Xunit.Sdk;
using DefaultWait = OpenQA.Selenium.Support.UI.DefaultWait<OpenQA.Selenium.Appium.Windows.WindowsDriver<OpenQA.Selenium.Appium.Windows.WindowsElement>>;

namespace MaterialDesignThemes.UITests
{
public static class AppiumHelpers
{
private static IClock Clock { get; } = new SystemClock();

public static WindowsElement GetElementByAccessibilityId(
this WindowsDriver<WindowsElement> driver,
string selector,
TimeSpan? timeout = null,
TimeSpan? pollingInterval = null)
{
if (driver is null)
{
throw new ArgumentNullException(nameof(driver));
}

if (string.IsNullOrEmpty(selector))
{
throw new ArgumentException($"'{nameof(selector)}' cannot be null or empty", nameof(selector));
}

DefaultWait wait = GetWait(driver, timeout, pollingInterval);
wait.IgnoreExceptionTypes(typeof(WebDriverException));
return wait.Until(driver => driver.FindElementByAccessibilityId(selector)!);
}

public static void WaitFor(this WindowsDriver<WindowsElement> driver,
Func<bool> condition,
TimeSpan? timeout = null,
TimeSpan? pollingInterval = null)
=> driver.WaitUntil(_ =>
{
Assert.True(condition());
return true;
}, timeout, pollingInterval);

public static void WaitUntil(
this WindowsDriver<WindowsElement> driver,
Action condition,
TimeSpan? timeout = null,
TimeSpan? pollingInterval = null)
=> driver.WaitUntil(_ =>
{
condition();
return true;
}, timeout, pollingInterval);

public static void WaitUntil(
this WindowsDriver<WindowsElement> driver,
Action<WindowsDriver<WindowsElement>> condition,
TimeSpan? timeout = null,
TimeSpan? pollingInterval = null)
=> driver.WaitUntil<bool>(driver =>
{
condition(driver);
return true;
}, timeout, pollingInterval);

public static TResult WaitUntil<TResult>(
this WindowsDriver<WindowsElement> driver,
Func<TResult> condition,
TimeSpan? timeout = null,
TimeSpan? pollingInterval = null)
=> driver.WaitUntil(_ => condition(), timeout, pollingInterval);

public static TResult WaitUntil<TResult>(
this WindowsDriver<WindowsElement> driver,
Func<WindowsDriver<WindowsElement>, TResult> condition,
TimeSpan? timeout = null,
TimeSpan? pollingInterval = null)
{
if (driver is null)
{
throw new ArgumentNullException(nameof(driver));
}

if (condition is null)
{
throw new ArgumentNullException(nameof(condition));
}

DefaultWait wait = GetWait(driver, timeout, pollingInterval);
wait.IgnoreExceptionTypes(
typeof(WebDriverException),
typeof(XunitException)
);
return wait.Until(condition);
}

private static DefaultWait GetWait(
WindowsDriver<WindowsElement> driver,
TimeSpan? timeout,
TimeSpan? pollingInterval = null)
{
TimeSpan timeoutValue = timeout ?? TimeSpan.FromSeconds(3);
TimeSpan interval = pollingInterval ?? TimeSpan.FromMilliseconds(timeoutValue.TotalMilliseconds / 10);
var wait = new DefaultWait(driver, Clock)
{
Timeout = timeoutValue,
PollingInterval = interval
};
return wait;
}
}
}
38 changes: 38 additions & 0 deletions MaterialDesignThemes.UITests/DemoApp/DemoAppTests.cs
@@ -0,0 +1,38 @@
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Windows;
using Xunit;
using Xunit.Abstractions;

namespace MaterialDesignThemes.UITests.DemoApp
{
public class DemoAppTests : TestBase
{
public DemoAppTests(ITestOutputHelper output)
: base(output, App.DemoAppPath)
{
WindowsElement? element = Driver.FindElementByName("Material Design In XAML Toolkit");
Assert.NotNull(element);
}

[Fact]
public void CanOpenAllPagesOnTheDemoApp()
{
using var recorder = new TestRecorder(Driver, Output);

var mainWindow = new MainWindow(Driver);

foreach (AppiumWebElement? listItem in mainWindow.PageListItems)
{
var rect = mainWindow.PagesListBox.Rect;
Driver.WaitFor(() => mainWindow.PagesListBox.Rect.Right <= 1);
Driver.WaitFor(() => mainWindow.HamburgerToggleButton.Displayed);
mainWindow.HamburgerToggleButton.Click();

Driver.WaitFor(() => listItem.Location.X >= 0);
listItem.Click();
}

recorder.Success();
}
}
}
25 changes: 25 additions & 0 deletions MaterialDesignThemes.UITests/DemoApp/MainWindow.cs
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Windows;

namespace MaterialDesignThemes.UITests.DemoApp
{
public class MainWindow
{
private WindowsDriver<WindowsElement> Driver { get; }

public MainWindow(WindowsDriver<WindowsElement> driver)
=> Driver = driver ?? throw new ArgumentNullException(nameof(driver));

public WindowsElement PagesListBox
=> Driver.FindElementByName("DemoPagesListBox")!;

public IList<AppiumWebElement> PageListItems
=> PagesListBox.FindElements(By.XPath("//ListItem"))!;

public WindowsElement HamburgerToggleButton
=> Driver.FindElementByName("HamburgerToggleButton")!;
}
}

0 comments on commit 82d7706

Please sign in to comment.