Renamed docs folder
This commit is contained in:
parent
5ab3a06f26
commit
db1d44a7a5
7
doc_build/about.txt
Normal file
7
doc_build/about.txt
Normal file
@ -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.
|
316
doc_src/HowTo.txt
Normal file
316
doc_src/HowTo.txt
Normal file
@ -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]
|
||||||
|
|
88
doc_src/index.txt
Normal file
88
doc_src/index.txt
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user