使用UI Automation实现自动化测试--5-7

使用UI Automation实现自动化测试--5 (Winfrom和WPF中弹出和关闭对话框的不同处理方式)

在使用UI AutomationWinformWPF的程序测试中发现有一些不同的地方,而这些不同来自于WinformWPF的处理机制不同。下面我们通过一个简单的实例来加以说明:

实例描述

我们使用InvokePattern来点击按钮弹出一个对话框,然后点击对话框中的“确定”按钮关闭对话框。

两种方式对比

首先我们使用如下代码来针对WinfomWPF分别进行测试:


 1public static void ClickButtonById(int processId, string buttonId)
 2{
 3    AutomationElement element = FindElementById(processId, buttonId);
 4    if (element == null)
 5    {
 6        throw new NullReferenceException(string.Format("Element with AutomationId '{0}' can not be find.", element.Current.Name));
 7    }
 8    InvokePattern currentPattern = GetInvokePattern(element);
 9    currentPattern.Invoke();
10}

         上面的代码主要是用来点击按钮,我们的目的是点击按钮弹出MessageBox,然后点击MessageBox中的“OK”按钮关闭此对话框。

通过测试结果发现,上面的代码在WPF程序中完全可以通过,但是在Winform程序中,点击按钮弹出对话框之后发生阻塞现象,导致程序无法向下执行,所以我们通过如上代码视图点击MessageBox中的按钮来关闭此MessageBox将不可能实现,原因就在于Winform中的MessageBox弹出后就会出现阻塞现象,而WPF中使用了另一种处理方式(对此笔者解释的不够深刻,欢迎广大高手帮忙指正, 另外,此问题在Windows 7操作系统上面不会呈现,也可能与操作系统中API对UI Automation的支持有关)。

