Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / chrome / test / functional / webdriver_pages / settings.py
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
5 import types
6
7 import selenium.common.exceptions
8 from selenium.webdriver.common.action_chains import ActionChains
9 from selenium.webdriver.support.ui import WebDriverWait
10
11
12 def _FocusField(driver, list_elem, field_elem):
13   """Focuses a field in a dynamic list.
14
15   Note, the item containing the field should not be focused already.
16
17   Typing into a field is tricky because the js automatically focuses and
18   selects the text field after 50ms after it first receives focus. This
19   method focuses the field and waits for the timeout to occur.
20   For more info, see inline_editable_list.js and search for setTimeout.
21   See crbug.com/97369.
22
23   Args:
24     list_elem: An element in the HTML list.
25     field_elem: An element in the HTML text field.
26
27   Raises:
28     RuntimeError: If a timeout occurs when waiting for the focus event.
29   """
30   # To wait properly for the focus, we focus the last text field, and then
31   # add a focus listener to it, so that we return when the element is focused
32   # again after the timeout. We have to focus a different element in between
33   # these steps, otherwise the focus event will not fire since the element
34   # already has focus.
35   # Ideally this should be fixed in the page.
36
37   correct_focus_script = """
38       (function(listElem, itemElem, callback) {
39         if (document.activeElement == itemElem) {
40           callback();
41           return;
42         }
43         itemElem.focus();
44         listElem.focus();
45         itemElem.addEventListener("focus", callback);
46       }).apply(null, arguments);
47   """
48   driver.set_script_timeout(5)
49   try:
50     driver.execute_async_script(correct_focus_script, list_elem, field_elem)
51   except selenium.common.exceptions.TimeoutException:
52     raise RuntimeError('Unable to focus list item ' + field_elem.tag_name)
53
54
55 class Item(object):
56   """A list item web element."""
57   def __init__(self, elem):
58     self._elem = elem
59
60   def Remove(self, driver):
61     button = self._elem.find_element_by_xpath('./button')
62     ActionChains(driver).move_to_element(button).click().perform()
63
64
65 class TextFieldsItem(Item):
66   """An item consisting only of text fields."""
67   def _GetFields(self):
68     """Returns the text fields list."""
69     return self._elem.find_elements_by_tag_name('input')
70
71   def Set(self, values):
72     """Sets the value(s) of the item's text field(s).
73
74     Args:
75       values: The new value or the list of the new values of the fields.
76     """
77     field_list = self._GetFields()
78     if len(field_list) > 1:
79       assert type(values) == types.ListType, \
80           """The values must be a list for a HTML list that has multi-field
81           items. '%s' should be in a list.""" % values
82       value_list = values
83     else:
84       value_list = [values]
85
86     assert len(field_list) == len(value_list), \
87         """The item to be added must have the same number of fields as an item
88         in the HTML list. Given item '%s' should have %s fields.""" % (
89             value_list, len(field_list))
90     for field, value in zip(field_list, value_list):
91       field.clear()
92       field.send_keys(value)
93     field_list[-1].send_keys('\n') # press enter on the last field.
94
95   def Get(self):
96     """Returns the list of the text field values."""
97     return map(lambda f: f.get_attribute('value'), self._GetFields())
98
99
100 class TextField(object):
101   """A text field web element."""
102   def __init__(self, elem):
103     self._elem = elem
104
105   def Set(self, value):
106     """Sets the value of the text field.
107
108     Args:
109       value: The new value of the field.
110     """
111     self._elem.clear()
112     self._elem.send_keys(value)
113
114   def Get(self):
115     """Returns the value of the text field."""
116     return self._elem.get_attribute('value')
117
118
119 class List(object):
120   """A web element that holds a list of items."""
121
122   def __init__(self, driver, elem, item_class=Item):
123     """item element is an element in the HTML list.
124     item class is the class of item the list holds."""
125     self._driver = driver
126     self._elem = elem
127     self._item_class = item_class
128
129   def RemoveAll(self):
130     """Removes all items from the list.
131
132     In the loop the removal of an elem renders the remaining elems of the list
133     invalid. After each item is removed, GetItems() is called.
134     """
135     for i in range(len(self.GetItems())):
136       self.GetItems()[0].Remove(self._driver)
137
138   def GetItems(self):
139     """Returns all the items that are in the list."""
140     items = self._GetItemElems()
141     return map(lambda x: self._item_class(x), items)
142
143   def GetSize(self):
144     """Returns the number of items in the list."""
145     return len(self._GetItemElems())
146
147   def _GetItemElems(self):
148     return self._elem.find_elements_by_xpath('.//*[@role="listitem"]')
149
150
151 class DynamicList(List):
152   """A web element that holds a dynamic list of items of text fields.
153
154   Terminology:
155     item element: an element in the HTML list item.
156     item_class: the class of item the list holds
157     placeholder: the last item element in the list, which is not committed yet
158
159   The user can add new items to the list by typing in the placeholder item.
160   When a user presses enter or focuses something else, the placeholder item
161   is committed and a new placeholder is created. An item may contain 1 or
162   more text fields.
163   """
164
165   def __init__(self, driver, elem, item_class=TextFieldsItem):
166     return super(DynamicList, self).__init__(
167         driver, elem, item_class=item_class)
168
169   def GetPlaceholderItem(self):
170     return self.GetItems()[-1]
171
172   def GetCommittedItems(self):
173     """Returns all the items that are in the list, except the placeholder."""
174     return map(lambda x: self._item_class(x), self._GetCommittedItemElems())
175
176   def GetSize(self):
177     """Returns the number of items in the list, excluding the placeholder."""
178     return len(self._GetCommittedItemElems())
179
180   def _GetCommittedItemElems(self):
181     return self._GetItemElems()[:-1]
182
183   def _GetPlaceholderElem(self):
184     return self._GetItemElems()[-1]
185
186
187 class AutofillEditAddressDialog(object):
188   """The overlay for editing an autofill address."""
189
190   _URL = 'chrome://settings-frame/autofillEditAddress'
191
192   @staticmethod
193   def FromNavigation(driver):
194     """Creates an instance of the dialog by navigating directly to it."""
195     driver.get(AutofillEditAddressDialog._URL)
196     return AutofillEditAddressDialog(driver)
197
198   def __init__(self, driver):
199     self.driver = driver
200     assert self._URL == driver.current_url
201     self.dialog_elem = driver.find_element_by_id(
202         'autofill-edit-address-overlay')
203
204   def Fill(self, names=None, addr_line_1=None, city=None, state=None,
205            postal_code=None, country_code=None, phones=None):
206     """Fills in the given data into the appropriate fields.
207
208     If filling into a text field, the given value will replace the current one.
209     If filling into a list, the values will be added after all items are
210     deleted.
211
212     Note: 'names', in the new autofill UI, is an array of full names. A full
213       name is an array of first, middle, last names. Example:
214         names=[['Joe', '', 'King'], ['Fred', 'W', 'Michno']]
215
216     Args:
217       names: List of names; each name should be [first, middle, last].
218       addr_line_1: First line in the address.
219       city: City.
220       state: State.
221       postal_code: Postal code (zip code for US).
222       country_code: Country code (e.g., US or FR).
223       phones: List of phone numbers.
224     """
225     id_dict = {'addr-line-1': addr_line_1,
226                'city': city,
227                'state': state,
228                'postal-code': postal_code}
229     for id, value in id_dict.items():
230       if value is not None:
231         TextField(self.dialog_elem.find_element_by_id(id)).Set(value)
232
233     list_id_dict = {'full-name-list': names,
234                     'phone-list': phones}
235     for list_id, values in list_id_dict.items():
236       if values is not None:
237         list = DynamicList(self.driver,
238                            self.dialog_elem.find_element_by_id(list_id))
239         list.RemoveAll()
240         for value in values:
241           list.GetPlaceholderItem().Set(value)
242
243     if country_code is not None:
244       self.dialog_elem.find_element_by_xpath(
245           './/*[@id="country"]/*[@value="%s"]' % country_code).click()
246
247   def GetStateLabel(self):
248     """Returns the label used for the state text field."""
249     return self.dialog_elem.find_element_by_id('state-label').text
250
251   def GetPostalCodeLabel(self):
252     """Returns the label used for the postal code text field."""
253     return self.dialog_elem.find_element_by_id('postal-code-label').text
254
255   def GetPhones(self):
256     """Returns a list of the phone numbers in the phones list."""
257     list = DynamicList(
258         self.driver, self.dialog_elem.find_element_by_id('phone-list'))
259     return [item.Get()[0] for item in list.GetCommittedItems()]
260
261
262 class ContentTypes(object):
263   COOKIES = 'cookies'
264   IMAGES = 'images'
265   JAVASCRIPT = 'javascript'
266   HANDLERS = 'handlers'
267   PLUGINS = 'plugins'
268   POPUPS = 'popups'
269   GEOLOCATION = 'location'
270   NOTIFICATIONS = 'notifications'
271   PASSWORDS = 'passwords'
272
273
274 class Behaviors(object):
275   ALLOW = 'allow'
276   SESSION_ONLY = 'session_only'
277   ASK = 'ask'
278   BLOCK = 'block'
279
280
281 class ContentSettingsPage(object):
282   """The overlay for managing exceptions on the Content Settings page."""
283
284   _URL = 'chrome://settings-frame/content'
285
286   @staticmethod
287   def FromNavigation(driver):
288     """Creates an instance of the dialog by navigating directly to it."""
289     driver.get(ContentSettingsPage._URL)
290     return ContentSettingsPage(driver)
291
292   def __init__(self, driver):
293     assert self._URL == driver.current_url
294     self.page_elem = driver.find_element_by_id(
295         'content-settings-page')
296
297   def SetContentTypeOption(self, content_type, option):
298     """Set the option for the specified content type.
299
300     Args:
301       content_type: The content type to manage.
302       option: The option to allow, deny or ask.
303     """
304     self.page_elem.find_element_by_xpath(
305         './/*[@name="%s"][@value="%s"]' % (content_type, option)).click()
306
307
308 class ManageExceptionsPage(object):
309   """The overlay for the content exceptions page."""
310
311   @staticmethod
312   def FromNavigation(driver, content_type):
313     """Creates an instance of the dialog by navigating directly to it.
314
315     Args:
316       driver: The remote WebDriver instance to manage some content type.
317       content_type: The content type to manage.
318     """
319     content_url = 'chrome://settings-frame/contentExceptions#%s' % content_type
320     driver.get(content_url)
321     return ManageExceptionsPage(driver, content_type)
322
323   def __init__(self, driver, content_type):
324     self._list_elem = driver.find_element_by_xpath(
325         './/*[@id="content-settings-exceptions-area"]'
326         '//*[@contenttype="%s"]//list[@role="list"]'
327         '[@class="settings-list"]' % content_type)
328     self._driver = driver
329     self._content_type = content_type
330     try:
331       self._incognito_list_elem = driver.find_element_by_xpath(
332           './/*[@id="content-settings-exceptions-area"]'
333           '//*[@contenttype="%s"]//div[not(@hidden)]'
334           '//list[@mode="otr"][@role="list"]'
335           '[@class="settings-list"]' % content_type)
336     except selenium.common.exceptions.NoSuchElementException:
337       self._incognito_list_elem = None
338
339   def _AssertIncognitoAvailable(self):
340     if not self._incognito_list_elem:
341       raise AssertionError(
342           'Incognito settings in "%s" content page not available'
343           % self._content_type)
344
345   def _GetExceptionList(self, incognito):
346     if not incognito:
347       list_elem = self._list_elem
348     else:
349       list_elem = self._incognito_list_elem
350     return DynamicList(self._driver, list_elem)
351
352   def _GetPatternList(self, incognito):
353     if not incognito:
354       list_elem = self._list_elem
355     else:
356       list_elem = self._incognito_list_elem
357     pattern_list = [p.text for p in
358         list_elem.find_elements_by_xpath(
359             './/*[contains(@class, "exception-pattern")]'
360             '//*[@class="static-text"]')]
361     return pattern_list
362
363   def AddNewException(self, pattern, behavior, incognito=False):
364     """Add a new pattern and behavior to the Exceptions page.
365
366     Args:
367       pattern: Hostname pattern string.
368       behavior: Setting for the hostname pattern (Allow, Block, Session Only).
369       incognito: Incognito list box. Display to false.
370
371     Raises:
372       AssertionError when an exception cannot be added on the content page.
373     """
374     if incognito:
375       self._AssertIncognitoAvailable()
376       list_elem = self._incognito_list_elem
377     else:
378       list_elem = self._list_elem
379     # Select behavior first.
380     try:
381       list_elem.find_element_by_xpath(
382           './/*[@class="exception-setting"]'
383           '[not(@displaymode)]//option[@value="%s"]'
384              % behavior).click()
385     except selenium.common.exceptions.NoSuchElementException:
386       raise AssertionError(
387           'Adding new exception not allowed in "%s" content page'
388           % self._content_type)
389     # Set pattern now.
390     self._GetExceptionList(incognito).GetPlaceholderItem().Set(pattern)
391
392   def DeleteException(self, pattern, incognito=False):
393     """Delete the exception for the selected hostname pattern.
394
395     Args:
396       pattern: Hostname pattern string.
397       incognito: Incognito list box. Default to false.
398     """
399     if incognito:
400       self._AssertIncognitoAvailable()
401     list = self._GetExceptionList(incognito)
402     items = filter(lambda item: item.Get()[0] == pattern,
403                    list.GetComittedItems())
404     map(lambda item: item.Remove(self._driver), items)
405
406   def GetExceptions(self, incognito=False):
407     """Returns a dictionary of {pattern: behavior}.
408
409     Example: {'file:///*': 'block'}
410
411     Args:
412       incognito: Incognito list box. Default to false.
413     """
414     if incognito:
415       self._AssertIncognitoAvailable()
416       list_elem = self._incognito_list_elem
417     else:
418       list_elem = self._list_elem
419     pattern_list = self._GetPatternList(incognito)
420     behavior_list = list_elem.find_elements_by_xpath(
421         './/*[@role="listitem"][@class="deletable-item"]'
422         '//*[@class="exception-setting"][@displaymode="static"]')
423     assert len(pattern_list) == len(behavior_list), \
424            'Number of patterns does not match the behaviors.'
425     return dict(zip(pattern_list, [b.text.lower() for b in behavior_list]))
426
427   def GetBehaviorForPattern(self, pattern, incognito=False):
428     """Returns the behavior for a given pattern on the Exceptions page.
429
430     Args:
431       pattern: Hostname pattern string.
432       incognito: Incognito list box. Default to false.
433      """
434     if incognito:
435       self._AssertIncognitoAvailable()
436     assert self.GetExceptions(incognito).has_key(pattern), \
437            'No displayed host name matches pattern "%s"' % pattern
438     return self.GetExceptions(incognito)[pattern]
439
440   def SetBehaviorForPattern(self, pattern, behavior, incognito=False):
441     """Set the behavior for the selected pattern on the Exceptions page.
442
443     Args:
444       pattern: Hostname pattern string.
445       behavior: Setting for the hostname pattern (Allow, Block, Session Only).
446       incognito: Incognito list box. Default to false.
447
448     Raises:
449       AssertionError when the behavior cannot be changed on the content page.
450     """
451     if incognito:
452       self._AssertIncognitoAvailable()
453       list_elem = self._incognito_list_elem
454     else:
455       list_elem = self._list_elem
456     pattern_list = self._GetPatternList(incognito)
457     listitem_list = list_elem.find_elements_by_xpath(
458         './/*[@role="listitem"][@class="deletable-item"]')
459     pattern_listitem_dict = dict(zip(pattern_list, listitem_list))
460     # Set focus to appropriate listitem.
461     listitem_elem = pattern_listitem_dict[pattern]
462     listitem_elem.click()
463     # Set behavior.
464     try:
465       listitem_elem.find_element_by_xpath(
466           './/option[@value="%s"]' % behavior).click()
467     except selenium.common.exceptions.ElementNotVisibleException:
468       raise AssertionError(
469           'Changing the behavior is invalid for pattern '
470           '"%s" in "%s" content page' % (behavior, self._content_type))
471     # Send enter key.
472     pattern_elem = listitem_elem.find_element_by_tag_name('input')
473     pattern_elem.send_keys('\n')
474
475
476 class RestoreOnStartupType(object):
477   NEW_TAB_PAGE = 5
478   RESTORE_SESSION = 1
479   RESTORE_URLS = 4
480
481
482 class BasicSettingsPage(object):
483   """The basic settings page."""
484   _URL = 'chrome://settings-frame/settings'
485
486   @staticmethod
487   def FromNavigation(driver):
488     """Creates an instance of BasicSetting page by navigating to it."""
489     driver.get(BasicSettingsPage._URL)
490     return BasicSettingsPage(driver)
491
492   def __init__(self, driver):
493     self._driver = driver
494     assert self._URL == driver.current_url
495
496   def SetOnStartupOptions(self, on_startup_option):
497     """Set on-startup options.
498
499     Args:
500       on_startup_option: option types for on start up settings.
501
502     Raises:
503       AssertionError when invalid startup option type is provided.
504     """
505     if on_startup_option == RestoreOnStartupType.NEW_TAB_PAGE:
506       startup_option_elem = self._driver.find_element_by_id('startup-newtab')
507     elif on_startup_option == RestoreOnStartupType.RESTORE_SESSION:
508       startup_option_elem = self._driver.find_element_by_id(
509           'startup-restore-session')
510     elif on_startup_option == RestoreOnStartupType.RESTORE_URLS:
511       startup_option_elem = self._driver.find_element_by_id(
512           'startup-show-pages')
513     else:
514       raise AssertionError('Invalid value for restore start up option!')
515     startup_option_elem.click()
516
517   def _GoToStartupSetPages(self):
518     self._driver.find_element_by_id('startup-set-pages').click()
519
520   def _FillStartupURL(self, url):
521     list = DynamicList(self._driver, self._driver.find_element_by_id(
522                        'startupPagesList'))
523     list.GetPlaceholderItem().Set(url + '\n')
524
525   def AddStartupPage(self, url):
526     """Add a startup URL.
527
528     Args:
529       url: A startup url.
530     """
531     self._GoToStartupSetPages()
532     self._FillStartupURL(url)
533     self._driver.find_element_by_id('startup-overlay-confirm').click()
534     self._driver.get(self._URL)
535
536   def UseCurrentPageForStartup(self, title_list):
537     """Use current pages and verify page url show up in settings.
538
539     Args:
540       title_list: startup web page title list.
541     """
542     self._GoToStartupSetPages()
543     self._driver.find_element_by_id('startupUseCurrentButton').click()
544     self._driver.find_element_by_id('startup-overlay-confirm').click()
545     def is_current_page_visible(driver):
546       title_elem_list = driver.find_elements_by_xpath(
547           '//*[contains(@class, "title")][text()="%s"]' % title_list[0])
548       if len(title_elem_list) == 0:
549         return False
550       return True
551     WebDriverWait(self._driver, 10).until(is_current_page_visible)
552     self._driver.get(self._URL)
553
554   def VerifyStartupURLs(self, title_list):
555     """Verify saved startup URLs appear in set page UI.
556
557     Args:
558       title_list: A list of startup page title.
559
560     Raises:
561       AssertionError when start up URLs do not appear in set page UI.
562     """
563     self._GoToStartupSetPages()
564     for i in range(len(title_list)):
565       try:
566         self._driver.find_element_by_xpath(
567             '//*[contains(@class, "title")][text()="%s"]' % title_list[i])
568       except selenium.common.exceptions.NoSuchElementException:
569         raise AssertionError("Current page %s did not appear as startup page."
570             % title_list[i])
571     self._driver.find_element_by_id('startup-overlay-cancel').click()
572
573   def CancelStartupURLSetting(self, url):
574     """Cancel start up URL settings.
575
576     Args:
577       url: A startup url.
578     """
579     self._GoToStartupSetPages()
580     self._FillStartupURL(url)
581     self._driver.find_element_by_id('startup-overlay-cancel').click()
582     self._driver.get(self._URL)
583
584
585 class PasswordsSettings(object):
586   """The overlay for managing passwords on the Content Settings page."""
587
588   _URL = 'chrome://settings-frame/passwords'
589
590   class PasswordsItem(Item):
591     """A list of passwords item web element."""
592     def _GetFields(self):
593       """Returns the field list element."""
594       return self._elem.find_elements_by_xpath('./div/*')
595
596     def GetSite(self):
597       """Returns the site field value."""
598       return self._GetFields()[0].text
599
600     def GetUsername(self):
601       """Returns the username field value."""
602       return self._GetFields()[1].text
603
604
605   @staticmethod
606   def FromNavigation(driver):
607     """Creates an instance of the dialog by navigating directly to it.
608
609     Args:
610       driver: The remote WebDriver instance to manage some content type.
611     """
612     driver.get(PasswordsSettings._URL)
613     return PasswordsSettings(driver)
614
615   def __init__(self, driver):
616     self._driver = driver
617     assert self._URL == driver.current_url
618     list_elem = driver.find_element_by_id('saved-passwords-list')
619     self._items_list = List(self._driver, list_elem, self.PasswordsItem)
620
621   def DeleteItem(self, url, username):
622     """Deletes a line entry in Passwords Content Settings.
623
624     Args:
625       url: The URL string as it appears in the UI.
626       username: The username string as it appears in the second column.
627     """
628     for password_item in self._items_list.GetItems():
629       if (password_item.GetSite() == url and
630           password_item.GetUsername() == username):
631         password_item.Remove(self._driver)
632
633
634 class CookiesAndSiteDataSettings(object):
635   """The overlay for managing cookies on the Content Settings page."""
636
637   _URL = 'chrome://settings-frame/cookies'
638
639   @staticmethod
640   def FromNavigation(driver):
641     """Creates an instance of the dialog by navigating directly to it.
642
643     Args:
644       driver: The remote WebDriver instance for managing content type.
645     """
646     driver.get(CookiesAndSiteDataSettings._URL)
647     return CookiesAndSiteDataSettings(driver)
648
649   def __init__(self, driver):
650     self._driver = driver
651     assert self._URL == driver.current_url
652     self._list_elem = driver.find_element_by_id('cookies-list')
653
654   def GetSiteNameList(self):
655     """Returns a list of the site names.
656
657     This is a public function since the test needs to check if the site is
658     deleted.
659     """
660     site_list = [p.text for p in
661                  self._list_elem.find_elements_by_xpath(
662                      './/*[contains(@class, "deletable-item")]'
663                      '//div[@class="cookie-site"]')]
664     return site_list
665
666   def _GetCookieNameList(self):
667     """Returns a list where each item is the list of cookie names of each site.
668
669     Example: site1 | cookie1 cookie2
670              site2 | cookieA
671              site3 | cookieA cookie1 cookieB
672
673     Returns:
674       A cookie names list such as:
675       [ ['cookie1', 'cookie2'], ['cookieA'], ['cookieA', 'cookie1', 'cookieB'] ]
676     """
677     cookie_name_list = []
678     for elem in self._list_elem.find_elements_by_xpath(
679         './/*[@role="listitem"]'):
680       elem.click()
681       cookie_name_list.append([c.text for c in
682             elem.find_elements_by_xpath('.//div[@class="cookie-item"]')])
683     return cookie_name_list
684
685   def DeleteSiteData(self, site):
686     """Delete a site entry with its cookies in cookies content settings.
687
688     Args:
689       site: The site string as it appears in the UI.
690     """
691     delete_button_list = self._list_elem.find_elements_by_class_name(
692         'row-delete-button')
693     site_list = self.GetSiteNameList()
694     for i in range(len(site_list)):
695       if site_list[i] == site:
696         # Highlight the item so the close button shows up, then delete button
697         # shows up, then click on the delete button.
698         ActionChains(self._driver).move_to_element(
699             delete_button_list[i]).click().perform()