Best Python code snippet using fMBT_python
fmbtgti.py
Source:fmbtgti.py
1# fMBT, free Model Based Testing tool2# Copyright (c) 2013-2014, Intel Corporation.3#4# This program is free software; you can redistribute it and/or modify it5# under the terms and conditions of the GNU Lesser General Public License,6# version 2.1, as published by the Free Software Foundation.7#8# This program is distributed in the hope it will be useful, but WITHOUT9# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for11# more details.12#13# You should have received a copy of the GNU Lesser General Public License along with14# this program; if not, write to the Free Software Foundation, Inc.,15# 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.16# fMBT GUI Test Interface (fmbtgti) provides a set of classes that17# implement a GUI testing interface with a rich collection of18# convenience methods and visual logging capabilities.19#20# GUI Test Interface does not implement a connection to any particular21# GUI technology, such as Android or X11. Platforms are wired up as22# separate GUITestConnection implementations that are given to23# GUITestInterface with the setConnection method. GUI Test Interface24# classes and wiring up required methods to the technology.25"""This module provides common functionality for all GUITestInterfaces26(like fmbtandroid.Device, fmbttizen.Device, fmbtx11.Screen,27fmbtvnc.Screen).28Bitmap matching (Optical Image Recognition, OIR)29------------------------------------------------30Bitmaps are searched from the directories listed in bitmapPath.31If a bitmap is found in a directory, default OIR parameters for32matching the bitmap are loaded from .fmbtoirrc file in the directory.33If a bitmap is not found in a directory, but .fmbtoirrc in the34directory contains lines like35 includedir = alt-directory36then the bitmap file is searched from alt-directory, too. If found37from there, OIR parameters defined in .fmbtoirrc below the includedir38line will be used for matching alt-directory/bitmap.39There can be several includedir lines, and many alternative sets of40OIR parameters can be given for each includedir. Sets are separated by41"alternative" line in the file.42Example:43Assume that bitmaps/android-4.2/device-A contains bitmaps captured44from the screen of device A.45Assume device B has higher resolution so that bitmaps of A have to be46scaled up by factor 1.3 or 1.2 - depending on bitmap - to match device47B. This can be handled by creating bitmaps/android-4.2/device-B48directory with .fmbtoirrc file that contains:49# OIR parameters for bitmaps in device-B in case some bitmaps will be50# created directly from device-B screenshots.51colorMatch = 0.952# Two alternative parameter sets for bitmaps in device-A53includedir = ../device-A54scale = 1.355colorMatch = 0.956bitmapPixelSize = 357screenshotPixelSize = 358alternative59scale = 1.260colorMatch = 0.861bitmapPixelSize = 362bitmapPixelSize = 363After that, device B can be tested without taking new screenshot.64d = fmbtandroid.Device()65d.setBitmapPath("bitmaps/android-4.2/device-B")66All bitmap methods support alternative bitmaps. For example, if67 verifyBitmap("bitmap.png")68finds bitmap.png file in directory bitmapdir in the bitmap path, but69bitmap.png cannot be found in the screenshot with any given match70parameters, then it automatically tries the same for all71 bitmap.png.alt*.png72files in the same bitmapdir directory. If any of those match, then73verifyBitmap("bitmap.png") returns True. Visual log shows the74information about which of the alternatives actually matched.75"""76import cgi77import ctypes78import datetime79import distutils.sysconfig80import gc81import glob82import inspect83import math84import os85import re86import shlex87import shutil88import subprocess89import sys90import time91import traceback92import types93import fmbt94import fmbt_config95import eyenfinger96# See imagemagick convert parameters.97_OCRPREPROCESS = [98 ''99]100# See tesseract -pagesegmode.101_OCRPAGESEGMODES = [3]102_g_defaultOcrEngine = None # optical character recognition engine103_g_defaultOirEngine = None # optical image recognition engine104_g_ocrEngines = []105_g_oirEngines = []106_g_forcedLocExt = ".fmbtoir.loc"107class _USE_DEFAULTS:108 pass109def _fmbtLog(msg):110 fmbt.fmbtlog("fmbtgti: %s" % (msg,))111def _filenameTimestamp(t=None):112 return fmbt.formatTime("%Y%m%d-%H%M%S-%f", t)113def _takeDragArgs(d):114 return _takeArgs(("startOffset", "startPos",115 "delayBeforeMoves", "delayBetweenMoves",116 "delayAfterMoves", "movePoints"), d)117def _takeTapArgs(d):118 return _takeArgs(("tapOffset", "tapPos", "long", "hold", "count", "delayBetweenTaps", "button"), d)119def _takeWaitArgs(d):120 return _takeArgs(("waitTime", "pollDelay",121 "beforeRefresh", "afterRefresh"), d)122def _takeOirArgs(screenshotOrOirEngine, d, thatsAll=False):123 if isinstance(screenshotOrOirEngine, Screenshot):124 oirEngine = screenshotOrOirEngine.oirEngine()125 else:126 oirEngine = screenshotOrOirEngine127 return _takeArgs(oirEngine._findBitmapArgNames(), d, thatsAll)128def _takeOcrArgs(screenshotOrOcrEngine, d, thatsAll=False):129 if isinstance(screenshotOrOcrEngine, Screenshot):130 ocrEngine = screenshotOrOcrEngine.ocrEngine()131 else:132 ocrEngine = screenshotOrOcrEngine133 return _takeArgs(ocrEngine._findTextArgNames(), d, thatsAll)134def _takeArgs(argNames, d, thatsAll=False):135 """136 Returns pair:137 (dict of items where key in argNames,138 dict of items that were left in d)139 If thatsAll is True, require that all arguments have been140 consumed.141 """142 retval = {}143 for a in argNames:144 if a in d:145 retval[a] = d.pop(a, None)146 if thatsAll and len(d) > 0:147 raise TypeError('Unknown argument(s): "%s"' %148 ('", "'.join(sorted(d.keys()))))149 return retval, d150def _convert(srcFile, convertArgs, dstFile):151 if isinstance(convertArgs, basestring):152 convertArgs = shlex.split(convertArgs)153 if (os.access(dstFile, os.R_OK) and154 os.access(srcFile, os.R_OK) and155 os.stat(srcFile).st_mtime < os.stat(dstFile).st_mtime):156 return # cached file is up-to-date157 subprocess.call([fmbt_config.imagemagick_convert, srcFile] + convertArgs + [dstFile])158def _ppFilename(origFilename, preprocess):159 return origFilename + ".fmbtoir.cache." + re.sub("[^a-zA-Z0-9.]", "", preprocess) + ".png"160def _intCoords((x, y), (width, height)):161 if 0 <= x <= 1 and type(x) == float: x = x * width162 if 0 <= y <= 1 and type(y) == float: y = y * height163 return (int(round(x)), int(round(y)))164def _boxOnRegion((x1, y1, x2, y2), (minX, minY, maxX, maxY), partial=True):165 if partial:166 return (x1 < x2 and ((minX <= x1 <= maxX) or (minX <= x2 <= maxX)) and167 y1 < y2 and ((minY <= y1 <= maxY) or (minY <= y2 <= maxY)))168 else:169 return (x1 < x2 and ((minX <= x1 <= maxX) and (minX <= x2 <= maxX)) and170 y1 < y2 and ((minY <= y1 <= maxY) and (minY <= y2 <= maxY)))171def _edgeDistanceInDirection((x, y), (width, height), direction):172 x, y = _intCoords((x, y), (width, height))173 direction = direction % 360 # -90 => 270, 765 => 45174 dirRad = math.radians(direction)175 if 0 < direction < 180:176 distTopBottom = y / math.sin(dirRad)177 elif 180 < direction < 360:178 distTopBottom = -(height - y) / math.sin(dirRad)179 else:180 distTopBottom = float('inf')181 if 90 < direction < 270:182 distLeftRight = -x / math.cos(dirRad)183 elif 270 < direction <= 360 or 0 <= direction < 90:184 distLeftRight = (width - x) / math.cos(dirRad)185 else:186 distLeftRight = float('inf')187 return min(distTopBottom, distLeftRight)188### Binding to eye4graphics C-library189class _Bbox(ctypes.Structure):190 _fields_ = [("left", ctypes.c_int32),191 ("top", ctypes.c_int32),192 ("right", ctypes.c_int32),193 ("bottom", ctypes.c_int32),194 ("error", ctypes.c_int32)]195class _Rgb888(ctypes.Structure):196 _fields_ = [("red", ctypes.c_uint8),197 ("green", ctypes.c_uint8),198 ("blue", ctypes.c_uint8)]199_libpath = ["", ".",200 os.path.dirname(os.path.abspath(__file__)),201 distutils.sysconfig.get_python_lib(plat_specific=1)]202_suffix = ".so"203if os.name == "nt":204 _suffix = ".dll"205for _dirname in _libpath:206 try:207 eye4graphics = ctypes.CDLL(os.path.join(_dirname , "eye4graphics"+_suffix))208 struct_bbox = _Bbox(0, 0, 0, 0, 0)209 eye4graphics.findNextColor.restype = ctypes.c_int210 eye4graphics.findNextHighErrorBlock.argtypes = [211 ctypes.c_void_p,212 ctypes.c_void_p,213 ctypes.c_int,214 ctypes.c_int,215 ctypes.c_double,216 ctypes.c_void_p]217 eye4graphics.findNextDiff.restype = ctypes.c_int218 eye4graphics.findNextDiff.argtypes = [219 ctypes.c_void_p,220 ctypes.c_void_p,221 ctypes.c_void_p,222 ctypes.c_double,223 ctypes.c_double,224 ctypes.c_void_p,225 ctypes.c_void_p,226 ctypes.c_int]227 eye4graphics.openImage.argtypes = [ctypes.c_char_p]228 eye4graphics.openImage.restype = ctypes.c_void_p229 eye4graphics.openedImageDimensions.argtypes = [ctypes.c_void_p, ctypes.c_void_p]230 eye4graphics.closeImage.argtypes = [ctypes.c_void_p]231 eye4graphics.rgb5652rgb.restype = ctypes.c_int232 eye4graphics.rgb5652rgb.argtypes = [233 ctypes.c_void_p,234 ctypes.c_int,235 ctypes.c_int,236 ctypes.c_void_p]237 break238 except: pass239else:240 raise ImportError("%s cannot load eye4graphics%s" % (__file__, _suffix))241def _e4gOpenImage(filename):242 image = eye4graphics.openImage(filename)243 if not image:244 raise IOError('Cannot open image "%s"' % (filename,))245 else:246 return image247def _e4gImageDimensions(e4gImage):248 struct_bbox = _Bbox(0, 0, 0, 0, 0)249 eye4graphics.openedImageDimensions(ctypes.byref(struct_bbox), e4gImage)250 return (struct_bbox.right, struct_bbox.bottom)251def _e4gImageIsBlank(filename):252 e4gImage = _e4gOpenImage(filename)253 rv = (eye4graphics.openedImageIsBlank(e4gImage) == 1)254 eye4graphics.closeImage(e4gImage)255 return rv256### end of binding to eye4graphics.so257def sortItems(items, criteria):258 """259 Returns GUIItems sorted according to given criteria260 Parameters:261 items (list of GUIItems):262 items to be sorted263 criteria (string):264 Supported values:265 "topleft" - sort by top left coordinates of items266 "area" - sort by areas of items267 """268 if criteria == "topleft":269 top_left_items = [(i.bbox()[1], i.bbox()[0], i) for i in items]270 top_left_items.sort()271 return [tli[2] for tli in top_left_items]272 elif criteria == "area":273 area_items = [274 ((i.bbox()[2] - i.bbox()[0]) * (i.bbox()[3] - i.bbox()[1]), i)275 for i in items]276 area_items.sort()277 return [ai[1] for ai in area_items]278 else:279 raise ValueError('invalid sort criteria "%s"' % (criteria,))280class GUITestConnection(object):281 """282 Implements GUI testing primitives needed by GUITestInterface.283 All send* and recv* methods return284 - True on success285 - False on user error (unknown keyName, coordinates out of range)286 - raise Exception on framework error (connection lost, missing287 dependencies).288 """289 def sendPress(self, keyName):290 return self.sendKeyDown(keyName) and self.sendKeyUp(keyName)291 def sendKeyDown(self, keyName):292 raise NotImplementedError('sendKeyDown("%s") needed but not implemented.' % (keyName,))293 def sendKeyUp(self, keyName):294 raise NotImplementedError('sendKeyUp("%s") needed but not implemented.' % (keyName,))295 def sendTap(self, x, y):296 return self.sendTouchDown(x, y) and self.sendTouchUp(x, y)297 def sendTouchDown(self, x, y):298 raise NotImplementedError('sendTouchDown(%d, %d) needed but not implemented.' % (x, y))299 def sendTouchMove(self, x, y):300 raise NotImplementedError('sendTouchMove(%d, %d) needed but not implemented.' % (x, y))301 def sendTouchUp(self, x, y):302 raise NotImplementedError('sendTouchUp(%d, %d) needed but not implemented.' % (x, y))303 def sendType(self, text):304 raise NotImplementedError('sendType("%s") needed but not implemented.' % (text,))305 def recvScreenshot(self, filename):306 """307 Saves screenshot from the GUI under test to given filename.308 """309 raise NotImplementedError('recvScreenshot("%s") needed but not implemented.' % (filename,))310 def recvScreenUpdated(self, waitTime, pollDelay):311 """312 Wait until the screen has been updated, but no longer than the313 timeout (waitTime). Return True if the screen was updated314 before the timeout, otherwise False.315 Implementing this method is optional. If not implemented, the316 method returns None, and an inefficient recvScreenshot-based317 implementation is used instead from fmbtgti. pollDelay can be318 ignored if more efficient solutions are available319 (update-event based triggering, for instance).320 """321 return None322 def target(self):323 """324 Returns a string that is unique to each test target. For325 instance, Android device serial number.326 """327 return "GUITestConnectionTarget"328class SimulatedGUITestConnection(GUITestConnection):329 """330 Simulates GUITestConnection: records method calls and fakes screenshots331 All send* methods return True. recvScreenshot returns always True332 if non-empty list of fake screenshot filenames is given (see333 constructor and setScreenshotFilenames). Otherwise it returns334 False.335 """336 def __init__(self, screenshotFilenames=()):337 """338 Parameters:339 screenshotFilenames (tuple of filenames):340 calling recvScreenshot uses next item in this tuple as341 the observed screenshot.342 """343 GUITestConnection.__init__(self)344 self.setScreenshotFilenames(screenshotFilenames)345 self._calls = []346 for recordedMethod in ("sendPress", "sendKeyDown", "sendKeyUp",347 "sendTap", "sendTouchDown", "sendTouchMove",348 "sendTouchUp", "sendType"):349 self.__dict__[recordedMethod] = self._recorder(recordedMethod)350 def _recorder(self, method):351 return lambda *args, **kwargs: self._calls.append(352 (time.time(), method, args, kwargs)) or True353 def history(self):354 return self._calls355 def clearHistory(self):356 self._calls = []357 def setScreenshotFilenames(self, screenshotFilenames):358 self._screenshotFilenames = screenshotFilenames359 self._nextScreenshotFilename = 0360 def recvScreenshot(self, filename):361 self._calls.append((time.time(), "recvScreenshot", (filename,), {}))362 if self._screenshotFilenames:363 if self._nextScreenshotFilename >= len(self._screenshotFilenames):364 self._nextScreenshotFilename = 0365 fakeFilename = self._screenshotFilenames[self._nextScreenshotFilename]366 self._nextScreenshotFilename += 1367 if not os.access(fakeFilename, os.R_OK):368 raise IOError('screenshot file not found: "%s"' % (fakeFilename,))369 shutil.copy(fakeFilename, filename)370 return True371 else:372 return False373 def target(self):374 return "SimulatedGUITestConnection"375class OrEngine(object):376 """377 Optical recognition engine. Base class for OCR and OIR engines,378 enables registering engine instances.379 """380 def __init__(self, *args, **kwargs):381 pass382 def register(self, defaultOcr=False, defaultOir=False):383 """384 Register this engine instance to the list of OCR and/or OIR385 engines.386 Parameters:387 defaultOcr (optional, boolean):388 if True, use this OCR engine by default in all new389 GUITestInterface instances. The default is False.390 defaultOir (optional, boolean):391 if True, use this OIR engine by default in all new392 GUITestInterface instances. The default is False.393 Returns the index with which the engine was registered to the394 list of OCR or OIR engines. If this instance implements both395 OCR and OIR engines, returns pair (OCR index, OIR index).396 """397 # Allow a single engine implement both OCR and OIR engine398 # interfaces. Therefore, it must be possible to call399 # e.register(defaultOcr=True, defaultOir=True).400 #401 global _g_defaultOcrEngine, _g_defaultOirEngine402 global _g_ocrEngines, _g_oirEngines403 engineIndexes = []404 if isinstance(self, OcrEngine):405 if not self in _g_ocrEngines:406 _g_ocrEngines.append(self)407 engineIndexes.append(_g_ocrEngines.index(self))408 if defaultOcr:409 _g_defaultOcrEngine = self410 if isinstance(self, OirEngine):411 if not self in _g_oirEngines:412 _g_oirEngines.append(self)413 engineIndexes.append(_g_oirEngines.index(self))414 if defaultOir:415 _g_defaultOirEngine = self416 if len(engineIndexes) == 1:417 return engineIndexes[0]418 else:419 return engineIndexes420class OcrEngine(OrEngine):421 """422 This is an abstract interface for OCR engines that can be plugged423 into fmbtgti.GUITestInterface instances and Screenshots.424 To implement an OCR engine, you need to override _findText() at425 minimum. See _findText documentation in this class for426 requirements.427 If possible in your OCR engine, you can provide _dumpOcr() to428 reveal what is recognized in screenshots.429 For efficient caching of results and freeing cached results, you430 can override _addScreenshot() and _removeScreenshot(). Every431 screenshot is added before findText() or dumpOcr().432 A typical usage of OcrEngine instance:433 - oe.addScreenshot(ss)434 - oe.findText(ss, text1, <engine/screenshot/find-specific-args>)435 - oe.findText(ss, text2, <engine/screenshot/find-specific-args>)436 - oe.removeScreenshot(ss)437 Note that there may be several screenshots added before they are438 removed.439 """440 def __init__(self, *args, **kwargs):441 super(OcrEngine, self).__init__(*args, **kwargs)442 self._ssFindTextDefaults = {}443 self._findTextDefaults = {}444 ocrFindArgs, _ = _takeOcrArgs(self, kwargs)445 self._setFindTextDefaults(ocrFindArgs)446 def dumpOcr(self, screenshot, **kwargs):447 """448 Returns what is recognized in the screenshot. For debugging449 purposes.450 """451 ocrArgs = self.__ocrArgs(screenshot, **kwargs)452 return self._dumpOcr(screenshot, **ocrArgs)453 def _dumpOcr(self, screenshot, **kwargs):454 return None455 def addScreenshot(self, screenshot, **findTextDefaults):456 """457 Prepare for finding text from the screenshot.458 Parameters:459 screenshot (fmbtgti.Screenshot)460 screenshot object to be searched from.461 other parameters (optional)462 findText defaults for this screenshot.463 Notice that there may be many screenshots simultaneously.464 Do not keep reference to the screenshot object.465 """466 self.setScreenshotFindTextDefaults(screenshot, **findTextDefaults)467 return self._addScreenshot(screenshot, **findTextDefaults)468 def _addScreenshot(self, screenshot, **findTextDefaults):469 pass470 def removeScreenshot(self, screenshot):471 """472 OCR queries on the screenshot will not be made anymore.473 """474 self._removeScreenshot(screenshot)475 try:476 del self._ssFindTextDefaults[id(screenshot)]477 except KeyError:478 raise KeyError('screenshot "%s" does not have findTextDefaults. '479 'If OcrEngine.addScreenshot() is overridden, it '480 '*must* call parent\'s addScreenshot.' % (screenshot.filename(),))481 def _removeScreenshot(self, screenshot):482 pass483 def setFindTextDefaults(self, **findTextDefaults):484 return self._setFindTextDefaults(findTextDefaults, screenshot=None)485 def setScreenshotFindTextDefaults(self, screenshot, **findTextDefaults):486 return self._setFindTextDefaults(findTextDefaults, screenshot=screenshot)487 def _setFindTextDefaults(self, defaults, screenshot=None):488 """489 Set default values for optional arguments for findText().490 Parameters:491 defaults (dictionary)492 Default keyword arguments and their values.493 screenshot (optional, fmbtgti.Screenshot instance)494 Use the defaults for findText on this screenshot. If495 the defaults are None, make them default for all496 screenshots. Screenshot-specific defaults override497 engine default.498 """499 if screenshot == None:500 self._findTextDefaults.update(defaults)501 else:502 ssid = id(screenshot)503 if not ssid in self._ssFindTextDefaults:504 self._ssFindTextDefaults[ssid] = self._findTextDefaults.copy()505 self._ssFindTextDefaults[ssid].update(defaults)506 def findTextDefaults(self, screenshot=None):507 if screenshot == None:508 return self._findTextDefaults509 elif id(screenshot) in self._ssFindTextDefaults:510 return self._ssFindTextDefaults[id(screenshot)]511 else:512 return None513 def _findTextArgNames(self):514 """515 Returns names of optional findText arguments.516 """517 return inspect.getargspec(self._findText).args[3:]518 def __ocrArgs(self, screenshot, **priorityArgs):519 ocrArgs = {}520 ocrArgs.update(self._findTextDefaults)521 ssId = id(screenshot)522 if ssId in self._ssFindTextDefaults:523 ocrArgs.update(self._ssFindTextDefaults[ssId])524 ocrArgs.update(priorityArgs)525 return ocrArgs526 def findText(self, screenshot, text, **kwargs):527 """528 Return list of fmbtgti.GUIItems that match to text.529 """530 ocrArgs = self.__ocrArgs(screenshot, **kwargs)531 return self._findText(screenshot, text, **ocrArgs)532 def _findText(self, screenshot, text, **kwargs):533 """534 Find appearances of text from the screenshot.535 Parameters:536 screenshot (fmbtgti.Screenshot)537 Screenshot from which text is to be searched538 for. Use Screenshot.filename() to get the filename.539 text (string)540 text to be searched for.541 other arguments (engine specific)542 kwargs contain keyword arguments given to543 findText(screenshot, text, ...), already extended544 first with screenshot-specific findTextDefaults, then545 with engine-specific findTextDefaults.546 _findText *must* define all engine parameters as547 explicit keyword arguments:548 def _findText(self, screenshot, text, engArg1=42):549 ...550 Return list of fmbtgti.GUIItems.551 """552 raise NotImplementedError("_findText needed but not implemented.")553class _EyenfingerOcrEngine(OcrEngine):554 """555 OCR engine parameters that can be used in all556 ...OcrText() methods (swipeOcrText, tapOcrText, findItemsByOcrText, ...):557 match (float, optional):558 minimum match score in range [0.0, 1.0]. The default is559 1.0 (exact match).560 area ((left, top, right, bottom), optional):561 search from the given area only. Left, top, right and562 bottom are either absolute coordinates (integers) or563 floats in range [0.0, 1.0]. In the latter case they are564 scaled to screenshot dimensions. The default is (0.0,565 0.0, 1.0, 1.0), that is, search everywhere in the566 screenshot.567 lang (string, optional):568 pass given language option to Tesseract. See supported569 LANGUAGES (-l) in Tesseract documentation. The default570 is "eng" (English).571 pagesegmodes (list of integers, optional):572 try all integers as tesseract -pagesegmode573 arguments. The default is [3], another good option could574 be [3, 6].575 preprocess (string, optional):576 preprocess filter to be used in OCR for better577 result. Refer to eyenfinger.autoconfigure to search for578 a good one, or try with ImageMagick's convert:579 $ convert screenshot.png <preprocess> screenshot-pp.png580 $ tesseract screenshot-pp.png stdout581 configfile (string, optional):582 Tesseract configuration file.583 Example: limit recognized characters to hexadecimals by creating file584 "hexchars" with content585 tessedit_char_whitelist 0123456789abcdefABCDEF586 To use this file in a single run, pass it to any Ocr method:587 dut.verifyOcrText("DEADBEEF", configfile="hexchars")588 or to use it on every Ocr method, set it as a default:589 dut.ocrEngine().setFindTextDefaults(configfile="hexchars")590 """591 class _OcrResults(object):592 __slots__ = ("filename", "screenSize", "pagesegmodes", "preprocess", "area", "words", "lang", "configfile")593 def __init__(self, filename, screenSize):594 self.filename = filename595 self.screenSize = screenSize596 self.pagesegmodes = None597 self.preprocess = None598 self.area = None599 self.words = None600 self.lang = None601 self.configfile = None602 def __init__(self, *args, **engineDefaults):603 engineDefaults["area"] = engineDefaults.get("area", (0.0, 0.0, 1.0, 1.0))604 engineDefaults["lang"] = engineDefaults.get("lang", "eng")605 engineDefaults["match"] = engineDefaults.get("match", 1.0)606 engineDefaults["pagesegmodes"] = engineDefaults.get("pagesegmodes", _OCRPAGESEGMODES)607 engineDefaults["preprocess"] = engineDefaults.get("preprocess", _OCRPREPROCESS)608 engineDefaults["configfile"] = engineDefaults.get("configfile", None)609 super(_EyenfingerOcrEngine, self).__init__(*args, **engineDefaults)610 self._ss = {} # OCR results for screenshots611 def _addScreenshot(self, screenshot, **findTextDefaults):612 ssId = id(screenshot)613 self._ss[ssId] = _EyenfingerOcrEngine._OcrResults(screenshot.filename(), screenshot.size())614 def _removeScreenshot(self, screenshot):615 ssId = id(screenshot)616 if ssId in self._ss:617 del self._ss[ssId]618 def _findText(self, screenshot, text, match=None, preprocess=None, area=None, pagesegmodes=None, lang=None, configfile=None):619 ssId = id(screenshot)620 self._assumeOcrResults(screenshot, preprocess, area, pagesegmodes, lang, configfile)621 for ppfilter in self._ss[ssId].words.keys():622 try:623 score_text_bbox_list = eyenfinger.findText(624 text, self._ss[ssId].words[ppfilter], match=match)625 if not score_text_bbox_list:626 continue627 else:628 break629 except eyenfinger.BadMatch:630 continue631 else:632 return []633 retval = [GUIItem("OCR text (match %.2f)" % (score,),634 bbox, self._ss[ssId].filename,635 ocrFind=text, ocrFound=matching_text)636 for score, matching_text, bbox in score_text_bbox_list]637 return retval638 def _dumpOcr(self, screenshot, match=None, preprocess=None, area=None, pagesegmodes=None, lang=None, configfile=None):639 ssId = id(screenshot)640 self._assumeOcrResults(screenshot, preprocess, area, pagesegmodes, lang, configfile)641 w = []642 for ppfilter in self._ss[ssId].preprocess:643 for word in self._ss[ssId].words[ppfilter]:644 for appearance, (wid, middle, bbox) in enumerate(self._ss[ssId].words[ppfilter][word]):645 (x1, y1, x2, y2) = bbox646 w.append((word, (x1, y1, x2, y2)))647 return sorted(set(w), key=lambda i:(i[1][1]/8, i[1][0]))648 def _assumeOcrResults(self, screenshot, preprocess, area, pagesegmodes, lang, configfile):649 ssId = id(screenshot)650 if not type(preprocess) in (list, tuple):651 preprocess = [preprocess]652 if (self._ss[ssId].words == None653 or self._ss[ssId].preprocess != preprocess654 or self._ss[ssId].area != area655 or self._ss[ssId].lang != lang656 or self._ss[ssId].configfile != configfile):657 self._ss[ssId].words = {}658 self._ss[ssId].preprocess = preprocess659 self._ss[ssId].area = area660 self._ss[ssId].lang = lang661 self._ss[ssId].configfile = configfile662 for ppfilter in preprocess:663 pp = ppfilter % { "zoom": "-resize %sx" % (self._ss[ssId].screenSize[0] * 2) }664 try:665 eyenfinger.iRead(source=self._ss[ssId].filename, ocr=True, preprocess=pp, ocrArea=area, ocrPageSegModes=pagesegmodes, lang=lang, configfile=configfile)666 except Exception:667 self._ss[ssId].words = None668 raise669 self._ss[ssId].words[ppfilter] = eyenfinger._g_words670def _defaultOcrEngine():671 if _g_defaultOcrEngine:672 return _g_defaultOcrEngine673 else:674 _EyenfingerOcrEngine().register(defaultOcr=True)675 return _g_defaultOcrEngine676class OirEngine(OrEngine):677 """678 This is an abstract interface for OIR (optical image recognition)679 engines that can be plugged into fmbtgti.GUITestInterface680 instances and Screenshots.681 To implement an OIR engine, you need to override _findBitmap() at682 minimum. See _findBitmap documentation in this class for683 requirements.684 This base class provides full set of OIR parameters to685 _findBitmap. The parameters are combined from686 - OirEngine find defaults, specified when OirEngine is687 instantiated.688 - Screenshot instance find defaults.689 - bitmap / bitmap directory find defaults (read from the690 .fmbtoirrc that is in the same directory as the bitmap).691 - ...Bitmap() method parameters.692 The latter in the list override the former.693 For efficient caching of results and freeing cached results, you694 can override _addScreenshot() and _removeScreenshot(). Every695 screenshot is added before findBitmap().696 A typical usage of OirEngine instance:697 - oe.addScreenshot(ss)698 - oe.findBitmap(ss, bmpFilename1, <engine/screenshot/find-specific-args>)699 - oe.findBitmap(ss, bmpFilename2, <engine/screenshot/find-specific-args>)700 - oe.removeScreenshot(ss)701 Note that there may be several screenshots added before they are702 removed. ss is a Screenshot instance. Do not keep references to703 Screenshot intances, otherwise garbage collector will not remove704 them.705 """706 def __init__(self, *args, **kwargs):707 super(OirEngine, self).__init__(*args, **kwargs)708 self._ssFindBitmapDefaults = {}709 self._findBitmapDefaults = {}710 oirArgs, _ = _takeOirArgs(self, kwargs)711 self._setFindBitmapDefaults(oirArgs)712 def addScreenshot(self, screenshot, **findBitmapDefaults):713 """714 Prepare for finding bitmap from the screenshot.715 Parameters:716 screenshot (fmbtgti.Screenshot)717 screenshot object to be searched from.718 other parameters (optional)719 findBitmap defaults for this screenshot.720 Notice that there may be many screenshots simultaneously.721 Do not keep reference to the screenshot object.722 """723 self.setScreenshotFindBitmapDefaults(screenshot, **findBitmapDefaults)724 return self._addScreenshot(screenshot, **findBitmapDefaults)725 def _addScreenshot(self, screenshot, **findBitmapDefaults):726 pass727 def removeScreenshot(self, screenshot):728 """729 OIR queries on the screenshot will not be made anymore.730 """731 self._removeScreenshot(screenshot)732 try:733 del self._ssFindBitmapDefaults[id(screenshot)]734 except KeyError:735 raise KeyError('screenshot "%s" does not have findBitmapDefaults. '736 'If OirEngine.addScreenshot() is overridden, it '737 '*must* call parent\'s addScreenshot.' % (screenshot.filename(),))738 def _removeScreenshot(self, screenshot):739 pass740 def setFindBitmapDefaults(self, **findBitmapDefaults):741 return self._setFindBitmapDefaults(findBitmapDefaults, screenshot=None)742 def setScreenshotFindBitmapDefaults(self, screenshot, **findBitmapDefaults):743 return self._setFindBitmapDefaults(findBitmapDefaults, screenshot=screenshot)744 def _setFindBitmapDefaults(self, defaults, screenshot=None):745 """746 Set default values for optional arguments for findBitmap().747 Parameters:748 defaults (dictionary)749 Default keyword arguments and their values.750 screenshot (optional, fmbtgti.Screenshot instance)751 Use the defaults for findBitmap on this screenshot. If752 the defaults are None, make them default for all753 screenshots. Screenshot-specific defaults override754 engine default.755 """756 if screenshot == None:757 self._findBitmapDefaults.update(defaults)758 else:759 ssid = id(screenshot)760 if not ssid in self._ssFindBitmapDefaults:761 self._ssFindBitmapDefaults[ssid] = self._findBitmapDefaults.copy()762 self._ssFindBitmapDefaults[ssid].update(defaults)763 def findBitmapDefaults(self, screenshot=None):764 if screenshot == None:765 return self._findBitmapDefaults766 elif id(screenshot) in self._ssFindBitmapDefaults:767 return self._ssFindBitmapDefaults[id(screenshot)]768 else:769 return None770 def _findBitmapArgNames(self):771 """772 Returns names of optional findBitmap arguments.773 """774 return inspect.getargspec(self._findBitmap).args[3:]775 def __oirArgs(self, screenshot, bitmap, **priorityArgs):776 oirArgs = {}777 oirArgs.update(self._findBitmapDefaults)778 ssId = id(screenshot)779 if ssId in self._ssFindBitmapDefaults:780 oirArgs.update(self._ssFindBitmapDefaults[ssId])781 oirArgs.update(priorityArgs)782 return oirArgs783 def findBitmap(self, screenshot, bitmap, **kwargs):784 """785 Return list of fmbtgti.GUIItems that match to bitmap.786 """787 oirArgs = self.__oirArgs(screenshot, bitmap, **kwargs)788 bitmapLocsFilename = bitmap + _g_forcedLocExt789 if os.access(bitmapLocsFilename, os.R_OK):790 # Use hardcoded bitmap locations file instead of real OIR791 # bitmap.png.locs file format:792 # [(x11, y11, x12, y12), ..., (xn1, yn1, xn2, yn2)]793 try:794 bboxList = eval(file(bitmapLocsFilename).read().strip())795 foundItems = []796 for (left, top, right, bottom) in bboxList:797 x1, y1 = _intCoords((left, top), screenshot.size())798 x2, y2 = _intCoords((right, bottom), screenshot.size())799 foundItems.append(800 GUIItem("bitmap location", (x1, y1, x2, y2),801 screenshot.filename(), bitmap=bitmapLocsFilename))802 return foundItems803 except Exception, e:804 raise ValueError('Error reading bounding box list from %s: %s' %805 repr(bitmapLocsFilename), e)806 return self._findBitmap(screenshot, bitmap, **oirArgs)807 def _findBitmap(self, screenshot, bitmap, **kwargs):808 """809 Find appearances of bitmap from the screenshot.810 Parameters:811 screenshot (fmbtgti.Screenshot)812 Screenshot from which bitmap is to be searched813 for. Use Screenshot.filename() to get the filename.814 bitmap (string)815 bitmap to be searched for.816 other arguments (engine specific)817 kwargs contain keyword arguments given to818 findBitmap(screenshot, bitmap, ...), already extended819 first with screenshot-specific findBitmapDefaults, then820 with engine-specific findBitmapDefaults.821 _findBitmap *must* define all engine parameters as822 explicit keyword arguments:823 def _findBitmap(self, screenshot, bitmap, engArg1=42):824 ...825 Returns list of fmbtgti.GUIItems.826 """827 raise NotImplementedError("_findBitmap needed but not implemented.")828class _Eye4GraphicsOirEngine(OirEngine):829 """OIR engine parameters that can be used in all830 ...Bitmap() methods (swipeBitmap, tapBitmap, findItemsByBitmap, ...):831 colorMatch (float, optional):832 required color matching accuracy. The default is 1.0833 (exact match). For instance, 0.75 requires that every834 pixel's every RGB component value on the bitmap is at835 least 75 % match with the value of corresponding pixel's836 RGB component in the screenshot.837 opacityLimit (float, optional):838 threshold for comparing pixels with non-zero alpha839 channel. Pixels less opaque than the given threshold are840 skipped in match comparison. The default is 0, that is,841 alpha channel is ignored.842 area ((left, top, right, bottom), optional):843 search bitmap from the given area only. Left, top right844 and bottom are either absolute coordinates (integers) or845 floats in range [0.0, 1.0]. In the latter case they are846 scaled to screenshot dimensions. The default is (0.0,847 0.0, 1.0, 1.0), that is, search everywhere in the848 screenshot.849 limit (integer, optional):850 number of returned matches is limited to the limit. The851 default is -1: all matches are returned. Applicable in852 findItemsByBitmap.853 allowOverlap (boolean, optional):854 allow returned icons to overlap. If False, returned list855 contains only non-overlapping bounding boxes. The856 default is False.857 scale (float or pair of floats, optional):858 scale to be applied to the bitmap before859 matching. Single float is a factor for both X and Y860 axis, pair of floats is (xScale, yScale). The default is861 1.0.862 bitmapPixelSize (integer, optional):863 size of pixel rectangle on bitmap for which there must864 be same color on corresponding screenshot rectangle. If865 scale is 1.0, default is 1 (rectangle is 1x1). If scale866 != 1.0, the default is 2 (rectangle is 2x2).867 screenshotPixelSize (integer, optional):868 size of pixel rectangle on screenshot in which there869 must be a same color pixel as in the corresponding870 rectangle on bitmap. The default is scale *871 bitmapPixelSize.872 preprocess (string, optional):873 preprocess parameters that are executed to both screenshot874 and reference bitmap before running findBitmap. By default875 there is no preprocessing.876 Example: d.verifyBitmap("ref.png", preprocess="-threshold 60%")877 will execute two imagemagick commands:878 1. convert screenshot.png -threshold 60% screenshot-pp.png879 2. convert ref.png -threshold 60% ref-pp.png880 and then search for ref-pp.png in screenshot-pp.png. This results881 in black-and-white comparison (immune to slight color changes).882 If unsure about parameters, but you have a bitmap that should be883 detected in a screenshot, try obj.oirEngine().adjustParameters().884 Example:885 d.enableVisualLog("params.html")886 screenshot = d.refreshScreenshot()887 results = d.oirEngine().adjustParameters(screenshot, "mybitmap.png")888 if results:889 item, params = results[0]890 print "found %s with parameters:" % (item,)891 print "\n".join(sorted([" %s = %s" % (k, params[k]) for k in params]))892 print "verify:", d.verifyBitmap("mybitmap.png", **params)893 Notice, that you can force refreshScreenshot to load old screenshot:894 d.refreshScreenshot("old.png")895 """896 def __init__(self, *args, **engineDefaults):897 engineDefaults["colorMatch"] = engineDefaults.get("colorMatch", 1.0)898 engineDefaults["opacityLimit"] = engineDefaults.get("opacityLimit", 0.0)899 engineDefaults["area"] = engineDefaults.get("area", (0.0, 0.0, 1.0, 1.0))900 engineDefaults["limit"] = engineDefaults.get("limit", -1)901 engineDefaults["allowOverlap"] = engineDefaults.get("allowOverlap", False)902 engineDefaults["scale"] = engineDefaults.get("scale", 1.0)903 engineDefaults["bitmapPixelSize"] = engineDefaults.get("bitmapPixelSize", 0)904 engineDefaults["screenshotPixelSize"] = engineDefaults.get("screenshotPixelSize", 0)905 engineDefaults["preprocess"] = engineDefaults.get("preprocess", "")906 OirEngine.__init__(self, *args, **engineDefaults)907 self._openedImages = {}908 # openedRelatedScreenshots maps a screenshot filename to909 # a list of preprocessed screenshot objects. All those objects910 # must be closed when the screenshot is removed.911 self._openedRelatedScreenshots = {}912 self._findBitmapCache = {}913 def _addScreenshot(self, screenshot, **findBitmapDefaults):914 filename = screenshot.filename()915 self._openedImages[filename] = _e4gOpenImage(filename)916 # make sure size() is available, this can save an extra917 # opening of the screenshot file.918 if screenshot.size(allowReadingFile=False) == None:919 screenshot.setSize(_e4gImageDimensions(self._openedImages[filename]))920 self._findBitmapCache[filename] = {}921 def _removeScreenshot(self, screenshot):922 filename = screenshot.filename()923 if filename in self._openedRelatedScreenshots:924 for screenshotPP in self._openedRelatedScreenshots[filename]:925 self._removeScreenshot(screenshotPP)926 del self._openedRelatedScreenshots[filename]927 eye4graphics.closeImage(self._openedImages[filename])928 del self._openedImages[filename]929 del self._findBitmapCache[filename]930 def adjustParameters(self, screenshot, bitmap,931 scaleRange = [p/100.0 for p in range(110,210,10)],932 colorMatchRange = [p/100.0 for p in range(100,60,-10)],933 pixelSizeRange = range(2,5),934 resultCount = 1,935 **oirArgs):936 """937 Search for scale, colorMatch, bitmapPixelSize and938 screenshotPixelSize parameters that find the bitmap in the939 screenshot.940 Parameters:941 screenshot (Screenshot instance):942 screenshot that contains the bitmap.943 bitmap (string):944 bitmap filename.945 scaleRange (list of floats, optional):946 scales to go through.947 The default is: 1.1, 1.2, ... 2.0.948 colorMatchRange (list of floats, optional):949 colorMatches to try out.950 The default is: 1.0, 0.9, ... 0.7.951 pixelSizeRange (list of integers, optional):952 values for bitmapPixelSize and screenshotPixelSize.953 The default is: [2, 3]954 resultCount (integer, optional):955 number of parameter combinations to be found.956 The default is 1. 0 is unlimited.957 other OIR parameters: as usual, refer to engine documentation.958 Returns list of pairs: (GUIItem, findParams), where959 GUIItem is the detected item (GUIItem.bbox() is the box around it),960 and findParams is a dictionary containing the parameters.961 """962 if not screenshot.filename() in self._findBitmapCache:963 self.addScreenshot(screenshot)964 ssAdded = True965 else:966 ssAdded = False967 retval = []968 for colorMatch in colorMatchRange:969 for pixelSize in pixelSizeRange:970 for scale in scaleRange:971 findParams = oirArgs.copy()972 findParams.update({"colorMatch": colorMatch,973 "limit": 1,974 "scale": scale,975 "bitmapPixelSize": pixelSize,976 "screenshotPixelSize": pixelSize})977 results = self.findBitmap(screenshot, bitmap,978 **findParams)979 if results:980 retval.append((results[0], findParams))981 if len(retval) == resultCount:982 return retval983 if ssAdded:984 self.removeScreenshot(screenshot)985 return retval986 def _findBitmap(self, screenshot, bitmap, colorMatch=None,987 opacityLimit=None, area=None, limit=None,988 allowOverlap=None, scale=None,989 bitmapPixelSize=None, screenshotPixelSize=None,990 preprocess=None):991 """992 Find items on the screenshot that match to bitmap.993 """994 ssFilename = screenshot.filename()995 ssSize = screenshot.size()996 cacheKey = (bitmap, colorMatch, opacityLimit, area, limit,997 scale, bitmapPixelSize, screenshotPixelSize, preprocess)998 if cacheKey in self._findBitmapCache[ssFilename]:999 return self._findBitmapCache[ssFilename][cacheKey]1000 self._findBitmapCache[ssFilename][cacheKey] = []1001 if preprocess:1002 ssFilenamePP = _ppFilename(ssFilename, preprocess)1003 bitmapPP = _ppFilename(bitmap, preprocess)1004 if not ssFilenamePP in self._openedImages:1005 _convert(ssFilename, preprocess, ssFilenamePP)1006 screenshotPP = Screenshot(ssFilenamePP)1007 self.addScreenshot(screenshotPP)1008 if not ssFilename in self._openedRelatedScreenshots:1009 self._openedRelatedScreenshots[ssFilename] = []1010 self._openedRelatedScreenshots[ssFilename].append(screenshotPP)1011 _convert(bitmap, preprocess, bitmapPP)1012 ssFilename = ssFilenamePP1013 bitmap = bitmapPP1014 self._findBitmapCache[ssFilename][cacheKey] = []1015 e4gIcon = _e4gOpenImage(bitmap)1016 matchCount = 01017 leftTopRightBottomZero = (_intCoords((area[0], area[1]), ssSize) +1018 _intCoords((area[2], area[3]), ssSize) +1019 (0,))1020 struct_area_bbox = _Bbox(*leftTopRightBottomZero)1021 struct_bbox = _Bbox(0, 0, 0, 0, 0)1022 contOpts = 0 # search for the first hit1023 try:1024 xscale, yscale = scale1025 except TypeError:1026 xscale = yscale = float(scale)1027 while True:1028 if matchCount == limit: break1029 result = eye4graphics.findNextIcon(1030 ctypes.byref(struct_bbox),1031 ctypes.c_void_p(self._openedImages[ssFilename]),1032 ctypes.c_void_p(e4gIcon),1033 0, # no fuzzy matching1034 ctypes.c_double(colorMatch),1035 ctypes.c_double(opacityLimit),1036 ctypes.byref(struct_area_bbox),1037 ctypes.c_int(contOpts),1038 ctypes.c_float(xscale),1039 ctypes.c_float(yscale),1040 ctypes.c_int(bitmapPixelSize),1041 ctypes.c_int(screenshotPixelSize))1042 contOpts = 1 # search for the next hit1043 if result < 0: break1044 bbox = (int(struct_bbox.left), int(struct_bbox.top),1045 int(struct_bbox.right), int(struct_bbox.bottom))1046 addToFoundItems = True1047 if allowOverlap == False:1048 for guiItem in self._findBitmapCache[ssFilename][cacheKey]:1049 itemLeft, itemTop, itemRight, itemBottom = guiItem.bbox()1050 if ((itemLeft <= bbox[0] <= itemRight or itemLeft <= bbox[2] <= itemRight) and1051 (itemTop <= bbox[1] <= itemBottom or itemTop <= bbox[3] <= itemBottom)):1052 if ((itemLeft < bbox[0] < itemRight or itemLeft < bbox[2] < itemRight) or1053 (itemTop < bbox[1] < itemBottom or itemTop < bbox[3] < itemBottom)):1054 addToFoundItems = False1055 break1056 if addToFoundItems:1057 self._findBitmapCache[ssFilename][cacheKey].append(1058 GUIItem("bitmap", bbox, ssFilename, bitmap=bitmap))1059 matchCount += 11060 eye4graphics.closeImage(e4gIcon)1061 return self._findBitmapCache[ssFilename][cacheKey]1062def _defaultOirEngine():1063 if _g_defaultOirEngine:1064 return _g_defaultOirEngine1065 else:1066 _Eye4GraphicsOirEngine().register(defaultOir=True)1067 return _g_defaultOirEngine1068class _OirRc(object):1069 """Optical image recognition settings for a directory.1070 Currently loaded from file .fmbtoirc in the directory.1071 There is once _OirRc instance per directory.1072 """1073 _filename = ".fmbtoirrc"1074 _cache = {}1075 @classmethod1076 def load(cls, directory):1077 if directory in cls._cache:1078 pass1079 elif os.access(os.path.join(directory, cls._filename), os.R_OK):1080 cls._cache[directory] = cls(directory)1081 else:1082 cls._cache[directory] = None1083 return cls._cache[directory]1084 def __init__(self, directory):1085 self._key2value = {}1086 curdir = "."1087 self._dir2oirArgsList = {curdir: [{}]}1088 oirRcFilepath = os.path.join(directory, _OirRc._filename)1089 for line in file(oirRcFilepath):1090 line = line.strip()1091 if line == "" or line[0] in "#;":1092 continue1093 elif line == "alternative":1094 self._dir2oirArgsList[curdir].append({}) # new set of args1095 self._key2value = {}1096 elif "=" in line:1097 key, value_str = line.split("=", 1)1098 value_str = value_str.strip()1099 if key.strip().lower() == "includedir":1100 curdir = value_str1101 self._dir2oirArgsList[curdir] = [{}]1102 if not os.access(curdir, os.X_OK):1103 _fmbtLog("warning: %s: inaccessible includedir %s" %1104 (repr(oirRcFilepath), curdir))1105 else:1106 try: value = int(value_str)1107 except ValueError:1108 try: value = float(value_str)1109 except ValueError:1110 if value_str[0] in "([\"'": # tuple, list, string1111 value = eval(value_str)1112 else:1113 value = value_str1114 self._dir2oirArgsList[curdir][-1][key.strip()] = value1115 def searchDirs(self):1116 return self._dir2oirArgsList.keys()1117 def oirArgsList(self, searchDir):1118 return self._dir2oirArgsList[searchDir]1119class _Paths(object):1120 def __init__(self, bitmapPath, relativeRoot):1121 self.bitmapPath = bitmapPath1122 self.relativeRoot = relativeRoot1123 self._oirAL = {} # OIR parameters for bitmaps1124 self._abspaths = {} # bitmap to abspaths1125 def abspaths(self, bitmap, checkReadable=True):1126 if bitmap in self._abspaths:1127 return self._abspaths[bitmap]1128 if bitmap.startswith("/"):1129 path = [os.path.dirname(bitmap)]1130 bitmap = os.path.basename(bitmap)1131 else:1132 path = []1133 for singleDir in self.bitmapPath.split(":"):1134 if singleDir and not singleDir.startswith("/"):1135 path.append(os.path.join(self.relativeRoot, singleDir))1136 else:1137 path.append(singleDir)1138 for singleDir in path:1139 candidate = os.path.join(singleDir, bitmap)1140 if not checkReadable or os.access(candidate, os.R_OK):1141 oirRc = _OirRc.load(os.path.dirname(candidate))1142 if oirRc:1143 self._oirAL[candidate] = oirRc.oirArgsList(".")1144 else:1145 self._oirAL[candidate] = [{}]1146 self._oirAL[bitmap] = self._oirAL[candidate]1147 break1148 else:1149 # bitmap is not in singleDir, but there might be .fmbtoirrc1150 oirRc = _OirRc.load(os.path.dirname(candidate))1151 if oirRc:1152 for d in oirRc.searchDirs():1153 if d.startswith("/"):1154 candidate = os.path.join(d, os.path.basename(bitmap))1155 else:1156 candidate = os.path.join(os.path.dirname(candidate), d, os.path.basename(bitmap))1157 if os.access(candidate, os.R_OK):1158 self._oirAL[candidate] = oirRc.oirArgsList(d)1159 self._oirAL[bitmap] = self._oirAL[candidate]1160 break1161 if checkReadable and not os.access(candidate, os.R_OK):1162 raise ValueError('Bitmap "%s" not readable in bitmapPath %s' % (bitmap, ':'.join(path)))1163 self._abspaths[bitmap] = [candidate]1164 # check for alternative bitmaps1165 try:1166 candidate_ext = "." + candidate.rsplit(".", 1)[1]1167 except IndexError:1168 candidate_ext = ""1169 alt_candidates = glob.glob(candidate + ".alt*" + candidate_ext)1170 self._abspaths[bitmap].extend(alt_candidates)1171 return self._abspaths[bitmap]1172 def oirArgsList(self, bitmap):1173 """Returns list of alternative OIR parameters associated to the bitmap1174 by appropriate .fmbtoirrc file1175 """1176 if bitmap in self._oirAL:1177 return self._oirAL[bitmap]1178 else:1179 absBitmap = self.abspaths(bitmap)[0]1180 if absBitmap in self._oirAL:1181 return self._oirAL[absBitmap]1182 else:1183 return None1184class GUITestInterface(object):1185 def __init__(self, ocrEngine=None, oirEngine=None, rotateScreenshot=None):1186 self._paths = _Paths("", "")1187 self._conn = None1188 self._lastScreenshot = None1189 self._longPressHoldTime = 2.01190 self._longTapHoldTime = 2.01191 self._ocrEngine = None1192 self._oirEngine = None1193 self._rotateScreenshot = rotateScreenshot1194 self._screenshotLimit = None1195 self._screenshotRefCount = {} # filename -> Screenshot object ref count1196 self._screenshotArchiveMethod = "resize"1197 if ocrEngine == None:1198 self.setOcrEngine(_defaultOcrEngine())1199 else:1200 if type(ocrEngine) == int:1201 self.setOcrEngine(_g_ocrEngines[ocrEngine])1202 else:1203 self.setOcrEngine(ocrEngine)1204 if oirEngine == None:1205 self.setOirEngine(_defaultOirEngine())1206 else:1207 if type(oirEngine) == int:1208 self.setOirEngine(_g_oirEngines[oirEngine])1209 else:1210 self.setOirEngine(oirEngine)1211 self._screenshotDir = None1212 self._screenshotDirDefault = "screenshots"1213 self._screenshotSubdir = None1214 self._screenshotSubdirDefault = ""1215 self._screenSize = None1216 self._tapDefaults = {}1217 self._visualLog = None1218 self._visualLogFileObj = None1219 self._visualLogFilenames = set()1220 def bitmapPath(self):1221 """1222 Returns bitmapPath from which bitmaps are searched for.1223 """1224 return self._paths.bitmapPath1225 def bitmapPathRoot(self):1226 """1227 Returns the path that prefixes all relative directories in1228 bitmapPath.1229 """1230 return self._paths.relativeRoot1231 def close(self):1232 self._lastScreenshot = None1233 if self._visualLog:1234 if (hasattr(self._visualLog._outFileObj, "name") and1235 self._visualLog._outFileObj.name in self._visualLogFilenames):1236 self._visualLogFilenames.remove(self._visualLog._outFileObj.name)1237 self._visualLog.close()1238 if self._visualLogFileObj:1239 self._visualLogFileObj.close()1240 self._visualLog = None1241 def connection(self):1242 """1243 Returns GUITestConnection instance or None if not available.1244 See also existingConnection().1245 """1246 return self._conn1247 def drag(self, (x1, y1), (x2, y2), delayBetweenMoves=0.01,1248 delayBeforeMoves=0, delayAfterMoves=0, movePoints=20,1249 button=_USE_DEFAULTS):1250 """1251 Touch the screen on coordinates (x1, y1), drag along straight1252 line to coordinates (x2, y2), and raise fingertip.1253 Parameters:1254 coordinates (floats in range [0.0, 1.0] or integers):1255 floating point coordinates in range [0.0, 1.0] are1256 scaled to full screen width and height, others are1257 handled as absolute coordinate values.1258 delayBeforeMoves (float, optional):1259 seconds to wait after touching and before dragging.1260 If negative, starting touch event is not sent.1261 delayBetweenMoves (float, optional):1262 seconds to wait when moving between points when1263 dragging.1264 delayAfterMoves (float, optional):1265 seconds to wait after dragging, before raising1266 fingertip.1267 If negative, fingertip is not raised.1268 movePoints (integer, optional):1269 the number of intermediate move points between end1270 points of the line.1271 button (integer, optional):1272 send drag using given mouse button. The default is None.1273 Returns True on success, False if sending input failed.1274 """1275 x1, y1 = self.intCoords((x1, y1))1276 x2, y2 = self.intCoords((x2, y2))1277 extraArgs = {}1278 if button != _USE_DEFAULTS:1279 extraArgs["button"] = button1280 if delayBeforeMoves >= 0:1281 if not self.existingConnection().sendTouchDown(x1, y1, **extraArgs):1282 return False1283 if delayBeforeMoves > 0:1284 time.sleep(delayBeforeMoves)1285 else:1286 time.sleep(delayBetweenMoves)1287 for i in xrange(0, movePoints):1288 nx = x1 + int(round(((x2 - x1) / float(movePoints+1)) * (i+1)))1289 ny = y1 + int(round(((y2 - y1) / float(movePoints+1)) * (i+1)))1290 if not self.existingConnection().sendTouchMove(nx, ny, **extraArgs):1291 return False1292 time.sleep(delayBetweenMoves)1293 if delayAfterMoves > 0:1294 self.existingConnection().sendTouchMove(x2, y2, **extraArgs)1295 time.sleep(delayAfterMoves)1296 if delayAfterMoves >= 0:1297 if self.existingConnection().sendTouchUp(x2, y2, **extraArgs):1298 return True1299 else:1300 return False1301 else:1302 return True1303 def enableVisualLog(self, filenameOrObj,1304 screenshotWidth="240", thumbnailWidth="",1305 timeFormat="%s.%f", delayedDrawing=False,1306 copyBitmapsToScreenshotDir=False):1307 """1308 Start writing visual HTML log on this device object.1309 Parameters:1310 filenameOrObj (string or a file object)1311 The file to which the log is written. Log can be1312 split into multiple html files by using strftime1313 conversion specifications in filenameOrObj. For1314 instance, "%a-%H.html" will log to "Thu-16.html" on1315 Thurday from 4 pm to 5 pm.1316 screenshotWidth (string, optional)1317 Width of screenshot images in HTML.1318 The default is "240".1319 thumbnailWidth (string, optional)1320 Width of thumbnail images in HTML.1321 The default is "", that is, original size.1322 timeFormat (string, optional)1323 Timestamp format. The default is "%s.%f".1324 Refer to strftime documentation.1325 delayedDrawing (boolean, optional)1326 If True, screenshots with highlighted icons, words1327 and gestures are not created during the1328 test. Instead, only shell commands are stored for1329 later execution. The value True can significantly1330 save test execution time and disk space. The default1331 is False.1332 copyBitmapsToScreenshotDir (boolean, optional)1333 If True, every logged bitmap file will be copied to1334 bitmaps directory in screenshotDir. The default is1335 False.1336 """1337 if type(filenameOrObj) == str:1338 try:1339 outFileObj = file(filenameOrObj, "w")1340 self._visualLogFileObj = outFileObj1341 except Exception, e:1342 _fmbtLog('Failed to open file "%s" for logging.' % (filenameOrObj,))1343 raise1344 else:1345 outFileObj = filenameOrObj1346 # someone else opened the file => someone else will close it1347 self._visualLogFileObj = None1348 if hasattr(outFileObj, "name"):1349 if outFileObj.name in self._visualLogFilenames:1350 raise ValueError('Visual logging on file "%s" is already enabled' % (outFileObj.name,))1351 else:1352 self._visualLogFilenames.add(outFileObj.name)1353 self._visualLog = _VisualLog(self, outFileObj, screenshotWidth,1354 thumbnailWidth, timeFormat, delayedDrawing,1355 copyBitmapsToScreenshotDir)1356 def existingConnection(self):1357 """1358 Returns GUITestConnection, raises ConnectionError if not available.1359 See also connection()1360 """1361 if self._conn:1362 return self._conn1363 else:1364 raise ConnectionError("not connected")1365 def visualLog(self, *args):1366 """1367 Writes parameters to the visual log, given that visual logging is1368 enabled.1369 """1370 pass1371 def intCoords(self, (x, y)):1372 """1373 Convert floating point coordinate values in range [0.0, 1.0] to1374 screen coordinates.1375 """1376 width, height = self.screenSize()1377 return _intCoords((x, y), (width, height))1378 def relCoords(self, (x, y)):1379 """1380 Convert coordinates (x, y) to relative coordinates in range [0.0, 1.0].1381 """1382 width, height = self.screenSize()1383 ix, iy = _intCoords((x, y), (width, height))1384 return (float(ix)/width, float(iy)/height)1385 def itemOnScreen(self, guiItem):1386 """1387 Returns True if bbox of guiItem is non-empty and at least1388 partially on screen, otherwise False.1389 """1390 maxX, maxY = self.screenSize()1391 return _boxOnRegion(guiItem.bbox(), (0, 0, maxX, maxY))1392 def ocrEngine(self):1393 """1394 Returns the OCR engine that is used by default for new1395 screenshots.1396 """1397 return self._ocrEngine1398 def oirEngine(self):1399 """1400 Returns the OIR engine that is used by default for new1401 screenshots.1402 """1403 return self._oirEngine1404 def pressKey(self, keyName, long=False, hold=0.0, modifiers=None):1405 """1406 Press a key.1407 Parameters:1408 keyName (string):1409 the name of the key, like KEYCODE_HOME.1410 long (boolean, optional):1411 if True, press the key for long time.1412 hold (float, optional):1413 time in seconds to hold the key down.1414 modifiers (list of strings, optional)1415 modifier key(s) to be pressed at the same time.1416 """1417 extraParams = {}1418 if modifiers != None:1419 extraParams['modifiers'] = modifiers1420 if long and hold == 0.0:1421 hold = self._longPressHoldTime1422 if hold > 0.0:1423 try:1424 assert self.existingConnection().sendKeyDown(keyName, **extraParams)1425 time.sleep(hold)1426 assert self.existingConnection().sendKeyUp(keyName, **extraParams)1427 except AssertionError:1428 return False1429 return True1430 return self.existingConnection().sendPress(keyName, **extraParams)1431 def _newScreenshotFilepath(self):1432 """1433 Returns path and filename for next screenshot file.1434 Makes sure the file can be written (necessary directory1435 structure exists).1436 """1437 t = datetime.datetime.now()1438 if not self._conn:1439 target = ""1440 else:1441 target = self._conn.target()1442 filename = _filenameTimestamp(t) + "-" + target + ".png"1443 screenshotPath = self.screenshotDir()1444 if self.screenshotSubdir():1445 screenshotPath = os.path.join(screenshotPath,1446 self.screenshotSubdir())1447 screenshotPath = fmbt.formatTime(screenshotPath, t)1448 filepath = os.path.join(screenshotPath, filename)1449 necessaryDirs = os.path.dirname(filepath)1450 if necessaryDirs and not os.path.isdir(necessaryDirs):1451 try:1452 os.makedirs(necessaryDirs)1453 except Exception, e:1454 _fmbtLog('creating directory "%s" for screenshots failed: %s' %1455 (necessaryDirs, e))1456 raise1457 return filepath1458 def _archiveScreenshot(self, filepath):1459 if self._screenshotArchiveMethod == "remove":1460 try:1461 os.remove(filepath)1462 except IOError:1463 pass1464 elif self._screenshotArchiveMethod.startswith("resize"):1465 if self._screenshotArchiveMethod == "resize":1466 convertArgs = ["-resize",1467 "%sx" % (int(self.screenSize()[0]) / 4,)]1468 else:1469 widthHeight = self._screenshotArchiveMethod.split()[1]1470 convertArgs = ["-resize", widthHeight]1471 subprocess.call([fmbt_config.imagemagick_convert, filepath] + convertArgs + [filepath])1472 def _archiveScreenshots(self):1473 """1474 Archive screenshot files if screenshotLimit has been exceeded.1475 """1476 freeScreenshots = [filename1477 for (filename, refCount) in self._screenshotRefCount.iteritems()1478 if refCount == 0]1479 archiveCount = len(freeScreenshots) - self._screenshotLimit1480 if archiveCount > 0:1481 freeScreenshots.sort(reverse=True) # archive oldest1482 while archiveCount > 0:1483 toBeArchived = freeScreenshots.pop()1484 try:1485 self._archiveScreenshot(toBeArchived)1486 except IOError:1487 pass1488 del self._screenshotRefCount[toBeArchived]1489 archiveCount -= 11490 def refreshScreenshot(self, forcedScreenshot=None, rotate=None):1491 """1492 Takes new screenshot and updates the latest screenshot object.1493 Parameters:1494 forcedScreenshot (Screenshot or string, optional):1495 use given screenshot object or image file, do not1496 take new screenshot.1497 rotate (integer, optional):1498 rotate screenshot by given number of degrees. This1499 overrides constructor rotateScreenshot parameter1500 value. The default is None (no override).1501 Returns Screenshot object, and makes the same object "the1502 latest screenshot" that is used by all *Bitmap and *OcrText1503 methods. Returns None if screenshot cannot be taken.1504 """1505 if forcedScreenshot != None:1506 if type(forcedScreenshot) == str:1507 self._lastScreenshot = Screenshot(1508 screenshotFile=forcedScreenshot,1509 paths = self._paths,1510 ocrEngine=self._ocrEngine,1511 oirEngine=self._oirEngine,1512 screenshotRefCount=self._screenshotRefCount)1513 else:1514 self._lastScreenshot = forcedScreenshot1515 elif self._conn: # There is a connection, get new screenshot1516 if self.screenshotDir() == None:1517 self.setScreenshotDir(self._screenshotDirDefault)1518 if self.screenshotSubdir() == None:1519 self.setScreenshotSubdir(self._screenshotSubdirDefault)1520 screenshotFile = self._newScreenshotFilepath()1521 if self.existingConnection().recvScreenshot(screenshotFile):1522 # New screenshot successfully received from device1523 if rotate == None:1524 rotate = self._rotateScreenshot1525 if rotate != None and rotate != 0:1526 subprocess.call([fmbt_config.imagemagick_convert, screenshotFile, "-rotate", str(rotate), screenshotFile])1527 self._lastScreenshot = Screenshot(1528 screenshotFile=screenshotFile,1529 paths = self._paths,1530 ocrEngine=self._ocrEngine,1531 oirEngine=self._oirEngine,1532 screenshotRefCount=self._screenshotRefCount)1533 else:1534 self._lastScreenshot = None1535 else: # No connection, cannot get a screenshot1536 self._lastScreenshot = None1537 # Make sure unreachable Screenshot instances are released from1538 # memory.1539 gc.collect()1540 for obj in gc.garbage:1541 if isinstance(obj, Screenshot):1542 if hasattr(obj, "_logCallReturnValue"):1543 # Some methods have been wrapped by visual1544 # log. Break reference cycles to let gc collect1545 # them.1546 try:1547 del obj.findItemsByBitmap1548 except:1549 pass1550 try:1551 del obj.findItemsByOcr1552 except:1553 pass1554 del gc.garbage[:]1555 gc.collect()1556 # If screenshotLimit has been set, archive old screenshot1557 # stored on the disk.1558 if self._screenshotLimit != None and self._screenshotLimit >= 0:1559 self._archiveScreenshots()1560 return self._lastScreenshot1561 def screenshot(self):1562 """1563 Returns the latest Screenshot object.1564 Use refreshScreenshot() to get a new screenshot.1565 """1566 return self._lastScreenshot1567 def screenshotArchiveMethod(self):1568 """1569 Returns how screenshots exceeding screenshotLimit are archived.1570 """1571 return self._screenshotArchiveMethod1572 def screenshotDir(self):1573 """1574 Returns the directory under which new screenshots are saved.1575 """1576 return self._screenshotDir1577 def screenshotLimit(self):1578 """1579 Returns the limit after which unused screenshots are archived.1580 """1581 return self._screenshotLimit1582 def screenshotSubdir(self):1583 """1584 Returns the subdirectory in screenshotDir under which new1585 screenshots are stored.1586 """1587 return self._screenshotSubdir1588 def screenSize(self):1589 """1590 Returns screen size in pixels in tuple (width, height).1591 """1592 if self._lastScreenshot != None:1593 self._screenSize = self._lastScreenshot.size()1594 if self._screenSize == None:1595 if self._lastScreenshot == None:1596 try:1597 if self.refreshScreenshot():1598 self._screenSize = self._lastScreenshot.size()1599 self._lastScreenshot = None1600 except Exception:1601 pass1602 if (self._screenSize == None and1603 hasattr(self.existingConnection(), "recvScreenSize")):1604 self._screenSize = self.existingConnection().recvScreenSize()1605 else:1606 self._screenSize = self._lastScreenshot.size()1607 return self._screenSize1608 def setBitmapPath(self, bitmapPath, rootForRelativePaths=None):1609 """1610 Set new path for finding bitmaps.1611 Parameters:1612 bitmapPath (string)1613 colon-separated list of directories from which1614 bitmap methods look for bitmap files.1615 rootForRelativePaths (string, optional)1616 path that will prefix all relative paths in1617 bitmapPath.1618 Example:1619 gui.setBitmapPath("bitmaps:icons:/tmp", "/home/X")1620 gui.tapBitmap("start.png")1621 will look for /home/X/bitmaps/start.png,1622 /home/X/icons/start.png and /tmp/start.png, in this order.1623 """1624 self._paths.bitmapPath = bitmapPath1625 if rootForRelativePaths != None:1626 self._paths.relativeRoot = rootForRelativePaths1627 def setConnection(self, conn):1628 """1629 Set the connection object that performs actions on real target.1630 Parameters:1631 conn (GUITestConnection instance):1632 The connection to be used.1633 """1634 self._conn = conn1635 def setOcrEngine(self, ocrEngine):1636 """1637 Set OCR (optical character recognition) engine that will be1638 used by default in new screenshots.1639 Returns previous default.1640 """1641 prevDefault = self._ocrEngine1642 self._ocrEngine = ocrEngine1643 return prevDefault1644 def setOirEngine(self, oirEngine):1645 """1646 Set OIR (optical image recognition) engine that will be used1647 by default in new screenshots.1648 Returns previous default.1649 """1650 prevDefault = self._oirEngine1651 self._oirEngine = oirEngine1652 return prevDefault1653 def setScreenshotArchiveMethod(self, screenshotArchiveMethod):1654 """1655 Set method for archiving screenshots when screenshotLimit is exceeded.1656 Parameters:1657 screenshotArchiveMethod (string)1658 Supported methods are "resize [WxH]" and "remove"1659 where W and H are integers that define maximum width and1660 height for an archived screenshot.1661 The default method is "resize".1662 """1663 if screenshotArchiveMethod == "remove":1664 pass1665 elif screenshotArchiveMethod == "resize":1666 pass1667 elif screenshotArchiveMethod.startswith("resize"):1668 try:1669 w, h = screenshotArchiveMethod.split(" ")[1].split("x")1670 except:1671 raise ValueError("Invalid resize syntax")1672 try:1673 w, h = int(w), int(h)1674 except:1675 raise ValueError(1676 "Invalid resize width or height, integer expected")1677 else:1678 raise ValueError('Unknown archive method "%s"' %1679 (screenshotArchiveMethod,))1680 self._screenshotArchiveMethod = screenshotArchiveMethod1681 def setScreenshotDir(self, screenshotDir):1682 self._screenshotDir = screenshotDir1683 self._newScreenshotFilepath() # make directories1684 def setScreenshotLimit(self, screenshotLimit):1685 """1686 Set maximum number for unarchived screenshots.1687 Parameters:1688 screenshotLimit (integer)1689 Maximum number of unarchived screenshots that are1690 free for archiving (that is, not referenced by test code).1691 The default is None, that is, there is no limit and1692 screenshots are never archived.1693 See also:1694 setScreenshotArchiveMethod()1695 """1696 self._screenshotLimit = screenshotLimit1697 def setScreenshotSubdir(self, screenshotSubdir):1698 """1699 Define a subdirectory under screenshotDir() for screenshot files.1700 Parameters:1701 screenshotSubdir (string)1702 Name of a subdirectory. The name should contain1703 conversion specifiers supported by strftime.1704 Example:1705 sut.setScreenshotSubdir("%m-%d-%H")1706 A screenshot taken on June 20th at 4.30pm will1707 be stored to screenshotDir/06-20-16. That is,1708 screenshots taken on different hours will be1709 stored to different subdirectories.1710 By default, all screenshots are stored directly to screenshotDir().1711 """1712 self._screenshotSubdir = screenshotSubdir1713 def setTapDefaults(self, **tapDefaults):1714 """1715 Define default parameters for tap methods.1716 Parameters:1717 **tapDefaults (keyword arguments):1718 default arguments to be used in sendTap call unless1719 explicitely overridden by user.1720 Example:1721 sut.setTapDefaults(button=1)1722 after this sut.tapBitmap("ref.png") does the same as1723 sut.tapBitmap("ref.png", button=1) did before.1724 """1725 self._tapDefaults.update(tapDefaults)1726 def swipe(self, (x, y), direction, distance=1.0, **dragArgs):1727 """1728 swipe starting from coordinates (x, y) to given direction.1729 Parameters:1730 coordinates (floats in range [0.0, 1.0] or integers):1731 floating point coordinates in range [0.0, 1.0] are1732 scaled to full screen width and height, others are1733 handled as absolute coordinate values.1734 direction (string or integer):1735 Angle (0..360 degrees), or "north", "south", "east"1736 and "west" (corresponding to angles 90, 270, 0 and1737 180).1738 distance (float, optional):1739 Swipe distance. Values in range [0.0, 1.0] are1740 scaled to the distance from the coordinates to the1741 edge of the screen. The default is 1.0: swipe to the1742 edge of the screen.1743 rest of the parameters: refer to drag documentation.1744 Returns True on success, False if sending input failed.1745 """1746 if type(direction) == str:1747 d = direction.lower()1748 if d in ["n", "north"]: direction = 901749 elif d in ["s", "south"]: direction = 2701750 elif d in ["e", "east"]: direction = 01751 elif d in ["w", "west"]: direction = 1801752 else: raise ValueError('Illegal direction "%s"' % (direction,))1753 direction = direction % 3601754 x, y = self.intCoords((x, y))1755 dirRad = math.radians(direction)1756 distToEdge = _edgeDistanceInDirection((x, y), self.screenSize(), direction)1757 if distance > 1.0: distance = float(distance) / distToEdge1758 x2 = int(x + math.cos(dirRad) * distToEdge * distance)1759 y2 = int(y - math.sin(dirRad) * distToEdge * distance)1760 return self.drag((x, y), (x2, y2), **dragArgs)1761 def swipeBitmap(self, bitmap, direction, distance=1.0, **dragAndOirArgs):1762 """1763 swipe starting from bitmap to given direction.1764 Parameters:1765 bitmap (string)1766 bitmap from which swipe starts1767 direction, distance1768 refer to swipe documentation.1769 startPos, startOffset1770 refer to swipeItem documentation.1771 optical image recognition arguments (optional)1772 refer to help(obj.oirEngine()).1773 delayBeforeMoves, delayBetweenMoves, delayAfterMoves,1774 movePoints1775 refer to drag documentation.1776 Returns True on success, False if sending input failed.1777 """1778 assert self._lastScreenshot != None, "Screenshot required."1779 dragArgs, rest = _takeDragArgs(dragAndOirArgs)1780 oirArgs, _ = _takeOirArgs(self._lastScreenshot, rest, thatsAll=True)1781 oirArgs["limit"] = 11782 items = self._lastScreenshot.findItemsByBitmap(bitmap, **oirArgs)1783 if len(items) == 0:1784 return False1785 return self.swipeItem(items[0], direction, distance, **dragArgs)1786 def swipeItem(self, viewItem, direction, distance=1.0, **dragArgs):1787 """1788 swipe starting from viewItem to given direction.1789 Parameters:1790 viewItem (ViewItem)1791 item from which swipe starts1792 direction, distance1793 refer to swipe documentation.1794 startPos (pair of floats (x, y)):1795 position of starting swipe, relative to the item.1796 (0.0, 0.0) is the top-left corner,1797 (1.0, 0.0) is the top-right corner,1798 (1.0, 1.0) is the lower-right corner.1799 Values < 0.0 and > 1.0 start swiping from coordinates1800 outside the item.1801 The default is (0.5, 0.5), in the middle of the item.1802 startOffset (pair of integers or floats (x, y)):1803 offset of swipe start coordinates. Integers are1804 pixels, floats are relative to screensize.1805 Example:1806 startOffset=(0, 0.1) will keep the X coordinate1807 unchagned and add 10 % of screensize to Y.1808 delayBeforeMoves, delayBetweenMoves, delayAfterMoves,1809 movePoints1810 refer to drag documentation.1811 Returns True on success, False if sending input failed.1812 """1813 if "startPos" in dragArgs:1814 posX, posY = dragArgs["startPos"]1815 del dragArgs["startPos"]1816 x1, y1, x2, y2 = viewItem.bbox()1817 swipeCoords = (x1 + (x2-x1) * posX,1818 y1 + (y2-y1) * posY)1819 else:1820 swipeCoords = viewItem.coords()1821 if "startOffset" in dragArgs:1822 offX, offY = dragArgs["startOffset"]1823 del dragArgs["startOffset"]1824 x, y = swipeCoords1825 if isinstance(offX, int):1826 x += offX1827 elif isinstance(offX, float):1828 width, _ = self.screenSize()1829 x += offX * width1830 else:1831 raise TypeError('invalid offset %s (int or float expected)' %1832 (repr(offX),))1833 if isinstance(offY, int):1834 y += offY1835 elif isinstance(offY, float):1836 _, height = self.screenSize()1837 y += offY * height1838 else:1839 raise TypeError('invalid offset %s (int or float expected)' %1840 (repr(offY),))1841 swipeCoords = (x, y)1842 return self.swipe(swipeCoords, direction, distance, **dragArgs)1843 def swipeOcrText(self, text, direction, distance=1.0, **dragAndOcrArgs):1844 """1845 Find text from the latest screenshot using OCR, and swipe it.1846 Parameters:1847 text (string):1848 the text to be swiped.1849 direction, distance1850 refer to swipe documentation.1851 startPos1852 refer to swipeItem documentation.1853 delayBeforeMoves, delayBetweenMoves, delayAfterMoves,1854 movePoints1855 refer to drag documentation.1856 OCR engine specific arguments1857 refer to help(obj.ocrEngine())1858 Returns True on success, False otherwise.1859 """1860 assert self._lastScreenshot != None, "Screenshot required."1861 dragArgs, rest = _takeDragArgs(dragAndOcrArgs)1862 ocrArgs, _ = _takeOcrArgs(self._lastScreenshot, rest, thatsAll=True)1863 items = self._lastScreenshot.findItemsByOcr(text, **ocrArgs)1864 if len(items) == 0:1865 return False1866 return self.swipeItem(items[0], direction, distance, **dragArgs)1867 def tap(self, (x, y), long=_USE_DEFAULTS, hold=_USE_DEFAULTS,1868 count=_USE_DEFAULTS, delayBetweenTaps=_USE_DEFAULTS,1869 button=_USE_DEFAULTS):1870 """1871 Tap screen on coordinates (x, y).1872 Parameters:1873 coordinates (floats in range [0.0, 1.0] or integers):1874 floating point coordinates in range [0.0, 1.0] are1875 scaled to full screen width and height, others are1876 handled as absolute coordinate values.1877 count (integer, optional):1878 number of taps to the coordinates. The default is 1.1879 delayBetweenTaps (float, optional):1880 time (seconds) between taps when count > 1.1881 The default is 0.175 (175 ms).1882 long (boolean, optional):1883 if True, touch the screen for a long time.1884 hold (float, optional):1885 time in seconds to touch the screen.1886 button (integer, optional):1887 send tap using given mouse button. The default is1888 None: button parameter is not passed to the1889 underlying connection layer (sendTouchDown etc.),1890 the default in the underlying layer will be used.1891 Note that all connection layers may not support1892 this parameter.1893 Returns True if successful, otherwise False.1894 """1895 if long == _USE_DEFAULTS:1896 long = self._tapDefaults.get("long", False)1897 if hold == _USE_DEFAULTS:1898 hold = self._tapDefaults.get("hold", 0.0)1899 if count == _USE_DEFAULTS:1900 count = self._tapDefaults.get("count", 1)1901 if delayBetweenTaps == _USE_DEFAULTS:1902 delayBetweenTaps = self._tapDefaults.get("delayBetweenTaps", 0.175)1903 if button == _USE_DEFAULTS:1904 button = self._tapDefaults.get("button", None)1905 x, y = self.intCoords((x, y))1906 count = int(count)1907 if long and hold == 0.0:1908 hold = self._longTapHoldTime1909 extraParams = {}1910 if button != None:1911 extraParams['button'] = button1912 if count == 0:1913 self.existingConnection().sendTouchMove(x, y)1914 while count > 0:1915 if hold > 0.0:1916 try:1917 assert self.existingConnection().sendTouchDown(x, y, **extraParams)1918 time.sleep(hold)1919 assert self.existingConnection().sendTouchUp(x, y, **extraParams)1920 except AssertionError:1921 return False1922 else:1923 if not self.existingConnection().sendTap(x, y, **extraParams):1924 return False1925 count = int(count) - 11926 return True1927 def tapBitmap(self, bitmap, **tapAndOirArgs):1928 """1929 Find a bitmap from the latest screenshot, and tap it.1930 Parameters:1931 bitmap (string):1932 filename of the bitmap to be tapped.1933 optical image recognition arguments (optional)1934 refer to help(obj.oirEngine()).1935 tapPos (pair of floats (x,y)):1936 refer to tapItem documentation.1937 tapOffset (pair of floats or integers (x, y)):1938 refer to tapItem documentation.1939 long, hold, count, delayBetweenTaps, button (optional):1940 refer to tap documentation.1941 Returns True if successful, otherwise False.1942 """1943 assert self._lastScreenshot != None, "Screenshot required."1944 tapArgs, rest = _takeTapArgs(tapAndOirArgs)1945 oirArgs, _ = _takeOirArgs(self._lastScreenshot, rest, thatsAll=True)1946 oirArgs["limit"] = 11947 items = self._lastScreenshot.findItemsByBitmap(bitmap, **oirArgs)1948 if len(items) == 0:1949 return False1950 return self.tapItem(items[0], **tapArgs)1951 def tapDefaults(self):1952 """1953 Returns default parameters for sendTap method.1954 See also setTapDefaults.1955 """1956 return self._tapDefaults1957 def tapItem(self, viewItem, **tapArgs):1958 """1959 Tap the center point of viewItem.1960 Parameters:1961 viewItem (GUIItem object):1962 item to be tapped, possibly returned by1963 findItemsBy... methods in Screenshot or View.1964 tapPos (pair of floats (x, y)):1965 position to tap, relative to the item.1966 (0.0, 0.0) is the top-left corner,1967 (1.0, 0.0) is the top-right corner,1968 (1.0, 1.0) is the lower-right corner.1969 Values < 0 and > 1 tap coordinates outside the item.1970 The default is (0.5, 0.5), in the middle of the item.1971 tapOffset (pair of floats or integers (x, y)):1972 offset of tap coordinates. Integers are1973 pixels, floats are relative to screensize.1974 Example:1975 tapOffset=(0, 0.1) will keep the X coordinate1976 unchagned and add 10 % of screensize to Y.1977 long, hold, count, delayBetweenTaps, button (optional):1978 refer to tap documentation.1979 """1980 if "tapPos" in tapArgs:1981 posX, posY = tapArgs["tapPos"]1982 del tapArgs["tapPos"]1983 x1, y1, x2, y2 = viewItem.bbox()1984 tapCoords = (x1 + (x2-x1) * posX,1985 y1 + (y2-y1) * posY)1986 else:1987 tapCoords = viewItem.coords()1988 if "tapOffset" in tapArgs:1989 offX, offY = tapArgs["tapOffset"]1990 del tapArgs["tapOffset"]1991 x, y = tapCoords1992 if isinstance(offX, int):1993 x += offX1994 elif isinstance(offX, float):1995 width, _ = self.screenSize()1996 x += offX * width1997 else:1998 raise TypeError('invalid offset %s (int or float expected)' %1999 (repr(offX),))2000 if isinstance(offY, int):2001 y += offY2002 elif isinstance(offY, float):2003 _, height = self.screenSize()2004 y += offY * height2005 else:2006 raise TypeError('invalid offset %s (int or float expected)' %2007 (repr(offY),))2008 tapCoords = (x, y)2009 return self.tap(tapCoords, **tapArgs)2010 def tapOcrText(self, text, appearance=0, **tapAndOcrArgs):2011 """2012 Find text from the latest screenshot using OCR, and tap it.2013 Parameters:2014 text (string):2015 the text to be tapped.2016 long, hold, count, delayBetweenTaps, button (optional):2017 refer to tap documentation.2018 OCR engine specific arguments2019 refer to help(obj.ocrEngine())2020 Returns True if successful, otherwise False.2021 """2022 assert self._lastScreenshot != None, "Screenshot required."2023 tapArgs, rest = _takeTapArgs(tapAndOcrArgs)2024 ocrArgs, _ = _takeOcrArgs(self._lastScreenshot, rest, thatsAll=True)2025 items = self._lastScreenshot.findItemsByOcr(text, **ocrArgs)2026 if len(items) <= appearance:2027 return False2028 return self.tapItem(items[appearance], **tapArgs)2029 def type(self, text):2030 """2031 Type text.2032 """2033 return self.existingConnection().sendType(text)2034 def verifyOcrText(self, text, **ocrArgs):2035 """2036 Verify using OCR that the last screenshot contains the text.2037 Parameters:2038 text (string):2039 text to be verified.2040 OCR engine specific arguments2041 refer to help(obj.ocrEngine())2042 Returns True if successful, otherwise False.2043 """2044 assert self._lastScreenshot != None, "Screenshot required."2045 ocrArgs, _ = _takeOcrArgs(self._lastScreenshot, ocrArgs, thatsAll=True)2046 return self._lastScreenshot.findItemsByOcr(text, **ocrArgs) != []2047 def verifyBitmap(self, bitmap, **oirArgs):2048 """2049 Verify that bitmap is present in the last screenshot.2050 Parameters:2051 bitmap (string):2052 filename of the bitmap file to be searched for.2053 optical image recognition arguments (optional)2054 refer to help(obj.oirEngine()).2055 """2056 assert self._lastScreenshot != None, "Screenshot required."2057 oirArgs, _ = _takeOirArgs(self._lastScreenshot, oirArgs, thatsAll=True)2058 oirArgs["limit"] = 12059 return self._lastScreenshot.findItemsByBitmap(bitmap, **oirArgs) != []2060 def wait(self, refreshFunc, waitFunc, waitFuncArgs=(), waitFuncKwargs={},2061 waitTime = 5.0, pollDelay = 1.0,2062 beforeRefresh = lambda: None, afterRefresh = lambda: None):2063 """2064 Wait until waitFunc returns True or waitTime has expired.2065 Parameters:2066 refreshFunc (function):2067 this function is called before re-evaluating2068 waitFunc. For instance, refreshScreenshot.2069 waitFunc, waitFuncArgs, waitFuncKwargs (function, tuple,2070 dictionary):2071 wait for waitFunc(waitFuncArgs, waitFuncKwargs) to2072 return True2073 waitTime (float, optional):2074 max. time in seconds to wait for. The default is2075 5.0.2076 pollDelay (float, optional):2077 time in seconds to sleep between refreshs. The2078 default is 1.0.2079 beforeRefresh (function, optional):2080 this function will be called before every refreshFunc call.2081 The default is no operation.2082 afterRefresh (function, optional):2083 this function will be called after every refreshFunc call.2084 The default is no operation.2085 Returns True if waitFunc returns True - either immediately or2086 before waitTime has expired - otherwise False.2087 refreshFunc will not be called if waitFunc returns immediately2088 True.2089 """2090 if waitFunc(*waitFuncArgs, **waitFuncKwargs):2091 return True2092 startTime = time.time()2093 endTime = startTime + waitTime2094 now = startTime2095 while now < endTime:2096 time.sleep(min(pollDelay, (endTime - now)))2097 now = time.time()2098 beforeRefresh()2099 refreshFunc()2100 afterRefresh()2101 if waitFunc(*waitFuncArgs, **waitFuncKwargs):2102 return True2103 return False2104 def waitAny(self, listOfFuncs, waitTime=5.0, pollDelay=1.0):2105 """2106 Wait until any function returns True (or equivalent)2107 Parameters:2108 listOfFuncs (enumerable set of functions):2109 functions that will be called without parameters.2110 waitTime (float, optional):2111 max. time to wait in seconds. If None, the call2112 is blocked until a function returns True or2113 equivalent. The default is 5.0.2114 pollDelay (float, optional):2115 time in seconds to sleep before calling2116 functions again, if no function returned True.2117 The default is 1.0.2118 Returns tuple [(function index, function, return value), ...]2119 of functions in the list that returned True or equivalent.2120 Returns empty list in case of timeout.2121 Exceptions raised by functions are not catched.2122 Example: run an async cmd on Windows, wait for it to finish2123 or dialog X or Y to appear:2124 sut.shellSOE(cmd, asyncStatus="c:/temp/cmd.async.status")2125 detected = sut.waitAny(2126 [lambda: sut.topWindowProperties()["title"] == "Dialog X",2127 lambda: sut.topWindowProperties()["title"] == "Dialog Y",2128 lambda: sut.pycosh("cat c:/temp/cmd.async.status")],2129 waitTime=60)2130 if not detected:2131 ...waiting was timed out...2132 else:2133 index, func, retval = detected[0]2134 if index == 2:2135 ...c:/temp/cmd.async.status contents are in retval...2136 """2137 startTime = time.time()2138 if waitTime is None:2139 endTime = float("inf")2140 else:2141 endTime = startTime + waitTime2142 now = startTime2143 rv = []2144 while now <= endTime and not rv:2145 for index, func in enumerate(listOfFuncs):2146 retval = func()2147 if retval:2148 rv.append((index, func, retval))2149 time.sleep(min(pollDelay, (endTime - now)))2150 now = time.time()2151 return rv2152 def waitAnyBitmap(self, listOfBitmaps, **waitAndOirArgs):2153 """2154 Wait until any of given bitmaps appears on screen.2155 Parameters:2156 listOfBitmaps (list of strings):2157 list of bitmaps (filenames) to be waited for.2158 optical image recognition arguments (optional)2159 refer to help(obj.oirEngine()).2160 waitTime, pollDelay, beforeRefresh, afterRefresh (optional):2161 refer to wait documentation.2162 Returns list of bitmaps appearing in the first screenshot that2163 contains at least one of the bitmaps. If none of the bitmaps2164 appear within the time limit, returns empty list.2165 If the bitmap is not found from most recently refreshed2166 screenshot, waitAnyBitmap updates the screenshot.2167 """2168 if listOfBitmaps == []: return []2169 if not self._lastScreenshot: self.refreshScreenshot()2170 waitArgs, rest = _takeWaitArgs(waitAndOirArgs)2171 oirArgs, _ = _takeOirArgs(self._lastScreenshot, rest, thatsAll=True)2172 foundBitmaps = []2173 def observe():2174 for bitmap in listOfBitmaps:2175 if self._lastScreenshot.findItemsByBitmap(bitmap, **oirArgs):2176 foundBitmaps.append(bitmap)2177 return foundBitmaps != []2178 self.wait(self.refreshScreenshot, observe, **waitArgs)2179 return foundBitmaps2180 def waitAnyOcrText(self, listOfTexts, **waitAndOcrArgs):2181 """2182 Wait until OCR recognizes any of texts on the screen.2183 Parameters:2184 listOfTexts (list of string):2185 texts to be waited for.2186 waitTime, pollDelay, beforeRefresh, afterRefresh (optional):2187 refer to wait documentation.2188 OCR engine specific arguments2189 refer to help(obj.ocrEngine())2190 Returns list of texts that appeared in the first screenshot2191 that contains at least one of the texts. If none of the texts2192 appear within the time limit, returns empty list.2193 If any of texts is not found from most recently refreshed2194 screenshot, waitAnyOcrText updates the screenshot.2195 """2196 if listOfTexts == []: return []2197 if not self._lastScreenshot: self.refreshScreenshot()2198 waitArgs, rest = _takeWaitArgs(waitAndOcrArgs)2199 ocrArgs, _ = _takeOcrArgs(self._lastScreenshot, rest, thatsAll=True)2200 foundTexts = []2201 def observe():2202 for text in listOfTexts:2203 if self.verifyOcrText(text, **ocrArgs):2204 foundTexts.append(text)2205 return foundTexts != []2206 self.wait(self.refreshScreenshot, observe, **waitArgs)2207 return foundTexts2208 def waitBitmap(self, bitmap, **waitAndOirArgs):2209 """2210 Wait until bitmap appears on screen.2211 Parameters:2212 bitmap (string):2213 filename of the bitmap to be waited for.2214 optical image recognition arguments (optional)2215 refer to help(obj.oirEngine()).2216 waitTime, pollDelay, beforeRefresh, afterRefresh (optional):2217 refer to wait documentation.2218 Returns True if bitmap appeared within given time limit,2219 otherwise False.2220 If the bitmap is not found from most recently refreshed2221 screenshot, waitBitmap updates the screenshot.2222 """2223 return self.waitAnyBitmap([bitmap], **waitAndOirArgs) != []2224 def waitOcrText(self, text, **waitAndOcrArgs):2225 """2226 Wait until OCR detects text on the screen.2227 Parameters:2228 text (string):2229 text to be waited for.2230 waitTime, pollDelay, beforeRefresh, afterRefresh (optional):2231 refer to wait documentation.2232 OCR engine specific arguments2233 refer to help(obj.ocrEngine())2234 Returns True if the text appeared within given time limit,2235 otherwise False.2236 If the text is not found from most recently refreshed2237 screenshot, waitOcrText updates the screenshot.2238 """2239 return self.waitAnyOcrText([text], **waitAndOcrArgs) != []2240 def waitScreenUpdated(self, **waitArgs):2241 """2242 Wait until screenshot has been updated or waitTime expired.2243 Parameters:2244 waitTime, pollDelay, beforeRefresh, afterRefresh (optional):2245 refer to wait documentation.2246 Returns True if screenshot was updated before waitTime expired,2247 otherwise False. If waitTime is 0, screenshot is refreshed once.2248 Returns True if the screenshot differs from the previous.2249 waitScreenUpdated refreshes the screenshot.2250 """2251 waitTime = waitArgs.get("waitTime", 5.0)2252 pollDelay = waitArgs.get("pollDelay", 1.0)2253 beforeRefresh = waitArgs.get("beforeRefresh", lambda: None)2254 afterRefresh = waitArgs.get("afterRefresh", lambda: None)2255 updated = self.existingConnection().recvScreenUpdated(waitTime, pollDelay)2256 if updated == None:2257 # optimised version is not available, this is a fallback2258 previousScreenshot = self.screenshot()2259 if previousScreenshot == None:2260 beforeRefresh()2261 self.refreshScreenshot()2262 afterRefresh()2263 return True2264 # return True if screen changed from previous even with2265 # waitTime == 0, therefore refresh before calling wait.2266 beforeRefresh()2267 self.refreshScreenshot()2268 afterRefresh()2269 return self.wait(2270 self.refreshScreenshot,2271 lambda: not self.verifyBitmap(previousScreenshot.filename()),2272 **waitArgs)2273 elif updated == True:2274 self.refreshScreenshot()2275 elif updated == False:2276 pass # no need to fetch the same screen2277 else:2278 raise ValueError("recvScreenUpdated returned illegal value: %s" % (repr(updated),))2279 return updated2280class Screenshot(object):2281 """2282 Screenshot class takes and holds a screenshot (bitmap) of device2283 display, or a forced bitmap file if device connection is not given.2284 """2285 def __init__(self, screenshotFile=None, paths=None,2286 ocrEngine=None, oirEngine=None, screenshotRefCount=None):2287 self._filename = screenshotFile2288 self._ocrEngine = ocrEngine2289 self._ocrEngineNotified = False2290 self._oirEngine = oirEngine2291 self._oirEngineNotified = False2292 self._screenshotRefCount = screenshotRefCount2293 if (type(self._screenshotRefCount) == dict and self._filename):2294 self._screenshotRefCount[self._filename] = (1 +2295 self._screenshotRefCount.get(self._filename, 0))2296 self._screenSize = None2297 self._paths = paths2298 def __del__(self):2299 if self._ocrEngine and self._ocrEngineNotified:2300 self._ocrEngine.removeScreenshot(self)2301 if self._oirEngine and self._oirEngineNotified:2302 if (self._ocrEngineNotified == False or2303 id(self._oirEngine) != id(self._ocrEngine)):2304 self._oirEngine.removeScreenshot(self)2305 if (type(self._screenshotRefCount) == dict and self._filename):2306 self._screenshotRefCount[self._filename] -= 12307 def isBlank(self):2308 """2309 Returns True if screenshot is blank, otherwise False.2310 """2311 return _e4gImageIsBlank(self._filename)2312 def setSize(self, screenSize):2313 self._screenSize = screenSize2314 def size(self, allowReadingFile=True):2315 """2316 Returns screenshot size in pixels, as pair (width, height).2317 """2318 if self._screenSize == None and allowReadingFile:2319 e4gImage = _e4gOpenImage(self._filename)2320 self._screenSize = _e4gImageDimensions(e4gImage)2321 eye4graphics.closeImage(e4gImage)2322 return self._screenSize2323 def _notifyOcrEngine(self):2324 if self._ocrEngine and not self._ocrEngineNotified:2325 self._ocrEngine.addScreenshot(self)2326 self._ocrEngineNotified = True2327 if id(self._ocrEngine) == id(self._oirEngine):2328 self._oirEngineNotified = True2329 def _notifyOirEngine(self):2330 if self._oirEngine and not self._oirEngineNotified:2331 self._oirEngine.addScreenshot(self)2332 self._oirEngineNotified = True2333 if id(self._oirEngine) == id(self._ocrEngine):2334 self._ocrEngineNotified = True2335 def dumpHcr(self, filename, **hcrArgs):2336 """2337 Visualize high contrast regions, write image to given file.2338 Experimental.2339 """2340 items = self.findItemsByHcr(**hcrArgs)2341 eyenfinger.drawBboxes(self.filename(), filename,2342 [i.bbox() for i in items])2343 def dumpOcr(self, **kwargs):2344 """2345 Return what OCR engine recognizes on this screenshot.2346 Not all OCR engines provide this functionality.2347 """2348 self._notifyOcrEngine()2349 return self._ocrEngine.dumpOcr(self, **kwargs)2350 def dumpOcrWords(self, **kwargs):2351 """2352 Deprecated, use dumpOcr().2353 """2354 return self.dumpOcr(**kwargs)2355 def filename(self):2356 return self._filename2357 def _findFirstMatchingBitmapCandidate(self, bitmap, **oirArgs):2358 for candidate in self._paths.abspaths(bitmap):2359 found = self._oirEngine.findBitmap(self, candidate, **oirArgs)2360 if found:2361 return found2362 return []2363 def findItemsByBitmap(self, bitmap, **oirFindArgs):2364 if self._oirEngine != None:2365 self._notifyOirEngine()2366 oirArgsList = self._paths.oirArgsList(bitmap)2367 results = []2368 if oirArgsList:2369 for oirArgs in oirArgsList:2370 oirArgs, _ = _takeOirArgs(self._oirEngine, oirArgs.copy())2371 oirArgs.update(oirFindArgs)2372 results.extend(self._findFirstMatchingBitmapCandidate(2373 bitmap, **oirArgs))2374 if results: break2375 else:2376 oirArgs = oirFindArgs2377 results.extend(self._findFirstMatchingBitmapCandidate(2378 bitmap, **oirArgs))2379 return results2380 else:2381 raise RuntimeError('Trying to use OIR on "%s" without OIR engine.' % (self.filename(),))2382 def findItemsByDiff(self, image, colorMatch=1.0, limit=1, area=None):2383 """2384 Return list of items that differ in this and the reference images2385 Parameters:2386 image (string):2387 filename of reference image.2388 colorMatch (optional, float):2389 required color matching accuracy. The default is 1.02390 (exact match)2391 limit (optional, integer):2392 max number of matching items to be returned.2393 The default is 1.2394 """2395 foundItems = []2396 closeImageA = False2397 closeImageB = False2398 self._notifyOirEngine()2399 try:2400 # Open imageA and imageB for comparison2401 if (self.filename() in getattr(self._oirEngine, "_openedImages", {})):2402 # if possible, use already opened image object2403 imageA = self._oirEngine._openedImages[self.filename()]2404 else:2405 imageA = _e4gOpenImage(self.filename())2406 closeImageA = True2407 imageB = _e4gOpenImage(image)2408 closeImageB = True2409 # Find differing pixels2410 bbox = _Bbox(-1, 0, 0, 0, 0)2411 while limit != 0:2412 found = eye4graphics.findNextDiff(2413 ctypes.byref(bbox),2414 ctypes.c_void_p(imageA),2415 ctypes.c_void_p(imageB),2416 ctypes.c_double(colorMatch),2417 ctypes.c_double(1.0), # opacityLimit2418 None, # searchAreaA2419 None, # searchAreaB2420 1)2421 if found != 1:2422 break2423 rgbDiff = (bbox.error >> 16 & 0xff,2424 bbox.error >> 8 & 0xff,2425 bbox.error & 0xff)2426 foundItems.append(2427 GUIItem("DIFF %s" %2428 (rgbDiff,),2429 (bbox.left, bbox.top, bbox.right, bbox.bottom),2430 self))2431 limit -= 12432 finally:2433 if closeImageA:2434 eye4graphics.closeImage(imageA)2435 if closeImageB:2436 eye4graphics.closeImage(imageB)2437 return foundItems2438 def findItemsByColor(self, rgb888, colorMatch=1.0, limit=1, area=None,2439 invertMatch=False, group=""):2440 """2441 Return list of items that match given color.2442 Parameters:2443 rgb888 (integer triplet (red, green, blue)):2444 color to be searched for.2445 colorMatch (optional, float):2446 required color matching accuracy. The default is 1.02447 (exact match).2448 limit (optional, integer):2449 max number of matching items to be returned.2450 The default is 1.2451 area ((left, top, right, bottom), optional):2452 subregion in the screenshot from which items will be2453 searched for. The default is (0.0, 0.0, 1.0, 1.0), that2454 is whole screen.2455 invertMatch (optional, boolean):2456 if True, search for items *not* matching the color.2457 The default is False.2458 group (optional, string):2459 group matching pixels to large items. Accepted2460 values are "adjacent" (group pixels that are next to2461 each other) and "" (no grouping). The default is "".2462 """2463 self._notifyOirEngine()2464 if (self.filename() in getattr(self._oirEngine, "_openedImages", {})):2465 # if possible, use already opened image object2466 image = self._oirEngine._openedImages[self.filename()]2467 closeImage = False2468 else:2469 image = _e4gOpenImage(self.filename())2470 closeImage = True2471 bbox = _Bbox(-1, 0, 0, 0, 0)2472 color = _Rgb888(*rgb888)2473 ssSize = self.size()2474 if area == None:2475 area = (0.0, 0.0, 1.0, 1.0)2476 leftTopRightBottomZero = (_intCoords((area[0], area[1]), ssSize) +2477 _intCoords((area[2], area[3]), ssSize) +2478 (0,))2479 areaBbox = _Bbox(*leftTopRightBottomZero)2480 foundItems = []2481 coord2item = {} # (x, y) -> viewItem2482 try:2483 while limit != 0:2484 found = eye4graphics.findNextColor(2485 ctypes.byref(bbox),2486 ctypes.c_void_p(image),2487 ctypes.byref(color),2488 ctypes.c_double(colorMatch),2489 ctypes.c_double(1.0), # opacityLimit2490 ctypes.c_int(invertMatch),2491 ctypes.byref(areaBbox))2492 if found != 1:2493 break2494 foundColor = int(bbox.error)2495 foundRgb = (foundColor >> 16 & 0xff,2496 foundColor >> 8 & 0xff,2497 foundColor & 0xff)2498 if invertMatch:2499 comp = "!="2500 else:2501 comp = "=="2502 if not group:2503 foundItems.append(2504 GUIItem("RGB#%.2x%.2x%.2x%s%.2x%.2x%.2x (%s)" %2505 (rgb888 + (comp,) + foundRgb + (colorMatch,)),2506 (bbox.left, bbox.top, bbox.right, bbox.bottom),2507 self))2508 limit -= 12509 elif group == "adjacent":2510 x = bbox.left2511 y = bbox.top2512 itemA = None2513 itemB = None2514 if (x-1, y) in coord2item:2515 # AAAx2516 itemA = coord2item[(x-1, y)]2517 if itemA._bbox[2] < x: # right < x2518 itemA._bbox = (itemA._bbox[0], itemA._bbox[1], x, itemA._bbox[3])2519 coord2item[(x, y)] = itemA2520 if (x, y-1) in coord2item:2521 # BBB2522 # x2523 itemB = coord2item[(x, y-1)]2524 if itemB._bbox[3] < y: # bottom < y2525 itemB._bbox = (itemB._bbox[0], itemB._bbox[1], itemB._bbox[2], y)2526 coord2item[(x, y)] = itemB2527 if itemA:2528 # BBB2529 # AAAx2530 if itemB != itemA:2531 itemB._bbox = (min(itemA._bbox[0], itemB._bbox[0]),2532 min(itemA._bbox[1], itemB._bbox[1]),2533 max(itemA._bbox[2], itemB._bbox[2]),2534 max(itemA._bbox[3], itemB._bbox[3]))2535 for ax in xrange(itemA._bbox[0], itemA._bbox[2]+1):2536 for ay in xrange(itemA._bbox[1], itemA._bbox[3]+1):2537 if coord2item.get((ax, ay), None) == itemA:2538 coord2item[(ax, ay)] = itemB2539 foundItems.remove(itemA)2540 limit += 12541 if not itemA and not itemB:2542 itemA = GUIItem("RGB#%.2x%.2x%.2x%s%.2x%.2x%.2x (%s)" %2543 (rgb888 + (comp,) + foundRgb + (colorMatch,)),2544 (bbox.left, bbox.top, bbox.right, bbox.bottom),2545 self)2546 limit -= 12547 foundItems.append(itemA)2548 coord2item[(x, y)] = itemA2549 finally:2550 if closeImage:2551 eye4graphics.closeImage(image)2552 return foundItems2553 def findItemsByOcr(self, text, **ocrEngineArgs):2554 if self._ocrEngine != None:2555 self._notifyOcrEngine()2556 return self._ocrEngine.findText(self, text, **ocrEngineArgs)2557 else:2558 raise RuntimeError('Trying to use OCR on "%s" without OCR engine.' % (self.filename(),))2559 def findItemsByHcr(self, xRes=24, yRes=24, threshold=0.1):2560 """2561 Return "high contrast regions" in the screenshot.2562 Experimental. See if it finds regions that could be2563 interacted with.2564 """2565 ppFilename = "%s-hcrpp.png" % (self.filename(),)2566 _convert(self.filename(),2567 ["-colorspace", "gray", "-depth", "3"],2568 ppFilename)2569 bbox = _Bbox(0, 0, 0, 0, 0)2570 foundItems = []2571 try:2572 image = _e4gOpenImage(ppFilename)2573 while True:2574 if eye4graphics.findNextHighErrorBlock(ctypes.byref(bbox), image, xRes, yRes, threshold, 0) == 0:2575 break2576 foundItems.append(GUIItem(2577 "%sx%s/%s" % (bbox.left/xRes, bbox.top/yRes, bbox.error),2578 (bbox.left, bbox.top, bbox.right, bbox.bottom),2579 self))2580 finally:2581 eye4graphics.closeImage(image)2582 return foundItems2583 def getColor(self, (x, y)):2584 """2585 Return pixel color at coordinates2586 Parameters:2587 (x, y) (pair of integers or floats):2588 coordinates in the image.2589 Returns tuple of integers: (red, green, blue).2590 """2591 self._notifyOirEngine()2592 xsize, ysize = self.size()2593 x, y = _intCoords((x, y), (xsize, ysize))2594 if not (0 <= x < xsize and 0 <= y < ysize):2595 raise ValueError("invalid coordinates (%s, %s)" % (x, y))2596 if (self.filename() in getattr(self._oirEngine, "_openedImages", {})):2597 # if possible, use already opened image object2598 image = self._oirEngine._openedImages[self.filename()]2599 closeImage = False2600 else:2601 image = _e4gOpenImage(self.filename())2602 closeImage = True2603 try:2604 color = _Rgb888(0, 0, 0)2605 v = eye4graphics.rgb888at(ctypes.byref(color),2606 ctypes.c_void_p(image),2607 ctypes.c_int(x),2608 ctypes.c_int(y))2609 if v == 0:2610 return (int(color.red), int(color.green), int(color.blue))2611 else:2612 return None2613 finally:2614 if closeImage:2615 eye4graphics.closeImage(image)2616 def ocrItems(self, **ocrArgs):2617 """2618 Return list of GUIItems, each of them corresponding to a word2619 recognized by OCR. OCR engine must support dumpOcr.2620 Parameters:2621 **ocrArgs (keyword parameters):2622 refer to ocrEngine() documentation.2623 """2624 foundItems = []2625 if self._ocrEngine != None:2626 self._notifyOcrEngine()2627 for word, bbox in self._ocrEngine.dumpOcr(self, **ocrArgs):2628 foundItems.append((GUIItem(word, bbox, self.filename())))2629 return foundItems2630 def save(self, fileOrDirName):2631 """2632 Save screenshot to given file or directory.2633 Parameters:2634 fileOrDirName (string):2635 name of the destination file or directory.2636 """2637 shutil.copy(self._filename, fileOrDirName)2638 def crop(self, area):2639 """2640 Return cropped copy of the screenshot.2641 Parameters:2642 area (left, top, right, bottom):2643 coordinates for cropping.2644 Does not change original screenshot, does not update2645 the latest screenshot returned by screenshot().2646 Example: save cropped screenshot (topmost 5 % of the screen)2647 sut.screenshot().crop((0.0, 0.0, 1.0, 0.05)).save("top-bar.png")2648 """2649 left, top, right, bottom = area2650 x1, y1 = _intCoords((left, top), self.size())2651 x2, y2 = _intCoords((right, bottom), self.size())2652 cropCoords = "%sx%s+%s+%s" % (x2-x1, y2-y1, x1, y1)2653 croppedFilename = self._filename + "-crop_%s.png" % (cropCoords,)2654 _convert(self._filename, ["-crop", cropCoords], croppedFilename)2655 return Screenshot(croppedFilename, self._paths, self._ocrEngine,2656 self._oirEngine, self._screenshotRefCount)2657 def flop(self):2658 """2659 Return horizontally flopped copy of the screenshot.2660 """2661 resultFilename = self._filename + "-flop.png"2662 _convert(self._filename, ["-flop"], resultFilename)2663 return Screenshot(resultFilename, self._paths, self._ocrEngine,2664 self._oirEngine, self._screenshotRefCount)2665 def flip(self):2666 """2667 Return vertically flipped copy of the screenshot.2668 """2669 resultFilename = self._filename + "-flip.png"2670 _convert(self._filename, ["-flip"], resultFilename)2671 return Screenshot(resultFilename, self._paths, self._ocrEngine,2672 self._oirEngine, self._screenshotRefCount)2673 def ocrEngine(self):2674 return self._ocrEngine2675 def oirEngine(self):2676 return self._oirEngine2677 def __str__(self):2678 return 'Screenshot(filename="%s")' % (self._filename,)2679class GUIItem(object):2680 """2681 GUIItem holds the information of a single GUI item.2682 """2683 def __init__(self, name, bbox, screenshot, bitmap=None, ocrFind=None, ocrFound=None):2684 self._name = name2685 if screenshot and hasattr(screenshot, "size"):2686 x1, y1 = _intCoords(bbox[:2], screenshot.size())2687 x2, y2 = _intCoords(bbox[2:], screenshot.size())2688 self._bbox = (x1, y1, x2, y2)2689 else:2690 self._bbox = bbox2691 self._bitmap = bitmap2692 self._screenshot = screenshot2693 self._ocrFind = ocrFind2694 self._ocrFound = ocrFound2695 def bbox(self): return self._bbox2696 def name(self): return self._name2697 def coords(self):2698 left, top, right, bottom = self.bbox()2699 return (left + (right-left)/2, top + (bottom-top)/2)2700 def dump(self): return str(self)2701 def __str__(self):2702 extras = ""2703 if self._bitmap:2704 extras += ', bitmap="%s"' % (self._bitmap,)2705 if self._ocrFind:2706 extras += ', find="%s"' % (self._ocrFind,)2707 if self._ocrFound:2708 extras += ', found="%s"' % (self._ocrFound,)2709 if self._screenshot:2710 extras += ', screenshot="%s"' % (self._screenshot,)2711 return ('GUIItem("%s", bbox=%s%s)' % (2712 self.name(), self.bbox(), extras))2713class _VisualLog:2714 def __init__(self, device, outFileObj,2715 screenshotWidth, thumbnailWidth,2716 timeFormat, delayedDrawing,2717 copyBitmapsToScreenshotDir):2718 self._device = device2719 self._outFileObj = outFileObj2720 if hasattr(self._outFileObj, "name"):2721 self._outFilename = self._outFileObj.name2722 else:2723 self._outFilename = ""2724 self._formattedOutFilename = self._outFilename2725 self._bytesToFile = 02726 self._testStep = -12727 self._previousTs = -12728 self._actionName = None2729 self._callStack = []2730 self._highlightCounter = 02731 self._screenshotWidth = screenshotWidth2732 self._thumbnailWidth = thumbnailWidth2733 self._timeFormat = timeFormat2734 self._copyBitmapsToScreenshotDir = copyBitmapsToScreenshotDir2735 self._userFrameId = 02736 self._userFunction = ""2737 self._userCallCount = 02738 eyenfinger.iSetDefaultDelayedDrawing(delayedDrawing)2739 device.refreshScreenshot = self.refreshScreenshotLogger(device.refreshScreenshot)2740 device.tap = self.tapLogger(device.tap)2741 device.drag = self.dragLogger(device.drag)2742 device.visualLog = self.messageLogger(device.visualLog)2743 attrs = ['callContact', 'callNumber', 'close',2744 'install', 'loadConfig', 'platformVersion',2745 'pressAppSwitch', 'pressBack', 'pressHome',2746 'pressKey', 'pressMenu', 'pressPower',2747 'pressVolumeUp', 'pressVolumeDown',2748 'reboot', 'reconnect', 'refreshView',2749 'shell', 'shellSOE', 'smsNumber', 'supportsView',2750 'swipe', 'swipeBitmap', 'swipeItem', 'swipeOcrText',2751 'systemProperty',2752 'tapBitmap', 'tapId', 'tapItem', 'tapOcrText',2753 'tapText', 'topApp', 'topWindow', 'type',2754 'uninstall',2755 'verifyOcrText', 'verifyText', 'verifyBitmap',2756 'waitAnyBitmap', 'waitBitmap', 'waitOcrText',2757 'waitScreenUpdated', 'waitText']2758 winattrs = ['closeWindow', 'errorReporting', 'existingView',2759 'fileProperties', 'getMatchingPaths', 'getClipboard',2760 'kill', 'osProperties', 'pinch', 'pinchOpen', 'pinchClose',2761 'rmFile', 'reconnect', 'refreshViewDefaults',2762 'setClipboard', 'setErrorReporting', 'setDisplaySize',2763 'setForegroundWindow', 'setRefreshViewDefaults',2764 'findRegistry', 'setRegistry', 'getRegistry',2765 'productList', 'processStatus', 'pycosh',2766 'setScreenshotSize', 'setTopWindow', 'setViewSource',2767 'showWindow', 'topWindow', 'topWindowProperties',2768 'viewSource', 'windowList', 'windowStatus',2769 'viewStats']2770 for a in attrs + winattrs:2771 if hasattr(device, a):2772 m = getattr(device, a)2773 setattr(device, m.func_name, self.genericLogger(m))2774 if not self.logFileSplit():2775 self.logHeader()2776 self._blockId = 02777 def close(self):2778 if self._outFileObj != None:2779 html = []2780 if self._bytesToFile > 0:2781 for c in xrange(len(self._callStack)):2782 html.append('') # end call2783 html.append('</body></html>') # end html2784 self.write('\n'.join(html))2785 if (self._formattedOutFilename and2786 "%" in self._outFilename):2787 # Files with strftime-formatted names are opened and2788 # closed by this class. Other file-like objects are2789 # own by someone else.2790 if hasattr(self._outFileObj, "close"):2791 self._outFileObj.close()2792 if os.stat(self._formattedOutFilename).st_size == 0:2793 os.remove(self._formattedOutFilename)2794 # File instance should be closed by the opener2795 self._outFileObj = None2796 def open(self, newFormattedFilename):2797 self._bytesToFile = 02798 self._formattedOutFilename = newFormattedFilename2799 self._outFileObj = file(self._formattedOutFilename, "w")2800 def write(self, s):2801 self._bytesToFile += len(s)2802 if self._outFileObj != None:2803 self._outFileObj.write(s)2804 self._outFileObj.flush()2805 def timestamp(self, t=None):2806 return fmbt.formatTime(self._timeFormat, t)2807 def epochTimestamp(self, t=None):2808 return fmbt.formatTime("%s.%f", t)2809 def htmlTimestamp(self, t=None):2810 if t == None:2811 t = datetime.datetime.now()2812 retval = '<div class="time" id="t_%s"><a id="time%s">%s</a></div>' % (2813 self.epochTimestamp(t), self.epochTimestamp(t), self.timestamp(t))2814 return retval2815 def logFileSplit(self):2816 """Returns True if old log file was closed and new initialised"""2817 if "%" in self._outFilename:2818 # log filename is strftime formatted2819 newOutFilename = fmbt.formatTime(self._outFilename)2820 if newOutFilename != self._formattedOutFilename:2821 self.close()2822 # prepare new log file2823 self.open(newOutFilename)2824 self.logHeader()2825 return True2826 return False2827 def logBlock(self):2828 ts = fmbt.getTestStep()2829 an = fmbt.getActionName()2830 inactiveOutputHtml = ""2831 #If the test step number has not changed,2832 #the previous block was an "inactive" output action.2833 if self._testStep == ts and not an[:4] in ["tag:", "AAL:"]:2834 inactiveOutputHtml = ' after_inactive_action'2835 if ts == -1 or an == "undefined":2836 an = self._userFunction2837 ts = self._userCallCount2838 if self._testStep != ts or self._actionName != an:2839 if self._blockId != 0:2840 # new top level log entry2841 self.write('</div></li></ul>')2842 # if log splitting is in use, this is a good place to2843 # start logging into next file2844 self.logFileSplit()2845 actionHtml = '''\n<ul><li>%s2846 <div class="step%s" ><a id="blockId%s" href="javascript:showHide('S%s')"> %s. %s</a></div>\n2847 <div class="funccalls" id="S%s">''' % (self.htmlTimestamp(), inactiveOutputHtml, self._blockId,2848 self._blockId, ts, cgi.escape(an), self._blockId)2849 self.write(actionHtml)2850 self._testStep = ts2851 self._actionName = an2852 self._blockId += 12853 def logCall(self, img=None, width="", imgTip=""):2854 callee = inspect.currentframe().f_back.f_code.co_name[:-4] # cut "WRAP"2855 argv = inspect.getargvalues(inspect.currentframe().f_back)2856 # calleeArgs = str(argv.locals['args']) + " " + str(argv.locals['kwargs'])2857 args = [repr(a) for a in argv.locals['args']]2858 for key, value in argv.locals['kwargs'].iteritems():2859 args.append("%s=%s" % (key, repr(value)))2860 calleeArgs = "(%s)" % (", ".join(args),)2861 callerFrame = inspect.currentframe().f_back.f_back2862 callerFilename = callerFrame.f_code.co_filename2863 callerLineno = callerFrame.f_lineno2864 stackSize = len(self._callStack)2865 if stackSize == 0 and (self._userFunction == '<module>' or not self._userFrameId in [(id(se[0]), getattr(se[0].f_back, "f_lasti", None)) for se in inspect.stack()]):2866 self._userFunction = callerFrame.f_code.co_name2867 self._userCallCount += 12868 self._userFrameId = (id(callerFrame), getattr(callerFrame.f_back, "f_lasti", None))2869 self.logBlock()2870 imgHtml = self.imgToHtml(img, width, imgTip, "call:%s" % (callee,))2871 t = datetime.datetime.now()2872 callHtml = '''\n2873 <ul class="depth_%s" id="ul_%s">\n\t<li>%s<div class="call"><a title="%s:%s">%s%s</a></div>2874 %s''' % (stackSize, self.epochTimestamp(t), self.htmlTimestamp(t), cgi.escape(callerFilename), callerLineno, cgi.escape(callee), cgi.escape(str(calleeArgs)), imgHtml)2875 self.write(callHtml)2876 self._callStack.append(callee)2877 return (self.timestamp(t), callerFilename, callerLineno)2878 def logReturn(self, retval, img=None, width="", imgTip="", tip=""):2879 imgHtml = self.imgToHtml(img, width, imgTip, "return:%s" % (self._callStack[-1],))2880 self._callStack.pop()2881 retvalClass = retval2882 formattedRetval = str(retval)2883 if isinstance(retval, basestring):2884 formattedRetval = repr(retval)2885 if retval != True and retval != False:2886 retvalClass = None2887 returnHtml = '''\n2888 \n<li>%s<div class="returnvalue"><a title="%s" class="%s">== %s</a></div>%s</li>\n</ul>\n2889 ''' % (self.htmlTimestamp(), "returned from: " + tip + "()", retvalClass, cgi.escape(formattedRetval), imgHtml)2890 self.write(returnHtml)2891 def logException(self):2892 einfo = sys.exc_info()2893 self._callStack.pop()2894 excHtml = '''<li>%s<div class="exception"><a title="%s">!! %s</a></div></li></ul>\n''' % (2895 self.htmlTimestamp(), cgi.escape(traceback.format_exception(*einfo)[-2].replace('"','').strip()),2896 cgi.escape(str(traceback.format_exception_only(einfo[0], einfo[1])[0])))2897 self.write(excHtml)2898 def logMessage(self, msg):2899 callerFrame = inspect.currentframe().f_back.f_back2900 callerFilename = callerFrame.f_code.co_filename2901 callerLineno = callerFrame.f_lineno2902 self.logBlock()2903 t = datetime.datetime.now()2904 msgHtml = '''<ul><li>%s<a title="%s:%s"><div class="message">%s</div></a></li></ul>\n''' % (2905 self.htmlTimestamp(t), cgi.escape(callerFilename), callerLineno, cgi.escape(msg))2906 self.write(msgHtml)2907 def logHeader(self):2908 self.write(r'''2909<!DOCTYPE html><html>2910<head><meta charset="utf-8"><title>fmbtandroid visual log</title>2911<script>2912function showHide(eid){2913 if (document.getElementById(eid).style.display != 'inline-block'){2914 document.getElementById(eid).style.display = 'inline-block';2915 } else {2916 document.getElementById(eid).style.display = 'none';2917 }2918}2919function hideChildLists(eid) {2920 var jqelement = $("#"+eid);2921 jqelement.find("ul[class^=depth_]").each(function(index, element) {2922 var element_depth = parseInt(element.className.replace("depth_", ""));2923 var parentFunctionCallElement = $(element).closest("ul.depth_" + (element_depth-1).toString()).find("li > div.call > a").first();2924 //If the function has a parent function call2925 if (parentFunctionCallElement.length > 0){2926 parentFunctionCallElement.css("color", "blue");2927 parentFunctionCallElement.unbind("click").bind("click", function(event) {2928 $(event.currentTarget).parent().parent().find("ul.depth_" + (element_depth).toString()).toggle();2929 });2930 $(parentFunctionCallElement).parent().parent().find("ul.depth_" + (element_depth).toString()).hide();2931 }2932 });2933}2934function formatInactiveOutputActions(){2935 var stepElements = $("div.step");2936 for (var i=0; i < stepElements.length; i++){2937 if(i>0 && $(stepElements[i]).hasClass("after_inactive_action")) {2938 $(stepElements[i-1]).find("a").css("color", "gray");2939 $(stepElements[i-1]).find("a").prepend("[Not triggered]");2940 }2941 }2942}2943function tidyReturnValues(){2944 $("div.returnvalue").each(function(key, val){2945 //Format filenames and view dumps2946 if ($(val).text().indexOf("filename=\"/") > -1 || $(val).text().indexOf("dump=\"/") > 0){2947 var longfilename = $(val).text();2948 var shortfilename = longfilename.replace(/(\/.*\/)(.*\.(png|view))/g, "\<span class=\"path\"\>$1\<\/span\>$2");2949 $(val).html(shortfilename + "<span class=\"path_expand\">[Toggle path]</span>");2950 }2951 else if ( $(val).text().indexOf('\\n') > -1 || $(val).text().indexOf(', ') > -1) {2952 var formattedHtml = "<span class='formatted_returnvalue'>" + $(val).html() + "</span>";2953 //Line breaks and commas => <br/>2954 formattedHtml = formattedHtml.replace(/\\r/g, "");2955 formattedHtml = formattedHtml.replace(/\\n[^']/g, "\<br\/\> ");2956 formattedHtml = formattedHtml.replace(/\)\), /g, "\)\),\<br\/\> ");2957 if (formattedHtml.indexOf("))") == -1){2958 formattedHtml = formattedHtml.replace(/, /g, ", \<br\/\> ");2959 }2960 $(val).html("<span class='unformatted_returnvalue'>" + $(val).html() + "</span>" + formattedHtml);2961 }2962 });2963 $(".unformatted_returnvalue").on("click", function(event) {2964 toggleSingleElement(event, ".unformatted_returnvalue");2965 toggleSingleElement(event, ".formatted_returnvalue");2966 });2967 $(".formatted_returnvalue").on("click", function(event) {2968 toggleSingleElement(event, ".unformatted_returnvalue");2969 toggleSingleElement(event, ".formatted_returnvalue");2970 });2971 $(".formatted_returnvalue").hide();2972}2973function initialize(){2974 $(".funccalls").each(function(index, element){2975 hideChildLists(element.id);2976 });2977 $("img").each(function(index, element){2978 if (element.dataset.hasOwnProperty("imgage")) {2979 if (Math.round(10*element.dataset.imgage)/10 > 0.3) {2980 $(element).parent().append("<p class=\"age\">Screenshot age: " +2981 Math.round(100*element.dataset.imgage)/100 + "s</p>");2982 }2983 }2984 });2985 tidyReturnValues();2986 formatInactiveOutputActions();2987 $("body").prepend("<p id='path_toggle'></p>");2988 $("body").prepend("<p id='help'><span style='color: blue'>Blue</span> and <span style='color: gray'>gray</span> items are clickable.</p>");2989 $("#path_toggle").on("click", function(event) {2990 togglePaths();2991 });2992 $(".path_expand").on("click", function(event) {2993 toggleSingleElement(event, ".path");2994 });2995 $("#br_toggle").on("click", function(event) {2996 toggleFormattedRetval(event);2997 });2998 //Hide paths by default2999 togglePaths();3000}3001function toggleSingleElement(event, selector) {3002 $(event.currentTarget).parent().find(selector).toggle();3003}3004function togglePaths(){3005 $(".path").toggle();3006 if ($(".path").css("display") != 'none'){3007 $("#path_toggle").text("Path shortening is OFF globally. ");3008 }3009 else {3010 $("#path_toggle").text("Path shortening is ON globally.");3011 }3012}3013</script>3014<style>3015 body { font-family: "Courier New", Courier, monospace; }3016 .time {3017 width: 200px;3018 display: inline-block;3019 height: 100%;3020 vertical-align: top;3021 }3022 .spacer{3023 display: block;3024 padding-left: 200px;3025 }3026 ul {3027 list-style-type: none;3028 padding-left: 150px;3029 }3030 ul li div.time, ul li div.returnvalue {3031 display: inline-block;3032 max-width:75%;3033 }3034 ul li div.step { display: block; }3035 ul li div { display: inline-block; }3036 .path_expand, #path_toggle { color: blue; }3037 .True { color: green; }3038 .False { color: red; }3039 .funccalls { display: none }3040 .unformatted_returnvalue { color: gray; }3041 .formatted_returnvalue { color: gray; }3042 img:hover {3043 -moz-transform: scale(3);3044 -webkit-transform: scale(3);3045 -o-transform: scale(3);3046 -ms-transform: scale(3);3047 transform: scale(3);3048 transition: transform 0.3s;3049 z-index: 100;3050 }3051 .age {3052 position: relative;3053 background-color: black;3054 top: -80px;3055 left: 20px;3056 width: 180px;3057 color: red;3058 z-index: 0;3059 }3060</style>3061</head><body>3062<script>3063function initializejQuery(callback) {3064 var script = document.createElement("script");3065 script.src = "https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js";3066 document.body.appendChild(script);3067 var interval = setInterval(function() {3068 if (window.jQuery) {3069 console.log("jquery loaded");3070 callback();3071 clearInterval(interval);3072 }3073 }, 100); //Check every 100ms if jquery has loaded.3074}3075initializejQuery(function() {3076 $( document ).ready(function() {3077 initialize();3078 console.log("Document initialized");3079 });3080});3081</script>3082 '''3083 )3084 def doCallLogException(self, origMethod, args, kwargs):3085 try: return origMethod(*args, **kwargs)3086 except:3087 self.logException()3088 raise3089 def genericLogger(loggerSelf, origMethod):3090 def origMethodWRAP(*args, **kwargs):3091 loggerSelf.logCall()3092 retval = loggerSelf.doCallLogException(origMethod, args, kwargs)3093 loggerSelf.logReturn(retval, tip=origMethod.func_name)3094 return retval3095 loggerSelf.changeCodeName(origMethodWRAP, origMethod.func_name + "WRAP")3096 return origMethodWRAP3097 def messageLogger(loggerSelf, origMethod):3098 def origMethodWRAP(*args, **kwargs):3099 loggerSelf.logMessage(" ".join([str(a) for a in args]))3100 return True3101 loggerSelf.changeCodeName(origMethodWRAP, origMethod.func_name + "WRAP")3102 return origMethodWRAP3103 def dragLogger(loggerSelf, origMethod):3104 def dragWRAP(*args, **kwargs):3105 loggerSelf.logCall()3106 x1, y1 = args[0]3107 x2, y2 = args[1]3108 retval = loggerSelf.doCallLogException(origMethod, args, kwargs)3109 try:3110 screenshotFilename = loggerSelf._device.screenshot().filename()3111 highlightFilename = loggerSelf.highlightFilename(screenshotFilename)3112 iC = loggerSelf._device.intCoords3113 eyenfinger.drawLines(screenshotFilename, highlightFilename, [], [iC((x1, y1)), iC((x2, y2))])3114 loggerSelf.logReturn(retval, img=highlightFilename, width=loggerSelf._screenshotWidth, tip=origMethod.func_name)3115 except:3116 loggerSelf.logReturn(str(retval) + " (no screenshot available)", tip=origMethod.func_name)3117 return retval3118 return dragWRAP3119 def refreshScreenshotLogger(loggerSelf, origMethod):3120 def refreshScreenshotWRAP(*args, **kwargs):3121 loggerSelf._highlightCounter = 03122 logCallReturnValue = loggerSelf.logCall()3123 retval = loggerSelf.doCallLogException(origMethod, args, kwargs)3124 if retval != None:3125 retval._logCallReturnValue = logCallReturnValue3126 loggerSelf.logReturn(retval, img=retval, tip=origMethod.func_name)3127 retval.findItemsByBitmap = loggerSelf.findItemsByBitmapLogger(retval.findItemsByBitmap, retval)3128 retval.findItemsByOcr = loggerSelf.findItemsByOcrLogger(retval.findItemsByOcr, retval)3129 else:3130 loggerSelf.logReturn(retval, tip=origMethod.func_name)3131 return retval3132 return refreshScreenshotWRAP3133 def tapLogger(loggerSelf, origMethod):3134 def tapWRAP(*args, **kwargs):3135 loggerSelf.logCall()3136 retval = loggerSelf.doCallLogException(origMethod, args, kwargs)3137 try:3138 screenshotFilename = loggerSelf._device.screenshot().filename()3139 highlightFilename = loggerSelf.highlightFilename(screenshotFilename)3140 eyenfinger.drawClickedPoint(screenshotFilename, highlightFilename, loggerSelf._device.intCoords(args[0]))3141 loggerSelf.logReturn(retval, img=highlightFilename, width=loggerSelf._screenshotWidth, tip=origMethod.func_name, imgTip=loggerSelf._device.screenshot()._logCallReturnValue)3142 except:3143 loggerSelf.logReturn(str(retval) + " (no screenshot available)", tip=origMethod.func_name)3144 return retval3145 return tapWRAP3146 def findItemsByBitmapLogger(loggerSelf, origMethod, screenshotObj):3147 def findItemsByBitmapWRAP(*args, **kwargs):3148 bitmap = args[0]3149 absPathBitmap = screenshotObj._paths.abspaths(bitmap)[0]3150 if loggerSelf._copyBitmapsToScreenshotDir:3151 screenshotDirBitmap = os.path.join(3152 os.path.dirname(screenshotObj.filename()),3153 "bitmaps",3154 bitmap.lstrip(os.sep))3155 if not os.access(screenshotDirBitmap, os.R_OK):3156 # bitmap is not yet copied under screenshotDir3157 destDir = os.path.dirname(screenshotDirBitmap)3158 if not os.access(destDir, os.W_OK):3159 try:3160 os.makedirs(destDir)3161 except IOError:3162 pass # cannot make dir / dir not writable3163 try:3164 shutil.copy(absPathBitmap, destDir)3165 absPathBitmap = screenshotDirBitmap3166 except IOError:3167 pass # cannot copy bitmap3168 else:3169 absPathBitmap = screenshotDirBitmap3170 loggerSelf.logCall(img=absPathBitmap)3171 retval = loggerSelf.doCallLogException(origMethod, args, kwargs)3172 if len(retval) == 0:3173 loggerSelf.logReturn("not found in", img=screenshotObj, tip=origMethod.func_name)3174 else:3175 foundItems = retval3176 screenshotFilename = screenshotObj.filename()3177 highlightFilename = loggerSelf.highlightFilename(screenshotFilename)3178 eyenfinger.drawIcon(screenshotFilename, highlightFilename, foundItems[0]._bitmap, [i.bbox() for i in foundItems])3179 loggerSelf.logReturn([str(quiItem) for quiItem in retval], img=highlightFilename, width=loggerSelf._screenshotWidth, tip=origMethod.func_name, imgTip=screenshotObj._logCallReturnValue)3180 return retval3181 return findItemsByBitmapWRAP3182 def findItemsByOcrLogger(loggerSelf, origMethod, screenshotObj):3183 def findItemsByOcrWRAP(*args, **kwargs):3184 loggerSelf.logCall()3185 retval = loggerSelf.doCallLogException(origMethod, args, kwargs)3186 if len(retval) == 0:3187 loggerSelf.logReturn("not found in words " + str(screenshotObj.dumpOcrWords()),3188 img=screenshotObj, tip=origMethod.func_name)3189 else:3190 foundItem = retval[0]3191 screenshotFilename = screenshotObj.filename()3192 highlightFilename = loggerSelf.highlightFilename(screenshotFilename)3193 eyenfinger.drawIcon(screenshotFilename, highlightFilename, args[0], foundItem.bbox())3194 for appearance, foundItem in enumerate(retval[1:42]):3195 eyenfinger.drawIcon(highlightFilename, highlightFilename, str(appearance+1) + ": " + args[0], foundItem.bbox())3196 loggerSelf.logReturn([str(retval[0])], img=highlightFilename, width=loggerSelf._screenshotWidth, tip=origMethod.func_name, imgTip=screenshotObj._logCallReturnValue)3197 return retval3198 return findItemsByOcrWRAP3199 def relFilePath(self, fileOrDirName, fileLikeObj):3200 if hasattr(fileLikeObj, "name"):3201 referenceDir = os.path.dirname(fileLikeObj.name)3202 else:3203 return fileOrDirName # keep it absolute if there's no reference3204 return os.path.relpath(fileOrDirName, referenceDir)3205 def imgToHtml(self, img, width="", imgTip="", imgClass=""):3206 if imgClass: imgClassAttr = 'class="%s" ' % (imgClass,)3207 else: imgClassAttr = ""3208 imgAge = 03209 imgAgeAttr = ""3210 if isinstance(img, Screenshot):3211 #We must use the original screenshot modification time3212 try:3213 imgAge = time.time() - os.stat(self.unHighlightFilename(img.filename())).st_mtime3214 except:3215 #The file returned by unHighlightFilename did not exist.3216 pass3217 if imgAge > 0:3218 imgAgeAttr = ' data-imgage="%s"' % (imgAge,)3219 imgHtmlName = self.relFilePath(img.filename(), self._outFileObj)3220 imgHtml = '\n<div class="spacer"><img %s title="%s" src="%s" width="%s" alt="%s"%s/></div>' % (3221 imgClassAttr,3222 "%s refreshScreenshot() at %s:%s" % img._logCallReturnValue,3223 imgHtmlName, self._screenshotWidth, imgHtmlName, imgAgeAttr)3224 elif img:3225 try:3226 imgAge = time.time() - os.stat(self.unHighlightFilename(img)).st_mtime3227 except:3228 pass3229 if width: width = 'width="%s"' % (width,)3230 if type(imgTip) == tuple and len(imgTip) == 3:3231 imgTip = 'title="%s refreshScreenshot() at %s:%s"' % imgTip3232 if imgAge > 0:3233 imgAgeAttr = ' data-imgage="%s"' % (imgAge,)3234 else:3235 imgTip = 'title="%s"' % (imgTip,)3236 imgHtmlName = self.relFilePath(img, self._outFileObj)3237 imgHtml = '<div class="spacer"><img %s%s src="%s" %s alt="%s"%s/></div>' % (3238 imgClassAttr, imgTip, imgHtmlName, width, imgHtmlName, imgAgeAttr)3239 else:3240 imgHtml = ""3241 return "\n" + imgHtml + "\n"3242 def unHighlightFilename(self, screenshotFilename):3243 '''Get the filename of the original screenshot based on3244 the name of a highlighted screenshot.'''3245 if self._highlightCounter > 0:3246 try:3247 return re.match('(.*)\.\d{5}\.png', screenshotFilename).group(1)3248 except:3249 return screenshotFilename3250 else:3251 return screenshotFilename3252 def highlightFilename(self, screenshotFilename):3253 self._highlightCounter += 13254 retval = screenshotFilename + "." + str(self._highlightCounter).zfill(5) + ".png"3255 return retval3256 def changeCodeName(self, func, newName):3257 c = func.func_code3258 func.func_name = newName3259 func.func_code = types.CodeType(3260 c.co_argcount, c.co_nlocals, c.co_stacksize, c.co_flags,3261 c.co_code, c.co_consts, c.co_names, c.co_varnames,3262 c.co_filename, newName, c.co_firstlineno, c.co_lnotab, c.co_freevars)...
fmbtwindows.py
Source:fmbtwindows.py
...626 return self.existingConnection().sendClearCache()627 def errorReporting(self):628 """629 Returns Windows error reporting (WER) settings in a dictionary630 See also: setErrorReporting()631 MSDN WER Settings.632 """633 supported_settings = ["DisableArchive",634 "Disabled",635 "DisableQueue",636 "DontShowUI",637 "DontSendAdditionalData",638 "LoggingDisabled",639 "MaxArchiveCount",640 "MaxQueueCount"]641 settings = {}642 for setting in supported_settings:643 settings[setting] = self.getRegistry(644 r"HKEY_CURRENT_USER\Software\Microsoft\Windows\Windows Error Reporting",645 setting)[0]646 return settings647 def existingView(self):648 if self._lastView:649 return self._lastView650 else:651 raise FMBTWindowsError("view is not available. Missing refreshView()?")652 def fileProperties(self, filepath):653 """654 Returns file properties as a dictionary.655 Parameters:656 filepath (string):657 full path to the file.658 """659 escapedFilename = filepath.replace('/', '\\').replace('\\', r'\\\\')660 return self.existingConnection().evalPython(661 '''wmicGet("datafile",'''662 '''componentArgs=("where", "name='%s'"))''' %663 escapedFilename)664 def getFile(self, remoteFilename, localFilename=None, compress=False):665 """666 Fetch file from the device.667 Parameters:668 remoteFilename (string):669 file to be fetched on device670 localFilename (optional, string or None):671 file to be saved to local filesystem. If None,672 return contents of the file without saving them.673 compress (optional, boolean or integer):674 if True, file contents will be compressed for the transfer.675 Integer (0-9) defines compression level. The default is676 False: transfer without compression.677 """678 return self._conn.recvFile(remoteFilename, localFilename, compress)679 def getMatchingPaths(self, pathnamePattern):680 """681 Returns list of paths matching pathnamePattern on the device.682 Parameters:683 pathnamePattern (string):684 Pattern for matching files and directories on the device.685 Example:686 getMatchingPaths("c:/windows/*.ini")687 Implementation runs glob.glob(pathnamePattern) on remote device.688 """689 return self._conn.recvMatchingPaths(pathnamePattern)690 def getClipboard(self):691 """692 Returns clipboard contents in text format.693 See also: setClipboard()694 """695 return self.existingConnection().evalPython("getClipboardText()")696 def itemOnScreen(self, guiItem, relation="touch", topWindowBbox=None):697 """698 Returns True if bbox of guiItem is non-empty and on the screen699 Parameters:700 relation (string, optional):701 One of the following:702 - "overlap": item intersects the screen and the window.703 - "touch": mid point (the default touch point) of the item704 is within the screen and the window.705 - "within": the screen and the window includes the item.706 The default is "touch".707 """708 if guiItem.properties().get("IsOffscreen", False) == "True":709 return False710 if relation == "touch":711 x1, y1, x2, y2 = guiItem.bbox()712 if x1 == x2 or y1 == y2:713 return False # a dimension is missing => empty item714 itemBox = (guiItem.coords()[0], guiItem.coords()[1],715 guiItem.coords()[0] + 1, guiItem.coords()[1] + 1)716 partial = True717 elif relation == "overlap":718 itemBox = guiItem.bbox()719 partial = True720 elif relation == "within":721 itemBox = guiItem.bbox()722 partial = False723 else:724 raise ValueError('invalid itemOnScreen relation: "%s"' % (relation,))725 maxX, maxY = self.screenSize()726 if topWindowBbox == None:727 try:728 topWindowBbox = self.topWindowProperties()['bbox']729 except TypeError:730 topWindowBbox = (0, 0, maxX, maxY)731 return (fmbtgti._boxOnRegion(itemBox, (0, 0, maxX, maxY), partial=partial) and732 fmbtgti._boxOnRegion(itemBox, topWindowBbox, partial=partial))733 def kill(self, pid):734 """735 Terminate process736 Parameters:737 pid (integer):738 ID of the process to be terminated.739 """740 try:741 return self.existingConnection().evalPython(742 "kill(%s)" % (repr(pid),))743 except:744 return False745 def keyNames(self):746 """747 Returns list of key names recognized by pressKey748 """749 return sorted(_g_keyNames)750 def osProperties(self):751 """752 Returns OS properties as a dictionary753 """754 return self.existingConnection().evalPython(755 "wmicGet('os')")756 def pinch(self, (x, y), startDistance, endDistance,757 finger1Dir=90, finger2Dir=270, movePoints=20,758 duration=0.75):759 """760 Pinch (open or close) on coordinates (x, y).761 Parameters:762 x, y (integer):763 the central point of the gesture. Values in range764 [0.0, 1.0] are scaled to full screen width and765 height.766 startDistance, endDistance (float):767 distance from both finger tips to the central point768 of the gesture, at the start and at the end of the769 gesture. Values in range [0.0, 1.0] are scaled up to770 the distance from the coordinates to the edge of the771 screen. Both finger tips will reach an edge if772 distance is 1.0.773 finger1Dir, finger2Dir (integer, optional):774 directions for finger tip movements, in range [0,775 360]. 0 is to the east, 90 to the north, etc. The776 defaults are 90 and 270.777 movePoints (integer, optional):778 number of points to which finger tips are moved779 after laying them to the initial positions. The780 default is 20.781 duration (float, optional):782 duration of the gesture in seconds, the default is 0.75.783 """784 screenWidth, screenHeight = self.screenSize()785 screenDiagonal = math.sqrt(screenWidth**2 + screenHeight**2)786 if x == None: x = 0.5787 if y == None: y = 0.5788 x, y = self.intCoords((x, y))789 if type(startDistance) == float and 0.0 <= startDistance <= 1.0:790 startDistanceInPixels = (791 startDistance *792 min(fmbtgti._edgeDistanceInDirection((x, y), self.screenSize(), finger1Dir),793 fmbtgti._edgeDistanceInDirection((x, y), self.screenSize(), finger2Dir)))794 else:795 startDistanceInPixels = int(startDistance)796 if type(endDistance) == float and 0.0 <= endDistance <= 1.0:797 endDistanceInPixels = (798 endDistance *799 min(fmbtgti._edgeDistanceInDirection((x, y), self.screenSize(), finger1Dir),800 fmbtgti._edgeDistanceInDirection((x, y), self.screenSize(), finger2Dir)))801 else:802 endDistanceInPixels = int(endDistance)803 finger1startX = int(x + math.cos(math.radians(finger1Dir)) * startDistanceInPixels)804 finger1startY = int(y - math.sin(math.radians(finger1Dir)) * startDistanceInPixels)805 finger1endX = int(x + math.cos(math.radians(finger1Dir)) * endDistanceInPixels)806 finger1endY = int(y - math.sin(math.radians(finger1Dir)) * endDistanceInPixels)807 finger2startX = int(x + math.cos(math.radians(finger2Dir)) * startDistanceInPixels)808 finger2startY = int(y - math.sin(math.radians(finger2Dir)) * startDistanceInPixels)809 finger2endX = int(x + math.cos(math.radians(finger2Dir)) * endDistanceInPixels)810 finger2endY = int(y - math.sin(math.radians(finger2Dir)) * endDistanceInPixels)811 self.existingConnection().sendPinch(812 (finger1startX, finger1startY), (finger1endX, finger1endY),813 (finger2startX, finger2startY), (finger2endX, finger2endY),814 movePoints, duration)815 return True816 def pinchOpen(self, (x, y) = (0.5, 0.5), startDistance=0.1, endDistance=0.5, **pinchKwArgs):817 """818 Make the open pinch gesture.819 Parameters:820 x, y (integer, optional):821 the central point of the gesture, the default is in822 the middle of the screen.823 startDistance, endDistance (float, optional):824 refer to pinch documentation. The default is 0.1 and825 0.5.826 for the rest of the parameters, refer to pinch documentation.827 """828 return self.pinch((x, y), startDistance, endDistance, **pinchKwArgs)829 def pinchClose(self, (x, y) = (0.5, 0.5), startDistance=0.5, endDistance=0.1, **pinchKwArgs):830 """831 Make the close pinch gesture.832 Parameters:833 x, y (integer, optional):834 the central point of the gesture, the default is in835 the middle of the screen.836 startDistance, endDistance (float, optional):837 refer to pinch documentation. The default is 0.5 and838 0.1.839 rest of the parameters: refer to pinch documentation.840 """841 return self.pinch((x, y), startDistance, endDistance, **pinchKwArgs)842 def putFile(self, localFilename, remoteFilepath, data=None):843 """844 Send local file to the device.845 Parameters:846 localFilename (string):847 file to be sent.848 remoteFilepath (string):849 destination on the device. If destination is an850 existing directory, the file will be saved to the851 directory with its original local name. Otherwise the file852 will be saved with remoteFilepath as new name.853 data (string, optional):854 data to be stored to remoteFilepath. The default is855 the data in the local file.856 Example: Copy local /tmp/file.txt to c:/temp857 putFile("/tmp/file.txt", "c:/temp/")858 Example: Create new remote file859 putFile(None, "c:/temp/file.txt", "remote file contents")860 """861 return self._conn.sendFile(localFilename, remoteFilepath, data)862 def rmFile(self, remoteFilepath):863 """864 Remove a file from the device.865 Parameters:866 remoteFilepath (string):867 file to be removed from the device.868 """869 return self.existingConnection().evalPython(870 "os.remove(%s)" % (repr(remoteFilepath),))871 def reconnect(self, connspec=None, password=None):872 """873 Close connections to the device and reconnect.874 Parameters:875 connspec (string, optional):876 Specification for new connection. The default is current877 connspec.878 password (string, optional):879 Password for new connection. The default is current password.880 """881 self.setConnection(None)882 import gc883 gc.collect()884 if connspec != None:885 self._connspec = connspec886 if password != None:887 self._password = password888 if self._connspec == None:889 _adapterLog("reconnect failed: missing connspec")890 return False891 try:892 self.setConnection(WindowsConnection(893 self._connspec, self._password, self))894 return True895 except Exception, e:896 _adapterLog("reconnect failed: %s" % (e,))897 return False898 def getDumpFilename(self, suffix):899 if self.screenshotDir() == None:900 self.setScreenshotDir(self._screenshotDirDefault)901 if self.screenshotSubdir() == None:902 self.setScreenshotSubdir(self._screenshotSubdirDefault)903 return self._newScreenshotFilepath()[:-3] + suffix904 def refreshView(self, window=None, forcedView=None, viewSource=None,905 items=None, properties=None, area=None,906 filterType="none", filterCondition="",907 dumpChildClass="", dumpChildName="", doNotDump=False):908 """909 (Re)reads widgets on the top window and updates the latest view.910 Parameters:911 window (integer (hWnd) or string (title), optional):912 read widgets from given window instead of the top window.913 forcedView (View or filename, optional):914 use given View object or view file instead of reading the915 items from the device.916 viewSource (string, optional):917 source of UI information. Supported sources are:918 "uiautomation" the UIAutomation framework.919 "enumchildwindows" less data920 but does not require UIAutomation.921 The default is "uiautomation".922 You can define TreeWalker used by "uiautomation" by defining923 viewSource as "uiautomation/raw", "uiautomation/control" or924 "uiautomation/content".925 See also setViewSource().926 items (list of view items, optional):927 update only contents of these items in the view.928 Works only for "uiautomation" view source.929 properties (list of property names, optional):930 read only given properties from items, the default931 is to read all available properties.932 Works only for "uiautomation" view source.933 See also setViewSource().934 area ((left, top, right, bottom), optional):935 refresh only items that intersect the area.936 The default is None: locations do not affect refreshed937 items.938 filterType (string, optional):939 specify how the widgets should be filtered.940 Supported values are:941 "none", which is the default, which means that all widgets942 will be retrieved.943 "first": only the first element which satisfy the condition944 defined by the filterCondition parameter is returned.945 "all": all elements which satisfy the condition defined by946 the filterCondition parameter are returned.947 "first" and "all" allow to specify an additional "+children"948 qualifier, which returns also all element's children.949 So, passing "first+children" as filterType, then the element950 all its children are returned, if the element (and only it)951 satisfy filterCondition.952 It's import to underline that the conditions defined by the953 items, properties, and area, parameters are still all954 applied on top of filterType and filterCondition.955 filterCondition (string, optional):956 specify the condition for filtering the widgets.957 It only works if filterType is not "none".958 Currently only a basic filter conditions are supported, that959 allows to specify a property name, the == operator, and960 a double-quoted string with the value to be compared.961 Filter conditions can be chained together using the "and"962 operator; this allows more fine-grained filtering.963 For example:964 'ClassName == "ToolBar" and Name == "Explorer"'965 matches all widgets whose "ClassName" property is equal966 to "ToolBar" and the Name is "Explorer".967 The list of currently allowed properties is the following:968 AutomationId, ClassName, HelpText, LabeledBy, Name.969 dumpChildClass (string, optional):970 if specified, only widgets of this class will be dumped /971 reported. Otherwise all widgets will be dumped (if no other972 dumping option is given. See below).973 For example, setting dumpChildClass to "TextBlock" will974 return only this kind of widgets.975 Imagine a big ListView, where a single ListViewItem is a976 complex container / panel which incorporates several widgets,977 but you don't want to dump all of them, and you're interested978 only to the ones which carry textual information.979 You can do this using the filtering options to catch the980 ListView widget and visiting all its children/descendants,981 but setting dumpChildClass will give back only the TextBlocks.982 So, this is quite different from the above filtering options,983 because filtering only defines how the UI widgets are984 traversed (and eventually cached), whereas dumping defines985 what widgets will be really reported back and collected by986 the view.987 Filtering reduces the amount of widgets that will be988 evaluated. On top of that, dumping reduces the number of989 widgets that will be returned back to the view.990 dumpChildName (string, optional):991 if specified, only widgets with this name will be dumped /992 reported. Otherwise all widgets will be dumped (if no other993 dumping option is given).994 It works exactly like dumpChildClass, but works on the Name995 property. So, the same logic applies.996 It can be combined with dumpChildClass to further reduce the997 number of returned widgets.998 For example, dumpChildName = "Foo" will give back all widgets999 which have "Foo" as Name.1000 doNotDump (boolean, optional):1001 if specified, no widgets will be dumped / reported, regarding1002 of all other dump options.1003 It's used to only cache the widgets in the server. All widgets1004 will be traversed and cached (if a cache option is defined).1005 See also setRefreshViewDefaults().1006 Returns View object.1007 """1008 if window == None:1009 window = self._refreshViewDefaults.get("window", None)1010 if forcedView == None:1011 forcedView = self._refreshViewDefaults.get("forcedView", None)1012 if viewSource == None:1013 viewSource = self.viewSource()1014 if not viewSource in _g_viewSources:1015 raise ValueError('invalid view source "%s"' % (viewSource,))1016 if items == None:1017 items = self._refreshViewDefaults.get("items", [])1018 if properties == None:1019 properties = self._refreshViewDefaults.get("properties", None)1020 if area == None:1021 area = self._refreshViewDefaults.get("area", None)1022 if forcedView != None:1023 retryCount = 01024 startTime = time.time()1025 lastStartTime = startTime1026 viewFilename = forcedView1027 if isinstance(forcedView, View):1028 self._lastView = forcedView1029 elif type(forcedView) in [str, unicode]:1030 try:1031 self._lastView = View(1032 forcedView, ast.literal_eval(file(viewFilename).read()),1033 device=self, freeDumps=dumpChildClass or dumpChildName or doNotDump)1034 except Exception:1035 self._lastView = None1036 endTime = time.time()1037 else:1038 viewFilename = self.getDumpFilename("view")1039 retryCount = 01040 startTime = time.time()1041 lastStartTime = startTime1042 while True:1043 try:1044 topWindowBbox = self.topWindowProperties()['bbox']1045 except TypeError:1046 topWindowBbox = None # top window unavailable1047 if area:1048 leftTopRightBottom = (1049 self.intCoords((area[0], area[1])) +1050 self.intCoords((area[2], area[3])))1051 else:1052 leftTopRightBottom = None1053 if viewSource == "enumchildwindows":1054 viewData = self._conn.recvViewData(window)1055 else:1056 if "/" in viewSource:1057 walker = viewSource.split("/")[1]1058 else:1059 walker = "raw"1060 if properties != None:1061 if properties == "all":1062 viewItemProperties = None1063 elif properties == "fast":1064 viewItemProperties = ["AutomationId",1065 "BoundingRectangle",1066 "ClassName",1067 "HelpText",1068 "ToggleState",1069 "Value",1070 "Minimum",1071 "Maximum",1072 "Name"]1073 elif isinstance(properties, list) or isinstance(properties, tuple):1074 viewItemProperties = list(properties)1075 else:1076 raise ValueError('invalid properties argument, expected "all", '1077 '"fast" or a list')1078 else:1079 viewItemProperties = properties1080 viewData = self._conn.recvViewUIAutomation(1081 window, items, viewItemProperties, leftTopRightBottom, walker,1082 filterType, filterCondition, dumpChildClass, dumpChildName, doNotDump)1083 file(viewFilename, "w").write(repr(viewData))1084 try:1085 self._lastView = View(1086 viewFilename, viewData,1087 itemOnScreen=lambda i: self.itemOnScreen(i, topWindowBbox=topWindowBbox),1088 device=self, freeDumps=dumpChildClass or dumpChildName or doNotDump)1089 break1090 except Exception, e:1091 self._lastView = None1092 _adapterLog(1093 "refreshView %s failed (%s), source=%s topWindow=%s" %1094 (retryCount, e, repr(viewSource), self.topWindow()))1095 retryCount += 11096 if retryCount < self._refreshViewRetryLimit:1097 time.sleep(0.2)1098 else:1099 break1100 lastStartTime = time.time()1101 endTime = time.time()1102 itemCount = -11103 if self._lastView:1104 itemCount = len(self._lastView._viewItems)1105 self._lastViewStats = {1106 "retries": retryCount,1107 "timestamp": endTime,1108 "total time": endTime - startTime,1109 "last time": endTime - lastStartTime,1110 "filename": viewFilename,1111 "source": viewSource,1112 "forced": (forcedView != None),1113 "window": window,1114 "view": str(self._lastView),1115 "item count": itemCount}1116 return self._lastView1117 def refreshViewDefaults(self):1118 """Returns default arguments for refreshView() calls.1119 See also setRefreshViewDefaults().1120 """1121 return dict(self._refreshViewDefaults)1122 def setClipboard(self, data):1123 """1124 Set text on clipboard1125 Parameters:1126 data (string):1127 data to be set on the clipboard.1128 Note: any type of data on clipboard will be emptied.1129 See also: getClipboard()1130 """1131 return self.existingConnection().evalPython(1132 "setClipboardText(%s)" % (repr(data),))1133 def setErrorReporting(self, settings):1134 """1135 Modify Windows error reporting settings (WER)1136 Parameters:1137 settings (dictionary):1138 WER settings and values to be set.1139 Example: disable showing interactive crash dialogs1140 setErrorReporting({"DontShowUI": 1})1141 See also: errorReporting(),1142 MSDN WER Settings.1143 """1144 for setting in settings:1145 self.setRegistry(1146 r"HKEY_CURRENT_USER\Software\Microsoft\Windows\Windows Error Reporting",1147 setting, settings[setting])1148 return True1149 def setDisplaySize(self, size):1150 """1151 Transform coordinates of synthesized events (like a tap) from1152 screenshot resolution to display input area size. By default1153 events are synthesized directly to screenshot coordinates.1154 Parameters:...
Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!