{"id":60,"date":"2011-12-03T15:10:00","date_gmt":"2011-12-03T15:10:00","guid":{"rendered":"http:\/\/tpodolak.com.hostingasp.pl\/blog\/2011\/12\/03\/ui-automation-czyli-testy-automatyczne-w-net\/"},"modified":"2016-01-31T00:29:14","modified_gmt":"2016-01-31T00:29:14","slug":"ui-automation-czyli-testy-automatyczne-w-net","status":"publish","type":"post","link":"https:\/\/tpodolak.com\/blog\/2011\/12\/03\/ui-automation-czyli-testy-automatyczne-w-net\/","title":{"rendered":"UI Automation &#8211; czyli testy automatyczne w .NET"},"content":{"rendered":"<p>Biblioteka Microsoft UI Automation ujrza\u0142a \u015bwiat\u0142o dzienne wraz z premier\u0105 .NET 3.0 &#8211; jednak\u017ce pozosta\u0142a ona w cieniu swoich wi\u0119kszych braci WPF oraz WPF, kt\u00f3re r\u00f3wnie\u017c zosta\u0142y wprowadzone do Frameworka 3.0. Microsoft UI automation zapewnia nam dost\u0119p do wszystkich element\u00f3w drzewa wizualnego aplikacji. Dzi\u0119ki czemu mamy mo\u017cliwo\u015b\u0107:<\/p>\n<ul>\n<li>Znajdowania wybranych przez nas kontrolek<\/li>\n<li>Interakcji z kontrolkami &#8211; wpisywanie tekst\u00f3w do TextBox-\u00f3w, klikanie w przyciski itp<\/li>\n<li>Wczytywania warto\u015bci ju\u017c wprowadzonych do kontrolek<\/li>\n<\/ul>\n<p>Ka\u017cda kontrolka znajduj\u0105ca si\u0119 na widoku jest traktowana przez UI Automation jako AutomationElement. Rootem naszego drzewa wizualnego pulpit, dost\u0119p do niego uzyskujemy poprzez statyczn\u0105 w\u0142a\u015bciwo\u015b\u0107<\/p>\n<pre lang=\"csharp\">\r\nAutomationElement.RootElement\r\n<\/pre>\n<p>Maj\u0105c dost\u0119p do roota mo\u017cemy nast\u0119pnie porusza\u0107 si\u0119 po drzewie tak aby znale\u017a\u0107 interesuj\u0105ce nas elementy. Za\u0142\u00f3\u017cmy, \u017ce napisali\u015bmy prost\u0105 aplikacj\u0119 WPF-ow\u0105, kt\u00f3ra wykonuje podstawowe obliczenia matematyczne (pomys\u0142 na prost\u0105 aplikacj\u0119 do testowania zaczerpni\u0119ty z <a href=\"http:\/\/blog.ganeshzone.net\/index.php\/2011\/08\/automated-ui-testing-of-wpf-applications-using-microsoft-ui-automation-library\/\">tej<\/a> strony &#8211; kod jednak pisa\u0142em po swojemu :D).<br \/>\nWizualnie aplikacja ta przedstawia si\u0119 w nast\u0119puj\u0105cy spos\u00f3b:<\/p>\n<pre lang=\"xml\">\r\n<Window x:Class=\"UIAutomation.Views.MainWindow\"\r\n        xmlns=\"http:\/\/schemas.microsoft.com\/winfx\/2006\/xaml\/presentation\"\r\n        xmlns:x=\"http:\/\/schemas.microsoft.com\/winfx\/2006\/xaml\"\r\n        Height=\"350\" Width=\"525\" Title=\"{Binding Title}\">\r\n    <Grid x:Name=\"SomeUniqueName1\">\r\n        <Grid.RowDefinitions>\r\n            <RowDefinition><\/RowDefinition>\r\n            <RowDefinition><\/RowDefinition>\r\n            <RowDefinition><\/RowDefinition>\r\n            <RowDefinition><\/RowDefinition>\r\n        <\/Grid.RowDefinitions>\r\n        <Grid.ColumnDefinitions>\r\n            <ColumnDefinition><\/ColumnDefinition>\r\n            <ColumnDefinition><\/ColumnDefinition>\r\n            <ColumnDefinition><\/ColumnDefinition>\r\n            <ColumnDefinition><\/ColumnDefinition>\r\n        <\/Grid.ColumnDefinitions>\r\n        <Label Content=\"Pierwsza liczba:\" HorizontalAlignment=\"Center\" VerticalAlignment=\"Center\" Grid.ColumnSpan=\"2\"><\/Label>\r\n        <Label Content=\"Druga liczba:\" HorizontalAlignment=\"Center\" VerticalAlignment=\"Center\" Grid.Row=\"1\" Grid.ColumnSpan=\"2\"><\/Label>\r\n        <Label Content=\"Wynik:\" HorizontalAlignment=\"Center\" VerticalAlignment=\"Center\" Grid.Row=\"2\"  Grid.ColumnSpan=\"2\"><\/Label>\r\n        <Button Name=\"btnAdd\"  Grid.Column=\"0\" Grid.Row=\"3\" Content=\"Dodaj\" Command=\"{Binding AddCommand}\" \/>\r\n        <Button Name=\"btnSubtract\" Grid.Column=\"1\" Grid.Row=\"3\"  Content=\"Odejmij\" Command=\"{Binding SubstractCommand}\" \/>\r\n        <Button Name=\"btnMultiply\" Grid.Column=\"2\" Grid.Row=\"3\"  Content=\"Pomn\u00c3\u00b3\u00c5\u00bc\" Command=\"{Binding MultiplyCommand}\" \/>\r\n        <Button Name=\"btnDivide\" Grid.Column=\"3\" Grid.Row=\"3\"  Content=\"Podziel\" Command=\"{Binding DivideCommand}\" \/>\r\n        <TextBox Name=\"txtFirstNumber\"  Grid.Column=\"2\"  VerticalAlignment=\"Center\" Grid.ColumnSpan=\"2\" Text=\"{Binding FirstNumber,Mode=TwoWay}\"><\/TextBox>\r\n        <TextBox Name=\"txtSecondNumber\" Grid.Column=\"2\" Grid.Row=\"1\" HorizontalAlignment=\"Stretch\" VerticalAlignment=\"Center\" Text=\"{Binding SecondNumber,Mode=TwoWay}\" Grid.ColumnSpan=\"2\"><\/TextBox>\r\n        <TextBox Name=\"txtResult\"  Grid.Column=\"2\" Grid.Row=\"2\" HorizontalAlignment=\"Stretch\" VerticalAlignment=\"Center\" Text=\"{Binding Result,Mode=TwoWay}\" Grid.ColumnSpan=\"2\"><\/TextBox>\r\n    <\/Grid>\r\n<\/Window>\r\n<\/pre>\n<p>W celu poruszania si\u0119 po drzewie wizualnym mo\u017cemy skorzysta\u0107 z dw\u00f3ch metod<\/p>\n<pre lang=\"csharp\">\r\npublic AutomationElement FindFirst(TreeScope scope, Condition condition);\r\npublic AutomationElementCollection FindAll(TreeScope scope, Condition condition);\r\n<\/pre>\n<p>pierwsza z nich zwraca nam konkretny element, kt\u00f3ry spe\u0142nia podane przez nas kryterium. Pierwszy parametr funkcji <span style=\"font-style: italic;\">TreeScope scope<\/span> okre\u015bla nam spos\u00f3b przeszukiwania drzewa wizualnego. Dost\u0119pne opcje to:<\/p>\n<ul>\n<li>Element &#8211; wyszukiwanie odbywa si\u0119 tylko na danym elemencie<\/li>\n<li>Children &#8211; wyszukiwanie odbywa si\u0119 na wszystkich dzieciach danego elementu &#8211; nie zag\u0142\u0119biamy si\u0119 rekurencyjnie,sprawdzamy tylko jeden poziom w d\u00f3\u0142 od danego elementu<\/li>\n<li>Descendants &#8211; wyszukiwanie odbywa si\u0119 na wszystkich potomkach &#8211; przeszukiwanie rekurencyjne w d\u00f3\u0142<\/li>\n<li>Subtree &#8211; wyszukiwanie odbywa si\u0119 na wszystkich potomkach, oraz elemencie od kt\u00f3rego zaczynamy<\/li>\n<li>Ancestors &#8211; wyszukiwanie odbywa si\u0119 na wszystkich rodzicach &#8211; rekurencyjnie w g\u00f3r\u0119 drzewa<\/li>\n<\/ul>\n<p>Drugim z parametr\u00f3w jakie musimy poda\u0107 jest to UIAutomation.Condition. Parametr ten okre\u015bla jaki warunek musi spe\u0142ni\u0107 dany element aby zosta\u0142 uwzgl\u0119dniony w wyniku wyszukiwania. Sama klasa Condition jest klas\u0105 abstrakcyjn\u0105, z kt\u00f3rej dziedzicz\u0105 wyspecjalizowane klasy. Do okre\u015blenia warunk\u00f3w najcz\u0119\u015bciej b\u0119dziemy korzysta\u0107 z potomka klasy Condition, mianowicie z<\/p>\n<pre lang=\"csharp\">\r\nPropertyCondition\r\n<\/pre>\n<p>okre\u015bla ona jaka w\u0142a\u015bciwo\u015b\u0107 kontrolki\/aplikacji b\u0119dzie uwzgl\u0119dniana przy wyszukiwaniu wyniku.<\/p>\n<p>Pierwszym krokiem przy testowaniu aplikacji b\u0119dzie oczywi\u015bcie znalezienie g\u0142\u00f3wnego okna programu. Maj\u0105c w solucji stworzony projekt z aplikacj\u0105 WPF-ow\u0105, dodajmy drugi projekt &#8211; tym razem b\u0119dzie to projekt typu <span style=\"font-style: italic;\">ConsoleApplication<\/span>.Nast\u0119pnie musimy doda\u0107 referencj\u0119 do odpowiednich ddl-ek. Potrzebujemy nast\u0119puj\u0105cych bibliotek:<\/p>\n<ul>\n<li>UIAutomation.dll<\/li>\n<li>UIAutomationClient.dll<\/li>\n<li>UIAutomationType.dll<\/li>\n<\/ul>\n<p>Nast\u0119pnie w naszym projekcie konsolowym w metodzie main odpalamy aplikacj\u0119, kt\u00f3r\u0105 chcemy testowa\u0107. W moim przypadku wygl\u0105da to w nast\u0119puj\u0105cy spos\u00f3b:<\/p>\n<pre lang=\"csharp\">\r\nConsole.WriteLine(\"Odpalam aplikacje\");\r\nProcess p = Process.Start(\"..\\..\\..\\UIAutomation\\bin\\Debug\\UIAutomation.exe\");\r\n<\/pre>\n<p>Nast\u0119pnie musimy dosta\u0107 si\u0119 do g\u0142\u00f3wnego okna testowanej aplikacji. Mo\u017cemy to zrobi\u0107 u\u017cywaj\u0105c funkcji <span style=\"font-style: italic;\">FindFirst<\/span>:<\/p>\n<pre lang=\"csharp\">\r\nAutomationElement mainForm =  AutomationElement.RootElement.FindFirst(TreeScope.Children,new PropertyCondition(AutomationElement.ProcessIdProperty, p.Id));\r\n<\/pre>\n<p>lub mo\u017cemy uzyska\u0107 dost\u0119p do okna przy pomocy funkcji<\/p>\n<pre lang=\"csharp\">\r\nAutomationElement mainForm = AutomationElement.FromHandle(p.MainWindowHandle);\r\n<\/pre>\n<p>W pierwszym przypadku wykorzystuj\u0105c funkcj\u0119 <span style=\"font-style: italic;\">FindFirst<\/span> przeszukujemy wszystkie dzieci (<span style=\"font-style: italic;\">TreeScope.Children<\/span>) roota (czyli pulpitu). Jako warunek wyszukiwania podali\u015bmy obiekt klasy <span style=\"font-style: italic;\">PropertyCondition<\/span>. Jako w\u0142a\u015bciwo\u015b\u0107 do por\u00f3wnywania podali\u015bmy id processu (AutomationElement.ProcessIdProperty). W drugim przypadku po prostu uzyskujemy dost\u0119p do okna dzi\u0119ki znajomo\u015bci jego uchwytu (w\u0142a\u015bciwo\u015b\u0107 <span style=\"font-style: italic;\">MainWindowHandle <\/span>z klasy <span style=\"font-style: italic;\">Process<\/span>). Wed\u0142ug mnie lepiej skorzysta\u0107 z funkcji pierwszej, zw\u0142aszcza gdy odpalamy aplikacj\u0119, kt\u00f3ra si\u0119 d\u0142ugo uruchamia. W pierwszym przypadku wystarczy zrobi\u0107 odpowiedni\u0105 funkcj\u0119 oczekuj\u0105c\u0105 na odpalanie (gdy\u017c funkcja zwr\u00f3ci nam null gdy okna jeszcze nie b\u0119dzie), natomiast druga opcja rzuci nam wyj\u0105tek. Oczekiwania na za\u0142adowanie okna mo\u017ce wygl\u0105da\u0107 w nast\u0119puj\u0105cy spos\u00f3b<\/p>\n<pre lang=\"csharp\">\r\nAutomationElement mainForm;\r\nwhile ((mainForm = AutomationElement.RootElement.FindFirst(TreeScope.Children,new PropertyCondition(AutomationElement.ProcessIdProperty,p.Id)))==null)\r\n{\r\n    Thread.Sleep(100);\r\n}\r\n<\/pre>\n<p>Maj\u0105c dost\u0119p do g\u0142\u00f3wnego okna mo\u017cemy przeprowadzi\u0107 testy &#8211; przetestujemy czy po wpisaniu danych do tekstboks\u00f3w i przeprowadzeniu odpowiednich akcji (dodawanie,usuwanie,odejmowanie,dzielenie) w polu wynik pojawi si\u0119 odpowiednia warto\u015b\u0107. Zacznijmy od zlokalizowania textbox\u00f3w. Pos\u0142u\u017cmy si\u0119 tutaj przedstawion\u0105 wcze\u015bniej funkcj\u0105 <span style=\"font-style: italic;\">FindFirst<\/span>.<\/p>\n<pre lang=\"csharp\">\r\nvar firstTextbox = mainForm.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, \"txtFirstNumber\"));\r\n<\/pre>\n<p>Wyszukiwanie odbywa si\u0119 po w\u0142a\u015bciwo\u015bci AutomationElement.AutomationIdProperty &#8211; w\u0142a\u015bciwo\u015b\u0107 ta jest zawsze taka sama jak nazwa kontrolki &#8211; chyba, \u017ce kto\u015b j\u0105 zmieni przy pomocy <span style=\"font-style: italic;\">AttachedProperty <\/span><span style=\"font-style: italic;\">AutomationProperties.AutomationId<\/span>. Odpalaj\u0105c powy\u017cszy kod okazuje si\u0119, \u017ce nasza zmienna <span style=\"font-style: italic;\">firstTextbox <\/span> jest nullem &#8211; czyli framework nie by\u0142 w stanie znale\u017a\u0107 naszego textboxa. Dlaczego tak si\u0119 sta\u0142o?? Odpowiedzi mo\u017ce nam dostarczy\u0107 aplikacja Snoop, dzi\u0119ki kt\u00f3rej mo\u017cemy podejrze\u0107 drzewo wizualne naszego programu.<br \/>\n<a href=\"http:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2011\/12\/ui-automation-czyli-testy-automatyczne-w-net\/snoopResult.jpg\" rel=\"attachment wp-att-492\"><img decoding=\"async\" src=\"http:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2011\/12\/ui-automation-czyli-testy-automatyczne-w-net\/snoopResult.jpg\" alt=\"snoopResult\" width=\"650\" class=\"aligncenter size-full wp-image-492\" srcset=\"https:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2011\/12\/ui-automation-czyli-testy-automatyczne-w-net\/snoopResult.jpg 922w, https:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2011\/12\/ui-automation-czyli-testy-automatyczne-w-net\/snoopResult-150x135.jpg 150w, https:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2011\/12\/ui-automation-czyli-testy-automatyczne-w-net\/snoopResult-300x269.jpg 300w\" sizes=\"(max-width: 922px) 100vw, 922px\" \/><\/a><br \/>\nNa powy\u017cszym screenie widzimy, \u017ce szukany textbox nie jest bezpo\u015brednim dzieckiem <span style=\"font-style: italic;\">MainWindow<\/span>, jest natomiast dzieckiem grida (tego mo\u017cna by\u0142o si\u0119 spodziewa\u0107 zw\u0142aszcza, \u017ce umie\u015bcili\u015bmy textbox w gridzie). Zauwa\u017cmy jednak, \u017ce MainWindow nie jest bezpo\u015brednim rodzicem grida &#8211; znajduje si\u0119 on dopiero na poziomie 4 w drzewie. Z racji du\u017cego skomplikowania drzewa wizualnego aplikacji wed\u0142ug mnie bezpiecznie jest u\u017cywa\u0107 funkcji Find z parametrem TreeScope.Descendants. Unikniemy dzi\u0119ki temu przykrych niespodzianek z nieodnalezionymi kontrolkami. Nasza funkcja zatem powinna wygl\u0105da\u0107 w nast\u0119puj\u0105cy spos\u00f3b.<\/p>\n<pre lang=\"csharp\">\r\nvar firstTextbox = mainForm.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.AutomationIdProperty, \"txtFirstNumber\"));\r\n<\/pre>\n<p>W analogiczny spos\u00f3b znajdujemy pozosta\u0142e przyciski na naszej formie, a nast\u0119pnie postarajmy si\u0119 wprowadzi\u0107 do nich jaki\u015b text. W celu interakcji z kontrolkami wykorzystamy tzw. <span style=\"font-style: italic;\">Patterns<\/span><br \/>\n<span style=\"font-style: italic;\">Patterns <\/span> definiuj\u0105 okre\u015blone funkcjonalno\u015bci, kt\u00f3re wspiera nasza kontrolka. Do najcz\u0119\u015bciej u\u017cywanych <span style=\"font-style: italic;\">patterns <\/span>nale\u017c\u0105:<\/p>\n<ul>\n<li><span style=\"font-style: italic;\">SelectionPattern<\/span> &#8211; u\u017cywany do manipulacjami kontrolkami wspieraj\u0105cymi zaznaczanie np.ListBox-ami<\/li>\n<li><span style=\"font-style: italic;\">TextPattern<\/span> &#8211; u\u017cywany do manipulacjami kontrolkami wspieraj\u0105cymi edycj\u0119<\/li>\n<li><span style=\"font-style: italic;\">ValuePattern <\/span>&#8211; u\u017cywany do pobierania i ustawiania warto\u015bci kontrolek nie wspieraj\u0105cych wielokrotnych warto\u015bci<\/li>\n<li><span style=\"font-style: italic;\">InvokePattern <\/span>&#8211; u\u017cywane do kontrolek wspieraj\u0105cych wywo\u0142ania &#8211; np. przyciski (wywo\u0142anie przyci\u015bni\u0119cia)<\/li>\n<li><span style=\"font-style: italic;\">ScrollPattern <\/span>&#8211; u\u017cywane do kontrolek posiadaj\u0105cych ScrolBar-y<\/li>\n<li><span style=\"font-style: italic;\">RangeValuePattern <\/span>&#8211; u\u017cywany do kontrolek mog\u0105cych posiada\u0107 jaki\u015b zakres warto\u015bci np. ComboBox<\/li>\n<\/ul>\n<p>Zatem w celu ustawienia warto\u015bci w textbox-ie pos\u0142u\u017cymy si\u0119 nast\u0119puj\u0105cym kodem<\/p>\n<pre lang=\"csharp\">\r\nvar pattern = txtFirstNumber.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;\r\npattern.SetValue(\"50\");\r\n<\/pre>\n<p>W analogiczny spos\u00f3b ustawiamy zawarto\u015b\u0107 drugiego textbox-a<\/p>\n<pre lang=\"csharp\">\r\nvar pattern = txtSecondNumber.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;\r\npattern.SetValue(\"50\");\r\n<\/pre>\n<p>Maj\u0105c wprowadzone dane do textbox-\u00f3w wypada\u0142oby teraz przeprowadzi\u0107 obliczenia. Zatem musimy w jaki\u015b spos\u00f3b aby &#8220;nacisn\u0105\u0107&#8221; jeden z naszych czterech przycisk\u00f3w. Znajd\u017amy zatem nasze przyciski:<\/p>\n<pre lang=\"csharp\">\r\nvar btnAdd = mainForm.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.AutomationIdProperty, \"btnAdd\"));\r\nvar btnSubtract = mainForm.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.AutomationIdProperty, \"btnSubtract\"));\r\nvar btnMultiply = mainForm.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.AutomationIdProperty, \"btnMultiply\"));\r\nvar btnDivide = mainForm.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.AutomationIdProperty, \"btnDivide\"));\r\n<\/pre>\n<p>Nast\u0119pnie wykorzystuj\u0105c wspomniany wcze\u015bniej <span style=\"font-style: italic;\">InvokePattern<\/span> naci\u015bnijmy przycisk &#8220;Dodaj&#8221;.<\/p>\n<pre lang=\"csharp\">\r\nInvokePattern ipClickButton1 = (InvokePattern)btnAdd.GetCurrentPattern(InvokePattern.Pattern);\r\nipClickButton1.Invoke();\r\n<\/pre>\n<p>Ostatnim krokiem b\u0119dzie zweryfikowanie czy warto\u015b\u0107 w textbox-ie <span style=\"font-style: italic;\">txtResult<\/span> jest zgodna z oczekiwaniami. W celu wyci\u0105gni\u0119cia warto\u015bci z txtResult po raz kolejny pos\u0142u\u017c\u0119 si\u0119 <span style=\"font-style: italic;\">ValuePattern<\/span><\/p>\n<pre lang=\"csharp\">\r\nvar value = (txtResult.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern).Current.Value\r\n<\/pre>\n<p>Wynik otrzymany w textbox-ie por\u00f3wnujemy z oczekiwanym rezultatem<\/p>\n<pre lang=\"csharp\">\r\nConsole.Write(\"Dodawanie test OK ? , {0}\",value == \"100\");\r\n<\/pre>\n<p>Na koniec najlepiej wrzuci\u0107 nasz test w jak\u0105\u015b p\u0119tle, losowo generowa\u0107 warto\u015bci oraz por\u00f3wnywa\u0107 je z warto\u015bciami oczekiwanymi. Dobrym pomys\u0142em jest r\u00f3wnie\u017c stworzenie sobie jakiej\u015b klasy pomocniczej, w kt\u00f3rej zosta\u0142yby umieszczone funkcje do wype\u0142niania textbox-ow, klikania w buttony itp &#8211; gdy\u017c jak wida\u0107 du\u017co kodu jest powtarzalna. Ostatecznie aplikacja testowa mo\u017ce wygl\u0105da\u0107 w nast\u0119puj\u0105cy spos\u00f3b:<\/p>\n<pre lang=\"csharp\">\r\nclass Program\r\n{\r\n    static void Main(string[] args)\r\n    {\r\n        Random rand = new Random();\r\n        try\r\n        {\r\n            Console.WriteLine(\"Odpalam aplikacje\");\r\n            Process p = Process.Start(\"..\\..\\..\\UIAutomation\\bin\\Debug\\UIAutomation.exe\");\r\n            AutomationElement mainForm;\r\n            while ((mainForm = AutomationElement.RootElement.FindFirst(TreeScope.Children,new PropertyCondition(AutomationElement.ProcessIdProperty,p.Id)))==null)\r\n            {\r\n                Thread.Sleep(100);\r\n            }\r\n            \r\n            var txtFirstNumber = mainForm.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.AutomationIdProperty, \"txtFirstNumber\"));\r\n            var txtSecondNumber = mainForm.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.AutomationIdProperty, \"txtSecondNumber\"));\r\n            var txtResult = mainForm.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.AutomationIdProperty, \"txtResult\"));\r\n            \r\n            var btnAdd = mainForm.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.AutomationIdProperty, \"btnAdd\"));\r\n            var btnSubtract = mainForm.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.AutomationIdProperty, \"btnSubtract\"));\r\n            var btnMultiply = mainForm.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.AutomationIdProperty, \"btnMultiply\"));\r\n            var btnDivide = mainForm.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.AutomationIdProperty, \"btnDivide\"));\r\n            \r\n            for (int i = 0; i < 200; i++)\r\n            {\r\n                double firstNumber = rand.Next(1, 1000);\r\n                double seconNumber = rand.Next(1, 1000);\r\n                Console.WriteLine(\"nn Test dla warto\u015bci: {0} , {1}\", firstNumber, seconNumber);\r\n                txtFirstNumber.SetTextBoxValue(firstNumber.ToString());\r\n                txtSecondNumber.SetTextBoxValue(seconNumber.ToString());\r\n                btnAdd.Click();\r\n                Console.WriteLine(\"Dodawanie test OK ? , {0}\", txtResult.GetTextBoxValue().ConvertToDouble() == firstNumber + seconNumber);\r\n                btnDivide.Click();\r\n                Console.WriteLine(\"Dzielenie test OK ? , {0}\", Math.Round(txtResult.GetTextBoxValue().ConvertToDouble(), 2) == Math.Round(firstNumber \/ seconNumber, 2));\r\n                btnSubtract.Click();\r\n                Console.WriteLine(\"Odejmowanie test OK ? , {0}\", txtResult.GetTextBoxValue().ConvertToDouble() == firstNumber - seconNumber);\r\n                btnMultiply.Click();\r\n                Console.WriteLine(\"Mno\u017cenie test OK ? , {0}\", txtResult.GetTextBoxValue().ConvertToDouble() == firstNumber * seconNumber);\r\n            }\r\n            \r\n            Console.WriteLine(\"nEnd test runn\");\r\n            Console.ReadKey();\r\n            p.Close();\r\n            p.Kill();\r\n        }\r\n        catch (Exception ex)\r\n        {\r\n            Console.WriteLine(\"Fatal error: \" + ex.Message);\r\n            Console.WriteLine(\"nEnd test runn\");\r\n            Console.ReadKey();\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>oraz klasa pomocnicza<\/p>\n<pre lang=\"csharp\">\r\npublic static class UiAutomationHelper\r\n{\r\n    public static string GetTextBoxValue(this AutomationElement automationElement)\r\n    {\r\n        return (automationElement.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern).Current.Value;\r\n    }\r\n    \r\n    public static void SetTextBoxValue(this AutomationElement automationElement,string value)\r\n    {\r\n        (automationElement.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern).SetValue(value);\r\n    }\r\n    \r\n    public static void Click(this AutomationElement automationElement)\r\n    {\r\n        InvokePattern ipClickButton1 = (InvokePattern)automationElement.GetCurrentPattern(InvokePattern.Pattern);\r\n        ipClickButton1.Invoke();\r\n        Thread.Sleep(100); \/\/ dajmy czas na pokazanie okna, wykonanie funkcji itp\r\n    }\r\n    \r\n    public static double ConvertToDouble(this string str)\r\n    {\r\n        double number;\r\n        if (double.TryParse(str.Replace('.',','), out number  ))\r\n            return number;\r\n        throw new Exception(\"Niepoprawna warto\u015b\u0107\");\r\n    }\r\n}\r\n<\/pre>\n<p><a href=\"http:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2011\/12\/ui-automation-czyli-testy-automatyczne-w-net\/result.jpg\" rel=\"attachment wp-att-491\"><img decoding=\"async\" src=\"http:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2011\/12\/ui-automation-czyli-testy-automatyczne-w-net\/result.jpg\" alt=\"result\" width=\"650\" class=\"aligncenter size-full wp-image-491\" srcset=\"https:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2011\/12\/ui-automation-czyli-testy-automatyczne-w-net\/result.jpg 676w, https:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2011\/12\/ui-automation-czyli-testy-automatyczne-w-net\/result-150x76.jpg 150w, https:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2011\/12\/ui-automation-czyli-testy-automatyczne-w-net\/result-300x151.jpg 300w\" sizes=\"(max-width: 676px) 100vw, 676px\" \/><\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Biblioteka Microsoft UI Automation ujrza\u0142a \u015bwiat\u0142o dzienne wraz z premier\u0105 .NET 3.0 &#8211; jednak\u017ce pozosta\u0142a ona w cieniu swoich wi\u0119kszych braci WPF oraz WPF, kt\u00f3re r\u00f3wnie\u017c zosta\u0142y wprowadzone do Frameworka 3.0. Microsoft UI automation zapewnia nam dost\u0119p do wszystkich element\u00f3w drzewa wizualnego aplikacji. Dzi\u0119ki czemu mamy mo\u017cliwo\u015b\u0107: Znajdowania wybranych przez nas kontrolek Interakcji z kontrolkami [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[131],"tags":[258],"class_list":["post-60","post","type-post","status-publish","format-standard","hentry","category-uiautomation","tag-uiautomation"],"_links":{"self":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/60","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/comments?post=60"}],"version-history":[{"count":3,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/60\/revisions"}],"predecessor-version":[{"id":494,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/60\/revisions\/494"}],"wp:attachment":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/media?parent=60"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/categories?post=60"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/tags?post=60"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}