• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
5import abc
6import datetime
7import os
8import urllib2
9
10from autotest_lib.client.bin import test, utils
11from autotest_lib.client.common_lib import error, file_utils, lsbrelease_utils
12from autotest_lib.client.cros import constants
13from autotest_lib.client.cros.image_comparison import image_comparison_factory
14from PIL import Image
15from PIL import ImageDraw
16
17class 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