Best Python code snippet using fMBT_python
fmbtandroid.py
Source:fmbtandroid.py  
...496                  tag for the log entry, the default is "fMBT".497        """498        if not priority.lower() in ["v", "d", "i", "w", "e"]:499            return False500        return self.existingConnection().sendDeviceLog(501            msg, priority.lower(), tag)502    def displayRotation(self):503        """504        Returns current rotation of the display.505        Returns integer, that is ROTATION_0, ROTATION_90, ROTATION_180506        or ROTATION_270. Returns None if rotation is not available.507        Example: take a screenshot rotated to current display orientation508          d.refreshScreenshot(rotate=-d.displayRotation())509        """510        if self._conn:511            return self._conn.recvCurrentDisplayOrientation()512        else:513            return None514    def displayPowered(self):515        """516        Returns True if display is powered, otherwise False.517        """518        if self._conn:519            return self._conn.recvDisplayPowered()520        else:521            return None522    def drag(self, (x1, y1), (x2, y2), delayBetweenMoves=None, delayBeforeMoves=None, delayAfterMoves=None, movePoints=None):523        """524        Touch the screen on coordinates (x1, y1), drag along straight525        line to coordinates (x2, y2), and raise fingertip.526        coordinates (floats in range [0.0, 1.0] or integers):527                floating point coordinates in range [0.0, 1.0] are528                scaled to full screen width and height, others are529                handled as absolute coordinate values.530        delayBeforeMoves (float, optional):531                seconds to wait after touching and before dragging.532                If negative, starting touch event is not sent.533        delayBetweenMoves (float, optional):534                seconds to wait when moving between points when535                dragging.536        delayAfterMoves (float, optional):537                seconds to wait after dragging, before raising538                fingertip.539                If negative, fingertip is not raised.540        movePoints (integer, optional):541                the number of intermediate move points between end542                points of the line.543        Returns True on success, False if sending input failed.544        """545        if (delayBetweenMoves == None and546            delayBeforeMoves == None and547            delayAfterMoves == None and548            movePoints == None and549            self.platformVersion() >= "4.3"):550            x1, y1 = self.intCoords((x1, y1))551            x2, y2 = self.intCoords((x2, y2))552            return self.existingConnection().sendSwipe(x1, y1, x2, y2)553        else:554            kwArgs = {}555            if delayBetweenMoves != None: kwArgs["delayBetweenMoves"] = delayBetweenMoves556            if delayBeforeMoves != None: kwArgs["delayBeforeMoves"] = delayBeforeMoves557            if delayAfterMoves != None: kwArgs["delayAfterMoves"] = delayAfterMoves558            if movePoints != None: kwArgs["movePoints"] = movePoints559            return fmbtgti.GUITestInterface.drag(560                self, (x1, y1), (x2, y2),561                **kwArgs)562    def dumpIni(self):563        """564        Returns contents of current device configuration as a string (in565        INI format).566        """567        return self._conf.dump()568    def ini(self):569        """570        Returns an Ini object containing effective device571        configuration.572        """573        return self._conf574    def install(self, filename, lock=False, reinstall=False, downgrade=False,575                sdcard=False, algo=None, key=None, iv=None):576        """577        Install apk on the device.578        Parameters:579          filename (string):580                APK filename on host.581          lock (boolean, optional):582                forward-lock the app. Correspond to adb install "-l".583                The default is False.584          reinstall (boolean, optional):585                Reinstall the app, keep its data. Corresponds to "-r".586                The default is False.587          downgrade (boolean, optional):588                Allow downgrading the application. Corresponds to "-d".589                The default is False.590          sdcard (boolean, optional):591                Install on SD card. Corresponds to "-s".592                The default is False.593          algo (string, optional):594                Algorithm name. Corresponds to "--algo".595                The default is None.596          key (string, optional):597                Hex-encoded key. Corresponds to "--key".598                The default is None.599          iv (string, optional):600                Hex-encoded iv. Corresponds to "--iv".601                The default is None.602        Returns True if successful, False if device is not connected,603        and "adb install" command output (string) otherwise.604        Example:605          status = d.install("/tmp/PythonAPK.apk")606          if status != True:607              print "Installation failed, output:", status608        """609        if self._conn:610            return self._conn.install(filename, lock, reinstall, downgrade,611                                      sdcard, algo, key, iv)612        else:613            return False614    def keyNames(self):615        """616        Returns list of keyNames supported by pressKey.617        """618        return sorted(_g_keyNames)619    def loadConfig(self, filenameOrObj, override=True, level=""):620        try:621            if type(filenameOrObj) == str:622                filename = filenameOrObj623                fileObj = file(filenameOrObj)624            else:625                fileObj = filenameOrObj626                filename = getattr(fileObj, "name", "<string>")627                if hasattr(fileObj, "seek"):628                    fileObj.seek(0)629            self._conf.addFile(fileObj, override=override)630        except Exception, e:631            _adapterLog('Loading %s configuration from "%s" failed: %s' % (level, filename, e))632            return633        _adapterLog('Loaded %s configuration from "%s"' % (level, filename))634    def navigationBarVisible(self):635        """636        Returns True if the navigation bar is showing, otherwise False.637        """638        return self.existingConnection().recvNavigationBarVisible()639    def pinch(self, (x, y), startDistance, endDistance,640              finger1Dir=90, finger2Dir=270, movePoints=100):641        """642        Pinch (open or close) on coordinates (x, y).643        Parameters:644          x, y (integer):645                  the central point of the gesture. Values in range646                  [0.0, 1.0] are scaled to full screen width and647                  height.648          startDistance, endDistance (float):649                  distance from both finger tips to the central point650                  of the gesture, at the start and at the end of the651                  gesture. Values in range [0.0, 1.0] are scaled up to652                  the distance from the coordinates to the edge of the653                  screen. Both finger tips will reach an edge if654                  distance is 1.0.655          finger1Dir, finger2Dir (integer, optional):656                  directions for finger tip movements, in range [0,657                  360]. 0 is to the east, 90 to the north, etc. The658                  defaults are 90 and 270.659          movePoints (integer, optional):660                  number of points to which finger tips are moved661                  after laying them to the initial positions. The662                  default is 100.663        """664        screenWidth, screenHeight = self.screenSize()665        screenDiagonal = math.sqrt(screenWidth**2 + screenHeight**2)666        if x == None: x = 0.5667        if y == None: y = 0.5668        x, y = self.intCoords((x, y))669        if type(startDistance) == float and 0.0 <= startDistance <= 1.0:670            startDistanceInPixels = (startDistance *671                                     max(fmbtgti._edgeDistanceInDirection((x, y), self.screenSize(), finger1Dir),672                                         fmbtgti._edgeDistanceInDirection((x, y), self.screenSize(), finger2Dir)))673        else: startDistanceInPixels = int(startDistance)674        if type(endDistance) == float and 0.0 <= endDistance <= 1.0:675            endDistanceInPixels = (endDistance *676                                   max(fmbtgti._edgeDistanceInDirection((x, y), self.screenSize(), finger1Dir),677                                       fmbtgti._edgeDistanceInDirection((x, y), self.screenSize(), finger2Dir)))678        else: endDistanceInPixels = int(endDistance)679        finger1startX = int(x + math.cos(math.radians(finger1Dir)) * startDistanceInPixels)680        finger1startY = int(y - math.sin(math.radians(finger1Dir)) * startDistanceInPixels)681        finger1endX = int(x + math.cos(math.radians(finger1Dir)) * endDistanceInPixels)682        finger1endY = int(y - math.sin(math.radians(finger1Dir)) * endDistanceInPixels)683        finger2startX = int(x + math.cos(math.radians(finger2Dir)) * startDistanceInPixels)684        finger2startY = int(y - math.sin(math.radians(finger2Dir)) * startDistanceInPixels)685        finger2endX = int(x + math.cos(math.radians(finger2Dir)) * endDistanceInPixels)686        finger2endY = int(y - math.sin(math.radians(finger2Dir)) * endDistanceInPixels)687        self.existingConnection().sendMonkeyPinchZoom(688            finger1startX, finger1startY, finger1endX, finger1endY,689            finger2startX, finger2startY, finger2endX, finger2endY,690            movePoints)691        return True692    def pinchOpen(self, (x, y) = (0.5, 0.5), startDistance=0.1, endDistance=0.5, **pinchKwArgs):693        """694        Make the open pinch gesture.695        Parameters:696          x, y (integer, optional):697                  the central point of the gesture, the default is in698                  the middle of the screen.699          startDistance, endDistance (float, optional):700                  refer to pinch documentation. The default is 0.1 and701                  0.5.702          for the rest of the parameters, refer to pinch documentation.703        """704        return self.pinch((x, y), startDistance, endDistance, **pinchKwArgs)705    def pinchClose(self, (x, y) = (0.5, 0.5), startDistance=0.5, endDistance=0.1, **pinchKwArgs):706        """707        Make the close pinch gesture.708        Parameters:709          x, y (integer, optional):710                  the central point of the gesture, the default is in711                  the middle of the screen.712          startDistance, endDistance (float, optional):713                  refer to pinch documentation. The default is 0.5 and714                  0.1.715          rest of the parameters: refer to pinch documentation.716        """717        return self.pinch((x, y), startDistance, endDistance, **pinchKwArgs)718    def platformVersion(self):719        """720        Returns the platform version of the device.721        """722        try:723            return self.existingConnection().recvPlatformVersion()724        except:725            return "nosoftware"726    def pressAppSwitch(self, **pressKeyKwArgs):727        """728        Press the app switch button.729        Optional parameters are the same as for pressKey.730        """731        return self.pressKey("KEYCODE_APP_SWITCH", **pressKeyKwArgs)732    def pressBack(self, **pressKeyKwArgs):733        """734        Press the back button.735        Optional parameters are the same as for pressKey.736        """737        return self.pressKey("KEYCODE_BACK", **pressKeyKwArgs)738    def pressHome(self, **pressKeyKwArgs):739        """740        Press the home button.741        Optional parameters are the same as for pressKey.742        """743        return self.pressKey("KEYCODE_HOME", **pressKeyKwArgs)744    def pressKey(self, keyName, long=False, hold=0.0, modifiers=None):745        """746        Press a key on the device.747        Parameters:748          keyName (string):749                  the name of the key, like KEYCODE_HOME. If KEYCODE_750                  prefix is not given, it is added. Refer to Android751                  KeyEvent documentation.752          long (boolean, optional):753                  if True, press the key for long time.754          hold (float, optional):755                  time in seconds to hold the key down.756          modifiers (list of strings, optional):757                  modifier key(s) to be pressed at the same time.758        """759        if not keyName.upper().startswith("KEYCODE_"):760            keyName = "KEYCODE_" + keyName761        keyName = keyName.upper()762        if modifiers != None:763            modifiers = [764                m.upper() if m.upper().startswith("KEYCODE_") else "KEYCODE_" + m.upper()765                for m in modifiers]766        return fmbtgti.GUITestInterface.pressKey(self, keyName, long, hold, modifiers)767    def pressMenu(self, **pressKeyKwArgs):768        """769        Press the menu button.770        Optional parameters are the same as for pressKey.771        """772        return self.pressKey("KEYCODE_MENU", **pressKeyKwArgs)773    def pressPower(self, **pressKeyKwArgs):774        """775        Press the power button.776        Optional parameters are the same as for pressKey.777        """778        return self.pressKey("KEYCODE_POWER", **pressKeyKwArgs)779    def pressSearch(self, **pressKeyKwArgs):780        """781        Press the search button.782        Optional parameters are the same as for pressKey.783        """784        return self.pressKey("KEYCODE_SEARCH", **pressKeyKwArgs)785    def pressVolumeUp(self, **pressKeyKwArgs):786        """787        Press the volume up button.788        Optional parameters are the same as for pressKey.789        """790        return self.pressKey("KEYCODE_VOLUME_UP", **pressKeyKwArgs)791    def pressVolumeDown(self, **pressKeyKwArgs):792        """793        Press the volume down button.794        Optional parameters are the same as for pressKey.795        """796        return self.pressKey("KEYCODE_VOLUME_DOWN", **pressKeyKwArgs)797    def reboot(self, reconnect=True, firstBoot=False, timeout=120):798        """799        Reboot the device.800        Parameters801          reconnect (boolean, optional)802                  If True, do not return until the device has been803                  connected after boot. Otherwise return once reboot804                  command has been sent. The default is True.805          firstBoot (boolean, optional)806                  If True, the device boots like it would have been807                  flashed. Requires that "adb root" works. The default808                  is False.809          timeout (integer, optional)810                  Timeout in seconds for reconnecting after reboot.811                  The default is 120 s.812        Returns True on success, otherwise False.813        """814        return self.existingConnection().reboot(reconnect, firstBoot, timeout)815    def reconnect(self):816        """817        Close connections to the device and reconnect.818        """819        conn = self.connection()820        if hasattr(conn, "settings"):821            self._lastConnectionSettings = conn.settings()822        connSettings = self._lastConnectionSettings823        self.setConnection(None)824        self._lastConnectionSettings = connSettings825        del conn # make sure gc will collect the connection object826        import gc827        gc.collect()828        try:829            self.setConnection(_AndroidDeviceConnection(830                self.serialNumber, **connSettings))831            return True832        except Exception, e:833            _adapterLog("reconnect failed: %s" % (e,))834            return False835    def refreshScreenshot(self, forcedScreenshot=None, rotate=None):836        # convert Android display/user rotation to degrees837        if rotate in ROTATIONS:838            rotate = ROTATION_DEGS[rotate]839        elif rotate in [-ROTATION_0, -ROTATION_90, -ROTATION_180, -ROTATION_270]:840            rotate = -ROTATION_DEGS[-rotate]841        elif rotate == None:842            if self._autoRotateScreenshot:843                if not forcedScreenshot:844                    drot = self.displayRotation()845                else:846                    drot = None847                if drot != None:848                    return self.refreshScreenshot(forcedScreenshot, rotate=-drot)849        rv = fmbtgti.GUITestInterface.refreshScreenshot(self, forcedScreenshot, rotate)850        if rv:851            if not forcedScreenshot:852                self._screenSize = self.existingConnection().recvScreenSize()853            else:854                self._screenSize = self.screenshot().size()855        else:856            self._screenSize = self.existingConnection().recvScreenSize()857        return rv858    refreshScreenshot.__doc__ = fmbtgti.GUITestInterface.refreshScreenshot.__doc__859    def refreshView(self, forcedView=None, uiautomatorDump=None):860        """861        (Re)reads view items on display and updates the latest View862        object.863        Parameters:864          forcedView (View or filename, optional):865                use given View object or view file instead of reading866                items from the device.867          uiautomatorDump (boolean, optional):868                use uiautomator to read view dump from the device.869                If not given, uiautomatorDump() default will be used.870        Returns created View object.871        """872        def formatErrors(errors, filename):873            return 'refreshView parse errors in "%s":\n    %s' % (874                filename,875                "\n    ".join(["line %s: %s error: %s" % e for e in errors]),)876        if self._conn:877            displayToScreen = self._conn._displayToScreen878        else:879            displayToScreen = None880        if forcedView != None:881            if isinstance(forcedView, View):882                self._lastView = forcedView883            elif type(forcedView) == str:884                self._lastView = View(self.screenshotDir(), self.serialNumber, file(forcedView).read(), displayToScreen, self.itemOnScreen, self.intCoords)885                _adapterLog(formatErrors(self._lastView.errors(), self._lastView.filename()))886            else:887                raise ValueError("forcedView must be a View object or a filename")888            return self._lastView889        retryCount = 0890        while True:891            if uiautomatorDump == True or (uiautomatorDump == None and self.uiautomatorDump()):892                dump = self.existingConnection().recvUiautomatorDump()893            else:894                try:895                    dump = self.existingConnection().recvViewData()896                except AndroidConnectionError:897                    self._lastView = None898                    raise899            if dump != None:900                viewDir = os.path.dirname(self._newScreenshotFilepath())901                view = View(viewDir, self.serialNumber, dump, displayToScreen, self.itemOnScreen, self.intCoords)902            else:903                _adapterLog("refreshView window dump reading failed")904                view = None905                # fail quickly if there is no answer906                retryCount += self._PARSE_VIEW_RETRY_LIMIT / 2907            if dump == None or len(view.errors()) > 0:908                if view:909                    _adapterLog(formatErrors(view.errors(), view.filename()))910                if retryCount < self._PARSE_VIEW_RETRY_LIMIT:911                    retryCount += 1912                    time.sleep(0.2) # sleep before retry913                else:914                    self._lastView = None915                    raise AndroidConnectionError("Cannot read window dump")916            else:917                # successfully parsed or parsed with errors but no more retries918                self._lastView = view919                self._screenSize = self.existingConnection().recvScreenSize()920                return view921    def screenLocked(self):922        """923        Return True if showing lockscreen, otherwise False.924        """925        if self._conn:926            return self._conn.recvShowingLockscreen()927        else:928            return None929    def screenSize(self):930        if self._screenSize:931            return self._screenSize932        else:933            self._screenSize = fmbtgti.GUITestInterface.screenSize(self)934            return self._screenSize935    def setAccelerometer(self, abc):936        """937        Set emulator accelerometer readings938        Parameters:939          abc (tuple of floats):940                  new 3-axis accelerometer readings, one to three values.941        Returns True if successful, False if failed. Raises an exception942        if emulator cannot be connected to. Does not work with real hardware.943        Note: to rotate display with real hardware, see setUserRotation().944        Example:945          d.setAccelerometer((9.8, 0))946          d.setAccelerometer((0, 9.8))947          d.setAccelerometer((-9.6, 0.2, 0.8))948        """949        if self._conn:950            return self._conn.sendAcceleration(abc)951        else:952            return False953    def setAccelerometerRotation(self, value):954        """955        Enable or disable accelerometer-based screen rotation956        Parameters:957          value (boolean):958                  True: enable accelerometer-based rotation959                  False: disable accelerometer-based rotation.960        Returns True if successful, otherwise False.961        """962        if self._conn:963            return self._conn.sendAccelerometerRotation(value)964        else:965            return False966    def setAutoRotateScreenshot(self, value):967        """968        Enable or disable automatic screenshot rotation.969        Parameters:970          value (boolean):971                  If True, rotate screenshot automatically to compensate972                  current display rotation.973        refreshScreenshot()'s optional rotate parameter overrides this974        setting.975        See also autoRotateScreenshot(), displayRotation().976        """977        if value:978            self._autoRotateScreenshot = True979        else:980            self._autoRotateScreenshot = False981    def setConnection(self, connection):982        self._lastConnectionSettings = {}983        fmbtgti.GUITestInterface.setConnection(self, connection)984        if hasattr(self.connection(), "_serialNumber"):985            self.serialNumber = self.connection()._serialNumber986    def setDisplaySize(self, size=(None, None)):987        """988        Transform coordinates of synthesized events from screenshot989        resolution to given input area size. By default events are990        synthesized directly to screenshot coordinates.991        Parameters:992          size (pair of integers (width, height), optional):993                  width and height of display in pixels. If not994                  given, values from Android system properties995                  "mDisplayWidth" and "mDisplayHeight" will be used.996        Returns None.997        """998        width, height = size999        if width == None or height == None:1000            w, h = self.existingConnection().recvScreenSize()1001            if w == h == 0:1002                w, h = self.existingConnection().recvDefaultViewportSize()1003        if width == None:1004            width = w1005        if height == None:1006            height = h1007        screenWidth, screenHeight = self.screenSize()1008        self.existingConnection().setScreenToDisplayCoords(1009            lambda x, y: (x * width / screenWidth,1010                          y * height / screenHeight))1011        self.existingConnection().setDisplayToScreenCoords(1012            lambda x, y: (x * screenWidth / width,1013                          y * screenHeight / height))1014    def setUiautomatorDump(self, uiautomatorDump):1015        """1016        Enable or disable using uiautomator on refreshView()1017        Parameters:1018          uiautomatorDump (boolean):1019                  If True, uiautomator will be used on refreshView()1020                  by default.1021        """1022        self._uiautomatorDump = uiautomatorDump1023    def setUserRotation(self, rotation):1024        """1025        Enable or disable accelerometer-based screen rotation1026        Parameters:1027          rotation (integer):1028                  values 0, 1, 2 and 3 correspond to1029                  ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270.1030        Returns True if successful, otherwise False.1031        Example:1032          # Disable accelerometer-based rotation for user rotation1033          # to take effect.1034          d.setAccelerometerRotation(False)1035          d.setUserRotation(fmbtandroid.ROTATION_90)1036          time.sleep(2)1037          d.setUserRotation(fmbtandroid.ROTATION_0)1038          time.sleep(2)1039          d.setAccelerometerRotation(True)1040        """1041        if rotation in ROTATIONS:1042            pass # already in correct scale1043        elif rotation in ROTATION_DEGS:1044            rotation = ROTATION_DEGS.index(rotation)1045        else:1046            raise ValueError('invalid rotation "%s"' % (rotation,))1047        if self._conn:1048            return self._conn.sendUserRotation(rotation)1049        else:1050            return False1051    def shell(self, shellCommand, timeout=None):1052        """1053        Execute shellCommand in adb shell.1054        Parameters:1055          shellCommand (string):1056                  command to be executed in adb shell.1057                  Arguments separated by whitespace.1058          timeout (optional, integer):1059                  time in seconds after which the command1060                  will timeout. The default is None (no timeout).1061        Returns output of "adb shell" command, or None if timed out.1062        If you wish to receive exitstatus or standard output and error1063        separated from shellCommand, refer to shellSOE().1064        """1065        try:1066            output = self.existingConnection()._runAdb(1067                ["shell", shellCommand],1068                expectedExitStatus=EXITSTATUS_ANY,1069                timeout=timeout)[1]1070        except FMBTAndroidRunError:1071            output = None1072        return output1073    def shellSOE(self, shellCommand, timeout=None):1074        """1075        Execute shellCommand in adb shell.1076        Parameters:1077          shellCommand (string):1078                  command to be executed in adb shell.1079                  Arguments separated by whitespace.1080          timeout (optional, integer):1081                  time in seconds after which the command1082                  will timeout. The default is None (no timeout).1083        Returns tuple (exitStatus, standardOutput, standardError)1084        or (None, None, None) if timed out.1085        Requires tar and uuencode to be available on the device.1086        """1087        return self.existingConnection().shellSOE(shellCommand, timeout)1088    def smsNumber(self, number, message):1089        """1090        Send message using SMS to given number.1091        Parameters:1092          number (string)1093                  phone number to which the SMS will be sent1094          message (string)1095                  the message to be sent.1096        Returns True on success, otherwise False.1097        """1098        smsCommand = ('am start -a android.intent.action.SENDTO ' +1099                      '-d sms:%s --es sms_body "%s"' +1100                      ' --ez exit_on_sent true')  % (number, message)1101        status, out, err = self.shellSOE(smsCommand)1102        if status != 0:1103            _logFailedCommand("sms", smsCommand, status, out, err)1104            return False1105        _adapterLog("SMS command returned %s" % (out + err,))1106        time.sleep(2)1107        if 'talk' in self.topWindow():1108            _adapterLog("Messaging app is Hangouts")1109            self.pressKey("KEYCODE_ENTER")1110            time.sleep(1)1111            self.pressKey("KEYCODE_BACK")1112            time.sleep(1)1113            self.pressKey("KEYCODE_BACK")1114        else:1115            self.pressKey("KEYCODE_DPAD_RIGHT")1116            time.sleep(1)1117            self.pressKey("KEYCODE_ENTER")1118        return True1119    def statusBarVisible(self):1120        """1121        Returns True if the status bar is showing, otherwise False.1122        """1123        return self.existingConnection().recvStatusBarVisible()1124    def supportsView(self):1125        """1126        Check if connected device supports reading view data.1127        View data is needed by refreshView(), view(), verifyText() and1128        waitText(). It is produced by Android window dump.1129        Returns True if view data can be read, otherwise False.1130        """1131        if self._supportsView == None:1132            try:1133                if self.uiautomatorDump():1134                    if self.existingConnection().recvUiautomatorDump():1135                        self._supportsView = True1136                    else:1137                        self._supportsView = False1138                else:1139                    self.existingConnection().recvViewData()1140                    self._supportsView = True1141            except AndroidConnectionError:1142                self._supportsView = False1143        return self._supportsView1144    def swipeText(self, text, direction, partial=False, **swipeKwArgs):1145        """1146        Find an item with given text from the latest view, and swipe it.1147        Parameters:1148          text (string):1149                  text to be swiped.1150          direction (string or integer):1151                  refer to swipe documentation1152          distance (float, optional):1153                  refer to swipe documentation1154          partial (boolean, optional):1155                  refer to verifyText documentation. The default is1156                  False.1157          startPos1158                  refer to swipeItem documentation.1159          delayBeforeMoves, delayBetweenMoves, delayAfterMoves,1160          movePoints1161                  refer to drag documentation.1162        Returns True if successful, otherwise False.1163        """1164        assert self._lastView != None, "View required."1165        items = self._lastView.findItemsByText(text, partial=partial, count=1, onScreen=True)1166        if len(items) == 0: return False1167        return self.swipeItem(items[0], direction, **swipeKwArgs)1168    def systemProperty(self, propertyName):1169        """1170        Returns Android Monkey Device properties, such as1171        "clock.uptime", refer to Android Monkey documentation.1172        """1173        return self.existingConnection().recvVariable(propertyName)1174    def tapId(self, viewItemId, **tapKwArgs):1175        """1176        Find an item with given id from the latest view, and tap it.1177        """1178        assert self._lastView != None, "View required."1179        items = self._lastView.findItemsById(viewItemId, count=1, onScreen=True)1180        if len(items) > 0:1181            return self.tapItem(items[0], **tapKwArgs)1182        else:1183            _adapterLog("tapItemById(%s): no items found" % (viewItemId,))1184            return False1185    def tapText(self, text, partial=False, **tapKwArgs):1186        """1187        Find an item with given text from the latest view, and tap it.1188        Parameters:1189          partial (boolean, optional):1190                  refer to verifyText documentation. The default is1191                  False.1192          tapPos (pair of floats (x, y), optional):1193                  refer to tapItem documentation.1194          long, hold, count, delayBetweenTaps (optional):1195                  refer to tap documentation.1196        Returns True if successful, otherwise False.1197        """1198        assert self._lastView != None, "View required."1199        items = self._lastView.findItemsByText(text, partial=partial, count=1, onScreen=True)1200        if len(items) == 0: return False1201        return self.tapItem(items[0], **tapKwArgs)1202    def tapContentDesc(self, contentDesc, **tapKwArgs):1203        """1204        Find an item with given content description, and tap it.1205        Parameters:1206          contentDesc (string):1207                  content description of the item to be tapped.1208          tapPos (pair of floats (x, y), optional):1209                  refer to tapItem documentation.1210          long, hold, count, delayBetweenTaps (optional):1211                  refer to tap documentation.1212        Returns True if successful, otherwise False.1213        Note: Requires that the latest refreshView used the uiautomator1214        backend for fetching the view data. Example:1215        d.refreshView(uiautomatorDump=True)1216        d.tapContentDesc("Apps")1217        """1218        assert self._lastView != None, "View required."1219        items = self._lastView.findItemsByContentDesc(contentDesc)1220        if len(items) == 0: return False1221        return self.tapItem(items[0], **tapKwArgs)1222    def topApp(self):1223        """1224        Returns the name of the top application.1225        """1226        if not self._conn:1227            return None1228        else:1229            return self._conn.recvTopAppWindow()[0]1230    def topWindow(self):1231        """1232        Returns the name of the top window.1233        """1234        # the top window may be None during transitions, therefore1235        # retry a couple of times if necessary.1236        if not self._conn:1237            return None1238        timeout = 0.51239        pollDelay = 0.21240        start = time.time()1241        tw = self.existingConnection().recvTopAppWindow()[1]1242        while tw == None and (time.time() - start < timeout):1243            time.sleep(pollDelay)1244            tw = self.existingConnection().recvTopAppWindow()[1]1245        return tw1246    def topWindowStack(self):1247        """1248        Returns window names in the stack of the top fullscreen application.1249        The topmost window is the last one in the list.1250        """1251        return self.existingConnection().recvTopWindowStack()1252    def uiautomatorDump(self):1253        """1254        Returns whether or not uiautomator is used for refreshView()1255        """1256        return self._uiautomatorDump1257    def uninstall(self, apkname, keepData=False):1258        """1259        Uninstall a package from the device.1260        Parameters:1261          package (string):1262                  the package to be uninstalled.1263          keepData (boolean, optional):1264                  keep app data and cache.1265                  Corresponds to adb uninstall "-k".1266                  The default is False.1267        Returns True on success, otherwise False.1268        Example:1269          d.uninstall("com.android.python27")1270        """1271        if self._conn:1272            return self._conn.uninstall(apkname, keepData)1273        else:1274            return False1275    def userRotation(self):1276        """1277        Returns rotation set with setUserRotation.1278        """1279        return self.existingConnection().recvUserRotation()1280    def verifyText(self, text, partial=False):1281        """1282        Verify that the last view has at least one item with given1283        text.1284        Parameters:1285          text (string):1286                  text to be searched for in items.1287          partial (boolean, optional):1288                  if True, match items if item text contains given1289                  text, otherwise match only if item text is equal to1290                  the given text. The default is False (exact match).1291        """1292        assert self._lastView != None, "View required."1293        return self._lastView.findItemsByText(text, partial=partial, count=1, onScreen=True) != []1294    def view(self):1295        """1296        Returns the last view (the most recently refreshed view).1297        """1298        return self._lastView1299    def waitAnyText(self, listOfTexts, partial=False, uiautomatorDump=False, **waitKwArgs):1300        """1301        Wait until any of texts is on the screen.1302        Parameters:1303          listOfTexts (list of string):1304                  texts to be waited for.1305          partial (boolean, optional):1306                refer to verifyText. The default is False.1307          uiautomatorDump (boolean, optional):1308                use uiautomator to read view dump from the device.1309                If not given, uiautomatorDump() default will be used.1310          waitTime, pollDelay, beforeRefresh, afterRefresh (optional):1311                  refer to wait documentation.1312        Returns list of texts that appear in the first refreshed view1313        that contains at least one of the texts. If none of the texts1314        appear within the time limit, returns empty list.1315        If any of texts is not found from the latest refreshed1316        view, waitAnyText will update the view in pollDelay interval until1317        waitTime is exceeded.1318        """1319        if listOfTexts == []: return []1320        if not self._lastView: self.refreshView(uiautomatorDump=uiautomatorDump)1321        waitArgs, rest = fmbtgti._takeWaitArgs(waitKwArgs)1322        foundTexts = []1323        def observe():1324            for text in listOfTexts:1325                if self.verifyText(text, partial=partial):1326                    foundTexts.append(text)1327            return foundTexts != []1328        self.wait(1329            lambda: self.refreshView(uiautomatorDump=uiautomatorDump),1330            observe, **waitArgs)1331        return foundTexts1332    def waitText(self, text, *waitAnyTextArgs, **waitAnyTextKwArgs):1333        """1334        Wait until text appears in any view item.1335        Parameters:1336          text (string):1337                text to be waited for.1338          partial (boolean, optional):1339                refer to verifyText. The default is False.1340          uiautomatorDump (boolean, optional):1341                use uiautomator to read view dump from the device.1342                If not given, uiautomatorDump() default will be used.1343          waitTime, pollDelay, beforeRefresh, afterRefresh (optional):1344                refer to wait documentation.1345        Returns True if text appeared within given time limit,1346        otherwise False.1347        Updates the last view if the text is not found in the latest view1348        and waitTime > 0.1349        """1350        return self.waitAnyText(1351            [text], *waitAnyTextArgs, **waitAnyTextKwArgs) != []1352    def wake(self):1353        """1354        Force the device to wake up.1355        """1356        return self.existingConnection().sendWake()1357    def _loadDeviceAndTestINIs(self, homeDir, deviceName, iniFile):1358        if deviceName != None:1359            _deviceIniFilename = homeDir + os.sep + "etc" + os.sep + deviceName + ".ini"1360            self.loadConfig(_deviceIniFilename, override=True, level="device")1361        if iniFile:1362            self.loadConfig(iniFile, override=True, level="test")1363class Ini:1364    """1365    Container for device configuration loaded from INI files.1366    INI file syntax:1367    [section1]1368    key1 = value11369    ; commented = out1370    # commented = out1371    """1372    def __init__(self, iniFile=None):1373        """1374        Initialise the container, optionally with an initial configuration.1375        Parameters:1376          iniFile (file object, optional):1377                  load the initial configuration from iniFile.1378                  The default is None: start with empty configuration.1379        """1380        # _conf is a dictionary:1381        # (section, key) -> value1382        self._conf = {}1383        if iniFile:1384            self.addFile(iniFile)1385    def addFile(self, iniFile, override=True):1386        """1387        Add values from a file to the current configuration.1388        Parameters:1389          iniFile (file object):1390                  load values from this file object.1391          override (boolean, optional):1392                  If True, loaded values override existing values.1393                  Otherwise, only currently undefined values are1394                  loaded. The default is True.1395        """1396        for line in iniFile:1397            line = line.strip()1398            if line.startswith('[') and line.endswith(']'):1399                section = line[1:-1].strip()1400            elif line.startswith(";") or line.startswith("#"):1401                continue1402            elif '=' in line:1403                key, value = line.split('=', 1)1404                if override or (section, key.strip()) not in self._conf:1405                    self._conf[(section, key.strip())] = value.strip()1406    def sections(self):1407        """1408        Returns list of sections in the current configuration.1409        """1410        return list(set([k[0] for k in self._conf.keys()]))1411    def keys(self, section):1412        """1413        Returns list of keys in a section in the current configuration.1414        Parameters:1415          section (string):1416                  the name of the section.1417        """1418        return [k[1] for k in self._conf.keys() if k[0] == section]1419    def dump(self):1420        """1421        Returns the current configuration as a single string in the1422        INI format.1423        """1424        lines = []1425        for section in sorted(self.sections()):1426            lines.append("[%s]" % (section,))1427            for key in sorted(self.keys(section)):1428                lines.append("%-16s = %s" % (key, self._conf[(section, key)]))1429            lines.append("")1430        return "\n".join(lines)1431    def set(self, section, key, value):1432        """1433        Set new value for a key in a section.1434        Parameters:1435          section, key (strings):1436                  the section, the key.1437          value (string):1438                  the new value. If not string already, it will be1439                  converted to string, and it will be loaded as a1440                  string when loaded from file object.1441        """1442        self._conf[(section, key)] = str(value)1443    def value(self, section, key, default=""):1444        """1445        Returns the value (string) associated with a key in a section.1446        Parameters:1447          section, key (strings):1448                  the section and the key.1449          default (string, optional):1450                  the default value to be used and stored if there is1451                  no value associated to the key in the section. The1452                  default is the empty string.1453        Reading a value of an undefined key in an undefined section1454        adds the key and the section to the configuration with the1455        returned (the default) value. This makes all returned values1456        visible in dump().1457        """1458        if not (section, key) in self._conf:1459            self._conf[(section, key)] = default1460        return self._conf[(section, key)]1461# For backward compatibility, someone might be using old _DeviceConf1462_DeviceConf = Ini1463class ViewItem(fmbtgti.GUIItem):1464    """1465    ViewItem holds the information of a single GUI element.1466    """1467    _boundsRegEx = re.compile(r'\[([0-9]+),([0-9]+)\]\[([0-9]+),([0-9]+)\]')1468    def __init__(self, className, code, indent, properties, parent, rawProps, dumpFilename, displayToScreen):1469        self._p = properties1470        self._parent = parent1471        self._className = className1472        self._code = code1473        self._indent = indent1474        self._children = []1475        self._parentsVisible = True1476        if "resource-id" in self._p:1477            self._id = self._p["resource-id"].split(":", 1)[-1]1478        else:1479            self._id = self.property("mID")1480        self._rawProps = ""1481        if not "bounds" in self._p:1482            if not "scrolling:mScrollX" in self._p:1483                self._p["scrolling:mScrollX"] = 01484                self._p["scrolling:mScrollY"] = 01485            self._visible = self._p.get("getVisibility()", "") == "VISIBLE"1486            if "text:mText" in self._p:1487                self._text = self._p["text:mText"]1488            else:1489                self._text = None1490        else:1491            self._visible = True1492            self._text = self._p["text"]1493        fmbtgti.GUIItem.__init__(self, className, self._calculateBbox(displayToScreen), dumpFilename)1494    def addChild(self, child):1495        child._parentsVisible = self.visibleBranch()1496        self._children.append(child)1497    def _calculateBbox(self, displayToScreen):1498        if "bounds" in self._p:1499            try:1500                left, top, right, bottom = [1501                    int(v) for v in1502                    ViewItem._boundsRegEx.findall(self._p["bounds"])[0]]1503            except IndexError:1504                raise ValueError('invalid bounds "%s"' % (self._p["bounds"],))1505            width = right - left1506            height = bottom - top1507        elif "layout:getLocationOnScreen_x()" in self._p:1508            left = int(self._p["layout:getLocationOnScreen_x()"])1509            top = int(self._p["layout:getLocationOnScreen_y()"])1510            height = int(self._p["layout:getHeight()"])1511            width = int(self._p["layout:getWidth()"])1512        elif "layout:mLeft" in self._p:1513            left = int(self._p["layout:mLeft"])1514            top = int(self._p["layout:mTop"])1515            parent = self._parent1516            while parent:1517                pp = parent._p1518                left += int(pp["layout:mLeft"]) - int(pp["scrolling:mScrollX"])1519                top += int(pp["layout:mTop"]) - int(pp["scrolling:mScrollY"])1520                parent = parent._parent1521            height = int(self._p["layout:getHeight()"])1522            width = int(self._p["layout:getWidth()"])1523        else:1524            raise ValueError("bounding box not found, layout fields missing")1525        screenLeft, screenTop = displayToScreen(left, top)1526        screenRight, screenBottom = displayToScreen(left + width, top + height)1527        return (screenLeft, screenTop, screenRight, screenBottom)1528    def children(self):   return self._children1529    def className(self):  return self._className1530    def code(self):       return self._code1531    def indent(self):     return self._indent1532    def id(self):         return self._id1533    def parent(self):     return self._parent1534    def properties(self): return self._p1535    def property(self, propertyName):1536        return self._p.get(propertyName, None)1537    def visibleBranch(self):1538        """Returns True if this item and all items containing this are visible1539        up to the root node"""1540        return self._parentsVisible and self.visible()1541    def text(self):1542        return self._text1543    def content_desc(self):1544        if "content-desc" in self._p:1545            return self._p["content-desc"]1546        elif "accessibility:getContentDescription()" in self._p:1547            return self._p["accessibility:getContentDescription()"]1548        else:1549            return None1550    def visible(self):1551        return self._visible1552    def dump(self):1553        p = self._p1554        return ("ViewItem(\n\tchildren = %d\n\tclassName = '%s'\n\tcode = '%s'\n\t" +1555                "indent = %d\n\tproperties = {\n\t\t%s\n\t})") % (1556            len(self._children), self._className, self._code, self._indent,1557            '\n\t\t'.join(['"%s": %s' % (key, p[key]) for key in sorted(p.keys())]))1558    def dumpProperties(self):1559        rv = []1560        if self._p:1561            for key in [k for k in sorted(self._p.keys()) if not "layout:" in k and not "padding:" in k and not "drawing:" in k]: # sorted(self._p.keys()): # [k for k in sorted(self._p.keys()) if not ":" in k]:1562                rv.append("%s=%s" % (key, self._p[key]))1563        return "\n".join(rv)1564    def __str__(self):1565        if self.text():1566            text = ", text=%s" % (repr(self.text()),)1567        else:1568            text = ""1569        if "content-desc" in self._p and self._p["content-desc"]:1570            text += ", content_desc=%s" % (repr(self.content_desc(),))1571        return ("ViewItem(className=%s, id=%s, bbox=%s%s)"  % (1572                repr(self._className), repr(self.id()), self.bbox(), text))1573class View(object):1574    """1575    View provides interface to screen dumps from Android. It parses1576    the dump to a hierarchy of ViewItems. find* methods enable searching1577    for ViewItems based on their properties.1578    """1579    def __init__(self, screenshotDir, serialNumber, dump, displayToScreen=None,1580                 itemOnScreen=None, intCoords=None):1581        self.screenshotDir = screenshotDir1582        self.serialNumber = serialNumber1583        self._viewItems = []1584        self._errors = []1585        self._lineRegEx = re.compile("(?P<indent>\s*)(?P<class>[\w.$]+)@(?P<id>[0-9A-Fa-f]{4,8} )(?P<properties>.*)")1586        self._olderAndroidLineRegEx = re.compile("(?P<indent>\s*)(?P<class>[\w.$]+)@(?P<id>\w)(?P<properties>.*)")1587        self._propRegEx = re.compile("(?P<prop>(?P<name>[^=]+)=(?P<len>\d+),)(?P<data>[^\s]* ?)")1588        self._dump = dump1589        self._rawDumpFilename = self.screenshotDir + os.sep + fmbtgti._filenameTimestamp() + "-" + self.serialNumber + ".view"1590        file(self._rawDumpFilename, "w").write(self._dump)1591        if displayToScreen == None:1592            displayToScreen = lambda x, y: (x, y)1593        if itemOnScreen == None:1594            itemOnScreen = lambda item: True1595        self._itemOnScreen = itemOnScreen1596        if intCoords == None:1597            intCoords = lambda x, y: (int(x), int(y))1598        self._intCoords = intCoords1599        try:1600            if dump.startswith("<?xm"):1601                self._parseUIAutomatorDump(dump, self._rawDumpFilename, displayToScreen)1602            else:1603                self._parseDump(dump, self._rawDumpFilename, displayToScreen)1604        except Exception, e:1605            self._errors.append((-1, "", "Parser error"))1606    def viewItems(self): return self._viewItems1607    def errors(self): return self._errors1608    def dumpRaw(self): return self._dump1609    def dumpItems(self, itemList = None):1610        if itemList == None: itemList = self._viewItems1611        l = []1612        for i in itemList:1613            l.append(self._dumpItem(i))1614        return '\n'.join(l)1615    def dumpTree(self, rootItem = None):1616        l = []1617        if rootItem != None:1618            l.extend(self._dumpSubTree(rootItem, 0))1619        else:1620            for i in self._viewItems:1621                if i._indent == 0:1622                    l.extend(self._dumpSubTree(i, 0))1623        return '\n'.join(l)1624    def _dumpSubTree(self, viewItem, indent):1625        l = []1626        i = viewItem1627        l.append(" "*indent + self._dumpItem(viewItem))1628        for i in viewItem.children():1629            l.extend(self._dumpSubTree(i, indent + 4))1630        return l1631    def _dumpItem(self, viewItem):1632        i = viewItem1633        if i.text() != None: t = '"%s"' % (i.text(),)1634        else: t = None1635        return "id=%s cls=%s text=%s bbox=%s vis=%s" % (1636            i.id(), i.className(), t, i.bbox(), i.visibleBranch())1637    def filename(self):1638        return self._rawDumpFilename1639    def findItems(self, comparator, count=-1, searchRootItem=None, searchItems=None, onScreen=False):1640        """1641        Returns list of ViewItems to which comparator returns True.1642        Parameters:1643          comparator (function that takes one parameter (ViewItem))1644                  returns True for all accepted items.1645          count (integer, optional):1646                  maximum number of items to be returned.1647                  The default is -1 (unlimited).1648          searchRootItem (ViewItem, optional):1649                  search only among items that are children of1650                  searchRootItem. The default is None (search from all).1651          searchItems (list of ViewItems, optional):1652                  search only among given items. The default is None,1653                  (search from all).1654          onScreen (boolean, optional):1655                  search only among items that are on screen. The1656                  default is False.1657        """1658        foundItems = []1659        if count == 0: return foundItems1660        if searchRootItem != None:1661            # find from searchRootItem and its children1662            if comparator(searchRootItem) and (1663                    not onScreen or1664                    searchRootItem.visibleBranch() and self._itemOnScreen(searchRootItem)):1665                foundItems.append(searchRootItem)1666            for c in searchRootItem.children():1667                foundItems.extend(self.findItems(comparator, count=count-len(foundItems), searchRootItem=c, onScreen=onScreen))1668        else:1669            if searchItems != None:1670                # find from listed items only1671                searchDomain = searchItems1672            else:1673                # find from all items1674                searchDomain = self._viewItems1675            for i in searchDomain:1676                if comparator(i) and (1677                        not onScreen or1678                        i.visibleBranch() and self._itemOnScreen(i)):1679                    foundItems.append(i)1680                    if count > 0 and len(foundItems) >= count:1681                        break1682        return foundItems1683    def findItemsByText(self, text, partial=False, count=-1, searchRootItem=None, searchItems=None, onScreen=False):1684        """1685        Returns list of ViewItems with given text.1686        """1687        if partial:1688            c = lambda item: item.text().find(text) != -1 if item.text() != None else False1689        else:1690            c = lambda item: item.text() == text1691        return self.findItems(c, count=count, searchRootItem=searchRootItem, searchItems=searchItems, onScreen=onScreen)1692    def findItemsById(self, id, count=-1, searchRootItem=None, searchItems=None, onScreen=False):1693        """1694        Returns list of ViewItems with given id.1695        """1696        c = lambda item: item.id() == id1697        return self.findItems(c, count=count, searchRootItem=searchRootItem, searchItems=searchItems, onScreen=onScreen)1698    def findItemsByClass(self, className, partial=True, count=-1, searchRootItem=None, searchItems=None, onScreen=False):1699        """1700        Returns list of ViewItems with given class.1701        """1702        if partial: c = lambda item: item.className().find(className) != -11703        else: c = lambda item: item.className() == className1704        return self.findItems(c, count=count, searchRootItem=searchRootItem, searchItems=searchItems, onScreen=onScreen)1705    def findItemsByIdAndClass(self, id, className, partial=True, count=-1, searchRootItem=None, searchItems=None, onScreen=False):1706        """1707        Returns list of ViewItems with given id and class.1708        """1709        idOk = self.findItemsById(id, count=-1, searchRootItem=searchRootItem, onScreen=onScreen)1710        return self.findItemsByClass(className, partial=partial, count=count, searchItems=idOk, onScreen=onScreen)1711    def findItemsByContentDesc(self, content_desc, partial=False, count=-1, searchRootItem=None, searchItems=None, onScreen=False):1712        """1713        Returns list of ViewItems with given content-desc.1714        Works on uiautomatorDumps only.1715        """1716        if partial:1717            c = lambda item: item.content_desc().find(content_desc) != -11718        else:1719            c = lambda item: item.content_desc() == content_desc1720        return self.findItems(c, count=count, searchRootItem=searchRootItem, searchItems=searchItems, onScreen=onScreen)1721    def findItemsByRawProps(self, s, count=-1, searchRootItem=None, searchItems=None, onScreen=False):1722        """1723        Returns list of ViewItems with given string in properties.1724        """1725        c = lambda item: item._rawProps.find(s) != -11726        return self.findItems(c, count=count, searchRootItem=searchRootItem, searchItems=searchItems, onScreen=onScreen)1727    def findItemsByPos(self, pos, count=-1, searchRootItem=None, searchItems=None, onScreen=False):1728        """1729        Returns list of ViewItems whose bounding box contains the position.1730        Parameters:1731          pos (pair of floats (0.0..0.1) or integers (x, y)):1732                  coordinates that fall in the bounding box of found items.1733          other parameters: refer to findItems documentation.1734        Items are listed in ascending order based on area. They may1735        or may not be from the same branch in the widget hierarchy.1736        """1737        x, y = self._intCoords(pos)1738        c = lambda item: (item.bbox()[0] <= x <= item.bbox()[2] and item.bbox()[1] <= y <= item.bbox()[3])1739        items = self.findItems(c, count=count, searchRootItem=searchRootItem, searchItems=searchItems, onScreen=onScreen)1740        # sort from smallest to greatest area1741        area_items = [((i.bbox()[2] - i.bbox()[0]) * (i.bbox()[3] - i.bbox()[1]), i) for i in items]1742        return [i for _, i in sorted(area_items)]1743    def findItemsInRegion(self, bbox, count=-1, searchRootItem=None, searchItems=None, onScreen=False):1744        """1745        Returns list of ViewItems whose bounding box is within the region.1746        Parameters:1747          bbox (four-tuple of floats (0.0..1.0) or integers):1748                  bounding box that specifies search region1749                  (left, top, right, bottom).1750          other parameters: refer to findItems documentation.1751        Returned items are listed in ascending order based on area.1752        """1753        left, top = self._intCoords((bbox[0], bbox[1]))1754        right, bottom = self._intCoords((bbox[2], bbox[3]))1755        c = lambda item: (left <= item.bbox()[0] <= item.bbox()[2] <= right and1756                          top <= item.bbox()[1] <= item.bbox()[3] <= bottom)1757        items = self.findItems(c, count=count, searchRootItem=searchRootItem,1758                               searchItems=searchItems, onScreen=onScreen)1759        area_items = [((i.bbox()[2] - i.bbox()[0]) * (i.bbox()[3] - i.bbox()[1]), i) for i in items]1760        return [i for _, i in sorted(area_items)]1761    def items(self):1762        """1763        Returns list of all items in the view1764        """1765        return fmbtgti.sortItems(self._viewItems, "topleft")1766    def save(self, fileOrDirName):1767        """1768        Save view dump to a file.1769        """1770        shutil.copy(self._rawDumpFilename, fileOrDirName)1771    def _parseUIAutomatorDump(self, dump, rawDumpFilename, displayToScreen):1772        """1773        Process XML output from "uiautomator dump" and create1774        a tree of ViewItems.1775        """1776        def add_elt(elt, parent, indent, results):1777            if ("resource-id" in elt.attrib and1778                "bounds" in elt.attrib):1779                try:1780                    vi = ViewItem(elt.attrib["class"],1781                                  elt.attrib["resource-id"].split(":", 1)[-1],1782                                  indent,1783                                  elt.attrib,1784                                  parent,1785                                  "",1786                                  self._rawDumpFilename,1787                                  displayToScreen)1788                    results.append(vi)1789                    if parent:1790                        parent.addChild(self._viewItems[-1])1791                except Exception, e:1792                    adapterlog("parseUIAutomatorDump error: %s" % (e,))1793                    vi = parent1794            else:1795                vi = parent1796            for child in elt.getchildren():1797                add_elt(child, vi, indent + 1, results)1798        self._viewItems = []1799        tree = xml.etree.ElementTree.parse(rawDumpFilename)1800        root = tree.getroot()1801        add_elt(root, None, 0, self._viewItems)1802        return self._viewItems1803    def _parseDump(self, dump, rawDumpFilename, displayToScreen):1804        """1805        Process the raw window service dump data and create a tree of1806        ViewItems.1807        """1808        if not isinstance(dump, unicode):1809            try:1810                dump = unicode(dump, "utf-8")1811            except UnicodeDecodeError, e:1812                self._errors.append((0, 0, "converting to unicode failed: %s" % (e,)))1813        # This code originates from tema-android-adapter-3.2,1814        # AndroidAdapter/guireader.py.1815        self._viewItems = []1816        cellLayout = ""1817        parent = None1818        previousItem = None1819        currentIndent = 01820        visible = True1821        self.TOP_PAGED_VIEW = ""1822        last_line = set(["DONE", "DONE."])1823        for lineIndex, line in enumerate(dump.splitlines()):1824            if line in last_line:1825                break1826            # separate indent, class and properties for each GUI object1827            # TODO: branch here according to self._androidVersion1828            matcher = self._lineRegEx.match(line)1829            if not matcher:1830                # FIXME: this hack falls back to old format,1831                # should branch according to self._androidVersion!1832                matcher = self._olderAndroidLineRegEx.match(line)1833                if not matcher:1834                    self._errors.append((lineIndex + 1, line, "illegal line"))1835                    continue # skip this line1836            className = matcher.group("class")1837            # Indent specifies the hierarchy level of the object1838            indent = len(matcher.group("indent"))1839            # If the indent is bigger that previous, this object is a1840            # child for the previous object1841            if indent > currentIndent:1842                parent = self._viewItems[-1]1843            elif indent < currentIndent:1844                for tmp in range(0, currentIndent - indent):1845                    parent = parent.parent()1846            currentIndent = indent1847            propertiesData = matcher.group("properties")1848            properties = {}1849            index = 01850            x = 01851            y = 01852            # Process the properties of each GUI object1853            while index < len(propertiesData):1854                # Separate name and value for each property [^=]*=1855                propMatch = self._propRegEx.match(propertiesData[index:-1])1856                if not propMatch:1857                    self._errors.append((lineIndex, line,1858                                         "property parse error"))1859                    break1860                name = propMatch.group("name")1861                if not name:1862                    self._errors.append(1863                        (lineIndex, line,1864                         'illegal property name "%s"' % (name,)))1865                    break1866                try:1867                    dataLength = int(propMatch.group("len"))1868                except ValueError:1869                    self._errors.append(1870                        (lineIndex, line,1871                         'illegal length (int) "%s"' % (propMatch.group("len"),)))1872                    break1873                data = propMatch.group("data")1874                dataStart = index + propMatch.start("data")1875                if len(data) < dataLength:1876                    if not data:1877                        self._errors.append(1878                            (lineIndex, line,1879                             'property "%s": data missing, expected %s' % (name, dataLength, len(data))))1880                        break1881                properties[name] = propertiesData[dataStart:dataStart + dataLength]1882                index = dataStart + dataLength + 11883            try:1884                vi = ViewItem(matcher.group("class"), matcher.group("id"), indent, properties, parent, matcher.group("properties"), self._rawDumpFilename, displayToScreen)1885                self._viewItems.append(vi)1886                if parent:1887                    parent.addChild(self._viewItems[-1])1888            except Exception, e:1889                self._errors.append(1890                    (lineIndex, line,1891                     "creating view item failed (%s: %s)" % (type(e), e)))1892        return self._viewItems1893    def __str__(self):1894        return 'View(items=%s, dump="%s")' % (1895            len(self._viewItems), self._rawDumpFilename)1896class _AndroidDeviceConnection(fmbtgti.GUITestConnection):1897    """1898    Connection to the Android Device being tested.1899    """1900    _m_host = os.getenv("FMBTANDROID_ADB_FORWARD_HOST", 'localhost')1901    _m_port = int(os.getenv("FMBTANDROID_ADB_FORWARD_PORT", random.randint(20000, 29999)))1902    _w_host = _m_host1903    def __init__(self, serialNumber, **kwArgs):1904        fmbtgti.GUITestConnection.__init__(self)1905        self._serialNumber = serialNumber1906        self._adbPort = kwArgs.pop("adbPort", None)1907        self._monkeyPortForward = kwArgs.pop(1908            "adbForwardPort", _AndroidDeviceConnection._m_port)1909        self._windowPortForward = kwArgs.pop(1910            "windowPortForward", self._monkeyPortForward + 1)1911        self._stopOnError = kwArgs.pop("stopOnError", True)1912        self._monkeyOptions = kwArgs.pop("monkeyOptions", [])1913        self._screencapArgs = kwArgs.pop("screencapArgs", [])1914        self._screencapFormat = kwArgs.pop("screencapFormat", "raw")1915        self.setScreenToDisplayCoords(1916            kwArgs.pop("screenToDisplay", lambda x, y: (x, y)))1917        self.setDisplayToScreenCoords(1918            kwArgs.pop("displayToScreen", lambda x, y: (x, y)))1919        if kwArgs:1920            raise TypeError('_AndroidDeviceConnection.__init__() got an '1921                            'unexpected keyword argument %s=%s' % (1922                kwArgs.keys()[0], repr(kwArgs[kwArgs.keys()[0]])))1923        self._detectFeatures()1924        self._emulatorSocket = None1925        try:1926            self._resetMonkey()1927            self._resetWindow()1928        finally:1929            # Next _AndroidDeviceConnection instance will use different ports1930            _AndroidDeviceConnection._m_port += 1001931    def __del__(self):1932        try: self._monkeySocket.close()1933        except: pass1934        try: self._emulatorSocket.close()1935        except: pass1936    def settings(self):1937        """Returns restorable property values"""1938        rv = {1939            "adbPort": self._adbPort,1940            "adbForwardPort": self._monkeyPortForward,1941            "stopOnError": self._stopOnError,1942            "monkeyOptions": self._monkeyOptions,1943            "screencapArgs": self._screencapArgs,1944            "screencapFormat": self._screencapFormat,1945            "screenToDisplay": self._screenToDisplay,1946            "displayToScreen": self._displayToScreen,1947        }1948        return rv1949    def target(self):1950        return self._serialNumber1951    def _cat(self, remoteFilename):1952        fd, filename = tempfile.mkstemp("fmbtandroid-cat-")1953        os.close(fd)1954        self._runAdb(["pull", remoteFilename, filename], 0, timeout=_LONG_TIMEOUT)1955        contents = file(filename).read()1956        os.remove(filename)1957        return contents1958    def _runAdb(self, adbCommand, expectedExitStatus=0, timeout=None):1959        if not self._stopOnError:1960            expect = None1961        else:1962            expect = expectedExitStatus1963        if self._adbPort:1964            adbPortArgs = ["-P", str(self._adbPort)]1965        else:1966            adbPortArgs = []1967        command = [_g_adbExecutable, "-s", self._serialNumber] + adbPortArgs1968        if type(adbCommand) == list or type(adbCommand) == tuple:1969            command.extend(adbCommand)1970        else:1971            command.append(adbCommand)1972        return _run(command, expectedExitStatus=expect, timeout=timeout)1973    def _emulatorCommand(self, command):1974        if not self._emulatorSocket:1975            try:1976                emulatorPort = int(re.findall("emulator-([0-9]*)", self._serialNumber)[0])1977            except (IndexError, ValueError):1978                raise FMBTAndroidError("emulator port detection failed")1979            try:1980                self._emulatorSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)1981                self._emulatorSocket.connect(("localhost", emulatorPort))1982            except socket.error, e:1983                raise FMBTAndroidError("connecting to the emulator failed: %s" % (e,))1984        self._emulatorSocket.sendall(command + "\n")1985        data = self._emulatorSocket.recv(4096)1986        try:1987            data = data.splitlines()[-1].strip()1988        except IndexError:1989            raise FMBTAndroidError("no response from the emulator")1990        if data.startswith("OK"):1991            return True, data1992        else:1993            return False, data1994    def _runSetupCmd(self, cmd, expectedExitStatus=0):1995        _adapterLog('setting up connections: "%s"' % (cmd,))1996        try:1997            if expectedExitStatus == None: # execute asynchronously1998                self._runAdb(cmd, expectedExitStatus)1999            else: # command is expected to exit2000                self._runAdb(cmd, expectedExitStatus, timeout=_SHORT_TIMEOUT)2001        except (FMBTAndroidRunError, AndroidDeviceNotFound), e:2002            _adapterLog("connection setup problem: %s" % (e,))2003            return False2004        return True2005    def _detectFeatures(self):2006        # check supported features2007        outputLines = self._runAdb(["shell", "getprop", "ro.build.version.release"],2008                                   timeout=_SHORT_TIMEOUT)[1].splitlines()2009        if len(outputLines) >= 1:2010            self._platformVersion = outputLines[0].strip().split("=")[-1]2011        else:2012            self._platformVersion = "N/A"2013        outputLines = self._runAdb(["shell", "id"],2014                                   timeout=_SHORT_TIMEOUT)[1].splitlines()2015        if len(outputLines) == 1 and "uid=0" in outputLines[0]:2016            self._shellUid0 = True2017        else:2018            self._shellUid0 = False2019        outputLines = self._runAdb(["shell", "su", "root", "id"],2020                                   timeout=_SHORT_TIMEOUT)[1].splitlines()2021        if len(outputLines) == 1 and "uid=0" in outputLines[0]:2022            self._shellSupportsSu = True2023        else:2024            self._shellSupportsSu = False2025        outputLines = self._runAdb(["shell", "tar"],2026                                   expectedExitStatus=EXITSTATUS_ANY,2027                                   timeout=_SHORT_TIMEOUT)[1].splitlines()2028        if len(outputLines) == 1 and "bin" in outputLines[0]:2029            self._shellSupportsTar = False2030        else:2031            self._shellSupportsTar = True2032        outputLines = self._runAdb(["shell", "echo -n foo | toybox base64"],2033                                   expectedExitStatus=EXITSTATUS_ANY,2034                                   timeout=_SHORT_TIMEOUT)[1].splitlines()2035        if len(outputLines) == 1 and "Zm9v" in outputLines[0]:2036            self._shellSupportsToyboxBase64 = True2037        else:2038            self._shellSupportsToyboxBase64 = False2039        outputLines = self._runAdb(["shell", "echo -n foo | uuencode -"],2040                                   expectedExitStatus=EXITSTATUS_ANY,2041                                   timeout=_SHORT_TIMEOUT)[1].splitlines()2042        if len(outputLines) > 0 and "begin" in outputLines[0]:2043            self._shellSupportsUuencode = True2044        else:2045            self._shellSupportsUuencode = False2046    def _resetWindow(self):2047        setupCommands = [["shell", "service" , "call", "window", "1", "i32", "4939"],2048                         ["forward", "tcp:"+str(self._windowPortForward), "tcp:4939"]]2049        for c in setupCommands:2050            self._runSetupCmd(c)2051    def _resetMonkey(self, timeout=12, pollDelay=.25):2052        tryKillingMonkeyOnFailure = 12053        failureCountSinceKill = 02054        endTime = time.time() + timeout2055        if self._shellUid0:2056            monkeyLaunch = ["monkey"]2057        elif self._shellSupportsSu:2058            monkeyLaunch = ["su", "root", "monkey"]2059        else:2060            monkeyLaunch = ["monkey"]2061        if self._monkeyOptions:2062            monkeyLaunch += self._monkeyOptions2063        while time.time() < endTime:2064            monkeyShellCmd = (" ".join(monkeyLaunch + ["--port", "1080"]) +2065                              " >/sdcard/fmbtandroid.monkey.outerr 2>&1")2066            _adapterLog('launching monkey: adb shell "%s"' % (monkeyShellCmd,))2067            self._runAdb(["shell", monkeyShellCmd], expectedExitStatus=None)2068            time.sleep(pollDelay)2069            if not self._runSetupCmd(["forward", "tcp:"+str(self._monkeyPortForward), "tcp:1080"]):2070                time.sleep(pollDelay)2071                failureCountSinceKill += 12072                continue2073            try:2074                self._monkeySocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)2075                self._monkeySocket.connect((self._m_host, self._monkeyPortForward))2076                self._monkeySocket.setblocking(0)2077                self._monkeySocket.settimeout(5.0)2078                _ping = self._monkeyCommand("getvar build.version.release", retry=0)[1]2079                if len(_ping) > 0:2080                    return True2081            except Exception, e:2082                _, monkeyOutput, _ = self._runAdb(["shell", "cat /sdcard/fmbtandroid.monkey.outerr"],2083                                                  expectedExitStatus=EXITSTATUS_ANY,2084                                                  timeout=_SHORT_TIMEOUT)2085                if "/sdcard/fmbtandroid.monkey.outerr: No such file or directory" in monkeyOutput:2086                    msg = 'cannot read/write /sdcard on device %s' % (self._serialNumber,)2087                    _adapterLog(msg)2088                    raise AndroidConnectionError(msg)2089                elif "Error: Unknown option:" in monkeyOutput:2090                    uo = [l for l in monkeyOutput.splitlines() if "Error: Unknown option:" in l][0].split(":")[-1].strip()2091                    _adapterLog('detected an unknown option for monkey: "%s". Disabling it.' % (uo,))2092                    try:2093                        monkeyLaunch.remove(uo)2094                    except ValueError:2095                        pass2096                    continue2097                elif "Error binding to network socket" in monkeyOutput:2098                    _adapterLog('monkey network socket binding failed, killing old monkey')2099                    self.pkill("monkey")2100                    time.sleep(pollDelay)2101                    continue2102                _adapterLog("monkey connection failed, output: %s" % (monkeyOutput,))2103                failureCountSinceKill += 12104            time.sleep(pollDelay)2105            if failureCountSinceKill > 2 and tryKillingMonkeyOnFailure > 0:2106                self.pkill("monkey")2107                tryKillingMonkeyOnFailure -= 12108                failureCountSinceKill = 02109                time.sleep(pollDelay)2110        if self._stopOnError:2111            msg = 'Android monkey error: cannot connect to "adb shell monkey --port 1080" to device %s' % (self._serialNumber)2112            _adapterLog(msg)2113            raise AndroidConnectionError(msg)2114        else:2115            return False2116    def _monkeyCommand(self, command, retry=3):2117        try:2118            self._monkeySocket.sendall(command + "\n")2119            data = self._monkeySocket.recv(4096).strip()2120            if len(data) == 0 and retry > 0:2121                return self._monkeyCommand(command, retry-1)2122            if data == "OK":2123                return True, None2124            elif data.startswith("OK:"):2125                return True, data.split("OK:")[1]2126            _adapterLog("monkeyCommand failing... command: '%s' response: '%s'" % (command, data))2127            return False, None2128        except socket.error:2129            try: self._monkeySocket.close()2130            except: pass2131            if retry > 0:2132                self._resetMonkey()2133                return self._monkeyCommand(command, retry=retry-1)2134            else:2135                raise AndroidConnectionError('Android monkey socket connection lost while sending command "%s"' % (command,))2136    def install(self, filename, lock, reinstall, downgrade,2137                sdcard, algo, key, iv):2138        cmd = ["install"]2139        if lock:2140            cmd.append("-l")2141        if reinstall:2142            cmd.append("-r")2143        if downgrade:2144            cmd.append("-d")2145        if sdcard:2146            cmd.append("-s")2147        if algo != None:2148            cmd.extend(["--algo", algo])2149        if key != None:2150            cmd.extend(["--key", key])2151        if iv != None:2152            cmd.extend(["--iv", iv])2153        cmd.append(filename)2154        status, output, error = self._runAdb(cmd, [0, 1], timeout=_LONG_TIMEOUT)2155        if "Success" in output:2156            return True2157        else:2158            return output + "\n" + error2159    def uninstall(self, apkname, keepData):2160        cmd = ["uninstall"]2161        if keepData:2162            cmd.append("-k")2163        cmd.append(apkname)2164        status, output, error = self._runAdb(2165            cmd, expectedExitStatus=EXITSTATUS_ANY, timeout=_LONG_TIMEOUT)2166        if "Success" in output:2167            return True2168        else:2169            return False2170    def pkill(self, pattern, signal=15, exact=False):2171        """send signal to all processes where process name contains pattern"""2172        _, ps, _ = self._runAdb(["shell", "ps"], timeout=_SHORT_TIMEOUT)2173        if self._shellSupportsSu:2174            shell_kill = ["shell", "su", "root", "kill"]2175        else:2176            shell_kill = ["shell", "kill"]2177        pids = []2178        for line in [l.strip() for l in ps.splitlines()]:2179            fields = line.split()2180            if len(fields) > 7:2181                if exact:2182                    if pattern == fields[7]:2183                        pids.append(fields[1])2184                else:2185                    if pattern in " ".join(fields[7:]):2186                        pids.append(fields[1])2187        if pids:2188            _adapterLog(str(shell_kill + ["-" + str(signal)] + pids))2189            self._runAdb(shell_kill + ["-" + str(signal)] + pids,2190                         expectedExitStatus=EXITSTATUS_ANY,2191                         timeout=_SHORT_TIMEOUT)2192            return True2193        else:2194            return False2195    def recvPlatformVersion(self):2196        return self._platformVersion2197    def reboot(self, reconnect, firstBootAfterFlashing, timeout):2198        if firstBootAfterFlashing:2199            self._runAdb("root", expectedExitStatus=EXITSTATUS_ANY,2200                         timeout=_SHORT_TIMEOUT)2201            time.sleep(2)2202            self._runAdb(["shell", "rm",2203                          "/data/data/com.android.launcher/shared_prefs/com.android.launcher2.prefs.xml"],2204                         expectedExitStatus=EXITSTATUS_ANY,2205                         timeout=_SHORT_TIMEOUT)2206        self._runAdb("reboot", expectedExitStatus=EXITSTATUS_ANY)2207        _adapterLog("rebooting " + self._serialNumber)2208        if reconnect:2209            time.sleep(2)2210            endTime = time.time() + timeout2211            status, _, _ = self._runAdb("wait-for-device", expectedExitStatus=None, timeout=timeout)2212            if status != 0:2213                raise AndroidDeviceNotFound('"timeout -k 1 %s adb wait-for-device" status %s' % (timeout, status))2214            self._detectFeatures()2215            while time.time() < endTime:2216                try:2217                    if self._resetMonkey(timeout=1, pollDelay=1):2218                        break2219                except AndroidConnectionError:2220                    pass2221                time.sleep(1)2222            else:2223                msg = "reboot: reconnecting to " + self._serialNumber + " failed"2224                _adapterLog(msg)2225                raise AndroidConnectionError(msg)2226            self._resetWindow()2227        return True2228    def recvVariable(self, variableName):2229        ok, value = self._monkeyCommand("getvar " + variableName)2230        if ok: return value2231        else:2232            # LOG: getvar variableName failed2233            return None2234    def recvScreenSize(self):2235        _, output, _ = self._runAdb(["shell", "dumpsys", "display"], 0,2236                                    timeout=_SHORT_TIMEOUT)2237        try:2238            # parse default display properties2239            ddName, ddWidth, ddHeight, ddWdpi, ddHdpi = re.findall(2240                r'DisplayDeviceInfo\{[^,]*"([^"]*)"[:,] ([0-9]*) x ([0-9]*),.*, ([0-9.]*) x ([0-9.]*) dpi,.*FLAG_DEFAULT_DISPLAY.*\}',2241                output)[0]2242            ddWidth, ddHeight = int(ddWidth), int(ddHeight)2243        except (IndexError, ValueError), e:2244            _adapterLog('recvScreenSize: cannot read size from "%s"' %2245                        (output,))2246            raise FMBTAndroidError('cannot read screen size from dumpsys')2247        vpWidth, vpHeight = self.recvDefaultViewportSize()2248        if ((vpWidth > vpHeight) and (ddWidth < ddHeight) or2249            (vpWidth < vpHeight) and (ddWidth > ddHeight)):2250            ddWidth, ddHeight = ddHeight, ddWidth2251        return int(ddWidth), int(ddHeight)2252    def recvDefaultViewportSize(self):2253        _, output, _ = self._runAdb(["shell", "dumpsys", "display"], 0,2254                                    timeout=_SHORT_TIMEOUT)2255        try:2256            _, w, h = re.findall("mDefaultViewport(\[0\])?=DisplayViewport\{.*deviceWidth=([0-9]*), deviceHeight=([0-9]*)\}", output)[0]2257            width = int(w)2258            height = int(h)2259        except (IndexError, ValueError), e:2260            _adapterLog('recvScreenSize: cannot read size from "%s"' %2261                        (output,))2262            raise FMBTAndroidError('cannot read screen size from dumpsys')2263        return width, height2264    def recvCurrentDisplayOrientation(self):2265        _, output, _ = self._runAdb(["shell", "dumpsys", "display"], 0,2266                                    timeout=_SHORT_TIMEOUT)2267        s = re.findall("mCurrentOrientation=([0-9])", output)2268        if s:2269            return int(s[0])2270        else:2271            return None2272    def recvDisplayPowered(self):2273        _, output, _ = self._runAdb(["shell", "dumpsys", "power"], 0,2274                                    timeout=_SHORT_TIMEOUT)2275        s = re.findall("Display Power: state=(OFF|ON)", output)2276        if s:2277            return s[0] == "ON"2278        else:2279            return None2280    def recvShowingLockscreen(self):2281        _, output, _ = self._runAdb(["shell", "dumpsys", "window"], 0,2282                                    timeout=_SHORT_TIMEOUT)2283        s = re.findall("mShowingLockscreen=(true|false)", output)2284        if s:2285            if s[0] == "true":2286                return True2287            else:2288                return False2289        else:2290            return None2291    def recvLastAccelerometer(self):2292        _, output, _ = self._runAdb(["shell", "dumpsys", "sensorservice"], 0,2293                                    timeout=_SHORT_TIMEOUT)2294        s = re.findall("3-axis Accelerometer.*last=<([- .0-9]*),([- .0-9]*),([- .0-9]*)>", output)2295        try:2296            rv = tuple([float(d) for d in s[0]])2297        except (IndexError, ValueError):2298            rv = (None, None, None)2299        return rv2300    def sendAcceleration(self, abc):2301        """abc is a tuple of 1, 2 or 3 floats, new accelerometer readings"""2302        try:2303            self._emulatorCommand("sensor set acceleration %s" %2304                                  (":".join([str(value) for value in abc]),))2305        except FMBTAndroidError, e:2306            raise FMBTAndroidError(2307                "accelerometer can be set only on emulator (%s)" % (e,))2308        return True2309    def sendAccelerometerRotation(self, value):2310        if value:2311            sendValue = "i:1"2312        else:2313            sendValue = "i:0"2314        try:2315            self._runAdb(["shell", "content", "insert",2316                          "--uri", "content://settings/system",2317                          "--bind", "name:s:accelerometer_rotation",2318                          "--bind", "value:" + sendValue],2319                         expectedExitStatus=0,2320                         timeout=_SHORT_TIMEOUT)2321        except Exception:2322            return False2323        return True2324    def recvAccelerometerRotation(self):2325        try:2326            _, output, _ = self._runAdb(2327                ["shell", "content", "query",2328                 "--uri", "content://settings/system/accelerometer_rotation"],2329                timeout=_SHORT_TIMEOUT)2330            s = re.findall("value=(.*)", output)[0]2331            return int(s) == 1 # True if accelerometer_rotation is enabled2332        except Exception:2333            return None2334    def sendDeviceLog(self, msg, priority, tag):2335        cmd = ["shell", "log", "-p", priority, "-t", tag, msg]2336        status, out, err = self._runAdb(cmd, [0, 124], timeout=_SHORT_TIMEOUT)2337        if status == 124:2338            errormsg = "log timeout: %s" % (["adb"] + cmd)2339            _adapterLog(errormsg)2340            raise FMBTAndroidError(errormsg)2341        return True2342    def sendUserRotation(self, rotation):2343        allowedRotations = [ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270]2344        if not rotation in allowedRotations:2345            raise ValueError("invalid rotation: %s, use one of %s" %2346                             (allowedRotations,))2347        sendValue = "i:%s" % (rotation,)2348        try:...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!!
