diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 885a8e5aae..9c9ed952df 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -1440,6 +1440,7 @@ secpol securestring SEEMASKINVOKEIDLIST SELCHANGE +selfhost SENDCHANGE sendvirtualinput serverside @@ -1879,6 +1880,7 @@ winexe winforms winget wingetcreate +wingetpkgs Winhook WINL winlogon diff --git a/.pipelines/v2/templates/job-build-ui-tests.yml b/.pipelines/v2/templates/job-build-ui-tests.yml index 566b377723..b9fad16d44 100644 --- a/.pipelines/v2/templates/job-build-ui-tests.yml +++ b/.pipelines/v2/templates/job-build-ui-tests.yml @@ -123,9 +123,7 @@ jobs: displayName: Stage UI Test Build Outputs inputs: sourceFolder: '$(Build.SourcesDirectory)' - contents: | - $(BuildPlatform)/$(BuildConfiguration)/tests/**/* - **/$(BuildPlatform)/$(BuildConfiguration)/tests/**/* + contents: '**/$(BuildPlatform)/$(BuildConfiguration)/tests/**/*' targetFolder: '$(JobOutputDirectory)\$(BuildPlatform)\$(BuildConfiguration)' - publish: $(JobOutputDirectory) diff --git a/.pipelines/v2/templates/job-test-project.yml b/.pipelines/v2/templates/job-test-project.yml index 54f5490122..2c5fdc78ff 100644 --- a/.pipelines/v2/templates/job-test-project.yml +++ b/.pipelines/v2/templates/job-test-project.yml @@ -11,16 +11,14 @@ parameters: - name: useLatestWebView2 type: boolean default: false - - name: useLatestOfficialBuild - type: boolean - default: true - - name: useCurrentBranchBuild - type: boolean - default: false - - name: powerToysBuildId + - name: buildSource type: string - default: "" - displayName: "PowerToys Build ID (leave empty for latest)" + default: "latestMainOfficialBuild" + displayName: "Build Source" + - name: specificBuildId + type: string + default: "xxxx" + displayName: "Build ID (for specific builds)" - name: uiTestModules type: object default: [] @@ -117,21 +115,18 @@ jobs: & '$(build.sourcesdirectory)\.pipelines\InstallWinAppDriver.ps1' displayName: Download and install WinAppDriver - - ${{ if eq(parameters.useLatestOfficialBuild, true) }}: + - ${{ if ne(parameters.buildSource, 'buildNow') }}: - task: DownloadPipelineArtifact@2 inputs: buildType: 'specific' project: 'Dart' definition: '76541' - ${{ if ne(parameters.powerToysBuildId, '') }}: + ${{ if eq(parameters.buildSource, 'specificBuildId') }}: buildVersionToDownload: 'specific' - buildId: '${{ parameters.powerToysBuildId }}' + buildId: '${{ parameters.specificBuildId }}' ${{ else }}: buildVersionToDownload: 'latestFromBranch' - ${{ if eq(parameters.useCurrentBranchBuild, true) }}: - branchName: '$(Build.SourceBranch)' - ${{ else }}: - branchName: 'refs/heads/main' + branchName: 'refs/heads/main' artifactName: 'build-$(BuildPlatform)-Release' targetPath: '$(Build.ArtifactStagingDirectory)' ${{ if eq(parameters.installMode, 'peruser') }}: @@ -141,7 +136,7 @@ jobs: patterns: | **/PowerToysSetup*.exe - - ${{ if eq(parameters.useLatestOfficialBuild, true) }}: + - ${{ if ne(parameters.buildSource, 'buildNow') }}: - ${{ if eq(parameters.installMode, 'peruser') }}: - pwsh: |- & "$(build.sourcesdirectory)\.pipelines\installPowerToys.ps1" -InstallMode "PerUser" @@ -177,7 +172,7 @@ jobs: !**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll env: platform: '$(TestPlatform)' - useInstallerForTest: ${{ parameters.useLatestOfficialBuild }} + useInstallerForTest: ${{ ne(parameters.buildSource, 'buildNow') }} - ${{ if ne(length(parameters.uiTestModules), 0) }}: - ${{ each module in parameters.uiTestModules }}: @@ -199,4 +194,4 @@ jobs: !**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll env: platform: '$(TestPlatform)' - useInstallerForTest: ${{ parameters.useLatestOfficialBuild }} + useInstallerForTest: ${{ ne(parameters.buildSource, 'buildNow') }} diff --git a/.pipelines/v2/templates/pipeline-ui-tests-automation.yml b/.pipelines/v2/templates/pipeline-ui-tests-automation.yml index 0133306211..0682cc5e32 100644 --- a/.pipelines/v2/templates/pipeline-ui-tests-automation.yml +++ b/.pipelines/v2/templates/pipeline-ui-tests-automation.yml @@ -19,165 +19,40 @@ parameters: - name: useLatestWebView2 type: boolean default: false - - name: useLatestOfficialBuild - type: boolean - default: true - - name: testBothInstallModes - type: boolean - default: true - - name: useCurrentBranchBuild - type: boolean - default: false - - name: powerToysBuildId + - name: buildSource type: string - default: "" - displayName: "PowerToys Build ID (leave empty for latest)" + default: "latestMainOfficialBuild" + displayName: "Build Source" + values: + - latestMainOfficialBuild + - buildNow + - specificBuildId + - name: specificBuildId + type: string + default: 'xxxx' + displayName: "Build ID (only used when Build Source = specificBuildId)" - name: uiTestModules type: object default: [] stages: - ${{ each platform in parameters.buildPlatforms }}: - - ${{ if eq(parameters.useLatestOfficialBuild, false) }}: - - stage: Build_${{ platform }} - displayName: Build ${{ platform }} - dependsOn: [] - jobs: - - template: job-build-project.yml - parameters: - pool: - ${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}: - name: SHINE-INT-L - ${{ else }}: - name: SHINE-OSS-L - ${{ if eq(parameters.useVSPreview, true) }}: - demands: ImageOverride -equals SHINE-VS17-Preview - buildPlatforms: - - ${{ platform }} - buildConfigurations: [Release] - enablePackageCaching: true - enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }} - runTests: false - buildTests: true - useVSPreview: ${{ parameters.useVSPreview }} - timeoutInMinutes: 90 + # Full build path: build PowerToys + UI tests + run tests + - ${{ if eq(parameters.buildSource, 'buildNow') }}: + - template: pipeline-ui-tests-full-build.yml + parameters: + platform: ${{ platform }} + enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }} + useVSPreview: ${{ parameters.useVSPreview }} + useLatestWebView2: ${{ parameters.useLatestWebView2 }} + uiTestModules: ${{ parameters.uiTestModules }} - - ${{ if eq(parameters.useLatestOfficialBuild, true) }}: - - stage: BuildUITests_${{ platform }} - displayName: Build UI Tests Only - dependsOn: [] - jobs: - - template: job-build-ui-tests.yml - parameters: - pool: - ${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}: - name: SHINE-INT-L - ${{ else }}: - name: SHINE-OSS-L - ${{ if eq(parameters.useVSPreview, true) }}: - demands: ImageOverride -equals SHINE-VS17-Preview - buildPlatforms: - - ${{ platform }} - uiTestModules: ${{ parameters.uiTestModules }} - - - ${{ if eq(platform, 'x64') }}: - - stage: Test_x64Win10 - displayName: Test x64Win10 - ${{ if eq(parameters.useLatestOfficialBuild, true) }}: - dependsOn: - - BuildUITests_${{ platform }} - ${{ else }}: - dependsOn: - - Build_${{ platform }} - jobs: - - template: job-test-project.yml - parameters: - platform: x64Win10 - configuration: Release - useLatestWebView2: ${{ parameters.useLatestWebView2 }} - useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }} - useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }} - powerToysBuildId: ${{ parameters.powerToysBuildId }} - uiTestModules: ${{ parameters.uiTestModules }} - - # Additional per-user installation test (when both modes are enabled) - - ${{ if and(eq(parameters.useLatestOfficialBuild, true), eq(parameters.testBothInstallModes, true)) }}: - - template: job-test-project.yml - parameters: - platform: x64Win10 - configuration: Release - useLatestWebView2: ${{ parameters.useLatestWebView2 }} - useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }} - useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }} - powerToysBuildId: ${{ parameters.powerToysBuildId }} - uiTestModules: ${{ parameters.uiTestModules }} - installMode: 'peruser' - jobSuffix: '_PerUser' - - - ${{ if eq(platform, 'x64') }}: - - stage: Test_x64Win11 - displayName: Test x64Win11 - ${{ if eq(parameters.useLatestOfficialBuild, true) }}: - dependsOn: - - BuildUITests_${{ platform }} - ${{ else }}: - dependsOn: - - Build_${{ platform }} - jobs: - - template: job-test-project.yml - parameters: - platform: x64Win11 - configuration: Release - useLatestWebView2: ${{ parameters.useLatestWebView2 }} - useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }} - useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }} - powerToysBuildId: ${{ parameters.powerToysBuildId }} - uiTestModules: ${{ parameters.uiTestModules }} - - # Additional per-user installation test (when both modes are enabled) - - ${{ if and(eq(parameters.useLatestOfficialBuild, true), eq(parameters.testBothInstallModes, true)) }}: - - template: job-test-project.yml - parameters: - platform: x64Win11 - configuration: Release - useLatestWebView2: ${{ parameters.useLatestWebView2 }} - useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }} - useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }} - powerToysBuildId: ${{ parameters.powerToysBuildId }} - uiTestModules: ${{ parameters.uiTestModules }} - installMode: 'peruser' - jobSuffix: '_PerUser' - - - ${{ if ne(platform, 'x64') }}: - - stage: Test_${{ platform }} - displayName: Test ${{ platform }} - ${{ if eq(parameters.useLatestOfficialBuild, true) }}: - dependsOn: - - BuildUITests_${{ platform }} - ${{ else }}: - dependsOn: - - Build_${{ platform }} - jobs: - - template: job-test-project.yml - parameters: - platform: ${{ platform }} - configuration: Release - useLatestWebView2: ${{ parameters.useLatestWebView2 }} - useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }} - useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }} - powerToysBuildId: ${{ parameters.powerToysBuildId }} - uiTestModules: ${{ parameters.uiTestModules }} - - # Additional per-user installation test (when both modes are enabled) - - ${{ if and(eq(parameters.useLatestOfficialBuild, true), eq(parameters.testBothInstallModes, true)) }}: - - template: job-test-project.yml - parameters: - platform: ${{ platform }} - configuration: Release - useLatestWebView2: ${{ parameters.useLatestWebView2 }} - useLatestOfficialBuild: ${{ parameters.useLatestOfficialBuild }} - useCurrentBranchBuild: ${{ parameters.useCurrentBranchBuild }} - powerToysBuildId: ${{ parameters.powerToysBuildId }} - uiTestModules: ${{ parameters.uiTestModules }} - installMode: 'peruser' - jobSuffix: '_PerUser' \ No newline at end of file + # Official build path: build UI tests only + download official build + run tests + - ${{ if ne(parameters.buildSource, 'buildNow') }}: + - template: pipeline-ui-tests-official-build.yml + parameters: + platform: ${{ platform }} + buildSource: ${{ parameters.buildSource }} + specificBuildId: ${{ parameters.specificBuildId }} + useLatestWebView2: ${{ parameters.useLatestWebView2 }} + uiTestModules: ${{ parameters.uiTestModules }} diff --git a/.pipelines/v2/templates/pipeline-ui-tests-full-build.yml b/.pipelines/v2/templates/pipeline-ui-tests-full-build.yml new file mode 100644 index 0000000000..a2373feb80 --- /dev/null +++ b/.pipelines/v2/templates/pipeline-ui-tests-full-build.yml @@ -0,0 +1,80 @@ +# Template for full build path: Build PowerToys + Build UI Tests + Run Tests +parameters: + - name: platform + type: string + - name: enableMsBuildCaching + type: boolean + default: false + - name: useVSPreview + type: boolean + default: false + - name: useLatestWebView2 + type: boolean + default: false + - name: uiTestModules + type: object + default: [] + +stages: + # Stage 1: Build full PowerToys project + - stage: Build_${{ parameters.platform }} + displayName: Build PowerToys ${{ parameters.platform }} + dependsOn: [] + jobs: + - template: job-build-project.yml + parameters: + pool: + ${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}: + name: SHINE-INT-L + ${{ else }}: + name: SHINE-OSS-L + ${{ if eq(parameters.useVSPreview, true) }}: + demands: ImageOverride -equals SHINE-VS17-Preview + buildPlatforms: + - ${{ parameters.platform }} + buildConfigurations: [Release] + enablePackageCaching: true + enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }} + runTests: false + buildTests: true + useVSPreview: ${{ parameters.useVSPreview }} + timeoutInMinutes: 90 + + # Stage 2: Run UI Tests + - ${{ if eq(parameters.platform, 'x64') }}: + - stage: Test_x64Win10_FullBuild + displayName: Test x64Win10 (Full Build) + dependsOn: Build_${{ parameters.platform }} + jobs: + - template: job-test-project.yml + parameters: + platform: x64Win10 + configuration: Release + useLatestWebView2: ${{ parameters.useLatestWebView2 }} + buildSource: 'buildNow' + uiTestModules: ${{ parameters.uiTestModules }} + + - stage: Test_x64Win11_FullBuild + displayName: Test x64Win11 (Full Build) + dependsOn: Build_${{ parameters.platform }} + jobs: + - template: job-test-project.yml + parameters: + platform: x64Win11 + configuration: Release + useLatestWebView2: ${{ parameters.useLatestWebView2 }} + buildSource: 'buildNow' + uiTestModules: ${{ parameters.uiTestModules }} + + - ${{ if ne(parameters.platform, 'x64') }}: + - stage: Test_${{ parameters.platform }}_FullBuild + displayName: Test ${{ parameters.platform }} (Full Build) + dependsOn: Build_${{ parameters.platform }} + jobs: + - template: job-test-project.yml + parameters: + platform: ${{ parameters.platform }} + configuration: Release + useLatestWebView2: ${{ parameters.useLatestWebView2 }} + buildSource: 'buildNow' + uiTestModules: ${{ parameters.uiTestModules }} diff --git a/.pipelines/v2/templates/pipeline-ui-tests-official-build.yml b/.pipelines/v2/templates/pipeline-ui-tests-official-build.yml new file mode 100644 index 0000000000..1da11324fe --- /dev/null +++ b/.pipelines/v2/templates/pipeline-ui-tests-official-build.yml @@ -0,0 +1,110 @@ +# Template for official build path: Download Official Build + Build UI Tests Only + Run Tests +parameters: + - name: platform + type: string + - name: buildSource + type: string + - name: specificBuildId + type: string + default: 'xxxx' + - name: useLatestWebView2 + type: boolean + default: false + - name: uiTestModules + type: object + default: [] + +stages: + # Stage 1: Build UI Tests Only + - stage: BuildUITests_${{ parameters.platform }} + displayName: Build UI Tests Only ${{ parameters.platform }} + dependsOn: [] + jobs: + - template: job-build-ui-tests.yml + parameters: + pool: + ${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}: + name: SHINE-INT-L + ${{ else }}: + name: SHINE-OSS-L + buildPlatforms: + - ${{ parameters.platform }} + uiTestModules: ${{ parameters.uiTestModules }} + + # Stage 2: Run UI Tests with Official Build + - ${{ if eq(parameters.platform, 'x64') }}: + - stage: Test_x64Win10_OfficialBuild + displayName: Test x64Win10 (Official Build) + dependsOn: BuildUITests_${{ parameters.platform }} + jobs: + - template: job-test-project.yml + parameters: + platform: x64Win10 + configuration: Release + useLatestWebView2: ${{ parameters.useLatestWebView2 }} + buildSource: ${{ parameters.buildSource }} + specificBuildId: ${{ parameters.specificBuildId }} + uiTestModules: ${{ parameters.uiTestModules }} + + # Additional per-user installation test + - template: job-test-project.yml + parameters: + platform: x64Win10 + configuration: Release + useLatestWebView2: ${{ parameters.useLatestWebView2 }} + buildSource: ${{ parameters.buildSource }} + specificBuildId: ${{ parameters.specificBuildId }} + uiTestModules: ${{ parameters.uiTestModules }} + installMode: 'peruser' + jobSuffix: '_PerUser' + + - stage: Test_x64Win11_OfficialBuild + displayName: Test x64Win11 (Official Build) + dependsOn: BuildUITests_${{ parameters.platform }} + jobs: + - template: job-test-project.yml + parameters: + platform: x64Win11 + configuration: Release + useLatestWebView2: ${{ parameters.useLatestWebView2 }} + buildSource: ${{ parameters.buildSource }} + specificBuildId: ${{ parameters.specificBuildId }} + uiTestModules: ${{ parameters.uiTestModules }} + + # Additional per-user installation test + - template: job-test-project.yml + parameters: + platform: x64Win11 + configuration: Release + useLatestWebView2: ${{ parameters.useLatestWebView2 }} + buildSource: ${{ parameters.buildSource }} + specificBuildId: ${{ parameters.specificBuildId }} + uiTestModules: ${{ parameters.uiTestModules }} + installMode: 'peruser' + jobSuffix: '_PerUser' + + - ${{ if ne(parameters.platform, 'x64') }}: + - stage: Test_${{ parameters.platform }}_OfficialBuild + displayName: Test ${{ parameters.platform }} (Official Build) + dependsOn: BuildUITests_${{ parameters.platform }} + jobs: + - template: job-test-project.yml + parameters: + platform: ${{ parameters.platform }} + configuration: Release + useLatestWebView2: ${{ parameters.useLatestWebView2 }} + buildSource: ${{ parameters.buildSource }} + specificBuildId: ${{ parameters.specificBuildId }} + uiTestModules: ${{ parameters.uiTestModules }} + + # Additional per-user installation test + - template: job-test-project.yml + parameters: + platform: ${{ parameters.platform }} + configuration: Release + useLatestWebView2: ${{ parameters.useLatestWebView2 }} + buildSource: ${{ parameters.buildSource }} + specificBuildId: ${{ parameters.specificBuildId }} + uiTestModules: ${{ parameters.uiTestModules }} + installMode: 'peruser' + jobSuffix: '_PerUser' diff --git a/PowerToys.sln b/PowerToys.sln index 55b707b0b9..00986aae29 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -786,6 +786,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.TimeDa EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.WindowWalker.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.WindowWalker.UnitTests\Microsoft.CmdPal.Ext.WindowWalker.UnitTests.csproj", "{E816D7B0-4688-4ECB-97CC-3D8E798F3829}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.UnitTestBase", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj", "{00D8659C-2068-40B6-8B86-759CD6284BBB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -2840,6 +2842,14 @@ Global {E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Release|ARM64.Build.0 = Release|ARM64 {E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Release|x64.ActiveCfg = Release|x64 {E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Release|x64.Build.0 = Release|x64 + {00D8659C-2068-40B6-8B86-759CD6284BBB}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {00D8659C-2068-40B6-8B86-759CD6284BBB}.Debug|ARM64.Build.0 = Debug|ARM64 + {00D8659C-2068-40B6-8B86-759CD6284BBB}.Debug|x64.ActiveCfg = Debug|x64 + {00D8659C-2068-40B6-8B86-759CD6284BBB}.Debug|x64.Build.0 = Debug|x64 + {00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|ARM64.ActiveCfg = Release|ARM64 + {00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|ARM64.Build.0 = Release|ARM64 + {00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.ActiveCfg = Release|x64 + {00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -3150,6 +3160,7 @@ Global {E816D7AE-4688-4ECB-97CC-3D8E798F3827} = {8EF25507-2575-4ADE-BF7E-D23376903AB8} {E816D7AF-4688-4ECB-97CC-3D8E798F3828} = {8EF25507-2575-4ADE-BF7E-D23376903AB8} {E816D7B0-4688-4ECB-97CC-3D8E798F3829} = {8EF25507-2575-4ADE-BF7E-D23376903AB8} + {00D8659C-2068-40B6-8B86-759CD6284BBB} = {8EF25507-2575-4ADE-BF7E-D23376903AB8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/doc/devdocs/UITests.md b/doc/devdocs/UITests.md index 2a829d6e90..63bddb0591 100644 --- a/doc/devdocs/UITests.md +++ b/doc/devdocs/UITests.md @@ -22,23 +22,23 @@ The PowerToys UI test pipeline provides flexible options for building and testin ### 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. +- **buildSource**: Select the build type for testing: + - `latestMainOfficialBuild`: Downloads and uses the latest official PowerToys build from main branch + - `buildNow`: Builds PowerToys from current source code and uses it for testing + - `specificBuildId`: Downloads a specific PowerToys build using the build ID specified in `specificBuildId` parameter -- **useCurrentBranchBuild**: When checked along with `useLatestOfficialBuild`, downloads the official build from the current branch instead of main. + **Default value**: `latestMainOfficialBuild` - **Default value**: `false` (downloads from main branch) +- **specificBuildId**: When `buildSource` is set to `specificBuildId`, specify the exact PowerToys build ID to download and test against. + + **Default value**: `"xxxx"` (placeholder, enter actual build ID when using specificBuildId option) **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 + - Testing against a specific known build for reproducibility + - Regression testing against a particular build version + - Validating fixes in a specific build before release - **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 + **Usage**: Enter the build ID number (e.g., `12345`) to download that specific build. Only used when `buildSource` is set to `specificBuildId`. - **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 @@ -50,19 +50,19 @@ The PowerToys UI test pipeline provides flexible options for building and testin ### 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 +1. **Official Build Testing** (`buildSource = latestMainOfficialBuild` or `specificBuildId`) + - Downloads and installs official PowerToys build (latest from main or specific build ID) + - Builds only UI test projects (all or specific based on `uiTestModules`) + - Runs UI tests against installed PowerToys + - Tests both machine-level and per-user installation modes automatically -2. **Full Build + Testing** (`useLatestOfficialBuild = false`) - - Builds entire PowerToys solution +2. **Current Source Build Testing** (`buildSource = buildNow`) + - Builds entire PowerToys solution from current source code - 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 + - Runs UI tests against freshly built PowerToys + - Uses artifacts from current pipeline build -> **Note**: Both modes support the `uiTestModules` parameter to control which specific UI test modules to build and run. +> **Note**: All modes support the `uiTestModules` parameter to control which specific UI test modules to build and run. Both machine-level and per-user installation modes are tested automatically when using official builds. ### Pipeline Access - Pipeline: https://microsoft.visualstudio.com/Dart/_build?definitionId=161438&_a=summary diff --git a/doc/devdocs/core/installer.md b/doc/devdocs/core/installer.md index 781f31d682..b4619e26cd 100644 --- a/doc/devdocs/core/installer.md +++ b/doc/devdocs/core/installer.md @@ -87,6 +87,13 @@ ### Building PowerToys Locally +#### One stop script for building installer +1. Open developer powershell for vs 2022 +2. Run tools\build\build-installer.ps1 +> For the first-time setup, please run the installer as an administrator. This ensures that the Wix tool can move wix.target to the desired location and trust the certificate used to sign the MSIX packages. + +The following manual steps will not install the MSIX apps (such as Command Palette) on your local installer. + #### Prerequisites for building the MSI installer 1. Install the [WiX Toolset Visual Studio 2022 Extension](https://marketplace.visualstudio.com/items?itemName=WixToolset.WixToolsetVisualStudio2022Extension). diff --git a/doc/devdocs/development/test-winget-install-locally.md b/doc/devdocs/development/test-winget-install-locally.md new file mode 100644 index 0000000000..a59d32c52d --- /dev/null +++ b/doc/devdocs/development/test-winget-install-locally.md @@ -0,0 +1,33 @@ +## If for any reason, you'd like to test winget install scenario, you can follow this doc: + +### Powertoys winget manifest definition: +[winget repository](https://github.com/microsoft/winget-pkgs/tree/master/manifests/m/Microsoft/PowerToys) + +### How to test a winget installation locally: +1. Get artifacts from release CI pipeline Pipelines - Runs for PowerToys Signed YAML Release Build, or you can build one yourself by execute the + 'tools\build\build-installer.ps1' script + +2. Get the artifact hash, this is required to define winget manifest +```powershell +cd /path/to/your/directory/contains/installer +Get-FileHash -Path ".\.exe" -Algorithm SHA256 +``` + 3. Host your installer.exe - Attention: staged github release artifacts or artifacts in release pipeline is not OK in this step +You can self-host it or you can upload to a publicly available endpoint +**How to selfhost it** (A extremely simple way): +```powershell +python -m http.server 8000 +``` + +4. Download a version folder from wingetpkgs like: [version 0.92.1](https://github.com/microsoft/winget-pkgs/tree/master/manifests/m/Microsoft/PowerToys/0.92.1) +and you get **a folder contains 3 yml files** +>note: Do not put any files other than these three in this folder + +5. Modify the yml files based on your version and the self hosted artifact link, and modify the sha256 hash for the installer you'd like to use + +6. Start winget install: +```powershell +#execute as admin +winget settings --enable LocalManifestFiles +winget install --manifest "" --architecture x64 --scope user +``` \ No newline at end of file diff --git a/src/common/UITestAutomation/SessionHelper.cs b/src/common/UITestAutomation/SessionHelper.cs index 059af3cfce..0ca3eb3ddd 100644 --- a/src/common/UITestAutomation/SessionHelper.cs +++ b/src/common/UITestAutomation/SessionHelper.cs @@ -180,12 +180,11 @@ namespace Microsoft.PowerToys.UITest ExitExe(runnerProcessInfo.FileName); runner = Process.Start(runnerProcessInfo); - Thread.Sleep(5000); + + WaitForWindowAndSetCapability(opts, "PowerToys Settings", 5000, 5); // Exit CmdPal UI before launching new process if use installer for test ExitExeByName("Microsoft.CmdPal.UI"); - - WaitForWindowAndSetCapability(opts, "PowerToys Settings", 5000, 5); } catch (Exception ex) { @@ -211,7 +210,6 @@ namespace Microsoft.PowerToys.UITest var process = Process.Start(processStartInfo); process?.WaitForExit(); - Thread.Sleep(3000); // Wait for the app to start WaitForWindowAndSetCapability(opts, "Command Palette", 5000, 10); } diff --git a/src/common/UITestAutomation/UITestBase.cs b/src/common/UITestAutomation/UITestBase.cs index f40780e58c..778634ab87 100644 --- a/src/common/UITestAutomation/UITestBase.cs +++ b/src/common/UITestAutomation/UITestBase.cs @@ -282,7 +282,34 @@ namespace Microsoft.PowerToys.UITest /// The found element. protected Element FindByPartialName(string partialName, int timeoutMS = 5000, bool global = false) { - return Session.Find(By.XPath($"//*[contains(@Name, '{partialName}')]"), timeoutMS, global); + return FindByPartialName(partialName, timeoutMS, global); + } + + /// + /// Base method for finding elements by selector and filtering by name pattern. + /// + /// The class of the element, should be Element or its derived class. + /// The selector to find initial candidates. + /// Pattern to match against the Name attribute. Supports regex patterns. + /// The timeout in milliseconds (default is 5000). + /// Custom error message when no element is found. + /// The found element. + private T FindByNamePattern(By selector, string namePattern, int timeoutMS = 5000, bool global = false, string? errorMessage = null) + where T : Element, new() + { + var elements = Session.FindAll(selector, timeoutMS, global); + var regex = new Regex(namePattern, RegexOptions.IgnoreCase); + + foreach (var element in elements) + { + var name = element.GetAttribute("Name"); + if (!string.IsNullOrEmpty(name) && regex.IsMatch(name)) + { + return element; + } + } + + throw new NoSuchElementException(errorMessage ?? $"No element found matching pattern: {namePattern}"); } /// @@ -295,19 +322,7 @@ namespace Microsoft.PowerToys.UITest protected T FindByPattern(string pattern, int timeoutMS = 5000, bool global = false) where T : Element, new() { - var elements = Session.FindAll(By.XPath("//*[@Name]"), timeoutMS, global); - var regex = new Regex(pattern, RegexOptions.IgnoreCase); - - foreach (var element in elements) - { - var name = element.GetAttribute("Name"); - if (!string.IsNullOrEmpty(name) && regex.IsMatch(name)) - { - return element; - } - } - - throw new NoSuchElementException($"No element found matching pattern: {pattern}"); + return FindByNamePattern(By.XPath("//*[@Name]"), pattern, timeoutMS, global, $"No element found matching pattern: {pattern}"); } /// @@ -358,19 +373,7 @@ namespace Microsoft.PowerToys.UITest protected T FindByClassNameAndNamePattern(string className, string namePattern, int timeoutMS = 5000, bool global = false) where T : Element, new() { - var elements = Session.FindAll(By.ClassName(className), timeoutMS, global); - var regex = new Regex(namePattern, RegexOptions.IgnoreCase); - - foreach (var element in elements) - { - var name = element.GetAttribute("Name"); - if (!string.IsNullOrEmpty(name) && regex.IsMatch(name)) - { - return element; - } - } - - throw new NoSuchElementException($"No element with ClassName '{className}' found matching name pattern: {namePattern}"); + return FindByNamePattern(By.ClassName(className), namePattern, timeoutMS, global, $"No element with ClassName '{className}' found matching name pattern: {namePattern}"); } /// diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPage.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPage.cs index 2817ab9824..af2a3d76be 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPage.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPage.cs @@ -32,6 +32,7 @@ public partial class MainListPage : DynamicListPage, public MainListPage(IServiceProvider serviceProvider) { Icon = IconHelpers.FromRelativePath("Assets\\StoreLogo.scale-200.png"); + PlaceholderText = Properties.Resources.builtin_main_list_page_searchbar_placeholder; _serviceProvider = serviceProvider; _tlcManager = _serviceProvider.GetService()!; diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.Designer.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.Designer.cs index 4c1339ae7d..1deb9cdf74 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.Designer.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.Designer.cs @@ -321,6 +321,15 @@ namespace Microsoft.CmdPal.UI.ViewModels.Properties { } } + /// + /// Looks up a localized string similar to Search for apps, files and commands.... + /// + public static string builtin_main_list_page_searchbar_placeholder { + get { + return ResourceManager.GetString("builtin_main_list_page_searchbar_placeholder", resourceCulture); + } + } + /// /// Looks up a localized string similar to Creates a project for a new Command Palette extension. /// diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.resx b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.resx index ce4061a58a..0d341e3981 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.resx +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.resx @@ -227,4 +227,7 @@ Disabled + + Search for apps, files and commands... + \ No newline at end of file diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml index 1a71c893b0..9fb047641f 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml @@ -76,44 +76,44 @@ - - - - - - - - - - - - - - + /// The tSettingsManager instance /// The list of available results - public static List GetNetworkConnectionResults(SettingsManager manager) + public static List GetNetworkConnectionResults(ISettingsInterface manager) { var results = new List(); @@ -151,7 +151,7 @@ internal static class Commands CompositeFormat sysIpv4DescriptionCompositeFormate = CompositeFormat.Parse(Resources.Microsoft_plugin_sys_ip4_description); CompositeFormat sysIpv6DescriptionCompositeFormate = CompositeFormat.Parse(Resources.Microsoft_plugin_sys_ip6_description); CompositeFormat sysMacDescriptionCompositeFormate = CompositeFormat.Parse(Resources.Microsoft_plugin_sys_mac_description); - var hideDisconnectedNetworkInfo = manager.HideDisconnectedNetworkInfo; + var hideDisconnectedNetworkInfo = manager.HideDisconnectedNetworkInfo(); foreach (NetworkConnectionProperties intInfo in networkPropertiesCache) { @@ -200,7 +200,7 @@ internal static class Commands return results; } - public static List GetAllCommands(SettingsManager manager) + public static List GetAllCommands(ISettingsInterface manager) { var list = new List(); var listLock = new object(); @@ -209,11 +209,11 @@ internal static class Commands // On global queries the first word/part has to be 'ip', 'mac' or 'address' for network results var networkConnectionResults = Commands.GetNetworkConnectionResults(manager); - var isBootedInUefiMode = Win32Helpers.GetSystemFirmwareType() == FirmwareType.Uefi; + var isBootedInUefiMode = manager.GetSystemFirmwareType() == FirmwareType.Uefi; - var hideEmptyRB = manager.HideEmptyRecycleBin; - var confirmSystemCommands = manager.ShowDialogToConfirmCommand; - var showSuccessOnEmptyRB = manager.ShowSuccessMessageAfterEmptyingRecycleBin; + var hideEmptyRB = manager.HideEmptyRecycleBin(); + var confirmSystemCommands = manager.ShowDialogToConfirmCommand(); + var showSuccessOnEmptyRB = manager.ShowSuccessMessageAfterEmptyingRecycleBin(); // normal system commands are fast and can be returned immediately var systemCommands = Commands.GetSystemCommands(isBootedInUefiMode, hideEmptyRB, confirmSystemCommands, showSuccessOnEmptyRB); diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Helpers/ISettingsInterface.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Helpers/ISettingsInterface.cs new file mode 100644 index 0000000000..1690007126 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Helpers/ISettingsInterface.cs @@ -0,0 +1,24 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.CmdPal.Ext.System.Helpers; + +public interface ISettingsInterface +{ + public bool ShowDialogToConfirmCommand(); + + public bool ShowSuccessMessageAfterEmptyingRecycleBin(); + + public bool HideEmptyRecycleBin(); + + public bool HideDisconnectedNetworkInfo(); + + public FirmwareType GetSystemFirmwareType(); +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Helpers/SettingsManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Helpers/SettingsManager.cs index 9d51c42615..952b68d7aa 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Helpers/SettingsManager.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Helpers/SettingsManager.cs @@ -7,7 +7,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit; namespace Microsoft.CmdPal.Ext.System.Helpers; -public class SettingsManager : JsonSettingsManager +public class SettingsManager : JsonSettingsManager, ISettingsInterface { private static readonly string _namespace = "system"; @@ -37,14 +37,6 @@ public class SettingsManager : JsonSettingsManager Resources.Microsoft_plugin_ext_settings_hideDisconnectedNetworkInfo, false); - public bool ShowDialogToConfirmCommand => _showDialogToConfirmCommand.Value; - - public bool ShowSuccessMessageAfterEmptyingRecycleBin => _showSuccessMessageAfterEmptyingRecycleBin.Value; - - public bool HideEmptyRecycleBin => _hideEmptyRecycleBin.Value; - - public bool HideDisconnectedNetworkInfo => _hideDisconnectedNetworkInfo.Value; - internal static string SettingsJsonPath() { var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal"); @@ -54,6 +46,16 @@ public class SettingsManager : JsonSettingsManager return Path.Combine(directory, "settings.json"); } + public bool ShowDialogToConfirmCommand() => _showDialogToConfirmCommand.Value; + + public bool ShowSuccessMessageAfterEmptyingRecycleBin() => _showSuccessMessageAfterEmptyingRecycleBin.Value; + + public bool HideEmptyRecycleBin() => _hideEmptyRecycleBin.Value; + + public bool HideDisconnectedNetworkInfo() => _hideDisconnectedNetworkInfo.Value; + + public FirmwareType GetSystemFirmwareType() => Win32Helpers.GetSystemFirmwareType(); + public SettingsManager() { FilePath = SettingsJsonPath(); diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Pages/SystemCommandPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Pages/SystemCommandPage.cs index 3d02cdf18f..bb7deb1c9e 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Pages/SystemCommandPage.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Pages/SystemCommandPage.cs @@ -10,9 +10,9 @@ namespace Microsoft.CmdPal.Ext.System.Pages; public sealed partial class SystemCommandPage : ListPage { - private readonly SettingsManager _settingsManager; + private readonly ISettingsInterface _settingsManager; - public SystemCommandPage(SettingsManager settingsManager) + public SystemCommandPage(ISettingsInterface settingsManager) { Title = Resources.Microsoft_plugin_ext_system_page_title; Name = Resources.Microsoft_plugin_command_name_open;