Home | History | Annotate | Download | only in ui
      1 # Copyright (c) 2014 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 abc
      6 import datetime
      7 import os
      8 import urllib2
      9 
     10 from autotest_lib.client.bin import test, utils
     11 from autotest_lib.client.common_lib import error, file_utils, lsbrelease_utils
     12 from autotest_lib.client.cros import constants
     13 from autotest_lib.client.cros.image_comparison import image_comparison_factory
     14 from PIL import Image
     15 from PIL import ImageDraw
     16 
     17 class ui_TestBase(test.test):
     18     """ Encapsulates steps needed to collect screenshots for ui pieces.
     19 
     20     Each child class must implement:
     21     1. Abstract method capture_screenshot()
     22     Each child class will define its own custom way of capturing the screenshot
     23     of the piece it cares about.
     24 
     25     E.g Child class ui_SystemTray will capture system tray screenshot,
     26     ui_SettingsPage for the Chrome Settings page, etc.
     27 
     28     2. Abstract property test_area:
     29     This will get appended to screenshot file names so we know what image it is.
     30 
     31     Flow at runtime:
     32     At run time, autotest will call run_once() method on a particular child
     33     class object, call it Y.
     34 
     35     Say X is a parent of Y.
     36 
     37     Y.run_once() will save any values passed from control file so as to use them
     38     later.
     39 
     40     Y.run_once() will then call the parent's X.run_screenshot_comparison_test()
     41 
     42     This is the template algorithm for collecting screenshots.
     43 
     44     Y.run_screenshot_comparison_test will execute its steps. It will then call
     45     X.test_area to get custom string to use for project name and filename.
     46 
     47      It will execute more steps and then call capture_screenshot(). X doesn't
     48      implement that, but Y does, so the method will get called on Y to produce
     49      Y's custom behavior.
     50 
     51      Control will be returned to Y run_screenshot_comparison_test() which will
     52      execute remainder steps.
     53 
     54     """
     55 
     56     __metaclass__ = abc.ABCMeta
     57 
     58     WORKING_DIR = '/tmp/test'
     59     REMOTE_DIR = 'http://storage.googleapis.com/chromiumos-test-assets-public'
     60     AUTOTEST_CROS_UI_DIR = '/usr/local/autotest/cros/ui'
     61     IMG_COMP_CONF_FILE = 'image_comparison.conf'
     62 
     63     version = 2
     64 
     65 
     66     def run_screenshot_comparison_test(self):
     67         """
     68         Template method to run screenshot comparison tests for ui pieces.
     69 
     70         1. Set up test dirs.
     71         2. Create folder name
     72         3. Download golden image.
     73         4. Capture test image.
     74         5. Compare images locally, if FAIL upload to remote for analysis later.
     75         6. Clean up test dirs.
     76 
     77         """
     78 
     79         img_comp_conf_path = os.path.join(ui_TestBase.AUTOTEST_CROS_UI_DIR,
     80                                           ui_TestBase.IMG_COMP_CONF_FILE)
     81 
     82         img_comp_factory = image_comparison_factory.ImageComparisonFactory(
     83                 img_comp_conf_path)
     84 
     85         golden_image_local_dir = os.path.join(ui_TestBase.WORKING_DIR,
     86                                               'golden_images')
     87 
     88         file_utils.make_leaf_dir(golden_image_local_dir)
     89 
     90         filename = '%s.png' % self.tagged_testname
     91 
     92         golden_image_remote_path = os.path.join(
     93                 ui_TestBase.REMOTE_DIR,
     94                 'ui',
     95                 lsbrelease_utils.get_chrome_milestone(),
     96                 self.folder_name,
     97                 filename)
     98 
     99         golden_image_local_path = os.path.join(golden_image_local_dir, filename)
    100 
    101         test_image_filepath = os.path.join(ui_TestBase.WORKING_DIR, filename)
    102 
    103         try:
    104             file_utils.download_file(golden_image_remote_path,
    105                                      golden_image_local_path)
    106         except urllib2.HTTPError as e:
    107             warn = "No screenshot found for {0} on milestone {1}. ".format(
    108                 self.tagged_testname, lsbrelease_utils.get_chrome_milestone())
    109             warn += e.msg
    110             raise error.TestWarn(warn)
    111 
    112         self.capture_screenshot(test_image_filepath)
    113 
    114 
    115 
    116         comparer = img_comp_factory.make_pdiff_comparer()
    117         comp_res = comparer.compare(golden_image_local_path,
    118                                     test_image_filepath)
    119 
    120         if comp_res.diff_pixel_count > img_comp_factory.pixel_thres:
    121             publisher = img_comp_factory.make_imagediff_publisher(
    122                     self.resultsdir)
    123 
    124             # get chrome version
    125             version_string = utils.system_output(
    126                 constants.CHROME_VERSION_COMMAND, ignore_status=True)
    127             version_string = utils.parse_chrome_version(version_string)[0]
    128 
    129             # tags for publishing
    130             tags = {
    131                 'testname': self.tagged_testname,
    132                 'chromeos_version': utils.get_chromeos_release_version(),
    133                 'chrome_version': version_string,
    134                 'board':  utils.get_board(),
    135                 'date': datetime.date.today().strftime("%m/%d/%y"),
    136                 'diff_pixels': comp_res.diff_pixel_count
    137             }
    138 
    139             publisher.publish(golden_image_local_path,
    140                                     test_image_filepath,
    141                                     comp_res.pdiff_image_path, tags)
    142 
    143             raise error.TestFail('Test Failed. Please see image comparison '
    144                                  'result by opening index.html from the '
    145                                  'results directory.')
    146 
    147         file_utils.rm_dir_if_exists(ui_TestBase.WORKING_DIR)
    148 
    149 
    150     @property
    151     def folder_name(self):
    152         """
    153         Computes the folder name to look for golden images in
    154         based on the current test area.
    155 
    156         If we have tagged our testcase, it removes the tag to
    157         get the base testname.
    158 
    159         E.g if we add the tag 'guest' to the ui_SystemTray class,
    160         the tagged test name will be ui_SystemTray.guest
    161 
    162         This removes the tag if it was added
    163         """
    164 
    165         return self.tagged_testname.split('.')[0]
    166 
    167 
    168     @abc.abstractmethod
    169     def capture_screenshot(self, filepath):
    170         """
    171         Abstract method to capture a screenshot.
    172         Child classes must implement a custom way to take screenshots.
    173         This is because each will want to crop to different areas of the screen.
    174 
    175         @param filepath: string, complete path to save the screenshot.
    176 
    177         """
    178         pass
    179 
    180     def draw_image_mask(self, filepath, rectangle, fill='white'):
    181         """
    182         Used to draw a mask over selected portions of the captured screenshot.
    183         This allows us to mask out things that change between runs while
    184         letting us focus on the parts we do care about.
    185 
    186         @param filepath: string, the complete path to the image
    187         @param rectangle: tuple, the top left and bottom right coordinates
    188         @param fill: string, the color to fill the mask with
    189 
    190         """
    191 
    192         im = Image.open(filepath)
    193         draw = ImageDraw.Draw(im)
    194         draw.rectangle(rectangle, fill=fill)
    195         im.save(filepath)
    196