PyWinAuto Basics
| Introduction | |
| Install | |
| Example | |
| print_control_identifiers() | |
| Nested objects | |
| Search for nested object | |
| Search with ctrl_ids and enter text | |
| Search with app.windows() | |
| requirements.txt | |
| Related Articles |
Introduction
pywinauto is a set of modules
python
for automating the graphical interface
Microsoft Windows
.
In its simplest form, it allows you to send mouse and keyboard actions to
Windows
dialog boxes and controls.
Official documentation
Install
python -m pip install pywinauto
Collecting pywinauto Downloading pywinauto-0.6.8-py2.py3-none-any.whl (362 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 362.9/362.9 kB ? eta 0:00:00 Collecting six Using cached six-1.16.0-py2.py3-none-any.whl (11 kB) Collecting comtypes Downloading comtypes-1.2.0-py2.py3-none-any.whl (184 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 184.3/184.3 kB ? eta 0:00:00 Collecting pywin32 Downloading pywin32-306-cp311-cp311-win_amd64.whl (9.2 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 9.2/9.2 MB 21.8 MB/s eta 0:00:00 Installing collected packages: pywin32, comtypes, six, pywinauto Successfully installed comtypes-1.2.0 pywin32-306 pywinauto-0.6.8 six-1.16.0
Example
In some cases, you need to go to the directory with the .exe file before launching the application
import os os.chdir("PATH")
from pywinauto.application import Application app = Application(backend='uia').start('notepad.exe') # app = Application(backend='win32').start('')
python pywinauto_tutorial.py
from pywinauto.application import Application app = Application(backend='uia').start('notepad.exe') app = Application(backend='uia').connect(title='Untitled - Notepad', timeout=100)
print_control_identifiers()
To control the application, you need to get a list of controller objects. To do this you can use print_control_identifiers()
print_control_identifiers() displays a list of all controllers available from the current state of the application and not all controllers at all.
from pywinauto.application import Application app = Application(backend='uia').start('notepad.exe') app = Application(backend='uia').connect(title='Untitled - Notepad', timeout=100) app.UntitledNotepad.print_control_identifiers()
Control Identifiers: Dialog - 'Untitled - Notepad' (L-1127, T248, R-579, B1034) ['Untitled - Notepad', 'Untitled - NotepadDialog', 'Dialog'] child_window(title="Untitled - Notepad", control_type="Window") | | Edit - 'Text Editor' (L-1118, T311, R-588, B996) | ['Edit', 'Edit0', 'Edit1'] | child_window(title="Text Editor", auto_id="15", control_type="Edit") | | | | ScrollBar - 'Vertical' (L-609, T311, R-588, B975) | | ['VerticalScrollBar', 'Vertical', 'ScrollBar', 'ScrollBar0', 'ScrollBar1'] | | child_window(title="Vertical", auto_id="NonClientVerticalScrollBar", control_type="ScrollBar") | | | | | | Button - 'Line up' (L-609, T311, R-588, B332) | | | ['Button', 'Line up', 'Line upButton', 'Button0', 'Button1'] | | | child_window(title="Line up", auto_id="UpButton", control_type="Button") | | | | | | Button - 'Line down' (L-609, T954, R-588, B975) | | | ['Button2', 'Line downButton', 'Line down'] | | | child_window(title="Line down", auto_id="DownButton", control_type="Button") | | | | ScrollBar - 'Horizontal' (L-1118, T975, R-609, B996) | | ['HorizontalScrollBar', 'ScrollBar2', 'Horizontal'] | | child_window(title="Horizontal", auto_id="NonClientHorizontalScrollBar", control_type="ScrollBar") | | | | | | Button - 'Column left' (L-1118, T975, R-1097, B996) | | | ['Button3', 'Column left', 'Column leftButton'] | | | child_window(title="Column left", auto_id="UpButton", control_type="Button") | | | | | | Button - 'Column right' (L-630, T975, R-609, B996) | | | ['Button4', 'Column rightButton', 'Column right'] | | | child_window(title="Column right", auto_id="DownButton", control_type="Button") | | | | Thumb - '' (L-609, T975, R-588, B996) | | ['Thumb'] | | StatusBar - 'Status Bar' (L-1118, T996, R-588, B1025) | ['StatusBar', 'Status Bar', 'Status BarStatusBar'] | child_window(title="Status Bar", auto_id="1025", control_type="StatusBar") | | | | Static - '' (L0, T0, R0, B0) | | ['Static', 'Static0', 'Static1'] | | …
You can learn more about notepad control identifiers in the article «Autotesting a notebook with pywinauto»
from pywinauto.application import Application app = Application(backend='uia').start('notepad.exe') app = Application(backend='uia').connect( title='Untitled - Notepad', timeout=100 ) # app.UntitledNotepad.print_control_identifiers() text_editor = app.UntitledNotepad.child_window( title="Text Editor", auto_id="15", control_type="Edit" ).wrapper_object() text_editor.type_keys( "Subscribe to t.me/aofeed", with_spaces=True )
There are often a lot of controllers and it is inconvenient to look at their output to the terminal.
This problem is solved by writing to a file that needs to be specified as an argument
app.MyApp.print_control_identifiers(filename="controls.txt")
File controls.txt will be created in the working directory, that is, most likely next to with the .exe file being run and not next to the script.
Objects in nested menus
When was the first time we used
print_control_identifiers()
a list of all controllers available from the start window was obtained.
To get the lists of controllers that are available from the submenu, you must first run
click_input()
to the desired menu and then immediately execute
print_control_identifiers()
Using this algorithm, you can create a library from all available controllers.
from pywinauto.application import Application app = Application(backend='uia').start('notepad.exe') app = Application(backend='uia').connect( title='Untitled - Notepad', timeout=100 ) # app.UntitledNotepad.print_control_identifiers() text_editor = app.UntitledNotepad.child_window( title="Text Editor", auto_id="15", control_type="Edit" ).wrapper_object() text_editor.type_keys( "Subscribe to t.me/aofeed", with_spaces=True ) file_menu = app.UntitledNotepad.child_window(title="File", control_type="MenuItem").wrapper_object() file_menu.click_input() app.UntitledNotepad.print_control_identifiers()
Control Identifiers: Dialog - '*Untitled - Notepad' (L-1512, T253, R641, B1009) ['*Untitled - Notepad', 'Dialog', '*Untitled - NotepadDialog'] child_window(title="*Untitled - Notepad", control_type="Window") | | Menu - 'File' (L-1503, T315, R-1234, B527) | ['File', 'FileMenu', 'Menu', 'Menu0', 'Menu1', 'File0', 'File1'] | child_window(title="File", control_type="Menu") | | | | MenuItem - 'New Ctrl+N' (L-1500, T318, R-1237, B342) | | ['New\tCtrl+N', 'New\tCtrl+NMenuItem', 'MenuItem', 'MenuItem0', 'MenuItem1'] | | child_window(title="New Ctrl+N", auto_id="1", control_type="MenuItem") | | | | MenuItem - 'New Window Ctrl+Shift+N' (L-1500, T342, R-1237, B366) | | ['MenuItem2', 'New Window\tCtrl+Shift+N', 'New Window\tCtrl+Shift+NMenuItem'] | | child_window(title="New Window Ctrl+Shift+N", auto_id="8", control_type="MenuItem") | | | | MenuItem - 'Open... Ctrl+O' (L-1500, T366, R-1237, B390) | | ['Open...\tCtrl+O', 'Open...\tCtrl+OMenuItem', 'MenuItem3'] | | child_window(title="Open... Ctrl+O", auto_id="2", control_type="MenuItem") | | | | MenuItem - 'Save Ctrl+S' (L-1500, T390, R-1237, B414) | | ['Save\tCtrl+SMenuItem', 'Save\tCtrl+S', 'MenuItem4'] | | child_window(title="Save Ctrl+S", auto_id="3", control_type="MenuItem") | | | | MenuItem - 'Save As... Ctrl+Shift+S' (L-1500, T414, R-1237, B438) | | ['MenuItem5', 'Save As...\tCtrl+Shift+S', 'Save As...\tCtrl+Shift+SMenuItem'] | | child_window(title="Save As... Ctrl+Shift+S", auto_id="4", control_type="MenuItem") | | | | MenuItem - 'Page Setup...' (L-1500, T445, R-1237, B469) | | ['Page Setup...MenuItem', 'MenuItem6', 'Page Setup...'] | | child_window(title="Page Setup...", auto_id="5", control_type="MenuItem") | | | | MenuItem - 'Print... Ctrl+P' (L-1500, T469, R-1237, B493) | | ['Print...\tCtrl+P', 'MenuItem7', 'Print...\tCtrl+PMenuItem'] | | child_window(title="Print... Ctrl+P", auto_id="6", control_type="MenuItem") | | | | MenuItem - 'Exit' (L-1500, T500, R-1237, B524) | | ['Exit', 'ExitMenuItem', 'MenuItem8'] | | child_window(title="Exit", auto_id="7", control_type="MenuItem") | | Edit - 'Text Editor' (L-1503, T316, R632, B971) | ['Edit', 'Edit0', 'Edit1'] | child_window(title="Text Editor", auto_id="15", control_type="Edit") | | | | ScrollBar - 'Vertical' (L611, T316, R632, B950) | | ['Vertical', 'ScrollBar', 'VerticalScrollBar', 'ScrollBar0', 'ScrollBar1'] | | child_window(title="Vertical", auto_id="NonClientVerticalScrollBar", control_type="ScrollBar") | | | | | | Button - 'Line up' (L611, T316, R632, B337) | | | ['Line up', 'Line upButton', 'Button', 'Button0', 'Button1'] | | | child_window(title="Line up", auto_id="UpButton", control_type="Button") | | | | | | Button - 'Line down' (L611, T929, R632, B950) | | | ['Line down', 'Line downButton', 'Button2'] | | | child_window(title="Line down", auto_id="DownButton", control_type="Button") | | …
Additionally, you can find the control identifiers Notepad in the article «Autotesting a notebook with pywinauto»
Example of searching for a nested object
Let's open a new window.
To do this, in the previous output we will find
… | | MenuItem - 'New Window Ctrl+Shift+N' (L-1500, T342, R-1237, B366) | | ['MenuItem2', 'New Window\tCtrl+Shift+N', 'New Window\tCtrl+Shift+NMenuItem'] | | child_window(title="New Window Ctrl+Shift+N", auto_id="8", control_type="MenuItem") …
Copy the line with clild_window, but instead of New Window Ctrl+Shift+N we will use New Window\tCtrl+Shift+N
from pywinauto.application import Application app = Application(backend='uia').start('notepad.exe') app = Application(backend='uia').connect( title='Untitled - Notepad', timeout=100 ) # app.UntitledNotepad.print_control_identifiers() text_editor = app.UntitledNotepad.child_window( title="Text Editor", auto_id="15", control_type="Edit" ).wrapper_object() text_editor.type_keys( "Subscribe to t.me/aofeed", with_spaces=True ) file_menu = app.UntitledNotepad.child_window( title="File", control_type="MenuItem" ).wrapper_object() file_menu.click_input() app.UntitledNotepad.print_control_identifiers() new_window = app.UntitledNotepad.child_window( title="New Window\tCtrl+Shift+N", auto_id="8", control_type="MenuItem" ).wrapper_object() new_window.click_input()
The dialog box
For simplicity, we will not open a new window. Open the notepad, enter the text and use the close controller
… | | Button - 'Close' (L573, T254, R633, B291) | | ['CloseButton', 'Close', 'Button7'] | | child_window(title="Close", control_type="Button") …
Let's close the notebook. The Save, Don't Save dialog box appears and so on.
At this point, you need to print the controllers of this dialog box and use the one you need.
from pywinauto.application import Application app = Application(backend='uia').start('notepad.exe') app = Application(backend='uia').connect( title='Untitled - Notepad', timeout=100 ) text_editor = app.UntitledNotepad.child_window( title="Text Editor", auto_id="15", control_type="Edit" ).wrapper_object() text_editor.type_keys( "Subscribe to t.me/aofeed", with_spaces=True ) file_menu = app.UntitledNotepad.child_window( title="File", control_type="MenuItem" ).wrapper_object() file_menu.click_input() close = app.UntitledNotepad.child_window( title="Close", control_type="Button" ).wrapper_object() close.click_input() app.UntitledNotepad.print_control_identifiers() dont_save = app.UntitledNotepad.child_window( title="Don't Save", auto_id="CommandButton_7", control_type="Button" ).wrapper_object() dont_save.click_input()
… Control Identifiers: Dialog - '*Untitled - Notepad' (L321, T173, R769, B851) ['Dialog', '*Untitled - Notepad', '*Untitled - NotepadDialog', 'Dialog0', 'Dialog1'] child_window(title="*Untitled - Notepad", control_type="Window") | | Dialog - 'Notepad' (L1042, T568, R1500, B745) | ['Notepad', 'Dialog2', 'NotepadDialog'] | child_window(title="Notepad", control_type="Window") | | | | Static - 'Do you want to save changes to Untitled?' (L1064, T619, R1427, B647) | | ['Static', 'Do you want to save changes to Untitled?', 'Do you want to save changes to Untitled?Static', 'Static0', 'Static1'] | | child_window(title="Do you want to save changes to Untitled?", auto_id="MainInstruction", control_type="Text") | | | | Static - '' (L0, T0, R0, B0) | | ['Static2'] | | child_window(auto_id="ContentText", control_type="Text") | | | | Button - 'Save' (L1174, T696, R1261, B725) | | ['Button', 'Save', 'SaveButton', 'Button0', 'Button1'] | | child_window(title="Save", auto_id="CommandButton_6", control_type="Button") | | | | Button - 'Don't Save' (L1269, T696, R1383, B725) | | ['Button2', "Don't SaveButton", "Don't Save"] | | child_window(title="Don't Save", auto_id="CommandButton_7", control_type="Button") | | | | Button - 'Cancel' (L1391, T696, R1478, B725) | | ['Cancel', 'Button3', 'CancelButton'] | | child_window(title="Cancel", auto_id="CommandButton_2", control_type="Button") | | | | TitleBar - '' (L1051, T571, R1491, B606) | | ['TitleBar', 'TitleBar0', 'TitleBar1'] | | | | | | Button - 'Close' (L1448, T569, R1492, B606) | | | ['Button4', 'CloseButton', 'Close', 'CloseButton0', 'CloseButton1', 'Close0', 'Close1'] | | | child_window(title="Close", control_type="Button") …
Controllers
There are two sets of controllers:
Searching for a text input element
Let's say you need to enter text into similar element
www.aredel.com
Approximate procedure:
Launch the application.
Define the title of the dialog box - in this example, it is the App Name.
Print controllers for the dialog box
app = Application(backend='win32').start("path_to_exe") app = app.connect(best_match="App Name", timeout=3) dlg = app.AppName dlg.print_ctrl_ids()
Somewhere in the output there will be a similar result
Control Identifiers: Dialog - 'App Name' (L735, T375, R1186, B777) ['App NameDialog', 'App Name', 'Dialog'] child_window(title="App Name", class_name="#32770") | … | ComboBox - '' (L819, T518, R1067, B539) | ['ComboBox', '&Destination:ComboBox', 'ComboBox0', 'ComboBox1'] | child_window(title="", class_name="ComboBox") | | | | Edit - '' (L822, T521, R1047, B536) | | ['&Destination:Edit', 'Edit', 'Edit0', 'Edit1'] | | child_window(title="", class_name="Edit") …
If there was text in the field, for example abc, it would be displayed like this:
… | ComboBox - 'abc' (L819, T518, R1067, B539) | ['ComboBox', '&Destination:ComboBox', 'ComboBox0', 'ComboBox1'] | child_window(title="abc", class_name="ComboBox") | | | | Edit - 'abc' (L822, T521, R1047, B536) | | ['&Destination:Edit', 'Edit', 'Edit0', 'Edit1'] | | child_window(title="abc", class_name="Edit") …
Since the desired element is contained inside the ComboBox, you can iterate through all ComboBox using found_index
Let's assume that we are lucky and the desired box has an index of 0.
destination = dlg.child_window(class_name="ComboBox", found_index=0) destination.print_ctrl_ids()
Control Identifiers: ComboBox - 'abc' (L819, T518, R1067, B539) ['ComboBox'] child_window(title="abc", class_name="ComboBox") | | Edit - 'abc' (L822, T521, R1047, B536) | ['Edit'] | child_window(title="abc", class_name="Edit")
In a similar way, we will find a nested input field. Since there is only one child element, there will also be an index of 0.
text_input = destination.child_window(class_name="Edit", found_index=0) text_input.set_text("https://aredel.com")
Result:
www.aredel.com
Full code
app = Application(backend='win32').start("path_to_exe") app = app.connect(best_match="App Name", timeout=3) dlg = app.AppName # dlg.print_ctrl_ids() destination = dlg.child_window(class_name="ComboBox", found_index=0) # destination.print_ctrl_ids() text_input = destination.child_window(class_name="Edit", found_index=0) text_input.set_text("https://aredel.com")
app.windows()
An example of a search by app.windows(). I spied on it here
… print([w.window_text() for w in app.windows()]) i = 0 for w in app.windows(): i += 1 print(i) print(dir(w)) print(w.children()) if i == 1: wind = w print(dir(wind)) print(wind.children()) children = wind.children() i = 0 for child in children: i += 1 print(dir(child)) print(child.texts) if i == 1: child.click()
requirements.txt
certifi==2024.2.2 charset-normalizer==3.3.2 comtypes==1.4.1 idna==3.7 pywin32==306 pywinauto==0.6.8 requests==2.31.0 six==1.16.0 typing_extensions==4.12.2 urllib3==2.2.1 WMI==1.5.1
| Automation | |
| pywinauto | |
| Python |