[FancyZones]UI testing that works in CI (#29453)

* added test project

* run fz test

* rename proj

* editor test project

* check if FZ is running

* rename

* added assert messages

* spelling

* dev docs

* spelling

* update to latest stable

* exclude ui tests deps

* update packages list in notice.md

* added sample tests

* added file for tests run

* removed unrecognized

* removed run

* fix test configuration

* rename job

* change dependance

* run test template

* removed condition

* tabulation fix

* removed arg

* removed dependance

* removed log

* removed parameters

* test

* test

* added parameters

* pool

* pool

* vs test

* dependance

* download artifact

* publish artifact

* artifact publish conditions

* artifact name, default download path

* set folders

* prepare dotnet and vstest platform

* copy all

* target dotnet8

* test build agents

* set vs test version

* spellcheck

* set test platform version

* package feed selector

* hardcoded vstest location

* are other tests running?

* location

* vstest.console

* upd command

* script path

* search vstest.console

* vs path

* tools dir

* check files

* try full path

* try vstest task

* try full path in vstest task

* change path, remove unnecessary

* test with full vsconsole path

* winappdriver task

* changed args and condition

* default address

* added start operation type

* task name

* remove resolution

* Update run-ui-tests-ci.yml

* Update run-ui-tests-ci.yml

* Update run-ui-tests-ci.yml

* Update run-ui-tests-ci.yml

* AgentResolution should be a string

* Update run-ui-tests-ci.yml

testing against what WinUI gallery has for agent

* Update run-ui-tests-ci.yml

* Update run-ui-tests-ci.yml

* added WinAppDriver.exe

* spellcheck

* remove task

* checkout

* path

* src dir variable

* added init to the second project

* set longer timeout

* try waiting

* rerun

* log session info

* exclude WinAppDriver files from spell-check

* split io class: editor params

* remove unnecessary

* move data to the common project

* io test helper

* write retry

* Moved constants

* file utils

* prepare editor files before launch

* remove unused file

* spellcheck

* create directory

* fixed cleaning up

* remove WinAppDriver from deps

* start WinAppDriver from the default installation path

* installation script

* Revert "spellcheck"

This reverts commit 4bdc395730.

* Revert "exclude WinAppDriver files from spell-check"

This reverts commit 21ee6db3f5.

* install

* installation argument

* spellcheck

* change winappdriver path in fz tests

* delete iohelper

* update docs

* deleted obsolete winappdriver tests

* net version

* try without vstest location

* spellcheck

* Revert "try without vstest location"

This reverts commit 7cd39f3ae6.

* moved json tag constants to the common project
This commit is contained in:
Seraphima Zykova
2024-03-22 13:10:10 +01:00
committed by GitHub
parent c39e306784
commit f6e7635a4e
57 changed files with 1692 additions and 4172 deletions

View File

@@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.FancyZonesEditor.UITests
{
[TestClass]
public class Init
{
private static Process? appDriver;
[AssemblyInitialize]
public static void SetupAll(TestContext context)
{
string winAppDriverPath = "C:\\Program Files (x86)\\Windows Application Driver\\WinAppDriver.exe";
context.WriteLine($"Attempting to launch WinAppDriver at: {winAppDriverPath}");
appDriver = Process.Start(winAppDriverPath);
}
[AssemblyCleanup]
public static void CleanupAll()
{
try
{
appDriver?.Kill();
}
catch
{
}
}
}
}

View File

@@ -0,0 +1,177 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using FancyZonesEditorCommon.Data;
using Microsoft.FancyZonesEditor.UITests;
using Microsoft.FancyZonesEditor.UnitTests.Utils;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UITests_FancyZonesEditor
{
[TestClass]
public class RunFancyZonesEditorTest
{
private static FancyZonesEditorSession? _session;
private static TestContext? _context;
[ClassInitialize]
public static void ClassInitialize(TestContext testContext)
{
_context = testContext;
// prepare files to launch Editor without errors
EditorParameters editorParameters = new EditorParameters();
EditorParameters.ParamsWrapper parameters = new EditorParameters.ParamsWrapper
{
ProcessId = 1,
SpanZonesAcrossMonitors = false,
Monitors = new List<EditorParameters.NativeMonitorDataWrapper>
{
new EditorParameters.NativeMonitorDataWrapper
{
Monitor = "monitor-1",
MonitorInstanceId = "instance-id-1",
MonitorSerialNumber = "serial-number-1",
MonitorNumber = 1,
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
Dpi = 96,
LeftCoordinate = 0,
TopCoordinate = 0,
WorkAreaHeight = 1040,
WorkAreaWidth = 1920,
MonitorHeight = 1080,
MonitorWidth = 1920,
IsSelected = true,
},
},
};
FancyZonesEditorSession.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
LayoutTemplates layoutTemplates = new LayoutTemplates();
LayoutTemplates.TemplateLayoutsListWrapper templateLayoutsListWrapper = new LayoutTemplates.TemplateLayoutsListWrapper
{
LayoutTemplates = new List<LayoutTemplates.TemplateLayoutWrapper>
{
new LayoutTemplates.TemplateLayoutWrapper
{
Type = Constants.TemplateLayoutJsonTags[Constants.TemplateLayout.Empty],
},
new LayoutTemplates.TemplateLayoutWrapper
{
Type = Constants.TemplateLayoutJsonTags[Constants.TemplateLayout.Focus],
ZoneCount = 10,
},
new LayoutTemplates.TemplateLayoutWrapper
{
Type = Constants.TemplateLayoutJsonTags[Constants.TemplateLayout.Rows],
ZoneCount = 2,
ShowSpacing = true,
Spacing = 10,
SensitivityRadius = 10,
},
new LayoutTemplates.TemplateLayoutWrapper
{
Type = Constants.TemplateLayoutJsonTags[Constants.TemplateLayout.Columns],
ZoneCount = 2,
ShowSpacing = true,
Spacing = 20,
SensitivityRadius = 20,
},
new LayoutTemplates.TemplateLayoutWrapper
{
Type = Constants.TemplateLayoutJsonTags[Constants.TemplateLayout.Grid],
ZoneCount = 4,
ShowSpacing = false,
Spacing = 10,
SensitivityRadius = 30,
},
new LayoutTemplates.TemplateLayoutWrapper
{
Type = Constants.TemplateLayoutJsonTags[Constants.TemplateLayout.PriorityGrid],
ZoneCount = 3,
ShowSpacing = true,
Spacing = 1,
SensitivityRadius = 40,
},
},
};
FancyZonesEditorSession.Files.LayoutTemplatesIOHelper.WriteData(layoutTemplates.Serialize(templateLayoutsListWrapper));
CustomLayouts customLayouts = new CustomLayouts();
CustomLayouts.CustomLayoutListWrapper customLayoutListWrapper = new CustomLayouts.CustomLayoutListWrapper
{
CustomLayouts = new List<CustomLayouts.CustomLayoutWrapper> { },
};
FancyZonesEditorSession.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(customLayoutListWrapper));
DefaultLayouts defaultLayouts = new DefaultLayouts();
DefaultLayouts.DefaultLayoutsListWrapper defaultLayoutsListWrapper = new DefaultLayouts.DefaultLayoutsListWrapper
{
DefaultLayouts = new List<DefaultLayouts.DefaultLayoutWrapper> { },
};
FancyZonesEditorSession.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(defaultLayoutsListWrapper));
LayoutHotkeys layoutHotkeys = new LayoutHotkeys();
LayoutHotkeys.LayoutHotkeysWrapper layoutHotkeysWrapper = new LayoutHotkeys.LayoutHotkeysWrapper
{
LayoutHotkeys = new List<LayoutHotkeys.LayoutHotkeyWrapper> { },
};
FancyZonesEditorSession.Files.LayoutHotkeysIOHelper.WriteData(layoutHotkeys.Serialize(layoutHotkeysWrapper));
AppliedLayouts appliedLayouts = new AppliedLayouts();
AppliedLayouts.AppliedLayoutsListWrapper appliedLayoutsWrapper = new AppliedLayouts.AppliedLayoutsListWrapper
{
AppliedLayouts = new List<AppliedLayouts.AppliedLayoutWrapper> { },
};
FancyZonesEditorSession.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
}
[ClassCleanup]
public static void ClassCleanup()
{
FancyZonesEditorSession.Files.Restore();
_context = null;
}
[TestInitialize]
public void TestInitialize()
{
_session = new FancyZonesEditorSession(_context!);
}
[TestCleanup]
public void TestCleanup()
{
_session?.Close(_context!);
}
[TestMethod]
public void OpenEditorWindow() // verify the session is initialized
{
Assert.IsNotNull(_session?.Session);
}
[TestMethod]
public void OpenNewLayoutDialog() // verify the new layout dialog is opened
{
_session?.Click_CreateNewLayout();
Assert.IsNotNull(_session?.Session?.FindElementsByName("Choose layout type")); // check the pane header
}
[TestMethod]
public void OpenEditLayoutDialog() // verify the edit layout dialog is opened
{
_session?.Click_EditLayout(TestConstants.TemplateLayoutNames[Constants.TemplateLayout.Grid]);
Assert.IsNotNull(_session?.Session?.FindElementByAccessibilityId("EditLayoutDialogTitle")); // check the pane header
Assert.IsNotNull(_session?.Session?.FindElementsByName("Edit 'Grid'")); // verify it's opened for the correct layout
}
[TestMethod]
public void OpenContextMenu() // verify the context menu is opened
{
Assert.IsNotNull(_session?.OpenContextMenu(TestConstants.TemplateLayoutNames[Constants.TemplateLayout.Columns]));
}
}
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using static FancyZonesEditorCommon.Data.Constants;
namespace Microsoft.FancyZonesEditor.UITests
{
public static class TestConstants
{
public static readonly Dictionary<TemplateLayout, string> TemplateLayoutNames = new Dictionary<TemplateLayout, string>()
{
{ TemplateLayout.Empty, "No layout" },
{ TemplateLayout.Focus, "Focus" },
{ TemplateLayout.Rows, "Rows" },
{ TemplateLayout.Columns, "Columns" },
{ TemplateLayout.Grid, "Grid" },
{ TemplateLayout.PriorityGrid, "PriorityGrid" },
};
}
}