解决方案

     那么我们通过什么方式来解决此问题呢?很多人会想到多线程,但是我们也可以通过发送异步消息来达到相应的效果。下面我们就通过多线程和发送异步消息的方式来方式来点击WinformMessageBox中的“OK”按钮,此方法同样可以点击WPFMessageBox中的按钮达到关闭对话框的效果。

  1. 1.       多线程的方式

 1public static void ClickButtonById(int processId, string buttonId)
 2{
 3    AutomationElement element = FindElementById(processId, buttonId);
 4
 5    if (element == null)
 6    {
 7        throw new NullReferenceException(string.Format("Element with AutomationId '{0}' can not be find.", element.Current.AutomationId));
 8    }
 9
10    ThreadStart start2 = null;
11
12    if (start2 == null)
13    {
14        start2 = delegate
15        {
16            object obj2;
17            if (!element.TryGetCurrentPattern(InvokePattern.Pattern, out obj2))
18            {
19                throw new Exception(string.Format("Button element with AutomationId '{0}' does not support the InvokePattern.", element.Current.AutomationId));
20            }
21            (obj2 as InvokePattern).Invoke();
22        };
23    }
24    ThreadStart start = start2;
25    new Thread(start).Start();
26    Thread.Sleep(0xbb8);
27}

 

  1. 1.       发送异步消息的方式

 


 1[DllImport("user32.dll", CharSet = CharSet.Auto)]
 2private static extern IntPtr PostMessage(HandleRef hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
 3[DllImport("user32.dll", CharSet = CharSet.Auto)]
 4private static extern IntPtr SendMessage(HandleRef hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
 5
 6public static void ClickButtonById(this Tester tester, int processId, string buttonId)
 7{
 8    AutomationElement element = FindElementById(processId, buttonId);
 9    if (element == null)
10    {
11        throw new NullReferenceException(string.Format("Element with AutomationId '{0}' can not be find.", element.Current.AutomationId));
12    }
13
14    IntPtr nativeWindowHandle = (IntPtr)element.Current.NativeWindowHandle;
15    if (nativeWindowHandle != IntPtr.Zero)
16    {
17        HandleRef hWnd = new HandleRef(nativeWindowHandle, nativeWindowHandle);
18        PostMessage(hWnd, 0xf5, IntPtr.Zero, IntPtr.Zero);
19    }
20    }
21}

 

相关FindWindow和FindElement代码:


 1/// <summary>
 2/// Get the automation elemention of current form.
 3/// </summary>
 4/// <param name="processId">Process Id</param>
 5/// <returns>Target element</returns>
 6public static AutomationElement FindWindowByProcessId(int processId)
 7{
 8    AutomationElement targetWindow = null;
 9    int count = 0;
10    try
11    {
12        Process p = Process.GetProcessById(processId);
13        targetWindow = AutomationElement.FromHandle(p.MainWindowHandle);
14        return targetWindow;
15    }
16    catch (Exception ex)
17    {
18        count++;
19        StringBuilder sb = new StringBuilder();
20        string message = sb.AppendLine(string.Format("Target window is not existing.try #{0}", count)).ToString();
21        if (count > 5)
22        {
23            throw new InvalidProgramException(message, ex);
24        }
25        else
26        {
27            return FindWindowByProcessId(processId);
28        }
29    }
30}
31
32
33/// <summary>
34/// Get the automation element by automation Id.
35/// </summary>
36/// <param name="windowName">Window name</param>
37/// <param name="automationId">Control automation Id</param>
38/// <returns>Automatin element searched by automation Id</returns>
39public static AutomationElement FindElementById(int processId, string automationId)
40{
41    AutomationElement aeForm = FindWindowByProcessId(processId);
42    AutomationElement tarFindElement = aeForm.FindFirst(TreeScope.Descendants,
43    new PropertyCondition(AutomationElement.AutomationIdProperty, automationId));
44    return tarFindElement;
45}

 

          本节主要针对在非Window 7操作系统上面出现的有关WPFWinform中的MessageBox关闭问题作了简单的探讨。

使用UI Automation实现自动化测试--6 (模拟鼠标在自动化测试中的应用)

模拟鼠标在自动化测试中的应用

     尽管UI Automation可以实现控件的自动化,但是,自定义控件的自动化往往由于自定义控件中对自动化的支持不够,而导致无法使用UI Automation的相关Pattern来操作此控件。此时我们可以使用模拟鼠标的操作来达到相应的效果。

. NET并没有提供改变鼠标指针位置、模拟点击操作的函数;但是Windows API提供了。在.NET中模拟鼠标通常是调用底层的API来实现。

注:在使用前必须添加using System.Runtime.InteropServices;命名空间。

移动鼠标到指定的位置

/// <summary>

/// Add mouse move event

/// </summary>

/// <param name="x">Move to specify x coordinate</param>

/// <param name="y">Move to specify y coordinate</param>

/// <returns></returns>

 [DllImport("user32.dll")]

extern static bool SetCursorPos(int x, int y);

该函数可以改变鼠标指针的位置。其中xy是相对于屏幕左上角的绝对位置。

移动鼠标到指定的位置并进行相应的鼠标操作

/// <summary>

/// Mouse click event

/// </summary>

/// <param name="mouseEventFlag">MouseEventFlag </param>

/// <param name="incrementX">X coordinate</param>

/// <param name="incrementY">Y coordinate</param>

/// <param name="data"></param>

/// <param name="extraInfo"></param>

[DllImport("user32.dll")]

extern static void mouse_event(int mouseEventFlag, int incrementX, intincrementY, uint data, UIntPtr extraInfo);

这个函数不仅可以设置鼠标指针绝对的位置,而且可以以相对坐标来设置。另外,该函数还可以模拟鼠标左右键点击、鼠标滚轮操作等。其中的MouseEventFlag是一个常量,定义如下:

const int MOUSEEVENTF_MOVE = 0x0001;

const int MOUSEEVENTF_LEFTDOWN = 0x0002;

const int MOUSEEVENTF_LEFTUP = 0x0004;

const int MOUSEEVENTF_RIGHTDOWN = 0x0008;

const int MOUSEEVENTF_RIGHTUP = 0x0010;

const int MOUSEEVENTF_MIDDLEDOWN = 0x0020;

const int MOUSEEVENTF_MIDDLEUP = 0x0040;

const int MOUSEEVENTF_ABSOLUTE = 0x8000;

上面列举的两个函数的详细说明,可参考MSDN帮助文档。

如下代码为在控件的指定位置上单击鼠标左键和右键。

 

public void ClickLeftMouse(int processId, string automationId)

{

    AutomationElement element = FindElementById(processId, automationId);

    if (element == null)

    {

        throw new NullReferenceException(string.Format("Element with AutomationId '{0}' and Name '{1}' can not be find.",

            element.Current.AutomationId, element.Current.Name));

    }

 

    Rect rect = element.Current.BoundingRectangle;

    int IncrementX = (int)(rect.Left + rect.Width / 2);

    int IncrementY = (int)(rect.Top + rect.Height / 2);

 

    //Make the cursor position to the element.

    SetCursorPos(IncrementX, IncrementY);

    //Make the left mouse down and up.

    mouse_event(MOUSEEVENTF_LEFTDOWN, IncrementX, IncrementY, 0, 0);

    mouse_event(MOUSEEVENTF_LEFTUP, IncrementX, IncrementY, 0, 0);

}

 

public void ClickRightMouse(int processId, string automationId)

{

    AutomationElement element = FindElementById(processId, automationId);

    if (element == null)

    {

        throw new NullReferenceException(string.Format("Element with AutomationId '{0}' and Name '{1}' can not be find.",

            element.Current.AutomationId, element.Current.Name));

    }

 

    Rect rect = element.Current.BoundingRectangle;

    int IncrementX = (int)(rect.Left + rect.Width / 2);

    int IncrementY = (int)(rect.Top + rect.Height / 2);

 

    //Make the cursor position to the element.

    SetCursorPos(IncrementX, IncrementY);

    //Make the left mouse down and up.

    mouse_event(MOUSEEVENTF_RIGHTDOWN, IncrementX, IncrementY, 0, 0);

    mouse_event(MOUSEEVENTF_RIGHTUP, IncrementX, IncrementY, 0, 0);

}

 

 

在上面的代码中,我们通过如下步骤来达到点击鼠标的目的:

  1. 1.       通过UI Automation来找到需要鼠标点击的控件;
  2. 2.       然后取得此控件的坐标;
  3. 3.       在设置点击坐标在控件的中心位置;
  4. 4.       移动鼠标的位置到控件的中心位置(此步骤可以不要,但是为了达到双保险的效果,我们还是移动一次)。
  5. 5.       调用Win32 API模拟鼠标点击控件。

注:Rect需要引用WindowBase.dll,添加using System.Windows;明明空间。

    下面的例子演示了通过模拟鼠标左键点击button来弹出对话框,并且模拟鼠标点击对话框中的“OK”按钮关闭对话框。

using System;

using System.Text;

using System.Diagnostics;

using System.Threading;

using System.Windows.Automation;

using System.Runtime.InteropServices;

using System.Windows;

 

namespace UIATest

{

    class Program

    {

        static void Main(string[] args)

        {

            Process process =Process.Start(@"F:\CSharpDotNet\AutomationTest\ATP\ATP.TestForm\bin\Debug\ATP.TestForm.exe");

            int processId = process.Id;

            Thread.Sleep(1000);

 

            ClickLeftMouse(processId, "button1");

            Thread.Sleep(3000);

 

            ClickLeftMouse(processId, "2");

            Console.WriteLine("Test finised.");

        }

 

        #region ClickMouse

 

        #region Import DLL

        /// <summary>

        /// Add mouse move event

        /// </summary>

        /// <param name="x">Move to specify x coordinate</param>

        /// <param name="y">Move to specify y coordinate</param>

        /// <returns></returns>

        [DllImport("user32.dll")]

        extern static bool SetCursorPos(int x, int y);

 

        /// <summary>

        /// Mouse click event

        /// </summary>

        /// <param name="mouseEventFlag">MouseEventFlag </param>

        /// <param name="incrementX">X coordinate</param>

        /// <param name="incrementY">Y coordinate</param>

        /// <param name="data"></param>

        /// <param name="extraInfo"></param>

        [DllImport("user32.dll")]

        extern static void mouse_event(int mouseEventFlag, int incrementX, int incrementY, uint data, UIntPtrextraInfo);

 

        const int MOUSEEVENTF_MOVE = 0x0001;

        const int MOUSEEVENTF_LEFTDOWN = 0x0002;

        const int MOUSEEVENTF_LEFTUP = 0x0004;

        const int MOUSEEVENTF_RIGHTDOWN = 0x0008;

        const int MOUSEEVENTF_RIGHTUP = 0x0010;

        const int MOUSEEVENTF_MIDDLEDOWN = 0x0020;

        const int MOUSEEVENTF_MIDDLEUP = 0x0040;

        const int MOUSEEVENTF_ABSOLUTE = 0x8000;

 

        #endregion 

        public static void ClickLeftMouse(int processId, string automationId)

        {

            AutomationElement element = FindElementById(processId, automationId);

 

            if (element == null)

            {

                throw new NullReferenceException(string.Format("Element with AutomationId '{0}' and Name '{1}' can not be find.",

                    element.Current.AutomationId, element.Current.Name));

            }

 

            Rect rect = element.Current.BoundingRectangle;

            int IncrementX = (int)(rect.Left + rect.Width / 2);

            int IncrementY = (int)(rect.Top + rect.Height / 2);

 

            //Make the cursor position to the element.

            SetCursorPos(IncrementX, IncrementY);

 

            //Make the left mouse down and up.

            mouse_event(MOUSEEVENTF_LEFTDOWN, IncrementX, IncrementY, 0, UIntPtr.Zero);

            mouse_event(MOUSEEVENTF_LEFTUP, IncrementX, IncrementY, 0, UIntPtr.Zero);

        }

 

        #endregion

 

        /// <summary>

        /// Get the automation elemention of current form.

        /// </summary>

        /// <param name="processId">Process Id</param>

        /// <returns>Target element</returns>

        public static AutomationElement FindWindowByProcessId(int processId)

        {

            AutomationElement targetWindow = null;

            int count = 0;

            try

            {

                Process p = Process.GetProcessById(processId);

                targetWindow = AutomationElement.FromHandle(p.MainWindowHandle);

                return targetWindow;

            }

            catch (Exception ex)

            {

                count++;

                StringBuilder sb = new StringBuilder();

                string message = sb.AppendLine(string.Format("Target window is not existing.try #{0}", count)).ToString();

                if (count > 5)

                {

                    throw new InvalidProgramException(message, ex);

                }

                else

                {

                    return FindWindowByProcessId(processId);

                }

            }

        }

 

        /// <summary>

        /// Get the automation element by automation Id.

        /// </summary>

        /// <param name="windowName">Window name</param>

        /// <param name="automationId">Control automation Id</param>

        /// <returns>Automatin element searched by automation Id</returns>

        public static AutomationElement FindElementById(int processId, string automationId)

        {

            AutomationElement aeForm = FindWindowByProcessId(processId);

            AutomationElement tarFindElement = aeForm.FindFirst(TreeScope.Descendants,

            new PropertyCondition(AutomationElement.AutomationIdProperty, automationId));

            return tarFindElement;

        }

    }

}

Summary

     本节首先简单介绍了如何在.NET中模拟鼠标操作,进而通过实例演示了模拟鼠标在基于UI Automation的自动化测试中应用。

使用UI Automation实现自动化测试--7.1 (模拟键盘输入数据在自动化测试中的应用)

Chapter 7 Simulated keyboard

 

7.1 模拟键盘输入

7.1.1 Introduction

此部分我们通过System.Windows.Forms.SendKeys类中的Send方法来模拟通过键盘输入数据到相应的控件中。

7.1.2 SendKeys Class IntroductionMSDN

使用 SendKeys 将键击和组合键击发送到活动应用程序。此类无法实例化。若要发送一个键击给某个类并立即继续程序流,请使用 Send。若要等待键击启动的任何进程,请使用 SendWait

每个键都由一个或多个字符表示。若要指定单个键盘字符,请使用该字符本身。例如,若要表示字母 A,请将字符串“A”传递给方法。若要表示多个字符,请将各个附加字符追加到它之前的字符的后面。若要表示字母 A C,请将参数指定为“ABC”

加号 (+)、插入符号 (^)、百分号 (%)、波浪号 (~) 以及圆括号 ( )  SendKeys 具有特殊含义。若要指定这些字符中的某个字符,请将其放在大括号 ({}) 内。例如,若要指定加号,请使用“{+}”。若要指定大括号字符,请使用“{{}”“{}}”。中括号 ([ ])  SendKeys 没有特殊含义,但必须将它们放在大括号内。在其他应用程序中,中括号具有特殊含义,此含义可能会在发生动态数据交换 (DDE) 时起重要作用。

7.1.3 SendKeys MethodMSDN

SendKeys有两个重要的方法:

  1. 1.         Send方法:向活动应用程序发送击键。 
  2. 2.         SendWait方法:向活动应用程序发送给定的键,然后等待消息被处理。 

有关SendKeys的参考:

英文站点:http://msdn.microsoft.com/en-us/library/8c6yea83(VS.85).aspx

中文站点:http://msdn.microsoft.com/zh-cn/library/system.windows.forms.sendkeys_methods(VS.80).aspx

 

    下面我们通过一个实例来演示通过SendKeys类来模拟键盘操作进行数据输入和快捷菜单的打开:

using System;

using System.Text;

using System.Diagnostics;

using System.Threading;

using System.Windows.Automation;

using System.Runtime.InteropServices;

using System.Windows;

using System.Windows.Forms;

 

namespace UIATest

{

    class Program

    {

        static void Main(string[] args)

        {

            Process process = Process.Start(@"E:\WorkBook\ATP\WpfApp\bin\Debug\WpfApp.exe");

            int processId = process.Id;

 

            AutomationElement element = FindElementById(processId, "textBox1");

            SendKeys sendkeys = new SendKeys();

            sendkeys.Sendkeys(element, "Sending keys to input data");

            Console.WriteLine(sendkeys.ToString());

            sendkeys.Sendkeys(element, sendkeys.ContextMenu);

            Console.WriteLine(sendkeys.ToString());

            Console.WriteLine("Test finised.");  

        }

 

        /// <summary>

        /// Get the automation elemention of current form.

        /// </summary>

        /// <param name="processId">Process Id</param>

        /// <returns>Target element</returns>

        public static AutomationElement FindWindowByProcessId(int processId)

        {

            AutomationElement targetWindow = null;

            int count = 0;

            try

            {

                Process p = Process.GetProcessById(processId);

                targetWindow = AutomationElement.FromHandle(p.MainWindowHandle);

                return targetWindow;

            }

            catch (Exception ex)

            {

                count++;

                StringBuilder sb = new StringBuilder();

                string message = sb.AppendLine(string.Format("Target window is not existing.try #{0}", count)).ToString();

                if (count > 5)

                {

                    throw new InvalidProgramException(message, ex);

                }

                else

                {

                    return FindWindowByProcessId(processId);

                }

            }

        }

 

        /// <summary>

        /// Get the automation element by automation Id.

        /// </summary>

        /// <param name="windowName">Window name</param>

        /// <param name="automationId">Control automation Id</param>

        /// <returns>Automatin element searched by automation Id</returns>

        public static AutomationElement FindElementById(int processId, string automationId)

        {

            AutomationElement aeForm = FindWindowByProcessId(processId);

            AutomationElement tarFindElement = aeForm.FindFirst(TreeScope.Descendants,

            new PropertyCondition(AutomationElement.AutomationIdProperty, automationId));

            return tarFindElement;

        }

    }

}

      我们自定义一个SendKeys类来对已有的SendKeys类进行优化,代码如下:

 

using System;

using System.Windows.Automation;

using System.Threading;

using System.Text;

using System.Windows.Forms;

 

namespace UIATest

{

    public class SendKeys

    {

        StringBuilder builder = new StringBuilder();

 

        public string Alt = "%";

        public string ContextMenu = "+{F10}";

        public string Ctrl = "^";

        public string Shift = "+";

        public string Enter = "{Enter}";

        public string Delete = "{Del}";

        public string Save = "^S";

        public string SaveAll = "^+S";

        public string Copy = "^C";

        public string Cut = "^X";

        public string Paste = "^V";

        public string Undo = "^Z";

        public string Redo = "^Y";

        public string Print = "^P";

        public string Help = "{F1}";

        public string New = "^N";

 

        public string[] Keys{ get; set; }

 

 

        public void Sendkeys(AutomationElement element, string[] keys)

        {

            this.Keys = keys;

            try

            {

                element.SetFocus();

            }

            catch (Exception exception)

            {

                throw new Exception("Cannot set focus to this element.", exception);

            }

            string myKeys = "";

            foreach (string str2 in this.Keys)

            {

                myKeys = myKeys + str2;

            }

            Thread.Sleep(200);

            if ((this.ContainsUnescapedKey(myKeys, '^') || this.ContainsUnescapedKey(myKeys, '%')) ||this.ContainsUnescapedKey(myKeys, '+'))

            {

                myKeys = myKeys.ToLower();

            }

            System.Windows.Forms.SendKeys.SendWait(myKeys);

            Thread.Sleep(0x3e8);

        }

 

        public void Sendkeys(AutomationElement element, string myKeys)

        {

            this.Keys = new string[1];

            this.Keys[0] = myKeys;

            try

            {

                element.SetFocus();

            }

            catch (Exception exception)

            {

                throw new Exception("Cannot set focus to this element.", exception);

            }

            Thread.Sleep(200);

            if ((this.ContainsUnescapedKey(myKeys, '^') || this.ContainsUnescapedKey(myKeys, '%')) ||this.ContainsUnescapedKey(myKeys, '+'))

            {

                myKeys = myKeys.ToLower();

            }

            System.Windows.Forms.SendKeys.SendWait(myKeys);

            Thread.Sleep(0x3e8);

        }

       

        private bool ContainsUnescapedKey(string keys, char key)

        {

            for (int i = 0; i < keys.Length; i++)

            {

                if (keys[i] == key)

                {

                    if ((i == 0) || (i == (keys.Length - 1)))

                    {

                        return true;

                    }

                    if ((keys[i - 1] != '{') || (keys[i + 1] != '}'))

                    {

                        return true;

                    }

                }

            }

            return false;

        }

 

        private string KeysToString(string[] keys)

        {

           

            if (keys != null)

            {

                for (int i = 0; i < keys.Length; i++)

                {

                    string str = keys[i];

                    if (str == null)

                    {

                        builder.Append(keys[i]);

                    }

                    int length = keys.Length - 1;

                    switch (str)

                    {

                        case "^":

                            builder.Append("Ctrl");

                            IsEquals(i, length, builder);

                            break;

                        case "+{F10}":

                            builder.Append("Open Context Menu");

                            IsEquals(i, length, builder);

                            break;

                        case "%":

                            builder.Append("Alt");

                            IsEquals(i, length, builder);

                            break;

                        case "+":

                            builder.Append("Shift");

                            IsEquals(i, length, builder);

                            break;

                        case "^S":

                            builder.Append("Save");

                            IsEquals(i, length, builder);

                            break;

                        case "^X":

                            builder.Append("Cut");

                            IsEquals(i, length, builder);

                            break;

                        case "^C":

                            builder.Append("Copy");

                            IsEquals(i, length, builder);

                            break;

                        case "^V":

                            builder.Append("Paste");

                            IsEquals(i, length, builder);

                            break;

                        case "^+S":

                            builder.Append("Save All");

                            IsEquals(i, length, builder);

                            break;

                        case "^P":

                            builder.Append("Print");

                            IsEquals(i, length, builder);

                            break;

                        case "^Z":

                            builder.Append("Undo");

                            IsEquals(i, length, builder);

                            break;

                        case "^Y":

                            builder.Append("Redo");

                            IsEquals(i, length, builder);

                            break;

                        case "^N":

                            builder.Append("New");

                            IsEquals(i, length, builder);

                            break;

                        default:

                            builder.Append(keys[i]);

                            IsEquals(i, length, builder);

                            break;

                    }

                }

            }

            return builder.ToString();

        }

 

        void IsEquals(int i, int length, StringBuilder builder)

        {

            if(i<length)

                builder.Append("+");

        }

           

        #region Public Method

 

        public override string ToString()

        {

            return string.Format("Sendkeys to input data or operator with keys = '{0}'",

                this.KeysToString(Keys));

        }

        #endregion

    }

}

 

    如下代码为对应的WPF窗体XAML代码:

<Window x:Class="WpfApp.SendKeysInputData"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="SendKeysInputData" Height="250" Width="367">

    <Grid>

        <TextBox Margin="28,80,41,101" Name="textBox1">

            <TextBox.ContextMenu>

                <ContextMenu>

                    <MenuItem Header="Open" />

                    <MenuItem Header="Save" />

                    <MenuItem Header="New" />

                    <MenuItem Header="Save As" />

                    <MenuItem Header="Load" />

                    <MenuItem Header="ReLoad" />

                </ContextMenu>

            </TextBox.ContextMenu>

        </TextBox>

    </Grid>

</Window>

7.1.4 Summary

本小节首先简单介绍了如何在.NET通过调用Win32 API来模拟键盘的操作,进而通过实例演示了模拟鼠标与模拟键盘在基于UI Automation的自动化测试中应用。

使用UI Automation实现自动化测试--7.2 (模拟键盘复杂操作在自动化测试中的应用)

7.2 模拟键盘复杂操作

7.2.1 Introduction

键盘的操作相对比较灵活多变,在7.1节中我们通过System.Windows.Forms.SendKeys类中的方法来实现模拟按键。但它是一种模拟常规的敲击键盘,但有时候我们需要按下某个键而不松开,例如按住CTRL键多选操作等。在此种情况下我们需要同调用Win32 API中的keybd_event函数模拟对键盘上的某些键的Down和Up操作。

7.2.2 keybd_event Function

    如下代码为模拟键盘操作的Win32 API函数:

/// <summary>

/// Simulate the keyboard operations

/// </summary>

/// <param name="bVk">Virtual key value</param>

/// <param name="bScan">Hardware scan code</param>

/// <param name="dwFlags">Action flag</param>

/// <param name="dwExtraInfo">Extension information which assication the keyborad action</param>

[DllImport("user32.dll")]

static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo);

