[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,59 @@
// 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;
namespace FancyZonesEditorCommon.Data
{
public class AppliedLayouts : EditorData<AppliedLayouts.AppliedLayoutsListWrapper>
{
public string File
{
get
{
return GetDataFolder() + "\\Microsoft\\PowerToys\\FancyZones\\applied-layouts.json";
}
}
public struct AppliedLayoutWrapper
{
public struct DeviceIdWrapper
{
public string Monitor { get; set; }
public string MonitorInstance { get; set; }
public int MonitorNumber { get; set; }
public string SerialNumber { get; set; }
public string VirtualDesktop { get; set; }
}
public struct LayoutWrapper
{
public string Uuid { get; set; }
public string Type { get; set; }
public bool ShowSpacing { get; set; }
public int Spacing { get; set; }
public int ZoneCount { get; set; }
public int SensitivityRadius { get; set; }
}
public DeviceIdWrapper Device { get; set; }
public LayoutWrapper AppliedLayout { get; set; }
}
public struct AppliedLayoutsListWrapper
{
public List<AppliedLayoutWrapper> AppliedLayouts { get; set; }
}
}
}

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.Collections.Generic;
using System.Collections.ObjectModel;
namespace FancyZonesEditorCommon.Data
{
public static class Constants
{
public enum TemplateLayout
{
Empty,
Focus,
Rows,
Columns,
Grid,
PriorityGrid,
}
public static readonly ReadOnlyDictionary<TemplateLayout, string> TemplateLayoutJsonTags = new ReadOnlyDictionary<TemplateLayout, string>(
new Dictionary<TemplateLayout, string>()
{
{ TemplateLayout.Empty, "blank" },
{ TemplateLayout.Focus, "focus" },
{ TemplateLayout.Rows, "rows" },
{ TemplateLayout.Columns, "columns" },
{ TemplateLayout.Grid, "grid" },
{ TemplateLayout.PriorityGrid, "priority-grid" },
});
public const string CustomLayoutJsonTag = "custom";
}
}

View File

@@ -0,0 +1,100 @@
// 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 System.Text.Json;
using static FancyZonesEditorCommon.Data.CustomLayouts;
namespace FancyZonesEditorCommon.Data
{
public class CustomLayouts : EditorData<CustomLayoutListWrapper>
{
public string File
{
get
{
return GetDataFolder() + "\\Microsoft\\PowerToys\\FancyZones\\custom-layouts.json";
}
}
public sealed class CanvasInfoWrapper
{
public struct CanvasZoneWrapper
{
public int X { get; set; }
public int Y { get; set; }
public int Width { get; set; }
public int Height { get; set; }
}
public int RefWidth { get; set; }
public int RefHeight { get; set; }
public List<CanvasZoneWrapper> Zones { get; set; }
public int SensitivityRadius { get; set; } = LayoutDefaultSettings.DefaultSensitivityRadius;
}
public sealed class GridInfoWrapper
{
public int Rows { get; set; }
public int Columns { get; set; }
public List<int> RowsPercentage { get; set; }
public List<int> ColumnsPercentage { get; set; }
public int[][] CellChildMap { get; set; }
public bool ShowSpacing { get; set; } = LayoutDefaultSettings.DefaultShowSpacing;
public int Spacing { get; set; } = LayoutDefaultSettings.DefaultSpacing;
public int SensitivityRadius { get; set; } = LayoutDefaultSettings.DefaultSensitivityRadius;
}
public struct CustomLayoutWrapper
{
public string Uuid { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public JsonElement Info { get; set; } // CanvasInfoWrapper or GridInfoWrapper
}
public struct CustomLayoutListWrapper
{
public List<CustomLayoutWrapper> CustomLayouts { get; set; }
}
public JsonElement ToJsonElement(CanvasInfoWrapper info)
{
string json = JsonSerializer.Serialize(info, this.JsonOptions);
return JsonSerializer.Deserialize<JsonElement>(json);
}
public JsonElement ToJsonElement(GridInfoWrapper info)
{
string json = JsonSerializer.Serialize(info, this.JsonOptions);
return JsonSerializer.Deserialize<JsonElement>(json);
}
public CanvasInfoWrapper CanvasFromJsonElement(string json)
{
return JsonSerializer.Deserialize<CanvasInfoWrapper>(json, this.JsonOptions);
}
public GridInfoWrapper GridFromJsonElement(string json)
{
return JsonSerializer.Deserialize<GridInfoWrapper>(json, this.JsonOptions);
}
}
}

View File

@@ -0,0 +1,47 @@
// 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.DefaultLayouts;
namespace FancyZonesEditorCommon.Data
{
public class DefaultLayouts : EditorData<DefaultLayoutsListWrapper>
{
public string File
{
get
{
return GetDataFolder() + "\\Microsoft\\PowerToys\\FancyZones\\default-layouts.json";
}
}
public struct DefaultLayoutWrapper
{
public struct LayoutWrapper
{
public string Uuid { get; set; }
public string Type { get; set; }
public bool ShowSpacing { get; set; }
public int Spacing { get; set; }
public int ZoneCount { get; set; }
public int SensitivityRadius { get; set; }
}
public string MonitorConfiguration { get; set; }
public LayoutWrapper Layout { get; set; }
}
public struct DefaultLayoutsListWrapper
{
public List<DefaultLayoutWrapper> DefaultLayouts { get; set; }
}
}
}

View File

@@ -0,0 +1,42 @@
// 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.Text.Json;
using FancyZonesEditorCommon.Utils;
namespace FancyZonesEditorCommon.Data
{
public class EditorData<T>
{
public string GetDataFolder()
{
return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
}
protected JsonSerializerOptions JsonOptions
{
get
{
return new JsonSerializerOptions
{
PropertyNamingPolicy = new DashCaseNamingPolicy(),
WriteIndented = true,
};
}
}
public T Read(string file)
{
IOUtils ioUtils = new IOUtils();
string data = ioUtils.ReadFile(file);
return JsonSerializer.Deserialize<T>(data, JsonOptions);
}
public string Serialize(T data)
{
return JsonSerializer.Serialize(data, JsonOptions);
}
}
}

View File

@@ -0,0 +1,89 @@
// 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 System.Globalization;
using System.Text;
namespace FancyZonesEditorCommon.Data
{
public class EditorParameters : EditorData<EditorParameters.ParamsWrapper>
{
public string File
{
get
{
return GetDataFolder() + "\\Microsoft\\PowerToys\\FancyZones\\editor-parameters.json";
}
}
public struct NativeMonitorDataWrapper
{
public string Monitor { get; set; }
public string MonitorInstanceId { get; set; }
public string MonitorSerialNumber { get; set; }
public int MonitorNumber { get; set; }
public string VirtualDesktop { get; set; }
public int Dpi { get; set; }
public int LeftCoordinate { get; set; }
public int TopCoordinate { get; set; }
public int WorkAreaWidth { get; set; }
public int WorkAreaHeight { get; set; }
public int MonitorWidth { get; set; }
public int MonitorHeight { get; set; }
public bool IsSelected { get; set; }
public override string ToString()
{
var sb = new StringBuilder();
// using CultureInfo.InvariantCulture since this is internal data
sb.Append("Monitor: ");
sb.AppendLine(Monitor);
sb.Append("Virtual desktop: ");
sb.AppendLine(VirtualDesktop);
sb.Append("DPI: ");
sb.AppendLine(Dpi.ToString(CultureInfo.InvariantCulture));
sb.Append("X: ");
sb.AppendLine(LeftCoordinate.ToString(CultureInfo.InvariantCulture));
sb.Append("Y: ");
sb.AppendLine(TopCoordinate.ToString(CultureInfo.InvariantCulture));
sb.Append("Width: ");
sb.AppendLine(MonitorWidth.ToString(CultureInfo.InvariantCulture));
sb.Append("Height: ");
sb.AppendLine(MonitorHeight.ToString(CultureInfo.InvariantCulture));
return sb.ToString();
}
}
public struct ParamsWrapper
{
public int ProcessId { get; set; }
public bool SpanZonesAcrossMonitors { get; set; }
public List<NativeMonitorDataWrapper> Monitors { get; set; }
}
public EditorParameters()
: base()
{
}
}
}

View File

@@ -0,0 +1,20 @@
// 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.
namespace FancyZonesEditorCommon.Data
{
public class LayoutDefaultSettings
{
// TODO: share the constants b/w C# Editor and FancyZoneLib
public const bool DefaultShowSpacing = true;
public const int DefaultSpacing = 16;
public const int DefaultZoneCount = 3;
public const int DefaultSensitivityRadius = 20;
public const int MaxZones = 128;
}
}

View File

@@ -0,0 +1,32 @@
// 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.LayoutHotkeys;
namespace FancyZonesEditorCommon.Data
{
public class LayoutHotkeys : EditorData<LayoutHotkeysWrapper>
{
public string File
{
get
{
return GetDataFolder() + "\\Microsoft\\PowerToys\\FancyZones\\layout-hotkeys.json";
}
}
public struct LayoutHotkeyWrapper
{
public int Key { get; set; }
public string LayoutId { get; set; }
}
public struct LayoutHotkeysWrapper
{
public List<LayoutHotkeyWrapper> LayoutHotkeys { get; set; }
}
}
}

View File

@@ -0,0 +1,38 @@
// 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.LayoutTemplates;
namespace FancyZonesEditorCommon.Data
{
public class LayoutTemplates : EditorData<TemplateLayoutsListWrapper>
{
public string File
{
get
{
return GetDataFolder() + "\\Microsoft\\PowerToys\\FancyZones\\layout-templates.json";
}
}
public struct TemplateLayoutWrapper
{
public string Type { get; set; }
public bool ShowSpacing { get; set; }
public int Spacing { get; set; }
public int ZoneCount { get; set; }
public int SensitivityRadius { get; set; }
}
public struct TemplateLayoutsListWrapper
{
public List<TemplateLayoutWrapper> LayoutTemplates { get; set; }
}
}
}

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\Version.props" />
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.20348.0</TargetFramework>
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
<Version>$(Version).0</Version>
<Authors>Microsoft Corporation</Authors>
<Product>PowerToys</Product>
<Description>PowerToys FancyZonesEditorCommon</Description>
<AssemblyName>PowerToys.FancyZonesEditorCommon</AssemblyName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.IO.Abstractions" />
</ItemGroup>
<PropertyGroup>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\FancyZonesEditorCommon\</OutputPath>
</PropertyGroup>
</Project>

View File

@@ -4,9 +4,7 @@
using System.Text.Json;
using FancyZonesEditor.Utils;
namespace FancyZonesEditor
namespace FancyZonesEditorCommon.Utils
{
public class DashCaseNamingPolicy : JsonNamingPolicy
{

View File

@@ -0,0 +1,54 @@
// 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 FancyZonesEditorCommon.Utils
{
public class IOUtils
{
private readonly IFileSystem _fileSystem = new FileSystem();
public IOUtils()
{
}
public void WriteFile(string fileName, string data)
{
_fileSystem.File.WriteAllText(fileName, data);
}
public string ReadFile(string fileName)
{
if (_fileSystem.File.Exists(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;
}
}
}

View File

@@ -4,7 +4,7 @@
using System.Linq;
namespace FancyZonesEditor.Utils
namespace FancyZonesEditorCommon.Utils
{
public static class StringUtils
{

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,33 @@
// 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 Microsoft.FancyZones.UnitTests.Utils;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UITests_FancyZones
{
[TestClass]
public class RunFancyZonesTest
{
private static FancyZonesSession? _session;
[ClassInitialize]
public static void ClassInitialize(TestContext testContext)
{
_session = new FancyZonesSession(testContext);
}
[ClassCleanup]
public static void ClassCleanup()
{
_session?.Close();
}
[TestMethod]
public void RunFancyZones()
{
Assert.IsNotNull(_session?.FancyZonesProcess);
}
}
}

View File

@@ -0,0 +1,30 @@
<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>{FE38FC07-1C05-4B57-ADA3-2FE2F53C6A52}</ProjectGuid>
<RootNamespace>Microsoft.FancyZones.UITests</RootNamespace>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
<Version>$(Version).0</Version>
</PropertyGroup>
<PropertyGroup>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\tests\UITests-FancyZones\</OutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Appium.WebDriver" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,66 @@
// 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.Diagnostics;
using System.IO;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.FancyZones.UnitTests.Utils
{
public class FancyZonesSession
{
private const string FancyZonesPath = @"\..\..\..\PowerToys.FancyZones.exe";
private const string FancyZonesProcessName = "PowerToys.FancyZones";
private bool stopFancyZones = true;
public Process? FancyZonesProcess { get; }
public FancyZonesSession(TestContext testContext)
{
try
{
// Check if FancyZones is already running
Process[] runningFZ = Process.GetProcessesByName(FancyZonesProcessName);
if (runningFZ.Length > 0)
{
FancyZonesProcess = runningFZ[0];
stopFancyZones = false;
}
else
{
// Launch FancyZones
string? path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
path += FancyZonesPath;
ProcessStartInfo info = new ProcessStartInfo(path);
FancyZonesProcess = Process.Start(info);
}
}
catch (Exception ex)
{
testContext.WriteLine(ex.Message);
}
Assert.IsNotNull(FancyZonesProcess, "FancyZones process not started");
}
public void Close()
{
// Close the application
if (FancyZonesProcess != null)
{
if (stopFancyZones)
{
FancyZonesProcess.Kill();
}
FancyZonesProcess.Close();
FancyZonesProcess.Dispose();
}
}
}
}

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++;
}
}
}
}

View File

@@ -69,6 +69,7 @@
<ProjectReference Include="..\..\..\..\common\interop\PowerToys.Interop.vcxproj" />
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\..\common\Common.UI\Common.UI.csproj" />
<ProjectReference Include="..\..\FancyZonesEditorCommon\FancyZonesEditorCommon.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">

View File

@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using FancyZonesEditorCommon.Data;
namespace FancyZonesEditor.Models
{
@@ -99,7 +100,7 @@ namespace FancyZonesEditor.Models
}
}
private bool _showSpacing = LayoutSettings.DefaultShowSpacing;
private bool _showSpacing = LayoutDefaultSettings.DefaultShowSpacing;
// Spacing - free space between cells
public int Spacing
@@ -129,7 +130,7 @@ namespace FancyZonesEditor.Models
get { return 1000; }
}
private int _spacing = LayoutSettings.DefaultSpacing;
private int _spacing = LayoutDefaultSettings.DefaultSpacing;
public GridLayoutModel()
: base()

View File

@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using FancyZonesEditorCommon.Data;
namespace FancyZonesEditor.Models
{
@@ -195,7 +196,7 @@ namespace FancyZonesEditor.Models
}
}
private int _sensitivityRadius = LayoutSettings.DefaultSensitivityRadius;
private int _sensitivityRadius = LayoutDefaultSettings.DefaultSensitivityRadius;
public int SensitivityRadiusMinimum
{
@@ -304,13 +305,13 @@ namespace FancyZonesEditor.Models
}
}
private int _zoneCount = LayoutSettings.DefaultZoneCount;
private int _zoneCount = LayoutDefaultSettings.DefaultZoneCount;
public bool IsZoneAddingAllowed
{
get
{
return TemplateZoneCount < LayoutSettings.MaxZones;
return TemplateZoneCount < LayoutDefaultSettings.MaxZones;
}
}

