From db1d44a7a5291deede7b1872751ecaece159b14e Mon Sep 17 00:00:00 2001 From: markm Date: Mon, 6 Feb 2006 23:42:02 +0000 Subject: [PATCH] Renamed docs folder --- doc_build/about.txt | 7 + doc_src/HowTo.txt | 316 ++++++++++++++++++++++++++++++++++++++++++++ doc_src/index.txt | 88 ++++++++++++ 3 files changed, 411 insertions(+) create mode 100644 doc_build/about.txt create mode 100644 doc_src/HowTo.txt create mode 100644 doc_src/index.txt diff --git a/doc_build/about.txt b/doc_build/about.txt new file mode 100644 index 0000000..5febabf --- /dev/null +++ b/doc_build/about.txt @@ -0,0 +1,7 @@ +About pywinauto +=============== + +Pywinauto is a tool to automate Windows dialogs and controls. + +There are also some tests supplied - but these are more +applicable to Localisation UI testing. \ No newline at end of file diff --git a/doc_src/HowTo.txt b/doc_src/HowTo.txt new file mode 100644 index 0000000..ca5b32b --- /dev/null +++ b/doc_src/HowTo.txt @@ -0,0 +1,316 @@ +======== +How To's +======== + +How to sepcify an usable Application instance +--------------------------------------------- +An ``Application()`` instance is the point of contact for all work +with the app you are automating. So the Application instance needs +to be connected to a process. There are two ways of doing this:: + + start_(self, cmd_line, timeout = app_start_timeout): + +or :: + + connect_(self, **kwargs): + + +``start_()`` is used when the application is not running and you +need to start it. Use it in the following way:: + + app = Application() + app.start_(r"c:\path\to\your\application -a -n -y --arguments") + +The timeout parameter is optional, it should only be necessary to use +it if the application takes a long time to start up. + +``connect_()`` is used when the application to be automated is already +running. To specify a an already runing application you need to specify +one of the following: + +:process: the process id of the application, e.g. + +:: + + app = Application() + app.connect_(process = 2341) + + +:handle: The windows handle of a window of the application, e.g. + +:: + + app = Application() + app.connect_(handle = 0x010f0c) + + +:path: The path of the executable of the process (``GetModuleFileNameEx`` + is used to find the path of each process and compared against + the value passed in) e.g. + +:: + + app = Application() + app.connect_(path = r"c:\windows\system32\notepad.exe") + +or any combination of the parameters that specify a window, these get +passed to the ``findwindows.find_windows()`` function. e.g. :: + + app = Application() + app.connect_(title_re = ".*Notepad", class_name = "Notepad") + + + +How to sepcify a dialog of the application +------------------------------------------ +Once the application instance knows what application it is connected to +a dialog to work on needs to be specified. + +There are many different ways of doing this. The most common will be +using item or attribute access to select a dialog based on it's title. e.g :: + + dlg = app.Notepad + +or equivalently :: + + dlg = app['Notepad'] + + +The next easiest method is to ask for the ``top_window_()`` e.g. :: + + dlg = app.top_window_() + +This will return the window that has the highest Z-Order of the top-level +windows of the application. + +**Note**: This is currently fairly untested so I am not sure it will +return the correct window. It will definitely be a top level window of +the application - it just might not be the one highest in the Z-Order. + +If this is not enough control they you can use the same parameters as +can be passed to ``findwindows.find_windows()`` e.g. :: + + dlg = app.window_(title_re = "Page Setup", class_name = "#32770") + +Finally to have the most control you can use :: + + dialogs = app.windows_() + +this will return a list of all the visible, enabled, top level windows +of the application. You can then use some of the methods in ``handleprops`` +module select the dialog you want. Once you have the handle you need +then use :: + + Application.window_(handle = win) + + +How to specify a control on a dialog +------------------------------------ + +There are a number of ways to specify a control, the simplest are:: + + app.dlg.control + app['dlg']['control'] + + +The 2nd is better for non English OS's where you need to pass unicode +strings e.g. app[u'your dlg title'][u'your ctrl title'] + +The code builds up multiple identifiers for each control from the following: + + + title + + friendly class + + title + friendly class + +If the control's text is empty (after removing non char characters) text is +not used. Instead we look for the closest control above and to the right fo +the contol. And append the friendly class. So the list becomes + + + friendly class + + closest text + friendly class + +Once a set of identifiers has been created for all controls in the dialog +we disambiguate them. + + + + +use the `WindowSpecification.print_control_identifiers()` method + +e.g. :: + + app.YourDialog.print_control_identifiers() + +Sample output:: + + Button - Paper (L1075, T394, R1411, B485) + 'PaperGroupBox' 'Paper' 'GroupBox' + Static - Si&ze: (L1087, T420, R1141, B433) + 'SizeStatic' 'Static' 'Size' + ComboBox - (L1159, T418, R1399, B439) + 'ComboBox' 'SizeComboBox' + Static - &Source: (L1087, T454, R1141, B467) + 'Source' 'Static' 'SourceStatic' + ComboBox - (L1159, T449, R1399, B470) + 'ComboBox' 'SourceComboBox' + Button - Orientation (L1075, T493, R1171, B584) + 'GroupBox' 'Orientation' 'OrientationGroupBox' + Button - P&ortrait (L1087, T514, R1165, B534) + 'Portrait' 'RadioButton' 'PortraitRadioButton' + Button - L&andscape (L1087, T548, R1165, B568) + 'RadioButton' 'LandscapeRadioButton' 'Landscape' + Button - Margins (inches) (L1183, T493, R1411, B584) + 'Marginsinches' 'MarginsinchesGroupBox' 'GroupBox' + Static - &Left: (L1195, T519, R1243, B532) + 'LeftStatic' 'Static' 'Left' + Edit - (L1243, T514, R1285, B534) + 'Edit' 'LeftEdit' + Static - &Right: (L1309, T519, R1357, B532) + 'Right' 'Static' 'RightStatic' + Edit - (L1357, T514, R1399, B534) + 'Edit' 'RightEdit' + Static - &Top: (L1195, T550, R1243, B563) + 'Top' 'Static' 'TopStatic' + Edit - (L1243, T548, R1285, B568) + 'Edit' 'TopEdit' + Static - &Bottom: (L1309, T550, R1357, B563) + 'BottomStatic' 'Static' 'Bottom' + Edit - (L1357, T548, R1399, B568) + 'Edit' 'BottomEdit' + Static - &Header: (L1075, T600, R1119, B613) + 'Header' 'Static' 'HeaderStatic' + Edit - (L1147, T599, R1408, B619) + 'Edit' 'TopEdit' + Static - &Footer: (L1075, T631, R1119, B644) + 'FooterStatic' 'Static' 'Footer' + Edit - (L1147, T630, R1408, B650) + 'Edit' 'FooterEdit' + Button - OK (L1348, T664, R1423, B687) + 'Button' 'OK' 'OKButton' + Button - Cancel (L1429, T664, R1504, B687) + 'Cancel' 'Button' 'CancelButton' + Button - &Printer... (L1510, T664, R1585, B687) + 'Button' 'Printer' 'PrinterButton' + Button - Preview (L1423, T394, R1585, B651) + 'Preview' 'GroupBox' 'PreviewGroupBox' + Static - (L1458, T456, R1549, B586) + 'PreviewStatic' 'Static' + Static - (L1549, T464, R1557, B594) + 'PreviewStatic' 'Static' + Static - (L1466, T586, R1557, B594) + 'Static' 'BottomStatic' + +This exmple has been taken from test_application.py + +**Note** The identifiers printed by this method have been run through +the process that makes the identifier unique. So if you have 2 edit boxes, +they will both have "Edit" listed in their identifiers. In reality though +the first one can be refered to as "Edit", "Edit0", "Edit1" and the 2nd +should be refered to as "Edit2" + +**Note** You do not have to be exact!. Say we take an instance from the +example above:: + + Button - Margins (inches) (L1183, T493, R1411, B584) + 'Marginsinches' 'MarginsinchesGroupBox' 'GroupBox' + +Let's say that you don't like any of these + + - ``GroupBox`` - too generic, it could be any group box + - ``Marginsinches`` and ``MarginsinchesGroupBox`` - these just don' + look right, it would be nicer to leave out the 'inches' part + +Well you CAN! The code does a best match on the identifer you use against +all the available identifiers in the dialog. + +For example if you break into the debugger you can see how different +identifiers can be used:: + + (Pdb) print app.PageSetup.Margins.Text() + Margins (inches) + (Pdb) print app.PageSetup.MarginsGroupBox.Text() + Margins (inches) + + +And this will also cater for typos. Though you still have to be careful +as if there are 2 similar identifiers in the dialog the typo you have +used might be more similar to another control then the one you were +thinking of. + +How to use pywinauto with application languages other than English +------------------------------------------------------------------ +Because Python does not support unicode identifiers in code +you cannot use attribute access to reference a control so +you would either have to use item access or make an explicit +calls to ``window_()``. + +So instead of writing:: + + app.dialog_ident.control_ident.Click() + +You would have to write:: + + app['dialog_ident']['control_ident'].Click() + +Or use ``window_()`` explictly:: + + app.window_(title_re = "NonAsciiCharacters").window_(title = "MoreNonAsciiCharacters").Click() + +To see an example of this see ``examples\MiscExamples.py.GetInfo()`` + + + +How to deal with controls that do not respond as expected (e.g. OwnerDraw Controls) +------------------------------------------------------------------------------------ + +Some controls (especially Ownerdrawn controls) do not respond to events as +expected. For example if you look at any HLP file and go to the Index Tab (click +'Search' button) you will see a listbox. Running Spy or Winspector on this +will show you that it is indeed a list box - but it is ownerdrawn. This means +that the developer has told Windows that they will override how items are displayed +and do it themselves. And in this case they have made it so that strings cannot be +retrieved :-(. + +So what problems does this cause? :: + + app.HelpTopics.ListBox.Texts() # 1 + app.HelpTopics.ListBox.Select("ItemInList") # 2 + + +1. Will return a list of empty strings, all this means is that pywinauto has not + been able to get the strings in the listbox + +2. This will fail with an IndexError because the Select(string) method of a ListBox + looks for the item in the Texts to know the index of the item that it should select. + +The following workaround will work on this control :: + + app.HelpTopics.ListBox.Select(1) + +This will select the 2nd item in the listbox, because it is not a string lookup +it works correctly. + +Unfortunately not even this will always work. The developer can make it so that the +control does not respond to standard events like Select. In this case the only way +you can select items in the listbox is by using the keyboard simulation of TypeKeys(). + +This allows you to send any keystrokes to a control. So to select the 3rd item you +would use:: + + app.Helptopics.ListBox1.TypeKeys("{HOME}{DOWN 2}{ENTER}") + + +- ``{HOME}`` will make sure that the first item is highlighted. +- ``{DOWN 2}`` will then move the highlight down 2 items +- ``{ENTER}`` will select the highlighted item + +If your application made extensive use of a similar control type then you could +make using it easier by deriving a new class from ListBox, that could use extra +knowledge about your particular application. For example in the WinHelp example +evertime an item is highlighted in the list view, it's text is inserted into the +Edit control above the list, and you CAN get the text of the item from there e.g. :: + + # print the text of the item currently selected in the list box + # (as long as you are not typing into the Edit control!) + print app.HelpTopics.Edit.Texts()[1] + \ No newline at end of file diff --git a/doc_src/index.txt b/doc_src/index.txt new file mode 100644 index 0000000..41a6b38 --- /dev/null +++ b/doc_src/index.txt @@ -0,0 +1,88 @@ +pywinauto +(c) Mark Mc Mahon 2006 +Released under the LGPL licence + + +What is it +---------- +pywinauto is a set of python modules to automate the Microsoft Windows GUI. +At it's simplest it allows you to send mouse and keyboard actions to windows +dialogs and controls. + + +Installation +------------ + - Unzip the pywinauto zip file to a folder. + - Install the following Python packages + - ctypes http://starship.python.net/crew/theller/ctypes/ + - Sendkeys http://www.rutherfurd.net/python/sendkeys/index.html + - *Optional* PIL http://www.pythonware.com/products/pil/index.htm + - *Optional* elementtree http://effbot.org/downloads/ + +To check you have it installed correctly +Run Python :: + + >>> import application + >>> app = application.Application()._start("notepad") + >>> app.notepad.TypeKeys("%FX") + + +Where to start +-------------- +Look at the examples provided in test_application.py +There are examples in there to work with Notepad and MSPaint. + + +How does it work +---------------- +A lot is done through attribute access (__getattr__) for each class. For example +when you get the attribute of an Application or Dialog object it looks for a +dialog or control (respectively). + +:: + + myapp.Notepad # looks for a Window/Dialog of your app that has a title 'similar' + # to "Notepad" + + myapp.PageSetup.OK # looks first for a dialog with a title like "PageSetup" + # then it looks for a control on that dialog with a title + # like "OK" + +This attribute resolution is delayed (currently a hard coded amount of time) until +it succeeds. So for example if you Select a menu option and then look for the +resulting dialog e.g. :: + + app.Notepad.MenuSelect("File->SaveAs") + app.SaveAs.ComboBox5.Select("UTF-8") + app.SaveAs.edit1.SetText("Example-utf8.txt") + app.SaveAs.Save.Click() + +At the 2nd line the SaveAs dialog might not be open by the time this line is +executed. So what happens is that we wait until we have a control to resolve +before resolving the dialog. At that point if we can't find a SaveAs dialog with +a ComboBox5 control then we wait a very short period of time and try again, +this is repeated up to a maximum time (currently 1 second!) + +This avoid the user having to use time.sleep or a "WaitForDialog" function. + + +Some similar tools for comparison +--------------------------------- + * Python tools + - Watsup + - winGuiAuto + + * Other scripting language tools + - Perl Win32::GuiTest + - Ruby GuiTest + - others? + + * Other free tools + - AutoIt + - See collection at: + + * Commercial tools + - WinRunner + - SilkTest + - Visual Test + - Many Others \ No newline at end of file