pyWinAuto: c:\.projects\py_pywinauto\pywinauto\application.py

0001# GUI Application automation and testing library
0002# Copyright (C) 2006 Mark Mc Mahon
0003#
0004# This library is free software; you can redistribute it and/or
0005# modify it under the terms of the GNU Lesser General Public License
0006# as published by the Free Software Foundation; either version 2.1
0007# of the License, or (at your option) any later version.
0008#
0009# This library is distributed in the hope that it will be useful,
0010# but WITHOUT ANY WARRANTY; without even the implied warranty of
0011# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
0012# See the GNU Lesser General Public License for more details.
0013#
0014# You should have received a copy of the GNU Lesser General Public
0015# License along with this library; if not, write to the
0016#    Free Software Foundation, Inc.,
0017#    59 Temple Place,
0018#    Suite 330,
0019#    Boston, MA 02111-1307 USA
0020
0021"""The application module is the main one that users will user first.
0022
0023When starting to automate and application you must initialize an instance
0024of the Application class. Then you must `start_()`_ that application or
0025`Connect_()`_ to a running instance of that application.
0026
0027Once you have an Application instance you can access dialogs in that
0028application either by using one of the methods below. ::
0029
0030   dlg = app.YourDialogTitle
0031   dlg = app.Window_(title = "your title", classname = "your class", ...)
0032   dlg = app['Your Dialog Title']
0033
0034Similarly once you have a dialog you can get a control from that dialog
0035in almost exactly the same ways. ::
0036
0037  ctrl = dlg.YourControlTitle
0038  ctrl = dlg.Window_(title = "Your control", classname = "Button", ...)
0039  ctrl = dlg["Your control"]
0040
0041**Note:** For attribute access of controls and dialogs you do not have to
0042have the title of the control exactly, it does a best match of the
0043avialable dialogs or controls.
0044
0045**See also:**
0046  `findwindows.find_windows()`_   for the keyword arguments that can be
0047  passed to both `Application.window_()`_ and `WindowSpecification.Window_()`_
0048
0049.. _Start_(): class-pywinauto.application.Application.html#start_
0050.. _Connect_(): class-pywinauto.application.Application.html#connect_
0051.. _findwindows.find_windows():  module-pywinauto.findwindows.html#find_windows
0052.. _Application.Window_(): class-pywinauto.application.Application.html#window_
0053.. _WindowSpecification.Window_(): class-pywinauto.application.WindowSpecification.html#control_
0054
0055"""
0056
0057__revision__ = "$Revision: 607 $"
0058
0059import time
0060import os.path
0061##import os
0062import warnings
0063import pickle
0064
0065import ctypes
0066
0067import win32structures
0068import win32functions
0069import win32defines
0070import controls
0071import findbestmatch
0072import findwindows
0073import handleprops
0074
0075from timings import Timings, WaitUntil, TimeoutError, WaitUntilPasses
0076
0077
0078class AppStartError(Exception):
0079    "There was a problem starting the Application"
0080    pass    #pragma: no cover
0081
0082class ProcessNotFoundError(Exception):
0083    "Could not find that process"
0084    pass    #pragma: no cover
0085
0086class AppNotConnected(Exception):
0087    "Application has been connected to a process yet"
0088    pass    #pragma: no cover
0089
0090
0091#wait_method_deprecation = "Wait* functions are just simple wrappers around " \
0092#    "Wait() or WaitNot(), so they may be removed in the future!"
0093
0094#=========================================================================
0095class WindowSpecification(object):
0096    """A specificiation for finding a window or control
0097
0098    Windows are resolved when used.
0099    You can also wait for existance or non existance of a window
0100    """
0101
0102    def __init__(self, app, search_criteria):
0103        """Initailize the class
0104
0105        * **search_criteria** the criteria to match a dialog
0106        """
0107        self.app = app
0108
0109        # kwargs will contain however to find this window
0110        self.criteria = [search_criteria, ]
0111
0112
0113    def __call__(self, *args, **kwargs):
0114        "No __call__ so return a usefull error"
0115
0116        if "best_match" in self.criteria[-1]:
0117            raise AttributeError(
0118                "WindowSpecification class has no '%s' method" %
0119                self.criteria[-1]['best_match'])
0120
0121        message =           "You tried to execute a function call on a WindowSpecification "           "instance. You probably have a typo for one of the methods of this "           "class.\n"           "The criteria leading up to this is: " + str(self.criteria)
0126
0127        raise AttributeError(message)
0128
0129
0130    def WrapperObject(self):
0131        "Allow the calling code to get the HwndWrapper object"
0132
0133        if self.app.use_history:
0134            ctrls = _resolve_from_appdata(self.criteria, self.app)
0135        else:
0136            ctrls = _resolve_control(self.criteria)
0137
0138        #self.app.RecordMatch(self.criteria, ctrls)
0139        #write_appdata(self.criteria, ctrls)
0140
0141        return ctrls[-1]
0142
0143    def ctrl_(self):
0144        "Allow the calling code to get the HwndWrapper object"
0145        message = "ctrl_() has been renamed to WrapperObject() please use "               "that method in the future. ctrl_() will be removed at some "               "future time."
0148        warnings.warn(message, DeprecationWarning)
0149        return self.WrapperObject()
0150
0151    def Window_(self, **criteria):
0152        """Add criteria for a control
0153
0154        When this window specification is resolved then this will be used
0155        to match against a control."""
0156
0157        new_item = WindowSpecification(self.app, self.criteria[0])
0158        new_item.criteria.append(criteria)
0159
0160        return new_item
0161    window_ = Window_
0162
0163    def __getitem__(self, key):
0164        """Allow access to dialogs/controls through item access
0165
0166        This allows::
0167
0168            app.['DialogTitle']['ControlTextClass']
0169
0170        to be used to access dialogs and controls.
0171
0172        Both this and __getattr__ use the rules outlined in the
0173        HowTo document.
0174        """
0175
0176        # if we already have 2 levels of criteria (dlg, control)
0177        # then resolve the control and do a getitem on it for the
0178        if len(self.criteria) == 2:
0179
0180            if self.app.use_history:
0181                ctrls = _resolve_from_appdata(
0182                    self.criteria,
0183                    self.app)
0184            else:
0185                ctrls = _resolve_control(
0186                    self.criteria)
0187
0188            #self.app.RecordMatch(self.criteria, ctrls)
0189
0190            # try to return a good error message if the control does not
0191            # have a __getitem__() method)
0192            if hasattr(ctrls[-1], '__getitem__'):
0193                return ctrls[-1][key]
0194            else:
0195                message = "The control does not have a __getitem__ method "                       "for item access (i.e. ctrl[key]) so maybe you have "                       "requested this in error?"
0198
0199                raise AttributeError(message)
0200
0201        # if we get here then we must have only had one criteria so far
0202        # so create a new WindowSpecification for this control
0203        new_item = WindowSpecification(self.app, self.criteria[0])
0204
0205        # add our new criteria
0206        new_item.criteria.append({"best_match" : key})
0207
0208        return new_item
0209
0210
0211    def __getattr__(self, attr):
0212        """Attribute access for this class
0213
0214        If we already have criteria for both dialog and control then
0215        resolve the control and return the requested attribute.
0216
0217        If we have only criteria for the dialog but the attribute
0218        requested is an attribute of DialogWrapper then resolve the
0219        dialog and return the requested attribute.
0220
0221        Otherwise delegate functionality to __getitem__() - which
0222        sets the appropriate criteria for the control.
0223        """
0224
0225        from pywinauto.controls.win32_controls import DialogWrapper
0226
0227        # if we already have 2 levels of criteria (dlg, conrol)
0228        # this third must be an attribute so resolve and get the
0229        # attribute and return it
0230        if len(self.criteria) == 2:
0231
0232            if self.app.use_history:
0233                ctrls = _resolve_from_appdata(
0234                    self.criteria,
0235                    self.app)
0236            else:
0237                ctrls = _resolve_control(
0238                    self.criteria)
0239
0240            #self.app.RecordMatch(self.criteria, ctrls)
0241            return getattr(ctrls[-1], attr)
0242
0243        else:
0244            # if we have been asked for an attribute of the dialog
0245            # then resolve the window and return the attribute
0246            if len(self.criteria) == 1 and hasattr(DialogWrapper, attr):
0247
0248                if self.app.use_history:
0249                    ctrls = _resolve_from_appdata(
0250                        self.criteria,
0251                        self.app)
0252                else:
0253                    ctrls = _resolve_control(
0254                        self.criteria)
0255
0256                #self.app.RecordMatch(self.criteria, ctrls)
0257                return getattr(ctrls[-1], attr)
0258
0259                # why was I using wait below and not just
0260                # getting the dialog and returning it's attribute
0261                #return getattr(
0262                #    self.Wait("ready",
0263                #        window_find_timeout,
0264                #        window_retry_interval),
0265                #    attr)
0266
0267        # It is a dialog/control criterion so let getitem
0268        # deal with it
0269        return self[attr]
0270
0271
0272    def Exists(self, timeout = None, retry_interval = None):
0273        "Check if the window exists"
0274
0275        # set the current timings -couldn't set as defaults as they are
0276        # evaluated at import time - and timings may be changed at any time
0277        if timeout is None:
0278            timeout = Timings.exists_timeout
0279        if retry_interval is None:
0280            retry_interval = Timings.exists_retry
0281
0282
0283        # modify the criteria as Exists should look for all
0284        # windows - including not visible and disabled
0285        exists_criteria = self.criteria[:]
0286        for criterion in exists_criteria:
0287            criterion['enabled_only'] = False
0288            criterion['visible_only'] = False
0289
0290        try:
0291            _resolve_control(
0292                exists_criteria, timeout, retry_interval)
0293
0294            return True
0295        except (findwindows.WindowNotFoundError, findbestmatch.MatchError):
0296            return False
0297
0298
0299    def Wait(self,
0300            wait_for,
0301            timeout = None,
0302            retry_interval = None):
0303
0304        """Wait for the window to be in a particular state
0305
0306        * **wait_for** The state to wait for the window to be in. It can be any
0307          of the following states
0308
0309            * 'exists' means that the window is a valid handle
0310            * 'visible' means that the window is not hidden
0311            * 'enabled' means that the window is not disabled
0312            * 'ready' means that the window is visible and enabled
0313            * 'active' means that the window is visible and enabled
0314
0315        * **timeout** Raise an error if the window is not in the appropriate
0316          state after this number of seconds.
0317
0318        * **retry_interval** How long to sleep between each retry
0319
0320        e.g. self.Dlg.Wait("exists enabled visible ready")
0321
0322        See also: ``Application.WaitNot()``
0323        """
0324
0325        # set the current timings -couldn't set as defaults as they are
0326        # evaluated at import time - and timings may be changed at any time
0327        if timeout is None:
0328            timeout = Timings.exists_timeout
0329        if retry_interval is None:
0330            retry_interval = Timings.window_find_retry
0331
0332        # allow for case mixups - just to make it easier to use
0333        waitfor = wait_for.lower()
0334
0335        # make a copy of the criteria that we can modify
0336        wait_criteria = self.criteria[:]
0337
0338        # update the criteria based on what has been requested
0339        # we go from least strict to most strict in case the user
0340        # has specified conflicting wait conditions
0341        for criterion in wait_criteria:
0342            if 'exists' in waitfor:
0343                criterion['enabled_only'] = False
0344                criterion['visible_only'] = False
0345
0346            if 'visible' in waitfor:
0347                criterion['enabled_only'] = False
0348                criterion['visible_only'] = True
0349
0350            if 'enabled' in waitfor:
0351                criterion['enabled_only'] = True
0352                criterion['visible_only'] = False
0353
0354            if 'ready' in waitfor:
0355                criterion['visible_only'] = True
0356                criterion['enabled_only'] = True
0357
0358            if 'active' in waitfor:
0359                criterion['active_only'] = True
0360
0361        if self.app.use_history:
0362            ctrls = _resolve_from_appdata(
0363                wait_criteria,
0364                self.app,
0365                timeout,
0366                retry_interval)
0367        else:
0368            ctrls = _resolve_control(
0369                wait_criteria,
0370                timeout,
0371                retry_interval)
0372
0373        #self.app.RecordMatch(self.criteria, ctrls)
0374
0375        return ctrls[-1]
0376
0377    def WaitNot(self,
0378            wait_for_not,
0379            timeout = None,
0380            retry_interval = None):
0381
0382        """Wait for the window to not be in a particular state
0383
0384        * **wait_for** The state to wait for the window to not be in. It can be any
0385          of the following states
0386
0387            * 'exists' means that the window is a valid handle
0388            * 'visible' means that the window is not hidden
0389            * 'enabled' means that the window is not disabled
0390            * 'ready' means that the window is visible and enabled
0391            * 'active' means that the window is visible and enabled
0392
0393        * **timeout** Raise an error if the window is sill in the
0394          state after this number of seconds.(Optional)
0395        * **wiat_interval** How long to sleep between each retry (Optional)
0396
0397        e.g. self.Dlg.WaitNot("exists enabled visible ready")
0398
0399        See also: ``Application.WaitNot("exists enabled visible ready")``
0400        """
0401
0402        # set the current timings -couldn't set as defaults as they are
0403        # evaluated at import time - and timings may be changed at any time
0404        if timeout is None:
0405            timeout = Timings.window_find_timeout
0406        if retry_interval is None:
0407            retry_interval = Timings.window_find_retry
0408
0409        ## remember the start time so we can do an accurate wait for the timeout
0410        #start = time.time()
0411
0412        waitnot_criteria = self.criteria[:]
0413        for criterion in waitnot_criteria:
0414            criterion['enabled_only'] = False
0415            criterion['visible_only'] = False
0416
0417        wait_for_not = wait_for_not.lower()
0418
0419
0420        #self.app.RecordMatch(self.criteria, ctrls)
0421
0422
0423        def WindowIsNotXXX():
0424            """Local function that returns False if the window is not 
0425            Visible, etc. Otherwise returns the best matching control"""
0426
0427            # first check if the window doesn't exist, because if it doesn't
0428            # exist, it definitely can't be visible, active enabled or ready
0429            try:
0430                if self.app.use_history:
0431                    ctrls = _resolve_from_appdata(
0432                        waitnot_criteria, self.app, 0, .01)
0433                else:
0434                    ctrls = _resolve_control(waitnot_criteria, 0, .01)
0435                # if we get here - then the window exists and we need to 
0436                # do the other checks below
0437
0438            except (findwindows.WindowNotFoundError, findbestmatch.MatchError):
0439                # Window doesn't exist
0440                return True
0441
0442            if 'exists' in wait_for_not:
0443                # well if we got here then the control must have
0444                # existed so we are not ready to stop checking
0445                # because we didn't want the control to exist!
0446                return ctrls
0447
0448            if 'ready' in wait_for_not:
0449                if ctrls[-1].IsVisible() and ctrls[-1].IsEnabled():
0450                    return ctrls
0451
0452            if 'enabled' in wait_for_not:
0453                if ctrls[-1].IsEnabled():
0454                    return ctrls
0455
0456            if 'visible' in wait_for_not:
0457                if ctrls[-1].IsVisible():
0458                    return ctrls
0459
0460            return True
0461
0462        try:
0463            wait_val = WaitUntil(timeout, retry_interval, WindowIsNotXXX)
0464        except TimeoutError, e:
0465            raise RuntimeError(
0466                "Timed out while waiting for window (%s - '%s') "
0467                "to not be in '%s' state"% (
0468                    e.function_value[-1].Class(),
0469                    e.function_value[-1].WindowText(),
0470                    "', '".join( wait_for_not.split() ) )
0471                )
0472
0473
0474
0475#        try:
0476#            # wait until the 
0477#            WaitUntil(timeout, retry_interval, WindowIsNotXXX)
0478#        except TimeoutError, e:
0479#            raise RuntimeError(
0480#                "Timed out while waiting for window (%s - '%s') "
0481#                "to not be in '%s' state"%
0482#                    (e.function_value[-1].Class(),
0483#                    e.function_value[-1].WindowText(),
0484#                    "', '".join( wait_for_not.split() ) )
0485#                )
0486
0487
0488
0489
0490
0491#
0492#        while True:
0493#
0494#            matched = True
0495#            if 'exists' in wait_for_not:
0496#                # well if we got here then the control must have
0497#                # existed so we are not ready to stop checking
0498#                # because we didn't want the control to exist!
0499#                matched = False
0500#
0501#            if 'ready' in wait_for_not:
0502#                if ctrls[-1].IsVisible() and ctrls[-1].IsEnabled():
0503#                    matched = False
0504#
0505#            if 'enabled' in wait_for_not:
0506#                if ctrls[-1].IsEnabled():
0507#                    matched = False
0508#
0509#            if 'visible' in wait_for_not:
0510#                if ctrls[-1].IsVisible():
0511#                    matched = False
0512#
0513#            if matched:
0514#                break
0515#
0516#
0517#            # stop trying if we have reached the timeout
0518#            waited = time.time() - start
0519#            if  waited < timeout:
0520#                # wait the interval or the time left until the timeout expires
0521#                # and let the loop run again
0522#                time.sleep(min(retry_interval, timeout - waited))
0523#
0524#            else:
0525#                raise RuntimeError(
0526#                    "Timed out while waiting for window (%s - '%s') "
0527#                    "to not be in '%s' state"%
0528#                        (ctrls[-1].Class(),
0529#                        ctrls[-1].WindowText(),
0530#                        "', '".join( wait_for_not.split() ) )
0531#                    )
0532
0533
0534
0535#    def WaitReady(self, timeout = None, retry_interval = None):
0536#        "Wait for the control to be ready (Exists, Visible and Enabled)"
0537#        warnings.warn(wait_method_deprecation, DeprecationWarning)
0538#        return self.Wait('ready', timeout, retry_interval)
0539#
0540#    def WaitNotReady(self, timeout = None, retry_interval = None):
0541#        "Wait for the control to be ready (Exists, Visible and Enabled)"
0542#        warnings.warn(wait_method_deprecation, DeprecationWarning)
0543#        return self.WaitNot('ready', timeout, retry_interval)
0544#
0545#    def WaitEnabled(self, timeout = None, retry_interval = None):
0546#        """Wait for the control to become enabled
0547#
0548#        Returns the control"""
0549#        warnings.warn(wait_method_deprecation, DeprecationWarning)
0550#        return self.Wait('enabled', timeout, retry_interval)
0551#
0552#    def WaitNotEnabled(self, timeout = None, retry_interval = None):
0553#        "Wait for the control to be disabled or not exist"
0554#        warnings.warn(wait_method_deprecation, DeprecationWarning)
0555#        self.WaitNot('enabled', timeout, retry_interval)
0556#
0557#    def WaitVisible(self, timeout = None,retry_interval = None):
0558#        """Wait for the control to become visible
0559#
0560#        Returns the control"""
0561#        warnings.warn(wait_method_deprecation, DeprecationWarning)
0562#        return self.Wait('visible', timeout, retry_interval)
0563#
0564#    def WaitNotVisible(self,
0565#        timeout = None,
0566#        retry_interval = None):
0567#        "Wait for the control to be invisible or not exist"
0568#        warnings.warn(wait_method_deprecation, DeprecationWarning)
0569#        self.WaitNot('visible', timeout, retry_interval)
0570#
0571#    def WaitExists(self, timeout = None, retry_interval = None):
0572#        """Wait for the control to not exist anymore"""
0573#        warnings.warn(wait_method_deprecation, DeprecationWarning)
0574#        return self.Wait('exists', timeout, retry_interval)
0575#
0576#    def WaitNotExists(self, timeout = None, retry_interval = None):
0577#        """Wait for the control to not exist anymore"""
0578#        warnings.warn(wait_method_deprecation, DeprecationWarning)
0579#        self.WaitNot('exists', timeout, retry_interval)
0580
0581    def _ctrl_identifiers(self):
0582
0583        ctrls = _resolve_control(
0584            self.criteria)
0585
0586        if ctrls[-1].IsDialog(