2016-11-06 110 views
1

我开始使用TestStack White(UI Automation)来自动化WPF现有应用程序中的测试。当使用标准控件时,一切正常。但是,在尝试交互自定义控件时遇到问题。如何定义自定义控件以启用UI自动化和TestStack White?

例如,我有一个LabeledComboBox,它实际上是一个TextBlock加上一个ComboBox。这被定义为从控制加上控件模板在XAML的派生类:

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自动化验证的唯一对UI自动化可见的部分是组合框,并且不能访问TextBlock。

但是,如果使用XAML和代码隐藏将其作为UserControl创建,则TextBox和ComboBox对UI自动化都可正常显示。

我试着为我的控件创建一个AutomationPeer(FrameworkElementAutomationPeer),但我还没有能够使TextBlock对UI自动化至今可见。一个有趣的结果是,FrameworkElementAutomationPeer :: GetChildrenCore()正确地返回2个自动化对等体的列表,一个用于TextBlock,一个用于ComboBox。

我应该如何更改自定义控件,以便使用UI自动化和白色进行正确测试?

回答

1

出于某种原因,如果它是ControlTemplate的一部分,TextBlockTextBlockAutomationPeer)的默认自动对等体将从UI树中删除相应的所有者。

您可以找到相关的代码在这里:https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Automation/Peers/TextBlockAutomationPeer.cs,16e7fab76ffcb40a

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控件类(从TextBlock的推导),并覆盖OnCreateAutomationPeer返回自定义一个。

+0

感谢您的解释,所有的解决方案正常工作。我在这里找到了更多信息:http://stackoverflow.com/a/2846419/5414769 –