mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-01 18:06:25 +01:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9081bc7438 | ||
|
|
7307242396 | ||
|
|
630ed29700 | ||
|
|
41b0b850e1 | ||
|
|
96dc200324 | ||
|
|
9597f87dc4 | ||
|
|
7572586b4c |
@@ -1,12 +1,5 @@
|
||||
trigger: none
|
||||
pr: none
|
||||
schedules:
|
||||
- cron: "0 0 * * *" # every day at midnight
|
||||
displayName: "Daily midnight Build"
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
always: false # only run if there's code changes!
|
||||
|
||||
name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)
|
||||
|
||||
@@ -15,7 +8,6 @@ parameters:
|
||||
type: object
|
||||
default:
|
||||
- x64
|
||||
- arm64
|
||||
- name: enableMsBuildCaching
|
||||
type: boolean
|
||||
displayName: "Enable MSBuild Caching"
|
||||
@@ -28,15 +20,6 @@ parameters:
|
||||
type: boolean
|
||||
displayName: "Build Using Visual Studio Preview"
|
||||
default: false
|
||||
- name: useLatestWinAppSDK
|
||||
type: boolean
|
||||
default: true
|
||||
- name: winAppSDKVersionNumber
|
||||
type: string
|
||||
default: 1.6
|
||||
- name: useExperimentalVersion
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
extends:
|
||||
template: templates/pipeline-ci-build.yml
|
||||
@@ -45,6 +28,3 @@ extends:
|
||||
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
|
||||
runTests: ${{ parameters.runTests }}
|
||||
useVSPreview: ${{ parameters.useVSPreview }}
|
||||
useLatestWinAppSDK: ${{ parameters.useLatestWinAppSDK }}
|
||||
winAppSDKVersionNumber: ${{ parameters.winAppSDKVersionNumber }}
|
||||
useExperimentalVersion: ${{ parameters.useExperimentalVersion }}
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
displayName: Download artifacts
|
||||
artifact: $(ArtifactName)
|
||||
patterns: |-
|
||||
**/tests/RegistryPreview.FuzzTests/**
|
||||
**/tests/*.FuzzTests/**
|
||||
|
||||
- task: onefuzz-task@0
|
||||
inputs:
|
||||
|
||||
@@ -61,9 +61,18 @@ jobs:
|
||||
reg add "HKLM\Software\Policies\Microsoft\Edge\WebView2\ReleaseChannels" /v PowerToys.exe /t REG_SZ /d "3"
|
||||
displayName: "Enable WebView2 Canary Channel"
|
||||
|
||||
- template: steps-download-artifacts-with-azure-cli.yml
|
||||
parameters:
|
||||
artifactName: $(TestArtifactsName)
|
||||
- ${{ if ne(parameters.platform, 'arm64') }}:
|
||||
- download: current
|
||||
displayName: Download artifacts
|
||||
artifact: $(TestArtifactsName)
|
||||
patterns: |-
|
||||
**
|
||||
!**\*.pdb
|
||||
!**\*.lib
|
||||
- ${{ else }}:
|
||||
- template: steps-download-artifacts-with-azure-cli.yml
|
||||
parameters:
|
||||
artifactName: $(TestArtifactsName)
|
||||
|
||||
- template: steps-ensure-dotnet-version.yml
|
||||
parameters:
|
||||
|
||||
@@ -43,43 +43,11 @@ stages:
|
||||
- template: job-ci-precheck.yml
|
||||
|
||||
- ${{ each platform in parameters.buildPlatforms }}:
|
||||
- stage: Build_${{ platform }}
|
||||
displayName: Build ${{ platform }}
|
||||
${{ if ne(variables['Build.Reason'], 'Manual') }}:
|
||||
dependsOn: [Precheck]
|
||||
condition: and(succeeded(), ne(dependencies.Precheck.outputs['Precheck.verifyBuildRequest.skipBuild'], 'Yes'))
|
||||
${{ else }}:
|
||||
dependsOn: []
|
||||
- stage: Test_${{ platform }}
|
||||
displayName: Test ${{ platform }}
|
||||
jobs:
|
||||
- template: job-build-project.yml
|
||||
- template: job-test-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: ${{ parameters.runTests }}
|
||||
useVSPreview: ${{ parameters.useVSPreview }}
|
||||
useLatestWinAppSDK: ${{ parameters.useLatestWinAppSDK }}
|
||||
${{ if eq(parameters.useLatestWinAppSDK, true) }}:
|
||||
winAppSDKVersionNumber: ${{ parameters.winAppSDKVersionNumber }}
|
||||
useExperimentalVersion: ${{ parameters.useExperimentalVersion }}
|
||||
|
||||
- ${{ if eq(parameters.runTests, true) }}:
|
||||
- stage: Test_${{ platform }}
|
||||
displayName: Test ${{ platform }}
|
||||
dependsOn:
|
||||
- Build_${{platform}}
|
||||
jobs:
|
||||
- template: job-test-project.yml
|
||||
parameters:
|
||||
platform: ${{ platform }}
|
||||
configuration: Release
|
||||
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
|
||||
platform: ${{ platform }}
|
||||
configuration: Release
|
||||
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
|
||||
|
||||
@@ -24,10 +24,18 @@ steps:
|
||||
- pwsh: |
|
||||
$azureCliPath = "$(Build.ArtifactStagingDirectory)\AzureCLI\bin"
|
||||
$env:Path = "$azureCliPath;" + $env:Path
|
||||
Write-Host "Add azure-devops..."
|
||||
az extension add -n azure-devops
|
||||
Write-Host "Configuring Azure DevOps defaults..."
|
||||
az devops configure --defaults organization='$(System.TeamFoundationCollectionUri)' project='$(System.TeamProject)' --use-git-aliases true
|
||||
Write-Host "check permission"
|
||||
az pipelines list --org "$(System.TeamFoundationCollectionUri)" --project "$(System.TeamProject)" --output table
|
||||
Write-Host "Downloading artifacts..."
|
||||
az pipelines runs artifact download --artifact-name ${{parameters.artifactName}} --path "$(Pipeline.Workspace)/${{parameters.artifactName}}" --run-id $(Build.BuildId) --debug
|
||||
if ($env:AZURE_DEVOPS_EXT_PAT -eq $null -or $env:AZURE_DEVOPS_EXT_PAT -eq "") {
|
||||
Write-Host "Error: AZURE_DEVOPS_EXT_PAT is not set."
|
||||
exit 1
|
||||
}
|
||||
az pipelines runs artifact download --artifact-name ${{parameters.artifactName}} --path "$(Pipeline.Workspace)/${{parameters.artifactName}}" --run-id 116384714 --debug
|
||||
displayName: 'Download artifacts with Azure CLI'
|
||||
env:
|
||||
AZURE_DEVOPS_EXT_PAT: $(System.AccessToken)
|
||||
|
||||
@@ -15,8 +15,8 @@ Param(
|
||||
$referencedFileVersionsPerDll = @{}
|
||||
$totalFailures = 0
|
||||
|
||||
Get-ChildItem $targetDir -Recurse -Filter *.deps.json -Exclude *UITests*,MouseJump.Common.UnitTests*,*.FuzzTests* | ForEach-Object {
|
||||
# Temporarily exclude All UI-Test, Fuzzer-Test projects because of Appium.WebDriver dependencies
|
||||
Get-ChildItem $targetDir -Recurse -Filter *.deps.json -Exclude UITests-FancyZones*,MouseJump.Common.UnitTests*,*.FuzzTests* | ForEach-Object {
|
||||
# Temporarily exclude FancyZones UI tests because of Appium.WebDriver dependencies
|
||||
$depsJsonFullFileName = $_.FullName
|
||||
$depsJsonFileName = $_.Name
|
||||
$depsJson = Get-Content $depsJsonFullFileName | ConvertFrom-Json
|
||||
|
||||
@@ -644,10 +644,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManagerEditorLibrar
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hosts.FuzzTests", "src\modules\Hosts\Hosts.FuzzTests\Hosts.FuzzTests.csproj", "{EBED240C-8702-452D-B764-6DB9DA9179AF}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hosts.UITests", "src\modules\Hosts\Hosts.UITests\Hosts.UITests.csproj", "{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RegistryPreview.FuzzTests", "src\modules\registrypreview\RegistryPreview.FuzzTests\RegistryPreview.FuzzTests.csproj", "{50A9F3DE-CF0B-4CF0-AFDE-3A3E245D7734}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
@@ -2256,14 +2252,20 @@ Global
|
||||
{CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|x64.ActiveCfg = Release|x64
|
||||
{CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|x64.Build.0 = Release|x64
|
||||
{CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|x86.ActiveCfg = Release|x64
|
||||
{CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|x86.Build.0 = Release|x64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Debug|x64.Build.0 = Debug|x64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Debug|x86.Build.0 = Debug|x64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Release|x64.ActiveCfg = Release|x64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Release|x64.Build.0 = Release|x64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Release|x86.ActiveCfg = Release|x64
|
||||
{A558C25D-2007-498E-8B6F-43405AFAE9E2}.Release|x86.Build.0 = Release|x64
|
||||
{08F9155D-B6DC-46E5-9C83-AF60B655898B}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{08F9155D-B6DC-46E5-9C83-AF60B655898B}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{08F9155D-B6DC-46E5-9C83-AF60B655898B}.Debug|x64.ActiveCfg = Debug|x64
|
||||
@@ -2288,22 +2290,6 @@ Global
|
||||
{EBED240C-8702-452D-B764-6DB9DA9179AF}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{EBED240C-8702-452D-B764-6DB9DA9179AF}.Release|x64.ActiveCfg = Release|x64
|
||||
{EBED240C-8702-452D-B764-6DB9DA9179AF}.Release|x64.Build.0 = Release|x64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Debug|x64.Build.0 = Debug|x64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Release|x64.ActiveCfg = Release|x64
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Release|x64.Build.0 = Release|x64
|
||||
{50A9F3DE-CF0B-4CF0-AFDE-3A3E245D7734}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{50A9F3DE-CF0B-4CF0-AFDE-3A3E245D7734}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{50A9F3DE-CF0B-4CF0-AFDE-3A3E245D7734}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{50A9F3DE-CF0B-4CF0-AFDE-3A3E245D7734}.Debug|x64.Build.0 = Debug|x64
|
||||
{50A9F3DE-CF0B-4CF0-AFDE-3A3E245D7734}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{50A9F3DE-CF0B-4CF0-AFDE-3A3E245D7734}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{50A9F3DE-CF0B-4CF0-AFDE-3A3E245D7734}.Release|x64.ActiveCfg = Release|x64
|
||||
{50A9F3DE-CF0B-4CF0-AFDE-3A3E245D7734}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -2543,8 +2529,6 @@ Global
|
||||
{08F9155D-B6DC-46E5-9C83-AF60B655898B} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
|
||||
{4382A954-179A-4078-92AF-715187DFFF50} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
|
||||
{EBED240C-8702-452D-B764-6DB9DA9179AF} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA}
|
||||
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA}
|
||||
{50A9F3DE-CF0B-4CF0-AFDE-3A3E245D7734} = {929C1324-22E8-4412-A9A8-80E85F3985A5}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
using OpenQA.Selenium.Interactions;
|
||||
using OpenQA.Selenium.Remote;
|
||||
using OpenQA.Selenium.Support.Events;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -18,12 +18,6 @@ namespace Microsoft.PowerToys.UITest
|
||||
this.by = by;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
// override ToString to return detailed debugging content provided by OpenQA.Selenium.By
|
||||
return this.by.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a By object using the name attribute.
|
||||
/// </summary>
|
||||
|
||||
@@ -3,11 +3,16 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Appium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
using OpenQA.Selenium.Interactions;
|
||||
using OpenQA.Selenium.Remote;
|
||||
using OpenQA.Selenium.Support.Events;
|
||||
using static Microsoft.PowerToys.UITest.UITestBase;
|
||||
|
||||
[assembly: InternalsVisibleTo("Session")]
|
||||
|
||||
@@ -16,10 +21,9 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <summary>
|
||||
/// Represents a basic UI element in the application.
|
||||
/// </summary>
|
||||
public abstract class Element
|
||||
public class Element
|
||||
{
|
||||
private WindowsElement? windowsElement;
|
||||
|
||||
private WindowsDriver<WindowsElement>? driver;
|
||||
|
||||
internal void SetWindowsElement(WindowsElement windowsElement) => this.windowsElement = windowsElement;
|
||||
@@ -39,20 +43,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
public string Text
|
||||
{
|
||||
get { return this.windowsElement?.Text ?? string.Empty; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the UI element is Enabled or not.
|
||||
/// </summary>
|
||||
public bool Enabled
|
||||
{
|
||||
get { return this.windowsElement?.Enabled ?? false; }
|
||||
}
|
||||
|
||||
public bool Selected
|
||||
{
|
||||
get { return this.windowsElement?.Selected ?? false; }
|
||||
get { return GetAttribute("Value"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -87,19 +78,26 @@ namespace Microsoft.PowerToys.UITest
|
||||
get { return GetAttribute("ControlType"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the UI element is enabled.
|
||||
/// </summary>
|
||||
/// <returns>True if the element is enabled; otherwise, false.</returns>
|
||||
public bool IsEnabled() => GetAttribute("IsEnabled") == "True";
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the UI element is selected.
|
||||
/// </summary>
|
||||
/// <returns>True if the element is selected; otherwise, false.</returns>
|
||||
public bool IsSelected() => GetAttribute("IsSelected") == "True";
|
||||
|
||||
/// <summary>
|
||||
/// Click the UI element.
|
||||
/// </summary>
|
||||
/// <param name="rightClick">If true, performs a right-click; otherwise, performs a left-click. Default value is false</param>
|
||||
/// <param name="rightClick">If true, performs a right-click; otherwise, performs a left-click.</param>
|
||||
public void Click(bool rightClick = false)
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
PerformAction(actions =>
|
||||
{
|
||||
actions.MoveToElement(windowElement);
|
||||
|
||||
// Move 2by2 offset to make click more stable instead of click on the border of the element
|
||||
actions.MoveByOffset(2, 2);
|
||||
|
||||
if (rightClick)
|
||||
{
|
||||
actions.ContextClick();
|
||||
@@ -108,25 +106,6 @@ namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
actions.Click();
|
||||
}
|
||||
|
||||
actions.Build().Perform();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Double Click the UI element.
|
||||
/// </summary>
|
||||
public void DoubleClick()
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
actions.MoveToElement(windowElement);
|
||||
|
||||
// Move 2by2 offset to make click more stable instead of click on the border of the element
|
||||
actions.MoveByOffset(2, 2);
|
||||
|
||||
actions.DoubleClick();
|
||||
actions.Build().Perform();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -154,7 +133,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
where T : Element, new()
|
||||
{
|
||||
Assert.IsNotNull(this.windowsElement, $"WindowsElement is null in method Find<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
|
||||
var foundElement = FindHelper.Find<T, AppiumWebElement>(
|
||||
var foundElement = FindElementHelper.Find<T, AppiumWebElement>(
|
||||
() =>
|
||||
{
|
||||
var element = this.windowsElement.FindElement(by.ToSeleniumBy());
|
||||
@@ -178,7 +157,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
where T : Element, new()
|
||||
{
|
||||
Assert.IsNotNull(this.windowsElement, $"WindowsElement is null in method FindAll<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
|
||||
var foundElements = FindHelper.FindAll<T, AppiumWebElement>(
|
||||
var foundElements = FindElementHelper.FindAll<T, AppiumWebElement>(
|
||||
() =>
|
||||
{
|
||||
var elements = this.windowsElement.FindElements(by.ToSeleniumBy());
|
||||
@@ -194,24 +173,13 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <summary>
|
||||
/// Simulates a manual operation on the element.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to perform on the element.</param>
|
||||
/// <param name="msPreAction">The number of milliseconds to wait before the action. Default value is 100 ms</param>
|
||||
/// <param name="msPostAction">The number of milliseconds to wait after the action. Default value is 100 ms</param>
|
||||
protected void PerformAction(Action<Actions, WindowsElement> action, int msPreAction = 100, int msPostAction = 100)
|
||||
private void PerformAction(Action<Actions> action)
|
||||
{
|
||||
if (msPreAction > 0)
|
||||
{
|
||||
Task.Delay(msPreAction).Wait();
|
||||
}
|
||||
|
||||
var windowElement = this.windowsElement!;
|
||||
var element = this.windowsElement;
|
||||
Actions actions = new Actions(this.driver);
|
||||
action(actions, windowElement);
|
||||
|
||||
if (msPostAction > 0)
|
||||
{
|
||||
Task.Delay(msPostAction).Wait();
|
||||
}
|
||||
actions.MoveToElement(element);
|
||||
action(actions);
|
||||
actions.Build().Perform();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,16 @@
|
||||
// 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.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Appium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
|
||||
[assembly: InternalsVisibleTo("Element")]
|
||||
@@ -15,7 +22,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <summary>
|
||||
/// Helper class for finding elements.
|
||||
/// </summary>
|
||||
internal static class FindHelper
|
||||
internal static class FindElementHelper
|
||||
{
|
||||
public static T Find<T, TW>(Func<TW> findElementFunc, WindowsDriver<WindowsElement>? driver, int timeoutMS)
|
||||
where T : Element, new()
|
||||
@@ -44,12 +51,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
Assert.IsNotNull(element, $"New Element {typeof(T).Name} error: element is null.");
|
||||
|
||||
T newElement = new T();
|
||||
if (timeoutMS > 0)
|
||||
{
|
||||
// Only set timeout if it is positive value
|
||||
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromMilliseconds(timeoutMS);
|
||||
}
|
||||
|
||||
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromMilliseconds(timeoutMS);
|
||||
newElement.SetSession(driver);
|
||||
newElement.SetWindowsElement(element);
|
||||
return newElement;
|
||||
@@ -1,40 +0,0 @@
|
||||
// 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 OpenQA.Selenium;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a textbox in the UI test environment.
|
||||
/// </summary>
|
||||
public class TextBox : Element
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the text of the textbox.
|
||||
/// </summary>
|
||||
/// <param name="value">The text to set.</param>
|
||||
/// <param name="clearText">A value indicating whether to clear the text before setting it. Default value is true</param>
|
||||
/// <returns>The current TextBox instance.</returns>
|
||||
public TextBox SetText(string value, bool clearText = true)
|
||||
{
|
||||
if (clearText)
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
// select all text and delete it
|
||||
windowElement.SendKeys(Keys.Control + "a");
|
||||
windowElement.SendKeys(Keys.Delete);
|
||||
});
|
||||
}
|
||||
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
windowElement.SendKeys(value);
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
// 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 Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a ToggleSwitch in the UI test environment.
|
||||
/// </summary>
|
||||
public class ToggleSwitch : Button
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the ToggleSwitch is on.
|
||||
/// </summary>
|
||||
public bool IsOn
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.Selected;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the ToggleSwitch to the specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">A value indicating whether the ToggleSwitch should be active. Default is true</param>
|
||||
/// <returns>The current ToggleSwitch instance.</returns>
|
||||
public ToggleSwitch Toggle(bool value = true)
|
||||
{
|
||||
if (this.IsOn != value)
|
||||
{
|
||||
// Toggle the switch
|
||||
this.Click();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,13 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
using OpenQA.Selenium.Interactions;
|
||||
using OpenQA.Selenium.Remote;
|
||||
using OpenQA.Selenium.Support.Events;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -2,11 +2,17 @@
|
||||
// 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.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Appium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
using OpenQA.Selenium.Interactions;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
@@ -39,7 +45,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
where T : Element, new()
|
||||
{
|
||||
Assert.IsNotNull(this.WindowsDriver, $"WindowsElement is null in method Find<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
|
||||
var foundElement = FindHelper.Find<T, WindowsElement>(
|
||||
var foundElement = FindElementHelper.Find<T, WindowsElement>(
|
||||
() =>
|
||||
{
|
||||
var element = this.WindowsDriver.FindElement(by.ToSeleniumBy());
|
||||
@@ -59,20 +65,21 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <param name="by">The selector to find the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 3000)
|
||||
public ReadOnlyCollection<T>? FindAll<T>(By by, int timeoutMS = 3000)
|
||||
where T : Element, new()
|
||||
{
|
||||
Assert.IsNotNull(this.WindowsDriver, $"WindowsElement is null in method FindAll<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
|
||||
var foundElements = FindHelper.FindAll<T, WindowsElement>(
|
||||
var foundElements = FindElementHelper.FindAll<T, WindowsElement>(
|
||||
() =>
|
||||
{
|
||||
var elements = this.WindowsDriver.FindElements(by.ToSeleniumBy());
|
||||
Assert.IsTrue(elements.Count > 0, $"Elements not found using selector: {by}");
|
||||
return elements;
|
||||
},
|
||||
this.WindowsDriver,
|
||||
timeoutMS);
|
||||
|
||||
return foundElements ?? new ReadOnlyCollection<T>(new List<T>());
|
||||
return foundElements;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -103,7 +110,6 @@ namespace Microsoft.PowerToys.UITest
|
||||
SetForegroundWindow(windowHandle);
|
||||
var hexWindowHandle = windowHandle.ToString("x");
|
||||
var appCapabilities = new AppiumOptions();
|
||||
|
||||
appCapabilities.AddAdditionalCapability("appTopLevelWindow", hexWindowHandle);
|
||||
appCapabilities.AddAdditionalCapability("deviceName", "WindowsPC");
|
||||
this.WindowsDriver = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), appCapabilities);
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="..\..\Common.Dotnet.CsWinRT.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<PublishAot>true</PublishAot>
|
||||
@@ -16,4 +15,7 @@
|
||||
<PackageReference Include="System.IO.Abstractions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -2,13 +2,17 @@
|
||||
// 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.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Appium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
using OpenQA.Selenium.Interactions;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
@@ -33,34 +37,6 @@ namespace Microsoft.PowerToys.UITest
|
||||
this.testInit.Cleanup();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an element by selector.
|
||||
/// Shortcut for this.Session.Find<T>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="by">The selector to find the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected T Find<T>(By by, int timeoutMS = 3000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.Find<T>(by, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all elements by selector.
|
||||
/// Shortcut for this.Session.FindAll<T>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the elements, should be Element or its derived class.</typeparam>
|
||||
/// <param name="by">The selector to find the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
protected ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 3000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.FindAll<T>(by, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Nested class for test initialization.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
// 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.Threading.Tasks;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Hosts.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class HostModuleTests : UITestBase
|
||||
{
|
||||
public HostModuleTests()
|
||||
: base(PowerToysModule.Hosts)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test if Empty-view is shown when no entries are present.
|
||||
/// And 'Add an entry' button from Empty-view is functional.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestEmptyView()
|
||||
{
|
||||
this.CloseWarningDialog();
|
||||
this.RemoveAllEntries();
|
||||
|
||||
// 'Add an entry' button (only show-up when list is empty) should be visible
|
||||
Assert.IsTrue(this.FindAll<Button>(By.Name("Add an entry")).Count == 1, "'Add an entry' button should be visible in the empty view");
|
||||
|
||||
// Click 'Add an entry' from empty-view for adding Host override rule
|
||||
this.Find<Button>(By.Name("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.FindAll<Button>(By.Name("Delete")).Count == 1, "Should have one row now");
|
||||
Assert.IsTrue(this.FindAll<Button>(By.Name("Add an entry")).Count == 0, "'Add an entry' button should be invisible if not empty view");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test if 'New entry' button is functional
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestAddingEntry()
|
||||
{
|
||||
this.CloseWarningDialog();
|
||||
this.RemoveAllEntries();
|
||||
|
||||
Assert.IsTrue(this.FindAll<Button>(By.Name("Delete")).Count == 0, "Should have no row after removing all");
|
||||
|
||||
this.AddEntry("192.168.0.1", "localhost", true);
|
||||
|
||||
Assert.IsTrue(this.FindAll<Button>(By.Name("Delete")).Count == 1, "Should have one row now");
|
||||
}
|
||||
|
||||
private void AddEntry(string ip, string host, bool active = true, bool clickAddEntryButton = true)
|
||||
{
|
||||
if (clickAddEntryButton)
|
||||
{
|
||||
// Click 'Add an entry' for adding Host override rule
|
||||
this.Find<Button>(By.Name("New entry")).Click();
|
||||
}
|
||||
|
||||
// Adding a new host override localhost -> 192.168.0.1
|
||||
Assert.IsFalse(this.Find<Button>(By.Name("Add")).Enabled, "Add button should be Disabled by default");
|
||||
|
||||
Assert.IsTrue(this.Find<TextBox>(By.Name("Address")).SetText(ip, false).Text == ip);
|
||||
Assert.IsTrue(this.Find<TextBox>(By.Name("Hosts")).SetText(host, false).Text == host);
|
||||
|
||||
this.Find<ToggleSwitch>(By.Name("Active")).Toggle(active);
|
||||
|
||||
Assert.IsTrue(this.Find<Button>(By.Name("Add")).Enabled, "Add button should be Enabled after providing valid inputs");
|
||||
|
||||
// Add the entry
|
||||
this.Find<Button>(By.Name("Add")).Click();
|
||||
|
||||
// 0.5 second delay after adding an entry
|
||||
Task.Delay(500).Wait();
|
||||
}
|
||||
|
||||
private void CloseWarningDialog()
|
||||
{
|
||||
// Find 'Accept' button which come in 'Warning' dialog
|
||||
if (this.FindAll<Window>(By.Name("Warning")).Count > 0 &&
|
||||
this.FindAll<Button>(By.Name("Accept")).Count > 0)
|
||||
{
|
||||
// Hide Warning dialog if any
|
||||
this.Find<Button>(By.Name("Accept")).Click();
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveAllEntries()
|
||||
{
|
||||
// Delete all existing host-override rules
|
||||
foreach (var deleteBtn in this.FindAll<Button>(By.Name("Delete")))
|
||||
{
|
||||
deleteBtn.Click();
|
||||
this.Find<Button>(By.Name("Yes")).Click();
|
||||
}
|
||||
|
||||
// Should have no row left, and no more delete button
|
||||
Assert.IsTrue(this.FindAll<Button>(By.Name("Delete")).Count == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<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>
|
||||
@@ -1,177 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Resources;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Microsoft.Win32;
|
||||
using RegistryPreviewUILib;
|
||||
|
||||
namespace RegistryPreview.FuzzTests
|
||||
{
|
||||
public class FuzzTests
|
||||
{
|
||||
private const string REGISTRYHEADER4 = "regedit4";
|
||||
private const string REGISTRYHEADER5 = "windows registry editor version 5.00";
|
||||
private const string KEYIMAGE = "ms-appx:///Assets/RegistryPreview/folder32.png";
|
||||
private const string DELETEDKEYIMAGE = "ms-appx:///Assets/RegistryPreview/deleted-folder32.png";
|
||||
|
||||
// Case 1: Fuzz test for CheckKeyLineForBrackets
|
||||
public static void FuzzCheckKeyLineForBrackets(ReadOnlySpan<byte> input)
|
||||
{
|
||||
string registryLine;
|
||||
|
||||
// Simulate registry file content as filenameText
|
||||
var filenameText = GenerateRegistryHeader(input);
|
||||
|
||||
string[] registryLines = filenameText.Split("\r");
|
||||
|
||||
if (registryLines.Length <= 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// REG files have to start with one of two headers and it's case-insensitive
|
||||
// The header in the registry file is either REGISTRYHEADER4 or REGISTRYHEADER5
|
||||
registryLine = registryLines[0];
|
||||
|
||||
// Check if the registry header is valid
|
||||
if (!IsValidRegistryHeader(registryLine))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int index = 1;
|
||||
registryLine = registryLines[index]; // Extract content after the header
|
||||
|
||||
ParseHelper.ProcessRegistryLine(registryLine);
|
||||
if (registryLine.StartsWith("[-", StringComparison.InvariantCulture))
|
||||
{
|
||||
// remove the - as we won't need it but it will get special treatment in the UI
|
||||
registryLine = registryLine.Remove(1, 1);
|
||||
|
||||
string imageName = DELETEDKEYIMAGE;
|
||||
|
||||
// Fuzz test for the CheckKeyLineForBrackets method
|
||||
ParseHelper.CheckKeyLineForBrackets(ref registryLine, ref imageName);
|
||||
}
|
||||
else if (registryLine.StartsWith('['))
|
||||
{
|
||||
string imageName = KEYIMAGE;
|
||||
|
||||
// Fuzz test for the CheckKeyLineForBrackets method
|
||||
ParseHelper.CheckKeyLineForBrackets(ref registryLine, ref imageName);
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Case 1: Fuzz test for StripFirstAndLast
|
||||
public static void FuzzStripFirstAndLast(ReadOnlySpan<byte> input)
|
||||
{
|
||||
string registryLine;
|
||||
|
||||
var filenameText = GenerateRegistryHeader(input);
|
||||
|
||||
filenameText = filenameText.Replace("\r\n", "\r");
|
||||
string[] registryLines = filenameText.Split("\r");
|
||||
|
||||
if (registryLines.Length <= 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// REG files have to start with one of two headers and it's case-insensitive
|
||||
registryLine = registryLines[0];
|
||||
|
||||
if (!IsValidRegistryHeader(registryLine))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int index = 1;
|
||||
registryLine = registryLines[index];
|
||||
|
||||
ParseHelper.ProcessRegistryLine(registryLine);
|
||||
|
||||
if (registryLine.StartsWith("[-", StringComparison.InvariantCulture))
|
||||
{
|
||||
// remove the - as we won't need it but it will get special treatment in the UI
|
||||
registryLine = registryLine.Remove(1, 1);
|
||||
|
||||
string imageName = DELETEDKEYIMAGE;
|
||||
ParseHelper.CheckKeyLineForBrackets(ref registryLine, ref imageName);
|
||||
|
||||
// Fuzz test for the StripFirstAndLast method
|
||||
registryLine = ParseHelper.StripFirstAndLast(registryLine);
|
||||
}
|
||||
else if (registryLine.StartsWith('['))
|
||||
{
|
||||
string imageName = KEYIMAGE;
|
||||
ParseHelper.CheckKeyLineForBrackets(ref registryLine, ref imageName);
|
||||
|
||||
// Fuzz test for the StripFirstAndLast method
|
||||
registryLine = ParseHelper.StripFirstAndLast(registryLine);
|
||||
}
|
||||
else if (registryLine.StartsWith('"') && registryLine.EndsWith("=-", StringComparison.InvariantCulture))
|
||||
{
|
||||
registryLine = registryLine.Replace("=-", string.Empty);
|
||||
|
||||
// remove the "'s without removing all of them
|
||||
// Fuzz test for the StripFirstAndLast method
|
||||
registryLine = ParseHelper.StripFirstAndLast(registryLine);
|
||||
}
|
||||
else if (registryLine.StartsWith('"'))
|
||||
{
|
||||
int equal = registryLine.IndexOf('=');
|
||||
if ((equal < 0) || (equal > registryLine.Length - 1))
|
||||
{
|
||||
// something is very wrong
|
||||
return;
|
||||
}
|
||||
|
||||
// set the name and the value
|
||||
string name = registryLine.Substring(0, equal);
|
||||
|
||||
// trim the whitespace and quotes from the name
|
||||
name = name.Trim();
|
||||
|
||||
// Fuzz test for the StripFirstAndLast method
|
||||
name = ParseHelper.StripFirstAndLast(name);
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GenerateRegistryHeader(ReadOnlySpan<byte> input)
|
||||
{
|
||||
string header = new Random().Next(2) == 0 ? REGISTRYHEADER4 : REGISTRYHEADER5;
|
||||
|
||||
string inputText = System.Text.Encoding.UTF8.GetString(input);
|
||||
string filenameText = header + "\r\n" + inputText;
|
||||
|
||||
return filenameText.Replace("\r\n", "\r");
|
||||
}
|
||||
|
||||
private static bool IsValidRegistryHeader(string line)
|
||||
{
|
||||
// Convert the line to lowercase once for comparison
|
||||
var lineLower = line.ToLowerInvariant();
|
||||
|
||||
switch (line)
|
||||
{
|
||||
case REGISTRYHEADER4:
|
||||
case REGISTRYHEADER5:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
// 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.
|
||||
|
||||
[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
|
||||
@@ -1,90 +0,0 @@
|
||||
// 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.
|
||||
{
|
||||
"configVersion": 3,
|
||||
"entries": [
|
||||
{
|
||||
"fuzzer": {
|
||||
"$type": "libfuzzerDotNet",
|
||||
"dll": "RegistryPreview.FuzzTests.dll",
|
||||
"class": "RegistryPreview.FuzzTests.FuzzTests",
|
||||
"method": "FuzzCheckKeyLineForBrackets",
|
||||
"FuzzingTargetBinaries": [
|
||||
"PowerToys.RegistryPreview.dll"
|
||||
]
|
||||
},
|
||||
"adoTemplate": {
|
||||
// supply the values appropriate to your
|
||||
// project, where bugs will be filed
|
||||
"org": "microsoft",
|
||||
"project": "OS",
|
||||
"AssignedTo": "mengyuanchen@microsoft.com",
|
||||
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\SHINE\\PowerToys",
|
||||
"IterationPath": "OS\\Future"
|
||||
},
|
||||
"jobNotificationEmail": "mengyuanchen@microsoft.com",
|
||||
"skip": false,
|
||||
"rebootAfterSetup": false,
|
||||
"oneFuzzJobs": [
|
||||
// at least one job is required
|
||||
{
|
||||
"projectName": "RegistryPreview",
|
||||
"targetName": "RegistryPreview-dotnet-CheckKeyLineForBrackets-fuzzer"
|
||||
}
|
||||
],
|
||||
"jobDependencies": [
|
||||
// this should contain, at minimum,
|
||||
// the DLL and PDB files
|
||||
// you will need to add any other files required
|
||||
// (globs are supported)
|
||||
"RegistryPreview.FuzzTests.dll",
|
||||
"RegistryPreview.FuzzTests.pdb",
|
||||
"Microsoft.Windows.SDK.NET.dll",
|
||||
"WinRT.Runtime.dll"
|
||||
],
|
||||
"SdlWorkItemId": 49911822
|
||||
},
|
||||
{
|
||||
"fuzzer": {
|
||||
"$type": "libfuzzerDotNet",
|
||||
"dll": "RegistryPreview.FuzzTests.dll",
|
||||
"class": "RegistryPreview.FuzzTests.FuzzTests",
|
||||
"method": "FuzzStripFirstAndLast",
|
||||
"FuzzingTargetBinaries": [
|
||||
"PowerToys.RegistryPreview.dll"
|
||||
]
|
||||
},
|
||||
"adoTemplate": {
|
||||
// supply the values appropriate to your
|
||||
// project, where bugs will be filed
|
||||
"org": "microsoft",
|
||||
"project": "OS",
|
||||
"AssignedTo": "mengyuanchen@microsoft.com",
|
||||
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\SHINE\\PowerToys",
|
||||
"IterationPath": "OS\\Future"
|
||||
},
|
||||
"jobNotificationEmail": "mengyuanchen@microsoft.com",
|
||||
"skip": false,
|
||||
"rebootAfterSetup": false,
|
||||
"oneFuzzJobs": [
|
||||
// at least one job is required
|
||||
{
|
||||
"projectName": "RegistryPreview",
|
||||
"targetName": "RegistryPreview-dotnet-StripFirstAndLasts-fuzzer"
|
||||
}
|
||||
],
|
||||
"jobDependencies": [
|
||||
// this should contain, at minimum,
|
||||
// the DLL and PDB files
|
||||
// you will need to add any other files required
|
||||
// (globs are supported)
|
||||
"RegistryPreview.FuzzTests.dll",
|
||||
"RegistryPreview.FuzzTests.pdb",
|
||||
"Microsoft.Windows.SDK.NET.dll",
|
||||
"WinRT.Runtime.dll"
|
||||
],
|
||||
"SdlWorkItemId": 49911822
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||
<Platforms>x64;ARM64</Platforms>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\tests\RegistryPreview.FuzzTests\</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\RegistryPreviewUILib\ParseHelper.cs" Link="ParseHelper.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="OneFuzzConfig.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSTest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,112 +0,0 @@
|
||||
// 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 RegistryPreviewUILib
|
||||
{
|
||||
public class ParseHelper
|
||||
{
|
||||
private const string ERRORIMAGE = "ms-appx:///Assets/RegistryPreview/error32.png";
|
||||
|
||||
/// <summary>
|
||||
/// Checks a Key line for the closing bracket and treat it as an error if it cannot be found
|
||||
/// </summary>
|
||||
public static void CheckKeyLineForBrackets(ref string registryLine, ref string imageName)
|
||||
{
|
||||
// following the current behavior of the registry editor, find the last ] and treat everything else as ignorable
|
||||
int lastBracket = registryLine.LastIndexOf(']');
|
||||
if (lastBracket == -1)
|
||||
{
|
||||
// since we don't have a last bracket yet, add an extra space and continue processing
|
||||
registryLine += " ";
|
||||
imageName = ERRORIMAGE;
|
||||
}
|
||||
else
|
||||
{
|
||||
// having found the last ] and there is text after it, drop the rest of the string on the floor
|
||||
if (lastBracket < registryLine.Length - 1)
|
||||
{
|
||||
registryLine = registryLine.Substring(0, lastBracket + 1);
|
||||
}
|
||||
|
||||
if (CheckForKnownGoodBranches(registryLine) == false)
|
||||
{
|
||||
imageName = ERRORIMAGE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make sure the root of a full path start with one of the five "hard coded" roots. Throw an error for the branch if it doesn't.
|
||||
/// </summary>
|
||||
private static bool CheckForKnownGoodBranches(string key)
|
||||
{
|
||||
if ((key.StartsWith("[HKEY_CLASSES_ROOT]", StringComparison.InvariantCultureIgnoreCase) == false &&
|
||||
key.StartsWith("[HKEY_CURRENT_USER]", StringComparison.InvariantCultureIgnoreCase) == false &&
|
||||
key.StartsWith("[HKEY_USERS]", StringComparison.InvariantCultureIgnoreCase) == false &&
|
||||
key.StartsWith("[HKEY_LOCAL_MACHINE]", StringComparison.InvariantCultureIgnoreCase) == false &&
|
||||
key.StartsWith("[HKEY_CURRENT_CONFIG]", StringComparison.InvariantCultureIgnoreCase) == false)
|
||||
&&
|
||||
(key.StartsWith(@"[HKEY_CLASSES_ROOT\", StringComparison.InvariantCultureIgnoreCase) == false &&
|
||||
key.StartsWith(@"[HKEY_CURRENT_USER\", StringComparison.InvariantCultureIgnoreCase) == false &&
|
||||
key.StartsWith(@"[HKEY_USERS\", StringComparison.InvariantCultureIgnoreCase) == false &&
|
||||
key.StartsWith(@"[HKEY_LOCAL_MACHINE\", StringComparison.InvariantCultureIgnoreCase) == false &&
|
||||
key.StartsWith(@"[HKEY_CURRENT_CONFIG\", StringComparison.InvariantCultureIgnoreCase) == false)
|
||||
&&
|
||||
(key.StartsWith("[HKCR]", StringComparison.InvariantCultureIgnoreCase) == false &&
|
||||
key.StartsWith("[HKCU]", StringComparison.InvariantCultureIgnoreCase) == false &&
|
||||
key.StartsWith("[HKU]", StringComparison.InvariantCultureIgnoreCase) == false &&
|
||||
key.StartsWith("[HKLM]", StringComparison.InvariantCultureIgnoreCase) == false &&
|
||||
key.StartsWith("[HKCC]", StringComparison.InvariantCultureIgnoreCase) == false)
|
||||
&&
|
||||
(key.StartsWith(@"[HKCR\", StringComparison.InvariantCultureIgnoreCase) == false &&
|
||||
key.StartsWith(@"[HKCU\", StringComparison.InvariantCultureIgnoreCase) == false &&
|
||||
key.StartsWith(@"[HKU\", StringComparison.InvariantCultureIgnoreCase) == false &&
|
||||
key.StartsWith(@"[HKLM\", StringComparison.InvariantCultureIgnoreCase) == false &&
|
||||
key.StartsWith(@"[HKCC\", StringComparison.InvariantCultureIgnoreCase) == false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rip the first and last character off a string,
|
||||
/// checking that the string is at least 2 characters long to avoid errors
|
||||
/// </summary>
|
||||
public static string StripFirstAndLast(string line)
|
||||
{
|
||||
if (line.Length > 1)
|
||||
{
|
||||
line = line.Remove(line.Length - 1, 1);
|
||||
line = line.Remove(0, 1);
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
public static string ProcessRegistryLine(string registryLine)
|
||||
{
|
||||
if (registryLine.StartsWith("@=-", StringComparison.InvariantCulture))
|
||||
{
|
||||
// REG file has a callout to delete the @ Value which won't work *but* the Registry Editor will
|
||||
// clear the value of the @ Value instead, so it's still a valid line.
|
||||
registryLine = registryLine.Replace("@=-", "\"(Default)\"=\"\"");
|
||||
}
|
||||
else if (registryLine.StartsWith("@=", StringComparison.InvariantCulture))
|
||||
{
|
||||
// This is the Value called "(Default)" so we tweak the line for the UX
|
||||
registryLine = registryLine.Replace("@=", "\"(Default)\"=");
|
||||
}
|
||||
|
||||
return registryLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -225,9 +225,7 @@ namespace RegistryPreviewUILib
|
||||
{
|
||||
// special case for when the registryLine begins with a @ - make some tweaks and
|
||||
// let the regular processing handle the rest.
|
||||
registryLine = ParseHelper.ProcessRegistryLine(registryLine);
|
||||
|
||||
/* if (registryLine.StartsWith("@=-", StringComparison.InvariantCulture))
|
||||
if (registryLine.StartsWith("@=-", StringComparison.InvariantCulture))
|
||||
{
|
||||
// REG file has a callout to delete the @ Value which won't work *but* the Registry Editor will
|
||||
// clear the value of the @ Value instead, so it's still a valid line.
|
||||
@@ -237,7 +235,7 @@ namespace RegistryPreviewUILib
|
||||
{
|
||||
// This is the Value called "(Default)" so we tweak the line for the UX
|
||||
registryLine = registryLine.Replace("@=", "\"(Default)\"=");
|
||||
}*/
|
||||
}
|
||||
|
||||
// continue until we have nothing left to read
|
||||
// switch logic, based off what the current line we're reading is
|
||||
@@ -247,10 +245,10 @@ namespace RegistryPreviewUILib
|
||||
registryLine = registryLine.Remove(1, 1);
|
||||
|
||||
string imageName = DELETEDKEYIMAGE;
|
||||
ParseHelper.CheckKeyLineForBrackets(ref registryLine, ref imageName);
|
||||
CheckKeyLineForBrackets(ref registryLine, ref imageName);
|
||||
|
||||
// this is a key, so remove the first [ and last ]
|
||||
registryLine = ParseHelper.StripFirstAndLast(registryLine);
|
||||
registryLine = StripFirstAndLast(registryLine);
|
||||
|
||||
// do not track the result of this node, since it should have no children
|
||||
AddTextToTree(registryLine, imageName);
|
||||
@@ -258,10 +256,10 @@ namespace RegistryPreviewUILib
|
||||
else if (registryLine.StartsWith('['))
|
||||
{
|
||||
string imageName = KEYIMAGE;
|
||||
ParseHelper.CheckKeyLineForBrackets(ref registryLine, ref imageName);
|
||||
CheckKeyLineForBrackets(ref registryLine, ref imageName);
|
||||
|
||||
// this is a key, so remove the first [ and last ]
|
||||
registryLine = ParseHelper.StripFirstAndLast(registryLine);
|
||||
registryLine = StripFirstAndLast(registryLine);
|
||||
|
||||
treeViewNode = AddTextToTree(registryLine, imageName);
|
||||
lastKeyPath = registryLine;
|
||||
@@ -272,7 +270,7 @@ namespace RegistryPreviewUILib
|
||||
registryLine = registryLine.Replace("=-", string.Empty);
|
||||
|
||||
// remove the "'s without removing all of them
|
||||
registryLine = ParseHelper.StripFirstAndLast(registryLine);
|
||||
registryLine = StripFirstAndLast(registryLine);
|
||||
|
||||
// Create a new listview item that will be used to display the delete value and store it
|
||||
registryValue = new RegistryValue(registryLine, string.Empty, string.Empty, lastKeyPath);
|
||||
@@ -302,7 +300,7 @@ namespace RegistryPreviewUILib
|
||||
|
||||
// trim the whitespace and quotes from the name
|
||||
name = name.Trim();
|
||||
name = ParseHelper.StripFirstAndLast(name);
|
||||
name = StripFirstAndLast(name);
|
||||
|
||||
// Clean out any escaped characters in the value, only for the preview
|
||||
name = StripEscapedCharacters(name);
|
||||
@@ -328,7 +326,7 @@ namespace RegistryPreviewUILib
|
||||
|
||||
if (value.StartsWith('"') && value.EndsWith('"'))
|
||||
{
|
||||
value = ParseHelper.StripFirstAndLast(value);
|
||||
value = StripFirstAndLast(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1040,7 +1038,7 @@ namespace RegistryPreviewUILib
|
||||
/// <summary>
|
||||
/// Checks a Key line for the closing bracket and treat it as an error if it cannot be found
|
||||
/// </summary>
|
||||
/* private void CheckKeyLineForBrackets(ref string registryLine, ref string imageName)
|
||||
private void CheckKeyLineForBrackets(ref string registryLine, ref string imageName)
|
||||
{
|
||||
// following the current behavior of the registry editor, find the last ] and treat everything else as ignorable
|
||||
int lastBracket = registryLine.LastIndexOf(']');
|
||||
@@ -1063,7 +1061,7 @@ namespace RegistryPreviewUILib
|
||||
imageName = ERRORIMAGE;
|
||||
}
|
||||
}
|
||||
} */
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes a binary registry value, sees if it has a ; and dumps the rest of the line - this does not work for REG_SZ values
|
||||
@@ -1084,7 +1082,7 @@ namespace RegistryPreviewUILib
|
||||
/// <summary>
|
||||
/// Make sure the root of a full path start with one of the five "hard coded" roots. Throw an error for the branch if it doesn't.
|
||||
/// </summary>
|
||||
/* private bool CheckForKnownGoodBranches(string key)
|
||||
private bool CheckForKnownGoodBranches(string key)
|
||||
{
|
||||
if ((key.StartsWith("[HKEY_CLASSES_ROOT]", StringComparison.InvariantCultureIgnoreCase) == false &&
|
||||
key.StartsWith("[HKEY_CURRENT_USER]", StringComparison.InvariantCultureIgnoreCase) == false &&
|
||||
@@ -1114,7 +1112,7 @@ namespace RegistryPreviewUILib
|
||||
}
|
||||
|
||||
return true;
|
||||
} */
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turns the Open Key button in the command bar on/off, depending on if a key is selected
|
||||
|
||||
Reference in New Issue
Block a user