1# Copyright (c) 2012 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import time
6
7from selenium.common.exceptions import NoSuchElementException
8from selenium.common.exceptions import TimeoutException as \
9    SeleniumTimeoutException
10from selenium.common.exceptions import WebDriverException
11from selenium.webdriver.support.ui import WebDriverWait
12
13class WebDriverCoreHelpers(object):
14    """Base class for manipulating web pages using webdriver."""
15
16    def __init__(self):
17        super(WebDriverCoreHelpers, self).__init__()
18        self.driver = None
19        self.wait = WebDriverWait(self.driver, timeout=5)
20
21
22    def _check_for_alert_in_message(self, message, alert_handler):
23        """Check for an alert in error message and handle it.
24
25        @param message: The error message.
26        @param alert_handler: The handler method to call.
27
28        """
29        if (message.find('An open modal dialog blocked') != -1 and
30            message.find('unexpected alert open') != -1):
31            alert = self.driver.switch_to_alert()
32            alert_handler(alert)
33        else:
34            raise RuntimeError(message)
35
36
37    def _handle_alert(self, xpath, alert_handler):
38        """Calls the alert handler if there is an alert.
39
40        @param xpath: The xpath that could raise the alert.
41        @param alert_handler: the handler method to call.
42
43        """
44        try:
45            self.driver.find_element_by_xpath(xpath)
46            return
47        except WebDriverException, e:
48            message = str(e)
49            # The messages differ based on the webdriver version
50            if (message.find('An open modal dialog blocked') == -1 and
51                message.find('unexpected alert open') == -1):
52                return
53            self._handler(alert_handler)
54        # Sometimes routers put out multiple alert statements on the same page.
55        self._handle_alert(xpath, alert_handler)
56
57
58    def _handler(self, alert_handler):
59        """Handles the alert.
60
61        @param alert_handler: The custom handler method to call.
62
63        """
64        alert = self.driver.switch_to_alert()
65        if not alert_handler:
66            # The caller did not provide us with a handler, dismiss and raise.
67            try:
68                alert_text = alert.text
69            except WebDriverException:
70                # There is a bug in selenium where the alert object will exist
71                # but you can't get to the text object right away.
72                time.sleep(1)
73            alert_text = alert.text
74            alert.accept()
75            raise RuntimeError('An alert was encountered and no handler was '
76                               'specified.  The text from the alert was: %s'
77                               % alert_text)
78        alert_handler(alert)
79
80
81    def set_wait_time(self, time):
82        """Sets the wait time of webdriver commands.
83
84        @param time: the time to wait in seconds.
85
86        """
87        self.wait = WebDriverWait(self.driver, timeout=time)
88
89
90    def restore_default_wait_time(self):
91        """Restores the default webdriver wait time."""
92        self.wait = WebDriverWait(self.driver, timeout=5)
93
94
95    def wait_for_objects_by_id(self, element_ids, wait_time=5):
96        """Wait for one of the element_ids to show up.
97
98        @param element_ids: A list of all the element ids to find.
99        @param wait_time: The time to wait before giving up.
100
101        @return The id that was found first.
102
103        """
104        xpaths = []
105        for element_id in element_ids:
106            xpaths.append('id("%s")' % element_id)
107        xpath_found = self.wait_for_objects_by_xpath(xpaths, wait_time)
108        for element_id in element_ids:
109            if element_id in xpath_found:
110                return element_id
111
112
113    def wait_for_objects_by_xpath(self, xpaths, wait_time=5):
114        """Wait for one of the items in the xpath to show up.
115
116        @param xpaths: A list of all the xpath's of elements to find.
117        @param wait_time: The time to wait before giving up.
118
119        @return The xpath that was found first.
120
121        """
122        excpetion = None
123        if wait_time < len(xpaths):
124            wait_time = len(xpaths)
125        start_time = int(time.time())
126        while (int(time.time()) - start_time) < wait_time:
127            for xpath in xpaths:
128                try:
129                    element = self.wait_for_object_by_xpath(xpath,
130                                                            wait_time=0.25)
131                    if element and element.is_displayed():
132                        return xpath
133                except SeleniumTimeoutException, e:
134                    exception = str(e)
135                    pass
136        raise SeleniumTimeoutException(exception)
137
138
139    def click_button_by_id(self, element_id, alert_handler=None):
140        """Clicks a button by id.
141
142        @param element_id: the id of the button
143        @param alert_handler: method invoked if an alert is detected. The method
144                              must take one parameter, a webdriver alert object
145
146        """
147        xpath = 'id("%s")' % element_id
148        return self.click_button_by_xpath(xpath, alert_handler)
149
150
151    def click_button_by_xpath(self, xpath, alert_handler=None):
152        """Clicks a button by xpath.
153
154        @param xpath: the xpath of the button
155        @param alert_handler: method invoked if an alert is detected. The method
156                              must take one parameter, a webdriver alert object
157
158        """
159        button = self.wait_for_object_by_xpath(xpath)
160        button.click()
161        self._handle_alert(xpath, alert_handler)
162
163
164    def get_url(self, page_url, page_title=None, element_xpath=None):
165        """Load page and check if the page loads completely, if not, reload.
166
167        @param page_url: The url to load.
168        @param page_title: The complete/partial title of the page after loaded.
169        @param element_xpath: The element that we search for to confirm that
170                              the page loaded.
171
172        """
173        self.driver.get(page_url)
174        if page_title:
175            try:
176                self.wait.until(lambda _: page_title in self.driver.title)
177            except SeleniumTimeoutException, e:
178                self.driver.get(page_url)
179                self.wait.until(lambda _: self.driver.title)
180            finally:
181                if not page_title in self.driver.title:
182                    raise WebDriverException('Page did not load. Expected %s in'
183                                             'title, but got %s as title.' %
184                                             (page_title, self.driver.title))
185        if element_xpath:
186            self.wait_for_object_by_xpath(element_xpath)
187
188
189    def wait_for_object_by_id(self, element_id, wait_time=5):
190        """Waits for an element to become available; returns a reference to it.
191
192        @param element_id: the id of the element to wait for
193        @param wait_time: the time to wait for the object
194
195        @returns a reference to the element if found before a timeout.
196
197        """
198        xpath = 'id("%s")' % element_id
199        return self.wait_for_object_by_xpath(xpath, wait_time=wait_time)
200
201
202    def wait_for_object_by_xpath_to_vanish(self, xpath, wait_time=5):
203        """Wait for the item in xpath to disappear from page.
204
205        @param xpath: The xpath of the object to wait on.
206        @param wait_time: The time to wait before giving up.
207
208        @return void or raise exception if object does not vanish.
209
210        """
211        start_time = int(time.time())
212        while (int(time.time()) - start_time) < wait_time:
213            if self.object_by_xpath_exist(xpath):
214                time.sleep(0.5)
215            else:
216                return
217        raise SeleniumTimeoutException('The object with xpath %s failed to'
218                                       ' vanish.' % xpath)
219
220
221    def wait_for_object_by_id_to_vanish(self, element_id, wait_time=5):
222        """Wait for the item in xpath to disappear from page.
223
224        @param element_id: The id of the object to wait on.
225        @param wait_time: The time to wait before giving up.
226
227        @return void or raise exception if object does not vanish.
228
229        """
230        xpath = 'id("%s")' % element_id
231        return self.wait_for_object_by_xpath_to_vanish(xpath,
232                                                       wait_time=wait_time)
233
234
235    def object_by_id_exist(self, element_id):
236        """Finds if an object exist in this particular page.
237
238        @param element_id: the id of the element to find
239
240        @returns True if the element exists. False if the element does not.
241
242        """
243        xpath = 'id("%s")' % element_id
244        return self.object_by_xpath_exist(xpath)
245
246
247    def object_by_xpath_exist(self, xpath):
248        """Finds if an object exist in this particular page.
249
250        @param xpath: the xpath of the element to find
251
252        @returns True if the xpath exists. False if the xpath does not.
253
254        """
255        try:
256            self.wait_for_object_by_xpath(xpath)
257        except SeleniumTimeoutException:
258            return False
259        return True
260
261
262    def wait_for_object_by_xpath(self, xpath, wait_time=5):
263        """Waits for an element to become available; returns a reference to it.
264
265        @param xpath: the xpath of the element to wait for
266        @param wait_time: the time to wait for the object.
267
268        @returns reference to the element if found before a timeout.
269
270        """
271        self.set_wait_time(wait_time)
272        try:
273            self.wait.until(lambda _: self.driver.find_element_by_xpath(xpath))
274            element = self.driver.find_element_by_xpath(xpath)
275        except (SeleniumTimeoutException, NoSuchElementException) as e:
276            self.restore_default_wait_time()
277            raise SeleniumTimeoutException('Unable to find the object by '
278                                           'xpath: %s\n WebDriver exception: '
279                                           '%s' % (xpath, str(e)))
280        self.restore_default_wait_time()
281        return element
282
283
284    def item_in_popup_by_id_exist(self, item, element_id):
285        """Returns if an item exists in a popup given a id
286
287        @param item: name of the item
288        @param element_id: the id of the popup
289
290        @returns True if the item exists; False otherwise.
291
292        """
293        xpath = 'id("%s")' % element_id
294        return self.item_in_popup_by_xpath_exist(item, xpath)
295
296
297    def item_in_popup_by_xpath_exist(self, item, xpath):
298        """Returns if an item exists in a popup given an xpath
299
300        @param item: name of the item
301        @param xpath: the xpath of the popup
302
303        @returns True if the item exists; False otherwise.
304
305        """
306        if self.number_of_items_in_popup_by_xpath(xpath) == 0:
307            raise SeleniumTimeoutException('The popup at xpath %s has no items.'
308                                           % xpath)
309        popup = self.driver.find_element_by_xpath(xpath)
310        for option in popup.find_elements_by_tag_name('option'):
311            if option.text == item:
312                return True
313        return False
314
315
316    def number_of_items_in_popup_by_id(self, element_id, alert_handler=None):
317        """Returns the number of items in a popup given the element ID.
318
319        @param element_id: the html ID of the item
320        @param alert_handler: method invoked if an alert is detected. The method
321                              must take one parameter, a webdriver alert object
322
323        @returns the number of items in the popup.
324
325        """
326        xpath = 'id("%s")' % element_id
327        return self.number_of_items_in_popup_by_xpath(xpath, alert_handler)
328
329
330    def number_of_items_in_popup_by_xpath(self, xpath, alert_handler=None):
331        """Returns the number of items in a popup given a xpath
332
333        @param xpath: the xpath of the popup
334        @param alert_handler: method invoked if an alert is detected. The method
335                         must take one parameter, a webdriver alert object
336
337        @returns the number of items in the popup.
338
339        """
340        popup = self.driver.find_element_by_xpath(xpath)
341        try:
342            self.wait.until(lambda _:
343                            len(popup.find_elements_by_tag_name('option')))
344        except SeleniumTimeoutException, e:
345            return 0
346        return len(popup.find_elements_by_tag_name('option'))
347
348
349    def select_item_from_popup_by_id(self, item, element_id,
350                                     wait_for_xpath=None, alert_handler=None):
351        """Selects an item from a popup, by passing the element ID.
352
353        @param item: the string of the item to select from the popup
354        @param element_id: the html ID of the item
355        @param wait_for_xpath: an item to wait for before returning, if not
356                               specified the method does not wait.
357        @param alert_handler: method invoked if an alert is detected. The method
358                              must take one parameter, a webdriver alert object
359
360        """
361        xpath = 'id("%s")' % element_id
362        self.select_item_from_popup_by_xpath(item, xpath, wait_for_xpath,
363                                             alert_handler)
364
365
366    def select_item_from_popup_by_xpath(self, item, xpath, wait_for_xpath=None,
367                                        alert_handler=None):
368        """Selects an item from a popup, by passing the xpath of the popup.
369
370        @param item: the string of the item to select from the popup
371        @param xpath: the xpath of the popup
372        @param wait_for_xpath: an item to wait for before returning, if not
373                               specified the method does not wait.
374        @param alert_handler: method invoked if an alert is detected. The method
375                              must take one parameter, a webdriver alert object
376
377        """
378        if self.number_of_items_in_popup_by_xpath(xpath) == 0:
379            raise SeleniumTimeoutException('The popup at xpath %s has no items.'
380                                           % xpath)
381        if not self.item_in_popup_by_xpath_exist(item, xpath):
382            raise SeleniumTimeoutException('The popup at xpath %s does not '
383                                           'contain the item %s.' % (xpath,
384                                           item))
385        popup = self.driver.find_element_by_xpath(xpath)
386        for option in popup.find_elements_by_tag_name('option'):
387            if option.text == item:
388                option.click()
389                break
390        self._handle_alert(xpath, alert_handler)
391        if wait_for_xpath:
392            self.wait_for_object_by_xpath(wait_for_xpath)
393
394
395    def set_content_of_text_field_by_id(self, content, text_field_id,
396                                        wait_for_xpath=None,
397                                        abort_check=False):
398        """Sets the content of a textfield, by passing the element ID.
399
400        @param content: the content to apply to the textfield
401        @param text_field_id: the html ID of the textfield
402        @param wait_for_xpath: an item to wait for before returning, if not
403                               specified the method does not wait.
404
405        """
406        xpath = 'id("%s")' % text_field_id
407        self.set_content_of_text_field_by_xpath(content, xpath,
408                                                wait_for_xpath=wait_for_xpath,
409                                                abort_check=abort_check)
410
411
412    def set_content_of_text_field_by_xpath(self, content, xpath,
413                                           wait_for_xpath=None,
414                                           abort_check=False):
415        """Sets the content of a textfield, by passing the xpath.
416
417        @param content: the content to apply to the textfield
418        @param xpath: the xpath of the textfield
419        @param wait_for_xpath: an item to wait for before returning, if not
420                               specified the method does not wait.
421        @param abort_check: do not get the current value before setting
422
423        """
424        # When we can get the value we know the text field is ready.
425        text_field = self.driver.find_element_by_xpath(xpath)
426        if text_field.get_attribute('type') != 'password' and not abort_check:
427            try:
428                self.wait.until(lambda _: text_field.get_attribute('value'))
429            except SeleniumTimeoutException, e:
430                raise SeleniumTimeoutException('Unable to obtain the value of '
431                                               'the text field %s.\nWebDriver '
432                                               'exception:%s' % (xpath, str(e)))
433        text_field.clear()
434        text_field.send_keys(content)
435        if wait_for_xpath: self.wait_for_object_by_xpath(wait_for_xpath)
436
437
438    def set_check_box_selected_by_id(self, check_box_id, selected=True,
439                                     wait_for_xpath=None, alert_handler=None):
440        """Sets the state of a checkbox, by passing the ID.
441
442        @param check_box_id: the html id of the checkbox
443        @param selected: True to check the checkbox; False to uncheck it
444        @param wait_for_xpath: an item to wait for before returning, if not
445                               specified the method does not wait.
446        @param alert_handler: method invoked if an alert is detected. The method
447                              must take one parameter, a webdriver alert object
448
449        """
450        xpath = 'id("%s")' % check_box_id
451        self.set_check_box_selected_by_xpath(xpath, selected, wait_for_xpath,
452                                             alert_handler)
453
454
455    def set_check_box_selected_by_xpath(self, xpath, selected=True,
456                                        wait_for_xpath=None,
457                                        alert_handler=None):
458        """Sets the state of a checkbox, by passing the xpath.
459
460        @param xpath: the xpath of the checkbox
461        @param selected: True to check the checkbox; False to uncheck it
462        @param wait_for_xpath: an item to wait for before returning, if not
463                               specified the method does not wait.
464        @param alert_handler: method invoked if an alert is detected. The method
465                              must take one parameter, a webdriver alert object
466        """
467        check_box = self.wait_for_object_by_xpath(xpath)
468        value = check_box.get_attribute('value')
469        if (value == '1' and not selected) or (value == '0' and selected):
470            check_box.click()
471        self._handle_alert(xpath, alert_handler)
472        if wait_for_xpath:
473            self.wait_for_object_by_xpath(wait_for_xpath)
474