自动操作软件 获取软件按钮内容 UIAutomation 软件自动化测试(我的一点补充)

近期玩了下 UIAutomation。C# 中有 UI Automation 库,C++可以看msdn的 Accessibility。这两个东西网上能找到的东西太少了,只能自己看微软的官方文档。我把我的一些代码段贴到下面,希望能帮助需要的人。Python有个库 UIAutomation,就是封装微软提供的 UIAutomation,网上有一些 资料。但是这个库的作者说没有准备文档,所以需要的人自己看Demo去猜函数该怎么用吧。我用的时候结合了另一个库 pyautogui,用于操作键盘、鼠标。

Python 的 UI Automation 这个库怎么用可以直接看 uiautomation.py 这个文件,其中 class Control() 这个在5156行。在寻找控件的时候可先用命令行工具 automation.py -t3 去查找,具体参考 这个网页;关于找到元素的代码参考 这个。在遇到问题时可以参考 C# 的代码。比如从 EditControl 获取内容:

var clickPattern = (TextPattern)textedit.GetCurrentPattern(TextPattern.Pattern);
Console.WriteLine(clickPattern.DocumentRange.GetText(100));
Console.WriteLine(clickPattern.DocumentRange.GetText(100));

相应的 python 代码:

EditControl.GetPattern(PatternId.TextPattern).DocumentRange.GetText(100)

传统的Win32程序有句柄这个东西,每个控件的句柄很容易获取。WPF 程序的句柄就一个,是主窗口的,里边的控件是框架渲染的,所以 spy++ 无能为力了。UIAutomation是向 WPF 窗体发消息,如果窗口不处理这个消息,那也是获取不到控件的,比如QQ轻聊版。处理消息的是 LresultFromObject 函数。引用 大牛 的话:

UIAutomation的工作原理是:
当你用UIAutomation操作程序时,UIAutomation会给程序发送WM_GETOBJECT消息,
如果程序处理WM_GETOBJECT消息,实现UI Automation Provider,并调用函数
UiaReturnRawElementProvider(HWND hwnd,WPARAM wparam,LPARAM lparam,IRawElementProviderSimple *el),
此程序就支持UIAutomation。
IRawElementProviderSimple就是UI Automation Provider,包含了控件的各种信息,如Name,ClassName,ContorlType,坐标...
UIAutomation根据程序返回的IRawElementProviderSimple,就能遍历程序的控件,得到控件各种属性,进行自动化操作。
所以如果你发现UIAutomation不能识别一些程序内的控件或部分不支持,这并不是UIAutomation的问题,
是程序作者没有处理WM_GETOBJECT或没有实现UIAutomation Provider,或者故意不想支持UIAutomation。

很多DirectUI程序都没有实现UIAutomation Provider,所以不支持自动化,要想支持自动化,必须程序作者修改源码支持。

和 spy++ 类似的有个 snoop,在 GitHub 上开源的,可以看 WPF 窗体的控件树。自动化测试的商业软件有 Ranorex,可以试用30天。他录制的过程生成了一个解决方案,这个解决方案可以直接在 VS 中打开进行更改。还有些 FlaUI,UISpy.exe,QAliber,White.NUnit IL ,Inspector,ildasm,ilspy 我就没尝试了。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Diagnostics;
using System.Threading;
using System.Windows.Automation.Provider;
using System.Windows.Automation.Text;
using System.Windows.Automation;
using System.Windows.Forms;
using System.Runtime.InteropServices;  // DllImport() 需要它。

