| # Copyright 2014 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """ |
| This module allows tests to interact with the Chrome Web Store (CWS) |
| using ChromeDriver. They should inherit from the webstore_test class, |
| and should override the run() method. |
| """ |
| |
| import logging |
| import time |
| |
| from autotest_lib.client.bin import test |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.client.common_lib.cros import chromedriver |
| from autotest_lib.client.common_lib.global_config import global_config |
| from selenium.webdriver.common.by import By |
| from selenium.webdriver.support import expected_conditions |
| from selenium.webdriver.support.ui import WebDriverWait |
| |
| # How long to wait, in seconds, for an app to launch. This is larger |
| # than it needs to be, because it might be slow on older Chromebooks |
| _LAUNCH_DELAY = 4 |
| |
| # How long to wait before entering the password when logging in to the CWS |
| _ENTER_PASSWORD_DELAY = 2 |
| |
| # How long to wait before entering payment info |
| _PAYMENT_DELAY = 5 |
| |
| def enum(*enumNames): |
| """ |
| Creates an enum. Returns an enum object with a value for each enum |
| name, as well as from_string and to_string mappings. |
| |
| @param enumNames: The strings representing the values of the enum |
| """ |
| enums = dict(zip(enumNames, range(len(enumNames)))) |
| reverse = dict((value, key) for key, value in enums.iteritems()) |
| enums['from_string'] = enums |
| enums['to_string'] = reverse |
| return type('Enum', (), enums) |
| |
| # TODO: staging and PNL don't work in these tests (crbug/396660) |
| TestEnv = enum('staging', 'pnl', 'prod', 'sandbox') |
| |
| ItemType = enum( |
| 'hosted_app', |
| 'packaged_app', |
| 'chrome_app', |
| 'extension', |
| 'theme', |
| ) |
| |
| # NOTE: paid installs don't work right now |
| InstallType = enum( |
| 'free', |
| 'free_trial', |
| 'paid', |
| ) |
| |
| def _labeled_button(label): |
| """ |
| Returns a button with the class webstore-test-button-label and the |
| specified label |
| |
| @param label: The label on the button |
| """ |
| return ('//div[contains(@class,"webstore-test-button-label") ' |
| 'and text()="' + label + '"]') |
| |
| def _install_type_click_xpath(item_type, install_type): |
| """ |
| Returns the XPath of the button to install an item of the given type. |
| |
| @param item_type: The type of the item to install |
| @param install_type: The type of installation being used |
| """ |
| if install_type == InstallType.free: |
| return _labeled_button('Free') |
| elif install_type == InstallType.free_trial: |
| # Both of these cases return buttons that say "Add to Chrome", |
| # but they are actually different buttons with only one being |
| # visible at a time. |
| if item_type == ItemType.hosted_app: |
| return ('//div[@id="cxdialog-install-paid-btn" and ' |
| '@aria-label="Add to Chrome"]') |
| else: |
| return _labeled_button('Add to Chrome') |
| else: |
| return ('//div[contains(@aria-label,"Buy for") ' |
| 'and not(contains(@style,"display: none"))]') |
| |
| def _get_chrome_flags(test_env): |
| """ |
| Returns the Chrome flags for the given test environment. |
| """ |
| flags = ['--apps-gallery-install-auto-confirm-for-tests=accept'] |
| if test_env == TestEnv.prod: |
| return flags |
| |
| url_middle = { |
| TestEnv.staging: 'staging.corp', |
| TestEnv.sandbox: 'staging.sandbox', |
| TestEnv.pnl: 'prod-not-live.corp' |
| }[test_env] |
| download_url_middle = { |
| TestEnv.staging: 'download-staging.corp', |
| TestEnv.sandbox: 'download-staging.sandbox', |
| TestEnv.pnl: 'omaha.sandbox' |
| }[test_env] |
| flags.append('--apps-gallery-url=https://webstore-' + url_middle + |
| '.google.com') |
| flags.append('--apps-gallery-update-url=https://' + download_url_middle + |
| '.google.com/service/update2/crx') |
| logging.info('Using flags %s', flags) |
| return flags |
| |
| |
| class webstore_test(test.test): |
| """ |
| The base class for tests that interact with the web store. |
| |
| Subclasses must define run(), but should not override run_once(). |
| Subclasses should use methods in this module such as install_item, |
| but they can also use the driver directly if they need to. |
| """ |
| |
| def initialize(self, test_env=TestEnv.sandbox, |
| account='cwsbotdeveloper1@gmail.com'): |
| """ |
| Initialize the test. |
| |
| @param test_env: The test environment to use |
| """ |
| super(webstore_test, self).initialize() |
| |
| self.username = account |
| self.password = global_config.get_config_value( |
| 'CLIENT', 'webstore_test_password', type=str) |
| |
| self.test_env = test_env |
| self._chrome_flags = _get_chrome_flags(test_env) |
| self.webstore_url = { |
| TestEnv.staging: |
| 'https://webstore-staging.corp.google.com', |
| TestEnv.sandbox: |
| 'https://webstore-staging.sandbox.google.com/webstore', |
| TestEnv.pnl: |
| 'https://webstore-prod-not-live.corp.google.com/webstore', |
| TestEnv.prod: |
| 'https://chrome.google.com/webstore' |
| }[test_env] |
| |
| |
| def build_url(self, page): |
| """ |
| Builds a webstore URL for the specified page. |
| |
| @param page: the page to build a URL for |
| """ |
| return self.webstore_url + page + "?gl=US" |
| |
| |
| def detail_page(self, item_id): |
| """ |
| Returns the URL of the detail page for the given item |
| |
| @param item_id: The item ID |
| """ |
| return self.build_url("/detail/" + item_id) |
| |
| |
| def wait_for(self, xpath): |
| """ |
| Waits until the element specified by the given XPath is visible |
| |
| @param xpath: The xpath of the element to wait for |
| """ |
| self._wait.until(expected_conditions.visibility_of_element_located( |
| (By.XPATH, xpath))) |
| |
| |
| def run_once(self, **kwargs): |
| with chromedriver.chromedriver( |
| username=self.username, |
| password=self.password, |
| extra_chrome_flags=self._chrome_flags) \ |
| as chromedriver_instance: |
| self.driver = chromedriver_instance.driver |
| self.driver.implicitly_wait(15) |
| self._wait = WebDriverWait(self.driver, 20) |
| logging.info('Running test on test environment %s', |
| TestEnv.to_string[self.test_env]) |
| self.run(**kwargs) |
| |
| |
| def run(self): |
| """ |
| Runs the test. Should be overridden by subclasses. |
| """ |
| raise error.TestError('The test needs to override run()') |
| |
| |
| def install_item(self, item_id, item_type, install_type): |
| """ |
| Installs an item from the CWS. |
| |
| @param item_id: The ID of the item to install |
| (a 32-char string of letters) |
| @param item_type: The type of the item to install |
| @param install_type: The type of installation |
| (free, free trial, or paid) |
| """ |
| logging.info('Installing item %s of type %s with install_type %s', |
| item_id, ItemType.to_string[item_type], |
| InstallType.to_string[install_type]) |
| |
| # We need to go to the CWS home page before going to the detail |
| # page due to a bug in the CWS |
| self.driver.get(self.webstore_url) |
| self.driver.get(self.detail_page(item_id)) |
| |
| install_type_click_xpath = _install_type_click_xpath( |
| item_type, install_type) |
| if item_type == ItemType.extension or item_type == ItemType.theme: |
| post_install_xpath = ( |
| '//div[@aria-label="Added to Chrome" ' |
| ' and not(contains(@style,"display: none"))]') |
| else: |
| post_install_xpath = _labeled_button('Launch app') |
| |
| # In this case we need to sign in again |
| if install_type != InstallType.free: |
| button_xpath = _labeled_button('Sign in to add') |
| logging.info('Clicking button %s', button_xpath) |
| self.driver.find_element_by_xpath(button_xpath).click() |
| time.sleep(_ENTER_PASSWORD_DELAY) |
| password_field = self.driver.find_element_by_xpath( |
| '//input[@id="Passwd"]') |
| password_field.send_keys(self.password) |
| self.driver.find_element_by_xpath('//input[@id="signIn"]').click() |
| |
| logging.info('Clicking %s', install_type_click_xpath) |
| self.driver.find_element_by_xpath(install_type_click_xpath).click() |
| |
| if install_type == InstallType.paid: |
| handle = self.driver.current_window_handle |
| iframe = self.driver.find_element_by_xpath( |
| '//iframe[contains(@src, "sandbox.google.com/checkout")]') |
| self.driver.switch_to_frame(iframe) |
| self.driver.find_element_by_id('purchaseButton').click() |
| time.sleep(_PAYMENT_DELAY) # Wait for animation to finish |
| self.driver.find_element_by_id('finishButton').click() |
| self.driver.switch_to_window(handle) |
| |
| self.wait_for(post_install_xpath) |
| |
| |
| def launch_app(self, app_id): |
| """ |
| Launches an app. Verifies that it launched by verifying that |
| a new tab/window was opened. |
| |
| @param app_id: The ID of the app to run |
| """ |
| logging.info('Launching app %s', app_id) |
| num_handles_before = len(self.driver.window_handles) |
| self.driver.get(self.webstore_url) |
| self.driver.get(self.detail_page(app_id)) |
| launch_button = self.driver.find_element_by_xpath( |
| _labeled_button('Launch app')) |
| launch_button.click(); |
| time.sleep(_LAUNCH_DELAY) # Wait for the app to launch |
| num_handles_after = len(self.driver.window_handles) |
| if num_handles_after <= num_handles_before: |
| raise error.TestError('App failed to launch') |