| Index: build/android/devil/android/app_ui.py
|
| diff --git a/build/android/devil/android/app_ui.py b/build/android/devil/android/app_ui.py
|
| deleted file mode 100644
|
| index 20a0bf34b4509bcc45e0c3f601c7641439e4e293..0000000000000000000000000000000000000000
|
| --- a/build/android/devil/android/app_ui.py
|
| +++ /dev/null
|
| @@ -1,212 +0,0 @@
|
| -# Copyright 2015 The Chromium Authors. All rights reserved.
|
| -# Use of this source code is governed by a BSD-style license that can be
|
| -# found in the LICENSE file.
|
| -
|
| -"""Provides functionality to interact with UI elements of an Android app."""
|
| -
|
| -import re
|
| -from xml.etree import ElementTree as element_tree
|
| -
|
| -from devil.android import decorators
|
| -from devil.android import device_temp_file
|
| -from devil.utils import geometry
|
| -from devil.utils import timeout_retry
|
| -
|
| -_DEFAULT_SHORT_TIMEOUT = 10
|
| -_DEFAULT_SHORT_RETRIES = 3
|
| -_DEFAULT_LONG_TIMEOUT = 30
|
| -_DEFAULT_LONG_RETRIES = 0
|
| -
|
| -# Parse rectangle bounds given as: '[left,top][right,bottom]'.
|
| -_RE_BOUNDS = re.compile(
|
| - r'\[(?P<left>\d+),(?P<top>\d+)\]\[(?P<right>\d+),(?P<bottom>\d+)\]')
|
| -
|
| -
|
| -class _UiNode(object):
|
| - def __init__(self, device, xml_node, package=None):
|
| - """Object to interact with a UI node from an xml snapshot.
|
| -
|
| - Note: there is usually no need to call this constructor directly. Instead,
|
| - use an AppUi object (below) to grab an xml screenshot from a device and
|
| - find nodes in it.
|
| -
|
| - Args:
|
| - device: A device_utils.DeviceUtils instance.
|
| - xml_node: An ElementTree instance of the node to interact with.
|
| - package: An optional package name for the app owning this node.
|
| - """
|
| - self._device = device
|
| - self._xml_node = xml_node
|
| - self._package = package
|
| -
|
| - def _GetAttribute(self, key):
|
| - """Get the value of an attribute of this node."""
|
| - return self._xml_node.attrib.get(key)
|
| -
|
| - @property
|
| - def bounds(self):
|
| - """Get a rectangle with the bounds of this UI node.
|
| -
|
| - Returns:
|
| - A geometry.Rectangle instance.
|
| - """
|
| - d = _RE_BOUNDS.match(self._GetAttribute('bounds')).groupdict()
|
| - return geometry.Rectangle.FromDict({k: int(v) for k, v in d.iteritems()})
|
| -
|
| - def Tap(self, point=None, dp_units=False):
|
| - """Send a tap event to the UI node.
|
| -
|
| - Args:
|
| - point: An optional geometry.Point instance indicating the location to
|
| - tap, relative to the bounds of the UI node, i.e. (0, 0) taps the
|
| - top-left corner. If ommited, the center of the node is tapped.
|
| - dp_units: If True, indicates that the coordinates of the point are given
|
| - in device-independent pixels; otherwise they are assumed to be "real"
|
| - pixels. This option has no effect when the point is ommited.
|
| - """
|
| - if point is None:
|
| - point = self.bounds.center
|
| - else:
|
| - if dp_units:
|
| - point = (float(self._device.pixel_density) / 160) * point
|
| - point += self.bounds.top_left
|
| -
|
| - x, y = (str(int(v)) for v in point)
|
| - self._device.RunShellCommand(['input', 'tap', x, y], check_return=True)
|
| -
|
| - def __getitem__(self, key):
|
| - """Retrieve a child of this node by its index.
|
| -
|
| - Args:
|
| - key: An integer with the index of the child to retrieve.
|
| - Returns:
|
| - A UI node instance of the selected child.
|
| - Raises:
|
| - IndexError if the index is out of range.
|
| - """
|
| - return type(self)(self._device, self._xml_node[key], package=self._package)
|
| -
|
| - def _Find(self, **kwargs):
|
| - """Find the first descendant node that matches a given criteria.
|
| -
|
| - Note: clients would usually call AppUi.GetUiNode or AppUi.WaitForUiNode
|
| - instead.
|
| -
|
| - For example:
|
| -
|
| - app = app_ui.AppUi(device, package='org.my.app')
|
| - app.GetUiNode(resource_id='some_element', text='hello')
|
| -
|
| - would retrieve the first matching node with both of the xml attributes:
|
| -
|
| - resource-id='org.my.app:id/some_element'
|
| - text='hello'
|
| -
|
| - As the example shows, if given and needed, the value of the resource_id key
|
| - is auto-completed with the package name specified in the AppUi constructor.
|
| -
|
| - Args:
|
| - Arguments are specified as key-value pairs, where keys correnspond to
|
| - attribute names in xml nodes (replacing any '-' with '_' to make them
|
| - valid identifiers). At least one argument must be supplied, and arguments
|
| - with a None value are ignored.
|
| - Returns:
|
| - A UI node instance of the first descendant node that matches ALL the
|
| - given key-value criteria; or None if no such node is found.
|
| - Raises:
|
| - TypeError if no search arguments are provided.
|
| - """
|
| - matches_criteria = self._NodeMatcher(kwargs)
|
| - for node in self._xml_node.iter():
|
| - if matches_criteria(node):
|
| - return type(self)(self._device, node, package=self._package)
|
| - return None
|
| -
|
| - def _NodeMatcher(self, kwargs):
|
| - # Auto-complete resource-id's using the package name if available.
|
| - resource_id = kwargs.get('resource_id')
|
| - if (resource_id is not None
|
| - and self._package is not None
|
| - and ':id/' not in resource_id):
|
| - kwargs['resource_id'] = '%s:id/%s' % (self._package, resource_id)
|
| -
|
| - criteria = [(k.replace('_', '-'), v)
|
| - for k, v in kwargs.iteritems()
|
| - if v is not None]
|
| - if not criteria:
|
| - raise TypeError('At least one search criteria should be specified')
|
| - return lambda node: all(node.get(k) == v for k, v in criteria)
|
| -
|
| -
|
| -class AppUi(object):
|
| - # timeout and retry arguments appear unused, but are handled by decorator.
|
| - # pylint: disable=unused-argument
|
| -
|
| - def __init__(self, device, package=None):
|
| - """Object to interact with the UI of an Android app.
|
| -
|
| - Args:
|
| - device: A device_utils.DeviceUtils instance.
|
| - package: An optional package name for the app.
|
| - """
|
| - self._device = device
|
| - self._package = package
|
| -
|
| - @property
|
| - def package(self):
|
| - return self._package
|
| -
|
| - @decorators.WithTimeoutAndRetriesDefaults(_DEFAULT_SHORT_TIMEOUT,
|
| - _DEFAULT_SHORT_RETRIES)
|
| - def _GetRootUiNode(self, timeout=None, retries=None):
|
| - """Get a node pointing to the root of the UI nodes on screen.
|
| -
|
| - Note: This is currently implemented via adb calls to uiatomator and it
|
| - is *slow*, ~2 secs per call. Do not rely on low-level implementation
|
| - details that may change in the future.
|
| -
|
| - TODO(crbug.com/567217): Swap to a more efficient implementation.
|
| -
|
| - Args:
|
| - timeout: A number of seconds to wait for the uiautomator dump.
|
| - retries: Number of times to retry if the adb command fails.
|
| - Returns:
|
| - A UI node instance pointing to the root of the xml screenshot.
|
| - """
|
| - with device_temp_file.DeviceTempFile(self._device.adb) as dtemp:
|
| - self._device.RunShellCommand(['uiautomator', 'dump', dtemp.name],
|
| - check_return=True)
|
| - xml_node = element_tree.fromstring(
|
| - self._device.ReadFile(dtemp.name, force_pull=True))
|
| - return _UiNode(self._device, xml_node, package=self._package)
|
| -
|
| - def GetUiNode(self, **kwargs):
|
| - """Get the first node found matching a specified criteria.
|
| -
|
| - Args:
|
| - See _UiNode._Find.
|
| - Returns:
|
| - A UI node instance of the node if found, otherwise None.
|
| - """
|
| - # pylint: disable=protected-access
|
| - return self._GetRootUiNode()._Find(**kwargs)
|
| -
|
| - @decorators.WithTimeoutAndRetriesDefaults(_DEFAULT_LONG_TIMEOUT,
|
| - _DEFAULT_LONG_RETRIES)
|
| - def WaitForUiNode(self, timeout=None, retries=None, **kwargs):
|
| - """Wait for a node matching a given criteria to appear on the screen.
|
| -
|
| - Args:
|
| - timeout: A number of seconds to wait for the matching node to appear.
|
| - retries: Number of times to retry in case of adb command errors.
|
| - For other args, to specify the search criteria, see _UiNode._Find.
|
| - Returns:
|
| - The UI node instance found.
|
| - Raises:
|
| - device_errors.CommandTimeoutError if the node is not found before the
|
| - timeout.
|
| - """
|
| - def node_found():
|
| - return self.GetUiNode(**kwargs)
|
| -
|
| - return timeout_retry.WaitFor(node_found)
|
|
|