diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 16fe478b2d16..b1023457ae34 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -2075,6 +2075,8 @@ wifi wikimedia wikipedia winapi +winapp +winappcli winappsdk windir WINDOWCREATED diff --git a/.github/skills/ui-tests-migration/LICENSE.txt b/.github/skills/ui-tests-migration/LICENSE.txt new file mode 100644 index 000000000000..c9766a251fed --- /dev/null +++ b/.github/skills/ui-tests-migration/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 Microsoft Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/.github/skills/ui-tests-migration/SKILL.md b/.github/skills/ui-tests-migration/SKILL.md new file mode 100644 index 000000000000..4a801070dd36 --- /dev/null +++ b/.github/skills/ui-tests-migration/SKILL.md @@ -0,0 +1,192 @@ +--- +name: ui-tests-migration +description: "Migrate PowerToys module UI tests from the legacy WinAppDriver/Selenium harness (Microsoft.PowerToys.UITest) to the new winappcli-based harness (Microsoft.PowerToys.UITest.Next). Use when asked to port/convert/rewrite/modernize a module's UI tests to the .Next framework, create a new [Module].UITests.Next project alongside existing legacy tests, or stand up brand-new winappcli UI tests for a module that has none by reading its human test sign-off markdown. Covers the API mapping (By/Element/Session/UITestBase, KeyboardHelper/MouseHelper/ClipboardHelper), project/csproj scaffolding, naming rules, common PowerToys test recipes (toggle a module, read an activation shortcut, fire a global hotkey, inspect the clipboard, discover overlay/editor windows), and build/run validation. Keywords: UI test, UITests, UITestAutomation, UITestAutomation.Next, winappcli, winapp.exe, WinAppDriver, Selenium, Appium, migrate, port, convert, modernize, .Next, end-to-end, E2E, MSTest." +license: Complete terms in LICENSE.txt +--- + +# PowerToys UI-Tests Migration (legacy → `.Next`) + +Convert a PowerToys module's UI tests from the legacy **WinAppDriver / Selenium / Appium** harness +(`Microsoft.PowerToys.UITest`, in `src/common/UITestAutomation/`) to the new **winappcli** harness +(`Microsoft.PowerToys.UITest.Next`, in `src/common/UITestAutomation.Next/`). + +The new harness shells out to `winapp.exe` and parses its JSON — **no WinAppDriver server on :4723, +no Selenium/Appium NuGet packages, no `WindowsElement`/`WindowsDriver`.** The public *shape* +(`UITestBase`, `Session`, `Find`, `By`, element wrappers like `ToggleSwitch`) is deliberately +similar, so most of the work is mechanical API mapping plus reworking a few patterns that don't +translate one-to-one (XPath selectors, stateful elements, instance mouse/keyboard helpers). + +## When to use this skill + +Use this skill when the task is to: + +- **Port** a module's existing legacy UI tests to `.Next` (e.g. "migrate the ScreenRuler UI tests to + the new framework", "convert FancyZones.UITests to winappcli"). +- **Create a new** `[Module].UITests.Next` project that re-implements the legacy tests with the new + harness, leaving the old project in place. +- **Stand up brand-new** `.Next` UI tests for a module that has **no** UI tests at all, by reading the + module's human test **sign-off markdown** (e.g. `ColorPickerUITest.md`) and turning each manual + checklist item into an automated test. + +This skill is the *how*: the framework differences, the API mapping, the project scaffolding, the +naming rules, the recurring PowerToys test recipes, and the build/validate loop. The *what* (which +module, which tests) comes from the calling prompt. + +> **Reference implementation — read these working examples before porting anything.** They are +> the ground truth for "what good looks like" with each harness: +> - **New (`.Next`)**: [ColorPickerEndToEndTests.cs](../../../src/modules/colorPicker/ColorPicker.UITests/ColorPickerEndToEndTests.cs) +> — full end-to-end scenario (navigate Settings → toggle module → read shortcut → fire hotkey → +> read overlay → click-capture → inspect editor), driven entirely through `winappcli`. +> - **Legacy**: [TestSpacing.cs](../../../src/modules/MeasureTool/Tests/ScreenRuler.UITests/TestSpacing.cs) +> + [TestHelper.cs](../../../src/modules/MeasureTool/Tests/ScreenRuler.UITests/TestHelper.cs) +> — a `UITestBase` subclass plus a static helper that navigates, toggles, reads the shortcut, fires +> the hotkey, and validates the clipboard. +> - **Worked Scenario-A port (validated 5/5, where the legacy suite scored 0/5 locally)**: the +> ScreenRuler suite ported from the legacy project above lives in +> [ScreenRuler.UITests.Next/TestHelper.cs](../../../src/modules/MeasureTool/Tests/ScreenRuler.UITests.Next/TestHelper.cs) +> + 5 test classes. It is the canonical port reference — cross-window toolbar discovery via +> `Session.FromProcess`, a DPI-aware `app.manifest`, cursor centering, and patient hotkey +> activation are all there because real runs needed them (see +> [references/patterns-and-pitfalls.md](references/patterns-and-pitfalls.md)). + +## Required reads (in order) + +1. **This `SKILL.md`** — the decision tree (which scenario), the naming rules, the high-level + workflow, and the build/validate loop. +2. **[references/framework-differences.md](references/framework-differences.md)** — the conceptual + deltas you MUST internalize before writing code: winappcli engine, stateless elements, selector + grammar (no XPath/CssSelector), session scopes (window vs process), lifecycle/hygiene/module + pre-enablement, multi-window discovery, and what the new harness does NOT (yet) provide. +3. **[references/api-mapping.md](references/api-mapping.md)** — the line-by-line cheat sheet: + namespaces, `By`, `Element` actions/properties, `Session`, `UITestBase`, the static + Keyboard/Mouse/Clipboard helpers, and the element-wrapper catalog. Keep this open while editing. +4. **[references/project-setup.md](references/project-setup.md)** — csproj scaffold, naming/placement + rules, `.slnx` registration, and how to build & run a `.Next` project. Uses the + [templates/](templates/) starter files. +5. **[references/porting-workflow.md](references/porting-workflow.md)** — the two end-to-end + playbooks: **A)** port existing legacy tests, and **B)** author tests from a human sign-off + markdown when none exist. +6. **[references/patterns-and-pitfalls.md](references/patterns-and-pitfalls.md)** — adaptable recipes + for the recurring PowerToys patterns (toggle a module + verify its process, read the activation + shortcut from a `ShortcutControl`, fire a global hotkey reliably, inspect the clipboard, discover + overlay/editor windows) and the gotchas that bite during migration. + +## Pick your scenario + +```mermaid +flowchart TD + A[Module to migrate] --> B{Does a legacy
UITests project exist?} + B -- Yes --> C["Scenario A: PORT
Create [Module].UITests.Next
Re-implement each legacy test"] + B -- No --> D{Is there a human test
sign-off .md?} + D -- Yes --> E["Scenario B: GREENFIELD
Create [Module].UITests
Turn each checklist item into a test"] + D -- No --> F[Ask the user for the
test spec / sign-off doc] +``` + +| Scenario | Trigger | New project name | Source of test cases | +|---|---|---|---| +| **A — Port** | A legacy `[Module].UITests` (or similar) project already exists and references `UITestAutomation.csproj` | **`[Module].UITests.Next`** — keep the `.Next` suffix so it lives **alongside** the legacy project | The existing legacy test methods (1:1 re-implementation) | +| **B — Greenfield** | The module has **no** UI tests at all | **`[Module].UITests`** — **drop** the `.Next` suffix; there's nothing to live alongside | The module's human sign-off markdown (manual checklist), e.g. `ColorPickerUITest.md` | + +Place the new project under **`src/modules/[Module]/Tests/[Module].UITests.Next/`** (or +`…/Tests/[Module].UITests/` for Scenario B). If the module already keeps tests in a different +`Tests/` layout, match the module's existing convention rather than forcing this one — see +[references/project-setup.md](references/project-setup.md). + +> **Keep it abstract.** Every PowerToys module is unique and the legacy tests were written by +> different people in different styles. Treat the recipes in this skill as *adaptable patterns*, not +> a rigid script. Re-create the **intent and assertions** of each test; do not mechanically translate +> brittle, harness-specific scaffolding (Selenium `Actions`, XPath walks, manual driver attaches) when +> the new harness has a cleaner idiom. + +## High-level workflow + +Create a TODO list and work top-to-bottom. Each step links to the reference that drives it. + +```markdown +- [ ] 1. Identify the module + scenario (A port / B greenfield) — this SKILL.md "Pick your scenario" +- [ ] 2. Read the two reference examples (ColorPicker .Next + ScreenRuler legacy) end-to-end +- [ ] 3. Inventory the source: + • Scenario A → list every [TestMethod] + shared helper in the legacy project + • Scenario B → read the module's sign-off .md; list each manual checklist item + — references/porting-workflow.md +- [ ] 4. Internalize the deltas — references/framework-differences.md +- [ ] 5. Scaffold the new project (csproj from template, name per the table, register in .slnx) + — references/project-setup.md +- [ ] 6. Re-implement tests, mapping each API as you go — references/api-mapping.md + + recipes from references/patterns-and-pitfalls.md +- [ ] 7. Build the new project to exit code 0 — this SKILL.md "Build & validate" +- [ ] 8. (If a live desktop is available) run the tests; otherwise report that they build and are + ready to run, and summarize coverage vs. the source +``` + +## Build & validate + +The `.Next` harness needs `winapp.exe` only at **run** time, not build time — the project has zero +managed dependency on the engine. So you can always compile-verify a migration even on an agent with +no winappcli installed. + +```pwsh +# 0. FIRST build of a brand-new project: restore so the assets file exists, otherwise the build +# fails with NETSDK1004 "Assets file ... project.assets.json not found". +dotnet restore src\modules\\Tests\.UITests.Next\.UITests.Next.csproj -p:Platform=x64 +# (Equivalently, run tools\build\build-essentials.cmd once at the start of the session.) + +# 1. Build just the new test project (fast inner loop). Prefer the repo build script. +tools\build\build.cmd -Path src\modules\\Tests\.UITests.Next -Platform x64 -Configuration Debug +# Exit code 0 = success; non-zero = failure. On failure read the errors log next to the project: +# build...errors.log + +# 2. Run (needs a live desktop). A .Next project is a Microsoft.Testing.Platform Exe — run the +# produced exe directly with a TRX report; filter to one test/category for a tight loop. +$exe = "\x64\Debug\tests\.UITests.Next\net10.0-windows10.0.26100.0\.UITests.Next.exe" +& $exe --filter "TestCategory=" --report-trx --report-trx-filename run.trx --results-directory +# --filter accepts "TestCategory=X" or "FullyQualifiedName~Y"; omit it to run everything. +# Exit 0 = all passed. Parse the .trx for per-test outcomes + failure messages. +``` + +- **Run it in a loop: write → build → run → diagnose → repeat.** UI tests surface environment-real + failures (DPI scaling, cursor position, hotkey-arming races) that only a live run reveals. Start + with one deterministic test (e.g. the activation/toggle test), get it green, then widen. +- **First, run the *legacy* suite once for a baseline — and run it ELEVATED.** The legacy harness + launches PowerToys via `ProcessStartInfo { Verb = "runas" }` (elevated), so a **non-elevated** test + host can't complete the launch and **every test fails at startup with a misleading `Win32Exception` + cascade** — a false 0/N that looks like "the tests are broken" but is purely the run method. (That's + why VS Test Explorer passes them: VS runs as admin.) Run from an **elevated** terminal: start + `WinAppDriver.exe` on `127.0.0.1:4723`, then run the built DLL with `vstest.console.exe` (see + [references/porting-workflow.md](references/porting-workflow.md) §A0 for the `-Verb RunAs` recipe). + A measurement failure on a scaled (non-100%) display is usually a pre-existing DPI issue (Pitfall + 12), not something the port must reproduce — the ScreenRuler legacy suite scores **4/5** elevated + here (Bounds fails at 150% scale) while the `.Next` port scores **5/5**. `.Next` tests themselves + need **no** elevation (the new harness launches the runner non-elevated). +- **Always** build to exit code 0 before declaring the migration done. Fix every compile error — do + not leave `// TODO: port this` stubs that break the build. +- Running the tests requires a **live interactive desktop** plus `winapp.exe` + (`winget install Microsoft.winappcli`, or set `WINAPP_CLI_PATH`). The whole PowerToys runner is + launched by the harness (`PowerToys.exe --open-settings`) — you should see the Settings window + appear. If the environment has no desktop (headless agent), state that the project **builds clean + and is ready to run**, and list which source tests/checklist items each new `[TestMethod]` covers. +- New `.csproj` files under `src/` MUST `` + right after `` (CI audits this). The template already does. + +## What NOT to do + +- **Do NOT delete or edit the legacy `[Module].UITests` project** in Scenario A. The `.Next` project + lives alongside it; removing the old one is a separate, explicit decision for the maintainers. +- **Do NOT touch product code.** This is a test-only migration. If a test needs a UIA hook that + doesn't exist (e.g. an `AutomationId` or a hidden automation-peer TextBlock), flag it for the user + rather than silently editing the module. (The ColorPicker example's `ColorHexAutomationPeer` hook + is a documented, pre-existing exception — see its class remarks.) +- **Do NOT port the legacy plumbing literally.** No Selenium `Actions`, no `WindowsDriver`/`WindowsElement`, + no `By.XPath`/`By.CssSelector`, no `:4723`. Map them to the winappcli idioms in + [references/api-mapping.md](references/api-mapping.md). +- **Do NOT add a `ProjectReference` to `UITestAutomation.csproj`** (the legacy harness) — reference + **`UITestAutomation.Next.csproj`** only. +- **Do NOT invent assertions** for a vague sign-off item. If a checklist line has no observable + pass/fail signal, implement what you can and leave a clearly-marked `TestContext.WriteLine` note + (or skip with an explanation) rather than asserting on something you can't actually read. +- **Do NOT introduce new third-party NuGet dependencies.** The `.Next` harness is intentionally + dependency-free (MSTest only). Use the Win32-based helpers it already ships. + +## What is NICE to do + +- **Improve the new UT Test framework if you see such opportunity**. The new framework works only with a few modules and may lack something other requires. If you see the old test uses something that we don't have in a new framework and it's handy, don't hesiate to port it to a new one. Or you may see the test uses a bunch of extra helpers ouside of test framework, which also may be a signal. \ No newline at end of file diff --git a/.github/skills/ui-tests-migration/references/api-mapping.md b/.github/skills/ui-tests-migration/references/api-mapping.md new file mode 100644 index 000000000000..41b61dfbb559 --- /dev/null +++ b/.github/skills/ui-tests-migration/references/api-mapping.md @@ -0,0 +1,171 @@ +# API mapping cheat sheet (legacy → `.Next`) + +Keep this open while editing. Left column is the legacy `Microsoft.PowerToys.UITest` API; right column +is the `Microsoft.PowerToys.UITest.Next` equivalent. "—" means no direct member; see the Notes. + +## Namespaces & usings + +| Legacy | `.Next` | +|---|---| +| `using Microsoft.PowerToys.UITest;` | `using Microsoft.PowerToys.UITest.Next;` | +| `using OpenQA.Selenium;` / `…Appium…` | *(delete — no Selenium/Appium)* | +| `[TestClass] : UITestBase` | `[TestClass] : UITestBase` *(same shape; different namespace)* | +| `using Microsoft.VisualStudio.TestTools.UnitTesting;` | *(unchanged)* | + +## `UITestBase` (the base class) + +| Legacy | `.Next` | Notes | +|---|---|---| +| `: base(PowerToysModule.PowerToysSettings)` | `: base(PowerToysModule.PowerToysSettings)` | Same enum name; **values differ** — see enum table below. | +| `: base(scope, WindowSize.Large)` | `: base(scope, WindowSize.Large)` | Same `WindowSize` enum. | +| `: base(scope, size, commandLineArgs: new[]{…})` | `: base(scope, size, enableModules: new[]{…})` | 3rd arg changed from launch args to a deterministic module-enable list. | +| `Session` (property) | `Session` (property) | Same name. Legacy is `required set`; `.Next` is `private set` (assigned by `TestInit`). | +| `Find(by, timeoutMS, global)` | `Find(by, timeoutMS)` | No `global` param (see framework-differences §4). | +| `Find(name)` / `Find(name)` | `Find(name)` / `Find(name)` | Same. | +| `Has/HasOne(by, …, global)` | `Has/HasOne(by, …)` | No `global`. | +| `FindByPartialName(s)` | `Find(By.Name(s))` | winappcli `By.Name` is already a substring match. | +| `FindByPattern(regex)` | `Session.FindAll(By.Name(...))` + C# `Regex` | No base helper; filter in C#. | +| `FindByClassName(c)` | `Find(By.Name(...))` with a typed wrapper | Wrappers pin ClassName; or `FindAll` + filter on `.ClassName`. | +| `SendKeys(Key[])` / `SendKeySequence(Key[])` | `KeyboardHelper.SendKeys(Key[])` | Static helper (also `Session.SendKeys` passthrough). | +| `MoveMouseTo(x,y)` | `MouseHelper.MoveTo(x,y)` | Static helper. | +| `GetMousePosition()` → `(int,int)` | `MouseHelper.GetMousePosition()` → `(int X,int Y)` | Static helper. | +| `IsWindowOpen(name)` | `WindowsFinder.ListByApp(proc).Count > 0` | Or `SessionHelper.IsRunning(scope)` for a process check. | +| `RestartScopeExe(enableModules?)` | `RestartScope(enableModules?)` | Returns the fresh `Session`. | +| `ExitScopeExe()` | *(automatic)* `sessionHelper.StopIfStarted()` in `TestCleanup` | Rarely needed manually. | + +## `PowerToysModule` enum (values differ!) + +| Legacy value | `.Next` value | Notes | +|---|---|---| +| `PowerToysSettings` | `PowerToysSettings` | Same. The default; drive most modules through it. | +| `FancyZone` | `FancyZonesEditor` | **Renamed.** | +| `Hosts` | `Hosts` | Same. | +| `Runner` | `Runner` | Same. | +| `Workspaces` | `Workspaces` | Same. | +| `PowerRename` | `PowerRename` | Same. | +| `CommandPalette` | `CommandPalette` | Same. | +| `ScreenRuler` | `ScreenRuler` | Same. | +| `LightSwitch` | `LightSwitch` | Same. | +| *(n/a)* | `ColorPicker` | New entry (overlay module — drive via the Settings scope). | + +## `By` selectors + +| Legacy | `.Next` | Notes | +|---|---|---| +| `By.Name("x")` | `By.Name("x")` | winappcli = case-insensitive **substring** over Name/AutomationId. | +| `By.AccessibilityId("Id")` | `By.AccessibilityId("Id")` | **Preferred.** Also `By.Id("Id")`. | +| `By.Id("Id")` | `By.Id("Id")` / `By.AccessibilityId("Id")` | Same intent. | +| `By.ClassName("C")` | *(none)* | Use a typed wrapper, or `FindAll` + filter on `.ClassName`. | +| `By.XPath("//*[contains(@Name,'x')]")` | `By.Name("x")` | Substring search covers `contains(@Name)`. | +| `By.XPath("//*[@Name='x']")` | `By.Name("x")` (+ C# exact filter if needed) | | +| `By.XPath` (structural axes) | scoped `element.Find(By.…)` or `FindAll` + C# filter | No XPath engine. | +| `By.CssSelector(...)` | *(none)* | Re-express as above. | +| *(n/a)* | `By.Slug("btn-x-1a2b")` | Direct slug from `inspect`/`search` output. | + +## `Element` — properties + +| Legacy | `.Next` | Notes | +|---|---|---| +| `Name` | `Name` | `.Next` is cached at Find time; re-find for fresh. | +| `ClassName` | `ClassName` | Cached. | +| `ControlType` | `ControlType` | Cached. | +| `Text` | `GetValue()` | TextPattern→ValuePattern→Selection→Name fallback. | +| `Enabled` | `IsEnabled` | Live read via `get-property`. | +| `Displayed` | `Displayed` (== `!IsOffscreen`) | Live read. | +| `Selected` | `Selected` | Live read (`IsSelected`). | +| `AutomationId` | `AutomationId` | Live read. | +| `HelpText` | `HelpText` | Live read (used for `ShortcutControl` text). | +| `Rect` → `Rectangle?` | `X`, `Y`, `Width`, `Height` (ints) | Cached snapshot; re-find if UI moved. | +| `GetAttribute("P")` | `GetAttribute("P")` / `GetProperty("P")` | Both live-read one UIA property. | + +## `Element` — actions + +| Legacy | `.Next` | Notes | +|---|---|---| +| `Click(rightClick=false, msPreAction=500, msPostAction=500)` | `Click(rightClick=false, msPostAction=200)` | **No `msPreAction`.** Uses UIA invoke (falls back to toggle/select/expand); `rightClick` → `click --right`. Add an explicit `Thread.Sleep` before if you relied on `msPreAction`. | +| `Click()` on a non-invokable element (TextBlock/ListItem) | `MouseClick(msPostAction=200)` | Real mouse simulation — use when the click is handled by an ancestor (the ColorPicker utility-stack label pattern). | +| `DoubleClick()` | `DoubleClick(msPostAction=200)` | Real mouse double-click. | +| Selenium `Actions` drag | `Drag(offsetX, offsetY, steps=10)` / `DragTo(target)` | Win32 mouse; uses cached center. | +| `Actions` key-down + drag | `KeyDownAndDrag(key, targetX, targetY, steps)` | Modifier-drag (FancyZones merge, tab tear-off). | +| `ReleaseKey(key)` | `KeyboardHelper.ReleaseKey(key)` | | +| `SetText`/`Clear`+`SendKeys` (TextBox) | `TextBox.SetText("v")` | `winapp ui set-value`. | +| `element.Find(by)` | `element.Find(by)` | Scoped search under the element. | +| `ScrollIntoView()` | `ScrollIntoView()` | Same. | +| — | `Scroll(ScrollDirection)`, `ScrollToEdge(toBottom)` | New scroll verbs. | +| — | `Focus()` | `winapp ui focus`. | +| — | `WaitForProperty(p, v, t)`, `WaitForValue(v, contains, t)`, `WaitForGone(t)` | Built-in waits (replace manual poll loops). | + +## `Session` + +| Legacy | `.Next` | Notes | +|---|---|---| +| `Find(by, t, global)` / `Find(name)` | `Find(by, t)` / `Find(name)` | No `global`. | +| `FindAll(by, t, global)` | `FindAll(by, t)` | No `global`; polls until found or timeout. | +| `Has`/`HasOne`/`Has` | `Has`/`HasOne`/`Has` | Same intent. | +| `Attach(PowerToysModule)` / `Attach(windowName)` | `Session.Attach(module, size?)` / `Session.FromProcess(app)` / `WindowsFinder.WaitForWindowByApp(...)` | Re-bind to another window/process. | +| `SendKeys(Key[])` / `SendKey(key, …)` | `Session.SendKeys(Key[])` or `KeyboardHelper.SendKeys(Key[])` | Prefer the static helper. | +| `MoveMouseTo(x,y, …)` | `MouseHelper.MoveTo(x,y)` | Static. | +| `PerformMouseAction(MouseActionType.LeftClick)` | `MouseHelper.LeftClick()` | See action map below. | +| `SetMainWindowSize(size)` | `WindowHelper.SetWindowSize(hwnd, size)` | `hwnd = new IntPtr(Session.WindowHandle)`. | +| `MainWindowHandler` (`IntPtr`) | `WindowHandle` (`long`) / `WindowHandleArg` (string) | | +| — | `Inspect(depth, interactive, …)` → `JsonElement` | `winapp ui inspect --json` tree (the ColorPicker editor walk). | +| — | `WaitForElement(by, t)`, `WaitFor(Func, t)` | Built-in waits. | +| — | `Screenshot(path, element?, captureScreen?)` / `TryScreenshot(...)` | | + +### `MouseActionType` → `MouseHelper` + +| Legacy `PerformMouseAction(...)` | `.Next` | +|---|---| +| `MouseActionType.LeftClick` | `MouseHelper.LeftClick()` | +| `MouseActionType.RightClick` | `MouseHelper.RightClick()` | +| `MouseActionType.LeftDown` / `LeftUp` | `MouseHelper.LeftDown()` / `LeftUp()` | +| `MouseActionType.RightDown` / `RightUp` | `MouseHelper.RightDown()` / `RightUp()` | +| (scroll) | `MouseHelper.ScrollUp()` / `ScrollDown()` / `ScrollWheel(amount)` | +| (drag) | `MouseHelper.Drag(fromX, fromY, toX, toY, steps)` | + +## Static helpers (new — no instance equivalent) + +| Need | `.Next` helper | +|---|---| +| Send a key chord (incl. global Win-key hotkeys) | `KeyboardHelper.SendKeys(Key.LWin, Key.Shift, Key.C)` | +| Hold/release a key | `KeyboardHelper.PressKey(key)` / `KeyboardHelper.ReleaseKey(key)` | +| Move cursor / read cursor | `MouseHelper.MoveTo(x,y)` / `MouseHelper.GetMousePosition()` | +| Click at the current/again a point | `MouseHelper.LeftClick()` / `LeftClickAt(x,y)` / `RightClick()` / `DoubleClick()` | +| Read clipboard | `ClipboardHelper.GetText()` | +| Clear clipboard | `ClipboardHelper.Clear()` | +| Set clipboard | `ClipboardHelper.SetText("v")` | +| Wait for clipboard to change | `ClipboardHelper.WaitForText(ignoredValue, timeoutMS)` | +| Seed module on/off baseline | `SettingsConfigHelper.ConfigureGlobalModuleSettings("ColorPicker", …)` | +| Edit a module's own settings.json | `SettingsConfigHelper.UpdateModuleSettings(name, default, json => {…})` | + +> The legacy `TestHelper.ClearClipboard`/`GetClipboardText` STA-thread wrappers are replaced by +> `ClipboardHelper` (which already runs on an STA thread internally). Delete the hand-rolled STA code. + +## Element wrappers (`Find`) + +| Wrapper | Legacy | `.Next` | Notes | +|---|---|---|---| +| `Element` | ✅ | ✅ | Base. | +| `Button` | ✅ | ✅ | | +| `CheckBox` | ✅ | ✅ | | +| `ComboBox` | ✅ | ✅ | `.Select(item)` / `.SelectByText(text)` / `.SelectedText`. | +| `RadioButton` | ✅ | ✅ | | +| `Slider` | ✅ | ✅ | | +| `Tab` | ✅ | ✅ | | +| `TextBlock` | ✅ | ✅ | | +| `TextBox` | ✅ | ✅ | `.SetText(v)` / `.Value`. | +| `ToggleSwitch` | ✅ | ✅ | `.IsOn` / `.Toggle(bool)`. Pins `ClassName="ToggleSwitch"`. | +| `Thumb` | ✅ | ✅ | | +| `NavigationViewItem` | ✅ | ✅ | UIA `ListItem`. | +| `Pane` | ✅ | ✅ | | +| `Custom` | ✅ | ✅ | UIA `Custom` (FancyZones zones, Workspaces canvas). | +| `Window` | ✅ | ✅ | | +| `Group` | ✅ | ❌ | Use `Find` or add a wrapper. | +| `HyperlinkButton` | ✅ | ❌ | Use `Find