Home | History | Annotate | Download | only in cros
      1 # Copyright 2014 The Chromium OS 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 """
      6 This module allows tests to interact with the Chrome Web Store (CWS)
      7 using ChromeDriver. They should inherit from the webstore_test class,
      8 and should override the run() method.
      9 """
     10 
     11 import logging
     12 import time
     13 
     14 from autotest_lib.client.bin import test
     15 from autotest_lib.client.common_lib import error
     16 from autotest_lib.client.common_lib.cros import chromedriver
     17 from autotest_lib.client.common_lib.global_config import global_config
     18 from selenium.webdriver.common.by import By
     19 from selenium.webdriver.support import expected_conditions
     20 from selenium.webdriver.support.ui import WebDriverWait
     21 
     22 # How long to wait, in seconds, for an app to launch. This is larger
     23 # than it needs to be, because it might be slow on older Chromebooks
     24 _LAUNCH_DELAY = 4
     25 
     26 # How long to wait before entering the password when logging in to the CWS
     27 _ENTER_PASSWORD_DELAY = 2
     28 
     29 # How long to wait before entering payment info
     30 _PAYMENT_DELAY = 5
     31 
     32 def enum(*enumNames):
     33     """
     34     Creates an enum. Returns an enum object with a value for each enum
     35     name, as well as from_string and to_string mappings.
     36 
     37     @param enumNames: The strings representing the values of the enum
     38     """
     39     enums = dict(zip(enumNames, range(len(enumNames))))
     40     reverse = dict((value, key) for key, value in enums.iteritems())
     41     enums['from_string'] = enums
     42     enums['to_string'] = reverse
     43     return type('Enum', (), enums)
     44 
     45 # TODO: staging and PNL don't work in these tests (crbug/396660)
     46 TestEnv = enum('staging', 'pnl', 'prod', 'sandbox')
     47 
     48 ItemType = enum(
     49     'hosted_app',
     50     'packaged_app',
     51     'chrome_app',
     52     'extension',
     53     'theme',
     54 )
     55 
     56 # NOTE: paid installs don't work right now
     57 InstallType = enum(
     58     'free',
     59     'free_trial',
     60     'paid',
     61 )
     62 
     63 def _labeled_button(label):
     64     """
     65     Returns a button with the class webstore-test-button-label and the
     66     specified label
     67 
     68     @param label: The label on the button
     69     """
     70     return ('//div[contains(@class,"webstore-test-button-label") '
     71             'and text()="' + label + '"]')
     72 
     73 def _install_type_click_xpath(item_type, install_type):
     74     """
     75     Returns the XPath of the button to install an item of the given type.
     76 
     77     @param item_type: The type of the item to install
     78     @param install_type: The type of installation being used
     79     """
     80     if install_type == InstallType.free:
     81         return _labeled_button('Free')
     82     elif install_type == InstallType.free_trial:
     83         # Both of these cases return buttons that say "Add to Chrome",
     84         # but they are actually different buttons with only one being
     85         # visible at a time.
     86         if item_type == ItemType.hosted_app:
     87             return ('//div[@id="cxdialog-install-paid-btn" and '
     88                     '@aria-label="Add to Chrome"]')
     89         else:
     90             return _labeled_button('Add to Chrome')
     91     else:
     92         return ('//div[contains(@aria-label,"Buy for") '
     93                 'and not(contains(@style,"display: none"))]')
     94 
     95 def _get_chrome_flags(test_env):
     96     """
     97     Returns the Chrome flags for the given test environment.
     98     """
     99     flags = ['--apps-gallery-install-auto-confirm-for-tests=accept']
    100     if test_env == TestEnv.prod:
    101         return flags
    102 
    103     url_middle = {
    104             TestEnv.staging: 'staging.corp',
    105             TestEnv.sandbox: 'staging.sandbox',
    106             TestEnv.pnl: 'prod-not-live.corp'
    107             }[test_env]
    108     download_url_middle = {
    109             TestEnv.staging: 'download-staging.corp',
    110             TestEnv.sandbox: 'download-staging.sandbox',
    111             TestEnv.pnl: 'omaha.sandbox'
    112             }[test_env]
    113     flags.append('--apps-gallery-url=https://webstore-' + url_middle +
    114             '.google.com')
    115     flags.append('--apps-gallery-update-url=https://' + download_url_middle +
    116             '.google.com/service/update2/crx')
    117     logging.info('Using flags %s', flags)
    118     return flags
    119 
    120 
    121 class webstore_test(test.test):
    122     """
    123     The base class for tests that interact with the web store.
    124 
    125     Subclasses must define run(), but should not override run_once().
    126     Subclasses should use methods in this module such as install_item,
    127     but they can also use the driver directly if they need to.
    128     """
    129 
    130     def initialize(self, test_env=TestEnv.sandbox,
    131                    account='cwsbotdeveloper1 (at] gmail.com'):
    132         """
    133         Initialize the test.
    134 
    135         @param test_env: The test environment to use
    136         """
    137         super(webstore_test, self).initialize()
    138 
    139         self.username = account
    140         self.password = global_config.get_config_value(
    141                 'CLIENT', 'webstore_test_password', type=str)
    142 
    143         self.test_env = test_env
    144         self._chrome_flags = _get_chrome_flags(test_env)
    145         self.webstore_url = {
    146                 TestEnv.staging:
    147                     'https://webstore-staging.corp.google.com',
    148                 TestEnv.sandbox:
    149                     'https://webstore-staging.sandbox.google.com/webstore',
    150                 TestEnv.pnl:
    151                     'https://webstore-prod-not-live.corp.google.com/webstore',
    152                 TestEnv.prod:
    153                     'https://chrome.google.com/webstore'
    154                 }[test_env]
    155 
    156 
    157     def build_url(self, page):
    158         """
    159         Builds a webstore URL for the specified page.
    160 
    161         @param page: the page to build a URL for
    162         """
    163         return self.webstore_url + page + "?gl=US"
    164 
    165 
    166     def detail_page(self, item_id):
    167         """
    168         Returns the URL of the detail page for the given item
    169 
    170         @param item_id: The item ID
    171         """
    172         return self.build_url("/detail/" + item_id)
    173 
    174 
    175     def wait_for(self, xpath):
    176         """
    177         Waits until the element specified by the given XPath is visible
    178 
    179         @param xpath: The xpath of the element to wait for
    180         """
    181         self._wait.until(expected_conditions.visibility_of_element_located(
    182                 (By.XPATH, xpath)))
    183 
    184 
    185     def run_once(self, **kwargs):
    186         with chromedriver.chromedriver(
    187                 username=self.username,
    188                 password=self.password,
    189                 extra_chrome_flags=self._chrome_flags) \
    190                 as chromedriver_instance:
    191             self.driver = chromedriver_instance.driver
    192             self.driver.implicitly_wait(15)
    193             self._wait = WebDriverWait(self.driver, 20)
    194             logging.info('Running test on test environment %s',
    195                     TestEnv.to_string[self.test_env])
    196             self.run(**kwargs)
    197 
    198 
    199     def run(self):
    200         """
    201         Runs the test. Should be overridden by subclasses.
    202         """
    203         raise error.TestError('The test needs to override run()')
    204 
    205 
    206     def install_item(self, item_id, item_type, install_type):
    207         """
    208         Installs an item from the CWS.
    209 
    210         @param item_id: The ID of the item to install
    211                 (a 32-char string of letters)
    212         @param item_type: The type of the item to install
    213         @param install_type: The type of installation
    214                 (free, free trial, or paid)
    215         """
    216         logging.info('Installing item %s of type %s with install_type %s',
    217                 item_id, ItemType.to_string[item_type],
    218                 InstallType.to_string[install_type])
    219 
    220         # We need to go to the CWS home page before going to the detail
    221         # page due to a bug in the CWS
    222         self.driver.get(self.webstore_url)
    223         self.driver.get(self.detail_page(item_id))
    224 
    225         install_type_click_xpath = _install_type_click_xpath(
    226                 item_type, install_type)
    227         if item_type == ItemType.extension or item_type == ItemType.theme:
    228             post_install_xpath = (
    229                 '//div[@aria-label="Added to Chrome" '
    230                 ' and not(contains(@style,"display: none"))]')
    231         else:
    232             post_install_xpath = _labeled_button('Launch app')
    233 
    234         # In this case we need to sign in again
    235         if install_type != InstallType.free:
    236             button_xpath = _labeled_button('Sign in to add')
    237             logging.info('Clicking button %s', button_xpath)
    238             self.driver.find_element_by_xpath(button_xpath).click()
    239             time.sleep(_ENTER_PASSWORD_DELAY)
    240             password_field = self.driver.find_element_by_xpath(
    241                     '//input[@id="Passwd"]')
    242             password_field.send_keys(self.password)
    243             self.driver.find_element_by_xpath('//input[@id="signIn"]').click()
    244 
    245         logging.info('Clicking %s', install_type_click_xpath)
    246         self.driver.find_element_by_xpath(install_type_click_xpath).click()
    247 
    248         if install_type == InstallType.paid:
    249             handle = self.driver.current_window_handle
    250             iframe = self.driver.find_element_by_xpath(
    251                 '//iframe[contains(@src, "sandbox.google.com/checkout")]')
    252             self.driver.switch_to_frame(iframe)
    253             self.driver.find_element_by_id('purchaseButton').click()
    254             time.sleep(_PAYMENT_DELAY) # Wait for animation to finish
    255             self.driver.find_element_by_id('finishButton').click()
    256             self.driver.switch_to_window(handle)
    257 
    258         self.wait_for(post_install_xpath)
    259 
    260 
    261     def launch_app(self, app_id):
    262         """
    263         Launches an app. Verifies that it launched by verifying that
    264         a new tab/window was opened.
    265 
    266         @param app_id: The ID of the app to run
    267         """
    268         logging.info('Launching app %s', app_id)
    269         num_handles_before = len(self.driver.window_handles)
    270         self.driver.get(self.webstore_url)
    271         self.driver.get(self.detail_page(app_id))
    272         launch_button = self.driver.find_element_by_xpath(
    273             _labeled_button('Launch app'))
    274         launch_button.click();
    275         time.sleep(_LAUNCH_DELAY) # Wait for the app to launch
    276         num_handles_after = len(self.driver.window_handles)
    277         if num_handles_after <= num_handles_before:
    278             raise error.TestError('App failed to launch')
    279