我应该如何定义自定义控件以启用 UI Automation 和 TestStack White?
How should I define Custom Controls to enable UI Automation and TestStack White?
我开始使用 TestStack White(UI 自动化)在 WPF 现有应用程序中自动化测试。使用标准控件时一切正常。但是,我 运行 在尝试与自定义控件交互时遇到了问题。
例如,我有一个LabeledComboBox,它实际上是一个TextBlock加上一个ComboBox。这被定义为从 Control 派生的 class 加上 XAML:
中的 ControlTemplate
public class LabeledComboBox : Control
{
static LabeledComboBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(LabeledComboBox), new FrameworkPropertyMetadata(typeof(LabeledComboBox)));
}
}
<local:LabeledComboBox>
<local:LabeledComboBox.Template>
<ControlTemplate TargetType="{x:Type local:LabeledComboBox}">
<StackPanel>
<TextBlock Text="Text"/>
<ComboBox/>
</StackPanel>
</ControlTemplate>
</local:LabeledComboBox.Template>
</local:LabeledComboBox>
此控件有效,但如果您 运行 UI Automation 验证 UI Automation 唯一可见的部分是 ComboBox,而无法访问 TextBlock。
但是,如果您使用 XAML 和代码隐藏将其创建为 UserControl,则 TextBox 和 ComboBox 都对 UI Automation 正确可见。
我已尝试为我的控件创建一个 AutomationPeer (FrameworkElementAutomationPeer),但到目前为止我无法使 TextBlock 对 UI Automation 可见。一个有趣的结果是 FrameworkElementAutomationPeer::GetChildrenCore() 正确 returns 2 个自动化对等点的列表,一个用于 TextBlock,一个用于 ComboBox。
我应该如何更改我的自定义控件,以便使用 UI Automation 和 White 对其进行正确测试?
如果 TextBlock
(TextBlockAutomationPeer
) 的默认自动化对等点是 ControlTemplate 的一部分,则它会从 UI 树中删除相应的所有者。
override protected bool IsControlElementCore()
{
// Return true if TextBlock is not part of a ControlTemplate
TextBlock tb = (TextBlock)Owner;
DependencyObject templatedParent = tb.TemplatedParent;
return templatedParent == null || templatedParent is ContentPresenter; // If the templatedParent is a ContentPresenter, this TextBlock is generated from a DataTemplate
}
因此,要解决此问题,您必须声明 TextBlock 不在 ControlTemplate 中,或者使用类似这样的代码(很难推广到整个应用程序...):
public class LabeledComboBox : Control
{
static LabeledComboBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(LabeledComboBox), new FrameworkPropertyMetadata(typeof(LabeledComboBox)));
}
// define our own peer
protected override AutomationPeer OnCreateAutomationPeer()
{
return new LabeledComboBoxAutomationPeer(this);
}
protected class LabeledComboBoxAutomationPeer : FrameworkElementAutomationPeer
{
public LabeledComboBoxAutomationPeer(LabeledComboBox owner) : base(owner)
{
}
// replace all TextBlockAutomationPeer by our custom peer for TextBlock
protected override List<AutomationPeer> GetChildrenCore()
{
var list = base.GetChildrenCore();
for (int i = 0; i < list.Count; i++)
{
var tb = list[i] as TextBlockAutomationPeer;
if (tb != null)
{
list[i] = new InteractiveTextBlockAutomationPeer((TextBlock)tb.Owner);
}
}
return list;
}
}
// just do the default stuff, instead of the strange TextBlockAutomationPeer implementation
protected class InteractiveTextBlockAutomationPeer : FrameworkElementAutomationPeer
{
public InteractiveTextBlockAutomationPeer(TextBlock owner) : base(owner)
{
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Text;
}
protected override string GetClassNameCore()
{
return "TextBlock";
}
}
}
另一种解决方案是创建您自己的 TextBlock 控件 class(从 TextBlock 派生)并将 OnCreateAutomationPeer
覆盖为 return 自定义控件。
我开始使用 TestStack White(UI 自动化)在 WPF 现有应用程序中自动化测试。使用标准控件时一切正常。但是,我 运行 在尝试与自定义控件交互时遇到了问题。
例如,我有一个LabeledComboBox,它实际上是一个TextBlock加上一个ComboBox。这被定义为从 Control 派生的 class 加上 XAML:
中的 ControlTemplatepublic class LabeledComboBox : Control
{
static LabeledComboBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(LabeledComboBox), new FrameworkPropertyMetadata(typeof(LabeledComboBox)));
}
}
<local:LabeledComboBox>
<local:LabeledComboBox.Template>
<ControlTemplate TargetType="{x:Type local:LabeledComboBox}">
<StackPanel>
<TextBlock Text="Text"/>
<ComboBox/>
</StackPanel>
</ControlTemplate>
</local:LabeledComboBox.Template>
</local:LabeledComboBox>
此控件有效,但如果您 运行 UI Automation 验证 UI Automation 唯一可见的部分是 ComboBox,而无法访问 TextBlock。
但是,如果您使用 XAML 和代码隐藏将其创建为 UserControl,则 TextBox 和 ComboBox 都对 UI Automation 正确可见。
我已尝试为我的控件创建一个 AutomationPeer (FrameworkElementAutomationPeer),但到目前为止我无法使 TextBlock 对 UI Automation 可见。一个有趣的结果是 FrameworkElementAutomationPeer::GetChildrenCore() 正确 returns 2 个自动化对等点的列表,一个用于 TextBlock,一个用于 ComboBox。
我应该如何更改我的自定义控件,以便使用 UI Automation 和 White 对其进行正确测试?
如果 TextBlock
(TextBlockAutomationPeer
) 的默认自动化对等点是 ControlTemplate 的一部分,则它会从 UI 树中删除相应的所有者。
override protected bool IsControlElementCore()
{
// Return true if TextBlock is not part of a ControlTemplate
TextBlock tb = (TextBlock)Owner;
DependencyObject templatedParent = tb.TemplatedParent;
return templatedParent == null || templatedParent is ContentPresenter; // If the templatedParent is a ContentPresenter, this TextBlock is generated from a DataTemplate
}
因此,要解决此问题,您必须声明 TextBlock 不在 ControlTemplate 中,或者使用类似这样的代码(很难推广到整个应用程序...):
public class LabeledComboBox : Control
{
static LabeledComboBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(LabeledComboBox), new FrameworkPropertyMetadata(typeof(LabeledComboBox)));
}
// define our own peer
protected override AutomationPeer OnCreateAutomationPeer()
{
return new LabeledComboBoxAutomationPeer(this);
}
protected class LabeledComboBoxAutomationPeer : FrameworkElementAutomationPeer
{
public LabeledComboBoxAutomationPeer(LabeledComboBox owner) : base(owner)
{
}
// replace all TextBlockAutomationPeer by our custom peer for TextBlock
protected override List<AutomationPeer> GetChildrenCore()
{
var list = base.GetChildrenCore();
for (int i = 0; i < list.Count; i++)
{
var tb = list[i] as TextBlockAutomationPeer;
if (tb != null)
{
list[i] = new InteractiveTextBlockAutomationPeer((TextBlock)tb.Owner);
}
}
return list;
}
}
// just do the default stuff, instead of the strange TextBlockAutomationPeer implementation
protected class InteractiveTextBlockAutomationPeer : FrameworkElementAutomationPeer
{
public InteractiveTextBlockAutomationPeer(TextBlock owner) : base(owner)
{
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Text;
}
protected override string GetClassNameCore()
{
return "TextBlock";
}
}
}
另一种解决方案是创建您自己的 TextBlock 控件 class(从 TextBlock 派生)并将 OnCreateAutomationPeer
覆盖为 return 自定义控件。