# Copyright 2015 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 base64 import mock_lorgnette import os from autotest_lib.client.cros import touch_playback_test_base from autotest_lib.client.common_lib import error from autotest_lib.client.common_lib.cros import chrome class documentscan_AppTestWithFakeLorgnette( touch_playback_test_base.touch_playback_test_base): """ Test that an extension using the DocumentScan Chrome API can successfully retrieve a scanned document from a mocked version of the lorgnette daemon. """ version = 1 # Application ID of the test scan application. _APP_ID = 'mljeglgkknlanoeffbeehogdhkhnaidk' # Document to open in order to launch the scan application. _APP_DOCUMENT = 'scan.html' # Window ID that references the scan application window. _APP_WINDOW_ID = 'ChromeApps-Sample-Document-Scan' # Element within the scan application document that contains image scans. _APP_SCANNED_IMAGE_ELEMENT = 'scannedImages' # Description of the fake mouse we add to the system. _MOUSE_DESCRIPTION = 'amazon_mouse.prop' # This input file was created as follows: # - Insert USB mouse (in this case the Amazon mouse) # - head /sys/class/input/*/name | grep -iB1 mouse # This will give you the /sys/class/inputXX for the mouse. # - evemu-record /dev/input/eventXX -1 > /tmp/button_click.event # Move the mouse diagonally upwards to the upper left, move # down and right a bit then click. _PLAYBACK_FILE = 'button_click.event' # Image file to serve up to Chrome in response to a scan request. _IMAGE_FILENAME = 'lorgnette-test.png' # Expected prefix for the SRC tag of the scanned images. _BASE64_IMAGE_HEADER = 'data:image/png;base64,' def _play_events(self, event_filename): """Simulate mouse events since the Chrome API enforces that the scan action come from a user gesture. @param event_filename string filename containing events to play back """ file_path = os.path.join(self.bindir, event_filename) self._blocking_playback(file_path, touch_type='mouse') def _launch_app(self, chrome_instance): """Launches the sample scanner Chrome app. @param chrome_instance object of type chrome.Chrome """ self._extension = chrome_instance.get_extension(self._extension_path) # TODO(pstew): chrome.management.launchApp() would have been # ideal here, but is not available even after adding the # "management" permission to the app. Instead, we perform # the launch action of the extension directly. cmd = ''' chrome.app.window.create('%s', { singleton: true, id: '%s', state: 'fullscreen' }); ''' % (self._APP_DOCUMENT, self._APP_WINDOW_ID) self._extension.ExecuteJavaScript(cmd) def _query_scan_element(self, query): """Queries the "scannedImages" element within the app window. @param query string javascript query to execute on the DIV element. """ cmd = ''' app_window = chrome.app.window.get('%s'); element = app_window.contentWindow.document.getElementById('%s'); element.%s; ''' % (self._APP_WINDOW_ID, self._APP_SCANNED_IMAGE_ELEMENT, query) return self._extension.EvaluateJavaScript(cmd) def _get_scan_count(self): """Counts the number of successful scanned images displayed. @param chrome_instance object of type chrome.Chrome """ result = self._query_scan_element('childNodes.length') # Subtract 1 for the text node member of the DIV element. return int(result) - 1 def _validate_image_data(self, expected_image_data): """Validates that the scanned image displayed by the app is the same as the image provided by the fake lorgnette daemon. """ image_src = self._query_scan_element('childNodes[0].src') if not image_src.startswith(self._BASE64_IMAGE_HEADER): raise error.TestError( 'Image SRC does not start with base64 data header: %s' % image_src) base64_data = image_src[len(self._BASE64_IMAGE_HEADER):] data = base64.b64decode(base64_data) if expected_image_data != data: raise error.TestError('Image data from tag is not the same as ' 'the test image data') def _validate_mock_method_calls(self, calls): """Validate the method calls made on the lorgnette mock instance. @param calls list of MethodCall named tuples from mock lorgnette. """ if len(calls) != 2: raise error.TestError('Expected 2 method calls but got: %r' % calls) for index, method_name in enumerate(['ListScanners', 'ScanImage']): if calls[index].method != method_name: raise error.TestError('Call #%d was %s instead of expected %s' % (index, calls[index].method, method_name)) def run_once(self): """Entry point of this test.""" mouse_file = os.path.join(self.bindir, self._MOUSE_DESCRIPTION) self._emulate_mouse(property_file=mouse_file) self._extension_path = os.path.join(os.path.dirname(__file__), 'document_scan_test_app') with chrome.Chrome(extension_paths=[self._extension_path], init_network_controller=True) as cr: img = os.path.join(self.bindir, self._IMAGE_FILENAME) with mock_lorgnette.MockLorgnette(img) as lorgnette_instance: self._launch_app(cr) self._play_events(self._PLAYBACK_FILE) scan_count = self._get_scan_count() if scan_count != 1: raise error.TestError('Scan count is %d instead of 1' % scan_count) self._validate_image_data(lorgnette_instance.image_data) self._validate_mock_method_calls( lorgnette_instance.get_method_calls())