Files
PowerToys/doc/devdocs/UITests.md
leileizhang 6d29c3a2c9 [pipeline] feat: Implement flexible UI test pipeline with configurable build and execution modes (#40490)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
**Root Cause:**
The current pipeline builds the entire solution and runs all UI tests
every time, which takes more than 2 hours to complete.

**Fix**
Make the PowerToys UI test pipeline provides flexible options for
building and testing:

### Pipeline Options

- **useLatestOfficialBuild**: When checked, downloads the latest
official PowerToys build and installs it for testing. This skips the
full solution build and only builds UI test projects.

- **useCurrentBranchBuild**: When checked along with
`useLatestOfficialBuild`, downloads the official build from the current
branch instead of main.

- **uiTestModules**: Specify which UI test modules to build and run.
Examples:
  - `UITests-FancyZones` - Only FancyZones UI tests
  - `MouseUtils.UITests` - Only MouseUtils UI tests
- `['UITests-FancyZones', 'MouseUtils.UITests']` - Multiple specific
modules
  - Leave empty to build and run all UI test modules

### Build Modes

1. **Official Build + Selective Testing** (`useLatestOfficialBuild =
true`)
   - Downloads and installs official PowerToys build
   - Builds only specified UI test projects
   - Runs specified UI tests against installed PowerToys
   - Controlled by `uiTestModules` parameter

2. **Full Build + Testing** (`useLatestOfficialBuild = false`)
   - Builds entire PowerToys solution
   - Builds UI test projects (all or specific based on `uiTestModules`)
   - Runs UI tests (all or specific based on `uiTestModules`)
   - Uses freshly built PowerToys for testing
  
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] **Closes:** #xxx
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [x] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-07-10 09:26:26 +08:00

6.4 KiB

UI tests framework

A specialized UI test framework for PowerToys that makes it easy to write UI tests for PowerToys modules or settings. Let's start writing UI tests!

Before running tests

Running tests

  • Exit PowerToys if it's running.

  • Open PowerToys.sln in Visual Studio and build the solution.

  • Run tests in the Test Explorer (Test > Test Explorer or Ctrl+E, T).

Running tests in pipeline

The PowerToys UI test pipeline provides flexible options for building and testing:

Pipeline Options

  • useLatestOfficialBuild: When checked, downloads the latest official PowerToys build and installs it for testing. This skips the full solution build and only builds UI test projects.

  • useCurrentBranchBuild: When checked along with useLatestOfficialBuild, downloads the official build from the current branch instead of main.

    Default value: false (downloads from main branch)

    When to use this:

    • Default scenario: The pipeline tests against the latest signed PowerToys build from the main branch, regardless of which branch your test code changes are from
    • Custom branch testing: Only specify true when:
      • Your branch has produced its own signed PowerToys build via the official build pipeline
      • You want to test against that specific branch's PowerToys build instead of main
      • You are testing PowerToys functionality changes that are only available in your branch's build

    Important notes:

    • The test pipeline itself runs from your specified branch, but by default tests against the main branch's PowerToys build
    • Not all branches have signed builds available - only use this if you're certain your branch has a signed build
    • If enabled but no build exists for your branch, the pipeline may fail or fall back to main
  • uiTestModules: Specify which UI test modules to build and run. This parameter controls both the .csproj projects to build and the .dll test assemblies to execute. Examples:

    • ['UITests-FancyZones'] - Only FancyZones UI tests
    • ['MouseUtils.UITests'] - Only MouseUtils UI tests
    • ['UITests-FancyZones', 'MouseUtils.UITests'] - Multiple specific modules
    • Leave empty to build and run all UI test modules

    Important: The uiTestModules parameter values must match both the test project names (for .csproj selection during build) and the test assembly names (for .dll execution during testing).

Build Modes

  1. Official Build + Selective Testing (useLatestOfficialBuild = true)

    • Downloads and installs official PowerToys build
    • Builds only specified UI test projects
    • Runs specified UI tests against installed PowerToys
    • Controlled by uiTestModules parameter
  2. Full Build + Testing (useLatestOfficialBuild = false)

    • Builds entire PowerToys solution
    • Builds UI test projects (all or specific based on uiTestModules)
    • Runs UI tests (all or specific based on uiTestModules)
    • Uses freshly built PowerToys for testing

Note

: Both modes support the uiTestModules parameter to control which specific UI test modules to build and run.

Pipeline Access

How to add the first UI tests for your modules

  • Create a new project and add the following references to the project file. Change the OutputPath to your own module's path.

      <Project Sdk="Microsoft.NET.Sdk">
      <!-- Look at Directory.Build.props in root for common stuff as well -->
      <Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
    
      <PropertyGroup>
          <ProjectGuid>{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}</ProjectGuid>
          <RootNamespace>PowerToys.Hosts.UITests</RootNamespace>
          <AssemblyName>PowerToys.Hosts.UITests</AssemblyName>
          <IsPackable>false</IsPackable>
          <IsTestProject>true</IsTestProject>
          <Nullable>enable</Nullable>
          <OutputType>Library</OutputType>
    
          <!-- This is a UI test, so don't run as part of MSBuild -->
          <RunVSTest>false</RunVSTest>
          </PropertyGroup>
          <PropertyGroup>
          <OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\tests\Hosts.UITests\</OutputPath>
          </PropertyGroup>
    
          <ItemGroup>
          <PackageReference Include="MSTest" />
          <ProjectReference Include="..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
          </ItemGroup>
      </Project>
    
    
  • Inherit your test class from UITestBase.

    Set Scope: The default scope starts from the PowerToys settings UI. If you want to start from your own module, set the constructor as shown below:

    Specify Scope:

      [TestClass]
      public class HostModuleTests : UITestBase
      {
          public HostModuleTests()
              : base(PowerToysModule.Hosts, WindowSize.Small_Vertical)
          {
          }
      }
    
  • Then you can start performing the UI operations.

Example

[TestMethod("Hosts.Basic.EmptyViewShouldWork")]
[TestCategory("Hosts File Editor #4")]
public void TestEmptyView()
{
    this.CloseWarningDialog();
    this.RemoveAllEntries();

    // 'Add an entry' button (only show-up when list is empty) should be visible
    Assert.IsTrue(this.HasOne<HyperlinkButton>("Add an entry"), "'Add an entry' button should be visible in the empty view");

    VisualAssert.AreEqual(this.TestContext, this.Find("Entries"), "EmptyView");

    // Click 'Add an entry' from empty-view for adding Host override rule
    this.Find<HyperlinkButton>("Add an entry").Click();

    this.AddEntry("192.168.0.1", "localhost", false, false);

    // Should have one row now and not more empty view
    Assert.IsTrue(this.Has<Button>("Delete"), "Should have one row now");
    Assert.IsFalse(this.Has<HyperlinkButton>("Add an entry"), "'Add an entry' button should be invisible if not empty view");

    VisualAssert.AreEqual(this.TestContext, this.Find("Entries"), "NonEmptyView");
}

Extra tools and information

Accessibility Tools: While working on tests, you may need a tool that helps you to view the element's accessibility data, e.g. for finding the button to click. For this purpose, you could use AccessibilityInsights.