namespace UITest
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        [DllImport("USER32.DLL", CharSet = CharSet.Unicode)]
        public static extern IntPtr FindWindow(string lpClassName,string lpWindowName);

        // Activate an application window.
        [DllImport("USER32.DLL")]
        public static extern bool SetForegroundWindow(IntPtr hWnd);
        public MainWindow()
        {
            InitializeComponent();
            try
            {
                Console.WriteLine("\nBegin WinForm UIAutomation test run\n");
                // launch Form1 application
                // get refernce to main Form control
                // get references to user controls
                // manipulate application
                // check resulting state and determine pass/fail

                Console.WriteLine("\nBegin WinForm UIAutomation test run\n");
                Console.WriteLine("Launching WinFormTest application");
                //启动被测试的程序
                Process p = Process.Start(@"D:\Program Files (x86)\{exe文件路径}");

                //自动化根元素.桌面的根。
                AutomationElement aeDeskTop = AutomationElement.RootElement;

                Thread.Sleep(2000);
                // AutomationElement aeForm = AutomationElement.FromHandle(p.MainWindowHandle);
                AutomationElement aeForm = null;
                //获得对主窗体对象的引用,该对象实际上就是 Form1 应用程序(方法一)
                //if (null == aeForm)
                //{
                //    Console.WriteLine("Can not find the WinFormTest from.");
                //}

                //获得对主窗体对象的引用,该对象实际上就是 Form1 应用程序(方法二)
                //有些exe启动另外一个exe自己就退出了,所以要自己再找一遍。
                int numWaits = 0;
                do
                {
                    Console.WriteLine("Looking for WinFormTest……");
                    //查找第一个自动化元素
                    aeForm = aeDeskTop.FindFirst(TreeScope.Children, new PropertyCondition(
                        AutomationElement.NameProperty, "{窗口名称}"));
                    ++numWaits;
                    Thread.Sleep(100);
                } while (null == aeForm && numWaits < 50);
                if (null == aeForm)
                    throw new NullReferenceException("Failed to find WinFormTest.");
                else
                    Console.WriteLine("Found it!");

                Console.WriteLine("Finding all user controls");

                //找到第一次出现的Button控件
                // AutomationElement aeButton = aeForm.FindFirst(TreeScope.Children,
                //    new PropertyCondition(AutomationElement.NameProperty, "Button"));   
                // 根据Button控件Content属性精确查找。

                //找到所有的TextBox控件
                //AutomationElementCollection aeAllTextBoxes = aeForm.FindAll(TreeScope.Children,
                //    new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit));

                // 控件初始化的顺序是先初始化后添加到控件
                // this.Controls.Add(this.textBox3);                  
                // this.Controls.Add(this.textBox2);
                // this.Controls.Add(this.textBox1);

                //AutomationElement aeTextBox1 = aeAllTextBoxes[2];
                //AutomationElement aeTextBox2 = aeAllTextBoxes[0];
                //AutomationElement aeTextBox3 = aeAllTextBoxes[1];   
                // 这个顺序与你编程的时候往面板上拖控件的顺序有关。

                Thread.Sleep(3000);
                AutomationElementCollection aeRadioButtons = aeForm.FindAll(TreeScope.Descendants,
                       new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.RadioButton));                
                Console.WriteLine(aeRadioButtons[10].Current.Name); // 这一句必须要有,否则aeRadioButtons就是个空的,奇怪!

                IntPtr hWnd =FindWindow(null,"{窗口名}");

                //for (int i = 0; i < 100; i++) {
                //    Console.WriteLine(aeWhats[i].GetCurrentPropertyValue(AutomationElement.CultureProperty) as string);
                //    Console.WriteLine(i.ToString());    // 只发现了一个。
                //}
                for (int i = 0; i < aeRadioButtons.Count; i++)
                {
                    Console.WriteLine("按钮名字:");
                    Console.WriteLine(aeRadioButtons[i].Current.Name);
                    if (aeRadioButtons[i].Current.Name == "{按钮名字}")
                    {
                        Console.WriteLine("找到RadioButton");
                        // 这里的RadioButton不是用来选的,是用来点击的。文档说RadioButon仅支持SelectionItemPattern,不支持点击。
                        // SelectionItemPattern selectionItemPattern = aeRadioButtons[i].GetCurrentPattern(SelectionItemPattern.Pattern) as SelectionItemPattern;
                        // selectionItemPattern.Select();
                        Thread.Sleep(100);
                        SetForegroundWindow(hWnd);
                        aeRadioButtons[i].SetFocus();
                        Console.WriteLine("已设置焦点");
                        // 这个不行,原因 https://stackoverflow.com/questions/2958561/when-using-sendkeys-invalidoperationexception-undo-operation-encountered
                        //System.Windows.Forms.SendKeys.Send("{ENTER}");
                        // System.Windows.Forms.Control.Invoke(Delegate method);
                        Console.WriteLine("Select后");
                    }                    
                }

                // 此处需要刷新控件树,界面已刷新。
                //refresh TreeScope
                // 窗口刷新以后应该按新窗口对待。本来最开始的启动程序后获得的句柄也没什么用。
                Console.WriteLine("开始转换界面");
                Thread.Sleep(30*1000);
                AutomationElementCollection aeTextBlocks = aeForm.FindAll(TreeScope.Descendants,
                       new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Document));
                Console.WriteLine("尝试输出一个Label");                
                Console.WriteLine(aeTextBlocks[0].Current.LabeledBy); // 这一句必须要有,否则aeRadioButtons就是个空的,奇怪!

                for (int i = 0; i < aeTextBlocks.Count; i++)
                {
                    Console.WriteLine("文本框内容:");
                    Console.WriteLine(aeRadioButtons[i].Current.Name);
                }

                // TreeWalker walker = new TreeWalker(condition3);
                //AutomationElement elementNode = walker.GetFirstChild(aeWhats[0]);

                //AutomationElementCollection aeRadioButtons = aeForm.FindAll(TreeScope.Children,
                //    new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.RadioButton));
                //for(int i=0;i<100;i++)
                //    Console.WriteLine(aeRadioButtons[0].GetCurrentPropertyValue(AutomationElement.CultureProperty) as string);
                //-----------------------
				
				
                //Console.WriteLine("Settiing input to '30'");
                通过ValuePattern设置TextBox1的值
                //ValuePattern vpTextBox1 = (ValuePattern)aeTextBox1.GetCurrentPattern(ValuePattern.Pattern);
                //vpTextBox1.SetValue("30");
                //Console.WriteLine("Settiing input to '50'");
                通过ValuePattern设置TextBox2的值
                //ValuePattern vpTextBox2 = (ValuePattern)aeTextBox2.GetCurrentPattern(ValuePattern.Pattern);
                //vpTextBox2.SetValue("50");
                //Thread.Sleep(1500);
                //Console.WriteLine("Clickinig on button1 Button.");
                通过InvokePattern模拟点击按钮
                //InvokePattern ipClickButton1 = (InvokePattern)aeButton.GetCurrentPattern(InvokePattern.Pattern);
                //ipClickButton1.Invoke();
                //Thread.Sleep(1500);

                验证计算的结果与预期的结果是否相符合
                //Console.WriteLine("Checking textBox3 for '80'");
                //TextPattern tpTextBox3 = (TextPattern)aeTextBox3.GetCurrentPattern(TextPattern.Pattern);
                //string result = tpTextBox3.DocumentRange.GetText(-1);//获取textbox3中的值
                //                                                     //获取textbox3中的值
                //                                                     //string result = (string)aeTextBox2.GetCurrentPropertyValue(ValuePattern.ValueProperty);
                //if ("80" == result)
                //{
                //    Console.WriteLine("Found it.");
                //    Console.WriteLine("TTest scenario: *PASS*");
                //}
                //else
                //{
                //    Console.WriteLine("Did not find it.");
                //    Console.WriteLine("Test scenario: *FAIL*");
                //}

                //Console.WriteLine("Close application in 5 seconds.");
                //Thread.Sleep(5000);
                实现关闭被测试程序
                //WindowPattern wpCloseForm = (WindowPattern)aeForm.GetCurrentPattern(WindowPattern.Pattern);
                //wpCloseForm.Close();

                //Console.WriteLine("\nEnd test run\n");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Fatal error: " + ex.Message);
            }
        }
		// 遍历控件树,把类型名放入TreeNode(Windows.Forms中的类)里
        private void WalkEnabledElements(AutomationElement rootElement, TreeNode treeNode)
        {
            System.Windows.Automation.Condition condition1 = new PropertyCondition(AutomationElement.IsControlElementProperty, true);
            System.Windows.Automation.Condition condition2 = new PropertyCondition(AutomationElement.IsEnabledProperty, true);
            System.Windows.Automation.Condition condition3 = new PropertyCondition(AutomationElement.IsContentElementProperty, true);

            TreeWalker walker = new TreeWalker(new AndCondition(condition1, condition2));
            AutomationElement elementNode = walker.GetFirstChild(rootElement);
            while (elementNode != null)
            {
                TreeNode childTreeNode = treeNode.Nodes.Add(elementNode.Current.ControlType.LocalizedControlType);
                WalkEnabledElements(elementNode, childTreeNode);
                elementNode = walker.GetNextSibling(elementNode);
            }
        }
		// 操作ListItem所需的Pattern
        AutomationElement GetListItemParent(AutomationElement listItem)
        {
            if (listItem == null) throw new ArgumentException();
            SelectionItemPattern pattern = listItem.GetCurrentPattern(SelectionItemPattern.Pattern) as SelectionItemPattern;
            if (pattern == null)
            {
                return null;
            }
            else
            {
                SelectionItemPattern.SelectionItemPatternInformation properties = pattern.Current;
                return properties.SelectionContainer;
            }
        }
    }
}

 

  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值