上表中的代码是通过调用Win32 API中的keybd_event函数模拟对键盘上的某些键的Down和Up操作。

其中参数列表对应的解释如下

static extern void keybd_event

(

byte bVk,//虚拟键值

byte bScan,//硬件扫描码

uint dwFlags,//动作标识

uint dwExtraInfo//与键盘动作关联的辅加信息

);

 

/// <summary>

/// Map virtual key

/// </summary>

/// <param name="uCode">Virtual key code</param>

/// <param name="uMapType">Defines the translation to be performed</param>

/// <returns></returns>

[DllImport("user32.dll")]

static extern byte MapVirtualKey(uint uCode, uint uMapType);

上表中的代码中函数将一虚拟键码翻译(映射)成一扫描码或一字符值,或者将一扫描码翻译成一虚拟键码。

参数详细解释如下

1)         uCode:定义一个键的扫描码或虚拟键码。该值如何解释依赖于uMapType参数的值。

2)         wMapType:定义将要执行的翻译。该参数的值依赖于uCode参数的值。取值如下:

a)         0:代表uCode是一虚拟键码且被翻译为一扫描码。若一虚拟键码不区分左右,则返回左键的扫描码。若未进行翻译,则函数返回O

b)         1:代表uCode是一扫描码且被翻译为一虚拟键码,且此虚拟键码不区分左右。若未进行翻译,则函数返回0

