# Copyright (c) 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.
import logging
import os
import time
from autotest_lib.client.bin import test
from autotest_lib.client.bin import utils
from autotest_lib.client.common_lib import error
from autotest_lib.client.cros.input_playback import input_playback
class touch_playback_test_base(test.test):
"""Base class for touch tests involving playback."""
version = 1
_INPUTCONTROL = '/opt/google/input/inputcontrol'
_DEFAULT_SCROLL = 5000
@property
def _has_touchpad(self):
"""True if device under test has a touchpad; else False."""
return self.player.has('touchpad')
@property
def _has_touchscreen(self):
"""True if device under test has a touchscreen; else False."""
return self.player.has('touchscreen')
@property
def _has_mouse(self):
"""True if device under test has or emulates a USB mouse; else False."""
return self.player.has('mouse')
def warmup(self, mouse_props=None):
"""Test setup.
Instantiate player object to find touch devices, if any.
These devices can be used for playback later.
Emulate a USB mouse if a property file is provided.
Check if the inputcontrol script is avaiable on the disk.
@param mouse_props: optional property file for a mouse to emulate.
Created using 'evemu-describe /dev/input/X'.
"""
self.player = input_playback.InputPlayback()
if mouse_props:
self.player.emulate(input_type='mouse', property_file=mouse_props)
self.player.find_connected_inputs()
self._autotest_ext = None
self._has_inputcontrol = os.path.isfile(self._INPUTCONTROL)
self._platform = utils.get_board()
def _find_test_files(self, input_type, gestures):
"""Determine where the test files are.
Expected file format is: <boardname>_<input type>_<hwid>_<gesture name>
e.g. samus_touchpad_164.17_scroll_down
@param input_type: device type, e.g. 'touchpad'
@param gestures: list of gesture name strings used in filename
@returns: None if not all files are found. Dictionary of filepaths if
they are found, indexed by gesture names as given.
@raises: error.TestError if no hw_id is found.
"""
hw_id = self.player.devices[input_type].hw_id
if not hw_id:
raise error.TestError('No valid hw_id for this %s!' % input_type)
filepaths = {}
gesture_dir = os.path.join(self.bindir, 'gestures')
for gesture in gestures:
filename = '%s_%s_%s_%s' % (self._platform, input_type, hw_id,
gesture)
filepath = os.path.join(gesture_dir, filename)
if not os.path.exists(filepath):
logging.info('Did not find %s!', filepath)
return None
filepaths[gesture] = filepath
return filepaths
def _find_test_files_from_directions(self, input_type, fmt_str, directions):
"""Find test files given a list of directions and gesture name format.
@param input_type: device type, e.g. 'touchpad'
@param fmt_str: format string for filename, e.g. 'scroll-%s'
@param directions: list of directions for fmt_string
@returns: None if not all files are found. Dictionary of filepaths if
they are found, indexed by directions as given.
@raises: error.TestError if no hw_id is found.
"""
gestures = [fmt_str % d for d in directions]
temp_filepaths = self._find_test_files(input_type, gestures)
filepaths = {}
if temp_filepaths:
filepaths = {d: temp_filepaths[fmt_str % d] for d in directions}
return filepaths
def _emulate_mouse(self, property_file=None):
"""Emulate a mouse with the given property file.
player will use default mouse if no file is provided.
"""
self.player.emulate(input_type='mouse', property_file=property_file)
self.player.find_connected_inputs()
if not self._has_mouse:
raise error.TestError('Mouse emulation failed!')
def _playback(self, filepath, touch_type='touchpad'):
"""Playback a given input file on the given input."""
self.player.playback(filepath, touch_type)
def _blocking_playback(self, filepath, touch_type='touchpad'):
"""Playback a given input file on the given input; block until done."""
self.player.blocking_playback(filepath, touch_type)
def _set_touch_setting_by_inputcontrol(self, setting, value):
"""Set a given touch setting the given value by inputcontrol.
@param setting: Name of touch setting, e.g. 'tapclick'.
@param value: True for enabled, False for disabled.
"""
cmd_value = 1 if value else 0
utils.run('%s --%s %d' % (self._INPUTCONTROL, setting, cmd_value))
logging.info('%s turned %s.', setting, 'on' if value else 'off')
def _set_touch_setting(self, inputcontrol_setting, autotest_ext_setting,
value):
"""Set a given touch setting the given value.
@param inputcontrol_setting: Name of touch setting for the inputcontrol
script, e.g. 'tapclick'.
@param autotest_ext_setting: Name of touch setting for the autotest
extension, e.g. 'TapToClick'.
@param value: True for enabled, False for disabled.
"""
if self._has_inputcontrol:
self._set_touch_setting_by_inputcontrol(inputcontrol_setting, value)
elif self._autotest_ext is not None:
self._autotest_ext.EvaluateJavaScript(
'chrome.autotestPrivate.set%s(%s);'
% (autotest_ext_setting, ("%s" % value).lower()))
# TODO: remove this sleep once checking for value is available.
time.sleep(1)
else:
raise error.TestFail('Both inputcontrol and the autotest '
'extension are not availble.')
def _set_australian_scrolling(self, value):
"""Set australian scrolling to the given value.
@param value: True for enabled, False for disabled.
"""
self._set_touch_setting('australian_scrolling', 'NaturalScroll', value)
def _set_tap_to_click(self, value):
"""Set tap-to-click to the given value.
@param value: True for enabled, False for disabled.
"""
self._set_touch_setting('tapclick', 'TapToClick', value)
def _set_tap_dragging(self, value):
"""Set tap dragging to the given value.
@param value: True for enabled, False for disabled.
"""
self._set_touch_setting('tapdrag', 'TapDragging', value)
def _reload_page(self):
"""Reloads test page. Presuposes self._tab.
@raise: TestError if page is not reset.
"""
self._tab.Navigate(self._tab.url)
self._wait_for_page_ready()
def _set_autotest_ext(self, ext):
"""Set the autotest extension.
@ext: the autotest extension object.
"""
self._autotest_ext = ext
def _open_test_page(self, cr, filename='test_page.html'):
"""Prepare test page for testing. Set self._tab with page.
@param cr: chrome.Chrome() object
@param filename: name of file in self.bindir to open
"""
cr.browser.platform.SetHTTPServerDirectories(self.bindir)
self._tab = cr.browser.tabs[0]
self._tab.Navigate(cr.browser.platform.http_server.UrlOf(
os.path.join(self.bindir, filename)))
self._wait_for_page_ready()
def _wait_for_page_ready(self):
"""Wait for a variable pageReady on the test page to be true.
Presuposes self._tab and a pageReady variable.
@raises error.TestError if page is not ready after timeout.
"""
self._tab.WaitForDocumentReadyStateToBeComplete()
utils.poll_for_condition(
lambda: self._tab.EvaluateJavaScript('pageReady'),
exception=error.TestError('Test page is not ready!'))
def _center_cursor(self):
"""Playback mouse movement to center cursor.
Requres that self._emulate_mouse() has been called.
"""
self.player.blocking_playback_of_default_file(
'mouse_center_cursor_gesture', input_type='mouse')
def _set_scroll(self, value, scroll_vertical=True):
"""Set scroll position to given value. Presuposes self._tab.
@param scroll_vertical: True for vertical scroll,
False for horizontal Scroll.
@param value: True for enabled, False for disabled.
"""
if scroll_vertical:
self._tab.ExecuteJavaScript(
'document.body.scrollTop=%s' % value)
else:
self._tab.ExecuteJavaScript(
'document.body.scrollLeft=%s' % value)
def _set_default_scroll_position(self, scroll_vertical=True):
"""Set scroll position of page to default. Presuposes self._tab.
@param scroll_vertical: True for vertical scroll,
False for horizontal Scroll.
@raise: TestError if page is not set to default scroll position
"""
total_tries = 2
for i in xrange(total_tries):
try:
self._set_scroll(self._DEFAULT_SCROLL, scroll_vertical)
self._wait_for_default_scroll_position(scroll_vertical)
except error.TestError as e:
if i == total_tries - 1:
pos = self._get_scroll_position(scroll_vertical)
logging.error('SCROLL POSITION: %s', pos)
raise e
else:
break
def _get_scroll_position(self, scroll_vertical=True):
"""Return current scroll position of page. Presuposes self._tab.
@param scroll_vertical: True for vertical scroll,
False for horizontal Scroll.
"""
if scroll_vertical:
return int(self._tab.EvaluateJavaScript('document.body.scrollTop'))
else:
return int(self._tab.EvaluateJavaScript('document.body.scrollLeft'))
def _wait_for_default_scroll_position(self, scroll_vertical=True):
"""Wait for page to be at the default scroll position.
@param scroll_vertical: True for vertical scroll,
False for horizontal scroll.
@raise: TestError if page either does not move or does not stop moving.
"""
utils.poll_for_condition(
lambda: self._get_scroll_position(
scroll_vertical) == self._DEFAULT_SCROLL,
exception=error.TestError('Page not set to default scroll!'))
def _wait_for_scroll_position_to_settle(self, scroll_vertical=True):
"""Wait for page to move and then stop moving.
@param scroll_vertical: True for Vertical scroll and
False for horizontal scroll.
@raise: TestError if page either does not move or does not stop moving.
"""
# Wait until page starts moving.
utils.poll_for_condition(
lambda: self._get_scroll_position(
scroll_vertical) != self._DEFAULT_SCROLL,
exception=error.TestError('No scrolling occurred!'), timeout=30)
# Wait until page has stopped moving.
self._previous = self._DEFAULT_SCROLL
def _movement_stopped():
current = self._get_scroll_position()
result = current == self._previous
self._previous = current
return result
utils.poll_for_condition(
lambda: _movement_stopped(), sleep_interval=1,
exception=error.TestError('Page did not stop moving!'),
timeout=30)
def cleanup(self):
self.player.close()