View File

@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\Version.props" />
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.20348.0</TargetFramework>
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
<ProjectGuid>{3A9A791E-94A9-49F8-8401-C11CE288D5FB}</ProjectGuid>
<RootNamespace>Microsoft.FancyZonesEditor.UITests</RootNamespace>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
<Version>$(Version).0</Version>
</PropertyGroup>
<PropertyGroup>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\tests\UITests-FancyZonesEditor\</OutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Appium.WebDriver" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="System.IO.Abstractions" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FancyZonesEditorCommon\FancyZonesEditorCommon.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,43 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using FancyZonesEditorCommon.Data;
namespace Microsoft.FancyZonesEditor.UITests.Utils
{
public class FancyZonesEditorFiles
{
public IOTestHelper ParamsIOHelper { get; }
public IOTestHelper AppliedLayoutsIOHelper { get; }
public IOTestHelper CustomLayoutsIOHelper { get; }
public IOTestHelper DefaultLayoutsIOHelper { get; }
public IOTestHelper LayoutHotkeysIOHelper { get; }
public IOTestHelper LayoutTemplatesIOHelper { get; }
public FancyZonesEditorFiles()
{
ParamsIOHelper = new IOTestHelper(new EditorParameters().File);
AppliedLayoutsIOHelper = new IOTestHelper(new AppliedLayouts().File);
CustomLayoutsIOHelper = new IOTestHelper(new CustomLayouts().File);
DefaultLayoutsIOHelper = new IOTestHelper(new DefaultLayouts().File);
LayoutHotkeysIOHelper = new IOTestHelper(new LayoutHotkeys().File);
LayoutTemplatesIOHelper = new IOTestHelper(new LayoutTemplates().File);
}
public void Restore()
{
ParamsIOHelper.RestoreData();
AppliedLayoutsIOHelper.RestoreData();
CustomLayoutsIOHelper.RestoreData();
DefaultLayoutsIOHelper.RestoreData();
LayoutHotkeysIOHelper.RestoreData();
LayoutTemplatesIOHelper.RestoreData();
}
}
}