View File

@@ -3,32 +3,22 @@
// See the LICENSE file in the project root for more information.
using FancyZonesEditor.Models;
using FancyZonesEditorCommon.Data;
namespace FancyZonesEditor
{
public class LayoutSettings
{
// TODO: share the constants b/w C# Editor and FancyZoneLib
public const bool DefaultShowSpacing = true;
public const int DefaultSpacing = 16;
public const int DefaultZoneCount = 3;
public const int DefaultSensitivityRadius = 20;
public const int MaxZones = 128;
public string ZonesetUuid { get; set; } = string.Empty;
public LayoutType Type { get; set; } = LayoutType.PriorityGrid;
public bool ShowSpacing { get; set; } = DefaultShowSpacing;
public bool ShowSpacing { get; set; } = LayoutDefaultSettings.DefaultShowSpacing;
public int Spacing { get; set; } = DefaultSpacing;
public int Spacing { get; set; } = LayoutDefaultSettings.DefaultSpacing;
public int ZoneCount { get; set; } = DefaultZoneCount;
public int ZoneCount { get; set; } = LayoutDefaultSettings.DefaultZoneCount;
public int SensitivityRadius { get; set; } = DefaultSensitivityRadius;
public int SensitivityRadius { get; set; } = LayoutDefaultSettings.DefaultSensitivityRadius;
}
}

View File

@@ -487,6 +487,15 @@ namespace FancyZonesEditor.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to An error occurred while parsing editor parameters..
/// </summary>
public static string Error_Parsing_Editor_Parameters_Message {
get {
return ResourceManager.GetString("Error_Parsing_Editor_Parameters_Message", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to An error occurred while parsing layout hotkeys..
/// </summary>

View File

@@ -435,4 +435,7 @@
<data name="Set_Layout_As_Vertical_Default" xml:space="preserve">
<value>Set layout as a default for vertical monitor orientation</value>
</data>
<data name="Error_Parsing_Editor_Parameters_Message" xml:space="preserve">
<value>An error occurred while parsing editor parameters.</value>
</data>
</root>

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.
namespace FancyZonesEditor.Utils
{
public struct ParsingResult
{
public bool Result { get; }
public string Message { get; }
public string MalformedData { get; }
public ParsingResult(bool result, string message = "", string data = "")
{
Result = result;
Message = message;
MalformedData = data;
}
}
}