├── README.md ├── _mainframe.py ├── appveyor.yml ├── build.bat ├── ci ├── build.ps1 └── install.ps1 ├── code_manager.py ├── const.py ├── deps ├── wxPython-3.0.2.0-cp27-none-win32.whl ├── wxPython-3.0.2.0-cp27-none-win_amd64.whl └── wxPython_common-3.0.2.0-py2-none-any.whl ├── dev-requirements.txt ├── proxy.py ├── swapy-debug.spec ├── swapy-ob.py ├── swapy.spec ├── swapy_dog.ico ├── swapy_dog.png ├── swapy_dog_head.ico └── unittests ├── __init__.py └── test_codegen.py /README.md: -------------------------------------------------------------------------------- 1 | # SWAPY 2 | pywinauto Inspector and Code generator 3 | 4 | [![GitHub downloads](https://img.shields.io/github/downloads/pywinauto/SWAPY/0.4.8/swapy32bit.exe.svg)](https://github.com/pywinauto/SWAPY/releases/download/0.4.8/swapy32bit.exe) [![GitHub downloads](https://img.shields.io/github/downloads/pywinauto/SWAPY/0.4.8/swapy64bit.exe.svg)](https://github.com/pywinauto/SWAPY/releases/download/0.4.8/swapy64bit.exe) 5 | 6 | Swapy-ob is a tool for GUI Automation for Windows. Finally you will get native python code for pywinauto module. [Simple example video](https://youtu.be/xaMFHOq_Hls) 7 | 8 | ![](http://swapy.googlecode.com/files/swapy.JPG) 9 | 10 | Automate in 3 steps: 11 | * Select a control. 12 | * Choose action by right-click. 13 | * Get native python code. 14 | 15 | ![](http://swapy.googlecode.com/files/steps.jpg) 16 | -------------------------------------------------------------------------------- /_mainframe.py: -------------------------------------------------------------------------------- 1 | # GUI object/properties browser. 2 | # Copyright (C) 2011 Matiychuk D. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public License 6 | # as published by the Free Software Foundation; either version 2.1 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | # See the GNU Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library; if not, write to the 16 | # Free Software Foundation, Inc., 17 | # 59 Temple Place, 18 | # Suite 330, 19 | # Boston, MA 02111-1307 USA 20 | 21 | #Boa:Frame:MainFrame 22 | 23 | import const 24 | import exceptions 25 | import locale 26 | import platform 27 | import thread 28 | import traceback 29 | import wx 30 | 31 | import code_manager 32 | import proxy 33 | 34 | #Avoid limit of wx.ListCtrl in 512 symbols 35 | PROPERTIES = {} 36 | 37 | def create(parent): 38 | return Frame1(parent) 39 | 40 | 41 | [wxID_FRAME1, wxID_FRAME1LISTCTRL1_PROPERTIES, wxID_FRAME1STATICBOX_EDITOR, 42 | wxID_FRAME1STATICBOX_OBJECTSBROWSER, wxID_FRAME1STATICBOX_PROPRTIES, 43 | wxID_FRAME1TEXTCTRL_EDITOR, wxID_FRAME1TREECTRL_OBJECTSBROWSER 44 | ] = [wx.NewId() for _init_ctrls in range(7)] 45 | 46 | 47 | class Frame1(wx.Frame): 48 | """ 49 | Main application frame 50 | """ 51 | 52 | def _init_ctrls(self, prnt): 53 | 54 | #-----Main frame----- 55 | wx.Frame.__init__(self, id=wxID_FRAME1, name='', parent=prnt, 56 | style=wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN | wx.RESIZE_BORDER, 57 | title='SWAPY - Simple Windows Automation on Python v. %s. pywinauto v. %s. %s' % (const.VERSION, 58 | proxy.pywinauto.__version__, 59 | platform.architecture()[0])) 60 | self.SetIcon(wx.Icon(proxy.resource_path("swapy_dog_head.ico"), 61 | wx.BITMAP_TYPE_ICO)) 62 | 63 | self.Bind(wx.EVT_MENU, self.menu_action) # - make action 64 | #---------- 65 | 66 | #-----Static Boxes----- 67 | self.staticBox_ObjectsBrowser = wx.StaticBox(id=wxID_FRAME1STATICBOX_OBJECTSBROWSER, 68 | label='Objects browser', name='staticBox_ObjectsBrowser', 69 | parent=self) 70 | 71 | self.staticBox_Editor = wx.StaticBox(id=wxID_FRAME1STATICBOX_EDITOR, 72 | label='Editor', name='staticBox_Editor', parent=self,) 73 | 74 | self.staticBox_Proprties = wx.StaticBox(id=wxID_FRAME1STATICBOX_PROPRTIES, 75 | label='Properties', name='staticBox_Proprties', parent=self) 76 | #---------- 77 | 78 | #-----ObjectsBrowser----- 79 | self.treeCtrl_ObjectsBrowser = wx.TreeCtrl(id=wxID_FRAME1TREECTRL_OBJECTSBROWSER, 80 | name='treeCtrl_ObjectsBrowser', parent=self, style=wx.TR_HAS_BUTTONS) 81 | 82 | self.treeCtrl_ObjectsBrowser.Bind(wx.EVT_TREE_SEL_CHANGED, 83 | self.ObjectsBrowserSelChanged, id=wxID_FRAME1TREECTRL_OBJECTSBROWSER) 84 | 85 | self.treeCtrl_ObjectsBrowser.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.ObjectsBrowserRightClick) 86 | #---------- 87 | 88 | #-----Editor----- 89 | self.textCtrl_Editor = wx.TextCtrl(id=wxID_FRAME1TEXTCTRL_EDITOR, 90 | name='textCtrl_Editor', parent=self, style=wx.TE_MULTILINE | wx.TE_READONLY, value='') 91 | 92 | self.textCtrl_Editor.Bind(wx.EVT_CONTEXT_MENU, self.EditorContextMenu) 93 | 94 | self.textCtrl_Editor.SetInitialSize((300,250)) 95 | #---------- 96 | 97 | #-----Properties----- 98 | self.listCtrl_Properties = wx.ListCtrl(id=wxID_FRAME1LISTCTRL1_PROPERTIES, name='listCtrl1_Properties', 99 | parent=self, style=wx.LC_REPORT) 100 | 101 | self.listCtrl_Properties.InsertColumn(col=0, format=wx.LIST_FORMAT_LEFT, 102 | heading='Property', width=-1) 103 | 104 | self.listCtrl_Properties.InsertColumn(col=1, format=wx.LIST_FORMAT_LEFT, 105 | heading='Value', width=-1) 106 | 107 | self.listCtrl_Properties.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, 108 | self.PropertiesRightClick, id=wxID_FRAME1LISTCTRL1_PROPERTIES) 109 | 110 | #self.listCtrl_Properties.Bind(wx.EVT_LEFT_DCLICK, self.Refresh, id=wxID_FRAME1LISTCTRL1_PROPERTIES) 111 | #---------- 112 | 113 | #-----Sizers----- 114 | staticBox_ObjectsBrowser_sizer = wx.StaticBoxSizer(self.staticBox_ObjectsBrowser) 115 | staticBox_ObjectsBrowser_sizer.Add(self.treeCtrl_ObjectsBrowser, 1, wx.EXPAND, 2) 116 | 117 | staticBox_Editor_sizer = wx.StaticBoxSizer(self.staticBox_Editor) 118 | staticBox_Editor_sizer.Add(self.textCtrl_Editor, 1, wx.EXPAND, 2) 119 | 120 | staticBox_Proprties_sizer = wx.StaticBoxSizer(self.staticBox_Proprties) 121 | staticBox_Proprties_sizer.Add(self.listCtrl_Properties, 1, wx.EXPAND, 2) 122 | 123 | sizer_h = wx.BoxSizer(wx.HORIZONTAL) 124 | sizer_v = wx.BoxSizer(wx.VERTICAL) 125 | 126 | sizer_h.Add(staticBox_ObjectsBrowser_sizer, 1, wx.EXPAND, 2) 127 | sizer_h.Add(sizer_v, 1, wx.EXPAND|wx.ALL, 2) 128 | sizer_v.Add(staticBox_Editor_sizer, 1, wx.EXPAND, 2) 129 | sizer_v.Add(staticBox_Proprties_sizer, 1, wx.EXPAND, 2) 130 | 131 | self.SetSizerAndFit(sizer_h) 132 | #---------- 133 | 134 | def __init__(self, parent): 135 | self._init_ctrls(parent) 136 | self._init_windows_tree() 137 | self.textCtrl_Editor.SetForegroundColour(wx.LIGHT_GREY) 138 | self.textCtrl_Editor.AppendText('#Perform an action - right click on item in the object browser.') 139 | self.prop_updater = prop_viewer_updater(self.listCtrl_Properties) 140 | self.tree_updater = tree_updater(self.treeCtrl_ObjectsBrowser) 141 | 142 | def ObjectsBrowserSelChanged(self, event): 143 | tree_item = event.GetItem() 144 | obj = self.treeCtrl_ObjectsBrowser.GetItemData(tree_item).GetData() 145 | if not obj._check_existence(): 146 | self._init_windows_tree() 147 | tree_item = self.treeCtrl_ObjectsBrowser.GetRootItem() 148 | obj = self.treeCtrl_ObjectsBrowser.GetItemData(tree_item).GetData() 149 | self.prop_updater.props_update(obj) 150 | self.tree_updater.tree_update(tree_item, obj) 151 | obj.Highlight_control() 152 | 153 | def ObjectsBrowserRightClick(self, event): 154 | menu = wx.Menu() 155 | #tree_item = self.treeCtrl_ObjectsBrowser.GetSelection() 156 | tree_item = event.GetItem() 157 | obj = self.treeCtrl_ObjectsBrowser.GetItemData(tree_item).GetData() 158 | self.GLOB_last_rclick_tree_obj = obj 159 | #self.treeCtrl_ObjectsBrowser.SelectItem(tree_item) 160 | if obj._check_existence(): 161 | actions = obj.Get_actions() 162 | extended_actions = obj.Get_extended_actions() 163 | if not actions and not extended_actions: 164 | menu.Append(0, 'No actions') 165 | menu.Enable(0, False) 166 | else: 167 | if extended_actions: 168 | for _id, extended_action_name in extended_actions: 169 | menu.Append(_id, extended_action_name) 170 | if not obj._check_actionable(): 171 | menu.Enable(_id, False) 172 | menu.AppendSeparator() 173 | 174 | for _id, action_name in actions: 175 | menu.Append(_id, action_name) 176 | if not obj._check_actionable(): 177 | menu.Enable(_id, False) 178 | 179 | self.PopupMenu(menu) 180 | menu.Destroy() 181 | else: 182 | self._init_windows_tree() 183 | tree_item = self.treeCtrl_ObjectsBrowser.GetRootItem() 184 | obj = self.treeCtrl_ObjectsBrowser.GetItemData(tree_item).GetData() 185 | self.prop_updater.props_update(obj) 186 | self.tree_updater.tree_update(tree_item, obj) 187 | 188 | def PropertiesRightClick(self, event): 189 | self.GLOB_prop_item_index = event.GetIndex() 190 | menu = wx.Menu() 191 | for _id, option_name in sorted(const.PROPERTIES_ACTIONS.items()): 192 | if option_name: 193 | menu.Append(_id, option_name) 194 | else: 195 | menu.AppendSeparator() 196 | self.PopupMenu(menu) 197 | menu.Destroy() 198 | 199 | def EditorContextMenu(self, event): 200 | cm = code_manager.CodeManager() 201 | menu = wx.Menu() 202 | 203 | for _id, option_name in sorted(const.EDITOR_ACTIONS.items()): 204 | if option_name: 205 | menu.Append(_id, option_name) 206 | if not cm: # empty code 207 | menu.Enable(_id, False) 208 | else: 209 | menu.AppendSeparator() 210 | if not self.textCtrl_Editor.GetStringSelection(): # empty selection 211 | menu.Enable(404, False) # 404: 'Copy' 212 | 213 | self.PopupMenu(menu) 214 | menu.Destroy() 215 | 216 | def menu_action(self, event): 217 | menu_id = event.Id 218 | if menu_id in const.ACTIONS or \ 219 | menu_id in const.EXTENDED_ACTIONS: 220 | # object browser menu 221 | # regular action or extended action 222 | self.make_action(menu_id) 223 | 224 | elif menu_id in const.PROPERTIES_ACTIONS: 225 | # properties viewer menu 226 | self.properties_action(menu_id) 227 | 228 | elif menu_id in const.EDITOR_ACTIONS: 229 | # editor menu 230 | self.editor_action(menu_id) 231 | 232 | else: 233 | raise RuntimeError("Unknown menu_id=%s for properties " 234 | "menu" % menu_id) 235 | 236 | def properties_action(self, menu_id): 237 | item = self.GLOB_prop_item_index 238 | clipdata = wx.TextDataObject() 239 | 240 | if 'Copy all' == const.PROPERTIES_ACTIONS[menu_id]: 241 | all_texts = '' 242 | items_count = self.listCtrl_Properties.GetItemCount() 243 | for i in range(items_count): 244 | prop_name = self.listCtrl_Properties.GetItem(i, 0).GetText() 245 | val_name = self.listCtrl_Properties.GetItem(i, 1).GetText() 246 | all_texts += '%s : %s' % (prop_name, val_name) + '\n' 247 | clipdata.SetText(all_texts) 248 | 249 | elif 'Copy property' == const.PROPERTIES_ACTIONS[menu_id]: 250 | property = self.listCtrl_Properties.GetItem(item,0).GetText() 251 | clipdata.SetText(property) 252 | 253 | elif 'Copy value' == const.PROPERTIES_ACTIONS[menu_id]: 254 | #value = self.listCtrl_Properties.GetItem(item,1).GetText() 255 | key = self.listCtrl_Properties.GetItem(item,0).GetText() 256 | try: 257 | value_str = str(PROPERTIES[key]) 258 | except exceptions.UnicodeEncodeError: 259 | value_str = PROPERTIES[key].encode( 260 | locale.getpreferredencoding(), 'replace') 261 | clipdata.SetText(value_str) 262 | 263 | elif 'Copy unicode value' == const.PROPERTIES_ACTIONS[menu_id]: 264 | key = self.listCtrl_Properties.GetItem(item,0).GetText() 265 | try: 266 | value_unicode_escape = str(PROPERTIES[key]) 267 | except exceptions.UnicodeEncodeError: 268 | value_unicode_escape = PROPERTIES[key].encode('unicode-escape', 269 | 'replace') 270 | clipdata.SetText(value_unicode_escape) 271 | else: 272 | raise RuntimeError("Unknown menu_id=%s for properties " 273 | "menu" % menu_id) 274 | 275 | self.GLOB_prop_item_index = None 276 | wx.TheClipboard.Open() 277 | wx.TheClipboard.SetData(clipdata) 278 | wx.TheClipboard.Close() 279 | 280 | def make_action(self, menu_id): 281 | obj = self.GLOB_last_rclick_tree_obj 282 | 283 | if menu_id in const.ACTIONS: 284 | # Regular action 285 | action = const.ACTIONS[menu_id] 286 | try: 287 | code = obj.Get_code(action) 288 | obj.Exec_action(action) 289 | except: 290 | code = None 291 | dlg = wx.MessageDialog(self, traceback.format_exc(5), 292 | 'Warning!', wx.OK | wx.ICON_WARNING) 293 | dlg.ShowModal() 294 | dlg.Destroy() 295 | 296 | elif menu_id in const.EXTENDED_ACTIONS: 297 | # Extended action 298 | try: 299 | obj.SetCodestyle(menu_id) 300 | code = obj.Get_code() 301 | except: 302 | code = None 303 | dlg = wx.MessageDialog(self, traceback.format_exc(5), 304 | 'Warning!', wx.OK | wx.ICON_WARNING) 305 | dlg.ShowModal() 306 | dlg.Destroy() 307 | 308 | if code is not None: 309 | self.textCtrl_Editor.SetForegroundColour(wx.BLACK) 310 | self.textCtrl_Editor.SetValue(code) 311 | 312 | def editor_action(self, menu_id): 313 | cm = code_manager.CodeManager() 314 | 315 | if 'Clear last command' == const.EDITOR_ACTIONS[menu_id]: 316 | cm.clear_last() 317 | self.textCtrl_Editor.SetValue(cm.get_full_code()) 318 | 319 | elif 'Clear the code' == const.EDITOR_ACTIONS[menu_id]: 320 | def confirm_clearing(): 321 | yes_no_dlg = wx.MessageDialog(self.textCtrl_Editor, 322 | "Are you sure you want to clear all of " 323 | "the code?", 324 | "Clear all?", 325 | wx.YES_NO | wx.ICON_QUESTION) 326 | result = yes_no_dlg.ShowModal() == wx.ID_YES 327 | yes_no_dlg.Destroy() 328 | return result 329 | 330 | if confirm_clearing(): 331 | self.textCtrl_Editor.SetValue("") 332 | cm.clear() 333 | 334 | elif 'Copy' == const.EDITOR_ACTIONS[menu_id]: 335 | self.textCtrl_Editor.Copy() 336 | 337 | elif 'Select all' == const.EDITOR_ACTIONS[menu_id]: 338 | self.textCtrl_Editor.SetFocus() 339 | self.textCtrl_Editor.SelectAll() 340 | 341 | elif 'Save code to file' == const.EDITOR_ACTIONS[menu_id]: 342 | import os 343 | dlg = wx.FileDialog(self, "Choose a file", '', '', "*.py", 344 | wx.SAVE | wx.OVERWRITE_PROMPT) 345 | if dlg.ShowModal() == wx.ID_OK: 346 | with open(os.path.join(dlg.GetDirectory(), 347 | dlg.GetFilename()), 'w') as out_file: 348 | out_file.write("# automatically generated by SWAPY\n") 349 | out_file.write(self.textCtrl_Editor.GetValue()) 350 | dlg.Destroy() 351 | 352 | else: 353 | raise RuntimeError("Unknown menu_id=%s for editor " 354 | "menu" % menu_id) 355 | 356 | def _init_windows_tree(self): 357 | self.treeCtrl_ObjectsBrowser.DeleteAllItems() 358 | item_data = wx.TreeItemData() 359 | root_obj = proxy.PC_system(None) 360 | item_data.SetData(root_obj) 361 | self.treeCtrl_ObjectsBrowser.AddRoot(root_obj.GetProperties()['PC name'], data = item_data) 362 | #self.treeCtrl_ObjectsBrowser.AddRoot('PC name') 363 | del item_data 364 | #the_root = self.treeCtrl_ObjectsBrowser.GetRootItem() 365 | #self.treeCtrl_ObjectsBrowser.Expand(self.treeCtrl_ObjectsBrowser.GetRootItem()) 366 | 367 | 368 | class prop_viewer_updater(object): 369 | def __init__(self, listctrl): 370 | self.listctrl = listctrl 371 | self.updating = False 372 | self.queue = [] 373 | 374 | def props_update(self, obj): 375 | self.queue.append(obj) 376 | if self.updating: 377 | return 0 378 | else: 379 | thread.start_new_thread(self._update,()) 380 | 381 | def _update(self): 382 | self.updating = True 383 | obj = self.queue[-1] 384 | self.listctrl.DeleteAllItems() 385 | index = self.listctrl.InsertStringItem(0, 'Updating...') 386 | self.listctrl.SetStringItem(index, 1, '') 387 | global PROPERTIES 388 | try: 389 | PROPERTIES = obj.GetProperties() 390 | except: 391 | PROPERTIES = {} 392 | dlg = wx.MessageDialog(self.listctrl, traceback.format_exc(5), 393 | 'Warning!', wx.OK | wx.ICON_WARNING) 394 | dlg.ShowModal() 395 | dlg.Destroy() 396 | 397 | param_names = PROPERTIES.keys() 398 | param_names.sort(key=lambda name: name.lower(), reverse=True) 399 | 400 | if obj == self.queue[-1]: 401 | self.listctrl.DeleteAllItems() 402 | for p_name in param_names: 403 | p_name_str = str(p_name) 404 | try: 405 | p_values_str = str(PROPERTIES[p_name]) 406 | except exceptions.UnicodeEncodeError: 407 | p_values_str = PROPERTIES[p_name].encode( 408 | locale.getpreferredencoding(), 'replace') 409 | index = self.listctrl.InsertStringItem(0, p_name_str) 410 | self.listctrl.SetStringItem(index, 1, p_values_str) 411 | self.queue = [] 412 | self.updating = False 413 | 414 | else: 415 | self._update() 416 | #there is the newer object for properties view. 417 | #Do not update listctrl 418 | #run _update again 419 | 420 | 421 | class tree_updater(object): 422 | def __init__(self, treectrl): 423 | self.treectrl = treectrl 424 | self.updating = False 425 | self.queue = [] 426 | 427 | def tree_update(self, tree_item, obj): 428 | self.queue.append((tree_item, obj)) 429 | if self.updating: 430 | return 0 431 | else: 432 | thread.start_new_thread(self._update,()) 433 | 434 | def _update(self): 435 | self.updating = True 436 | tree_item, obj = self.queue[-1] 437 | self.treectrl.DeleteChildren(tree_item) 438 | subitems = obj.Get_subitems() 439 | for i_name, i_obj in subitems: 440 | item_data = wx.TreeItemData() 441 | item_data.SetData(i_obj) 442 | try: 443 | i_name_str = str(i_name) 444 | except exceptions.UnicodeEncodeError: 445 | i_name_str = i_name.encode(locale.getpreferredencoding(), 'replace') 446 | 447 | try: 448 | item_id = self.treectrl.AppendItem(tree_item, i_name_str, data=item_data) 449 | if (not i_obj._check_visibility()) or (not i_obj._check_actionable()): 450 | self.treectrl.SetItemTextColour(item_id,'gray') 451 | except wx._core.PyAssertionError: 452 | pass 453 | #Ignore tree item creation error when parent is not exists 454 | finally: 455 | del item_data 456 | self.treectrl.Expand(self.treectrl.GetRootItem()) 457 | 458 | if (tree_item, obj) == self.queue[-1]: 459 | self.queue = [] 460 | self.updating = False 461 | 462 | else: 463 | self._update() 464 | #there is the newer object for tree view. 465 | #Do not update treeCtrl 466 | #run _update again -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/pywinauto/pywinauto 2 | # 3 | # Hint: before committing any changes in the yaml file verify it in 4 | # the yaml online parser: http://yaml-online-parser.appspot.com/ 5 | # 6 | 7 | # fetch repository as a zip archive 8 | shallow_clone: true # default is "false" 9 | 10 | environment: 11 | 12 | matrix: 13 | - PYTHON: "C:\\Python27" 14 | PYTHON_VERSION: "2.7" 15 | PYTHON_ARCH: "32" 16 | 17 | - PYTHON: "C:\\Python27-x64" 18 | PYTHON_VERSION: "2.7" 19 | PYTHON_ARCH: "64" 20 | 21 | #os: 22 | # - unstable # Unstable worker image with logged in user, desktop and interactive build agent 23 | 24 | install: 25 | # Some listings for debug only 26 | #- ECHO "Filesystem root:" 27 | #- ps: "ls \"C:/\"" 28 | #- ECHO "Notepad location " 29 | #- ps: "ls C:\\Windows\\System32\\notepad.exe" 30 | 31 | - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" 32 | 33 | # Install the build dependencies of the project. 34 | - "%CMD_IN_ENV% pip install -r dev-requirements.txt" 35 | 36 | build: off # disable automatic builds 37 | 38 | 39 | before_build: 40 | # Download sample apps 41 | - "%CMD_IN_ENV% svn checkout https://github.com/pywinauto/pywinauto/trunk/apps ./apps" 42 | # Run unittests 43 | - "%CMD_IN_ENV% python -m unittest discover" 44 | 45 | build_script: 46 | # install wxPython 47 | - "%CMD_IN_ENV% powershell ./ci/install.ps1" 48 | # Print python modules again 49 | - "%CMD_IN_ENV% pip freeze" 50 | # Build UI 51 | - "%CMD_IN_ENV% powershell ./ci/build.ps1" 52 | 53 | 54 | #test_script: 55 | # # Run UI tests 56 | 57 | 58 | artifacts: 59 | # Archive the generated coverage report in the ci.appveyor.com build report. 60 | - path: swapy*.exe 61 | -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | set home=%cd% 2 | C:\Python27\Scripts\pyinstaller.exe --clean swapy.spec 3 | copy dist\swapy.exe %home%\swapy32bit.exe 4 | 5 | C:\Python27x64\Scripts\pyinstaller.exe --clean swapy.spec 6 | copy dist\swapy.exe %home%\swapy64bit.exe 7 | -------------------------------------------------------------------------------- /ci/build.ps1: -------------------------------------------------------------------------------- 1 | # Script to build with PyInstaller 2 | # Authors: Denis Matiychuk 3 | # License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ 4 | 5 | 6 | function BuildExe ($python_version, $architecture, $python_home, $build_type) { 7 | Write-Host "Building with PyInstaller" $python_version "for" $architecture "bit architecture to" $python_home 8 | $pyinstaller_path = $python_home + "\Scripts\pyinstaller.exe" 9 | if ($build_type -eq "release") { 10 | $args = "--clean swapy.spec" 11 | $input_filename = "swapy.exe" 12 | if ($architecture -eq "32") { 13 | $out_filename = "swapy32bit.exe" 14 | } else { 15 | $out_filename = "swapy64bit.exe" 16 | } 17 | } else { 18 | $args = "--clean swapy-debug.spec" 19 | $input_filename = "swapy-debug.exe" 20 | if ($architecture -eq "32") { 21 | $out_filename = "swapy32bit-debug.exe" 22 | } else { 23 | $out_filename = "swapy64bit-debug.exe" 24 | } 25 | } 26 | 27 | Write-Host "Start building" $pyinstaller_path $args 28 | Start-Process -FilePath $pyinstaller_path -ArgumentList $args -Wait -Passthru 29 | 30 | Write-Host "Copy out file" .\dist\$input_filename .\$out_filename 31 | Copy-Item .\dist\$input_filename .\$out_filename 32 | 33 | } 34 | 35 | function main () { 36 | BuildExe $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON "release" 37 | BuildExe $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON "debug" 38 | } 39 | 40 | main 41 | -------------------------------------------------------------------------------- /ci/install.ps1: -------------------------------------------------------------------------------- 1 | # Script to install wxPython 2 | # Authors: Denis Matiychuk 3 | # License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ 4 | 5 | $WX_32 = "./deps/wxPython-3.0.2.0-cp27-none-win32.whl" 6 | $WX_64 = "./deps/wxPython-3.0.2.0-cp27-none-win_amd64.whl" 7 | $WX_COMMON = "./deps/wxPython_common-3.0.2.0-py2-none-any.whl" 8 | 9 | 10 | function InstallWX ($python_version, $architecture, $python_home) { 11 | Write-Host "Installing wxPython" $python_version "for" $architecture "bit architecture to" $python_home 12 | $pip_path = $python_home + "\Scripts\pip.exe" 13 | 14 | if ($architecture -eq "32") { 15 | $wx_wheel = $WX_32 16 | } else { 17 | $wx_wheel = $WX_64 18 | } 19 | $args = "install " + $wx_wheel + " " + $WX_COMMON 20 | Write-Host "Install wheels" $args "by" $pip_path 21 | Start-Process -FilePath $pip_path -ArgumentList $args -Wait -Passthru 22 | } 23 | 24 | 25 | function main () { 26 | InstallWX $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON 27 | } 28 | 29 | main 30 | -------------------------------------------------------------------------------- /code_manager.py: -------------------------------------------------------------------------------- 1 | # GUI object/properties browser. 2 | # Copyright (C) 2015 Matiychuk D. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public License 6 | # as published by the Free Software Foundation; either version 2.1 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | # See the GNU Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library; if not, write to the 16 | # Free Software Foundation, Inc., 17 | # 59 Temple Place, 18 | # Suite 330, 19 | # Boston, MA 02111-1307 USA 20 | 21 | 22 | import re 23 | 24 | 25 | def check_valid_identifier(identifier): 26 | 27 | """ 28 | Check the identifier is a valid Python identifier. 29 | Since the identifier will be used as an attribute, 30 | don't check for reserved names. 31 | """ 32 | 33 | return bool(re.match("[_A-Za-z][_a-zA-Z0-9]*$", identifier)) 34 | 35 | 36 | class CodeSnippet(object): 37 | 38 | """ 39 | A piece of the code. 40 | `init_code` a code used one time in the beginning. 41 | `action_code` use already inited object. 42 | `close_code` a part passed to the end of the code. 43 | `indent` means `action_code` and `close_code` should be under the indent. 44 | """ 45 | 46 | INIT_SNIPPET = 1 47 | ACTION_SNIPPET = 2 48 | 49 | def __init__(self, owner, init_code='', action_code='', close_code='', 50 | indent=False): 51 | 52 | if not init_code and not action_code and not close_code: 53 | raise SyntaxError("At least one of init_code, " 54 | "action_code, close_code should be passed.") 55 | self.init_code = init_code 56 | self.action_code = action_code 57 | self.close_code = close_code 58 | self.indent = indent 59 | self.owner = owner 60 | 61 | def update(self, init_code=None, action_code=None, close_code=None, 62 | indent=None): 63 | 64 | """ 65 | Update code. 66 | Updates only passed the args. 67 | To clear a code use empty line. For instance 68 | .update(init_code='new init code', 69 | action_code='') # Erase old action_code 70 | """ 71 | if init_code is not None: 72 | self.init_code = init_code 73 | 74 | if action_code is not None: 75 | self.action_code = action_code 76 | 77 | if close_code is not None: 78 | self.close_code = close_code 79 | 80 | if indent is not None: 81 | self.indent = indent 82 | 83 | @property 84 | def types(self): 85 | 86 | """ 87 | Return mask with INIT_SNIPPET and\or ACTION_SNIPPET flags 88 | """ 89 | 90 | mask = 0 91 | if self.init_code or self.close_code: 92 | mask |= self.INIT_SNIPPET 93 | if self.action_code: 94 | mask |= self.ACTION_SNIPPET 95 | return mask 96 | 97 | def __repr__(self): 98 | lines = [] 99 | for code in ("init_code: %s" % self.init_code, 100 | "action_code: %s" % self.action_code, 101 | "close_code: %s" % self.close_code): 102 | lines.append(code) 103 | lines.append('-'*40) 104 | return '\n'.join(lines) 105 | 106 | 107 | class CodeManager(object): 108 | 109 | """ 110 | Manages code snippets. Handles intent if needed and keeps `close_code` 111 | always at the end. 112 | Single instance. 113 | """ 114 | 115 | single_object = None 116 | inited = False 117 | 118 | def __new__(cls, *args, **kwargs): 119 | if cls.single_object is None: 120 | new = super(CodeManager, cls).__new__(cls, *args, **kwargs) 121 | cls.single_object = new 122 | return new 123 | else: 124 | return cls.single_object 125 | 126 | def __init__(self, indent_symbols=' '*4): 127 | if not self.inited: 128 | self.snippets = [] 129 | self.indent_symbols = indent_symbols 130 | 131 | self.inited = True 132 | 133 | def __len__(self): 134 | return len(self.snippets) 135 | 136 | def _line(self, code, indent_count=0): 137 | return "{indents}{code}".format( 138 | indents=self.indent_symbols * indent_count, 139 | code=code) 140 | 141 | def add(self, snippet): 142 | self.snippets.append(snippet) 143 | 144 | def clear(self): 145 | 146 | """ 147 | Safely clear all the snippents. Reset all the code counters. 148 | """ 149 | 150 | while self.snippets: 151 | self.clear_last() 152 | 153 | def clear_last(self): 154 | 155 | """ 156 | Remove the latest snippet, decrease appropriate code counter 157 | for snippets of INIT type. 158 | """ 159 | 160 | if self.snippets: 161 | last_snippet = self.snippets.pop() 162 | if last_snippet.types & CodeSnippet.INIT_SNIPPET: 163 | last_snippet.owner.release_variable() 164 | 165 | def get_full_code(self): 166 | 167 | """ 168 | Compose complete code from the snippets. 169 | """ 170 | 171 | lines = [] 172 | endings = [] 173 | indent_count = 0 174 | for snippet in self.snippets: 175 | if snippet.init_code: 176 | lines.append(self._line(snippet.init_code, indent_count)) 177 | 178 | if snippet.indent: 179 | # Add indent if needed. Notice the indent does not affect the 180 | # init_code in this iteration. 181 | indent_count += 1 182 | 183 | if snippet.action_code: 184 | lines.append(self._line(snippet.action_code, indent_count)) 185 | 186 | if snippet.close_code: 187 | endings.append(self._line(snippet.close_code, indent_count)) 188 | 189 | # Add the close_code codes. 190 | # Reverse the list for a close_code from the first snippet was passed 191 | # at the end of the code. 192 | if lines: 193 | full_code = "\n".join(lines) 194 | full_code += 2*"\n" 195 | full_code += "\n".join(endings[::-1]) 196 | else: 197 | full_code = "" 198 | return full_code 199 | 200 | def get_init_snippet(self, owner): 201 | 202 | """ 203 | Return the owner's the first INIT snippet. 204 | """ 205 | 206 | for snippet in self.snippets: 207 | if snippet.owner == owner and \ 208 | snippet.types & CodeSnippet.INIT_SNIPPET: 209 | return snippet 210 | else: 211 | return None 212 | 213 | def __repr__(self): 214 | return self.get_full_code() 215 | 216 | 217 | class CodeGenerator(object): 218 | 219 | """ 220 | Code generation behavior. Expect be used as one of base classes of the 221 | SWAPYObject's wrapper. 222 | """ 223 | 224 | code_manager = CodeManager() 225 | code_var_name = None # Default value, will be rewrote with composed 226 | # variable name as an instance attribute. 227 | 228 | code_var_counters = {} # Default value, will be rewrote as instance's 229 | # class attribute by get_code_id(cls) 230 | 231 | @classmethod 232 | def get_code_id(cls, var_prefix='default'): 233 | 234 | """ 235 | Increment code id. For example, the script already has 236 | `button1=...` line, so for a new button make `button2=... code`. 237 | The idea is the CodeGenerator's default value 238 | code_var_counters['var_prefix'] will be overwrote by this funk 239 | as a control's wrapper class(e.g Pwa_window) attribute. 240 | Its non default value will be shared for all the control's wrapper 241 | class(e.g Pwa_window) instances. 242 | """ 243 | 244 | if var_prefix not in cls.code_var_counters or \ 245 | cls.code_var_counters[var_prefix] == 0: 246 | cls.code_var_counters[var_prefix] = 1 247 | return '' # "app=..." instead of "app1=..." 248 | 249 | else: 250 | cls.code_var_counters[var_prefix] += 1 251 | return cls.code_var_counters[var_prefix] 252 | 253 | @classmethod 254 | def decrement_code_id(cls, var_prefix='default'): 255 | 256 | """ 257 | Decrement code id. 258 | """ 259 | 260 | cls.code_var_counters[var_prefix] -= 1 261 | 262 | def get_code_self(self): 263 | 264 | """ 265 | Composes code to access the control. 266 | E. g.: `button1 = calcframe1['Button12']` 267 | Pattern may use the next argument: 268 | * {var} 269 | * {parent_var} 270 | * {main_parent_var} 271 | E. g.: `"{var} = {parent_var}['access_name']\n"`. 272 | """ 273 | 274 | pattern = self._code_self 275 | if pattern: 276 | if self.code_var_name is None: 277 | self.code_var_name = self.code_var_pattern.format( 278 | id=self.get_code_id(self.code_var_pattern)) 279 | 280 | format_kwargs = {'var': self.code_var_name} 281 | try: 282 | main_parent = self.code_parents[0] 283 | except IndexError: 284 | main_parent = None 285 | 286 | if self.parent or main_parent: 287 | if self.parent: 288 | format_kwargs['parent_var'] = self.parent.code_var_name 289 | if main_parent: 290 | format_kwargs['main_parent_var'] = main_parent.code_var_name 291 | return pattern.format(**format_kwargs) 292 | return "" 293 | 294 | def get_code_action(self, action): 295 | 296 | """ 297 | Composes code to run an action. E. g.: `button1.Click()` 298 | Pattern may use the next argument: 299 | * {var} 300 | * {action} 301 | * {parent_var} 302 | * {main_parent_var} 303 | E. g.: `"{var}.{action}()\n"`. 304 | """ 305 | 306 | format_kwargs = {'var': self.code_var_name, 307 | 'action': action} 308 | if self.parent: 309 | format_kwargs['parent_var'] = self.parent.code_var_name 310 | 311 | try: 312 | main_parent = self.code_parents[0] 313 | except IndexError: 314 | main_parent = None 315 | 316 | if main_parent: 317 | format_kwargs['main_parent_var'] = main_parent.code_var_name 318 | 319 | return self._code_action.format(**format_kwargs) 320 | 321 | def get_code_close(self): 322 | 323 | """ 324 | Composes code to close the access to the control. E.g.: `app.Kill_()` 325 | Pattern may use the next argument: 326 | * {var} 327 | * {parent_var} 328 | * {main_parent_var} 329 | E. g.: `"{var}.Kill_()\n"`. 330 | """ 331 | 332 | pattern = self._code_close 333 | if pattern: 334 | format_kwargs = {'var': self.code_var_name} 335 | if self.parent: 336 | format_kwargs['parent_var'] = self.parent.code_var_name 337 | 338 | try: 339 | main_parent = self.code_parents[0] 340 | except IndexError: 341 | main_parent = None 342 | 343 | if main_parent: 344 | format_kwargs['main_parent_var'] = main_parent.code_var_name 345 | 346 | return pattern.format(**format_kwargs) 347 | return "" 348 | 349 | def Get_code(self, action=None): 350 | 351 | """ 352 | Return all the code needed to make the action on the control. 353 | Walk parents if needed. 354 | """ 355 | 356 | if not self._check_existence(): # target does not exist 357 | raise Exception("Target object does not exist") 358 | 359 | if self.code_var_name is None: 360 | # parent/s code is not inited 361 | code_parents = self.code_parents[:] 362 | code_parents.reverse() # start from the top level parent 363 | 364 | for p in code_parents: 365 | if not p.code_var_name: 366 | p_code_self = p.get_code_self() 367 | p_close_code = p.get_code_close() 368 | if p_code_self or p_close_code: 369 | parent_snippet = CodeSnippet(p, 370 | init_code=p_code_self, 371 | close_code=p_close_code) 372 | self.code_manager.add(parent_snippet) 373 | 374 | own_code_self = self.get_code_self() 375 | own_close_code = self.get_code_close() 376 | # get_code_action call should be after the get_code_self call 377 | own_code_action = self.get_code_action(action) if action else '' 378 | 379 | if own_code_self or own_close_code or own_code_action: 380 | own_snippet = CodeSnippet(self, 381 | init_code=own_code_self, 382 | action_code=own_code_action, 383 | close_code=own_close_code) 384 | self.code_manager.add(own_snippet) 385 | else: 386 | # Already inited (all parents too), may use get_code_action 387 | own_code_action = self.get_code_action(action) if action else '' 388 | if own_code_action: 389 | new_action_snippet = CodeSnippet(self, 390 | action_code=own_code_action) 391 | self.code_manager.add(new_action_snippet) 392 | 393 | return self.code_manager.get_full_code() 394 | 395 | def update_code_style(self): 396 | 397 | """ 398 | Seeks for the first INIT snippet and update 399 | `init_code` and `close_code`. 400 | """ 401 | 402 | init_code_snippet = self.code_manager.get_init_snippet(self) 403 | if init_code_snippet: 404 | own_code_self = self.get_code_self() 405 | own_close_code = self.get_code_close() 406 | if own_code_self or own_close_code: 407 | init_code_snippet.update(init_code=own_code_self, 408 | close_code=own_close_code) 409 | 410 | def release_variable(self): 411 | 412 | """ 413 | Clear the access variable to mark the object is not inited and 414 | make possible other use the variable name. 415 | """ 416 | 417 | if self.code_var_name: 418 | self.code_var_name = None 419 | self.decrement_code_id(self.code_var_pattern) 420 | 421 | 422 | if __name__ == '__main__': 423 | c1 = CodeSnippet(None, 'with Start_ as app:', 'frame1 = app.Frame', '', 424 | True) 425 | c2 = CodeSnippet(None, 'button1 = frame1.button', 'button1.Click()', 426 | 'del button1') 427 | c3 = CodeSnippet(None, 'button2 = frame1.button', 'button2.Click()') 428 | c4 = CodeSnippet(None, 'with Start_ as app:', 'frame2 = app.Frame', '', 429 | True) 430 | c5 = CodeSnippet(None, '', 'button1.Click()') 431 | cm = CodeManager() 432 | 433 | print c1 434 | [cm.add(i) for i in [c1, c2, c3, c4, c5]] 435 | 436 | print id(c2) 437 | print cm 438 | 439 | c2.update('button1 = the_change', 'the_change.Click()', 440 | 'del the_change') 441 | print id(c2) 442 | print cm 443 | -------------------------------------------------------------------------------- /const.py: -------------------------------------------------------------------------------- 1 | # GUI object/properties browser. 2 | # Copyright (C) 2011 Matiychuk D. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public License 6 | # as published by the Free Software Foundation; either version 2.1 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | # See the GNU Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library; if not, write to the 16 | # Free Software Foundation, Inc., 17 | # 59 Temple Place, 18 | # Suite 330, 19 | # Boston, MA 02111-1307 USA 20 | 21 | 22 | ACTIONS = {101: 'Close', 23 | 102: 'Click', 24 | 103: 'ClickInput', 25 | 104: 'CloseClick', 26 | 105: 'DoubleClick', 27 | 106: 'DoubleClickInput', 28 | 107: 'DragMouse', 29 | 108: 'DrawOutline', 30 | 109: 'Maximize', 31 | 110: 'Minimize', 32 | 111: 'MoveMouse', 33 | 112: 'MoveWindow', 34 | 113: 'PressMouse', 35 | 114: 'PressMouseInput', 36 | 115: 'ReleaseMouse', 37 | 116: 'ReleaseMouseInput', 38 | 117: 'Restore', 39 | 118: 'RightClick', 40 | 119: 'RightClickInput', 41 | 120: 'SetFocus', 42 | 121: 'Select', 43 | 122: 'Collapse', 44 | 123: 'Expand', 45 | } 46 | 47 | EXTENDED_ACTIONS = {201: 'Application.Start', 48 | 202: 'Application.Connect', 49 | } 50 | 51 | PROPERTIES_ACTIONS = {301: 'Copy all', 52 | 302: None, 53 | 303: 'Copy property', 54 | 304: 'Copy value', 55 | 305: 'Copy unicode value', 56 | } 57 | 58 | EDITOR_ACTIONS = {401: 'Clear last command', 59 | 402: 'Clear the code', 60 | 403: None, 61 | 404: 'Copy', 62 | 405: 'Select all', 63 | 406: None, 64 | 407: 'Save code to file'} 65 | 66 | VERSION = '0.4.8' 67 | -------------------------------------------------------------------------------- /deps/wxPython-3.0.2.0-cp27-none-win32.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pywinauto/SWAPY/8ac9b53ac2b3fa670dbbb125395fcfbbc476ae5a/deps/wxPython-3.0.2.0-cp27-none-win32.whl -------------------------------------------------------------------------------- /deps/wxPython-3.0.2.0-cp27-none-win_amd64.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pywinauto/SWAPY/8ac9b53ac2b3fa670dbbb125395fcfbbc476ae5a/deps/wxPython-3.0.2.0-cp27-none-win_amd64.whl -------------------------------------------------------------------------------- /deps/wxPython_common-3.0.2.0-py2-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pywinauto/SWAPY/8ac9b53ac2b3fa670dbbb125395fcfbbc476ae5a/deps/wxPython_common-3.0.2.0-py2-none-any.whl -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | pywinauto 2 | Pillow==2.7.0 3 | PyInstaller==2.1 4 | -------------------------------------------------------------------------------- /proxy.py: -------------------------------------------------------------------------------- 1 | # GUI object/properties browser. 2 | # Copyright (C) 2011 Matiychuk D. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public License 6 | # as published by the Free Software Foundation; either version 2.1 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | # See the GNU Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library; if not, write to the 16 | # Free Software Foundation, Inc., 17 | # 59 Temple Place, 18 | # Suite 330, 19 | # Boston, MA 02111-1307 USA 20 | 21 | import exceptions 22 | import platform 23 | import os 24 | import sys 25 | import string 26 | import time 27 | import thread 28 | import warnings 29 | 30 | import pywinauto 31 | 32 | from code_manager import CodeGenerator, check_valid_identifier 33 | from const import * 34 | 35 | ''' 36 | proxy module for pywinauto 37 | ''' 38 | 39 | 40 | pywinauto.timings.Timings.window_find_timeout = 1 41 | 42 | 43 | def resource_path(filename): 44 | if hasattr(sys, '_MEIPASS'): 45 | # PyInstaller >= 1.6 46 | ###os.chdir(sys._MEIPASS) 47 | filename = os.path.join(sys._MEIPASS, filename) 48 | elif '_MEIPASS2' in os.environ: 49 | # PyInstaller < 1.6 (tested on 1.5 only) 50 | ###os.chdir(os.environ['_MEIPASS2']) 51 | filename = os.path.join(os.environ['_MEIPASS2'], filename) 52 | else: 53 | ###os.chdir(sys.path.dirname(sys.argv[0])) 54 | filename = os.path.join(os.path.dirname(sys.argv[0]), filename) 55 | return filename 56 | 57 | 58 | class PwaWrapper(object): 59 | 60 | """ 61 | Base proxy class for pywinauto objects. 62 | """ 63 | 64 | def __init__(self, pwa_obj, parent=None): 65 | ''' 66 | Constructor 67 | ''' 68 | #original pywinauto object 69 | self.pwa_obj = pwa_obj 70 | self.parent = parent 71 | default_sort_key = lambda name: name[0].lower() 72 | self.subitems_sort_key = default_sort_key 73 | 74 | def GetProperties(self): 75 | ''' 76 | Return dict of original + additional properties 77 | Can be overridden for non pywinauto objects 78 | ''' 79 | properties = {} 80 | properties.update(self._get_properties()) 81 | properties.update(self._get_additional_properties()) 82 | return properties 83 | 84 | def Get_subitems(self): 85 | ''' 86 | Return list of children - [(control_text, swapy_obj),...] 87 | Can be overridden for non pywinauto objects 88 | ''' 89 | subitems = [] 90 | 91 | subitems += self._get_children() 92 | subitems += self._get_additional_children() 93 | 94 | subitems.sort(key=self.subitems_sort_key) 95 | #encode names 96 | subitems_encoded = [] 97 | for (name, obj) in subitems: 98 | #name = name.encode('cp1251', 'replace') 99 | subitems_encoded.append((name, obj)) 100 | return subitems_encoded 101 | 102 | def Exec_action(self, action): 103 | ''' 104 | Execute action on the control 105 | ''' 106 | #print('self.pwa_obj.'+action+'()') 107 | exec('self.pwa_obj.'+action+'()') 108 | return 0 109 | 110 | def Get_actions(self): 111 | 112 | """ 113 | return allowed actions for this object. [(id,action_name),...] 114 | """ 115 | 116 | allowed_actions = [] 117 | try: 118 | obj_actions = dir(self.pwa_obj.WrapperObject()) 119 | except: 120 | obj_actions = dir(self.pwa_obj) 121 | for _id, action in ACTIONS.items(): 122 | if action in obj_actions: 123 | allowed_actions.append((_id, action)) 124 | allowed_actions.sort(key=lambda name: name[1].lower()) 125 | return allowed_actions 126 | 127 | def Get_extended_actions(self): 128 | 129 | """ 130 | Extended actions 131 | """ 132 | 133 | return [] 134 | 135 | def Highlight_control(self): 136 | if self._check_visibility(): 137 | thread.start_new_thread(self._highlight_control,(3,)) 138 | return 0 139 | 140 | def _get_properties(self): 141 | ''' 142 | Get original pywinauto's object properties 143 | ''' 144 | #print type(self.pwa_obj) 145 | try: 146 | properties = self.pwa_obj.GetProperties() 147 | except exceptions.RuntimeError: 148 | properties = {} #workaround 149 | return properties 150 | 151 | def _get_additional_properties(self): 152 | 153 | """ 154 | Get additional useful properties, like a handle, process ID, etc. 155 | Can be overridden by derived class 156 | """ 157 | 158 | additional_properties = {} 159 | 160 | #-----Access names 161 | access_names = [name for name, obj in self.__get_uniq_names(target_control=self.pwa_obj)] 162 | if access_names: 163 | additional_properties.update({'Access names' : access_names}) 164 | #----- 165 | 166 | #-----pwa_type 167 | additional_properties.update({'pwa_type' : str(type(self.pwa_obj))}) 168 | #--- 169 | 170 | #-----handle 171 | try: 172 | additional_properties.update({'handle' : str(self.pwa_obj.handle)}) 173 | except: 174 | pass 175 | #--- 176 | return additional_properties 177 | 178 | def _get_children(self): 179 | 180 | """ 181 | Return original pywinauto's object children & names 182 | [(control_text, swapy_obj),...] 183 | """ 184 | 185 | if self.pwa_obj.Parent() and isinstance(self.parent, Pwa_window): 186 | # Hide children of the non top level window control. 187 | # Expect all the children are accessible from the top level window. 188 | return [] 189 | 190 | u_names = None 191 | children = [] 192 | children_controls = self.pwa_obj.Children() 193 | for child_control in children_controls: 194 | try: 195 | texts = child_control.Texts() 196 | except exceptions.WindowsError: 197 | # texts = ['Unknown control name2!'] #workaround for 198 | # WindowsError: [Error 0] ... 199 | texts = None 200 | except exceptions.RuntimeError: 201 | # texts = ['Unknown control name3!'] #workaround for 202 | # RuntimeError: GetButtonInfo failed for button 203 | # with command id 256 204 | texts = None 205 | 206 | if texts: 207 | texts = filter(bool, texts) # filter out '' and None items 208 | 209 | if texts: # check again after the filtering 210 | title = ', '.join(texts) 211 | else: 212 | # .Texts() does not have a useful title, trying get it 213 | # from the uniqnames 214 | if u_names is None: 215 | # init unames list 216 | u_names = self.__get_uniq_names() 217 | 218 | child_uniq_name = [u_name for u_name, obj in u_names 219 | if obj.WrapperObject() == child_control] 220 | 221 | if child_uniq_name: 222 | title = child_uniq_name[-1] 223 | else: 224 | # uniqnames has no useful title 225 | title = 'Unknown control name1!' 226 | children.append((title, self._get_swapy_object(child_control))) 227 | 228 | return children 229 | 230 | def _get_additional_children(self): 231 | ''' 232 | Get additional children, like for a menu, submenu, subtab, etc. 233 | Should be overridden in derived classes of non standard pywinauto object 234 | ''' 235 | return [] 236 | 237 | def _get_pywinobj_type(self, obj): 238 | ''' 239 | Check self pywinauto object type 240 | ''' 241 | if type(obj) == pywinauto.application.WindowSpecification: 242 | return 'window' 243 | elif type(obj) == pywinauto.controls.menuwrapper.Menu: 244 | return 'menu' 245 | elif type(obj) == pywinauto.controls.menuwrapper.MenuItem: 246 | return 'menu_item' 247 | elif type(obj) == pywinauto.controls.win32_controls.ComboBoxWrapper: 248 | return 'combobox' 249 | elif type(obj) == pywinauto.controls.win32_controls.ListBoxWrapper: 250 | return 'listbox' 251 | elif type(obj) == pywinauto.controls.common_controls.ListViewWrapper: 252 | return 'listview' 253 | elif type(obj) == pywinauto.controls.common_controls.TabControlWrapper: 254 | return 'tab' 255 | elif type(obj) == pywinauto.controls.common_controls.ToolbarWrapper: 256 | return 'toolbar' 257 | elif type(obj) == pywinauto.controls.common_controls._toolbar_button: 258 | return 'toolbar_button' 259 | elif type(obj) == pywinauto.controls.common_controls.TreeViewWrapper: 260 | return 'tree_view' 261 | elif type(obj) == pywinauto.controls.common_controls._treeview_element: 262 | return 'tree_item' 263 | else: 264 | return 'unknown' 265 | 266 | def _get_swapy_object(self, pwa_obj): 267 | pwa_type = self._get_pywinobj_type(pwa_obj) 268 | #print pwa_type 269 | if pwa_type == 'window': 270 | process = Process(self, pwa_obj.ProcessID()) 271 | return Pwa_window(pwa_obj, process) 272 | if pwa_type == 'menu': 273 | return Pwa_menu(pwa_obj, self) 274 | if pwa_type == 'menu_item': 275 | return Pwa_menu_item(pwa_obj, self) 276 | if pwa_type == 'combobox': 277 | return Pwa_combobox(pwa_obj, self) 278 | if pwa_type == 'listbox': 279 | return Pwa_listbox(pwa_obj, self) 280 | if pwa_type == 'listview': 281 | return Pwa_listview(pwa_obj, self) 282 | if pwa_type == 'tab': 283 | return Pwa_tab(pwa_obj, self) 284 | if pwa_type == 'toolbar': 285 | return Pwa_toolbar(pwa_obj, self) 286 | if pwa_type == 'toolbar_button': 287 | return Pwa_toolbar_button(pwa_obj, self) 288 | if pwa_type == 'tree_view': 289 | return Pwa_tree(pwa_obj, self) 290 | if pwa_type == 'tree_item': 291 | return Pwa_tree_item(pwa_obj, self) 292 | else: 293 | return SWAPYObject(pwa_obj, self) 294 | 295 | def _highlight_control(self, repeat = 1): 296 | while repeat > 0: 297 | repeat -= 1 298 | self.pwa_obj.DrawOutline('red', thickness=1) 299 | time.sleep(0.3) 300 | self.pwa_obj.DrawOutline(colour=0xffffff, thickness=1) 301 | time.sleep(0.2) 302 | return 0 303 | 304 | def _check_visibility(self): 305 | ''' 306 | Check control/window visibility. 307 | Return pwa.IsVisible() or False if fails 308 | ''' 309 | is_visible = False 310 | try: 311 | is_visible = self.pwa_obj.IsVisible() 312 | except: 313 | pass 314 | return is_visible 315 | 316 | def _check_actionable(self): 317 | ''' 318 | Check control/window Actionable. 319 | Return True or False if fails 320 | ''' 321 | try: 322 | self.pwa_obj.VerifyActionable() 323 | except: 324 | is_actionable = False 325 | else: 326 | is_actionable = True 327 | return is_actionable 328 | 329 | def _check_existence(self): 330 | ''' 331 | Check control/window Exists. 332 | Return True or False if fails 333 | ''' 334 | 335 | try: 336 | handle_ = self.pwa_obj.handle 337 | obj = pywinauto.application.WindowSpecification({'handle': handle_}) 338 | except: 339 | is_exist = False 340 | else: 341 | is_exist = obj.Exists() 342 | return is_exist 343 | 344 | def __get_uniq_names(self, target_control=None): 345 | 346 | """ 347 | Return uniq_names of the control 348 | [(uniq_name, obj), ] 349 | If target_control specified, apply additional filtering for obj == target_control 350 | """ 351 | 352 | # TODO: cache this method 353 | 354 | pwa_app = pywinauto.application.Application() # TODO: do not call .Application() everywhere. 355 | 356 | try: 357 | parent_obj = self.pwa_obj.TopLevelParent() 358 | except pywinauto.controls.HwndWrapper.InvalidWindowHandle: 359 | #For non visible windows 360 | #... 361 | #InvalidWindowHandle: Handle 0x262710 is not a valid window handle 362 | parent_obj = self.pwa_obj 363 | except AttributeError: 364 | return [] 365 | 366 | visible_controls = [pwa_app.window_(handle=ch) for ch in 367 | pywinauto.findwindows.find_windows(parent=parent_obj.handle, top_level_only=False)] 368 | uniq_names_obj = [(uniq_name, obj) for uniq_name, obj 369 | in pywinauto.findbestmatch.build_unique_dict(visible_controls).items() 370 | if uniq_name != '' and (not target_control or obj.WrapperObject() == target_control)] 371 | return sorted(uniq_names_obj, key=lambda name_obj: len(name_obj[0])) # sort by name 372 | 373 | 374 | class SWAPYObject(PwaWrapper, CodeGenerator): 375 | 376 | """ 377 | Mix the pywinauto wrapper and the code generator 378 | """ 379 | 380 | code_self_pattern_attr = "{var} = {parent_var}.{access_name}" 381 | code_self_pattern_item = "{var} = {parent_var}[{access_name}]" 382 | code_action_pattern = "{var}.{action}()" 383 | main_parent_type = None 384 | short_name = 'control' 385 | __code_var_pattern = None # cached value, to access even if the pwa 386 | # object was closed 387 | 388 | def __init__(self, *args, **kwargs): 389 | super(SWAPYObject, self).__init__(*args, **kwargs) 390 | self.code_parents = self.get_code_parents() 391 | 392 | def get_code_parents(self): 393 | 394 | """ 395 | Collect a list of all parents needed to access the control. 396 | Some parents may be excluded regarding to the `self.main_parent_type` parameter. 397 | """ 398 | 399 | grab_all = True if not self.main_parent_type else False 400 | code_parents = [] 401 | parent = self.parent 402 | while parent: 403 | if not grab_all and isinstance(parent, self.main_parent_type): 404 | grab_all = True 405 | 406 | if grab_all: 407 | code_parents.append(parent) 408 | parent = parent.parent 409 | return code_parents 410 | 411 | @property 412 | def _code_self(self): 413 | 414 | """ 415 | Default _code_self. 416 | """ 417 | #print self._get_additional_properties() 418 | access_name = self.GetProperties()['Access names'][0] 419 | 420 | if check_valid_identifier(access_name): 421 | # A valid identifier 422 | code = self.code_self_pattern_attr.format( 423 | access_name=access_name, parent_var="{parent_var}", 424 | var="{var}") 425 | else: 426 | #Not valid, encode and use as app's item. 427 | if isinstance(access_name, unicode): 428 | access_name = "u'%s'" % access_name.encode('unicode-escape') 429 | elif isinstance(access_name, str): 430 | access_name = "'%s'" % access_name 431 | code = self.code_self_pattern_item.format( 432 | access_name=access_name, parent_var="{parent_var}", 433 | var="{var}") 434 | return code 435 | 436 | @property 437 | def _code_action(self): 438 | 439 | """ 440 | Default _code_action. 441 | """ 442 | code = self.code_action_pattern 443 | return code 444 | 445 | @property 446 | def _code_close(self): 447 | 448 | """ 449 | Default _code_close. 450 | """ 451 | return "" 452 | 453 | @property 454 | def code_var_pattern(self): 455 | 456 | """ 457 | Compose variable prefix, based on the control Class or 458 | short name of the SWAPY wrapper class. 459 | """ 460 | 461 | if self.__code_var_pattern is None: 462 | var_prefix = self.short_name 463 | if 'Class' in self.GetProperties(): 464 | crtl_class = filter(lambda c: c in string.ascii_letters, 465 | self.GetProperties()['Class']).lower() 466 | if crtl_class: 467 | var_prefix = crtl_class 468 | 469 | self.__code_var_pattern = "{var_prefix}{id}".format( 470 | var_prefix=var_prefix, id="{id}") 471 | return self.__code_var_pattern 472 | 473 | def SetCodestyle(self, extended_action_id): 474 | 475 | """ 476 | Switch a control code style regarding extended_action_id 477 | """ 478 | 479 | pass 480 | 481 | 482 | class VirtualSWAPYObject(SWAPYObject): 483 | def __init__(self, parent, index): 484 | self.parent = parent 485 | self.index = index 486 | self.pwa_obj = self 487 | self._check_visibility = self.parent._check_visibility 488 | self._check_actionable = self.parent._check_actionable 489 | self._check_existence = self.parent._check_existence 490 | self.code_parents = self.get_code_parents() 491 | 492 | code_action_pattern = "{parent_var}.{action}({index})" 493 | 494 | @property 495 | def _code_self(self): 496 | 497 | """ 498 | Rewrite default behavior. 499 | """ 500 | return "" 501 | 502 | @property 503 | def _code_action(self): 504 | index = self.index 505 | if isinstance(index, unicode): 506 | index = "u'%s'" % index.encode('unicode-escape') 507 | elif isinstance(index, str): 508 | index = "'%s'" % index 509 | code = self.code_action_pattern.format(index=index, 510 | action="{action}", 511 | var="{var}", 512 | parent_var="{parent_var}") 513 | return code 514 | 515 | @property 516 | def code_var_pattern(self): 517 | raise Exception('Must not be used "code_var_pattern" prop for a VirtualSWAPYObject') 518 | 519 | def Select(self): 520 | self.parent.pwa_obj.Select(self.index) 521 | 522 | def _get_properties(self): 523 | return {} 524 | 525 | def Get_subitems(self): 526 | return [] 527 | 528 | def Highlight_control(self): 529 | pass 530 | return 0 531 | 532 | 533 | class PC_system(SWAPYObject): 534 | handle = 0 535 | short_name = 'pc' # hope it never be used in the code generator 536 | 537 | single_object = None 538 | inited = False 539 | 540 | def __new__(cls, *args, **kwargs): 541 | if cls.single_object is None: 542 | new = super(PC_system, cls).__new__(cls, *args, **kwargs) 543 | cls.single_object = new 544 | return new 545 | else: 546 | return cls.single_object 547 | 548 | def __init__(self, *args, **kwargs): 549 | if not self.inited: 550 | super(PC_system, self).__init__(*args, **kwargs) 551 | self.inited = True 552 | 553 | @property 554 | def _code_self(self): 555 | # code = self.code_self_pattern.format(var="{var}") 556 | # return code 557 | return "from pywinauto.application import Application" 558 | # 559 | # @property 560 | # def code_var_pattern(self): 561 | # return "app{id}".format(id="{id}") 562 | 563 | def Get_subitems(self): 564 | ''' 565 | returns [(window_text, swapy_obj),...] 566 | ''' 567 | #windows-------------------- 568 | windows = [] 569 | try_count = 3 570 | app = pywinauto.application.Application() 571 | for i in range(try_count): 572 | try: 573 | handles = pywinauto.findwindows.find_windows() 574 | except exceptions.OverflowError: # workaround for OverflowError: array too large 575 | time.sleep(1) 576 | except exceptions.MemoryError:# workaround for MemoryError 577 | time.sleep(1) 578 | else: 579 | break 580 | else: 581 | #TODO: add swapy exception: Could not get windows list 582 | handles = [] 583 | #we have to find taskbar in windows list 584 | warnings.filterwarnings("ignore", category=FutureWarning) #ignore future warning in taskbar module 585 | from pywinauto import taskbar 586 | taskbar_handle = taskbar.TaskBarHandle() 587 | for w_handle in handles: 588 | wind = app.window_(handle=w_handle) 589 | if w_handle == taskbar_handle: 590 | title = 'TaskBar' 591 | else: 592 | texts = wind.Texts() 593 | texts = filter(bool, texts) # filter out '' and None items 594 | if not texts: 595 | title = 'Window#%s' % w_handle 596 | else: 597 | title = ', '.join(texts) 598 | windows.append((title, self._get_swapy_object(wind))) 599 | windows.sort(key=lambda name: name[0].lower()) 600 | #----------------------- 601 | 602 | #smt new---------------- 603 | #------------------------ 604 | return windows 605 | 606 | def _get_properties(self): 607 | info = {'Platform': platform.platform(), 608 | 'Processor': platform.processor(), 609 | 'PC name': platform.node()} 610 | return info 611 | 612 | def Get_actions(self): 613 | ''' 614 | No actions for PC_system 615 | ''' 616 | return [] 617 | 618 | def Highlight_control(self): 619 | pass 620 | return 0 621 | 622 | def _check_visibility(self): 623 | return True 624 | 625 | def _check_actionable(self): 626 | return True 627 | 628 | def _check_existence(self): 629 | return True 630 | 631 | 632 | class Process(CodeGenerator): 633 | 634 | """ 635 | Virtual parent for window objects. 636 | It will never be shown in the object browser. Used to hold 'app' counter 637 | independent of 'window' counters. 638 | """ 639 | processes = {} 640 | inited = False 641 | main_window = None 642 | 643 | def __new__(cls, parent, pid): 644 | if pid in cls.processes: 645 | return cls.processes[pid] 646 | else: 647 | new_process = super(Process, cls).__new__(cls, parent, pid) 648 | cls.processes[pid] = new_process 649 | return new_process 650 | 651 | def __init__(self, parent, pid): 652 | if not self.inited: 653 | self.parent = parent 654 | self._var_name = None 655 | 656 | self.inited = True 657 | 658 | @property 659 | def _code_self(self): 660 | return "" 661 | 662 | @property 663 | def _code_action(self): 664 | return "" 665 | 666 | @property 667 | def _code_close(self): 668 | return "" 669 | 670 | @property 671 | def code_var_pattern(self): 672 | return "{var_prefix}{id}".format(var_prefix='app', id="{id}") 673 | 674 | @property 675 | def code_var_name(self): 676 | if self._var_name is None: 677 | self._var_name = self.code_var_pattern.format( 678 | id=self.get_code_id(self.code_var_pattern)) 679 | return self._var_name 680 | 681 | 682 | class Pwa_window(SWAPYObject): 683 | code_self_close = "{parent_var}.Kill_()" 684 | short_name = 'window' 685 | 686 | handles = {} 687 | inited = False 688 | 689 | def __new__(cls, pwa_obj, parent=None): 690 | if pwa_obj.handle in cls.handles: 691 | return cls.handles[pwa_obj.handle] 692 | else: 693 | new_window = super(Pwa_window, cls).__new__(cls, pwa_obj, 694 | parent=None) 695 | cls.handles[pwa_obj.handle] = new_window 696 | return new_window 697 | 698 | def __init__(self, *args, **kwargs): 699 | if not self.inited: 700 | # Set default style 701 | self.code_self_style = self.__code_self_start 702 | self.code_close_style = self.__code_close_start 703 | super(Pwa_window, self).__init__(*args, **kwargs) 704 | 705 | self.inited = True 706 | 707 | def __code_self_connect(self): 708 | title = self.pwa_obj.WindowText().encode('unicode-escape') 709 | cls_name = self.pwa_obj.Class() 710 | code = "\n{parent_var} = Application().Connect(title=u'{title}', " \ 711 | "class_name='{cls_name}')\n".format(title=title, 712 | cls_name=cls_name, 713 | parent_var="{parent_var}") 714 | return code 715 | 716 | def __code_self_start(self): 717 | target_pid = self.pwa_obj.ProcessID() 718 | cmd_line = None 719 | process_modules = pywinauto.application._process_get_modules_wmi() 720 | for pid, name, process_cmdline in process_modules: 721 | if pid == target_pid: 722 | cmd_line = os.path.normpath(process_cmdline) 723 | cmd_line = cmd_line.encode('unicode-escape') 724 | break 725 | code = "\n{parent_var} = Application().Start(cmd_line=u'{cmd_line}')\n"\ 726 | .format(cmd_line=cmd_line, parent_var="{parent_var}") 727 | return code 728 | 729 | def __code_close_connect(self): 730 | return "" 731 | 732 | def __code_close_start(self): 733 | return self.code_self_close.format(parent_var="{parent_var}") 734 | 735 | @property 736 | def _code_self(self): 737 | code = "" 738 | if not self._get_additional_properties()['Access names']: 739 | raise NotImplementedError 740 | else: 741 | is_main_window = bool(self.parent.main_window is None or 742 | self.parent.main_window == self or 743 | self.parent.main_window.code_var_name is None) 744 | 745 | if is_main_window: 746 | code += self.code_self_style() 747 | self.parent.main_window = self 748 | code += super(Pwa_window, self)._code_self 749 | if is_main_window and \ 750 | self.code_self_style == self.__code_self_start: 751 | code += "\n{var}.Wait('ready')" 752 | self.parent.main_window = self 753 | 754 | return code 755 | 756 | @property 757 | def _code_close(self): 758 | 759 | """ 760 | Rewrite default behavior. 761 | """ 762 | code = "" 763 | is_main_window = bool(self.parent.main_window is None or 764 | self.parent.main_window == self or 765 | self.parent.main_window.code_var_name is None) 766 | if is_main_window: 767 | code = self.code_close_style() 768 | 769 | return code 770 | 771 | def _get_additional_children(self): 772 | ''' 773 | Add menu object as children 774 | ''' 775 | additional_children = [] 776 | menu = self.pwa_obj.Menu() 777 | if menu: 778 | menu_child = [('!Menu', self._get_swapy_object(menu))] 779 | additional_children += menu_child 780 | return additional_children 781 | 782 | def _get_additional_properties(self): 783 | ''' 784 | Get additional useful properties, like a handle, process ID, etc. 785 | Can be overridden by derived class 786 | ''' 787 | additional_properties = {} 788 | pwa_app = pywinauto.application.Application() 789 | #-----Access names 790 | 791 | access_names = [name for name in pywinauto.findbestmatch.build_unique_dict([self.pwa_obj]).keys() if name != ''] 792 | access_names.sort(key=len) 793 | additional_properties.update({'Access names': access_names}) 794 | #----- 795 | 796 | #-----pwa_type 797 | additional_properties.update({'pwa_type': str(type(self.pwa_obj))}) 798 | #--- 799 | 800 | #-----handle 801 | try: 802 | additional_properties.update({'handle': str(self.pwa_obj.handle)}) 803 | except: 804 | pass 805 | #--- 806 | return additional_properties 807 | 808 | def Get_extended_actions(self): 809 | 810 | """ 811 | Extended actions 812 | """ 813 | 814 | return [(_id, action) for _id, action in EXTENDED_ACTIONS.items()] 815 | 816 | def SetCodestyle(self, extended_action_id): 817 | 818 | """ 819 | Switch to `Start` or `Connect` code 820 | """ 821 | 822 | if 'Application.Start' == EXTENDED_ACTIONS[extended_action_id]: 823 | self.code_self_style = self.__code_self_start 824 | self.code_close_style = self.__code_close_start 825 | 826 | elif 'Application.Connect' == EXTENDED_ACTIONS[extended_action_id]: 827 | self.code_self_style = self.__code_self_connect 828 | self.code_close_style = self.__code_close_connect 829 | 830 | else: 831 | raise RuntimeError("Unknown menu id - %s" % extended_action_id) 832 | 833 | # if self.code_snippet is not None: 834 | # # Refresh self code after the changing of the code style 835 | # own_code_self = self.get_code_self() 836 | # own_close_code = self.get_code_close() 837 | # self.code_snippet.update(init_code=own_code_self, 838 | # close_code=own_close_code) 839 | 840 | self.update_code_style() 841 | 842 | def release_variable(self): 843 | super(Pwa_window, self).release_variable() 844 | if self.parent._var_name: 845 | self.parent._var_name = None 846 | self.parent.decrement_code_id(self.parent.code_var_pattern) 847 | 848 | 849 | class Pwa_menu(SWAPYObject): 850 | 851 | short_name = 'menu' 852 | 853 | def _check_visibility(self): 854 | is_visible = False 855 | try: 856 | is_visible = self.pwa_obj.ctrl.IsVisible() 857 | except AttributeError: 858 | pass 859 | return is_visible 860 | 861 | def _check_actionable(self): 862 | if self.pwa_obj.accessible: 863 | return True 864 | else: 865 | return False 866 | 867 | def _check_existence(self): 868 | try: 869 | self.pwa_obj.ctrl.handle 870 | except: 871 | return False 872 | else: 873 | return True 874 | 875 | def _get_additional_children(self): 876 | ''' 877 | Add submenu object as children 878 | ''' 879 | #print(dir(self.pwa_obj)) 880 | #print(self.pwa_obj.is_main_menu) 881 | #print(self.pwa_obj.owner_item) 882 | 883 | self.subitems_sort_key = lambda obj: obj[1].pwa_obj.Index() #sorts items by indexes 884 | 885 | if not self.pwa_obj.accessible: 886 | return [] 887 | 888 | additional_children = [] 889 | menu_items = self.pwa_obj.Items() 890 | for menu_item in menu_items: 891 | item_text = menu_item.Text() 892 | if not item_text: 893 | if menu_item.Type() == 2048: 894 | item_text = '-----Separator-----' 895 | else: 896 | item_text = 'Index: %d' % menu_item.Index() 897 | menu_item_child = [(item_text, self._get_swapy_object(menu_item))] 898 | additional_children += menu_item_child 899 | return additional_children 900 | 901 | def _get_children(self): 902 | ''' 903 | Return original pywinauto's object children 904 | 905 | ''' 906 | return [] 907 | 908 | def Highlight_control(self): 909 | pass 910 | return 0 911 | 912 | 913 | class Pwa_menu_item(Pwa_menu): 914 | 915 | short_name = 'menu_item' 916 | 917 | main_parent_type = Pwa_window 918 | code_self_pattern = "{var} = {main_parent_var}.MenuItem(u'{menu_path}')" 919 | 920 | @property 921 | def _code_self(self): 922 | menu_path = self.get_menuitems_path().encode('unicode-escape') 923 | code = self.code_self_pattern.format( 924 | menu_path=menu_path, main_parent_var="{main_parent_var}", 925 | var="{var}") 926 | return code 927 | 928 | def _check_actionable(self): 929 | if self.pwa_obj.State() == 3: #grayed 930 | is_actionable = False 931 | else: 932 | is_actionable = True 933 | return is_actionable 934 | 935 | def _get_additional_children(self): 936 | ''' 937 | Add submenu object as children 938 | ''' 939 | #print(dir(self.pwa_obj)) 940 | #print(self.pwa_obj.menu) 941 | #print self.get_menuitems_path() 942 | 943 | additional_children = [] 944 | submenu = self.pwa_obj.SubMenu() 945 | if submenu: 946 | submenu_child = [(self.pwa_obj.Text()+' submenu', self._get_swapy_object(submenu))] 947 | additional_children += submenu_child 948 | return additional_children 949 | 950 | def get_menuitems_path(self): 951 | ''' 952 | Compose menuitems_path for GetMenuPath. Example "#0 -> Save As", "Tools -> #0 -> Configure" 953 | ''' 954 | path = [] 955 | owner_item = self.pwa_obj 956 | 957 | while owner_item: 958 | text = owner_item.Text() 959 | if not text: 960 | text = '#%d' % owner_item.Index() 961 | path.append(text) 962 | menu = owner_item.menu 963 | owner_item = menu.owner_item 964 | return '->'.join(path[::-1]) 965 | 966 | 967 | class Pwa_combobox(SWAPYObject): 968 | 969 | short_name = 'combobox' 970 | 971 | def _get_additional_children(self): 972 | ''' 973 | Add ComboBox items as children 974 | ''' 975 | additional_children = [] 976 | for i, text in enumerate(self.pwa_obj.ItemTexts()): 977 | if not text: 978 | text = "option #%s" % i 979 | additional_children.append((text, 980 | virtual_combobox_item(self, i))) 981 | else: 982 | additional_children.append((text, 983 | virtual_combobox_item(self, text))) 984 | return additional_children 985 | 986 | 987 | class virtual_combobox_item(VirtualSWAPYObject): 988 | 989 | def _get_properties(self): 990 | index = None 991 | text = self.index 992 | for i, name in enumerate(self.parent.pwa_obj.ItemTexts()): 993 | if name == text: 994 | index = i 995 | break 996 | return {'Index': index, 'Text': text} 997 | 998 | 999 | class Pwa_listbox(SWAPYObject): 1000 | 1001 | short_name = 'listbox' 1002 | 1003 | def _get_additional_children(self): 1004 | 1005 | """ 1006 | Add ListBox items as children 1007 | """ 1008 | 1009 | additional_children = [] 1010 | for i, text in enumerate(self.pwa_obj.ItemTexts()): 1011 | if not text: 1012 | text = "option #%s" % i 1013 | additional_children.append((text, 1014 | virtual_listbox_item(self, i))) 1015 | else: 1016 | additional_children.append((text, 1017 | virtual_listbox_item(self, text))) 1018 | return additional_children 1019 | 1020 | 1021 | class virtual_listbox_item(VirtualSWAPYObject): 1022 | 1023 | def _get_properties(self): 1024 | index = None 1025 | text = self.index 1026 | for i, name in enumerate(self.parent.pwa_obj.ItemTexts()): 1027 | if name == text: 1028 | index = i 1029 | break 1030 | return {'Index': index, 'Text': text} 1031 | 1032 | 1033 | class Pwa_listview(SWAPYObject): 1034 | 1035 | short_name = 'listview' 1036 | 1037 | def _get_additional_children(self): 1038 | ''' 1039 | Add SysListView32 items as children 1040 | ''' 1041 | additional_children = [] 1042 | for item in self.pwa_obj.Items(): 1043 | text = item.Text() 1044 | if not text: 1045 | index = item.item_index 1046 | column_index = item.subitem_index 1047 | text = "option #%s,%s" % (index, column_index) 1048 | additional_children += [(text, listview_item(item, self))] 1049 | return additional_children 1050 | 1051 | 1052 | class listview_item(SWAPYObject): 1053 | 1054 | code_self_patt_text = "{var} = {parent_var}.GetItem({text})" 1055 | code_self_patt_index = "{var} = {parent_var}.GetItem({index}, {col_index})" 1056 | short_name = 'listview_item' 1057 | 1058 | @property 1059 | def _code_self(self): 1060 | text = self.pwa_obj.Text() 1061 | if not text: 1062 | index = self.pwa_obj.item_index 1063 | col_index = self.pwa_obj.subitem_index 1064 | code = self.code_self_patt_index.format(index=index, 1065 | col_index=col_index, 1066 | parent_var="{parent_var}", 1067 | var="{var}") 1068 | else: 1069 | if isinstance(text, unicode): 1070 | text = "u'%s'" % text.encode('unicode-escape') 1071 | elif isinstance(text, str): 1072 | text = "'%s'" % text 1073 | code = self.code_self_patt_text.format(text=text, 1074 | parent_var="{parent_var}", 1075 | var="{var}") 1076 | return code 1077 | 1078 | def _get_properties(self): 1079 | item_properties = {'index': self.pwa_obj.item_index, 1080 | 'column_index': self.pwa_obj.subitem_index} 1081 | item_properties.update(self.pwa_obj.ItemData()) 1082 | return item_properties 1083 | 1084 | def _check_visibility(self): 1085 | return True 1086 | 1087 | def _check_actionable(self): 1088 | return True 1089 | 1090 | def _check_existence(self): 1091 | return True 1092 | 1093 | def Get_subitems(self): 1094 | return [] 1095 | 1096 | def Highlight_control(self): 1097 | pass 1098 | return 0 1099 | 1100 | 1101 | class Pwa_tab(SWAPYObject): 1102 | 1103 | short_name = 'tab' 1104 | 1105 | def _get_additional_children(self): 1106 | 1107 | """ 1108 | Add TabControl items as children 1109 | """ 1110 | 1111 | additional_children = [] 1112 | for index in range(self.pwa_obj.TabCount()): 1113 | text = self.pwa_obj.GetTabText(index) 1114 | if not text: 1115 | text = "tab #%s" % index 1116 | additional_children += [(text, virtual_tab_item(self, index))] 1117 | return additional_children 1118 | 1119 | 1120 | class virtual_tab_item(VirtualSWAPYObject): 1121 | 1122 | @property 1123 | def _code_action(self): 1124 | index = self.parent.pwa_obj.GetTabText(self.index) 1125 | if isinstance(index, unicode): 1126 | index = "u'%s'" % index.encode('unicode-escape') 1127 | code = self.code_action_pattern.format(index=index, 1128 | action="{action}", 1129 | var="{var}", 1130 | parent_var="{parent_var}") 1131 | return code 1132 | 1133 | def _get_properties(self): 1134 | item_properties = {'Index' : self.index, 1135 | 'Texts': self.parent.pwa_obj.GetTabText(self.index)} 1136 | return item_properties 1137 | 1138 | 1139 | class Pwa_toolbar(SWAPYObject): 1140 | 1141 | short_name = 'toolbar' 1142 | 1143 | def _get_additional_children(self): 1144 | ''' 1145 | Add button objects as children 1146 | ''' 1147 | additional_children = [] 1148 | buttons_count = self.pwa_obj.ButtonCount() 1149 | for button_index in range(buttons_count): 1150 | try: 1151 | button = self.pwa_obj.Button(button_index) 1152 | button_text = button.info.text 1153 | if not button_text: 1154 | button_text = "button #%s" % button_index 1155 | button_object = self._get_swapy_object(button) 1156 | except exceptions.RuntimeError: 1157 | #button_text = ['Unknown button name1!'] #workaround for RuntimeError: GetButtonInfo failed for button with index 0 1158 | pass #ignore the button 1159 | else: 1160 | button_item = [(button_text, button_object)] 1161 | additional_children += button_item 1162 | return additional_children 1163 | 1164 | def _get_children(self): 1165 | ''' 1166 | Return original pywinauto's object children 1167 | 1168 | ''' 1169 | return [] 1170 | 1171 | 1172 | class Pwa_toolbar_button(SWAPYObject): 1173 | 1174 | code_self_pattern = "{var} = {parent_var}.Button({index})" 1175 | short_name = 'toolbar_button' 1176 | 1177 | @property 1178 | def _code_self(self): 1179 | text = self.pwa_obj.info.text 1180 | if not text: 1181 | index = self.pwa_obj.index 1182 | else: 1183 | index = text 1184 | 1185 | if isinstance(index, unicode): 1186 | index = "u'%s'" % index.encode('unicode-escape') 1187 | elif isinstance(index, str): 1188 | index = "'%s'" % index 1189 | 1190 | code = self.code_self_pattern.format(index=index, 1191 | action="{action}", 1192 | var="{var}", 1193 | parent_var="{parent_var}") 1194 | return code 1195 | 1196 | def _check_visibility(self): 1197 | is_visible = False 1198 | try: 1199 | is_visible = self.pwa_obj.toolbar_ctrl.IsVisible() 1200 | except: 1201 | pass 1202 | return is_visible 1203 | 1204 | def _check_actionable(self): 1205 | try: 1206 | self.pwa_obj.toolbar_ctrl.VerifyActionable() 1207 | except: 1208 | is_actionable = False 1209 | else: 1210 | is_actionable = True 1211 | return is_actionable 1212 | 1213 | def _check_existence(self): 1214 | try: 1215 | handle_ = self.pwa_obj.toolbar_ctrl.handle 1216 | obj = pywinauto.application.WindowSpecification({'handle': handle_}) 1217 | except: 1218 | is_exist = False 1219 | else: 1220 | is_exist = obj.Exists() 1221 | return is_exist 1222 | 1223 | def _get_children(self): 1224 | return [] 1225 | 1226 | def _get_properties(self): 1227 | o = self.pwa_obj 1228 | props = {'IsCheckable': o.IsCheckable(), 1229 | 'IsChecked': o.IsChecked(), 1230 | 'IsEnabled': o.IsEnabled(), 1231 | 'IsPressable': o.IsPressable(), 1232 | 'IsPressed': o.IsPressed(), 1233 | 'Rectangle': o.Rectangle(), 1234 | 'State': o.State(), 1235 | 'Style': o.Style(), 1236 | 'index': o.index, 1237 | 'text': o.info.text} 1238 | return props 1239 | 1240 | def Highlight_control(self): 1241 | pass 1242 | return 0 1243 | 1244 | 1245 | class Pwa_tree(SWAPYObject): 1246 | 1247 | short_name = 'tree' 1248 | 1249 | def _get_additional_children(self): 1250 | ''' 1251 | Add roots object as children 1252 | ''' 1253 | 1254 | additional_children = [] 1255 | roots = self.pwa_obj.Roots() 1256 | for root in roots: 1257 | root_text = root.Text() 1258 | obj = self._get_swapy_object(root) 1259 | obj.path = [root_text] 1260 | root_item = [(root_text, obj)] 1261 | additional_children += root_item 1262 | return additional_children 1263 | 1264 | def Highlight_control(self): 1265 | pass 1266 | return 0 1267 | 1268 | 1269 | class Pwa_tree_item(SWAPYObject): 1270 | 1271 | main_parent_type = Pwa_tree 1272 | code_self_pattern = "{var} = {main_parent_var}.GetItem({path})" 1273 | short_name = 'tree_item' 1274 | 1275 | @property 1276 | def _code_self(self): 1277 | path = self.path 1278 | for i in range(len(path)): 1279 | if isinstance(path[i], unicode): 1280 | path[i] = u'%s' % path[i].encode('unicode-escape') 1281 | 1282 | code = self.code_self_pattern.format( 1283 | path=path, var="{var}", main_parent_var="{main_parent_var}") 1284 | return code 1285 | 1286 | def _get_properties(self): 1287 | o = self.pwa_obj 1288 | props = {'Rectangle' : o.Rectangle(), 1289 | 'State' : o.State(), 1290 | 'Text' : o.Text(),} 1291 | return props 1292 | 1293 | def _check_visibility(self): 1294 | return True 1295 | # TODO: It seems like pywinauto bug 1296 | #return self.pwa_obj.EnsureVisible() 1297 | 1298 | def _check_existence(self): 1299 | return True 1300 | 1301 | def _check_actionable(self): 1302 | if self.parent.pwa_obj != self.pwa_obj.tree_ctrl: 1303 | # the parent is also tree item 1304 | return self.parent.pwa_obj.IsExpanded() 1305 | else: 1306 | return True 1307 | 1308 | def _get_children(self): 1309 | return [] 1310 | 1311 | def Highlight_control(self): 1312 | pass 1313 | return 0 1314 | 1315 | def _get_additional_children(self): 1316 | ''' 1317 | Add sub tree items object as children 1318 | ''' 1319 | 1320 | additional_children = [] 1321 | sub_items = self.pwa_obj.Children() 1322 | for item in sub_items: 1323 | item_text = item.Text() 1324 | obj = self._get_swapy_object(item) 1325 | obj.path = self.path + [item_text] 1326 | sub_item = [(item_text, obj)] 1327 | additional_children += sub_item 1328 | return additional_children 1329 | -------------------------------------------------------------------------------- /swapy-debug.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python -*- 2 | a = Analysis(['swapy-ob.py'], 3 | pathex=['swapy-git']) 4 | a.datas += [('swapy_dog_head.ico','swapy_dog_head.ico','DATA'),] 5 | pyz = PYZ(a.pure) 6 | exe = EXE(pyz, 7 | a.datas, 8 | a.scripts, 9 | a.binaries, 10 | a.zipfiles, 11 | exclude_binaries=False, 12 | name=os.path.join('dist', 'swapy-debug.exe'), 13 | debug=True, 14 | strip=False, 15 | upx=False, 16 | console=True, 17 | icon='swapy_dog.ico') 18 | -------------------------------------------------------------------------------- /swapy-ob.py: -------------------------------------------------------------------------------- 1 | # GUI object/properties browser. 2 | # Copyright (C) 2011 Matiychuk D. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public License 6 | # as published by the Free Software Foundation; either version 2.1 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | # See the GNU Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library; if not, write to the 16 | # Free Software Foundation, Inc., 17 | # 59 Temple Place, 18 | # Suite 330, 19 | # Boston, MA 02111-1307 USA 20 | 21 | #Boa:App:BoaApp 22 | 23 | import sys 24 | import traceback 25 | import wx 26 | 27 | import _mainframe 28 | 29 | 30 | def hook(exctype, value, tb): 31 | 32 | """ 33 | Handle all unexpected exceptions. Show the msgbox then close main window. 34 | """ 35 | 36 | frame = wx.GetApp().GetTopWindow() 37 | traceback_text = ''.join(traceback.format_exception(exctype, value, tb, 5)) 38 | dlg = wx.MessageDialog(frame, traceback_text, 39 | 'Error!', wx.OK | wx.ICON_ERROR) 40 | dlg.ShowModal() 41 | dlg.Destroy() 42 | frame.Destroy() 43 | 44 | sys.excepthook = hook 45 | 46 | 47 | modules ={'_mainframe': [0, '', '_mainframe.py'], 'proxy': [0, '', 'proxy.py']} 48 | 49 | 50 | class BoaApp(wx.App): 51 | def OnInit(self): 52 | self.main = _mainframe.create(None) 53 | self.main.Center() 54 | self.main.Show() 55 | self.SetTopWindow(self.main) 56 | return True 57 | 58 | 59 | def main(): 60 | application = BoaApp(0) 61 | application.MainLoop() 62 | 63 | 64 | if __name__ == '__main__': 65 | main() 66 | -------------------------------------------------------------------------------- /swapy.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python -*- 2 | a = Analysis(['swapy-ob.py'], 3 | pathex=['swapy-git']) 4 | a.datas += [('swapy_dog_head.ico','swapy_dog_head.ico','DATA'),] 5 | pyz = PYZ(a.pure) 6 | exe = EXE(pyz, 7 | a.datas, 8 | a.scripts, 9 | a.binaries, 10 | a.zipfiles, 11 | exclude_binaries=False, 12 | name=os.path.join('dist', 'swapy.exe'), 13 | debug=False, 14 | strip=False, 15 | upx=True, 16 | console=False, 17 | icon='swapy_dog.ico') 18 | -------------------------------------------------------------------------------- /swapy_dog.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pywinauto/SWAPY/8ac9b53ac2b3fa670dbbb125395fcfbbc476ae5a/swapy_dog.ico -------------------------------------------------------------------------------- /swapy_dog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pywinauto/SWAPY/8ac9b53ac2b3fa670dbbb125395fcfbbc476ae5a/swapy_dog.png -------------------------------------------------------------------------------- /swapy_dog_head.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pywinauto/SWAPY/8ac9b53ac2b3fa670dbbb125395fcfbbc476ae5a/swapy_dog_head.ico -------------------------------------------------------------------------------- /unittests/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /unittests/test_codegen.py: -------------------------------------------------------------------------------- 1 | # unit tests for code generator 2 | # Copyright (C) 2015 Matiychuk D. 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public License 6 | # as published by the Free Software Foundation; either version 2.1 7 | # of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | # See the GNU Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library; if not, write to the 16 | # Free Software Foundation, Inc., 17 | # 59 Temple Place, 18 | # Suite 330, 19 | # Boston, MA 02111-1307 USA 20 | 21 | from contextlib import contextmanager 22 | import os 23 | import string 24 | import unittest 25 | 26 | from pywinauto.application import Application 27 | from pywinauto.sysinfo import is_x64_Python 28 | 29 | import code_manager 30 | import const 31 | import proxy 32 | 33 | 34 | SAMPLE_APPS_PATH = u"..\\apps\\MFC_samples" 35 | 36 | 37 | @contextmanager 38 | def test_app(filename): 39 | mfc_samples_folder = os.path.join(os.path.dirname(__file__), 40 | SAMPLE_APPS_PATH) 41 | if is_x64_Python(): 42 | sample_exe = os.path.join(mfc_samples_folder, "x64", filename) 43 | else: 44 | sample_exe = os.path.join(mfc_samples_folder, filename) 45 | 46 | app = Application().start(sample_exe, timeout=3) 47 | app_path = os.path.normpath(sample_exe).encode('unicode-escape') 48 | try: 49 | yield app, app_path 50 | except: 51 | # Re-raise AssertionError and others 52 | raise 53 | finally: 54 | app.kill_() 55 | 56 | 57 | class BaseTestCase(unittest.TestCase): 58 | 59 | def setUp(self): 60 | 61 | """ 62 | Since the tests require different apps, use `with` statement instead. 63 | All setUp actions moved in the test_app contextmanager. 64 | """ 65 | 66 | pass 67 | 68 | def tearDown(self): 69 | 70 | """ 71 | All app's tearDown moved into the test_app contextmanager. 72 | """ 73 | 74 | code_manager.CodeManager().clear() # Clear single tone CodeManager 75 | reload(code_manager) # Reset class's counters 76 | reload(proxy) # Reset class's counters 77 | del self.pwa_root 78 | 79 | def get_proxy_object(self, path): 80 | if not hasattr(self, 'pwa_root'): 81 | self.pwa_root = proxy.PC_system(None) 82 | 83 | proxy_object = self.pwa_root 84 | for target_sub in path: 85 | subitems = proxy_object.Get_subitems() 86 | if not subitems: 87 | raise RuntimeError("'%s' cannot be found" % target_sub) 88 | for name, pwa_object in subitems: 89 | if target_sub == name: 90 | proxy_object = pwa_object 91 | break 92 | else: 93 | raise RuntimeError("Invalid path, '%s' not found" % target_sub) 94 | 95 | return proxy_object 96 | 97 | 98 | class ObjectBrowserTestCases(BaseTestCase): 99 | 100 | def testNestedControl(self): 101 | 102 | direct_path = (u'Common Controls Sample', 103 | u'Treeview1, Birds, Eagle, Hummingbird, Pigeon', 104 | u'Birds', 105 | ) 106 | 107 | indirect_path = (u'Common Controls Sample', 108 | u'CTreeCtrl', 109 | u'Treeview1, Birds, Eagle, Hummingbird, Pigeon', 110 | u'Birds', 111 | ) 112 | 113 | with test_app("CmnCtrl1.exe") as (app, app_path): 114 | self.assertRaises(RuntimeError, self.get_proxy_object, 115 | indirect_path) 116 | 117 | proxy_obj = self.get_proxy_object(direct_path) 118 | self.assertEqual(proxy_obj.pwa_obj.elem, 119 | app.Dialog.TreeView.GetItem(['Birds']).elem) 120 | 121 | def testNestedTopWindow(self): 122 | 123 | path = (u'About RowList', 124 | u'RowList Version 1.0', 125 | ) 126 | 127 | with test_app("RowList.exe") as (app, app_path): 128 | app['RowList Sample Application'].MenuItem( 129 | u'&Help->&About RowList...').Select() # open About dialog 130 | try: 131 | proxy_obj = self.get_proxy_object(path) 132 | except RuntimeError: 133 | self.fail("Controls of a nested top window are not accessible") 134 | self.assertEqual(proxy_obj.pwa_obj.Texts(), 135 | [u'RowList Version 1.0']) 136 | 137 | 138 | class EmptyTextsTestCases(BaseTestCase): 139 | 140 | def testToolbarCode(self): 141 | expected_code = \ 142 | "from pywinauto.application import Application\n\n" \ 143 | "app = Application().Start(cmd_line=u'{app_path}')\n" \ 144 | "{win_ident} = app[u'RowList Sample Application']\n" \ 145 | "{win_ident}.Wait('ready')\n" \ 146 | "toolbarwindow = {win_ident}[u'4']\n" \ 147 | "toolbar_button = toolbarwindow.Button(9)\n" \ 148 | "toolbar_button.Click()\n\n" \ 149 | "app.Kill_()" 150 | 151 | path = (u'RowList Sample Application', 152 | u'Toolbar', 153 | u'button #9', 154 | ) 155 | 156 | with test_app("RowList.exe") as (app, app_path): 157 | 158 | window = app.top_window_() 159 | 160 | class_name = window.GetProperties()['Class'] 161 | crtl_class = filter(lambda c: c in string.ascii_letters, 162 | class_name).lower() 163 | 164 | proxy_obj = self.get_proxy_object(path) 165 | code = proxy_obj.Get_code('Click') 166 | 167 | expected_code = expected_code.format(app_path=app_path, 168 | win_ident=crtl_class) 169 | self.assertEquals(expected_code, code) 170 | 171 | 172 | class CodeGeneratorTestCases(BaseTestCase): 173 | 174 | def testInitAllParents(self): 175 | 176 | """ 177 | all parents are inited after Get_code on a sub child. 178 | """ 179 | 180 | expected_code = \ 181 | "from pywinauto.application import Application\n\n" \ 182 | "app = Application().Start(cmd_line=u'{app_path}')\n" \ 183 | "window = app.Dialog\n" \ 184 | "window.Wait('ready')\n" \ 185 | "systreeview = window.TreeView\n" \ 186 | "tree_item = systreeview.GetItem([u'Birds'])\n" \ 187 | "tree_item.Expand()\n\n" \ 188 | "app.Kill_()" 189 | 190 | path = (u'Common Controls Sample', 191 | u'Treeview1, Birds, Eagle, Hummingbird, Pigeon', 192 | u'Birds', 193 | ) 194 | 195 | with test_app("CmnCtrl1.exe") as (app, app_path): 196 | proxy_obj = self.get_proxy_object(path) 197 | code = proxy_obj.Get_code('Expand') 198 | 199 | expected_code = expected_code.format(app_path=app_path) 200 | self.assertEquals(expected_code, code) 201 | 202 | def testChangeCodeStyle(self): 203 | 204 | """ 205 | code style of a top window may be changed on fly 206 | """ 207 | 208 | expected_code_connect = \ 209 | "from pywinauto.application import Application\n\n" \ 210 | "app = Application().Connect(title=u'Common Controls Sample', " \ 211 | "class_name='#32770')\n" \ 212 | "window = app.Dialog\n" \ 213 | "systreeview = window.TreeView\n" \ 214 | "tree_item = systreeview.GetItem([u'Birds'])\n" \ 215 | "tree_item.Expand()\n\n" 216 | 217 | expected_code_start = \ 218 | "from pywinauto.application import Application\n\n" \ 219 | "app = Application().Start(cmd_line=u'{app_path}')\n" \ 220 | "window = app.Dialog\n" \ 221 | "window.Wait('ready')\n" \ 222 | "systreeview = window.TreeView\n" \ 223 | "tree_item = systreeview.GetItem([u'Birds'])\n" \ 224 | "tree_item.Expand()\n\n" \ 225 | "app.Kill_()" 226 | 227 | control_path = (u'Common Controls Sample', 228 | u'Treeview1, Birds, Eagle, Hummingbird, Pigeon', 229 | u'Birds', 230 | ) 231 | 232 | with test_app("CmnCtrl1.exe") as (app, app_path): 233 | proxy_obj = self.get_proxy_object(control_path) 234 | window_obj = proxy_obj.parent.parent 235 | 236 | # Default code style 237 | code_default = proxy_obj.Get_code('Expand') 238 | expected_code_start = expected_code_start.format( 239 | app_path=app_path) 240 | self.assertEquals(expected_code_start, code_default) 241 | 242 | # Switch to Connect 243 | window_obj.SetCodestyle( 244 | [menu_id for menu_id, command in const.EXTENDED_ACTIONS.items() 245 | if command == 'Application.Connect'][0]) 246 | code_connect = proxy_obj.Get_code() 247 | self.assertEquals(expected_code_connect, code_connect) 248 | 249 | # Switch back to Start 250 | window_obj.SetCodestyle( 251 | [menu_id for menu_id, command in const.EXTENDED_ACTIONS.items() 252 | if command == 'Application.Start'][0]) 253 | code_start = proxy_obj.Get_code() 254 | expected_code_start = expected_code_start.format( 255 | app_path=app_path) 256 | self.assertEquals(expected_code_start, code_start) 257 | 258 | def testSecondAppCounter(self): 259 | 260 | """ 261 | app counter increased for another window 262 | """ 263 | 264 | expected_code_1app = \ 265 | "from pywinauto.application import Application\n\n" \ 266 | "app = Application().Start(cmd_line=u'{app_path1}')\n" \ 267 | "window = app.Dialog\n" \ 268 | "window.Wait('ready')\n" \ 269 | "systreeview = window.TreeView\n" \ 270 | "tree_item = systreeview.GetItem([u'Birds'])\n" \ 271 | "tree_item.Expand()\n\n" \ 272 | "app.Kill_()" 273 | 274 | expected_code_2apps = \ 275 | "from pywinauto.application import Application\n\n" \ 276 | "app = Application().Start(cmd_line=u'{app_path1}')\n" \ 277 | "window = app.Dialog\n" \ 278 | "window.Wait('ready')\n" \ 279 | "systreeview = window.TreeView\n" \ 280 | "tree_item = systreeview.GetItem([u'Birds'])\n" \ 281 | "tree_item.Expand()\n\n" \ 282 | "app2 = Application().Start(cmd_line=u'{app_path2}')\n" \ 283 | "window2 = app2.Dialog\n" \ 284 | "window2.Wait('ready')\n" \ 285 | "menu_item = window2.MenuItem(u'&Help->&About mymenu...')\n" \ 286 | "menu_item.Click()\n\n" \ 287 | "app2.Kill_()\n" \ 288 | "app.Kill_()" 289 | 290 | control_path_app1 = (u'Common Controls Sample', 291 | u'Treeview1, Birds, Eagle, Hummingbird, Pigeon', 292 | u'Birds', 293 | ) 294 | control_path_app2 = (u'BCDialogMenu', 295 | u'!Menu', 296 | u'&Help', 297 | u'&Help submenu', 298 | u'&About mymenu...' 299 | ) 300 | 301 | with test_app("CmnCtrl1.exe") as (app1, app_path1), \ 302 | test_app("BCDialogMenu.exe") as (app2, app_path2): 303 | proxy_obj_app1 = self.get_proxy_object(control_path_app1) 304 | code_1app = proxy_obj_app1.Get_code('Expand') 305 | 306 | proxy_obj_app2 = self.get_proxy_object(control_path_app2) 307 | code_2apps = proxy_obj_app2.Get_code('Click') 308 | 309 | expected_code_1app = expected_code_1app.format(app_path1=app_path1) 310 | self.assertEquals(expected_code_1app, code_1app) 311 | 312 | expected_code_2apps = expected_code_2apps.format(app_path1=app_path1, 313 | app_path2=app_path2) 314 | self.assertEquals(expected_code_2apps, code_2apps) 315 | 316 | 317 | class VariableReuseTestCases(BaseTestCase): 318 | 319 | def testReuseVariable(self): 320 | 321 | """ 322 | an object variable used again for a new action 323 | """ 324 | 325 | expected_code = \ 326 | "from pywinauto.application import Application\n\n" \ 327 | "app = Application().Start(cmd_line=u'{app_path}')\n" \ 328 | "window = app.Dialog\n" \ 329 | "window.Wait('ready')\n" \ 330 | "systreeview = window.TreeView\n" \ 331 | "tree_item = systreeview.GetItem([u'Birds'])\n" \ 332 | "tree_item.Expand()\n" \ 333 | "tree_item.Click()\n\n" \ 334 | "app.Kill_()" 335 | 336 | path = (u'Common Controls Sample', 337 | u'Treeview1, Birds, Eagle, Hummingbird, Pigeon', 338 | u'Birds', 339 | ) 340 | 341 | with test_app("CmnCtrl1.exe") as (app, app_path): 342 | proxy_obj = self.get_proxy_object(path) 343 | proxy_obj.Get_code('Expand') # First call 344 | code = proxy_obj.Get_code('Click') 345 | 346 | expected_code = expected_code.format(app_path=app_path) 347 | self.assertEquals(expected_code, code) 348 | 349 | def testSameAppAfterRefresh(self): 350 | 351 | """ 352 | app & window counters do not increase after refresh for the same window 353 | """ 354 | 355 | expected_code = \ 356 | "from pywinauto.application import Application\n\n" \ 357 | "app = Application().Start(cmd_line=u'{app_path}')\n" \ 358 | "window = app.Dialog\n" \ 359 | "window.Wait('ready')\n" \ 360 | "window.Click()\n" \ 361 | "window.Click()\n\n" \ 362 | "app.Kill_()" 363 | 364 | control_path = (u'Common Controls Sample', 365 | ) 366 | 367 | with test_app("CmnCtrl1.exe") as (app, app_path): 368 | proxy_obj_before = self.get_proxy_object(control_path) 369 | _ = proxy_obj_before.Get_code('Click') 370 | 371 | # rebuild elements tree (refresh) 372 | proxy_obj_after = self.get_proxy_object(control_path) 373 | 374 | code = proxy_obj_after.Get_code('Click') 375 | 376 | self.assertTrue(proxy_obj_before is proxy_obj_after) 377 | 378 | expected_code = expected_code.format(app_path=app_path) 379 | self.assertEquals(expected_code, code) 380 | 381 | def testSameAppSecondWindow(self): 382 | 383 | """ 384 | variable app reused for both windows of the same process 385 | """ 386 | 387 | expected_code = \ 388 | "from pywinauto.application import Application\n\n" \ 389 | "app = Application().Start(cmd_line=u'{app_path}')\n" \ 390 | "{win_ident} = app[u'RowList Sample Application']\n" \ 391 | "{win_ident}.Wait('ready')\n" \ 392 | "menu_item = {win_ident}.MenuItem(u'&Help->&About RowList...')\n" \ 393 | "menu_item.Click()\n" \ 394 | "window = app.Dialog\n" \ 395 | "button = window.OK\n" \ 396 | "button.Click()\n\n" \ 397 | "app.Kill_()" 398 | 399 | path_main_window = (u'RowList Sample Application', 400 | 401 | u'!Menu', 402 | u'&Help', 403 | u'&Help submenu', 404 | u'&About RowList...', 405 | ) 406 | 407 | path_about_window = (u'About RowList', 408 | 409 | u'OK', 410 | ) 411 | 412 | with test_app("RowList.exe") as (app, app_path): 413 | 414 | window = app.top_window_() 415 | class_name = window.GetProperties()['Class'] 416 | crtl_class = filter(lambda c: c in string.ascii_letters, 417 | class_name).lower() 418 | 419 | proxy_obj_main_window = self.get_proxy_object(path_main_window) 420 | proxy_obj_main_window.pwa_obj.Click() # Click menu 421 | _ = proxy_obj_main_window.Get_code('Click') 422 | 423 | # new window 424 | proxy_obj_about_window = self.get_proxy_object(path_about_window) 425 | code = proxy_obj_about_window.Get_code('Click') 426 | 427 | expected_code = expected_code.format(app_path=app_path, 428 | win_ident=crtl_class) 429 | self.assertEquals(expected_code, code) 430 | 431 | 432 | class ClearCommandsTestCases(BaseTestCase): 433 | 434 | def testClearLastCommand(self): 435 | 436 | """ 437 | last command is cleared 438 | """ 439 | 440 | expected_code_full = \ 441 | "from pywinauto.application import Application\n\n" \ 442 | "app = Application().Start(cmd_line=u'{app_path}')\n" \ 443 | "window = app.Dialog\n" \ 444 | "window.Wait('ready')\n" \ 445 | "systabcontrol = window.TabControl\n" \ 446 | "systabcontrol.Select(u'CTreeCtrl')\n\n" \ 447 | "app.Kill_()" 448 | 449 | expected_code_cleared = \ 450 | "from pywinauto.application import Application\n\n" \ 451 | "app = Application().Start(cmd_line=u'{app_path}')\n" \ 452 | "window = app.Dialog\n" \ 453 | "window.Wait('ready')\n" \ 454 | "systabcontrol = window.TabControl\n\n" \ 455 | "app.Kill_()" 456 | 457 | path = (u'Common Controls Sample', 458 | 459 | u'CTreeCtrl, CAnimateCtrl, CToolBarCtrl, CDateTimeCtrl, ' 460 | u'CMonthCalCtrl', 461 | 462 | u'CTreeCtrl', 463 | ) 464 | 465 | with test_app("CmnCtrl1.exe") as (app, app_path): 466 | proxy_obj = self.get_proxy_object(path) 467 | code_full = proxy_obj.Get_code('Select') 468 | 469 | cm = code_manager.CodeManager() 470 | cm.clear_last() 471 | code_cleared = cm.get_full_code() 472 | 473 | expected_code_full = expected_code_full.format(app_path=app_path) 474 | self.assertEquals(expected_code_full, code_full) 475 | 476 | expected_code_cleared = expected_code_cleared.format(app_path=app_path) 477 | self.assertEquals(expected_code_cleared, code_cleared) 478 | 479 | def testClear2LastCommand(self): 480 | 481 | """ 482 | last two commands are cleared 483 | """ 484 | 485 | expected_code_full = \ 486 | "from pywinauto.application import Application\n\n" \ 487 | "app = Application().Start(cmd_line=u'{app_path}')\n" \ 488 | "window = app.Dialog\n" \ 489 | "window.Wait('ready')\n" \ 490 | "systabcontrol = window.TabControl\n" \ 491 | "systabcontrol.Select(u'CTreeCtrl')\n\n" \ 492 | "app.Kill_()" 493 | 494 | expected_code_cleared = \ 495 | "from pywinauto.application import Application\n\n" \ 496 | "app = Application().Start(cmd_line=u'{app_path}')\n" \ 497 | "window = app.Dialog\n" \ 498 | "window.Wait('ready')\n\n" \ 499 | "app.Kill_()" 500 | 501 | path = (u'Common Controls Sample', 502 | 503 | u'CTreeCtrl, CAnimateCtrl, CToolBarCtrl, CDateTimeCtrl, ' 504 | u'CMonthCalCtrl', 505 | 506 | u'CTreeCtrl', 507 | ) 508 | 509 | with test_app("CmnCtrl1.exe") as (app, app_path): 510 | proxy_obj = self.get_proxy_object(path) 511 | code_full = proxy_obj.Get_code('Select') 512 | 513 | cm = code_manager.CodeManager() 514 | cm.clear_last() 515 | cm.clear_last() 516 | code_cleared = cm.get_full_code() 517 | 518 | expected_code_full = expected_code_full.format(app_path=app_path) 519 | self.assertEquals(expected_code_full, code_full) 520 | 521 | expected_code_cleared = expected_code_cleared.format(app_path=app_path) 522 | self.assertEquals(expected_code_cleared, code_cleared) 523 | 524 | def testClearAll(self): 525 | 526 | """ 527 | clear all the code 528 | """ 529 | 530 | expected_code_full = \ 531 | "from pywinauto.application import Application\n\n" \ 532 | "app = Application().Start(cmd_line=u'{app_path}')\n" \ 533 | "window = app.Dialog\n" \ 534 | "window.Wait('ready')\n" \ 535 | "systabcontrol = window.TabControl\n" \ 536 | "systabcontrol.Select(u'CTreeCtrl')\n\n" \ 537 | "app.Kill_()" 538 | 539 | path = (u'Common Controls Sample', 540 | 541 | u'CTreeCtrl, CAnimateCtrl, CToolBarCtrl, CDateTimeCtrl, ' 542 | u'CMonthCalCtrl', 543 | 544 | u'CTreeCtrl', 545 | ) 546 | 547 | with test_app("CmnCtrl1.exe") as (app, app_path): 548 | proxy_obj = self.get_proxy_object(path) 549 | code_full = proxy_obj.Get_code('Select') 550 | 551 | cm = code_manager.CodeManager() 552 | cm.clear() 553 | code_cleared = cm.get_full_code() 554 | 555 | expected_code_full = expected_code_full.format(app_path=app_path) 556 | self.assertEquals(expected_code_full, code_full) 557 | 558 | self.assertEquals("", code_cleared) 559 | 560 | def testReleaseVariable(self): 561 | 562 | """ 563 | variable released while the clear and used again by other object 564 | """ 565 | 566 | expected_code_button1 = \ 567 | "from pywinauto.application import Application\n\n" \ 568 | "app = Application().Start(cmd_line=u'{app_path}')\n" \ 569 | "window = app.Dialog\n" \ 570 | "window.Wait('ready')\n" \ 571 | "button = window.CheckBox8\n" \ 572 | "button.Click()\n\n" \ 573 | "app.Kill_()" 574 | 575 | expected_code_button2 = \ 576 | "from pywinauto.application import Application\n\n" \ 577 | "app = Application().Start(cmd_line=u'{app_path}')\n" \ 578 | "window = app.Dialog\n" \ 579 | "window.Wait('ready')\n" \ 580 | "button = window.CheckBox5\n" \ 581 | "button.Click()\n\n" \ 582 | "app.Kill_()" 583 | 584 | path_button1 = (u'Common Controls Sample', 585 | u'TVS_CHECKBOXES', 586 | ) 587 | 588 | path_button2 = (u'Common Controls Sample', 589 | u'TVS_DISABLEDRAGDROP', 590 | ) 591 | 592 | with test_app("CmnCtrl1.exe") as (app, app_path): 593 | proxy_obj_button1 = self.get_proxy_object(path_button1) 594 | code_button1 = proxy_obj_button1.Get_code('Click') 595 | 596 | cm = code_manager.CodeManager() 597 | cm.clear_last() 598 | 599 | proxy_obj_button2 = self.get_proxy_object(path_button2) 600 | code_button2 = proxy_obj_button2.Get_code('Click') 601 | 602 | expected_code_button1 = expected_code_button1.format(app_path=app_path) 603 | self.assertEquals(expected_code_button1, code_button1) 604 | 605 | expected_code_button2 = expected_code_button2.format(app_path=app_path) 606 | self.assertEquals(expected_code_button2, code_button2) 607 | 608 | 609 | class ControlsCodeTestCases(BaseTestCase): 610 | 611 | def testComboBoxCode(self): 612 | expected_code = \ 613 | "from pywinauto.application import Application\n\n" \ 614 | "app = Application().Start(cmd_line=u'{app_path}')\n" \ 615 | "window = app.Dialog\n" \ 616 | "window.Wait('ready')\n" \ 617 | "combobox = window.ComboBox\n" \ 618 | "combobox.Select(u'Gray')\n\n" \ 619 | "app.Kill_()" 620 | 621 | path = (u'Common Controls Sample', 622 | u'Gray, Gray, White, Black', 623 | u'Gray', 624 | ) 625 | 626 | with test_app("CmnCtrl3.exe") as (app, app_path): 627 | proxy_obj = self.get_proxy_object(path) 628 | code = proxy_obj.Get_code('Select') 629 | 630 | expected_code = expected_code.format(app_path=app_path) 631 | self.assertEquals(expected_code, code) 632 | 633 | def testListBoxCode(self): 634 | expected_code = \ 635 | "from pywinauto.application import Application\n\n" \ 636 | "app = Application().Start(cmd_line=u'{app_path}')\n" \ 637 | "window = app.Dialog\n" \ 638 | "window.Wait('ready')\n" \ 639 | "listbox = window.ListBox\n" \ 640 | "listbox.Select(u'BCSS_IMAGE')\n\n" \ 641 | "app.Kill_()" 642 | 643 | path = (u'Common Controls Sample', 644 | u'BCSS_NOSPLIT, BCSS_STRETCH, BCSS_ALIGNLEFT, BCSS_IMAGE', 645 | u'BCSS_IMAGE', 646 | ) 647 | 648 | with test_app("CmnCtrl3.exe") as (app, app_path): 649 | app.Dialog.TabControl.Select('CSplitButton') # open needed tab 650 | proxy_obj = self.get_proxy_object(path) 651 | code = proxy_obj.Get_code('Select') 652 | 653 | expected_code = expected_code.format(app_path=app_path) 654 | self.assertEquals(expected_code, code) 655 | 656 | def testMenuCode(self): 657 | expected_code = \ 658 | "from pywinauto.application import Application\n\n" \ 659 | "app = Application().Start(cmd_line=u'{app_path}')\n" \ 660 | "window = app.Dialog\n" \ 661 | "window.Wait('ready')\n" \ 662 | "menu_item = window.MenuItem" \ 663 | "(u'&Help->&About mymenu...')\n" \ 664 | "menu_item.Click()\n\n" \ 665 | "app.Kill_()" 666 | 667 | path = (u'BCDialogMenu', 668 | u'!Menu', 669 | u'&Help', 670 | u'&Help submenu', 671 | u'&About mymenu...' 672 | ) 673 | 674 | with test_app("BCDialogMenu.exe") as (app, app_path): 675 | proxy_obj = self.get_proxy_object(path) 676 | code = proxy_obj.Get_code('Click') 677 | 678 | expected_code = expected_code.format(app_path=app_path) 679 | self.assertEquals(expected_code, code) 680 | 681 | def testSysListView32Code(self): 682 | expected_code = \ 683 | "from pywinauto.application import Application\n\n" \ 684 | "app = Application().Start(cmd_line=u'{app_path}')\n" \ 685 | "{win_ident} = app[u'RowList Sample Application']\n" \ 686 | "{win_ident}.Wait('ready')\n" \ 687 | "syslistview = {win_ident}[u'1']\n" \ 688 | "listview_item = syslistview.GetItem(u'Gray')\n" \ 689 | "listview_item.Click()\n\n" \ 690 | "app.Kill_()" 691 | 692 | path = (u'RowList Sample Application', 693 | 694 | u'Yellow, 255, 255, 0, 40, 240, 120, Neutral, Red, 255, 0, 0, ' 695 | u'0, 240, 120, Warm, Green, 0, 255, 0, 80, 240, 120, Cool, ' 696 | u'Magenta, 255, 0, 255, 200, 240, 120, Warm, Cyan, 0, 255, ' 697 | u'255, 120, 240, 120, Cool, Blue, 0, 0, 255, 160, 240, 120, ' 698 | u'Cool, Gray, 192, 192, 192, 160, 0, 181, Neutral', 699 | 700 | u'Gray', 701 | ) 702 | 703 | with test_app("RowList.exe") as (app, app_path): 704 | 705 | window = app.top_window_() 706 | 707 | class_name = window.GetProperties()['Class'] 708 | crtl_class = filter(lambda c: c in string.ascii_letters, 709 | class_name).lower() 710 | 711 | proxy_obj = self.get_proxy_object(path) 712 | self.assertEquals(len(proxy_obj.pwa_obj.listview_ctrl.Items()), 56) 713 | code = proxy_obj.Get_code('Click') 714 | 715 | expected_code = expected_code.format(app_path=app_path, 716 | win_ident=crtl_class) 717 | self.assertEquals(expected_code, code) 718 | 719 | def testSysTabControl32Code(self): 720 | expected_code = \ 721 | "from pywinauto.application import Application\n\n" \ 722 | "app = Application().Start(cmd_line=u'{app_path}')\n" \ 723 | "window = app.Dialog\n" \ 724 | "window.Wait('ready')\n" \ 725 | "systabcontrol = window.TabControl\n" \ 726 | "systabcontrol.Select(u'CTreeCtrl')\n\n" \ 727 | "app.Kill_()" 728 | 729 | path = (u'Common Controls Sample', 730 | 731 | u'CTreeCtrl, CAnimateCtrl, CToolBarCtrl, CDateTimeCtrl, ' 732 | u'CMonthCalCtrl', 733 | 734 | u'CTreeCtrl', 735 | ) 736 | 737 | with test_app("CmnCtrl1.exe") as (app, app_path): 738 | proxy_obj = self.get_proxy_object(path) 739 | code = proxy_obj.Get_code('Select') 740 | 741 | expected_code = expected_code.format(app_path=app_path) 742 | self.assertEquals(expected_code, code) 743 | 744 | def testToolbarWindow32Code(self): 745 | expected_code = \ 746 | "from pywinauto.application import Application\n\n" \ 747 | "app = Application().Start(cmd_line=u'{app_path}')\n" \ 748 | "window = app.Dialog\n" \ 749 | "window.Wait('ready')\n" \ 750 | "toolbarwindow = window.Toolbar2\n" \ 751 | "toolbar_button = toolbarwindow.Button(u'Line')\n" \ 752 | "toolbar_button.Click()\n\n" \ 753 | "app.Kill_()" 754 | 755 | path = (u'Common Controls Sample', 756 | 757 | u'Erase, Pencil, Select, Brush, Airbrush, Fill, Line, Select ' 758 | u'Color, Magnify, Rectangle, Round Rect, Ellipse', 759 | 760 | u'Line', 761 | ) 762 | 763 | with test_app("CmnCtrl1.exe") as (app, app_path): 764 | app.Dialog.TabControl.Select('CToolBarCtrl') # open needed tab 765 | proxy_obj = self.get_proxy_object(path) 766 | code = proxy_obj.Get_code('Click') 767 | 768 | expected_code = expected_code.format(app_path=app_path) 769 | self.assertEquals(expected_code, code) 770 | --------------------------------------------------------------------------------