提交 491e8eb9 编写于 作者: S Sam Harwell

Use low-level COM interfaces for UI automation to avoid caches

上级 1804a31d
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.VisualStudio.IntegrationTest.Utilities;
using Microsoft.VisualStudio.IntegrationTest.Utilities.Input;
using Microsoft.VisualStudio.IntegrationTest.Utilities.OutOfProcess;
using System;
using System.Threading;
using System.Windows.Automation;
using Microsoft.VisualStudio.IntegrationTest.Utilities;
using Microsoft.VisualStudio.IntegrationTest.Utilities.Input;
namespace Roslyn.VisualStudio.IntegrationTests
{
......@@ -22,7 +20,7 @@ public abstract class AbstractIntegrationTest : IDisposable
protected AbstractIntegrationTest(
VisualStudioInstanceFactory instanceFactory)
{
Automation.TransactionTimeout = 20000;
Helper.Automation.TransactionTimeout = 20000;
_visualStudioContext = instanceFactory.GetNewOrUsedInstance(SharedIntegrationHostFixture.RequiredPackageIds);
VisualStudio = _visualStudioContext.Instance;
}
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Windows.Automation;
using Microsoft.VisualStudio.IntegrationTest.Utilities;
using Xunit;
......@@ -34,8 +33,8 @@ public void InteractiveWithDisplayFormAndWpfWindow()
wind.Title = ""wpf window text"";
wind.Show();");
AutomationElement form = AutomationElementHelper.FindAutomationElementAsync("win form text").Result;
AutomationElement wpf = AutomationElementHelper.FindAutomationElementAsync("wpf window text").Result;
var form = AutomationElementHelper.FindAutomationElementAsync("win form text").Result;
var wpf = AutomationElementHelper.FindAutomationElementAsync("wpf window text").Result;
// 3) Add UI elements to windows and verify
VisualStudio.InteractiveWindow.SubmitText(@"// add a label to the form
......@@ -47,11 +46,11 @@ public void InteractiveWithDisplayFormAndWpfWindow()
t.Text = ""wpf body text"";
wind.Content = t;");
AutomationElement formLabel = form.FindDescendantByPath("text");
Assert.Equal("forms label text", formLabel.Current.Name);
var formLabel = form.FindDescendantByPath("text");
Assert.Equal("forms label text", formLabel.CurrentName);
AutomationElement wpfContent = wpf.FindDescendantByPath("text");
Assert.Equal("wpf body text", wpfContent.Current.Name);
var wpfContent = wpf.FindDescendantByPath("text");
Assert.Equal("wpf body text", wpfContent.CurrentName);
// 4) Close windows
VisualStudio.InteractiveWindow.SubmitText(@"form.Close();
......
......@@ -2,25 +2,32 @@
using System;
using System.Collections.Generic;
using System.Windows.Automation;
using UIAutomationClient;
using AutomationElementIdentifiers = System.Windows.Automation.AutomationElementIdentifiers;
using ExpandCollapsePatternIdentifiers = System.Windows.Automation.ExpandCollapsePatternIdentifiers;
using InvokePatternIdentifiers = System.Windows.Automation.InvokePatternIdentifiers;
using SelectionItemPatternIdentifiers = System.Windows.Automation.SelectionItemPatternIdentifiers;
using TogglePatternIdentifiers = System.Windows.Automation.TogglePatternIdentifiers;
using ValuePatternIdentifiers = System.Windows.Automation.ValuePatternIdentifiers;
using ControlType = System.Windows.Automation.ControlType;
namespace Microsoft.VisualStudio.IntegrationTest.Utilities
{
public static class AutomationElementExtensions
{
/// <summary>
/// Given an <see cref="AutomationElement"/>, returns a descendant with the automation ID specified by <paramref name="automationId"/>.
/// Given an <see cref="IUIAutomationElement"/>, returns a descendant with the automation ID specified by <paramref name="automationId"/>.
/// Throws an <see cref="InvalidOperationException"/> if no such descendant is found.
/// </summary>
public static AutomationElement FindDescendantByAutomationId(this AutomationElement parent, string automationId)
public static IUIAutomationElement FindDescendantByAutomationId(this IUIAutomationElement parent, string automationId)
{
if (parent == null)
{
throw new ArgumentNullException(nameof(parent));
}
var condition = new PropertyCondition(AutomationElement.AutomationIdProperty, automationId);
var child = parent.FindFirst(TreeScope.Descendants, condition);
var condition = Helper.Automation.CreatePropertyCondition(AutomationElementIdentifiers.AutomationIdProperty.Id, automationId);
var child = parent.FindFirst(TreeScope.TreeScope_Descendants, condition);
if (child == null)
{
......@@ -31,18 +38,18 @@ public static AutomationElement FindDescendantByAutomationId(this AutomationElem
}
/// <summary>
/// Given an <see cref="AutomationElement"/>, returns a descendant with the automation ID specified by <paramref name="name"/>.
/// Given an <see cref="IUIAutomationElement"/>, returns a descendant with the automation ID specified by <paramref name="name"/>.
/// Throws an <see cref="InvalidOperationException"/> if no such descendant is found.
/// </summary>
public static AutomationElement FindDescendantByName(this AutomationElement parent, string name)
public static IUIAutomationElement FindDescendantByName(this IUIAutomationElement parent, string name)
{
if (parent == null)
{
throw new ArgumentNullException(nameof(parent));
}
var condition = new PropertyCondition(AutomationElement.NameProperty, name);
var child = parent.FindFirst(TreeScope.Descendants, condition);
var condition = Helper.Automation.CreatePropertyCondition(AutomationElementIdentifiers.NameProperty.Id, name);
var child = parent.FindFirst(TreeScope.TreeScope_Descendants, condition);
if (child == null)
{
......@@ -53,18 +60,18 @@ public static AutomationElement FindDescendantByName(this AutomationElement pare
}
/// <summary>
/// Given an <see cref="AutomationElement"/>, returns a descendant with the className specified by <paramref name="className"/>.
/// Given an <see cref="IUIAutomationElement"/>, returns a descendant with the className specified by <paramref name="className"/>.
/// Throws an <see cref="InvalidOperationException"/> if no such descendant is found.
/// </summary>
public static AutomationElement FindDescendantByClass(this AutomationElement parent, string className)
public static IUIAutomationElement FindDescendantByClass(this IUIAutomationElement parent, string className)
{
if (parent == null)
{
throw new ArgumentNullException(nameof(parent));
}
var condition = new PropertyCondition(AutomationElement.ClassNameProperty, className);
var child = parent.FindFirst(TreeScope.Descendants, condition);
var condition = Helper.Automation.CreatePropertyCondition(AutomationElementIdentifiers.ClassNameProperty.Id, className);
var child = parent.FindFirst(TreeScope.TreeScope_Descendants, condition);
if (child == null)
{
......@@ -75,31 +82,31 @@ public static AutomationElement FindDescendantByClass(this AutomationElement par
}
/// <summary>
/// Given an <see cref="AutomationElement"/>, returns all descendants with the given <paramref name="className"/>.
/// Given an <see cref="IUIAutomationElement"/>, returns all descendants with the given <paramref name="className"/>.
/// If none are found, the resulting collection will be empty.
/// </summary>
/// <returns></returns>
public static AutomationElementCollection FindDescendantsByClass(this AutomationElement parent, string className)
public static IUIAutomationElementArray FindDescendantsByClass(this IUIAutomationElement parent, string className)
{
if (parent == null)
{
throw new ArgumentNullException(nameof(parent));
}
var condition = new PropertyCondition(AutomationElement.ClassNameProperty, className);
return parent.FindAll(TreeScope.Descendants, condition);
var condition = Helper.Automation.CreatePropertyCondition(AutomationElementIdentifiers.ClassNameProperty.Id, className);
return parent.FindAll(TreeScope.TreeScope_Descendants, condition);
}
/// <summary>
/// Invokes an <see cref="AutomationElement"/>.
/// Invokes an <see cref="IUIAutomationElement"/>.
/// Throws an <see cref="InvalidOperationException"/> if <paramref name="element"/> does not
/// support the <see cref="InvokePattern"/>.
/// support the <see cref="IUIAutomationInvokePattern"/>.
/// </summary>
public static void Invoke(this AutomationElement element)
public static void Invoke(this IUIAutomationElement element)
{
if (element.TryGetCurrentPattern(InvokePattern.Pattern, out var invokePattern))
if (element.GetCurrentPattern(InvokePatternIdentifiers.Pattern.Id) is IUIAutomationInvokePattern invokePattern)
{
(invokePattern as InvokePattern).Invoke();
invokePattern.Invoke();
}
else
{
......@@ -108,15 +115,15 @@ public static void Invoke(this AutomationElement element)
}
/// <summary>
/// Expands an <see cref="AutomationElement"/>.
/// Expands an <see cref="IUIAutomationElement"/>.
/// Throws an <see cref="InvalidOperationException"/> if <paramref name="element"/> does not
/// support the <see cref="ExpandCollapsePattern"/>.
/// support the <see cref="IUIAutomationExpandCollapsePattern"/>.
/// </summary>
public static void Expand(this AutomationElement element)
public static void Expand(this IUIAutomationElement element)
{
if (element.TryGetCurrentPattern(ExpandCollapsePattern.Pattern, out var expandCollapsePattern))
if (element.GetCurrentPattern(ExpandCollapsePatternIdentifiers.Pattern.Id) is IUIAutomationExpandCollapsePattern expandCollapsePattern)
{
(expandCollapsePattern as ExpandCollapsePattern).Expand();
expandCollapsePattern.Expand();
}
else
{
......@@ -125,15 +132,15 @@ public static void Expand(this AutomationElement element)
}
/// <summary>
/// Collapses an <see cref="AutomationElement"/>.
/// Collapses an <see cref="IUIAutomationElement"/>.
/// Throws an <see cref="InvalidOperationException"/> if <paramref name="element"/> does not
/// support the <see cref="ExpandCollapsePattern"/>.
/// support the <see cref="IUIAutomationExpandCollapsePattern"/>.
/// </summary>
public static void Collapse(this AutomationElement element)
public static void Collapse(this IUIAutomationElement element)
{
if (element.TryGetCurrentPattern(ExpandCollapsePattern.Pattern, out var expandCollapsePattern))
if (element.GetCurrentPattern(ExpandCollapsePatternIdentifiers.Pattern.Id) is IUIAutomationExpandCollapsePattern expandCollapsePattern)
{
(expandCollapsePattern as ExpandCollapsePattern).Collapse();
expandCollapsePattern.Collapse();
}
else
{
......@@ -142,15 +149,15 @@ public static void Collapse(this AutomationElement element)
}
/// <summary>
/// Selects an <see cref="AutomationElement"/>.
/// Selects an <see cref="IUIAutomationElement"/>.
/// Throws an <see cref="InvalidOperationException"/> if <paramref name="element"/> does not
/// support the <see cref="SelectionItemPattern"/>.
/// support the <see cref="IUIAutomationSelectionItemPattern"/>.
/// </summary>
public static void Select(this AutomationElement element)
public static void Select(this IUIAutomationElement element)
{
if (element.TryGetCurrentPattern(SelectionItemPattern.Pattern, out var selectionItemPattern))
if (element.GetCurrentPattern(SelectionItemPatternIdentifiers.Pattern.Id) is IUIAutomationSelectionItemPattern selectionItemPattern)
{
(selectionItemPattern as SelectionItemPattern).Select();
selectionItemPattern.Select();
}
else
{
......@@ -159,15 +166,15 @@ public static void Select(this AutomationElement element)
}
/// <summary>
/// Gets the value of the given <see cref="AutomationElement"/>.
/// Gets the value of the given <see cref="IUIAutomationElement"/>.
/// Throws an <see cref="InvalidOperationException"/> if <paramref name="element"/> does not
/// support the <see cref="ValuePattern"/>.
/// support the <see cref="IUIAutomationValuePattern"/>.
/// </summary>
public static string GetValue(this AutomationElement element)
public static string GetValue(this IUIAutomationElement element)
{
if (element.TryGetCurrentPattern(ValuePattern.Pattern, out var valuePattern))
if (element.GetCurrentPattern(ValuePatternIdentifiers.Pattern.Id) is IUIAutomationValuePattern valuePattern)
{
return (valuePattern as ValuePattern).Current.Value;
return valuePattern.CurrentValue;
}
else
{
......@@ -176,15 +183,15 @@ public static string GetValue(this AutomationElement element)
}
/// <summary>
/// Sets the value of the given <see cref="AutomationElement"/>.
/// Sets the value of the given <see cref="IUIAutomationElement"/>.
/// Throws an <see cref="InvalidOperationException"/> if <paramref name="element"/> does not
/// support the <see cref="ValuePattern"/>.
/// support the <see cref="IUIAutomationValuePattern"/>.
/// </summary>
public static void SetValue(this AutomationElement element, string value)
public static void SetValue(this IUIAutomationElement element, string value)
{
if (element.TryGetCurrentPattern(ValuePattern.Pattern, out var valuePattern))
if (element.GetCurrentPattern(ValuePatternIdentifiers.Pattern.Id) is IUIAutomationValuePattern valuePattern)
{
(valuePattern as ValuePattern).SetValue(value);
valuePattern.SetValue(value);
}
else
{
......@@ -193,21 +200,21 @@ public static void SetValue(this AutomationElement element, string value)
}
/// <summary>
/// Given an <see cref="AutomationElement"/>, returns a descendent following the <paramref name="path"/>.
/// Given an <see cref="IUIAutomationElement"/>, returns a descendent following the <paramref name="path"/>.
/// Throws an <see cref="InvalidOperationException"/> if no such descendant is found.
/// </summary>
public static AutomationElement FindDescendantByPath(this AutomationElement element, string path)
public static IUIAutomationElement FindDescendantByPath(this IUIAutomationElement element, string path)
{
string[] pathParts = path.Split(".".ToCharArray());
// traverse the path
AutomationElement item = element;
AutomationElement next = null;
IUIAutomationElement item = element;
IUIAutomationElement next = null;
foreach (string pathPart in pathParts)
{
next = item.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.LocalizedControlTypeProperty, pathPart));
next = item.FindFirst(TreeScope.TreeScope_Descendants, Helper.Automation.CreatePropertyCondition(AutomationElementIdentifiers.LocalizedControlTypeProperty.Id, pathPart));
if (next == null)
{
......@@ -220,16 +227,17 @@ public static AutomationElement FindDescendantByPath(this AutomationElement elem
return item;
}
private static void ThrowUnableToFindChildException(string path, AutomationElement item)
private static void ThrowUnableToFindChildException(string path, IUIAutomationElement item)
{
// if not found, build a list of available children for debugging purposes
var validChildren = new List<string>();
try
{
foreach (AutomationElement sub in item.CachedChildren)
var children = item.GetCachedChildren();
for (int i = 0; i < children.Length; i++)
{
validChildren.Add(SimpleControlTypeName(sub));
validChildren.Add(SimpleControlTypeName(children.GetElement(i)));
}
}
catch (InvalidOperationException)
......@@ -242,24 +250,24 @@ private static void ThrowUnableToFindChildException(string path, AutomationEleme
string.Join(", ", validChildren)));
}
private static string SimpleControlTypeName(AutomationElement element)
private static string SimpleControlTypeName(IUIAutomationElement element)
{
ControlType type = element.GetCurrentPropertyValue(AutomationElement.ControlTypeProperty, true) as ControlType;
return type == null ? null : type.LocalizedControlType;
var type = ControlType.LookupById((int)element.GetCurrentPropertyValue(AutomationElementIdentifiers.ControlTypeProperty.Id));
return type?.LocalizedControlType;
}
/// <summary>
/// Returns true if the given <see cref="AutomationElement"/> is in the <see cref="ToggleState.On"/> state.
/// Returns true if the given <see cref="IUIAutomationElement"/> is in the <see cref="ToggleState.ToggleState_On"/> state.
/// Throws an <see cref="InvalidOperationException"/> if <paramref name="element"/> does not
/// support the <see cref="TogglePattern"/>.
/// support the <see cref="IUIAutomationTogglePattern"/>.
/// </summary>
/// <param name="element"></param>
/// <returns></returns>
public static bool IsToggledOn(this AutomationElement element)
public static bool IsToggledOn(this IUIAutomationElement element)
{
if (element.TryGetCurrentPattern(TogglePattern.Pattern, out var togglePattern))
if (element.GetCurrentPattern(TogglePatternIdentifiers.Pattern.Id) is IUIAutomationTogglePattern togglePattern)
{
return (togglePattern as TogglePattern).Current.ToggleState == ToggleState.On;
return togglePattern.CurrentToggleState == ToggleState.ToggleState_On;
}
else
{
......@@ -268,15 +276,15 @@ public static bool IsToggledOn(this AutomationElement element)
}
/// <summary>
/// Cycles through the <see cref="ToggleState"/>s of the given <see cref="AutomationElement"/>.
/// Cycles through the <see cref="ToggleState"/>s of the given <see cref="IUIAutomationElement"/>.
/// </summary>
/// Throws an <see cref="InvalidOperationException"/> if <paramref name="element"/> does not
/// support the <see cref="TogglePattern"/>.
public static void Toggle(this AutomationElement element)
/// support the <see cref="IUIAutomationTogglePattern"/>.
public static void Toggle(this IUIAutomationElement element)
{
if (element.TryGetCurrentPattern(TogglePattern.Pattern, out var togglePattern))
if (element.GetCurrentPattern(TogglePatternIdentifiers.Pattern.Id) is IUIAutomationTogglePattern togglePattern)
{
(togglePattern as TogglePattern).Toggle();
togglePattern.Toggle();
}
else
{
......@@ -285,11 +293,11 @@ public static void Toggle(this AutomationElement element)
}
/// <summary>
/// Given an <see cref="AutomationElement"/> returns a string representing the "name" of the element, if it has one.
/// Given an <see cref="IUIAutomationElement"/> returns a string representing the "name" of the element, if it has one.
/// </summary>
private static string GetNameForExceptionMessage(this AutomationElement element)
private static string GetNameForExceptionMessage(this IUIAutomationElement element)
{
return element.Current.AutomationId ?? element.Current.Name ?? "<unnamed>";
return element.CurrentAutomationId ?? element.CurrentName ?? "<unnamed>";
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using System.Windows.Automation;
using UIAutomationClient;
using AutomationElementIdentifiers = System.Windows.Automation.AutomationElementIdentifiers;
using InvokePatternIdentifiers = System.Windows.Automation.InvokePatternIdentifiers;
namespace Microsoft.VisualStudio.IntegrationTest.Utilities
{
......@@ -19,13 +22,15 @@ public static async Task ClickAutomationElementAsync(string elementName, bool re
{
var tcs = new TaskCompletionSource<object>();
Automation.AddAutomationEventHandler(InvokePattern.InvokedEvent, element, TreeScope.Element, (src, e) => {
tcs.SetResult(null);
});
Helper.Automation.AddAutomationEventHandler(
InvokePatternIdentifiers.InvokedEvent.Id,
element,
TreeScope.TreeScope_Element,
cacheRequest: null,
new AutomationEventHandler((src, e) => tcs.SetResult(null)));
if (element.TryGetCurrentPattern(InvokePattern.Pattern, out var invokePatternObj))
if (element.GetCurrentPattern(InvokePatternIdentifiers.Pattern.Id) is IUIAutomationInvokePattern invokePattern)
{
var invokePattern = (InvokePattern)invokePatternObj;
invokePattern.Invoke();
}
......@@ -39,19 +44,32 @@ public static async Task ClickAutomationElementAsync(string elementName, bool re
/// </summary>
/// <returns>The task referrign to the element finding.</returns>
public static async Task<AutomationElement> FindAutomationElementAsync(string elementName, bool recursive = false)
public static async Task<IUIAutomationElement> FindAutomationElementAsync(string elementName, bool recursive = false)
{
AutomationElement element = null;
var scope = recursive ? TreeScope.Descendants : TreeScope.Children;
var condition = new PropertyCondition(AutomationElement.NameProperty, elementName);
IUIAutomationElement element = null;
var scope = recursive ? TreeScope.TreeScope_Descendants : TreeScope.TreeScope_Children;
var condition = Helper.Automation.CreatePropertyCondition(AutomationElementIdentifiers.NameProperty.Id, elementName);
// TODO(Dustin): This is code is a bit terrifying. If anything goes wrong and the automation
// element can't be found, it'll continue to spin until the heat death of the universe.
await IntegrationHelper.WaitForResultAsync(
() => (element = AutomationElement.RootElement.FindFirst(scope, condition)) != null, expectedResult: true
() => (element = Helper.Automation.GetRootElement().FindFirst(scope, condition)) != null, expectedResult: true
).ConfigureAwait(false);
return element;
}
private class AutomationEventHandler : IUIAutomationEventHandler
{
private readonly Action<IUIAutomationElement, int> _action;
public AutomationEventHandler(Action<IUIAutomationElement, int> action)
{
_action = action ?? throw new ArgumentNullException(nameof(action));
}
public void HandleAutomationEvent(IUIAutomationElement sender, int eventId)
=> _action(sender, eventId);
}
}
}
......@@ -3,19 +3,22 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Automation;
using UIAutomationClient;
using AutomationElementIdentifiers = System.Windows.Automation.AutomationElementIdentifiers;
using AutomationProperty = System.Windows.Automation.AutomationProperty;
using ControlType = System.Windows.Automation.ControlType;
namespace Microsoft.VisualStudio.IntegrationTest.Utilities
{
public static class DialogHelpers
{
/// <summary>
/// Returns an <see cref="AutomationElement"/> representing the open dialog with automation ID
/// Returns an <see cref="IUIAutomationElement"/> representing the open dialog with automation ID
/// <paramref name="dialogAutomationId"/>.
/// Throws an <see cref="InvalidOperationException"/> if an open dialog with that name cannot be
/// found.
/// </summary>
public static AutomationElement GetOpenDialogById(int visualStudioHWnd, string dialogAutomationId)
public static IUIAutomationElement GetOpenDialogById(IntPtr visualStudioHWnd, string dialogAutomationId)
{
var dialogAutomationElement = FindDialogByAutomationId(visualStudioHWnd, dialogAutomationId, isOpen: true);
if (dialogAutomationElement == null)
......@@ -26,7 +29,7 @@ public static AutomationElement GetOpenDialogById(int visualStudioHWnd, string d
return dialogAutomationElement;
}
public static AutomationElement FindDialogByAutomationId(int visualStudioHWnd, string dialogAutomationId, bool isOpen, bool wait = true)
public static IUIAutomationElement FindDialogByAutomationId(IntPtr visualStudioHWnd, string dialogAutomationId, bool isOpen, bool wait = true)
{
return Retry(
() => FindDialogWorker(visualStudioHWnd, dialogAutomationId),
......@@ -37,7 +40,7 @@ public static AutomationElement FindDialogByAutomationId(int visualStudioHWnd, s
/// <summary>
/// Used to find legacy dialogs that don't have an AutomationId
/// </summary>
public static AutomationElement FindDialogByName(int visualStudioHWnd, string dialogName, bool isOpen)
public static IUIAutomationElement FindDialogByName(IntPtr visualStudioHWnd, string dialogName, bool isOpen)
{
return Retry(
() => FindDialogByNameWorker(visualStudioHWnd, dialogName),
......@@ -49,7 +52,7 @@ public static AutomationElement FindDialogByName(int visualStudioHWnd, string di
/// Selects a specific item in a combo box.
/// Note that combo box is found using its Automation ID, but the item is identified by name.
/// </summary>
public static void SelectComboBoxItem(int visualStudioHWnd, string dialogAutomationName, string comboBoxAutomationName, string itemText)
public static void SelectComboBoxItem(IntPtr visualStudioHWnd, string dialogAutomationName, string comboBoxAutomationName, string itemText)
{
var dialogAutomationElement = GetOpenDialogById(visualStudioHWnd, dialogAutomationName);
......@@ -65,7 +68,7 @@ public static void SelectComboBoxItem(int visualStudioHWnd, string dialogAutomat
/// <summary>
/// Selects a specific radio button from a dialog found by Id.
/// </summary>
public static void SelectRadioButton(int visualStudioHWnd, string dialogAutomationName, string radioButtonAutomationName)
public static void SelectRadioButton(IntPtr visualStudioHWnd, string dialogAutomationName, string radioButtonAutomationName)
{
var dialogAutomationElement = GetOpenDialogById(visualStudioHWnd, dialogAutomationName);
......@@ -77,7 +80,7 @@ public static void SelectRadioButton(int visualStudioHWnd, string dialogAutomati
/// Sets the value of the specified element in the dialog.
/// Used for setting the values of things like combo boxes and text fields.
/// </summary>
public static void SetElementValue(int visualStudioHWnd, string dialogAutomationId, string elementAutomationId, string value)
public static void SetElementValue(IntPtr visualStudioHWnd, string dialogAutomationId, string elementAutomationId, string value)
{
var dialogAutomationElement = GetOpenDialogById(visualStudioHWnd, dialogAutomationId);
......@@ -87,10 +90,10 @@ public static void SetElementValue(int visualStudioHWnd, string dialogAutomation
/// <summary>
/// Presses the specified button.
/// The button is identified using its automation ID; see <see cref="PressButtonWithName(int, string, string)"/>
/// The button is identified using its automation ID; see <see cref="PressButtonWithName(IntPtr, string, string)"/>
/// for the equivalent method that finds the button by name.
/// </summary>
public static void PressButton(int visualStudioHWnd, string dialogAutomationId, string buttonAutomationId)
public static void PressButton(IntPtr visualStudioHWnd, string dialogAutomationId, string buttonAutomationId)
{
var dialogAutomationElement = GetOpenDialogById(visualStudioHWnd, dialogAutomationId);
......@@ -100,10 +103,10 @@ public static void PressButton(int visualStudioHWnd, string dialogAutomationId,
/// <summary>
/// Presses the specified button.
/// The button is identified using its name; see <see cref="PressButton(int, string, string)"/>
/// The button is identified using its name; see <see cref="PressButton(IntPtr, string, string)"/>
/// for the equivalent methods that finds the button by automation ID.
/// </summary>
public static void PressButtonWithName(int visualStudioHWnd, string dialogAutomationId, string buttonName)
public static void PressButtonWithName(IntPtr visualStudioHWnd, string dialogAutomationId, string buttonName)
{
var dialogAutomationElement = GetOpenDialogById(visualStudioHWnd, dialogAutomationId);
......@@ -113,10 +116,10 @@ public static void PressButtonWithName(int visualStudioHWnd, string dialogAutoma
/// <summary>
/// Presses the specified button from a legacy dialog that has no AutomationId.
/// The button is identified using its name; see <see cref="PressButton(int, string, string)"/>
/// The button is identified using its name; see <see cref="PressButton(IntPtr, string, string)"/>
/// for the equivalent methods that finds the button by automation ID.
/// </summary>
public static void PressButtonWithNameFromDialogWithName(int visualStudioHWnd, string dialogName, string buttonName)
public static void PressButtonWithNameFromDialogWithName(IntPtr visualStudioHWnd, string dialogName, string buttonName)
{
var dialogAutomationElement = FindDialogByName(visualStudioHWnd, dialogName, isOpen: true);
......@@ -124,24 +127,27 @@ public static void PressButtonWithNameFromDialogWithName(int visualStudioHWnd, s
buttonAutomationElement.Invoke();
}
private static AutomationElement FindDialogWorker(int visualStudioHWnd, string dialogAutomationName)
=> FindDialogByPropertyWorker(visualStudioHWnd, dialogAutomationName, AutomationElement.AutomationIdProperty);
private static IUIAutomationElement FindDialogWorker(IntPtr visualStudioHWnd, string dialogAutomationName)
=> FindDialogByPropertyWorker(visualStudioHWnd, dialogAutomationName, AutomationElementIdentifiers.AutomationIdProperty);
private static AutomationElement FindDialogByNameWorker(int visualStudioHWnd, string dialogName)
=> FindDialogByPropertyWorker(visualStudioHWnd, dialogName, AutomationElement.NameProperty);
private static IUIAutomationElement FindDialogByNameWorker(IntPtr visualStudioHWnd, string dialogName)
=> FindDialogByPropertyWorker(visualStudioHWnd, dialogName, AutomationElementIdentifiers.NameProperty);
private static AutomationElement FindDialogByPropertyWorker(
int visualStudioHWnd,
private static IUIAutomationElement FindDialogByPropertyWorker(
IntPtr visualStudioHWnd,
string propertyValue,
AutomationProperty nameProperty)
{
var vsAutomationElement = AutomationElement.FromHandle(new IntPtr(visualStudioHWnd));
var vsAutomationElement = Helper.Automation.ElementFromHandle(visualStudioHWnd);
Condition elementCondition = new AndCondition(
new PropertyCondition(nameProperty, propertyValue),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window));
var elementCondition = Helper.Automation.CreateAndConditionFromArray(
new[]
{
Helper.Automation.CreatePropertyCondition(nameProperty.Id, propertyValue),
Helper.Automation.CreatePropertyCondition(AutomationElementIdentifiers.ControlTypeProperty.Id, ControlType.Window.Id),
});
return vsAutomationElement.FindFirst(TreeScope.Children, elementCondition);
return vsAutomationElement.FindFirst(UIAutomationClient.TreeScope.TreeScope_Children, elementCondition);
}
private static T Retry<T>(Func<T> action, Func<T, bool> stoppingCondition, TimeSpan delay)
......@@ -155,12 +161,6 @@ private static T Retry<T>(Func<T> action, Func<T, bool> stoppingCondition, TimeS
{
retval = action();
}
catch (ElementNotAvailableException)
{
// Devenv can throw automation exceptions if it's busy when we make DTE calls.
Thread.Sleep(delay);
continue;
}
catch (COMException)
{
// Devenv can throw COMExceptions if it's busy when we make DTE calls.
......
......@@ -2,11 +2,28 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using UIAutomationClient;
namespace Microsoft.VisualStudio.IntegrationTest.Utilities
{
public static class Helper
{
private static IUIAutomation2 _automation;
public static IUIAutomation2 Automation
{
get
{
if (_automation == null)
{
Interlocked.CompareExchange(ref _automation, new CUIAutomation8(), null);
}
return _automation;
}
}
/// <summary>
/// This method will retry the action represented by the 'action' argument,
/// milliseconds, waiting 'delay' milliseconds after each retry. If a given retry returns a value
......
......@@ -9,7 +9,6 @@
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Forms;
using Microsoft.CodeAnalysis.Editor.Implementation.Highlighting;
......@@ -26,6 +25,9 @@
using Microsoft.VisualStudio.Text.Tagging;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Text.Classification;
using UIAutomationClient;
using AutomationElementIdentifiers = System.Windows.Automation.AutomationElementIdentifiers;
using ControlType = System.Windows.Automation.ControlType;
namespace Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess
{
......@@ -354,7 +356,7 @@ public void MessageBox(string message)
public void VerifyDialog(string dialogAutomationId, bool isOpen)
{
var dialogAutomationElement = DialogHelpers.FindDialogByAutomationId(GetDTE().MainWindow.HWnd, dialogAutomationId, isOpen);
var dialogAutomationElement = DialogHelpers.FindDialogByAutomationId((IntPtr)GetDTE().MainWindow.HWnd, dialogAutomationId, isOpen);
if ((isOpen && dialogAutomationElement == null) ||
(!isOpen && dialogAutomationElement != null))
......@@ -365,7 +367,7 @@ public void VerifyDialog(string dialogAutomationId, bool isOpen)
public void DialogSendKeys(string dialogAutomationName, string keys)
{
var dialogAutomationElement = DialogHelpers.GetOpenDialogById(GetDTE().MainWindow.HWnd, dialogAutomationName);
var dialogAutomationElement = DialogHelpers.GetOpenDialogById((IntPtr)GetDTE().MainWindow.HWnd, dialogAutomationName);
dialogAutomationElement.SetFocus();
SendKeys.SendWait(keys);
......@@ -385,10 +387,10 @@ public void SendKeysToNavigateTo(string keys)
public void PressDialogButton(string dialogAutomationName, string buttonAutomationName)
{
DialogHelpers.PressButton(GetDTE().MainWindow.HWnd, dialogAutomationName, buttonAutomationName);
DialogHelpers.PressButton((IntPtr)GetDTE().MainWindow.HWnd, dialogAutomationName, buttonAutomationName);
}
private AutomationElement FindDialog(string dialogAutomationName, bool isOpen)
private IUIAutomationElement FindDialog(string dialogAutomationName, bool isOpen)
{
return Retry(
() => FindDialogWorker(dialogAutomationName),
......@@ -396,20 +398,23 @@ private AutomationElement FindDialog(string dialogAutomationName, bool isOpen)
delay: TimeSpan.FromMilliseconds(250));
}
private static AutomationElement FindDialogWorker(string dialogAutomationName)
private static IUIAutomationElement FindDialogWorker(string dialogAutomationName)
{
var vsAutomationElement = AutomationElement.FromHandle(new IntPtr(GetDTE().MainWindow.HWnd));
var vsAutomationElement = Helper.Automation.ElementFromHandle((IntPtr)GetDTE().MainWindow.HWnd);
System.Windows.Automation.Condition elementCondition = new AndCondition(
new PropertyCondition(AutomationElement.AutomationIdProperty, dialogAutomationName),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window));
var elementCondition = Helper.Automation.CreateAndConditionFromArray(
new[]
{
Helper.Automation.CreatePropertyCondition(AutomationElementIdentifiers.AutomationIdProperty.Id, dialogAutomationName),
Helper.Automation.CreatePropertyCondition(AutomationElementIdentifiers.ControlTypeProperty.Id, ControlType.Window.Id),
});
return vsAutomationElement.FindFirst(TreeScope.Descendants, elementCondition);
return vsAutomationElement.FindFirst(TreeScope.TreeScope_Descendants, elementCondition);
}
private static AutomationElement FindNavigateTo()
private static IUIAutomationElement FindNavigateTo()
{
var vsAutomationElement = AutomationElement.FromHandle(new IntPtr(GetDTE().MainWindow.HWnd));
var vsAutomationElement = Helper.Automation.ElementFromHandle((IntPtr)GetDTE().MainWindow.HWnd);
return vsAutomationElement.FindDescendantByAutomationId("PART_SearchBox");
}
......
......@@ -13,8 +13,8 @@ internal class Shell_InProc : InProcComponent
public string GetActiveWindowCaption()
=> InvokeOnUIThread(() => GetDTE().ActiveWindow.Caption);
public int GetHWnd()
=> GetDTE().MainWindow.HWnd;
public IntPtr GetHWnd()
=> (IntPtr)GetDTE().MainWindow.HWnd;
public bool IsActiveTabProvisional()
=> InvokeOnUIThread(() =>
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Windows.Automation;
using Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess;
using Microsoft.VisualStudio.IntegrationTest.Utilities.Input;
using Roslyn.Test.Utilities;
using UIAutomationClient;
using Xunit;
using AutomationElementIdentifiers = System.Windows.Automation.AutomationElementIdentifiers;
using GridPatternIdentifiers = System.Windows.Automation.GridPatternIdentifiers;
using SelectionItemPatternIdentifiers = System.Windows.Automation.SelectionItemPatternIdentifiers;
namespace Microsoft.VisualStudio.IntegrationTest.Utilities.OutOfProcess
{
......@@ -70,26 +71,26 @@ public void SelectParameter(string parameterName)
{
var dialogAutomationElement = DialogHelpers.FindDialogByAutomationId(GetMainWindowHWnd(), ChangeSignatureDialogAutomationId, isOpen: true);
Condition propertyCondition = new PropertyCondition(AutomationElement.AutomationIdProperty, "MemberSelectionList");
var grid = dialogAutomationElement.FindFirst(TreeScope.Descendants, propertyCondition);
var propertyCondition = Helper.Automation.CreatePropertyCondition(AutomationElementIdentifiers.AutomationIdProperty.Id, "MemberSelectionList");
var grid = dialogAutomationElement.FindFirst(TreeScope.TreeScope_Descendants, propertyCondition);
var gridPattern = grid.GetCurrentPattern(GridPattern.Pattern) as GridPattern;
var rowCount = (int)grid.GetCurrentPropertyValue(GridPattern.RowCountProperty);
var gridPattern = (IUIAutomationGridPattern)grid.GetCurrentPattern(GridPatternIdentifiers.Pattern.Id);
var rowCount = gridPattern.CurrentRowCount;
var columnToSelect = 2;
int i = 0;
for (; i < rowCount; i++)
{
// Modifier | Type | Parameter | Default
var item = gridPattern.GetItem(i, columnToSelect);
var name = item.GetCurrentPropertyValue(AutomationElement.NameProperty) as string;
var name = item.CurrentName;
if (name == parameterName)
{
// The parent of a cell is of DataItem control type, which support SelectionItemPattern.
TreeWalker walker = TreeWalker.ControlViewWalker;
var parent = walker.GetParent(item);
if (parent.TryGetCurrentPattern(SelectionItemPattern.Pattern, out var pattern))
var walker = Helper.Automation.ControlViewWalker;
var parent = walker.GetParentElement(item);
if (parent.GetCurrentPattern(SelectionItemPatternIdentifiers.Pattern.Id) is IUIAutomationSelectionItemPattern pattern)
{
(pattern as SelectionItemPattern).Select();
pattern.Select();
}
else
{
......@@ -106,7 +107,7 @@ public void SelectParameter(string parameterName)
}
}
private int GetMainWindowHWnd()
private IntPtr GetMainWindowHWnd()
=> VisualStudioInstance.Shell.GetHWnd();
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.VisualStudio.IntegrationTest.Utilities.OutOfProcess
{
public class Dialog_OutOfProc : OutOfProcComponent
......@@ -27,7 +29,7 @@ public void VerifyClosed(string dialogName)
public void Click(string dialogName, string buttonName)
=> DialogHelpers.PressButtonWithNameFromDialogWithName(GetMainWindowHWnd(), dialogName, buttonName);
private int GetMainWindowHWnd()
private IntPtr GetMainWindowHWnd()
=> VisualStudioInstance.Shell.GetHWnd();
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using System.Windows.Automation;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Windows;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.IntegrationTest.Utilities.Common;
using Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess;
using Microsoft.VisualStudio.IntegrationTest.Utilities.Input;
using UIAutomationClient;
namespace Microsoft.VisualStudio.IntegrationTest.Utilities.OutOfProcess
{
......@@ -173,7 +172,7 @@ public void SendKeys(params object[] keys)
public void MessageBox(string message)
=> _editorInProc.MessageBox(message);
public AutomationElement GetDialog(string dialogAutomationId)
public IUIAutomationElement GetDialog(string dialogAutomationId)
{
var dialog = DialogHelpers.GetOpenDialogById(_instance.Shell.GetHWnd(), dialogAutomationId);
return dialog;
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Windows.Automation;
using Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess;
using Microsoft.VisualStudio.IntegrationTest.Utilities.Input;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.VisualStudio.IntegrationTest.Utilities.OutOfProcess
......
......@@ -2,7 +2,6 @@
using System;
using System.Linq;
using System.Windows.Automation;
using Microsoft.CodeAnalysis.Shared.TestHooks;
namespace Microsoft.VisualStudio.IntegrationTest.Utilities.OutOfProcess
......@@ -98,12 +97,13 @@ public string[] GetSelectedItems()
var dialog = DialogHelpers.GetOpenDialogById(GetMainWindowHWnd(), ExtractInterfaceDialogID);
var memberSelectionList = dialog.FindDescendantByAutomationId("MemberSelectionList");
var listItems = memberSelectionList.FindDescendantsByClass("ListBoxItem");
var comListItems = memberSelectionList.FindDescendantsByClass("ListBoxItem");
var listItems = Enumerable.Range(0, comListItems.Length).Select(comListItems.GetElement);
return listItems.Cast<AutomationElement>()
return listItems
.Select(item => item.FindDescendantByClass("CheckBox"))
.Where(checkBox => checkBox.IsToggledOn())
.Select(checkbox => checkbox.Current.AutomationId)
.Select(checkbox => checkbox.CurrentAutomationId)
.ToArray();
}
......@@ -137,7 +137,7 @@ public void ToggleItem(string item)
checkBox.Toggle();
}
private int GetMainWindowHWnd()
private IntPtr GetMainWindowHWnd()
{
return VisualStudioInstance.Shell.GetHWnd();
}
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Windows.Automation;
using Microsoft.CodeAnalysis.Shared.TestHooks;
namespace Microsoft.VisualStudio.IntegrationTest.Utilities.OutOfProcess
......@@ -106,10 +104,16 @@ public string[] GetNewFileComboBoxItems()
createNewFileComboBox.Collapse();
return children.Cast<AutomationElement>().Select(element => element.Current.Name).ToArray();
var result = new string[children.Length];
for (int i = 0; i < children.Length; i++)
{
result[i] = children.GetElement(i).CurrentName;
}
return result;
}
private int GetMainWindowHWnd()
private IntPtr GetMainWindowHWnd()
{
return VisualStudioInstance.Shell.GetHWnd();
}
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Windows.Automation;
using Microsoft.VisualStudio.IntegrationTest.Utilities.InProcess;
using Microsoft.VisualStudio.IntegrationTest.Utilities.Input;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.VisualStudio.IntegrationTest.Utilities.OutOfProcess
{
......@@ -43,7 +37,7 @@ public void ClickApplyAndWaitForFeature(string expectedTitle, string featureName
public void ClickCancel(string expectedTitle)
=> DialogHelpers.PressButtonWithNameFromDialogWithName(GetMainWindowHWnd(), expectedTitle, "Cancel");
private int GetMainWindowHWnd()
private IntPtr GetMainWindowHWnd()
=> VisualStudioInstance.Shell.GetHWnd();
}
}
......@@ -18,7 +18,7 @@ public Shell_OutOfProc(VisualStudioInstance visualStudioInstance)
public string GetActiveWindowCaption()
=> _inProc.GetActiveWindowCaption();
public int GetHWnd()
public IntPtr GetHWnd()
=> _inProc.GetHWnd();
public bool IsActiveTabProvisional()
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册