根据我UIA自动化测试的经验, 总结了下面代码集. 在这个代码集中, 包含了:
1. 一个WPF的窗体程序
2. 一个WinForm的窗体, 这个窗体作为Model Dialog被WPF主程序打开
3. 针对这个WPF和WinForm的测试代码例子
4. 针对Win7 Calc.exe的测试代码例子
5. 一个简单的TestEngine
这个代码集的作用是:
1. 演示UIA中基本的概念, 比如AutomationID, AutomatonName, InvokePattern等的调用
2. 演示如何处理UI自动化的timing issue.
3. 演示简单WaitForReady的实现方法
4. 演示Click和Invoke的差别
5. 演示一个简单的UIA Engine
6. 演示如何通过AutomationPeer来给自绘画图案实现Invoke Pattern
7. 演示如何对WinForm实现Server side provider
8. 演示如何对WPF的databinding item设定AutomationID
WPF主窗口代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
{
private StackPanel pane;
private TextBlock timeBlock;
private Button buttonOpenNewWindow;
private ListBox listboxStringBinding;
private ListBox listboxManual;
private SelfDrawControl selfControl;
private DispatcherTimer tmr = new DispatcherTimer();
private DispatcherTimer deplyedExecution = new DispatcherTimer();
private Button kickoffFlashWindow;
private Button startWinformHoster;
private Button buttonNoAutomationID;
private Button busyButton;
private int busyCount = 0 ;
public Window1()
{
InitializeComponent();
this .Name = " Window1 " ;
CreateControls();
}
void CreateControls()
{
pane = new StackPanel();
pane.Name = " StackPane " ;
this .Content = pane;
timeBlock = new TextBlock();
timeBlock.Name = " TimeBlock " ;
pane.Children.Add(timeBlock);
buttonOpenNewWindow = new Button();
buttonOpenNewWindow.Name = " ButtonOpenNewWindow " ;
buttonOpenNewWindow.Content = " Open New Window " ;
pane.Children.Add(buttonOpenNewWindow);
// 直接binding的Listbox子元素没有AutomationID
listboxStringBinding = new ListBox();
listboxStringBinding.Name = " ListBox_Item_WithoutAutomationID " ;
listboxStringBinding.DataContext = new string [] { " 1 " , " 2 " , " 3 " };
Binding bind = new Binding();
bind.Source = new string [] { " 1 " , " 2 " , " 3 " };
listboxStringBinding.SetBinding(ListBox.ItemsSourceProperty, bind);
pane.Children.Add(listboxStringBinding);
selfControl = new SelfDrawControl();
selfControl.Name = " SelfDrawControl " ;
pane.Children.Add(selfControl);
listboxManual = new ListBox();
listboxManual.Name = " ListBox_Item_ManualBindAutomationID " ;
for ( int i = 0 ;i < 5 ;i ++ )
{
// 手动添加的的Listbox子元素可以通过下面的方法指定AutomationID,或者直接指定Name
ListBoxItem item = new ListBoxItem();
item.SetValue(System.Windows.Automation.AutomationProperties.AutomationIdProperty, " ManualItem " + i.ToString());
item.Content = i;
listboxManual.Items.Add(item);
}
pane.Children.Add(listboxManual);
kickoffFlashWindow = new Button();
kickoffFlashWindow.Name = " ButtonOpenFlashWindow " ;
kickoffFlashWindow.Content = " Kick off Flash Window " ;
pane.Children.Add(kickoffFlashWindow);
kickoffFlashWindow.Click += new RoutedEventHandler(kickoffFlashWindow_Click);
startWinformHoster = new Button();
startWinformHoster.Name = " ButtonOpenWinForm " ;
startWinformHoster.Content = " Start Winform Hoster " ;
pane.Children.Add(startWinformHoster);
// startWinformHoster.Click += delegate{(new WinFormControlHoster()).ShowDialog();};
startWinformHoster.Click += new RoutedEventHandler(startWinformHoster_Click);
tmr.Interval = new TimeSpan( 0 , 0 , 0 , 0 , 500 );
tmr.Tick += delegate { this .timeBlock.Text = DateTime.Now.ToString( " U " ); };
tmr.IsEnabled = true ;
buttonOpenNewWindow.Click += new RoutedEventHandler(buttonOpenNewWindow_Click);
buttonNoAutomationID = new Button();
buttonNoAutomationID.Content = " Button Wihtout AutomationID " ;
pane.Children.Add(buttonNoAutomationID);
busyButton = new Button();
busyButton.Name = " BusyButton " ;
busyButton.Content = busyCount.ToString();
pane.Children.Add(busyButton);
busyButton.Click += new RoutedEventHandler(busyButton_Click);
deplyedExecution.Interval = new TimeSpan( 0 , 0 , 5 );
deplyedExecution.Tick += new EventHandler(deplyedExecution_Tick);
deplyedExecution.IsEnabled = false ;
}
void busyButton_Click( object sender, RoutedEventArgs e)
{
busyCount ++ ;
busyButton.Content = busyCount.ToString();
for ( int i = 1 ; i < 10 ; i ++ )
{
System.Threading.Thread.Sleep( 150 );
}
}
void buttonOpenNewWindow_Click( object sender, RoutedEventArgs e)
{
BindingWithAutomationID bwid = new BindingWithAutomationID();
bwid.ShowDialog();
}
void kickoffFlashWindow_Click( object sender, RoutedEventArgs e)
{
deplyedExecution.IsEnabled = true ;
}
void deplyedExecution_Tick( object sender, EventArgs e)
{
FlashWindow fw = new FlashWindow( new TimeSpan( 0 , 0 , 3 ));
fw.Show();
deplyedExecution.IsEnabled = false ;
}
void startWinformHoster_Click( object sender, RoutedEventArgs e)
{
System.Windows.Forms.Form fm = new MyForm();
fm.ShowDialog();
}
}
WPF自绘窗口的代码, 以及对应AutomationPeer的实现:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
{
public SelfDrawControl()
{
InitializeComponent();
}
protected override void OnRender(DrawingContext dc)
{
dc.DrawRectangle(Brushes.Blue, new Pen(Brushes.Blue, 10 ), new Rect( new Point( 0 , 0 ), new Point(RenderSize.Width / 2 , RenderSize.Height)));
dc.DrawRectangle(Brushes.Black, new Pen(Brushes.Black, 10 ), new Rect( new Point(RenderSize.Width / 2 , 0 ), new Point(RenderSize.Width, RenderSize.Height)));
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
Point point = e.GetPosition( this );
if (point.X > 0 && point.X < RenderSize.Width / 2 )
{
DoClick( " blue " );
}
else
{
DoClick( " black " );
}
base .OnMouseLeftButtonDown(e);
}
internal void DoClick( string color)
{
MessageBox.Show(color);
}
protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer()
{
var peer = new SelfDrawControlAutomationPeer( this );
peer.InvalidatePeer();
return peer;
}
}
public class SelfDrawControlAutomationPeer :UserControlAutomationPeer
{
SelfDrawControl target;
List < AutomationPeer > children = null ;
public SelfDrawControlAutomationPeer(SelfDrawControl target): base (target)
{
this .target = target;
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Window;
}
protected override string GetClassNameCore()
{
return target.GetType().ToString();
}
/*
* 特别注意, 生成ChildrenCore的时候务必维护父子关系
* UI Testclient 可能在不同child parent之间遍历
* 务必保证childpeer.Parent = parent.Children[n]
* 否则会带来各种意外,这是在实现自定义Peer时候的最大陷阱
*/
protected override List < AutomationPeer > GetChildrenCore()
{
// return null;
if (children == null )
{
children = new List < AutomationPeer > ();
SelfDrawControlElementAutomationPeer bluepeer = new SelfDrawControlElementAutomationPeer(target, this , " blue " );
SelfDrawControlElementAutomationPeer redpeer = new SelfDrawControlElementAutomationPeer(target, this , " red " );
children.Add(bluepeer);
children.Add(redpeer);
}
return children;
}
}
public class SelfDrawControlElementAutomationPeer : AutomationPeer, IInvokeProvider, IValueProvider
{
private string color;
SelfDrawControl target;
SelfDrawControlAutomationPeer parentPeer;
public SelfDrawControlElementAutomationPeer(SelfDrawControl target, SelfDrawControlAutomationPeer parentPeer, string color)
{
this .color = color;
this .target = target;
this .parentPeer = parentPeer;
var o = this .GetParent();
}
public void Invoke()
{
target.DoClick(color);
}
public bool IsReadOnly { get { return true ; } }
public string Value { get { return color; } }
public void SetValue( string value) { }
protected override string GetAcceleratorKeyCore()
{
return string .Empty;
}
protected override string GetAccessKeyCore()
{
return string .Empty;
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Window;
}
protected override string GetAutomationIdCore()
{
return target.Name + " - " + color;
}
protected override Rect GetBoundingRectangleCore()
{
Rect parentRect = parentPeer.GetBoundingRectangle();
if (color == " blue " )
{
return new Rect( new Point(parentRect.X, parentRect.Y), new Point(parentRect.X + target.RenderSize.Width / 2 , parentRect.Y + target.RenderSize.Height));
}
if (color == " red " )
{
return new Rect( new Point(parentRect.X + target.RenderSize.Width / 2 , parentRect.Y), new Point(parentRect.X + target.RenderSize.Width, parentRect.Y + target.RenderSize.Height));
}
return Rect.Empty;
}
protected override List < AutomationPeer > GetChildrenCore()
{
return null ;
}
protected override string GetClassNameCore()
{
return " SelfDrawControlInnerGraphic " ;
}
protected override Point GetClickablePointCore()
{
return new Point( 0 , 0 );
}
protected override string GetHelpTextCore()
{
return " This is my HelpText " ;
}
protected override string GetItemStatusCore()
{
return " Status is active " ;
}
protected override string GetItemTypeCore()
{
return " This is my item type " ;
}
protected override AutomationPeer GetLabeledByCore()
{
return this ;
}
protected override string GetNameCore()
{
return " Name is : " + color;
}
protected override AutomationOrientation GetOrientationCore()
{
return AutomationOrientation.None;
}
public override object GetPattern(PatternInterface patternInterface)
{
if (patternInterface == PatternInterface.Invoke || patternInterface == PatternInterface.Value)
{
return this ;
}
return null ;
}
protected override bool HasKeyboardFocusCore()
{
return false ;
}
protected override bool IsContentElementCore()
{
return true ;
}
protected override bool IsControlElementCore()
{
return true ;
}
protected override bool IsEnabledCore()
{
return true ;
}
protected override bool IsKeyboardFocusableCore()
{
return false ;
}
protected override bool IsOffscreenCore()
{
return false ;
}
protected override bool IsPasswordCore()
{
return false ;
}
protected override bool IsRequiredForFormCore()
{
return false ;
}
protected override void SetFocusCore()
{
}
}
BindingWindow的XAML和代码:
<ListBox Name="InnerListbox" >
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="AutomationProperties.AutomationId" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Content}" ></Setter>
</Style>
</ListBox.Resources>
</ListBox>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
{
public BindingWithAutomationID()
{
InitializeComponent();
this .Name = " StyleBindingWindow " ;
// 可以使用Style的Setter来给binding的子元素增加AutomationID
this .InnerListbox.DataContext = new string [] { " 1 " , " 2 " , " 3 " };
Binding bind = new Binding();
bind.Source = new string [] { " 1 " , " 2 " , " 3 " };
this .InnerListbox.SetBinding(ListBox.ItemsSourceProperty, bind);
}
}
WinForm的代码及其Server side provider实现:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
public class MyForm : System.Windows.Forms.Form, IRawElementProviderSimple
{
private ValuePattern vp = new ValuePattern();
private Timer timer = new Timer();
private string curentNameProperty = string .Empty;
public MyForm()
{
timer.Interval = 500 ;
timer.Tick += new EventHandler(timer_Tick);
timer.Enabled = true ;
this .Name = " WinFormWindow " ;
this .Text = " ServerUIAFormDemo " ;
Button btn = new Button();
btn.Text = " This is a WinForm Button " ;
btn.Name = " button1 " ;
this .Controls.Add(btn);
curentNameProperty = string .Format( " {0} {1} " , this .Name, DateTime.Now.ToLongTimeString());
}
void timer_Tick( object sender, EventArgs e)
{
string newNameProperty = string .Format( " {0} {1} " , this .Name, DateTime.Now.ToLongTimeString());
AutomationInteropProvider.RaiseAutomationPropertyChangedEvent( this , new AutomationPropertyChangedEventArgs(AutomationElement.NameProperty, curentNameProperty, newNameProperty));
curentNameProperty = newNameProperty;
}
protected override void WndProc( ref Message m)
{
const int WM_GETOBJECT = 0x003D ;
if ((m.Msg == WM_GETOBJECT) && (m.LParam.ToInt32() ==
AutomationInteropProvider.RootObjectId))
{
m.Result = AutomationInteropProvider.ReturnRawElementProvider(
this .Handle, m.WParam, m.LParam,
(IRawElementProviderSimple) this );
return ;
}
base .WndProc( ref m);
}
public Object GetPatternProvider( int patternId)
{
if (patternId == ValuePatternIdentifiers.Pattern.Id)
{
// Create and return ValuePattern object
return vp;
}
else
{
return null ;
}
}
// This function handles all the UIA Property reqeust
public Object GetPropertyValue( int propertyId)
{
if (propertyId == AutomationElementIdentifiers.NameProperty.Id)
{
return curentNameProperty;
}
else if (propertyId == AutomationElementIdentifiers.NativeWindowHandleProperty.Id)
{
return this .Handle;
}
else if (propertyId == AutomationElementIdentifiers.AutomationIdProperty.Id)
{
return this .Name;
}
else if (propertyId == AutomationElementIdentifiers.ClassNameProperty.Id)
{
return " RootButtonControlClass " ;
}
else if (propertyId == AutomationElementIdentifiers.ControlTypeProperty.Id)
{
return ControlType.Window.Id;
}
else if (propertyId == AutomationElementIdentifiers.IsContentElementProperty.Id)
{
return false ;
}
else if (propertyId == AutomationElementIdentifiers.IsControlElementProperty.Id)
{
return true ;
}
else
{
return AutomationElement.NotSupported;
}
}
public IRawElementProviderSimple HostRawElementProvider
{
get
{
return AutomationInteropProvider.HostProviderFromHandle( this .Handle);
}
}
public ProviderOptions ProviderOptions
{
get
{
// Indicate this is server side implementation
return ProviderOptions.ServerSideProvider;
}
}
}
[ComVisible( true )]
public class ValuePattern : IValueProvider
{
public bool IsReadOnly { get { return true ; } }
public string Value
{
get
{
// Return current time as value pattern’s value
return DateTime.Now.ToLongTimeString();
}
}
public void SetValue( string value) { return ; }
}
下面是测试程序:
测试演示1: 演示UIA API里面的cached property:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
{
public static void Test()
{
CacheRequest cacheRequest = new CacheRequest();
cacheRequest.TreeScope = TreeScope.Element;
cacheRequest.Add(AutomationElement.NameProperty);
using (cacheRequest.Activate())
{
AutomationElement wpfRoot = AutomationElement.RootElement.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, " Window1 " ));
AutomationElement textBlock1 = wpfRoot.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, " TimeBlock " ));
AutomationElement textBlock2 = wpfRoot.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, " TimeBlock " ));
for ( int i = 0 ; i < 100 ; i ++ )
{
string cachedName = textBlock1.Cached.Name;
string uncachedName = textBlock2.Current.Name;
Console.WriteLine( " ================= " );
Console.WriteLine( " Cached Name is {0} " , cachedName);
Console.WriteLine( " UnCached Name is {0} " , uncachedName);
System.Threading.Thread.Sleep( 300 );
}
}
Console.WriteLine( " Test finishes................... " );
}
}
测试演示2: timing issue导致的问题和三种应对方法: Sleep/Polling/Event:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
{
static DateTime eventStartTime;
public static void NoUISyncLeadToErrorDemo()
{
CacheRequest cacheRequest = new CacheRequest();
cacheRequest.TreeScope = TreeScope.Element;
cacheRequest.Add(AutomationElement.NameProperty);
using (cacheRequest.Activate())
{
AutomationElement wpfRoot = AutomationElement.RootElement.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, " Window1 " ));
AutomationElement btnOpenNewWindow = wpfRoot.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, " ButtonOpenFlashWindow " ));
InvokePattern invokPtn = (InvokePattern)btnOpenNewWindow.GetCurrentPattern(InvokePattern.Pattern);
invokPtn.Invoke();
Console.WriteLine( " Button Clicked... " );
AutomationElement newWindow = AutomationElement.RootElement.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, " FlashWindow " ));
Console.WriteLine(newWindow.Current.Name);
}
Console.WriteLine( " Test finishes................... " );
}
public static void SimpleSleepSyncDemo()
{
DateTime startTime = DateTime.Now;
CacheRequest cacheRequest = new CacheRequest();
cacheRequest.TreeScope = TreeScope.Element;
cacheRequest.Add(AutomationElement.NameProperty);
using (cacheRequest.Activate())
{
AutomationElement wpfRoot = AutomationElement.RootElement.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, " Window1 " ));
AutomationElement btnOpenNewWindow = wpfRoot.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, " ButtonOpenFlashWindow " ));
InvokePattern invokPtn = (InvokePattern)btnOpenNewWindow.GetCurrentPattern(InvokePattern.Pattern);
invokPtn.Invoke();
Console.WriteLine( " Button Clicked... " );
Console.WriteLine( " Sleeping 6 seconds to wait... " );
System.Threading.Thread.Sleep( 1000 * 6 );
AutomationElement newWindow = AutomationElement.RootElement.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, " FlashWindow " ));
Console.WriteLine(newWindow.Current.Name);
}
Console.WriteLine( " Test finishes................... " );
Console.WriteLine( " Test cost {0} seconds " , (DateTime.Now - startTime).TotalSeconds);
}
public static void PollingSyncDemo()
{
DateTime startTime = DateTime.Now;
CacheRequest cacheRequest = new CacheRequest();
cacheRequest.TreeScope = TreeScope.Element;
cacheRequest.Add(AutomationElement.NameProperty);
using (cacheRequest.Activate())
{
AutomationElement wpfRoot = AutomationElement.RootElement.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, " Window1 " ));
AutomationElement btnOpenNewWindow = wpfRoot.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, " ButtonOpenFlashWindow " ));
InvokePattern invokPtn = (InvokePattern)btnOpenNewWindow.GetCurrentPattern(InvokePattern.Pattern);
invokPtn.Invoke();
Console.WriteLine( " Button Clicked... " );
int timeout = 10 * 1000 ;
while ( true )
{
AutomationElement newWindow = AutomationElement.RootElement.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, " FlashWindow " ));
if (newWindow == null )
{
Console.WriteLine( " Cannot find expected window. Sleep for a short time " );
System.Threading.Thread.Sleep( 500 );
timeout -= 300 ;
if (timeout <= 0 )
{
throw new TimeoutException( " Window probing process times out after 10 seconds. " );
}
}
else
{
Console.WriteLine(newWindow.Current.Name);
break ;
}
}
}
Console.WriteLine( " Test finishes................... " );
Console.WriteLine( " Test cost {0} seconds " , (DateTime.Now - startTime).TotalSeconds);
}
public static void EventSyncDemo()
{
eventStartTime = DateTime.Now;
CacheRequest cacheRequest = new CacheRequest();
cacheRequest.TreeScope = TreeScope.Element;
cacheRequest.Add(AutomationElement.NameProperty);
using (cacheRequest.Activate())
{
AutomationElement wpfRoot = AutomationElement.RootElement.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, " Window1 " ));
AutomationElement btnOpenNewWindow = wpfRoot.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, " ButtonOpenFlashWindow " ));
InvokePattern invokPtn = (InvokePattern)btnOpenNewWindow.GetCurrentPattern(InvokePattern.Pattern);
Console.WriteLine( " Register Event " );
AutomationEventHandler eventHandler = new AutomationEventHandler(OnWindowOpenOrClose);
Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent, AutomationElement.RootElement, TreeScope.Children, eventHandler);
Automation.AddAutomationEventHandler(WindowPattern.WindowClosedEvent, AutomationElement.RootElement, TreeScope.Subtree, eventHandler);
invokPtn.Invoke();
Console.WriteLine( " Button Clicked and Wait... " );
Console.ReadLine();
}
}
static void OnWindowOpenOrClose( object src, AutomationEventArgs e)
{
Console.WriteLine( " OnWindowOpenOrClose event triggers " );
if (e.EventId != WindowPattern.WindowOpenedEvent)
{
Console.WriteLine( " It is NOT WindowOpenedEvent.Ignore " );
return ;
}
AutomationElement sourceElement;
try
{
sourceElement = src as AutomationElement;
if (sourceElement.Current.AutomationId == " FlashWindow " )
{
Console.WriteLine(sourceElement.Current.Name);
}
}
catch (ElementNotAvailableException)
{
return ;
}
Console.WriteLine( " Test finishes................... " );
Console.WriteLine( " Test cost {0} seconds " , (DateTime.Now - eventStartTime).TotalSeconds);
}
}
测试演示3: 通过Waiter Pattern来简化timing issue的处理:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
{
private AutomationEvent _eventId;
private AutomationElement _element;
private TreeScope _scope;
private System.Threading.AutoResetEvent _event;
private Condition _condition;
private AutomationEventHandler _eventHandler;
public SimpleWaiter(AutomationEvent eventId, AutomationElement element, TreeScope scope, Condition condition)
{
this ._eventId = eventId;
this ._element = element;
this ._scope = scope;
this ._condition = condition;
_event = new System.Threading.AutoResetEvent( false );
_eventHandler = new AutomationEventHandler(Handler);
Automation.AddAutomationEventHandler(_eventId, _element, _scope, _eventHandler);
}
void Handler( object src, AutomationEventArgs e)
{
AutomationElement sourceElement;
sourceElement = src as AutomationElement;
var finditem = sourceElement.FindFirst(TreeScope.Element, _condition);
if (finditem != null && finditem.Equals(sourceElement))
{
_event.Set();
Automation.RemoveAutomationEventHandler(_eventId, _element, _eventHandler);
}
}
public void Wait( int timeOut)
{
_event.WaitOne(timeOut);
}
public static void WaiterDemo()
{
DateTime startTime = DateTime.Now;
CacheRequest cacheRequest = new CacheRequest();
cacheRequest.TreeScope = TreeScope.Element;
cacheRequest.Add(AutomationElement.NameProperty);
using (cacheRequest.Activate())
{
AutomationElement wpfRoot = AutomationElement.RootElement.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, " Window1 " ));
AutomationElement btnOpenNewWindow = wpfRoot.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, " ButtonOpenFlashWindow " ));
InvokePattern invokPtn = (InvokePattern)btnOpenNewWindow.GetCurrentPattern(InvokePattern.Pattern);
SimpleWaiter waiter = new SimpleWaiter(WindowPattern.WindowOpenedEvent, AutomationElement.RootElement, TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, " FlashWindow " ));
invokPtn.Invoke();
Console.WriteLine( " Button Clicked and Wait... " );
waiter.Wait( 1000 * 1000 );
AutomationElement newWindow = AutomationElement.RootElement.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, " FlashWindow " ));
Console.WriteLine(newWindow.Current.Name);
Console.WriteLine( " Test finishes................... " );
Console.WriteLine( " Test cost {0} seconds " , (DateTime.Now - startTime).TotalSeconds);
}
}
}
测试演示3: 如何模拟真实的鼠标click, 以及如何确保测试目标位于前台:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
public struct HARDWAREINPUT
{
public uint msg;
public ushort paramL;
public ushort paramH;
}
[StructLayout(LayoutKind.Sequential)]
public struct KEYBDINPUT
{
public ushort virtualKeyCode;
public ushort scanCode;
public uint flags;
public uint time;
public IntPtr extraInfo;
}
[StructLayout(LayoutKind.Sequential)]
public struct MOUSEINPUT
{
public int dx;
public int dy;
public uint mouseData;
public uint flags;
public uint time;
public IntPtr extraInfo;
}
[StructLayout(LayoutKind.Explicit)]
public struct InputTypeUnion
{
// Fields
[FieldOffset( 0 )]
public HARDWAREINPUT hi;
[FieldOffset( 0 )]
public KEYBDINPUT ki;
[FieldOffset( 0 )]
public MOUSEINPUT mi;
}
[StructLayout(LayoutKind.Sequential)]
public struct INPUT
{
public uint type;
public InputTypeUnion data;
}
class Clicker
{
[DllImport( " user32.dll " )]
static extern uint SendInput( uint inputCount, ref INPUT inputs, int inputSize);
static public void Click(Point point)
{
MovePointTo (point.X,point.Y);
Click();
}
static public Point GetClickPoint(AutomationElement ele)
{
Point clickablePt;
if (ele.TryGetClickablePoint( out clickablePt))
{
return clickablePt;
}
var boundingRect = ele.Current.BoundingRectangle;
return new Point(boundingRect.X + (boundingRect.Width / 2 ), boundingRect.Y + (boundingRect.Height / 2 ));
}
static void MovePointTo( double absX, double absY)
{
INPUT input = new INPUT();
var virtualScreen = System.Windows.Forms.SystemInformation.VirtualScreen;
absX = (((absX - virtualScreen.X) + 0.5 ) * 65536.0 ) / (( double )virtualScreen.Width);
absY = (((absY - virtualScreen.Y) + 0.5 ) * 65536.0 ) / (( double )virtualScreen.Height);
input.type = 0 ;
input.data.mi.dx = ( int )absX;
input.data.mi.dy = ( int )absY;
input.data.mi.flags = 0xc001 ;
SendInput( 1 , ref input, Marshal.SizeOf(input));
}
static void MouseDown()
{
INPUT input = new INPUT();
input.type = 0 ;
input.data.mi.flags = 2 ; // DOWN
input.data.mi.mouseData |= 1 ;
SendInput( 1 , ref input, Marshal.SizeOf(input));
}
static void MouseUp()
{
INPUT input = new INPUT();
input.type = 0 ;
input.data.mi.flags = 4 ; // UP
input.data.mi.mouseData |= 1 ;
SendInput( 1 , ref input, Marshal.SizeOf(input));
}
static void Click()
{
MouseDown();
MouseUp();
}
}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
{
DateTime startTime = DateTime.Now;
CacheRequest cacheRequest = new CacheRequest();
cacheRequest.TreeScope = TreeScope.Element;
cacheRequest.Add(AutomationElement.NameProperty);
using (cacheRequest.Activate())
{
AutomationElement wpfRoot = AutomationElement.RootElement.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, " Window1 " ));
WindowPattern wndptn = (WindowPattern)wpfRoot.GetCurrentPattern(WindowPattern.Pattern);
var currentStates = wndptn.Current.WindowVisualState;
if (currentStates != WindowVisualState.Minimized)
{
wndptn.SetWindowVisualState(currentStates);
}
else
{
wndptn.SetWindowVisualState(WindowVisualState.Normal);
}
AutomationElement btnOpenNewWindow = wpfRoot.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, " ButtonOpenFlashWindow " ));
InvokePattern invokPtn = (InvokePattern)btnOpenNewWindow.GetCurrentPattern(InvokePattern.Pattern);
SimpleWaiter waiter = new SimpleWaiter(WindowPattern.WindowOpenedEvent, AutomationElement.RootElement, TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, " FlashWindow " ));
var poi = Clicker.GetClickPoint(btnOpenNewWindow);
Clicker.Click(poi);
// invokPtn.Invoke();
Console.WriteLine( " Button Clicked and Wait... " );
waiter.Wait( 1000 * 1000 );
AutomationElement newWindow = AutomationElement.RootElement.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, " FlashWindow " ));
Console.WriteLine(newWindow.Current.Name);
Console.WriteLine( " Test finishes................... " );
Console.WriteLine( " Test cost {0} seconds " , (DateTime.Now - startTime).TotalSeconds);
}
}
测试演示4,5演示WaitForReady以及简单的Engine实现, 代码比较多, 就请自己下载吧.
http://files.cnblogs.com/lixiong/UIAutoDemo.zip