c)         2:代表uCode为一虚拟键码且被翻译为一未被移位的字符值存放于返回值的低序字中。死键(发音符号)则通过设置返回值的最高位来表示。若未进行翻译,则函数返回0

d)         3:代表uCode为一扫描码且被翻译为区分左右键的一虚拟键码。若未进行翻译,则函数返回0

返回值

返回值可以是一扫描码,或一虚拟键码,或一字符值,这完全依赖于不同的uCodeuMapType的值。若未进行翻译,则函数返回O

 

下面我们通过模拟鼠标和键盘相结合的方式来达到多选ListBox中的项:

using System;

using System.Text;

using System.Diagnostics;

using System.Threading;

using System.Windows.Automation;

using System.Runtime.InteropServices;

using System.Windows;

using System.Windows.Forms;

 

namespace UIATest

{

    class Program

    {

        static void Main(string[] args)

        {

            Process process = Process.Start(@"E:\WorkBook\ATP\WpfApp\bin\Debug\WpfApp.exe");

            int processId = process.Id;

 

            AutomationElement element = FindElementById(processId, "ListBox1");

            AutomationElementCollection listItems = element.FindAll(TreeScope.Children,

                new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ListItem));

 

            SendCtrlKey(true);

            Thread.Sleep(500);

            ClickLeftMouse(listItems[0]);