View File

@@ -0,0 +1,137 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Reflection;
using Microsoft.FancyZonesEditor.UITests.Utils;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Windows;
using OpenQA.Selenium.Interactions;
namespace Microsoft.FancyZonesEditor.UnitTests.Utils
{
public class FancyZonesEditorSession
{
protected const string WindowsApplicationDriverUrl = "http://127.0.0.1:4723";
private const string FancyZonesEditorPath = @"\..\..\..\PowerToys.FancyZonesEditor.exe";
private static FancyZonesEditorFiles? _files;
public static FancyZonesEditorFiles Files
{
get
{
if (_files == null)
{
_files = new FancyZonesEditorFiles();
}
return _files;
}
}
public WindowsDriver<WindowsElement>? Session { get; }
public WindowsElement? MainEditorWindow { get; }
public FancyZonesEditorSession(TestContext testContext)
{
try
{
// Launch FancyZonesEditor
string? path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
path += FancyZonesEditorPath;
AppiumOptions opts = new AppiumOptions();
opts.AddAdditionalCapability("app", path);
Session = new WindowsDriver<WindowsElement>(new Uri(WindowsApplicationDriverUrl), opts);
}
catch (Exception ex)
{
testContext.WriteLine(ex.Message);
}
Assert.IsNotNull(Session, "Session not initialized");
testContext.WriteLine("Session: " + Session.SessionId.ToString());
testContext.WriteLine("Title: " + Session.Title);
// Set implicit timeout to make element search to retry every 500 ms
Session.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(3);
// Find main editor window
try
{
MainEditorWindow = Session.FindElementByAccessibilityId("MainWindow1");
}
catch
{
Assert.IsNotNull(MainEditorWindow, "Main editor window not found");
}
}
public void Close(TestContext testContext)
{
// Close the session
if (Session != null)
{
try
{
// FZEditor application can be closed by explicitly closing main editor window
MainEditorWindow?.SendKeys(Keys.Alt + Keys.F4);
}
catch (Exception ex)
{
testContext.WriteLine(ex.Message);
}
Session.Quit();
Session.Dispose();
}
}
private WindowsElement? GetLayout(string layoutName)
{
var listItem = Session?.FindElementByName(layoutName);
Assert.IsNotNull(listItem, "Layout " + layoutName + " not found");
return listItem;
}
public WindowsElement? OpenContextMenu(string layoutName)
{
RightClick_Layout(layoutName);
var menu = Session?.FindElementByClassName("ContextMenu");
Assert.IsNotNull(menu, "Context menu not found");
return menu;
}
public void Click_CreateNewLayout()
{
var button = Session?.FindElementByAccessibilityId("NewLayoutButton");
Assert.IsNotNull(button, "Create new layout button not found");
button?.Click();
}
public void Click_EditLayout(string layoutName)
{
var layout = GetLayout(layoutName);
var editButton = layout?.FindElementByAccessibilityId("EditLayoutButton");
Assert.IsNotNull(editButton, "Edit button not found");
editButton.Click();
}
public void RightClick_Layout(string layoutName)
{
var layout = GetLayout(layoutName);
Actions actions = new Actions(Session);
actions.MoveToElement(layout);
actions.MoveByOffset(30, 30);
actions.ContextClick();
actions.Build().Perform();
}
}
}

