• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Lint as: python2, python3
2from __future__ import absolute_import
3from __future__ import division
4from __future__ import print_function
5
6import logging
7import os
8from six.moves import range
9import time
10
11from autotest_lib.client.common_lib import error
12from autotest_lib.client.common_lib import utils
13
14
15class UIScreenshoter(object):
16    """Simple class to take screenshots within the ui_utils framework."""
17
18    _SCREENSHOT_DIR_PATH = '/var/log/ui_utils'
19    _SCREENSHOT_BASENAME = 'ui-screenshot'
20
21    def __init__(self):
22        if not os.path.exists(self._SCREENSHOT_DIR_PATH):
23            os.mkdir(self._SCREENSHOT_DIR_PATH, 0o755)
24        self.screenshot_num = 0
25
26    def take_ss(self):
27        try:
28            utils.run('screenshot "{}/{}_iter{}.png"'.format(
29                      self._SCREENSHOT_DIR_PATH, self._SCREENSHOT_BASENAME,
30                      self.screenshot_num))
31            self.screenshot_num += 1
32        except Exception as e:
33            logging.warning('Unable to capture screenshot. %s', e)
34
35class UI_Handler(object):
36
37    REGEX_ALL = '/(.*?)/'
38
39    PROMISE_TEMPLATE = \
40        '''new Promise(function(resolve, reject) {
41            chrome.automation.getDesktop(function(root) {
42                    resolve(%s);
43                })
44            })'''
45
46    _GET_ON_SCREEN_ITEMS = "findAll({attributes:{role: 'staticText'},state:{" \
47                           "offscreen: false}}).map(node => node.name)"
48
49    def __init__(self):
50        self.screenshoter = UIScreenshoter()
51
52    def start_ui_root(self, cr):
53        """Start the UI root object for testing."""
54        self.ext = cr.autotest_ext
55
56    def is_obj_restricted(self, name, isRegex=False, role=None):
57        """
58        Return True if the object restriction is 'disabled'.
59        This usually means the button is either greyed out, locked, etc.
60
61        @param name: Parameter to provide to the 'name' attribute.
62        @param isRegex: bool, if the item is a regex.
63        @param role: Parameter to provide to the 'role' attribute.
64
65        """
66        FindParams = self._get_FindParams_str(name=name,
67                                              role=role,
68                                              isRegex=isRegex)
69        try:
70            restriction = self.ext.EvaluateJavaScript(
71                self.PROMISE_TEMPLATE % ("%s.restriction" % FindParams),
72                promise=True)
73        except Exception:
74            raise error.TestError(
75                'Could not find object {}.'.format(name))
76        if restriction == 'disabled':
77            return True
78        return False
79
80    def item_present(self, name, isRegex=False, flip=False, role=None):
81        """
82        Determines if an object is present on the screen
83
84        @param name: Parameter to provide to the 'name' attribute.
85        @param isRegex: bool, if the 'name' is a regex.
86        @param flip: Flips the return status.
87        @param role: Parameter to provide to the 'role' attribute.
88
89        @returns:
90            True if object is present and flip is False.
91            False if object is present and flip is True.
92            False if object is not present and flip is False.
93            True if object is not present and flip is True.
94
95        """
96        FindParams = self._get_FindParams_str(name=name,
97                                              role=role,
98                                              isRegex=isRegex)
99
100        if flip is True:
101            return not self._is_item_present(FindParams)
102        return self._is_item_present(FindParams)
103
104    def wait_for_ui_obj(self,
105                        name,
106                        isRegex=False,
107                        remove=False,
108                        role=None,
109                        timeout=10):
110        """
111        Waits for the UI object specified.
112
113        @param name: Parameter to provide to the 'name' attribute.
114        @param isRegex: bool, if the 'name' is a regex.
115        @param remove: bool, if you are waiting for the item to be removed.
116        @param role: Parameter to provide to the 'role' attribute.
117        @param timeout: int, time to wait for the item.
118
119        @raises error.TestError if the element is not loaded (or removed).
120
121        """
122        try:
123            utils.poll_for_condition(
124                condition=lambda: self.item_present(name=name,
125                                                    isRegex=isRegex,
126                                                    flip=remove,
127                                                    role=role),
128                timeout=timeout,
129                exception=error.TestError('{} did not load'
130                                          .format(name)))
131        except error.TestError:
132            self.screenshoter.take_ss()
133            logging.debug("CURRENT UI ITEMS VISIBLE {}".format(
134                self.list_screen_items()))
135            raise error.TestError('{} did not load'.format(name))
136
137    def did_obj_not_load(self, name, isRegex=False, timeout=5):
138        """
139        Specifically used to wait and see if an item appears on the UI.
140
141        NOTE: This is different from wait_for_ui_obj because that returns as
142        soon as the object is either loaded or not loaded. This function will
143        wait to ensure over the timeout period the object never loads.
144        Additionally it will return as soon as it does load. Basically a fancy
145        time.sleep()
146
147        @param name: Parameter to provide to the 'name' attribute.
148        @param isRegex: bool, if the item is a regex.
149        @param timeout: Time in seconds to wait for the object to appear.
150
151        @returns: True if object never loaded within the timeout period,
152            else False.
153
154        """
155        t1 = time.time()
156        while time.time() - t1 < timeout:
157            if self.item_present(name=name, isRegex=isRegex):
158                return False
159            time.sleep(1)
160        return True
161
162    def doDefault_on_obj(self, name, isRegex=False, role=None):
163        """Runs the .doDefault() js command on the element."""
164        FindParams = self._get_FindParams_str(name=name,
165                                              role=role,
166                                              isRegex=isRegex)
167        try:
168            self.ext.EvaluateJavaScript(
169                self.PROMISE_TEMPLATE % ("%s.doDefault()" % FindParams),
170                promise=True)
171        except:
172            logging.info('Unable to .doDefault() on {}. All items: {}'
173                         .format(FindParams, self.list_screen_items()))
174            raise error.TestError("doDefault failed on {}".format(FindParams))
175
176    def doCommand_on_obj(self, name, cmd, isRegex=False, role=None):
177        """Run the specified command on the element."""
178        FindParams = self._get_FindParams_str(name=name,
179                                              role=role,
180                                              isRegex=isRegex)
181        return self.ext.EvaluateJavaScript(self.PROMISE_TEMPLATE % """
182            %s.%s""" % (FindParams, cmd), promise=True)
183
184    def list_screen_items(self,
185                          role=None,
186                          name=None,
187                          isRegex=False,
188                          attr='name'):
189
190        """
191        List all the items currently visable on the screen.
192
193        If no paramters are given, it will return the name of each item,
194        including items with empty names.
195
196        @param role: The role of the items to use (ie button).
197        @param name: Parameter to provide to the 'name' attribute.
198        @param isRegex: bool, if the obj is a regex.
199        @param attr: Str, the attribute you want returned in the list
200            (eg 'name').
201
202        """
203
204        if isRegex:
205            if name is None:
206                raise error.TestError('If regex is True name must be given')
207            name = self._format_obj(name, isRegex)
208        elif name is not None:
209            name = self._format_obj(name, isRegex)
210        name = self.REGEX_ALL if name is None else name
211        role = self.REGEX_ALL if role is None else self._format_obj(role,
212                                                                    False)
213
214        new_promise = self.PROMISE_TEMPLATE % """root.findAll({attributes:
215            {name: %s, role: %s}}).map(node => node.%s)""" % (name, role, attr)
216        return self.ext.EvaluateJavaScript(new_promise, promise=True)
217
218    def get_name_role_list(self, name=None, role=None):
219        """
220        Return [{}, {}] containing the name/role of everything on screen.
221
222        """
223        name = self.REGEX_ALL if name is None else name
224        role = self.REGEX_ALL if role is None else self._format_obj(role,
225                                                                    False)
226
227        new_promise = self.PROMISE_TEMPLATE % """root.findAll({attributes:
228            {name: %s, role: %s}}).map(node =>
229            {return {name: node.name, role: node.role} })""" % (name, role)
230
231        return self.ext.EvaluateJavaScript(new_promise, promise=True)
232
233    def _format_obj(self, name, isRegex):
234        """
235        Formats the object for use in the javascript name attribute.
236
237        When searching for an element on the UI, a regex expression or string
238        can be used. If the search is using a string, the obj will need to be
239        wrapped in quotes. A Regex is not.
240
241        @param name: Parameter to provide to the 'name' attribute.
242        @param isRegex: if True, the object will be returned as is, if False
243            the obj will be returned wrapped in quotes.
244
245        @returns: The formatted string for regex/name.
246        """
247        if isRegex:
248            return name
249        else:
250            return '"{}"'.format(name)
251
252    def _get_FindParams_str(self, name, role, isRegex):
253        """Returns the FindParms string, so that automation node functions
254        can be run on it
255
256        @param role: The role of the items to use (ie button).
257        @param name: Parameter to provide to the 'name' attribute.
258        @param isRegex: bool, if the obj is a regex.
259
260        @returns: The ".find($FindParams)" string, which can be used to run
261            automation node commands, such as .doDefault()
262
263        """
264        FINDPARAMS_BASE = """
265        root.find({attributes:
266                  {name: %s,
267                   role: %s}}
268                 )"""
269
270        name = self._format_obj(name, isRegex)
271        if role is None:
272            role = self.REGEX_ALL
273        else:
274            role = self._format_obj(role, False)
275        return (FINDPARAMS_BASE % (name, role))
276
277    def _is_item_present(self, findParams):
278        """Return False if tempVar is None, else True."""
279        item_present = self.ext.EvaluateJavaScript(
280            self.PROMISE_TEMPLATE % findParams,
281            promise=True)
282        if item_present is None:
283            return False
284        return True
285
286    def click_and_wait_for_item_with_retries(self,
287                                             item_to_click,
288                                             item_to_wait_for,
289                                             isRegex_click=False,
290                                             isRegex_wait=False,
291                                             click_role=None,
292                                             wait_role=None):
293        """
294        Click on an item, and wait for a subsequent item to load. If the new
295        item does not load, we attempt to click the button again.
296
297        This being done to remove the corner case of button being visually
298        loaded, but not fully ready to be clicked yet. In simple terms:
299            Click button --> Check for next button to appear
300            IF button does not appear, its likely the original button click did
301            not work, thus reclick that button --> recheck for the next button.
302
303            If button did appear, stop clicking.
304
305        """
306        self.doDefault_on_obj(item_to_click,
307                              role=click_role,
308                              isRegex=isRegex_click)
309        for retry in range(3):
310            try:
311                self.wait_for_ui_obj(item_to_wait_for,
312                                     role=wait_role,
313                                     isRegex=isRegex_wait,
314                                     timeout=6)
315                break
316            except error.TestError:
317                self.doDefault_on_obj(item_to_click,
318                                      role=click_role,
319                                      isRegex=isRegex_click)
320        else:
321            raise error.TestError('Item {} did not load after 2 tries'.format(
322                                  item_to_wait_for))
323