blob: 91a6b8e62e1026cc4b806e76ecd1c696abdaac36 [file] [log] [blame]
# Lint as: python2, python3
# 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.
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import logging
import six
from six.moves import range
from six.moves import zip
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 import By
from import expected_conditions
from 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
# How long to wait before entering the password when logging in to the CWS
# How long to wait before entering payment info
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, list(range(len(enumNames)))))
reverse = dict((value, key) for key, value in six.iteritems(enums))
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(
# NOTE: paid installs don't work right now
InstallType = enum(
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 ==
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"]')
return _labeled_button('Add to Chrome')
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 ==
return flags
url_middle = {
TestEnv.staging: 'staging.corp',
TestEnv.sandbox: 'staging.sandbox',
TestEnv.pnl: 'prod-not-live.corp'
download_url_middle = {
TestEnv.staging: 'download-staging.corp',
TestEnv.sandbox: 'download-staging.sandbox',
TestEnv.pnl: 'omaha.sandbox'
flags.append('--apps-gallery-url=https://webstore-' + url_middle +
flags.append('--apps-gallery-update-url=https://' + download_url_middle +
'')'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,
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 = {
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
(By.XPATH, xpath)))
def run_once(self, **kwargs):
with chromedriver.chromedriver(
extra_chrome_flags=self._chrome_flags) \
as chromedriver_instance:
self.driver = chromedriver_instance.driver
self._wait = WebDriverWait(self.driver, 20)'Running test on test environment %s',
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)
"""'Installing item %s of type %s with install_type %s',
item_id, ItemType.to_string[item_type],
# We need to go to the CWS home page before going to the detail
# page due to a bug in the CWS
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"))]')
post_install_xpath = _labeled_button('Launch app')
# In this case we need to sign in again
if install_type !=
button_xpath = _labeled_button('Sign in to add')'Clicking button %s', button_xpath)
password_field = self.driver.find_element_by_xpath(
self.driver.find_element_by_xpath('//input[@id="signIn"]').click()'Clicking %s', install_type_click_xpath)
if install_type == InstallType.paid:
handle = self.driver.current_window_handle
iframe = self.driver.find_element_by_xpath(
'//iframe[contains(@src, "")]')
time.sleep(_PAYMENT_DELAY) # Wait for animation to finish
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
"""'Launching app %s', app_id)
num_handles_before = len(self.driver.window_handles)
launch_button = self.driver.find_element_by_xpath(
_labeled_button('Launch app'));
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')