WPF: 使用TestApi模拟用户输入

参考资料:

(1). WPF:自动点击某个FrameworkElement

(2). TestApi - a library of Test APIs

(3). Introduction to TestApi – Part 1: Input Injection APIs

 

1. 模拟用户输入的五种方式:

 (A)直接调用UI element的方法,例如:Button.IsPressed

   (B)利用可用的接口(UIA, MSAA, etc.),例如: AutomationElement

   (C)使用底层输入模拟,与操作系统相关,例如:Windows中的 SendInput Win32 API 和 Raw Input Win32 API

   (D)使用设备驱动模拟

   (E)使用机器人模拟人类操作,例如:敲击键盘

 方法A是framework级别的,只对WPF有效,而对Winform无效;

 方法B比framework级别低一些,但是任然有许多限制,因为一些framework需要的可用接的口实现方式是不同的;

   方法C和D是操作系统级别的,其中D比C要难以实现;

   方法E是一种普遍使用的方法(我想只是在美国吧,汗),虽然它代价昂贵而且速度很慢。

TestApi提供了最常用的B和C两种方式,其中B方式由AutomationUtilities类实现,C方式由Mouse 和 Keyboard两个类实现。

 

2. 使用TestApi模拟的例子

    例1:在WPF Window中查找并按下一个WPF Button,使用AutomationUtilitiesMouse 类.

//
// EXAMPLE #1
// This code below discovers and clicks the Close button in an About
//dialog box, thus dismissing the About dialog box.
//

string aboutDialogName = "About";
string closeButtonName = "Close";

AutomationElementCollection aboutDialogs
= AutomationUtilities.FindElementsByName(
AutomationElement.RootElement,
aboutDialogName);

AutomationElementCollection closeButtons
= AutomationUtilities.FindElementsByName(
aboutDialogs[
0],
closeButtonName);

//
// You can either invoke the discovered control, through its invoke
// pattern...
//

InvokePattern p
=
closeButtons[
0].GetCurrentPattern(InvokePattern.Pattern) as InvokePattern;
p.Invoke();

//
// ... or you can handle the Mouse directly and click on the control.
//

Mouse.MoveTo(closeButton.GetClickablePoint());
Mouse.Click(MouseButton.Left);

 例2:自动查找一个 TextBox 并在其中打字,使用Mouse 和 Keyboard

//
// EXAMPLE #2
// Discover the location of a TextBox with a given name.
//

string textboxName = "ssnInputField";

AutomationElement textBox
= AutomationUtilities.FindElementsByName(
AutomationElement.RootElement,
textboxName)[
0];

Point textboxLocation
= textbox.GetClickablePoint();

//
// Move the mouse to the textbox, click, then type something
//

Mouse.MoveTo(textboxLocation);
Mouse.Click(MouseButton.Left);

Keyboard.Type(
"Hello world.");
Keyboard.Press(Key.Shift);
Keyboard.Type(
"hello, capitalized world.");
Keyboard.Release(Key.Shift);

 

3. 后记

  TestApi中的Mouse Keyboard类可以在任何窗体应用程序中使用,与测试框架和测试流程无关,并且提供了源代码和文档,你可以集成到自己的project中,也可以直接引用Dll

  需要注意的是,虽然TestApi提供如此简单的方法实现UI测试,但是UI测试是一件棘手而复杂的事情,在任何时候都应该尽量避免。一般来说,宁可在应用程序中采用多层设计模式(multi-tier),而设计一个浅\薄(thin)的UI,以尽量规避UI测试。

 

4. 附:如何计算控件位置

   如需单独计算控件元素位置,而不是使用TestApi中的GetClickablePoint()方法,可采用以下方法:

1: /// <summary>

2: /// Get mouse move to location

3: /// </summary>

4: /// <param name="element">element</param>

5: /// <param name="logicalOffset">wpf logical pixel offset</param>

6: /// <returns>screen physical pixel location</returns>

7: public static System.Drawing.Point GetMoveToLocation(FrameworkElement element, Point logicalOffset)

8: {

9:   Point mouseLocation = default(Point);

10:  FlowDirection flowDirection = Window.GetWindow(element).FlowDirection;

11: 

12:  // We don't need to convert element location to physical screen pixel because wpf takes care of it.

13:  Point elementLocation = element.PointToScreen(new Point());

14: 

15: // We need to convert offset to physical screen pixel since we're pass in wpf logical pixel

16: double physicalXOffset = ConvertToPhysicalPixel(logicalOffset.X);

17: double physicalYOffset = ConvertToPhysicalPixel(logicalOffset.Y);

18: 

19: switch (flowDirection)

20:  {

21:    case FlowDirection.LeftToRight:

22:      mouseLocation = new Point(elementLocation.X + physicalXOffset, elementLocation.Y + physicalYOffset);

23:      break;

24:   case FlowDirection.RightToLeft:

25:    // We need to subtract physical offsetX because the element location starting point is in right most

26:     mouseLocation = new Point(elementLocation.X - physicalXOffset, elementLocation.Y + physicalYOffset);

27:     break;

28:  }

29: 

30:   return new System.Drawing.Point(Convert.ToInt32(mouseLocation.X), Convert.ToInt32(mouseLocation.Y));

31: }

32: 

33: /// <summary>

34: /// WPF has its own pixel system in double value type, and screen pixel includes different DPIs is in int value type.

35: /// In 96 dpi, wpf and screen pixels are the same, but other dpi, we need to convert wpf logical pixel to screen physical

36: /// pixel by using formula (wpf pixel value * dpi / 96.0).

37: /// </summary>

38: /// <param name="logicalPixel">Logical(WPF) pixel value</param>

39: /// <returns>Physical(Screen) pixel value</returns>

40: public static int ConvertToPhysicalPixel(double logicalPixel)

41: {

42:    return Convert.ToInt32(logicalPixel * GetDpi() / 96.0);

43: }

44: 

45: /// <summary>

46: /// Get DPI of the system

47: /// </summary>

48: /// <returns></returns>

49: public static float GetDpi()

50: {

51:   using (System.Drawing.Graphics graph = System.Drawing.Graphics.FromHwnd(IntPtr.Zero))

52:   {

53:      if (graph == null)

54:      {

55:        throw new NullReferenceException("Graphics not found");

56:      }

57: 

58:     if (!graph.DpiX.Equals(graph.DpiY))

59:      {

60:        throw new ArithmeticException("DpiX != DpiY");

61:      }

62: 

63:     return graph.DpiX;

64:   }

65: }

posted on 2010-11-23 11:05  I过T  阅读(1016)  评论(0编辑  收藏  举报

导航