            Thread.Sleep(500);

            ClickLeftMouse(listItems[2]);

            Thread.Sleep(500);

            ClickLeftMouse(listItems[3]);

            Thread.Sleep(500);

            ClickLeftMouse(listItems[5]);

            SendCtrlKey(false);

 

            //Note: The statement 'Thread.Sleep (500)' is to make each of these steps can be seen by tester.

            Console.WriteLine("Test finised.");  

        }

 

        #region Simulate keyboard

        /// <summary>

        /// Simulate the keyboard operations

        /// </summary>

        /// <param name="bVk">Virtual key value</param>

        /// <param name="bScan">Hardware scan code</param>

        /// <param name="dwFlags">Action flag</param>

        /// <param name="dwExtraInfo">Extension information which assication the keyborad action</param>

        [DllImport("user32.dll")]

        static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo);

        /// <summary>

        /// Map virtual key

        /// </summary>

        /// <param name="uCode">Virtual key code</param>

        /// <param name="uMap">Defines the translation to be performed</param>

        /// <returns></returns>

        [DllImport("user32.dll")]

        static extern byte MapVirtualKey(uint uCode, uint uMap);

 

        public static void SendCtrlKey(bool isKeyDown)

        {

            if (!isKeyDown)

            {

                keybd_event(17, MapVirtualKey(17, 0), 0x2, 0);//Up CTRL key

            }

            else

            {

                keybd_event(17, MapVirtualKey(17, 0), 0, 0); //Down CTRL key

            }

        }

 

        #endregion

 

        #region ClickMouse

 

        #region Import DLL

        /// <summary>

        /// Add mouse move event

        /// </summary>

        /// <param name="x">Move to specify x coordinate</param>

        /// <param name="y">Move to specify y coordinate</param>

        /// <returns></returns>

        [DllImport("user32.dll")]

        extern static bool SetCursorPos(int x, int y);

 

        /// <summary>

        /// Mouse click event

        /// </summary>

        /// <param name="mouseEventFlag">MouseEventFlag </param>

        /// <param name="incrementX">X coordinate</param>

        /// <param name="incrementY">Y coordinate</param>

        /// <param name="data"></param>

        /// <param name="extraInfo"></param>

        [DllImport("user32.dll")]

        extern static void mouse_event(int mouseEventFlag, int incrementX, int incrementY, uint data,UIntPtr extraInfo);

 

        const int MOUSEEVENTF_MOVE = 0x0001;

        const int MOUSEEVENTF_LEFTDOWN = 0x0002;

        const int MOUSEEVENTF_LEFTUP = 0x0004;

        const int MOUSEEVENTF_RIGHTDOWN = 0x0008;

        const int MOUSEEVENTF_RIGHTUP = 0x0010;

        const int MOUSEEVENTF_MIDDLEDOWN = 0x0020;

        const int MOUSEEVENTF_MIDDLEUP = 0x0040;

        const int MOUSEEVENTF_ABSOLUTE = 0x8000;

 

        #endregion 

        public static void ClickLeftMouse(AutomationElement element)

        {

            if (element == null)

            {

                throw new NullReferenceException(string.Format("Element with AutomationId '{0}' and Name '{1}' can not be find.",

                    element.Current.AutomationId, element.Current.Name));

            }

 

            Rect rect = element.Current.BoundingRectangle;

            int IncrementX = (int)(rect.Left + rect.Width / 2);

            int IncrementY = (int)(rect.Top + rect.Height / 2);

 

            //Make the cursor position to the element.

            SetCursorPos(IncrementX, IncrementY);

 

            //Make the left mouse down and up.

            mouse_event(MOUSEEVENTF_LEFTDOWN, IncrementX, IncrementY, 0, UIntPtr.Zero);

            mouse_event(MOUSEEVENTF_LEFTUP, IncrementX, IncrementY, 0, UIntPtr.Zero);

        }

 

        #endregion

 

        /// <summary>

        /// Get the automation elemention of current form.

        /// </summary>

        /// <param name="processId">Process Id</param>

        /// <returns>Target element</returns>

        public static AutomationElement FindWindowByProcessId(int processId)

        {

            AutomationElement targetWindow = null;

            int count = 0;

            try

            {

                Process p = Process.GetProcessById(processId);

                targetWindow = AutomationElement.FromHandle(p.MainWindowHandle);

                return targetWindow;

            }

            catch (Exception ex)

            {

                count++;

                StringBuilder sb = new StringBuilder();

                string message = sb.AppendLine(string.Format("Target window is not existing.try #{0}", count)).ToString();

                if (count > 5)

                {

                    throw new InvalidProgramException(message, ex);

                }

                else

                {

                    return FindWindowByProcessId(processId);

                }

            }

        }

 

        /// <summary>

        /// Get the automation element by automation Id.

        /// </summary>

        /// <param name="windowName">Window name</param>

        /// <param name="automationId">Control automation Id</param>

        /// <returns>Automatin element searched by automation Id</returns>

        public static AutomationElement FindElementById(int processId, string automationId)

        {

            AutomationElement aeForm = FindWindowByProcessId(processId);

            AutomationElement tarFindElement = aeForm.FindFirst(TreeScope.Descendants,

            new PropertyCondition(AutomationElement.AutomationIdProperty, automationId));

            return tarFindElement;

        }

    }

}

    

     对应的XAML代码如下:

