自動操作軟件 獲取軟件按鈕內容 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;
            }
        }
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章