1 # Copyright 2015 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 import base64 6 import mock_lorgnette 7 import os 8 9 from autotest_lib.client.cros import touch_playback_test_base 10 from autotest_lib.client.common_lib import error 11 from autotest_lib.client.common_lib.cros import chrome 12 13 14 class documentscan_AppTestWithFakeLorgnette( 15 touch_playback_test_base.touch_playback_test_base): 16 """ Test that an extension using the DocumentScan Chrome API can 17 successfully retrieve a scanned document from a mocked version 18 of the lorgnette daemon. 19 """ 20 version = 1 21 22 # Application ID of the test scan application. 23 _APP_ID = 'mljeglgkknlanoeffbeehogdhkhnaidk' 24 25 # Document to open in order to launch the scan application. 26 _APP_DOCUMENT = 'scan.html' 27 28 # Window ID that references the scan application window. 29 _APP_WINDOW_ID = 'ChromeApps-Sample-Document-Scan' 30 31 # Element within the scan application document that contains image scans. 32 _APP_SCANNED_IMAGE_ELEMENT = 'scannedImages' 33 34 # Description of the fake mouse we add to the system. 35 _MOUSE_DESCRIPTION = 'amazon_mouse.prop' 36 37 # This input file was created as follows: 38 # - Insert USB mouse (in this case the Amazon mouse) 39 # - head /sys/class/input/*/name | grep -iB1 mouse 40 # This will give you the /sys/class/inputXX for the mouse. 41 # - evemu-record /dev/input/eventXX -1 > /tmp/button_click.event 42 # Move the mouse diagonally upwards to the upper left, move 43 # down and right a bit then click. 44 _PLAYBACK_FILE = 'button_click.event' 45 46 # Image file to serve up to Chrome in response to a scan request. 47 _IMAGE_FILENAME = 'lorgnette-test.png' 48 49 # Expected prefix for the SRC tag of the scanned images. 50 _BASE64_IMAGE_HEADER = 'data:image/png;base64,' 51 52 def _play_events(self, event_filename): 53 """Simulate mouse events since the Chrome API enforces that 54 the scan action come from a user gesture. 55 56 @param event_filename string filename containing events to play back 57 """ 58 59 file_path = os.path.join(self.bindir, event_filename) 60 self._blocking_playback(file_path, touch_type='mouse') 61 62 63 def _launch_app(self, chrome_instance): 64 """Launches the sample scanner Chrome app. 65 66 @param chrome_instance object of type chrome.Chrome 67 """ 68 69 self._extension = chrome_instance.get_extension(self._extension_path) 70 71 # TODO(pstew): chrome.management.launchApp() would have been 72 # ideal here, but is not available even after adding the 73 # "management" permission to the app. Instead, we perform 74 # the launch action of the extension directly. 75 cmd = ''' 76 chrome.app.window.create('%s', { 77 singleton: true, 78 id: '%s', 79 state: 'fullscreen' 80 }); 81 ''' % (self._APP_DOCUMENT, self._APP_WINDOW_ID) 82 self._extension.ExecuteJavaScript(cmd) 83 84 85 def _query_scan_element(self, query): 86 """Queries the "scannedImages" element within the app window. 87 88 @param query string javascript query to execute on the DIV element. 89 """ 90 91 cmd = ''' 92 app_window = chrome.app.window.get('%s'); 93 element = app_window.contentWindow.document.getElementById('%s'); 94 element.%s; 95 ''' % (self._APP_WINDOW_ID, self._APP_SCANNED_IMAGE_ELEMENT, query) 96 return self._extension.EvaluateJavaScript(cmd) 97 98 99 def _get_scan_count(self): 100 """Counts the number of successful scanned images displayed. 101 102 @param chrome_instance object of type chrome.Chrome 103 """ 104 105 result = self._query_scan_element('childNodes.length') 106 107 # Subtract 1 for the text node member of the DIV element. 108 return int(result) - 1 109 110 111 def _validate_image_data(self, expected_image_data): 112 """Validates that the scanned image displayed by the app is the same 113 as the image provided by the fake lorgnette daemon. 114 """ 115 116 image_src = self._query_scan_element('childNodes[0].src') 117 if not image_src.startswith(self._BASE64_IMAGE_HEADER): 118 raise error.TestError( 119 'Image SRC does not start with base64 data header: %s' % 120 image_src) 121 122 base64_data = image_src[len(self._BASE64_IMAGE_HEADER):] 123 data = base64.b64decode(base64_data) 124 if expected_image_data != data: 125 raise error.TestError('Image data from tag is not the same as ' 126 'the test image data') 127 128 129 def _validate_mock_method_calls(self, calls): 130 """Validate the method calls made on the lorgnette mock instance. 131 132 @param calls list of MethodCall named tuples from mock lorgnette. 133 """ 134 135 if len(calls) != 2: 136 raise error.TestError('Expected 2 method calls but got: %r' % calls) 137 138 for index, method_name in enumerate(['ListScanners', 'ScanImage']): 139 if calls[index].method != method_name: 140 raise error.TestError('Call #%d was %s instead of expected %s' % 141 (index, calls[index].method, method_name)) 142 143 144 def run_once(self): 145 """Entry point of this test.""" 146 mouse_file = os.path.join(self.bindir, self._MOUSE_DESCRIPTION) 147 self._emulate_mouse(property_file=mouse_file) 148 149 self._extension_path = os.path.join(os.path.dirname(__file__), 150 'document_scan_test_app') 151 152 with chrome.Chrome(extension_paths=[self._extension_path], 153 is_component=False) as chrome_instance: 154 img = os.path.join(self.bindir, self._IMAGE_FILENAME) 155 with mock_lorgnette.MockLorgnette(img) as lorgnette_instance: 156 self._launch_app(chrome_instance) 157 158 self._play_events(self._PLAYBACK_FILE) 159 160 scan_count = self._get_scan_count() 161 if scan_count != 1: 162 raise error.TestError('Scan count is %d instead of 1' % 163 scan_count) 164 165 self._validate_image_data(lorgnette_instance.image_data) 166 self._validate_mock_method_calls( 167 lorgnette_instance.get_method_calls()) 168