<Window x:Class="WpfApp.ListBox"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="ListBox" Height="219" Width="349">

    <Grid>

        <ListBox SelectionMode="Extended" Name="ListBox1">

            <ListBoxItem>ListBoxItem 1</ListBoxItem>

            <ListBoxItem>ListBoxItem 2</ListBoxItem>

            <ListBoxItem>ListBoxItem 3</ListBoxItem>

            <ListBoxItem>ListBoxItem 4</ListBoxItem>

            <ListBoxItem>ListBoxItem 5</ListBoxItem>

            <ListBoxItem>ListBoxItem 6</ListBoxItem>

            <ListBoxItem>ListBoxItem 7</ListBoxItem>

            <ListBoxItem>ListBoxItem 8</ListBoxItem>

            <ListBoxItem>ListBoxItem 9</ListBoxItem>

            <ListBoxItem>ListBoxItem 10</ListBoxItem>

        </ListBox>

    </Grid>

</Window>

 

7.2.3 Summary

本小节首先简单介绍了如何在.NET通过调用Win32 API来模拟键盘的操作,进而通过实例演示了模拟鼠标与模拟键盘在基于UI Automation的自动化测试中应用。

 

posted @ 2015-04-15 22:55  Ellie_Auto  阅读(5339)  评论(0编辑  收藏  举报