Home | History | Annotate | Download | only in documentscan_AppTestWithFakeLorgnette
      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