View File

@@ -0,0 +1,113 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.IO.Abstractions;
using System.Threading.Tasks;
namespace Microsoft.FancyZonesEditor.UITests.Utils
{
public class IOTestHelper
{
private readonly IFileSystem _fileSystem = new FileSystem();
private string _file;
private string _data = string.Empty;
public IOTestHelper(string file)
{
_file = file;
if (_fileSystem.File.Exists(_file))
{
_data = ReadFile(_file);
}
else
{
_fileSystem.Directory.CreateDirectory(Path.GetDirectoryName(file));
}
}
~IOTestHelper()
{
RestoreData();
}
public void RestoreData()
{
if (_data != string.Empty)
{
WriteData(_data);
}
else
{
DeleteFile();
}
}
public void WriteData(string data)
{
var attempts = 0;
while (attempts < 10)
{
try
{
_fileSystem.File.WriteAllText(_file, data);
}
catch (Exception)
{
Task.Delay(10).Wait();
}
attempts++;
}
}
private string ReadFile(string fileName)
{
var attempts = 0;
while (attempts < 10)
{
try
{
using (Stream inputStream = _fileSystem.File.Open(fileName, FileMode.Open))
using (StreamReader reader = new StreamReader(inputStream))
{
string data = reader.ReadToEnd();
inputStream.Close();
return data;
}
}
catch (Exception)
{
Task.Delay(10).Wait();
}
attempts++;
}
return string.Empty;
}
public void DeleteFile()
{
var attempts = 0;
while (attempts < 10)
{
try
{
_fileSystem.File.Delete(_file);
}
catch (Exception)
{
Task.Delay(10).Wait();
}
attempts++;
}
}
}
}