pyWinAuto: c:\.projects\py_pywinauto\pywinauto\tests\repeatedhotkey.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"""Repeated Hotkeys Test
0022
0023**What is checked**
0024This test checks all the controls in a dialog to see if there are controls that
0025use the same hotkey character.
0026
0027**How is it checked**
0028A list of all the hotkeys (converted to uppercase) used in the dialog is
0029created. Then this list is examined to see if any hotkeys are used more than
0030once. If any are used more than once a list of all the controls that use this
0031hotkey are compiled to be used in the bug report.
0032
0033**When is a bug reported**
0034If more than one control has the same hotkey then a bug is reported.
0035
0036**Bug Extra Information**
0037The bug contains the following extra information
0038Name    Description
0039RepeatedHotkey  This is the hotkey that is repeated between the 2 controls
0040converted to uppercase, String
0041CharsUsedInDialog       This is a list of all the hotkeys used in the dialog,
0042String
0043AllCharsInDialog        This is a list of all the characters in the dialog for
0044controls that have a hotkeys, String
0045AvailableInControlS     A list of the available characters for each control.
0046Any of the characters in this list could be used as the new hotkey without
0047conflicting with any existing hotkey.
0048
0049**Is Reference dialog needed**
0050The reference dialog does not need to be available. If it is available then
0051for each bug discovered it is checked to see if it is a problem in the
0052reference dialog.
0053NOTE: Checking the reference dialog is not so exact here! Only when the
0054equivalent controls in the reference dialog all have the hotkeys will it be
0055reported as being in the reference also. I.e. if there are 3 controls with the
0056same hotkey in the Localised software  then those same controls in the
0057reference dialog must have the same hotkey for it to be reported as existing
0058in the reference also.
0059
0060**False positive bug reports**
0061There should be very few false positives from this test. Sometimes a control
0062only has one or 2 characters eg "X:" and it is impossible to avoid a hotkey
0063clash. Also for Asian languages hotkeys should be the same as the US software
0064so probably this test should be run on those languages.
0065
0066**Test Identifier**
0067The identifier for this test/bug is "RepeatedHotkey"
0068"""
0069
0070testname = "RepeatedHotkey"
0071__revision__ = "$Revision: 372 $"
0072
0073# to be able to run on Python 2.3 we need to use sets module
0074# rather then built-in set object
0075
0076import sets
0077
0078from pywinauto.win32defines import SS_NOPREFIX
0079
0080
0081
0082#-----------------------------------------------------------------------------
0083def RepeatedHotkeyTest(windows):
0084    "Return the repeated hotkey errors"
0085
0086    hotkeyControls, allChars, hotkeys = _CollectDialogInfo(windows)
0087
0088    # get the available characters in the dialog
0089    dlgAvailable = allChars.difference(hotkeys)
0090
0091    # remove some characters that aren't good choices for hotkeys
0092    dlgAvailable.difference_update(sets.Set("-& _"))
0093
0094
0095    bugs = []
0096    # for each hotkey
0097    for char, controls in hotkeyControls.items():
0098
0099        # if there is more than one control associated then it is a bug
0100        if len(controls) > 1:
0101
0102            ctrlsAvailableChars = ""
0103
0104            # build up the available characters for each control
0105            for ctrl in controls:
0106                controlChars = ""
0107                controlChars = sets.Set(ctrl.WindowText().lower())
0108
0109                controlAvailableChars = controlChars.intersection(dlgAvailable)
0110                controlAvailableChars =                       "<%s>" % _SetAsString(controlAvailableChars)
0112
0113                ctrlsAvailableChars += controlAvailableChars
0114
0115            refCtrls = [ctrl.ref for ctrl in controls if ctrl.ref]
0116            refHotkeyControls, refAllChars, refHotkeys =                   _CollectDialogInfo(refCtrls)
0118
0119            isInRef = -1
0120            if len(refHotkeys) > 1:
0121                isInRef = 1
0122            else:
0123                isInRef = 0
0124
0125            bugs.append((
0126                controls,
0127                {
0128                    "RepeatedHotkey" :          char,
0129                    "CharsUsedInDialog" :   _SetAsString(hotkeys),
0130                    "AllCharsInDialog" :    _SetAsString(allChars),
0131                    "AvailableInControls" : ctrlsAvailableChars,
0132                },
0133                testname,
0134                isInRef)
0135            )
0136
0137#       # What is the algorithm to try and do all that is necessary to find
0138#       # if it is possible to get a fix character if none of the current
0139#       # characters are free
0140#       for bug in bugs:
0141#               for c, chars in bug.bugData:
0142#                       # control has no possibilities
0143#                       if not chars:
0144#                               # check if there are any other hotkey controls
0145#                               # in the dialog that could be used
0146#                               others = set(c.Title.lower()).intersection(unUsedChars)
0147
0148
0149    return  bugs
0150
0151
0152
0153#-----------------------------------------------------------------------------
0154def _CollectDialogInfo(windows):
0155    "Collect information on the hotkeys in the dialog"
0156    hotkeyControls = {}
0157    allChars = ''
0158
0159    for win in windows:
0160        # skip it if it doesn't implement hotkey functionality
0161        if not ImplementsHotkey(win):
0162            continue
0163
0164        # get the hotkey
0165        pos, char = GetHotkey(win.WindowText())
0166
0167        # if no hotkey for this control
0168        # then continue
0169        if not char:
0170            continue
0171
0172        # store hotkey with list of used hotkeys
0173        # map this hotkey to the list of controls that have it
0174        hotkeyControls.setdefault(char.lower(), []).append(win)
0175
0176
0177        # Add the title of this control to the list of available
0178        # characters for the dialog
0179        allChars += win.WindowText().lower()
0180
0181
0182    allChars = sets.Set(allChars)
0183    hotkeys = sets.Set(hotkeyControls.keys())
0184
0185    return hotkeyControls, allChars, hotkeys
0186
0187
0188#-----------------------------------------------------------------------------
0189# get hokey position
0190def GetHotkey(text):
0191    "Return the position and character of the hotkey"
0192
0193    # find the last & character that is not followed
0194    # by & or by the end of the string
0195
0196    curEnd = len(text) + 1
0197    text = text.replace("&&", "__")
0198    while True:
0199        pos = text.rfind("&", 0, curEnd)
0200
0201        # One found was at the end of the text or
0202        # no (more) & were found
0203        # so return the None value
0204        if pos == -1 or pos == len(text):
0205            return (-1, '')
0206
0207        # One found but was prededed by non valid hotkey character
0208        # so continue, as that can't be a shortcut
0209        if text[pos - 1] == '&':
0210            curEnd = pos - 2
0211            continue
0212
0213        # 2 ampersands in a row - so skip
0214        # the 1st one and continue
0215        return (pos, text[pos+1])
0216
0217
0218
0219#-----------------------------------------------------------------------------
0220def _SetAsString(settojoin):
0221    "Convert the set to a ordered string"
0222    return "".join(sorted(settojoin))
0223
0224
0225#-----------------------------------------------------------------------------
0226def ImplementsHotkey(win):
0227    "checks whether a control interprets & character to be a hotkey"
0228
0229    # buttons always implement hotkey
0230    if win.Class() == "Button":
0231        return True
0232
0233    # Statics do if they don't have SS_NOPREFIX style
0234    elif win.Class() == "Static" and not win.HasStyle(SS_NOPREFIX):
0235        return True
0236
0237    if win.Class() == "MenuItem" and win.State() != "2048":
0238        return True
0239
0240    # Most controls don't - so just return false if
0241    # neither of the above condition hold
0242    return False
0243
0244
0245RepeatedHotkeyTest.TestsMenus = True