diff --git a/_mainframe.py b/_mainframe.py index d2d784c..700f002 100644 --- a/_mainframe.py +++ b/_mainframe.py @@ -25,8 +25,10 @@ import locale import platform import thread +import traceback import wx +import code_manager import proxy #Avoid limit of wx.ListCtrl in 512 symbols @@ -35,11 +37,13 @@ def create(parent): return Frame1(parent) + [wxID_FRAME1, wxID_FRAME1LISTCTRL1_PROPERTIES, wxID_FRAME1STATICBOX_EDITOR, wxID_FRAME1STATICBOX_OBJECTSBROWSER, wxID_FRAME1STATICBOX_PROPRTIES, wxID_FRAME1TEXTCTRL_EDITOR, wxID_FRAME1TREECTRL_OBJECTSBROWSER ] = [wx.NewId() for _init_ctrls in range(7)] + class Frame1(wx.Frame): """ Main application frame @@ -76,14 +80,16 @@ def _init_ctrls(self, prnt): name='treeCtrl_ObjectsBrowser', parent=self, style=wx.TR_HAS_BUTTONS) self.treeCtrl_ObjectsBrowser.Bind(wx.EVT_TREE_SEL_CHANGED, - self.OnTreeCtrl1TreeSelChanged, id=wxID_FRAME1TREECTRL_OBJECTSBROWSER) + self.ObjectsBrowserSelChanged, id=wxID_FRAME1TREECTRL_OBJECTSBROWSER) - self.treeCtrl_ObjectsBrowser.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.ObjectsBrowserRight_Click) + self.treeCtrl_ObjectsBrowser.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.ObjectsBrowserRightClick) #---------- #-----Editor----- self.textCtrl_Editor = wx.TextCtrl(id=wxID_FRAME1TEXTCTRL_EDITOR, name='textCtrl_Editor', parent=self, style=wx.TE_MULTILINE | wx.TE_READONLY, value='') + + self.textCtrl_Editor.Bind(wx.EVT_CONTEXT_MENU, self.EditorContextMenu) self.textCtrl_Editor.SetInitialSize((300,250)) #---------- @@ -99,7 +105,7 @@ def _init_ctrls(self, prnt): heading='Value', width=-1) self.listCtrl_Properties.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, - self.OnlistCtrl_PropertiesListItemRightClick, id=wxID_FRAME1LISTCTRL1_PROPERTIES) + self.PropertiesRightClick, id=wxID_FRAME1LISTCTRL1_PROPERTIES) #self.listCtrl_Properties.Bind(wx.EVT_LEFT_DCLICK, self.Refresh, id=wxID_FRAME1LISTCTRL1_PROPERTIES) #---------- @@ -127,13 +133,13 @@ def _init_ctrls(self, prnt): def __init__(self, parent): self._init_ctrls(parent) - self._init_windows_tree() - self.textCtrl_Editor.AppendText('import pywinauto\n\n') - self.textCtrl_Editor.AppendText('pwa_app = pywinauto.application.Application()\n') + self._init_windows_tree() + self.textCtrl_Editor.SetForegroundColour(wx.LIGHT_GREY) + self.textCtrl_Editor.AppendText('#Perform an action - right click on item in the object browser.') self.prop_updater = prop_viewer_updater(self.listCtrl_Properties) self.tree_updater = tree_updater(self.treeCtrl_ObjectsBrowser) - def OnTreeCtrl1TreeSelChanged(self, event): + def ObjectsBrowserSelChanged(self, event): tree_item = event.GetItem() obj = self.treeCtrl_ObjectsBrowser.GetItemData(tree_item).GetData() if not obj._check_existence(): @@ -144,7 +150,7 @@ def OnTreeCtrl1TreeSelChanged(self, event): self.tree_updater.tree_update(tree_item, obj) obj.Highlight_control() - def ObjectsBrowserRight_Click(self, event): + def ObjectsBrowserRightClick(self, event): menu = wx.Menu() #tree_item = self.treeCtrl_ObjectsBrowser.GetSelection() tree_item = event.GetItem() @@ -152,53 +158,86 @@ def ObjectsBrowserRight_Click(self, event): self.GLOB_last_rclick_tree_obj = obj #self.treeCtrl_ObjectsBrowser.SelectItem(tree_item) if obj._check_existence(): - actions = obj.Get_actions() - if actions: - for id, action_name in actions: - menu.Append(id, action_name) - if not obj._check_actionable(): - menu.Enable(id, False) - else: - menu.Append(0, 'No actions') - menu.Enable(0, False) - self.PopupMenu(menu) - menu.Destroy() + actions = obj.Get_actions() + extended_actions = obj.Get_extended_actions() + if not actions and not extended_actions: + menu.Append(0, 'No actions') + menu.Enable(0, False) + else: + if extended_actions: + for _id, extended_action_name in extended_actions: + menu.Append(_id, extended_action_name) + if not obj._check_actionable(): + menu.Enable(_id, False) + menu.AppendSeparator() + + for _id, action_name in actions: + menu.Append(_id, action_name) + if not obj._check_actionable(): + menu.Enable(_id, False) + + self.PopupMenu(menu) + menu.Destroy() else: - self._init_windows_tree() - tree_item = self.treeCtrl_ObjectsBrowser.GetRootItem() - obj = self.treeCtrl_ObjectsBrowser.GetItemData(tree_item).GetData() - self.prop_updater.props_update(obj) - self.tree_updater.tree_update(tree_item, obj) + self._init_windows_tree() + tree_item = self.treeCtrl_ObjectsBrowser.GetRootItem() + obj = self.treeCtrl_ObjectsBrowser.GetItemData(tree_item).GetData() + self.prop_updater.props_update(obj) + self.tree_updater.tree_update(tree_item, obj) - def OnlistCtrl_PropertiesListItemRightClick(self, event): + def PropertiesRightClick(self, event): self.GLOB_prop_item_index = event.GetIndex() menu = wx.Menu() - menu.Append(201, 'Copy all') - menu.AppendSeparator() - menu.Append(202, 'Copy property') - menu.Append(203, 'Copy value') - menu.Append(204, 'Copy unicode value') - self.PopupMenu(menu) - menu.Destroy() + for _id, option_name in sorted(const.PROPERTIES_ACTIONS.items()): + if option_name: + menu.Append(_id, option_name) + else: + menu.AppendSeparator() + self.PopupMenu(menu) + menu.Destroy() + + def EditorContextMenu(self, event): + cm = code_manager.CodeManager() + menu = wx.Menu() + + for _id, option_name in sorted(const.EDITOR_ACTIONS.items()): + if option_name: + menu.Append(_id, option_name) + if not cm: # empty code + menu.Enable(_id, False) + else: + menu.AppendSeparator() + if not self.textCtrl_Editor.GetStringSelection(): # empty selection + menu.Enable(404, False) # 404: 'Copy' + + self.PopupMenu(menu) + menu.Destroy() def menu_action(self, event): - id = event.Id - #print id - if 99 < id < 200: - #object browser menu - self.make_action(id) - elif 199 < id < 300: - #properties viewer menu - self.clipboard_action(id) + menu_id = event.Id + if menu_id in const.ACTIONS or \ + menu_id in const.EXTENDED_ACTIONS: + # object browser menu + # regular action or extended action + self.make_action(menu_id) + + elif menu_id in const.PROPERTIES_ACTIONS: + # properties viewer menu + self.properties_action(menu_id) + + elif menu_id in const.EDITOR_ACTIONS: + # editor menu + self.editor_action(menu_id) + else: - #Unknown menu id - pass + raise RuntimeError("Unknown menu_id=%s for properties " + "menu" % menu_id) - def clipboard_action(self, menu_id): + def properties_action(self, menu_id): item = self.GLOB_prop_item_index clipdata = wx.TextDataObject() - if menu_id == 201: - # Copy all + + if 'Copy all' == const.PROPERTIES_ACTIONS[menu_id]: all_texts = '' items_count = self.listCtrl_Properties.GetItemCount() for i in range(items_count): @@ -206,43 +245,114 @@ def clipboard_action(self, menu_id): val_name = self.listCtrl_Properties.GetItem(i, 1).GetText() all_texts += '%s : %s' % (prop_name, val_name) + '\n' clipdata.SetText(all_texts) - elif menu_id == 202: - # Copy property + + elif 'Copy property' == const.PROPERTIES_ACTIONS[menu_id]: property = self.listCtrl_Properties.GetItem(item,0).GetText() clipdata.SetText(property) - elif menu_id == 203: - # Copy value + + elif 'Copy value' == const.PROPERTIES_ACTIONS[menu_id]: #value = self.listCtrl_Properties.GetItem(item,1).GetText() key = self.listCtrl_Properties.GetItem(item,0).GetText() try: value_str = str(PROPERTIES[key]) except exceptions.UnicodeEncodeError: - value_str = PROPERTIES[key].encode(locale.getpreferredencoding(), 'replace') + value_str = PROPERTIES[key].encode( + locale.getpreferredencoding(), 'replace') clipdata.SetText(value_str) - elif menu_id == 204: - # Copy unicode value + + elif 'Copy unicode value' == const.PROPERTIES_ACTIONS[menu_id]: key = self.listCtrl_Properties.GetItem(item,0).GetText() try: value_unicode_escape = str(PROPERTIES[key]) except exceptions.UnicodeEncodeError: - value_unicode_escape = PROPERTIES[key].encode('unicode-escape', 'replace') + value_unicode_escape = PROPERTIES[key].encode('unicode-escape', + 'replace') clipdata.SetText(value_unicode_escape) else: - #Unknow id - pass + raise RuntimeError("Unknown menu_id=%s for properties " + "menu" % menu_id) + self.GLOB_prop_item_index = None wx.TheClipboard.Open() wx.TheClipboard.SetData(clipdata) wx.TheClipboard.Close() - - + def make_action(self, menu_id): - #tree_item = self.treeCtrl_ObjectsBrowser.GetSelection() - #obj = self.treeCtrl_ObjectsBrowser.GetItemData(tree_item).GetData() obj = self.GLOB_last_rclick_tree_obj - self.textCtrl_Editor.AppendText(obj.Get_code(menu_id)) - obj.Exec_action(menu_id) - + + if menu_id in const.ACTIONS: + # Regular action + action = const.ACTIONS[menu_id] + try: + code = obj.Get_code(action) + obj.Exec_action(action) + except: + code = None + dlg = wx.MessageDialog(self, traceback.format_exc(5), + 'Warning!', wx.OK | wx.ICON_WARNING) + dlg.ShowModal() + dlg.Destroy() + + elif menu_id in const.EXTENDED_ACTIONS: + # Extended action + try: + obj.SetCodestyle(menu_id) + code = obj.Get_code() + except: + code = None + dlg = wx.MessageDialog(self, traceback.format_exc(5), + 'Warning!', wx.OK | wx.ICON_WARNING) + dlg.ShowModal() + dlg.Destroy() + + if code is not None: + self.textCtrl_Editor.SetForegroundColour(wx.BLACK) + self.textCtrl_Editor.SetValue(code) + + def editor_action(self, menu_id): + cm = code_manager.CodeManager() + + if 'Clear last command' == const.EDITOR_ACTIONS[menu_id]: + cm.clear_last() + self.textCtrl_Editor.SetValue(cm.get_full_code()) + + elif 'Clear the code' == const.EDITOR_ACTIONS[menu_id]: + def confirm_clearing(): + yes_no_dlg = wx.MessageDialog(self.textCtrl_Editor, + "Are you sure you want to clear all of " + "the code?", + "Clear all?", + wx.YES_NO | wx.ICON_QUESTION) + result = yes_no_dlg.ShowModal() == wx.ID_YES + yes_no_dlg.Destroy() + return result + + if confirm_clearing(): + self.textCtrl_Editor.SetValue("") + cm.clear() + + elif 'Copy' == const.EDITOR_ACTIONS[menu_id]: + self.textCtrl_Editor.Copy() + + elif 'Select all' == const.EDITOR_ACTIONS[menu_id]: + self.textCtrl_Editor.SetFocus() + self.textCtrl_Editor.SelectAll() + + elif 'Save code to file' == const.EDITOR_ACTIONS[menu_id]: + import os + dlg = wx.FileDialog(self, "Choose a file", '', '', "*.py", + wx.SAVE | wx.OVERWRITE_PROMPT) + if dlg.ShowModal() == wx.ID_OK: + with open(os.path.join(dlg.GetDirectory(), + dlg.GetFilename()), 'w') as out_file: + out_file.write("# automatically generated by SWAPY\n") + out_file.write(self.textCtrl_Editor.GetValue()) + dlg.Destroy() + + else: + raise RuntimeError("Unknown menu_id=%s for editor " + "menu" % menu_id) + def _init_windows_tree(self): self.treeCtrl_ObjectsBrowser.DeleteAllItems() item_data = wx.TreeItemData() @@ -254,6 +364,7 @@ def _init_windows_tree(self): #the_root = self.treeCtrl_ObjectsBrowser.GetRootItem() #self.treeCtrl_ObjectsBrowser.Expand(self.treeCtrl_ObjectsBrowser.GetRootItem()) + class prop_viewer_updater(object): def __init__(self, listctrl): self.listctrl = listctrl @@ -274,7 +385,15 @@ def _update(self): index = self.listctrl.InsertStringItem(0, 'Updating...') self.listctrl.SetStringItem(index, 1, '') global PROPERTIES - PROPERTIES = obj.GetProperties() + try: + PROPERTIES = obj.GetProperties() + except: + PROPERTIES = {} + dlg = wx.MessageDialog(self.listctrl, traceback.format_exc(5), + 'Warning!', wx.OK | wx.ICON_WARNING) + dlg.ShowModal() + dlg.Destroy() + param_names = PROPERTIES.keys() param_names.sort(key=lambda name: name.lower(), reverse=True) @@ -285,7 +404,8 @@ def _update(self): try: p_values_str = str(PROPERTIES[p_name]) except exceptions.UnicodeEncodeError: - p_values_str = PROPERTIES[p_name].encode(locale.getpreferredencoding(), 'replace') + p_values_str = PROPERTIES[p_name].encode( + locale.getpreferredencoding(), 'replace') index = self.listctrl.InsertStringItem(0, p_name_str) self.listctrl.SetStringItem(index, 1, p_values_str) self.queue = [] @@ -296,7 +416,8 @@ def _update(self): #there is the newer object for properties view. #Do not update listctrl #run _update again - + + class tree_updater(object): def __init__(self, treectrl): self.treectrl = treectrl diff --git a/appveyor.yml b/appveyor.yml index fc3f700..307f29f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -28,21 +28,32 @@ install: #- ECHO "Notepad location " #- ps: "ls C:\\Windows\\System32\\notepad.exe" - # install wxPython - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - - "%CMD_IN_ENV% powershell ./ci/install.ps1" - # Install the build dependencies of the project. + # Install the build dependencies of the project. - "%CMD_IN_ENV% pip install -r dev-requirements.txt" build: off # disable automatic builds + +before_build: + # Download sample apps + - "%CMD_IN_ENV% svn checkout https://github.com/pywinauto/pywinauto/trunk/apps ./apps" + # Run unittests + - "%CMD_IN_ENV% python -m unittest discover" + build_script: + # install wxPython + - "%CMD_IN_ENV% powershell ./ci/install.ps1" + # Print python modules again + - "%CMD_IN_ENV% pip freeze" + # Build UI - "%CMD_IN_ENV% powershell ./ci/build.ps1" + #test_script: -# # Build the compiled extension and run the project tests -# - "%CMD_IN_ENV% powershell ./build.ps1" +# # Run UI tests + artifacts: # Archive the generated coverage report in the ci.appveyor.com build report. diff --git a/code_manager.py b/code_manager.py new file mode 100644 index 0000000..597c150 --- /dev/null +++ b/code_manager.py @@ -0,0 +1,442 @@ +# GUI object/properties browser. +# Copyright (C) 2015 Matiychuk D. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public License +# as published by the Free Software Foundation; either version 2.1 +# of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., +# 59 Temple Place, +# Suite 330, +# Boston, MA 02111-1307 USA + + +import re + + +def check_valid_identifier(identifier): + + """ + Check the identifier is a valid Python identifier. + Since the identifier will be used as an attribute, + don't check for reserved names. + """ + + return bool(re.match("[_A-Za-z][_a-zA-Z0-9]*$", identifier)) + + +class CodeSnippet(object): + + """ + A piece of the code. + `init_code` a code used one time in the beginning. + `action_code` use already inited object. + `close_code` a part passed to the end of the code. + `indent` means `action_code` and `close_code` should be under the indent. + """ + + INIT_SNIPPET = 1 + ACTION_SNIPPET = 2 + + def __init__(self, owner, init_code='', action_code='', close_code='', + indent=False): + + if not init_code and not action_code and not close_code: + raise SyntaxError("At least one of init_code, " + "action_code, close_code should be passed.") + self.init_code = init_code + self.action_code = action_code + self.close_code = close_code + self.indent = indent + self.owner = owner + + def update(self, init_code=None, action_code=None, close_code=None, + indent=None): + + """ + Update code. + Updates only passed the args. + To clear a code use empty line. For instance + .update(init_code='new init code', + action_code='') # Erase old action_code + """ + if init_code is not None: + self.init_code = init_code + + if action_code is not None: + self.action_code = action_code + + if close_code is not None: + self.close_code = close_code + + if indent is not None: + self.indent = indent + + @property + def types(self): + + """ + Return mask with INIT_SNIPPET and\or ACTION_SNIPPET flags + """ + + mask = 0 + if self.init_code or self.close_code: + mask |= self.INIT_SNIPPET + if self.action_code: + mask |= self.ACTION_SNIPPET + return mask + + def __repr__(self): + lines = [] + for code in ("init_code: %s" % self.init_code, + "action_code: %s" % self.action_code, + "close_code: %s" % self.close_code): + lines.append(code) + lines.append('-'*40) + return '\n'.join(lines) + + +class CodeManager(object): + + """ + Manages code snippets. Handles intent if needed and keeps `close_code` + always at the end. + Single instance. + """ + + single_object = None + inited = False + + def __new__(cls, *args, **kwargs): + if cls.single_object is None: + new = super(CodeManager, cls).__new__(cls, *args, **kwargs) + cls.single_object = new + return new + else: + return cls.single_object + + def __init__(self, indent_symbols=' '*4): + if not self.inited: + self.snippets = [] + self.indent_symbols = indent_symbols + + self.inited = True + + def __len__(self): + return len(self.snippets) + + def _line(self, code, indent_count=0): + return "{indents}{code}".format( + indents=self.indent_symbols * indent_count, + code=code) + + def add(self, snippet): + self.snippets.append(snippet) + + def clear(self): + + """ + Safely clear all the snippents. Reset all the code counters. + """ + + while self.snippets: + self.clear_last() + + def clear_last(self): + + """ + Remove the latest snippet, decrease appropriate code counter + for snippets of INIT type. + """ + + if self.snippets: + last_snippet = self.snippets.pop() + if last_snippet.types & CodeSnippet.INIT_SNIPPET: + last_snippet.owner.release_variable() + + def get_full_code(self): + + """ + Compose complete code from the snippets. + """ + + lines = [] + endings = [] + indent_count = 0 + for snippet in self.snippets: + if snippet.init_code: + lines.append(self._line(snippet.init_code, indent_count)) + + if snippet.indent: + # Add indent if needed. Notice the indent does not affect the + # init_code in this iteration. + indent_count += 1 + + if snippet.action_code: + lines.append(self._line(snippet.action_code, indent_count)) + + if snippet.close_code: + endings.append(self._line(snippet.close_code, indent_count)) + + # Add the close_code codes. + # Reverse the list for a close_code from the first snippet was passed + # at the end of the code. + if lines: + full_code = "\n".join(lines) + full_code += 2*"\n" + full_code += "\n".join(endings[::-1]) + else: + full_code = "" + return full_code + + def get_init_snippet(self, owner): + + """ + Return the owner's the first INIT snippet. + """ + + for snippet in self.snippets: + if snippet.owner == owner and \ + snippet.types & CodeSnippet.INIT_SNIPPET: + return snippet + else: + return None + + def __repr__(self): + return self.get_full_code() + + +class CodeGenerator(object): + + """ + Code generation behavior. Expect be used as one of base classes of the + SWAPYObject's wrapper. + """ + + code_manager = CodeManager() + code_var_name = None # Default value, will be rewrote with composed + # variable name as an instance attribute. + + code_var_counters = {} # Default value, will be rewrote as instance's + # class attribute by get_code_id(cls) + + @classmethod + def get_code_id(cls, var_prefix='default'): + + """ + Increment code id. For example, the script already has + `button1=...` line, so for a new button make `button2=... code`. + The idea is the CodeGenerator's default value + code_var_counters['var_prefix'] will be overwrote by this funk + as a control's wrapper class(e.g Pwa_window) attribute. + Its non default value will be shared for all the control's wrapper + class(e.g Pwa_window) instances. + """ + + if var_prefix not in cls.code_var_counters or \ + cls.code_var_counters[var_prefix] == 0: + cls.code_var_counters[var_prefix] = 1 + return '' # "app=..." instead of "app1=..." + + else: + cls.code_var_counters[var_prefix] += 1 + return cls.code_var_counters[var_prefix] + + @classmethod + def decrement_code_id(cls, var_prefix='default'): + + """ + Decrement code id. + """ + + cls.code_var_counters[var_prefix] -= 1 + + def get_code_self(self): + + """ + Composes code to access the control. + E. g.: `button1 = calcframe1['Button12']` + Pattern may use the next argument: + * {var} + * {parent_var} + * {main_parent_var} + E. g.: `"{var} = {parent_var}['access_name']\n"`. + """ + + pattern = self._code_self + if pattern: + if self.code_var_name is None: + self.code_var_name = self.code_var_pattern.format( + id=self.get_code_id(self.code_var_pattern)) + + format_kwargs = {'var': self.code_var_name} + try: + main_parent = self.code_parents[0] + except IndexError: + main_parent = None + + if self.parent or main_parent: + if self.parent: + format_kwargs['parent_var'] = self.parent.code_var_name + if main_parent: + format_kwargs['main_parent_var'] = main_parent.code_var_name + return pattern.format(**format_kwargs) + return "" + + def get_code_action(self, action): + + """ + Composes code to run an action. E. g.: `button1.Click()` + Pattern may use the next argument: + * {var} + * {action} + * {parent_var} + * {main_parent_var} + E. g.: `"{var}.{action}()\n"`. + """ + + format_kwargs = {'var': self.code_var_name, + 'action': action} + if self.parent: + format_kwargs['parent_var'] = self.parent.code_var_name + + try: + main_parent = self.code_parents[0] + except IndexError: + main_parent = None + + if main_parent: + format_kwargs['main_parent_var'] = main_parent.code_var_name + + return self._code_action.format(**format_kwargs) + + def get_code_close(self): + + """ + Composes code to close the access to the control. E.g.: `app.Kill_()` + Pattern may use the next argument: + * {var} + * {parent_var} + * {main_parent_var} + E. g.: `"{var}.Kill_()\n"`. + """ + + pattern = self._code_close + if pattern: + format_kwargs = {'var': self.code_var_name} + if self.parent: + format_kwargs['parent_var'] = self.parent.code_var_name + + try: + main_parent = self.code_parents[0] + except IndexError: + main_parent = None + + if main_parent: + format_kwargs['main_parent_var'] = main_parent.code_var_name + + return pattern.format(**format_kwargs) + return "" + + def Get_code(self, action=None): + + """ + Return all the code needed to make the action on the control. + Walk parents if needed. + """ + + if not self._check_existence(): # target does not exist + raise Exception("Target object does not exist") + + if self.code_var_name is None: + # parent/s code is not inited + code_parents = self.code_parents[:] + code_parents.reverse() # start from the top level parent + + for p in code_parents: + if not p.code_var_name: + p_code_self = p.get_code_self() + p_close_code = p.get_code_close() + if p_code_self or p_close_code: + parent_snippet = CodeSnippet(p, + init_code=p_code_self, + close_code=p_close_code) + self.code_manager.add(parent_snippet) + + own_code_self = self.get_code_self() + own_close_code = self.get_code_close() + # get_code_action call should be after the get_code_self call + own_code_action = self.get_code_action(action) if action else '' + + if own_code_self or own_close_code or own_code_action: + own_snippet = CodeSnippet(self, + init_code=own_code_self, + action_code=own_code_action, + close_code=own_close_code) + self.code_manager.add(own_snippet) + else: + # Already inited (all parents too), may use get_code_action + own_code_action = self.get_code_action(action) if action else '' + if own_code_action: + new_action_snippet = CodeSnippet(self, + action_code=own_code_action) + self.code_manager.add(new_action_snippet) + + return self.code_manager.get_full_code() + + def update_code_style(self): + + """ + Seeks for the first INIT snippet and update + `init_code` and `close_code`. + """ + + init_code_snippet = self.code_manager.get_init_snippet(self) + if init_code_snippet: + own_code_self = self.get_code_self() + own_close_code = self.get_code_close() + if own_code_self or own_close_code: + init_code_snippet.update(init_code=own_code_self, + close_code=own_close_code) + + def release_variable(self): + + """ + Clear the access variable to mark the object is not inited and + make possible other use the variable name. + """ + + if self.code_var_name: + self.code_var_name = None + self.decrement_code_id(self.code_var_pattern) + + +if __name__ == '__main__': + c1 = CodeSnippet(None, 'with Start_ as app:', 'frame1 = app.Frame', '', + True) + c2 = CodeSnippet(None, 'button1 = frame1.button', 'button1.Click()', + 'del button1') + c3 = CodeSnippet(None, 'button2 = frame1.button', 'button2.Click()') + c4 = CodeSnippet(None, 'with Start_ as app:', 'frame2 = app.Frame', '', + True) + c5 = CodeSnippet(None, '', 'button1.Click()') + cm = CodeManager() + + print c1 + [cm.add(i) for i in [c1, c2, c3, c4, c5]] + + print id(c2) + print cm + + c2.update('button1 = the_change', 'the_change.Click()', + 'del the_change') + print id(c2) + print cm diff --git a/const.py b/const.py index 9808001..bf0141c 100644 --- a/const.py +++ b/const.py @@ -19,29 +19,48 @@ # Boston, MA 02111-1307 USA -ACTIONS = {101 : 'Close', - 102 : 'Click', - 103 : 'ClickInput', - 104 : 'CloseClick', - 105 : 'DoubleClick', - 106 : 'DoubleClickInput', - 107 : 'DragMouse', - 108 : 'DrawOutline', - 109 : 'Maximize', - 110 : 'Minimize', - 111 : 'MoveMouse', - 112 : 'MoveWindow', - 113 : 'PressMouse', - 114 : 'PressMouseInput', - 115 : 'ReleaseMouse', - 116 : 'ReleaseMouseInput', - 117 : 'Restore', - 118 : 'RightClick', - 119 : 'RightClickInput', - 120 : 'SetFocus', - 121 : 'Select', - 122 : 'Collapse', - 123 : 'Expand', - } +ACTIONS = {101: 'Close', + 102: 'Click', + 103: 'ClickInput', + 104: 'CloseClick', + 105: 'DoubleClick', + 106: 'DoubleClickInput', + 107: 'DragMouse', + 108: 'DrawOutline', + 109: 'Maximize', + 110: 'Minimize', + 111: 'MoveMouse', + 112: 'MoveWindow', + 113: 'PressMouse', + 114: 'PressMouseInput', + 115: 'ReleaseMouse', + 116: 'ReleaseMouseInput', + 117: 'Restore', + 118: 'RightClick', + 119: 'RightClickInput', + 120: 'SetFocus', + 121: 'Select', + 122: 'Collapse', + 123: 'Expand', + } + +EXTENDED_ACTIONS = {201: 'Application.Start', + 202: 'Application.Connect', + } + +PROPERTIES_ACTIONS = {301: 'Copy all', + 302: None, + 303: 'Copy property', + 304: 'Copy value', + 305: 'Copy unicode value', + } + +EDITOR_ACTIONS = {401: 'Clear last command', + 402: 'Clear the code', + 403: None, + 404: 'Copy', + 405: 'Select all', + 406: None, + 407: 'Save code to file'} -VERSION = '0.4.6' +VERSION = '0.4.8' diff --git a/proxy.py b/proxy.py index fb7cb5e..510faa4 100644 --- a/proxy.py +++ b/proxy.py @@ -18,14 +18,18 @@ # Suite 330, # Boston, MA 02111-1307 USA -import pywinauto -import sys, os -import time -import wx -import thread import exceptions import platform +import os +import sys +import string +import time +import thread import warnings + +import pywinauto + +from code_manager import CodeGenerator, check_valid_identifier from const import * ''' @@ -35,6 +39,7 @@ pywinauto.timings.Timings.window_find_timeout = 1 + def resource_path(filename): if hasattr(sys, '_MEIPASS'): # PyInstaller >= 1.6 @@ -49,57 +54,43 @@ def resource_path(filename): filename = os.path.join(os.path.dirname(sys.argv[0]), filename) return filename -class SWAPYObject(object): - ''' + +class PwaWrapper(object): + + """ Base proxy class for pywinauto objects. - - ''' - def __init__(self, pwa_obj): + """ + + def __init__(self, pwa_obj, parent=None): ''' Constructor ''' #original pywinauto object self.pwa_obj = pwa_obj + self.parent = parent default_sort_key = lambda name: name[0].lower() self.subitems_sort_key = default_sort_key - + def GetProperties(self): ''' - Return dict of original + additional properies - Can be owerridden for non pywinauto obects + Return dict of original + additional properties + Can be overridden for non pywinauto objects ''' properties = {} - properties.update(self._get_properies()) + properties.update(self._get_properties()) properties.update(self._get_additional_properties()) return properties def Get_subitems(self): ''' Return list of children - [(control_text, swapy_obj),...] - Can be owerridden for non pywinauto obects + Can be overridden for non pywinauto objects ''' subitems = [] + subitems += self._get_children() - ''' - for control in children: - try: - texts = control.Texts() - except exceptions.RuntimeError: - texts = ['Unknown control name2!'] #workaround - while texts.count(''): - texts.remove('') - c_name = ', '.join(texts) - if not c_name: - #nontext_controlname = pywinauto.findbestmatch.GetNonTextControlName(control, children)[0] - top_level_parent = control.TopLevelParent().Children() - nontext_controlname = pywinauto.findbestmatch.GetNonTextControlName(control, top_level_parent)[0] - if nontext_controlname: - c_name = nontext_controlname - else: - c_name = 'Unknown control name1!' - subitems.append((c_name, self._get_swapy_object(control))) - ''' subitems += self._get_additional_children() + subitems.sort(key=self.subitems_sort_key) #encode names subitems_encoded = [] @@ -108,47 +99,45 @@ def Get_subitems(self): subitems_encoded.append((name, obj)) return subitems_encoded - def Exec_action(self, action_id): + def Exec_action(self, action): ''' Execute action on the control ''' - action = ACTIONS[action_id] #print('self.pwa_obj.'+action+'()') exec('self.pwa_obj.'+action+'()') return 0 def Get_actions(self): - ''' + + """ return allowed actions for this object. [(id,action_name),...] - ''' + """ + allowed_actions = [] try: obj_actions = dir(self.pwa_obj.WrapperObject()) except: obj_actions = dir(self.pwa_obj) - for id, action in ACTIONS.items(): + for _id, action in ACTIONS.items(): if action in obj_actions: - allowed_actions.append((id,action)) + allowed_actions.append((_id, action)) allowed_actions.sort(key=lambda name: name[1].lower()) return allowed_actions - - def Get_code(self, action_id): - ''' - Generate code for pywinauto module - ''' - action = ACTIONS[action_id] - code = "\ -ctrl = window['"+self._get_additional_properties()['Access names'][0].encode('unicode-escape', 'replace')+"']\n\ -ctrl."+action+"()\n" - return code - + + def Get_extended_actions(self): + + """ + Extended actions + """ + + return [] + def Highlight_control(self): if self._check_visibility(): thread.start_new_thread(self._highlight_control,(3,)) return 0 - - - def _get_properies(self): + + def _get_properties(self): ''' Get original pywinauto's object properties ''' @@ -160,32 +149,18 @@ def _get_properies(self): return properties def _get_additional_properties(self): - ''' - Get additonal useful properties, like a handle, process ID, etc. + + """ + Get additional useful properties, like a handle, process ID, etc. Can be overridden by derived class - ''' + """ + additional_properties = {} - pwa_app = pywinauto.application.Application() + #-----Access names - try: - #parent_obj = self.pwa_obj.Parent() - parent_obj = self.pwa_obj.TopLevelParent() - except: - pass - else: - try: - #all_controls = parent_obj.Children() - all_controls = [pwa_app.window_(handle=ch) for ch in pywinauto.findwindows.find_windows(parent=parent_obj.handle, top_level_only=False)] - except: - pass - else: - access_names = [] - uniq_names = pywinauto.findbestmatch.build_unique_dict(all_controls) - for uniq_name, obj in uniq_names.items(): - if uniq_name != '' and obj.WrapperObject() == self.pwa_obj: - access_names.append(uniq_name) - access_names.sort(key=len) - additional_properties.update({'Access names' : access_names}) + access_names = [name for name, obj in self.__get_uniq_names(target_control=self.pwa_obj)] + if access_names: + additional_properties.update({'Access names' : access_names}) #----- #-----pwa_type @@ -201,55 +176,61 @@ def _get_additional_properties(self): return additional_properties def _get_children(self): - ''' + + """ Return original pywinauto's object children & names [(control_text, swapy_obj),...] - ''' - def _get_name_control(control): - try: - texts = control.Texts() - except exceptions.WindowsError: - texts = ['Unknown control name2!'] #workaround for WindowsError: [Error 0] ... - except exceptions.RuntimeError: - texts = ['Unknown control name3!'] #workaround for RuntimeError: GetButtonInfo failed for button with command id 256 - while texts.count(''): - texts.remove('') - text = ', '.join(texts) - if not text: - u_names = [] - for uniq_name, obj in uniq_names.items(): - if uniq_name != '' and obj.WrapperObject() == control: - #if uniq_name != '' and obj == control: - u_names.append(uniq_name) - if u_names: - u_names.sort(key=len) - name = u_names[-1] + """ + + if self.pwa_obj.Parent() and isinstance(self.parent, Pwa_window): + # Hide children of the non top level window control. + # Expect all the children are accessible from the top level window. + return [] + + u_names = None + children = [] + children_controls = self.pwa_obj.Children() + for child_control in children_controls: + try: + texts = child_control.Texts() + except exceptions.WindowsError: + # texts = ['Unknown control name2!'] #workaround for + # WindowsError: [Error 0] ... + texts = None + except exceptions.RuntimeError: + # texts = ['Unknown control name3!'] #workaround for + # RuntimeError: GetButtonInfo failed for button + # with command id 256 + texts = None + + if texts: + texts = filter(bool, texts) # filter out '' and None items + + if texts: # check again after the filtering + title = ', '.join(texts) else: - name = 'Unknown control name1!' - else: - name = text - return (name, self._get_swapy_object(control)) - - pwa_app = pywinauto.application.Application() - try: - parent_obj = self.pwa_obj.TopLevelParent() - except pywinauto.controls.HwndWrapper.InvalidWindowHandle: - #For non visible windows - #... - #InvalidWindowHandle: Handle 0x262710 is not a vaild window handle - parent_obj = self.pwa_obj - children = self.pwa_obj.Children() - visible_controls = [pwa_app.window_(handle=ch) for ch in pywinauto.findwindows.find_windows(parent=parent_obj.handle, top_level_only=False)] - uniq_names = pywinauto.findbestmatch.build_unique_dict(visible_controls) - #uniq_names = pywinauto.findbestmatch.build_unique_dict(children) - names_children = map(_get_name_control, children) - return names_children - - + # .Texts() does not have a useful title, trying get it + # from the uniqnames + if u_names is None: + # init unames list + u_names = self.__get_uniq_names() + + child_uniq_name = [u_name for u_name, obj in u_names + if obj.WrapperObject() == child_control] + + if child_uniq_name: + title = child_uniq_name[-1] + else: + # uniqnames has no useful title + title = 'Unknown control name1!' + children.append((title, self._get_swapy_object(child_control))) + + return children + def _get_additional_children(self): ''' - Get additonal children, like for a menu, submenu, subtab, etc. - Should be owerriden in derived classes of non standart pywinauto object + Get additional children, like for a menu, submenu, subtab, etc. + Should be overridden in derived classes of non standard pywinauto object ''' return [] @@ -265,6 +246,8 @@ def _get_pywinobj_type(self, obj): return 'menu_item' elif type(obj) == pywinauto.controls.win32_controls.ComboBoxWrapper: return 'combobox' + elif type(obj) == pywinauto.controls.win32_controls.ListBoxWrapper: + return 'listbox' elif type(obj) == pywinauto.controls.common_controls.ListViewWrapper: return 'listview' elif type(obj) == pywinauto.controls.common_controls.TabControlWrapper: @@ -277,39 +260,38 @@ def _get_pywinobj_type(self, obj): return 'tree_view' elif type(obj) == pywinauto.controls.common_controls._treeview_element: return 'tree_item' - elif 1==0: - return 'other' else: return 'unknown' def _get_swapy_object(self, pwa_obj): pwa_type = self._get_pywinobj_type(pwa_obj) #print pwa_type - if pwa_type == 'smt_NEW': - return smt_NEW(pwa_obj) if pwa_type == 'window': - return Pwa_window(pwa_obj) + process = Process(self, pwa_obj.ProcessID()) + return Pwa_window(pwa_obj, process) if pwa_type == 'menu': - return Pwa_menu(pwa_obj) + return Pwa_menu(pwa_obj, self) if pwa_type == 'menu_item': - return Pwa_menu_item(pwa_obj) + return Pwa_menu_item(pwa_obj, self) if pwa_type == 'combobox': - return Pwa_combobox(pwa_obj) + return Pwa_combobox(pwa_obj, self) + if pwa_type == 'listbox': + return Pwa_listbox(pwa_obj, self) if pwa_type == 'listview': - return Pwa_listview(pwa_obj) + return Pwa_listview(pwa_obj, self) if pwa_type == 'tab': - return Pwa_tab(pwa_obj) + return Pwa_tab(pwa_obj, self) if pwa_type == 'toolbar': - return Pwa_toolbar(pwa_obj) + return Pwa_toolbar(pwa_obj, self) if pwa_type == 'toolbar_button': - return Pwa_toolbar_button(pwa_obj) + return Pwa_toolbar_button(pwa_obj, self) if pwa_type == 'tree_view': - return Pwa_tree(pwa_obj) + return Pwa_tree(pwa_obj, self) if pwa_type == 'tree_item': - return Pwa_tree_item(pwa_obj) + return Pwa_tree_item(pwa_obj, self) else: - return SWAPYObject(pwa_obj) - + return SWAPYObject(pwa_obj, self) + def _highlight_control(self, repeat = 1): while repeat > 0: repeat -= 1 @@ -358,8 +340,144 @@ def _check_existence(self): else: is_exist = obj.Exists() return is_exist - - + + def __get_uniq_names(self, target_control=None): + + """ + Return uniq_names of the control + [(uniq_name, obj), ] + If target_control specified, apply additional filtering for obj == target_control + """ + + # TODO: cache this method + + pwa_app = pywinauto.application.Application() # TODO: do not call .Application() everywhere. + + try: + parent_obj = self.pwa_obj.TopLevelParent() + except pywinauto.controls.HwndWrapper.InvalidWindowHandle: + #For non visible windows + #... + #InvalidWindowHandle: Handle 0x262710 is not a valid window handle + parent_obj = self.pwa_obj + except AttributeError: + return [] + + visible_controls = [pwa_app.window_(handle=ch) for ch in + pywinauto.findwindows.find_windows(parent=parent_obj.handle, top_level_only=False)] + uniq_names_obj = [(uniq_name, obj) for uniq_name, obj + in pywinauto.findbestmatch.build_unique_dict(visible_controls).items() + if uniq_name != '' and (not target_control or obj.WrapperObject() == target_control)] + return sorted(uniq_names_obj, key=lambda name_obj: len(name_obj[0])) # sort by name + + +class SWAPYObject(PwaWrapper, CodeGenerator): + + """ + Mix the pywinauto wrapper and the code generator + """ + + code_self_pattern_attr = "{var} = {parent_var}.{access_name}" + code_self_pattern_item = "{var} = {parent_var}[{access_name}]" + code_action_pattern = "{var}.{action}()" + main_parent_type = None + short_name = 'control' + __code_var_pattern = None # cached value, to access even if the pwa + # object was closed + + def __init__(self, *args, **kwargs): + super(SWAPYObject, self).__init__(*args, **kwargs) + self.code_parents = self.get_code_parents() + + def get_code_parents(self): + + """ + Collect a list of all parents needed to access the control. + Some parents may be excluded regarding to the `self.main_parent_type` parameter. + """ + + grab_all = True if not self.main_parent_type else False + code_parents = [] + parent = self.parent + while parent: + if not grab_all and isinstance(parent, self.main_parent_type): + grab_all = True + + if grab_all: + code_parents.append(parent) + parent = parent.parent + return code_parents + + @property + def _code_self(self): + + """ + Default _code_self. + """ + #print self._get_additional_properties() + access_name = self.GetProperties()['Access names'][0] + + if check_valid_identifier(access_name): + # A valid identifier + code = self.code_self_pattern_attr.format( + access_name=access_name, parent_var="{parent_var}", + var="{var}") + else: + #Not valid, encode and use as app's item. + if isinstance(access_name, unicode): + access_name = "u'%s'" % access_name.encode('unicode-escape') + elif isinstance(access_name, str): + access_name = "'%s'" % access_name + code = self.code_self_pattern_item.format( + access_name=access_name, parent_var="{parent_var}", + var="{var}") + return code + + @property + def _code_action(self): + + """ + Default _code_action. + """ + code = self.code_action_pattern + return code + + @property + def _code_close(self): + + """ + Default _code_close. + """ + return "" + + @property + def code_var_pattern(self): + + """ + Compose variable prefix, based on the control Class or + short name of the SWAPY wrapper class. + """ + + if self.__code_var_pattern is None: + var_prefix = self.short_name + if 'Class' in self.GetProperties(): + crtl_class = filter(lambda c: c in string.ascii_letters, + self.GetProperties()['Class']).lower() + if crtl_class: + var_prefix = crtl_class + + self.__code_var_pattern = "{var_prefix}{id}".format( + var_prefix=var_prefix, id="{id}") + return self.__code_var_pattern + + def SetCodestyle(self, extended_action_id): + + """ + Switch a control code style regarding extended_action_id + """ + + pass + class VirtualSWAPYObject(SWAPYObject): def __init__(self, parent, index): @@ -369,25 +487,39 @@ def __init__(self, parent, index): self._check_visibility = self.parent._check_visibility self._check_actionable = self.parent._check_actionable self._check_existence = self.parent._check_existence + self.code_parents = self.get_code_parents() + + code_action_pattern = "{parent_var}.{action}({index})" + + @property + def _code_self(self): + + """ + Rewrite default behavior. + """ + return "" + + @property + def _code_action(self): + index = self.index + if isinstance(index, unicode): + index = "u'%s'" % index.encode('unicode-escape') + elif isinstance(index, str): + index = "'%s'" % index + code = self.code_action_pattern.format(index=index, + action="{action}", + var="{var}", + parent_var="{parent_var}") + return code + + @property + def code_var_pattern(self): + raise Exception('Must not be used "code_var_pattern" prop for a VirtualSWAPYObject') def Select(self): self.parent.pwa_obj.Select(self.index) - - def Get_code(self, action_id): - ''' - Generate code for pywinauto module - ''' - action = ACTIONS[action_id] - arg = "" - try: - arg = "'"+self.index.encode('unicode-escape', 'replace')+"'" - except: - arg = str(self.index) - code = "\ -ctrl."+action+"("+arg+")\n" - return code - - def _get_properies(self): + + def _get_properties(self): return {} def Get_subitems(self): @@ -396,16 +528,38 @@ def Get_subitems(self): def Highlight_control(self): pass return 0 - ''' - def Get_code(self, action_id): - - return '#---Not implemented yet.---\n' - ''' - + class PC_system(SWAPYObject): handle = 0 - + short_name = 'pc' # hope it never be used in the code generator + + single_object = None + inited = False + + def __new__(cls, *args, **kwargs): + if cls.single_object is None: + new = super(PC_system, cls).__new__(cls, *args, **kwargs) + cls.single_object = new + return new + else: + return cls.single_object + + def __init__(self, *args, **kwargs): + if not self.inited: + super(PC_system, self).__init__(*args, **kwargs) + self.inited = True + + @property + def _code_self(self): + # code = self.code_self_pattern.format(var="{var}") + # return code + return "from pywinauto.application import Application" + # + # @property + # def code_var_pattern(self): + # return "app{id}".format(id="{id}") + def Get_subitems(self): ''' returns [(window_text, swapy_obj),...] @@ -433,15 +587,14 @@ def Get_subitems(self): for w_handle in handles: wind = app.window_(handle=w_handle) if w_handle == taskbar_handle: - texts = ['TaskBar'] + title = 'TaskBar' else: texts = wind.Texts() - while texts.count(''): - texts.remove('') - title = ', '.join(texts) - if not title: - title = 'Window#%s' % w_handle - #title = title.encode('cp1251', 'replace') + texts = filter(bool, texts) # filter out '' and None items + if not texts: + title = 'Window#%s' % w_handle + else: + title = ', '.join(texts) windows.append((title, self._get_swapy_object(wind))) windows.sort(key=lambda name: name[0].lower()) #----------------------- @@ -450,11 +603,10 @@ def Get_subitems(self): #------------------------ return windows - def _get_properies(self): - info = { 'Platform' : platform.platform(), \ - 'Processor' : platform.processor(), \ - 'PC name' : platform.node() } - + def _get_properties(self): + info = {'Platform': platform.platform(), + 'Processor': platform.processor(), + 'PC name': platform.node()} return info def Get_actions(self): @@ -462,13 +614,7 @@ def Get_actions(self): No actions for PC_system ''' return [] - - def Get_code(self, action_id): - ''' - No code for PC_system - ''' - return '' - + def Highlight_control(self): pass return 0 @@ -482,7 +628,146 @@ def _check_actionable(self): def _check_existence(self): return True + +class Process(CodeGenerator): + + """ + Virtual parent for window objects. + It will never be shown in the object browser. Used to hold 'app' counter + independent of 'window' counters. + """ + processes = {} + inited = False + main_window = None + + def __new__(cls, parent, pid): + if pid in cls.processes: + return cls.processes[pid] + else: + new_process = super(Process, cls).__new__(cls, parent, pid) + cls.processes[pid] = new_process + return new_process + + def __init__(self, parent, pid): + if not self.inited: + self.parent = parent + self._var_name = None + + self.inited = True + + @property + def _code_self(self): + return "" + + @property + def _code_action(self): + return "" + + @property + def _code_close(self): + return "" + + @property + def code_var_pattern(self): + return "{var_prefix}{id}".format(var_prefix='app', id="{id}") + + @property + def code_var_name(self): + if self._var_name is None: + self._var_name = self.code_var_pattern.format( + id=self.get_code_id(self.code_var_pattern)) + return self._var_name + + class Pwa_window(SWAPYObject): + code_self_close = "{parent_var}.Kill_()" + short_name = 'window' + + handles = {} + inited = False + + def __new__(cls, pwa_obj, parent=None): + if pwa_obj.handle in cls.handles: + return cls.handles[pwa_obj.handle] + else: + new_window = super(Pwa_window, cls).__new__(cls, pwa_obj, + parent=None) + cls.handles[pwa_obj.handle] = new_window + return new_window + + def __init__(self, *args, **kwargs): + if not self.inited: + # Set default style + self.code_self_style = self.__code_self_start + self.code_close_style = self.__code_close_start + super(Pwa_window, self).__init__(*args, **kwargs) + + self.inited = True + + def __code_self_connect(self): + title = self.pwa_obj.WindowText().encode('unicode-escape') + cls_name = self.pwa_obj.Class() + code = "\n{parent_var} = Application().Connect(title=u'{title}', " \ + "class_name='{cls_name}')\n".format(title=title, + cls_name=cls_name, + parent_var="{parent_var}") + return code + + def __code_self_start(self): + target_pid = self.pwa_obj.ProcessID() + cmd_line = None + process_modules = pywinauto.application._process_get_modules_wmi() + for pid, name, process_cmdline in process_modules: + if pid == target_pid: + cmd_line = os.path.normpath(process_cmdline) + cmd_line = cmd_line.encode('unicode-escape') + break + code = "\n{parent_var} = Application().Start(cmd_line=u'{cmd_line}')\n"\ + .format(cmd_line=cmd_line, parent_var="{parent_var}") + return code + + def __code_close_connect(self): + return "" + + def __code_close_start(self): + return self.code_self_close.format(parent_var="{parent_var}") + + @property + def _code_self(self): + code = "" + if not self._get_additional_properties()['Access names']: + raise NotImplementedError + else: + is_main_window = bool(self.parent.main_window is None or + self.parent.main_window == self or + self.parent.main_window.code_var_name is None) + + if is_main_window: + code += self.code_self_style() + self.parent.main_window = self + code += super(Pwa_window, self)._code_self + if is_main_window and \ + self.code_self_style == self.__code_self_start: + code += "\n{var}.Wait('ready')" + self.parent.main_window = self + + return code + + @property + def _code_close(self): + + """ + Rewrite default behavior. + """ + code = "" + is_main_window = bool(self.parent.main_window is None or + self.parent.main_window == self or + self.parent.main_window.code_var_name is None) + if is_main_window: + code = self.code_close_style() + + return code + def _get_additional_children(self): ''' Add menu object as children @@ -493,20 +778,78 @@ def _get_additional_children(self): menu_child = [('!Menu', self._get_swapy_object(menu))] additional_children += menu_child return additional_children - - def Get_code(self, action_id): + + def _get_additional_properties(self): ''' - winod code + Get additional useful properties, like a handle, process ID, etc. + Can be overridden by derived class ''' - action = ACTIONS[action_id] - code = "\ -w_handle = pywinauto.findwindows.find_windows(title=u'"+ self.pwa_obj.WindowText().encode('unicode-escape', 'replace') +"', class_name='"+ self.pwa_obj.Class() +"')[0]\n\ -window = pwa_app.window_(handle=w_handle)\n\ -window."+action+"()\n" - return code - + additional_properties = {} + pwa_app = pywinauto.application.Application() + #-----Access names + + access_names = [name for name in pywinauto.findbestmatch.build_unique_dict([self.pwa_obj]).keys() if name != ''] + access_names.sort(key=len) + additional_properties.update({'Access names': access_names}) + #----- + + #-----pwa_type + additional_properties.update({'pwa_type': str(type(self.pwa_obj))}) + #--- + + #-----handle + try: + additional_properties.update({'handle': str(self.pwa_obj.handle)}) + except: + pass + #--- + return additional_properties + + def Get_extended_actions(self): + + """ + Extended actions + """ + + return [(_id, action) for _id, action in EXTENDED_ACTIONS.items()] + + def SetCodestyle(self, extended_action_id): + + """ + Switch to `Start` or `Connect` code + """ + + if 'Application.Start' == EXTENDED_ACTIONS[extended_action_id]: + self.code_self_style = self.__code_self_start + self.code_close_style = self.__code_close_start + + elif 'Application.Connect' == EXTENDED_ACTIONS[extended_action_id]: + self.code_self_style = self.__code_self_connect + self.code_close_style = self.__code_close_connect + + else: + raise RuntimeError("Unknown menu id - %s" % extended_action_id) + + # if self.code_snippet is not None: + # # Refresh self code after the changing of the code style + # own_code_self = self.get_code_self() + # own_close_code = self.get_code_close() + # self.code_snippet.update(init_code=own_code_self, + # close_code=own_close_code) + + self.update_code_style() + + def release_variable(self): + super(Pwa_window, self).release_variable() + if self.parent._var_name: + self.parent._var_name = None + self.parent.decrement_code_id(self.parent.code_var_pattern) + + class Pwa_menu(SWAPYObject): + short_name = 'menu' + def _check_visibility(self): is_visible = False try: @@ -546,7 +889,7 @@ def _get_additional_children(self): menu_items = self.pwa_obj.Items() for menu_item in menu_items: item_text = menu_item.Text() - if item_text == '': + if not item_text: if menu_item.Type() == 2048: item_text = '-----Separator-----' else: @@ -565,9 +908,23 @@ def _get_children(self): def Highlight_control(self): pass return 0 - + + class Pwa_menu_item(Pwa_menu): + short_name = 'menu_item' + + main_parent_type = Pwa_window + code_self_pattern = "{var} = {main_parent_var}.MenuItem(u'{menu_path}')" + + @property + def _code_self(self): + menu_path = self.get_menuitems_path().encode('unicode-escape') + code = self.code_self_pattern.format( + menu_path=menu_path, main_parent_var="{main_parent_var}", + var="{var}") + return code + def _check_actionable(self): if self.pwa_obj.State() == 3: #grayed is_actionable = False @@ -605,80 +962,184 @@ def get_menuitems_path(self): menu = owner_item.menu owner_item = menu.owner_item return '->'.join(path[::-1]) - - def Get_code(self, action_id): - ''' - Generate code for pywinauto module - ''' - action = ACTIONS[action_id] - code = "\ -window.MenuItem(u'"+self.get_menuitems_path().encode('unicode-escape', 'replace')+"')."+action+"()\n\ -" - return code - + + class Pwa_combobox(SWAPYObject): + + short_name = 'combobox' + def _get_additional_children(self): ''' Add ComboBox items as children ''' additional_children = [] - items_texts = self.pwa_obj.ItemTexts() - for item_name in items_texts: - additional_children += [(item_name, virtual_combobox_item(self, item_name))] + for i, text in enumerate(self.pwa_obj.ItemTexts()): + if not text: + text = "option #%s" % i + additional_children.append((text, + virtual_combobox_item(self, i))) + else: + additional_children.append((text, + virtual_combobox_item(self, text))) return additional_children - + + class virtual_combobox_item(VirtualSWAPYObject): - def _get_properies(self): + def _get_properties(self): index = None text = self.index for i, name in enumerate(self.parent.pwa_obj.ItemTexts()): if name == text: index = i break - return {'Index' : index, 'Text' : text} # .encode('unicode-escape', 'replace')} - + return {'Index': index, 'Text': text} + + +class Pwa_listbox(SWAPYObject): + + short_name = 'listbox' + + def _get_additional_children(self): + + """ + Add ListBox items as children + """ + + additional_children = [] + for i, text in enumerate(self.pwa_obj.ItemTexts()): + if not text: + text = "option #%s" % i + additional_children.append((text, + virtual_listbox_item(self, i))) + else: + additional_children.append((text, + virtual_listbox_item(self, text))) + return additional_children + + +class virtual_listbox_item(VirtualSWAPYObject): + + def _get_properties(self): + index = None + text = self.index + for i, name in enumerate(self.parent.pwa_obj.ItemTexts()): + if name == text: + index = i + break + return {'Index': index, 'Text': text} + + class Pwa_listview(SWAPYObject): + + short_name = 'listview' + def _get_additional_children(self): ''' Add SysListView32 items as children ''' additional_children = [] - for index in range(self.pwa_obj.ItemCount()): - item = self.pwa_obj.GetItem(index) - additional_children += [(item['text'], virtual_listview_item(self, index))] + for item in self.pwa_obj.Items(): + text = item.Text() + if not text: + index = item.item_index + column_index = item.subitem_index + text = "option #%s,%s" % (index, column_index) + additional_children += [(text, listview_item(item, self))] return additional_children - -class virtual_listview_item(VirtualSWAPYObject): - def _get_properies(self): - item_properties = {'Index' : self.index} - for index, item_props in enumerate(self.parent.pwa_obj.Items()): - if index == self.index: - item_properties.update(item_props) - break + +class listview_item(SWAPYObject): + + code_self_patt_text = "{var} = {parent_var}.GetItem({text})" + code_self_patt_index = "{var} = {parent_var}.GetItem({index}, {col_index})" + short_name = 'listview_item' + + @property + def _code_self(self): + text = self.pwa_obj.Text() + if not text: + index = self.pwa_obj.item_index + col_index = self.pwa_obj.subitem_index + code = self.code_self_patt_index.format(index=index, + col_index=col_index, + parent_var="{parent_var}", + var="{var}") + else: + if isinstance(text, unicode): + text = "u'%s'" % text.encode('unicode-escape') + elif isinstance(text, str): + text = "'%s'" % text + code = self.code_self_patt_text.format(text=text, + parent_var="{parent_var}", + var="{var}") + return code + + def _get_properties(self): + item_properties = {'index': self.pwa_obj.item_index, + 'column_index': self.pwa_obj.subitem_index} + item_properties.update(self.pwa_obj.ItemData()) return item_properties + def _check_visibility(self): + return True + + def _check_actionable(self): + return True + + def _check_existence(self): + return True + + def Get_subitems(self): + return [] + + def Highlight_control(self): + pass + return 0 + + class Pwa_tab(SWAPYObject): + + short_name = 'tab' + def _get_additional_children(self): - ''' + + """ Add TabControl items as children - ''' + """ + additional_children = [] for index in range(self.pwa_obj.TabCount()): text = self.pwa_obj.GetTabText(index) + if not text: + text = "tab #%s" % index additional_children += [(text, virtual_tab_item(self, index))] return additional_children - + + class virtual_tab_item(VirtualSWAPYObject): - def _get_properies(self): + @property + def _code_action(self): + index = self.parent.pwa_obj.GetTabText(self.index) + if isinstance(index, unicode): + index = "u'%s'" % index.encode('unicode-escape') + code = self.code_action_pattern.format(index=index, + action="{action}", + var="{var}", + parent_var="{parent_var}") + return code + + def _get_properties(self): item_properties = {'Index' : self.index, 'Texts': self.parent.pwa_obj.GetTabText(self.index)} return item_properties + class Pwa_toolbar(SWAPYObject): + short_name = 'toolbar' + def _get_additional_children(self): ''' Add button objects as children @@ -686,15 +1147,18 @@ def _get_additional_children(self): additional_children = [] buttons_count = self.pwa_obj.ButtonCount() for button_index in range(buttons_count): - try: - button_text = self.pwa_obj.Button(button_index).info.text - button_object = self._get_swapy_object(self.pwa_obj.Button(button_index)) - except exceptions.RuntimeError: - #button_text = ['Unknown button name1!'] #workaround for RuntimeError: GetButtonInfo failed for button with index 0 - pass #ignore the button - else: - button_item = [(button_text, button_object)] - additional_children += button_item + try: + button = self.pwa_obj.Button(button_index) + button_text = button.info.text + if not button_text: + button_text = "button #%s" % button_index + button_object = self._get_swapy_object(button) + except exceptions.RuntimeError: + #button_text = ['Unknown button name1!'] #workaround for RuntimeError: GetButtonInfo failed for button with index 0 + pass #ignore the button + else: + button_item = [(button_text, button_object)] + additional_children += button_item return additional_children def _get_children(self): @@ -703,9 +1167,32 @@ def _get_children(self): ''' return [] - + + class Pwa_toolbar_button(SWAPYObject): - + + code_self_pattern = "{var} = {parent_var}.Button({index})" + short_name = 'toolbar_button' + + @property + def _code_self(self): + text = self.pwa_obj.info.text + if not text: + index = self.pwa_obj.index + else: + index = text + + if isinstance(index, unicode): + index = "u'%s'" % index.encode('unicode-escape') + elif isinstance(index, str): + index = "'%s'" % index + + code = self.code_self_pattern.format(index=index, + action="{action}", + var="{var}", + parent_var="{parent_var}") + return code + def _check_visibility(self): is_visible = False try: @@ -736,35 +1223,29 @@ def _check_existence(self): def _get_children(self): return [] - def _get_properies(self): + def _get_properties(self): o = self.pwa_obj - props = {'IsCheckable' : o.IsCheckable(), - 'IsChecked' : o.IsChecked(), + props = {'IsCheckable': o.IsCheckable(), + 'IsChecked': o.IsChecked(), 'IsEnabled': o.IsEnabled(), - 'IsPressable' : o.IsPressable(), - 'IsPressed' : o.IsPressed(), - 'Rectangle' : o.Rectangle(), - 'State' : o.State(), - 'Style' : o.Style(), - 'index' : o.index,} + 'IsPressable': o.IsPressable(), + 'IsPressed': o.IsPressed(), + 'Rectangle': o.Rectangle(), + 'State': o.State(), + 'Style': o.Style(), + 'index': o.index, + 'text': o.info.text} return props def Highlight_control(self): pass return 0 - - def Get_code(self, action_id): - ''' - Generate code for pywinauto module - ''' - action = ACTIONS[action_id] - arg = str(self.pwa_obj.index) - code = "\ -ctrl.Button("+arg+")."+action+"()\n" - return code + class Pwa_tree(SWAPYObject): + short_name = 'tree' + def _get_additional_children(self): ''' Add roots object as children @@ -784,9 +1265,25 @@ def Highlight_control(self): pass return 0 + class Pwa_tree_item(SWAPYObject): - def _get_properies(self): + main_parent_type = Pwa_tree + code_self_pattern = "{var} = {main_parent_var}.GetItem({path})" + short_name = 'tree_item' + + @property + def _code_self(self): + path = self.path + for i in range(len(path)): + if isinstance(path[i], unicode): + path[i] = u'%s' % path[i].encode('unicode-escape') + + code = self.code_self_pattern.format( + path=path, var="{var}", main_parent_var="{main_parent_var}") + return code + + def _get_properties(self): o = self.pwa_obj props = {'Rectangle' : o.Rectangle(), 'State' : o.State(), @@ -795,12 +1292,18 @@ def _get_properies(self): def _check_visibility(self): return True + # TODO: It seems like pywinauto bug + #return self.pwa_obj.EnsureVisible() def _check_existence(self): return True def _check_actionable(self): - return True + if self.parent.pwa_obj != self.pwa_obj.tree_ctrl: + # the parent is also tree item + return self.parent.pwa_obj.IsExpanded() + else: + return True def _get_children(self): return [] @@ -823,12 +1326,3 @@ def _get_additional_children(self): sub_item = [(item_text, obj)] additional_children += sub_item return additional_children - - def Get_code(self, action_id): - ''' - Generate code for pywinauto module - ''' - action = ACTIONS[action_id] - code = "\ -ctrl.GetItem("+str(self.path)+")."+action+"()\n" - return code \ No newline at end of file diff --git a/swapy-ob.py b/swapy-ob.py index 3912324..7eced83 100644 --- a/swapy-ob.py +++ b/swapy-ob.py @@ -20,12 +20,33 @@ #Boa:App:BoaApp +import sys +import traceback import wx import _mainframe + +def hook(exctype, value, tb): + + """ + Handle all unexpected exceptions. Show the msgbox then close main window. + """ + + frame = wx.GetApp().GetTopWindow() + traceback_text = ''.join(traceback.format_exception(exctype, value, tb, 5)) + dlg = wx.MessageDialog(frame, traceback_text, + 'Error!', wx.OK | wx.ICON_ERROR) + dlg.ShowModal() + dlg.Destroy() + frame.Destroy() + +sys.excepthook = hook + + modules ={'_mainframe': [0, '', '_mainframe.py'], 'proxy': [0, '', 'proxy.py']} + class BoaApp(wx.App): def OnInit(self): self.main = _mainframe.create(None) @@ -34,11 +55,11 @@ def OnInit(self): self.SetTopWindow(self.main) return True + def main(): application = BoaApp(0) application.MainLoop() - if __name__ == '__main__': main() diff --git a/unittests/__init__.py b/unittests/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/unittests/__init__.py @@ -0,0 +1 @@ + diff --git a/unittests/test_codegen.py b/unittests/test_codegen.py new file mode 100644 index 0000000..1b0c256 --- /dev/null +++ b/unittests/test_codegen.py @@ -0,0 +1,769 @@ +# unit tests for code generator +# Copyright (C) 2015 Matiychuk D. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public License +# as published by the Free Software Foundation; either version 2.1 +# of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., +# 59 Temple Place, +# Suite 330, +# Boston, MA 02111-1307 USA + +from contextlib import contextmanager +import os +import string +import unittest + +from pywinauto.application import Application +from pywinauto.sysinfo import is_x64_Python + +import code_manager +import const +import proxy + + +SAMPLE_APPS_PATH = u"..\\apps\\MFC_samples" + + +@contextmanager +def test_app(filename): + mfc_samples_folder = os.path.join(os.path.dirname(__file__), + SAMPLE_APPS_PATH) + if is_x64_Python(): + sample_exe = os.path.join(mfc_samples_folder, "x64", filename) + else: + sample_exe = os.path.join(mfc_samples_folder, filename) + + app = Application().start(sample_exe, timeout=3) + app_path = os.path.normpath(sample_exe).encode('unicode-escape') + try: + yield app, app_path + except: + # Re-raise AssertionError and others + raise + finally: + app.kill_() + + +class BaseTestCase(unittest.TestCase): + + def setUp(self): + + """ + Since the tests require different apps, use `with` statement instead. + All setUp actions moved in the test_app contextmanager. + """ + + pass + + def tearDown(self): + + """ + All app's tearDown moved into the test_app contextmanager. + """ + + code_manager.CodeManager().clear() # Clear single tone CodeManager + reload(code_manager) # Reset class's counters + reload(proxy) # Reset class's counters + del self.pwa_root + + def get_proxy_object(self, path): + if not hasattr(self, 'pwa_root'): + self.pwa_root = proxy.PC_system(None) + + proxy_object = self.pwa_root + for target_sub in path: + subitems = proxy_object.Get_subitems() + if not subitems: + raise RuntimeError("'%s' cannot be found" % target_sub) + for name, pwa_object in subitems: + if target_sub == name: + proxy_object = pwa_object + break + else: + raise RuntimeError("Invalid path, '%s' not found" % target_sub) + + return proxy_object + + +class ObjectBrowserTestCases(BaseTestCase): + + def testNestedControl(self): + + direct_path = (u'Common Controls Sample', + u'Treeview1, Birds, Eagle, Hummingbird, Pigeon', + u'Birds', + ) + + indirect_path = (u'Common Controls Sample', + u'CTreeCtrl', + u'Treeview1, Birds, Eagle, Hummingbird, Pigeon', + u'Birds', + ) + + with test_app("CmnCtrl1.exe") as (app, app_path): + self.assertRaises(RuntimeError, self.get_proxy_object, + indirect_path) + + proxy_obj = self.get_proxy_object(direct_path) + self.assertEqual(proxy_obj.pwa_obj.elem, + app.Dialog.TreeView.GetItem(['Birds']).elem) + + def testNestedTopWindow(self): + + path = (u'About RowList', + u'RowList Version 1.0', + ) + + with test_app("RowList.exe") as (app, app_path): + app['RowList Sample Application'].MenuItem( + u'&Help->&About RowList...').Select() # open About dialog + try: + proxy_obj = self.get_proxy_object(path) + except RuntimeError: + self.fail("Controls of a nested top window are not accessible") + self.assertEqual(proxy_obj.pwa_obj.Texts(), + [u'RowList Version 1.0']) + + +class EmptyTextsTestCases(BaseTestCase): + + def testToolbarCode(self): + expected_code = \ + "from pywinauto.application import Application\n\n" \ + "app = Application().Start(cmd_line=u'{app_path}')\n" \ + "{win_ident} = app[u'RowList Sample Application']\n" \ + "{win_ident}.Wait('ready')\n" \ + "toolbarwindow = {win_ident}[u'4']\n" \ + "toolbar_button = toolbarwindow.Button(9)\n" \ + "toolbar_button.Click()\n\n" \ + "app.Kill_()" + + path = (u'RowList Sample Application', + u'Toolbar', + u'button #9', + ) + + with test_app("RowList.exe") as (app, app_path): + + window = app.top_window_() + + class_name = window.GetProperties()['Class'] + crtl_class = filter(lambda c: c in string.ascii_letters, + class_name).lower() + + proxy_obj = self.get_proxy_object(path) + code = proxy_obj.Get_code('Click') + + expected_code = expected_code.format(app_path=app_path, + win_ident=crtl_class) + self.assertEquals(expected_code, code) + + +class CodeGeneratorTestCases(BaseTestCase): + + def testInitAllParents(self): + + """ + all parents are inited after Get_code on a sub child. + """ + + expected_code = \ + "from pywinauto.application import Application\n\n" \ + "app = Application().Start(cmd_line=u'{app_path}')\n" \ + "window = app.Dialog\n" \ + "window.Wait('ready')\n" \ + "systreeview = window.TreeView\n" \ + "tree_item = systreeview.GetItem([u'Birds'])\n" \ + "tree_item.Expand()\n\n" \ + "app.Kill_()" + + path = (u'Common Controls Sample', + u'Treeview1, Birds, Eagle, Hummingbird, Pigeon', + u'Birds', + ) + + with test_app("CmnCtrl1.exe") as (app, app_path): + proxy_obj = self.get_proxy_object(path) + code = proxy_obj.Get_code('Expand') + + expected_code = expected_code.format(app_path=app_path) + self.assertEquals(expected_code, code) + + def testChangeCodeStyle(self): + + """ + code style of a top window may be changed on fly + """ + + expected_code_connect = \ + "from pywinauto.application import Application\n\n" \ + "app = Application().Connect(title=u'Common Controls Sample', " \ + "class_name='#32770')\n" \ + "window = app.Dialog\n" \ + "systreeview = window.TreeView\n" \ + "tree_item = systreeview.GetItem([u'Birds'])\n" \ + "tree_item.Expand()\n\n" + + expected_code_start = \ + "from pywinauto.application import Application\n\n" \ + "app = Application().Start(cmd_line=u'{app_path}')\n" \ + "window = app.Dialog\n" \ + "window.Wait('ready')\n" \ + "systreeview = window.TreeView\n" \ + "tree_item = systreeview.GetItem([u'Birds'])\n" \ + "tree_item.Expand()\n\n" \ + "app.Kill_()" + + control_path = (u'Common Controls Sample', + u'Treeview1, Birds, Eagle, Hummingbird, Pigeon', + u'Birds', + ) + + with test_app("CmnCtrl1.exe") as (app, app_path): + proxy_obj = self.get_proxy_object(control_path) + window_obj = proxy_obj.parent.parent + + # Default code style + code_default = proxy_obj.Get_code('Expand') + expected_code_start = expected_code_start.format( + app_path=app_path) + self.assertEquals(expected_code_start, code_default) + + # Switch to Connect + window_obj.SetCodestyle( + [menu_id for menu_id, command in const.EXTENDED_ACTIONS.items() + if command == 'Application.Connect'][0]) + code_connect = proxy_obj.Get_code() + self.assertEquals(expected_code_connect, code_connect) + + # Switch back to Start + window_obj.SetCodestyle( + [menu_id for menu_id, command in const.EXTENDED_ACTIONS.items() + if command == 'Application.Start'][0]) + code_start = proxy_obj.Get_code() + expected_code_start = expected_code_start.format( + app_path=app_path) + self.assertEquals(expected_code_start, code_start) + + def testSecondAppCounter(self): + + """ + app counter increased for another window + """ + + expected_code_1app = \ + "from pywinauto.application import Application\n\n" \ + "app = Application().Start(cmd_line=u'{app_path1}')\n" \ + "window = app.Dialog\n" \ + "window.Wait('ready')\n" \ + "systreeview = window.TreeView\n" \ + "tree_item = systreeview.GetItem([u'Birds'])\n" \ + "tree_item.Expand()\n\n" \ + "app.Kill_()" + + expected_code_2apps = \ + "from pywinauto.application import Application\n\n" \ + "app = Application().Start(cmd_line=u'{app_path1}')\n" \ + "window = app.Dialog\n" \ + "window.Wait('ready')\n" \ + "systreeview = window.TreeView\n" \ + "tree_item = systreeview.GetItem([u'Birds'])\n" \ + "tree_item.Expand()\n\n" \ + "app2 = Application().Start(cmd_line=u'{app_path2}')\n" \ + "window2 = app2.Dialog\n" \ + "window2.Wait('ready')\n" \ + "menu_item = window2.MenuItem(u'&Help->&About mymenu...')\n" \ + "menu_item.Click()\n\n" \ + "app2.Kill_()\n" \ + "app.Kill_()" + + control_path_app1 = (u'Common Controls Sample', + u'Treeview1, Birds, Eagle, Hummingbird, Pigeon', + u'Birds', + ) + control_path_app2 = (u'BCDialogMenu', + u'!Menu', + u'&Help', + u'&Help submenu', + u'&About mymenu...' + ) + + with test_app("CmnCtrl1.exe") as (app1, app_path1), \ + test_app("BCDialogMenu.exe") as (app2, app_path2): + proxy_obj_app1 = self.get_proxy_object(control_path_app1) + code_1app = proxy_obj_app1.Get_code('Expand') + + proxy_obj_app2 = self.get_proxy_object(control_path_app2) + code_2apps = proxy_obj_app2.Get_code('Click') + + expected_code_1app = expected_code_1app.format(app_path1=app_path1) + self.assertEquals(expected_code_1app, code_1app) + + expected_code_2apps = expected_code_2apps.format(app_path1=app_path1, + app_path2=app_path2) + self.assertEquals(expected_code_2apps, code_2apps) + + +class VariableReuseTestCases(BaseTestCase): + + def testReuseVariable(self): + + """ + an object variable used again for a new action + """ + + expected_code = \ + "from pywinauto.application import Application\n\n" \ + "app = Application().Start(cmd_line=u'{app_path}')\n" \ + "window = app.Dialog\n" \ + "window.Wait('ready')\n" \ + "systreeview = window.TreeView\n" \ + "tree_item = systreeview.GetItem([u'Birds'])\n" \ + "tree_item.Expand()\n" \ + "tree_item.Click()\n\n" \ + "app.Kill_()" + + path = (u'Common Controls Sample', + u'Treeview1, Birds, Eagle, Hummingbird, Pigeon', + u'Birds', + ) + + with test_app("CmnCtrl1.exe") as (app, app_path): + proxy_obj = self.get_proxy_object(path) + proxy_obj.Get_code('Expand') # First call + code = proxy_obj.Get_code('Click') + + expected_code = expected_code.format(app_path=app_path) + self.assertEquals(expected_code, code) + + def testSameAppAfterRefresh(self): + + """ + app & window counters do not increase after refresh for the same window + """ + + expected_code = \ + "from pywinauto.application import Application\n\n" \ + "app = Application().Start(cmd_line=u'{app_path}')\n" \ + "window = app.Dialog\n" \ + "window.Wait('ready')\n" \ + "window.Click()\n" \ + "window.Click()\n\n" \ + "app.Kill_()" + + control_path = (u'Common Controls Sample', + ) + + with test_app("CmnCtrl1.exe") as (app, app_path): + proxy_obj_before = self.get_proxy_object(control_path) + _ = proxy_obj_before.Get_code('Click') + + # rebuild elements tree (refresh) + proxy_obj_after = self.get_proxy_object(control_path) + + code = proxy_obj_after.Get_code('Click') + + self.assertTrue(proxy_obj_before is proxy_obj_after) + + expected_code = expected_code.format(app_path=app_path) + self.assertEquals(expected_code, code) + + def testSameAppSecondWindow(self): + + """ + variable app reused for both windows of the same process + """ + + expected_code = \ + "from pywinauto.application import Application\n\n" \ + "app = Application().Start(cmd_line=u'{app_path}')\n" \ + "{win_ident} = app[u'RowList Sample Application']\n" \ + "{win_ident}.Wait('ready')\n" \ + "menu_item = {win_ident}.MenuItem(u'&Help->&About RowList...')\n" \ + "menu_item.Click()\n" \ + "window = app.Dialog\n" \ + "button = window.OK\n" \ + "button.Click()\n\n" \ + "app.Kill_()" + + path_main_window = (u'RowList Sample Application', + + u'!Menu', + u'&Help', + u'&Help submenu', + u'&About RowList...', + ) + + path_about_window = (u'About RowList', + + u'OK', + ) + + with test_app("RowList.exe") as (app, app_path): + + window = app.top_window_() + class_name = window.GetProperties()['Class'] + crtl_class = filter(lambda c: c in string.ascii_letters, + class_name).lower() + + proxy_obj_main_window = self.get_proxy_object(path_main_window) + proxy_obj_main_window.pwa_obj.Click() # Click menu + _ = proxy_obj_main_window.Get_code('Click') + + # new window + proxy_obj_about_window = self.get_proxy_object(path_about_window) + code = proxy_obj_about_window.Get_code('Click') + + expected_code = expected_code.format(app_path=app_path, + win_ident=crtl_class) + self.assertEquals(expected_code, code) + + +class ClearCommandsTestCases(BaseTestCase): + + def testClearLastCommand(self): + + """ + last command is cleared + """ + + expected_code_full = \ + "from pywinauto.application import Application\n\n" \ + "app = Application().Start(cmd_line=u'{app_path}')\n" \ + "window = app.Dialog\n" \ + "window.Wait('ready')\n" \ + "systabcontrol = window.TabControl\n" \ + "systabcontrol.Select(u'CTreeCtrl')\n\n" \ + "app.Kill_()" + + expected_code_cleared = \ + "from pywinauto.application import Application\n\n" \ + "app = Application().Start(cmd_line=u'{app_path}')\n" \ + "window = app.Dialog\n" \ + "window.Wait('ready')\n" \ + "systabcontrol = window.TabControl\n\n" \ + "app.Kill_()" + + path = (u'Common Controls Sample', + + u'CTreeCtrl, CAnimateCtrl, CToolBarCtrl, CDateTimeCtrl, ' + u'CMonthCalCtrl', + + u'CTreeCtrl', + ) + + with test_app("CmnCtrl1.exe") as (app, app_path): + proxy_obj = self.get_proxy_object(path) + code_full = proxy_obj.Get_code('Select') + + cm = code_manager.CodeManager() + cm.clear_last() + code_cleared = cm.get_full_code() + + expected_code_full = expected_code_full.format(app_path=app_path) + self.assertEquals(expected_code_full, code_full) + + expected_code_cleared = expected_code_cleared.format(app_path=app_path) + self.assertEquals(expected_code_cleared, code_cleared) + + def testClear2LastCommand(self): + + """ + last two commands are cleared + """ + + expected_code_full = \ + "from pywinauto.application import Application\n\n" \ + "app = Application().Start(cmd_line=u'{app_path}')\n" \ + "window = app.Dialog\n" \ + "window.Wait('ready')\n" \ + "systabcontrol = window.TabControl\n" \ + "systabcontrol.Select(u'CTreeCtrl')\n\n" \ + "app.Kill_()" + + expected_code_cleared = \ + "from pywinauto.application import Application\n\n" \ + "app = Application().Start(cmd_line=u'{app_path}')\n" \ + "window = app.Dialog\n" \ + "window.Wait('ready')\n\n" \ + "app.Kill_()" + + path = (u'Common Controls Sample', + + u'CTreeCtrl, CAnimateCtrl, CToolBarCtrl, CDateTimeCtrl, ' + u'CMonthCalCtrl', + + u'CTreeCtrl', + ) + + with test_app("CmnCtrl1.exe") as (app, app_path): + proxy_obj = self.get_proxy_object(path) + code_full = proxy_obj.Get_code('Select') + + cm = code_manager.CodeManager() + cm.clear_last() + cm.clear_last() + code_cleared = cm.get_full_code() + + expected_code_full = expected_code_full.format(app_path=app_path) + self.assertEquals(expected_code_full, code_full) + + expected_code_cleared = expected_code_cleared.format(app_path=app_path) + self.assertEquals(expected_code_cleared, code_cleared) + + def testClearAll(self): + + """ + clear all the code + """ + + expected_code_full = \ + "from pywinauto.application import Application\n\n" \ + "app = Application().Start(cmd_line=u'{app_path}')\n" \ + "window = app.Dialog\n" \ + "window.Wait('ready')\n" \ + "systabcontrol = window.TabControl\n" \ + "systabcontrol.Select(u'CTreeCtrl')\n\n" \ + "app.Kill_()" + + path = (u'Common Controls Sample', + + u'CTreeCtrl, CAnimateCtrl, CToolBarCtrl, CDateTimeCtrl, ' + u'CMonthCalCtrl', + + u'CTreeCtrl', + ) + + with test_app("CmnCtrl1.exe") as (app, app_path): + proxy_obj = self.get_proxy_object(path) + code_full = proxy_obj.Get_code('Select') + + cm = code_manager.CodeManager() + cm.clear() + code_cleared = cm.get_full_code() + + expected_code_full = expected_code_full.format(app_path=app_path) + self.assertEquals(expected_code_full, code_full) + + self.assertEquals("", code_cleared) + + def testReleaseVariable(self): + + """ + variable released while the clear and used again by other object + """ + + expected_code_button1 = \ + "from pywinauto.application import Application\n\n" \ + "app = Application().Start(cmd_line=u'{app_path}')\n" \ + "window = app.Dialog\n" \ + "window.Wait('ready')\n" \ + "button = window.CheckBox8\n" \ + "button.Click()\n\n" \ + "app.Kill_()" + + expected_code_button2 = \ + "from pywinauto.application import Application\n\n" \ + "app = Application().Start(cmd_line=u'{app_path}')\n" \ + "window = app.Dialog\n" \ + "window.Wait('ready')\n" \ + "button = window.CheckBox5\n" \ + "button.Click()\n\n" \ + "app.Kill_()" + + path_button1 = (u'Common Controls Sample', + u'TVS_CHECKBOXES', + ) + + path_button2 = (u'Common Controls Sample', + u'TVS_DISABLEDRAGDROP', + ) + + with test_app("CmnCtrl1.exe") as (app, app_path): + proxy_obj_button1 = self.get_proxy_object(path_button1) + code_button1 = proxy_obj_button1.Get_code('Click') + + cm = code_manager.CodeManager() + cm.clear_last() + + proxy_obj_button2 = self.get_proxy_object(path_button2) + code_button2 = proxy_obj_button2.Get_code('Click') + + expected_code_button1 = expected_code_button1.format(app_path=app_path) + self.assertEquals(expected_code_button1, code_button1) + + expected_code_button2 = expected_code_button2.format(app_path=app_path) + self.assertEquals(expected_code_button2, code_button2) + + +class ControlsCodeTestCases(BaseTestCase): + + def testComboBoxCode(self): + expected_code = \ + "from pywinauto.application import Application\n\n" \ + "app = Application().Start(cmd_line=u'{app_path}')\n" \ + "window = app.Dialog\n" \ + "window.Wait('ready')\n" \ + "combobox = window.ComboBox\n" \ + "combobox.Select(u'Gray')\n\n" \ + "app.Kill_()" + + path = (u'Common Controls Sample', + u'Gray, Gray, White, Black', + u'Gray', + ) + + with test_app("CmnCtrl3.exe") as (app, app_path): + proxy_obj = self.get_proxy_object(path) + code = proxy_obj.Get_code('Select') + + expected_code = expected_code.format(app_path=app_path) + self.assertEquals(expected_code, code) + + def testListBoxCode(self): + expected_code = \ + "from pywinauto.application import Application\n\n" \ + "app = Application().Start(cmd_line=u'{app_path}')\n" \ + "window = app.Dialog\n" \ + "window.Wait('ready')\n" \ + "listbox = window.ListBox\n" \ + "listbox.Select(u'BCSS_IMAGE')\n\n" \ + "app.Kill_()" + + path = (u'Common Controls Sample', + u'BCSS_NOSPLIT, BCSS_STRETCH, BCSS_ALIGNLEFT, BCSS_IMAGE', + u'BCSS_IMAGE', + ) + + with test_app("CmnCtrl3.exe") as (app, app_path): + app.Dialog.TabControl.Select('CSplitButton') # open needed tab + proxy_obj = self.get_proxy_object(path) + code = proxy_obj.Get_code('Select') + + expected_code = expected_code.format(app_path=app_path) + self.assertEquals(expected_code, code) + + def testMenuCode(self): + expected_code = \ + "from pywinauto.application import Application\n\n" \ + "app = Application().Start(cmd_line=u'{app_path}')\n" \ + "window = app.Dialog\n" \ + "window.Wait('ready')\n" \ + "menu_item = window.MenuItem" \ + "(u'&Help->&About mymenu...')\n" \ + "menu_item.Click()\n\n" \ + "app.Kill_()" + + path = (u'BCDialogMenu', + u'!Menu', + u'&Help', + u'&Help submenu', + u'&About mymenu...' + ) + + with test_app("BCDialogMenu.exe") as (app, app_path): + proxy_obj = self.get_proxy_object(path) + code = proxy_obj.Get_code('Click') + + expected_code = expected_code.format(app_path=app_path) + self.assertEquals(expected_code, code) + + def testSysListView32Code(self): + expected_code = \ + "from pywinauto.application import Application\n\n" \ + "app = Application().Start(cmd_line=u'{app_path}')\n" \ + "{win_ident} = app[u'RowList Sample Application']\n" \ + "{win_ident}.Wait('ready')\n" \ + "syslistview = {win_ident}[u'1']\n" \ + "listview_item = syslistview.GetItem(u'Gray')\n" \ + "listview_item.Click()\n\n" \ + "app.Kill_()" + + path = (u'RowList Sample Application', + + u'Yellow, 255, 255, 0, 40, 240, 120, Neutral, Red, 255, 0, 0, ' + u'0, 240, 120, Warm, Green, 0, 255, 0, 80, 240, 120, Cool, ' + u'Magenta, 255, 0, 255, 200, 240, 120, Warm, Cyan, 0, 255, ' + u'255, 120, 240, 120, Cool, Blue, 0, 0, 255, 160, 240, 120, ' + u'Cool, Gray, 192, 192, 192, 160, 0, 181, Neutral', + + u'Gray', + ) + + with test_app("RowList.exe") as (app, app_path): + + window = app.top_window_() + + class_name = window.GetProperties()['Class'] + crtl_class = filter(lambda c: c in string.ascii_letters, + class_name).lower() + + proxy_obj = self.get_proxy_object(path) + self.assertEquals(len(proxy_obj.pwa_obj.listview_ctrl.Items()), 56) + code = proxy_obj.Get_code('Click') + + expected_code = expected_code.format(app_path=app_path, + win_ident=crtl_class) + self.assertEquals(expected_code, code) + + def testSysTabControl32Code(self): + expected_code = \ + "from pywinauto.application import Application\n\n" \ + "app = Application().Start(cmd_line=u'{app_path}')\n" \ + "window = app.Dialog\n" \ + "window.Wait('ready')\n" \ + "systabcontrol = window.TabControl\n" \ + "systabcontrol.Select(u'CTreeCtrl')\n\n" \ + "app.Kill_()" + + path = (u'Common Controls Sample', + + u'CTreeCtrl, CAnimateCtrl, CToolBarCtrl, CDateTimeCtrl, ' + u'CMonthCalCtrl', + + u'CTreeCtrl', + ) + + with test_app("CmnCtrl1.exe") as (app, app_path): + proxy_obj = self.get_proxy_object(path) + code = proxy_obj.Get_code('Select') + + expected_code = expected_code.format(app_path=app_path) + self.assertEquals(expected_code, code) + + def testToolbarWindow32Code(self): + expected_code = \ + "from pywinauto.application import Application\n\n" \ + "app = Application().Start(cmd_line=u'{app_path}')\n" \ + "window = app.Dialog\n" \ + "window.Wait('ready')\n" \ + "toolbarwindow = window.Toolbar2\n" \ + "toolbar_button = toolbarwindow.Button(u'Line')\n" \ + "toolbar_button.Click()\n\n" \ + "app.Kill_()" + + path = (u'Common Controls Sample', + + u'Erase, Pencil, Select, Brush, Airbrush, Fill, Line, Select ' + u'Color, Magnify, Rectangle, Round Rect, Ellipse', + + u'Line', + ) + + with test_app("CmnCtrl1.exe") as (app, app_path): + app.Dialog.TabControl.Select('CToolBarCtrl') # open needed tab + proxy_obj = self.get_proxy_object(path) + code = proxy_obj.Get_code('Click') + + expected_code = expected_code.format(app_path=app_path) + self.assertEquals(expected_code, code)