Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/actions/spell-check/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2084,6 +2084,8 @@ wifi
wikimedia
wikipedia
winapi
winapp
winappcli
winappsdk
windir
WINDOWCREATED
Expand Down
5 changes: 4 additions & 1 deletion Cpp.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@
<LanguageStandard>stdcpplatest</LanguageStandard>
<BuildStlModules>false</BuildStlModules>
<!-- TODO: _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING for compatibility with VS 17.8. Check if we can remove. -->
<PreprocessorDefinitions>_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<!-- TODO: _SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS for VS 2026 (MSVC 14.51+). The STL turned
<experimental/coroutine> into a hard error (STL1011), and C++/WinRT's base.h still falls back to it when
__cpp_lib_coroutine isn't defined at include time. Remove once C++/WinRT no longer references the experimental header. -->
<PreprocessorDefinitions>_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;_SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<!-- CLR + CFG are not compatible >:{ -->
<ControlFlowGuard Condition="'$(CLRSupport)' == ''">Guard</ControlFlowGuard>
<DebugInformationFormat Condition="'%(ControlFlowGuard)' == 'Guard'">ProgramDatabase</DebugInformationFormat>
Expand Down
30 changes: 21 additions & 9 deletions PowerToys.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/common/Common.UI/Common.UI.csproj">
<Project Path="src/common/Common.UI.Controls/Common.UI.Controls.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/common/Common.UI.Controls/Common.UI.Controls.csproj">
<Project Path="src/common/Common.UI/Common.UI.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
Expand Down Expand Up @@ -54,10 +54,14 @@
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/common/UITestAutomation.Next/UITestAutomation.Next.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/common/UnitTests-CommonLib/UnitTests-CommonLib.vcxproj" Id="1a066c63-64b3-45f8-92fe-664e1cce8077" />
<Project Path="src/common/UnitTests-CommonUtils/UnitTests-CommonUtils.vcxproj" Id="8b5cfb38-ccba-40a8-ad7a-89c57b070884" />
<Project Path="src/common/updating/updating.vcxproj" Id="17da04df-e393-4397-9cf0-84dabe11032e" />
<Project Path="src/common/updating/UnitTests/UpdatingUnitTests.vcxproj" Id="a1b2c3d4-e5f6-7890-abcd-ef1234567890" />
<Project Path="src/common/updating/updating.vcxproj" Id="17da04df-e393-4397-9cf0-84dabe11032e" />
<Project Path="src/common/version/version.vcxproj" Id="cc6e41ac-8174-4e8a-8d22-85dd7f4851df" />
</Folder>
<Folder Name="/common/interop/">
Expand Down Expand Up @@ -190,6 +194,10 @@
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/colorPicker/ColorPicker.UITests/ColorPicker.UITests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/CommandPalette/">
<Project Path="src/modules/cmdpal/CmdPalKeyboardService/CmdPalKeyboardService.vcxproj" Id="5f63c743-f6ce-4dba-a200-2b3f8a14e8c2" />
Expand All @@ -200,11 +208,11 @@
</Project>
</Folder>
<Folder Name="/modules/CommandPalette/Built-in Extensions/">
<Project Path="src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Microsoft.CmdPal.Ext.Apps.csproj">
<Project Path="src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Actions/Microsoft.CmdPal.Ext.Actions.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Actions/Microsoft.CmdPal.Ext.Actions.csproj">
<Project Path="src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Microsoft.CmdPal.Ext.Apps.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
Expand Down Expand Up @@ -715,11 +723,11 @@
</Project>
</Folder>
<Folder Name="/modules/PowerDisplay/">
<Project Path="src/modules/powerdisplay/PowerDisplay.Models/PowerDisplay.Models.csproj">
<Project Path="src/modules/powerdisplay/PowerDisplay.Lib/PowerDisplay.Lib.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/powerdisplay/PowerDisplay.Lib/PowerDisplay.Lib.csproj">
<Project Path="src/modules/powerdisplay/PowerDisplay.Models/PowerDisplay.Models.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
Expand Down Expand Up @@ -1088,6 +1096,10 @@
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/settings-ui/Settings.UITests/Settings.UITests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/Solution Items/">
<File Path=".vsconfig" />
Expand Down Expand Up @@ -1119,14 +1131,14 @@
<BuildDependency Project="src/modules/fancyzones/editor/FancyZonesEditor/FancyZonesEditor.csproj" />
<BuildDependency Project="src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj" />
<BuildDependency Project="src/modules/fancyzones/FancyZonesModuleInterface/FancyZonesModuleInterface.vcxproj" />
<BuildDependency Project="src/modules/GrabAndMove/GrabAndMove/GrabAndMove.vcxproj" />
<BuildDependency Project="src/modules/GrabAndMove/GrabAndMoveModuleInterface/GrabAndMoveModuleInterface.vcxproj" />
<BuildDependency Project="src/modules/imageresizer/dll/ImageResizerExt.vcxproj" />
<BuildDependency Project="src/modules/imageresizer/ui/ImageResizerUI.csproj" />
<BuildDependency Project="src/modules/keyboardmanager/dll/KeyboardManager.vcxproj" />
<BuildDependency Project="src/modules/launcher/Microsoft.Launcher/Microsoft.Launcher.vcxproj" />
<BuildDependency Project="src/modules/LightSwitch/LightSwitchModuleInterface/LightSwitchModuleInterface.vcxproj" />
<BuildDependency Project="src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj" />
<BuildDependency Project="src/modules/GrabAndMove/GrabAndMoveModuleInterface/GrabAndMoveModuleInterface.vcxproj" />
<BuildDependency Project="src/modules/GrabAndMove/GrabAndMove/GrabAndMove.vcxproj" />
<BuildDependency Project="src/modules/powerrename/dll/PowerRenameExt.vcxproj" />
<BuildDependency Project="src/modules/powerrename/lib/PowerRenameLib.vcxproj" />
<BuildDependency Project="src/modules/previewpane/Common/PreviewHandlerCommon.csproj" />
Expand Down
49 changes: 49 additions & 0 deletions src/common/UITestAutomation.Next/By.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// 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 Microsoft.PowerToys.UITest.Next;

/// <summary>
/// Selector used to locate elements via winappcli. winappcli has its own selector grammar
/// (semantic slugs, plain text search) so this type maps onto the CLI's argument shape
/// rather than mimicking Selenium's <c>By</c>.
/// </summary>
public sealed class By
{
public enum Kind
{
/// <summary>Plain-text search against Name or AutomationId (case-insensitive substring).</summary>
Text,

/// <summary>Stable AutomationId, when the developer set one.</summary>
AutomationId,

/// <summary>A semantic slug (e.g., <c>btn-close-d1a0</c>) printed by <c>inspect</c>/<c>search</c>.</summary>
Slug,
}

public Kind Selector { get; }

public string Value { get; }

private By(Kind kind, string value)
{
Selector = kind;
Value = value;
}

/// <summary>Plain-text search; what you'd type into <c>winapp ui search "&lt;text&gt;"</c>.</summary>
public static By Name(string name) => new(Kind.Text, name);

/// <summary>Look up by stable AutomationId (winappcli also accepts these as selectors).</summary>
public static By AccessibilityId(string id) => new(Kind.AutomationId, id);

/// <inheritdoc cref="AccessibilityId(string)"/>
public static By Id(string id) => new(Kind.AutomationId, id);

/// <summary>Direct slug selector (e.g., <c>btn-colorpicker-b415</c>) as printed by inspect/search.</summary>
public static By Slug(string slug) => new(Kind.Slug, slug);

public override string ToString() => $"{Selector}={Value}";
}
75 changes: 75 additions & 0 deletions src/common/UITestAutomation.Next/ClipboardHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// 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 FormsClipboard = System.Windows.Forms.Clipboard;

namespace Microsoft.PowerToys.UITest.Next;

/// <summary>
/// Clipboard helpers that always execute on an STA thread (<see cref="FormsClipboard"/>
/// requires it). Tolerant — every method swallows clipboard errors and returns a default,
/// so callers can use them in test <c>finally</c> blocks without worrying about masking
/// the real failure.
/// </summary>
public static class ClipboardHelper
{
/// <summary>Return the current clipboard text, or <see cref="string.Empty"/> if none / on error.</summary>
public static string GetText() => RunSTA(() => FormsClipboard.ContainsText() ? FormsClipboard.GetText() : string.Empty) ?? string.Empty;

/// <summary>Clear the clipboard. Returns true on success, false on error.</summary>
public static bool Clear() => RunSTA(() => { FormsClipboard.Clear(); return true; });

/// <summary>Set the clipboard text. Returns true on success, false on error.</summary>
public static bool SetText(string value) => RunSTA(() => { FormsClipboard.SetText(value); return true; });

/// <summary>
/// Poll the clipboard up to <paramref name="timeoutMS"/> for the first non-empty text
/// different from <paramref name="ignoredValue"/>. Returns <see cref="string.Empty"/> on
/// timeout. Use when you've just cleared the clipboard and are waiting for an external
/// app (e.g. ColorPicker on click) to write into it.
/// </summary>
public static string WaitForText(string ignoredValue = "", int timeoutMS = 3_000, int pollIntervalMS = 100)
{
var deadline = DateTime.UtcNow + TimeSpan.FromMilliseconds(timeoutMS);
while (DateTime.UtcNow < deadline)
{
var text = GetText();
if (!string.IsNullOrEmpty(text) && text != ignoredValue)
{
return text;
}

Thread.Sleep(pollIntervalMS);
}

return string.Empty;
}

private static T? RunSTA<T>(Func<T> body)
{
T? result = default;
try
{
var thread = new Thread(() =>
{
try
{
result = body();
}
catch
{
// Best effort — clipboard can throw under contention (OpenClipboard failures).
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join(TimeSpan.FromSeconds(5));
}
catch
{
}

return result;
}
}
13 changes: 13 additions & 0 deletions src/common/UITestAutomation.Next/Element/Button.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// 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 Microsoft.PowerToys.UITest.Next;

public class Button : Element
{
public Button()
{
TargetControlType = "Button";
}
}
31 changes: 31 additions & 0 deletions src/common/UITestAutomation.Next/Element/CheckBox.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// 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 Microsoft.PowerToys.UITest.Next;

/// <summary>
/// WinUI/WPF <c>CheckBox</c> (UIA ControlType <c>CheckBox</c>). State is read via
/// <c>winapp ui get-property ToggleState</c> and changed via <c>winapp ui invoke</c>.
/// </summary>
public class CheckBox : Element
{
public CheckBox()
{
TargetControlType = "CheckBox";
}

/// <summary>True when UIA <c>ToggleState</c> is <c>On</c> (<c>Indeterminate</c> reads as not-checked).</summary>
public bool IsChecked => string.Equals(GetProperty("ToggleState"), "On", StringComparison.OrdinalIgnoreCase);

/// <summary>Flip to <paramref name="value"/> only if currently different.</summary>
public CheckBox SetCheck(bool value = true)
{
if (IsChecked != value)
{
Click();
}

return this;
}
}
52 changes: 52 additions & 0 deletions src/common/UITestAutomation.Next/Element/ComboBox.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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 Microsoft.PowerToys.UITest.Next;

/// <summary>
/// WinUI/WPF <c>ComboBox</c> (UIA ControlType <c>ComboBox</c>). Selection is driven CLI-first:
/// <see cref="Select"/> expands via <c>winapp ui invoke</c> then clicks the chosen item, while
/// editable combo boxes can be set directly with <see cref="SelectByText"/>
/// (<c>winapp ui set-value</c>).
/// </summary>
/// <remarks>
/// The dropdown items live in a popup that the owning process surfaces as a separate window
/// (e.g. Settings' <c>PopupHost</c>). Process-scoped sessions (<see cref="Session.FromProcess"/>)
/// see those items because every search re-resolves via <c>-a</c>; a window-scoped (<c>-w</c>)
/// session may not, in which case prefer <see cref="SelectByText"/>.
/// </remarks>
public class ComboBox : Element
{
public ComboBox()
{
TargetControlType = "ComboBox";
}

/// <summary>Currently selected item text via <c>winapp ui get-value</c> (SelectionPattern fallback).</summary>
public string SelectedText => GetValue();

/// <summary>
/// Expand the combo box (CLI <c>invoke</c> toggles ExpandCollapse) and click the item whose
/// Name matches <paramref name="itemName"/>.
/// </summary>
public ComboBox Select(string itemName, int timeoutMS = 5000)
{
EnsureBound();
Click();
Thread.Sleep(150);
Owner!.Find<Element>(By.Name(itemName), timeoutMS).Click();
return this;
}

/// <summary>
/// Set the combo box value directly via <c>winapp ui set-value</c> (UIA ValuePattern). Works
/// for editable combo boxes; for non-editable combos use <see cref="Select"/>.
/// </summary>
public ComboBox SelectByText(string text)
{
EnsureBound();
WinappCli.InvokeAssertSuccess("ui", "set-value", Selector, text, Owner!.TargetFlag, Owner!.TargetValue);
return this;
}
}
17 changes: 17 additions & 0 deletions src/common/UITestAutomation.Next/Element/Custom.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// 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 Microsoft.PowerToys.UITest.Next;

/// <summary>
/// Custom control (UIA ControlType <c>Custom</c>) — used by bespoke surfaces like FancyZones
/// zones and Workspaces canvases. Inherits drag from <see cref="Element"/>.
/// </summary>
public class Custom : Element
{
public Custom()
{
TargetControlType = "Custom";
}
}
Loading
Loading