| OLD | NEW |
| 1 # Copyright 2013 The Chromium Authors. All rights reserved. | 1 # Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import json | 5 import json |
| 6 import logging | 6 import logging |
| 7 import os | 7 import os |
| 8 import time | |
| 9 from distutils.version import LooseVersion | 8 from distutils.version import LooseVersion |
| 10 from PIL import Image | 9 from PIL import Image |
| 11 | 10 |
| 12 from ..common import cloud_bucket | 11 import cloud_bucket |
| 13 from ..common import ispy_utils | 12 import ispy_utils |
| 14 | 13 |
| 15 | 14 |
| 16 class ChromeUtils(object): | 15 class ChromeUtils(object): |
| 17 """A utility for using ISpy with Chrome.""" | 16 """A utility for using ISpy with Chrome.""" |
| 18 | 17 |
| 19 def __init__(self, cloud_bucket, version_file, screenshot_func): | 18 def __init__(self, cloud_bucket): |
| 20 """Initializes the utility class. | 19 """Initializes the utility class. |
| 21 | 20 |
| 22 Args: | 21 Args: |
| 23 cloud_bucket: a BaseCloudBucket in which to the version file, | 22 cloud_bucket: a BaseCloudBucket in which to the version file, |
| 24 expectations and results are to be stored. | 23 expectations and results are to be stored. |
| 25 version_file: path to the version file in the cloud bucket. The version | |
| 26 file contains a json list of ordered Chrome versions for which | |
| 27 expectations exist. | |
| 28 screenshot_func: a function that returns a PIL.Image. | |
| 29 """ | 24 """ |
| 30 self._cloud_bucket = cloud_bucket | 25 self._cloud_bucket = cloud_bucket |
| 31 self._version_file = version_file | |
| 32 self._screenshot_func = screenshot_func | |
| 33 self._ispy = ispy_utils.ISpyUtils(self._cloud_bucket) | 26 self._ispy = ispy_utils.ISpyUtils(self._cloud_bucket) |
| 34 with open( | 27 self._rebaselineable_cache = {} |
| 35 os.path.join(os.path.dirname(__file__), 'wait_on_ajax.js'), 'r') as f: | |
| 36 self._wait_for_unchanging_dom_script = f.read() | |
| 37 | 28 |
| 38 def UpdateExpectationVersion(self, chrome_version): | 29 def UpdateExpectationVersion(self, chrome_version, version_file): |
| 39 """Updates the most recent expectation version to the Chrome version. | 30 """Updates the most recent expectation version to the Chrome version. |
| 40 | 31 |
| 41 Should be called after generating a new set of expectations. | 32 Should be called after generating a new set of expectations. |
| 42 | 33 |
| 43 Args: | 34 Args: |
| 44 chrome_version: the chrome version as a string of the form "31.0.123.4". | 35 chrome_version: the chrome version as a string of the form "31.0.123.4". |
| 36 version_file: path to the version file in the cloud bucket. The version |
| 37 file contains a json list of ordered Chrome versions for which |
| 38 expectations exist. |
| 45 """ | 39 """ |
| 46 insert_pos = 0 | 40 insert_pos = 0 |
| 47 expectation_versions = [] | 41 expectation_versions = [] |
| 48 try: | 42 try: |
| 49 expectation_versions = self._GetExpectationVersionList() | 43 expectation_versions = self._GetExpectationVersionList(version_file) |
| 50 if expectation_versions: | 44 if expectation_versions: |
| 51 try: | 45 try: |
| 52 version = self._GetExpectationVersion( | 46 version = self._GetExpectationVersion( |
| 53 chrome_version, expectation_versions) | 47 chrome_version, expectation_versions) |
| 54 if version == chrome_version: | 48 if version == chrome_version: |
| 55 return | 49 return |
| 56 insert_pos = expectation_versions.index(version) | 50 insert_pos = expectation_versions.index(version) |
| 57 except: | 51 except: |
| 58 insert_pos = len(expectation_versions) | 52 insert_pos = len(expectation_versions) |
| 59 except cloud_bucket.FileNotFoundError: | 53 except cloud_bucket.FileNotFoundError: |
| 60 pass | 54 pass |
| 61 expectation_versions.insert(insert_pos, chrome_version) | 55 expectation_versions.insert(insert_pos, chrome_version) |
| 62 logging.info('Updating expectation version...') | 56 logging.info('Updating expectation version...') |
| 63 self._cloud_bucket.UploadFile( | 57 self._cloud_bucket.UploadFile( |
| 64 self._version_file, json.dumps(expectation_versions), | 58 version_file, json.dumps(expectation_versions), |
| 65 'application/json') | 59 'application/json') |
| 66 | 60 |
| 67 def _GetExpectationVersion(self, chrome_version, expectation_versions): | 61 def _GetExpectationVersion(self, chrome_version, expectation_versions): |
| 68 """Returns the expectation version for the given Chrome version. | 62 """Returns the expectation version for the given Chrome version. |
| 69 | 63 |
| 70 Args: | 64 Args: |
| 71 chrome_version: the chrome version as a string of the form "31.0.123.4". | 65 chrome_version: the chrome version as a string of the form "31.0.123.4". |
| 72 expectation_versions: Ordered list of Chrome versions for which | 66 expectation_versions: Ordered list of Chrome versions for which |
| 73 expectations exist, as stored in the version file. | 67 expectations exist, as stored in the version file. |
| 74 | 68 |
| 75 Returns: | 69 Returns: |
| 76 Expectation version string. | 70 Expectation version string. |
| 77 """ | 71 """ |
| 78 # Find the closest version that is not greater than the chrome version. | 72 # Find the closest version that is not greater than the chrome version. |
| 79 for version in expectation_versions: | 73 for version in expectation_versions: |
| 80 if LooseVersion(version) <= LooseVersion(chrome_version): | 74 if LooseVersion(version) <= LooseVersion(chrome_version): |
| 81 return version | 75 return version |
| 82 raise Exception('No expectation exists for Chrome %s' % chrome_version) | 76 raise Exception('No expectation exists for Chrome %s' % chrome_version) |
| 83 | 77 |
| 84 def _GetExpectationVersionList(self): | 78 def _GetExpectationVersionList(self, version_file): |
| 85 """Gets the list of expectation versions from google storage.""" | 79 """Gets the list of expectation versions from google storage. |
| 86 return json.loads(self._cloud_bucket.DownloadFile(self._version_file)) | 80 |
| 81 Args: |
| 82 version_file: path to the version file in the cloud bucket. The version |
| 83 file contains a json list of ordered Chrome versions for which |
| 84 expectations exist. |
| 85 |
| 86 Returns: |
| 87 Ordered list of Chrome versions. |
| 88 """ |
| 89 return json.loads(self._cloud_bucket.DownloadFile(version_file)) |
| 87 | 90 |
| 88 def _GetExpectationNameWithVersion(self, device_type, expectation, | 91 def _GetExpectationNameWithVersion(self, device_type, expectation, |
| 89 chrome_version): | 92 chrome_version, version_file): |
| 90 """Get the expectation to be used with the current Chrome version. | 93 """Get the expectation to be used with the current Chrome version. |
| 91 | 94 |
| 92 Args: | 95 Args: |
| 93 device_type: string identifier for the device type. | 96 device_type: string identifier for the device type. |
| 94 expectation: name for the expectation to generate. | 97 expectation: name for the expectation to generate. |
| 95 chrome_version: the chrome version as a string of the form "31.0.123.4". | 98 chrome_version: the chrome version as a string of the form "31.0.123.4". |
| 96 | 99 |
| 97 Returns: | 100 Returns: |
| 98 Version as an integer. | 101 Version as an integer. |
| 99 """ | 102 """ |
| 100 version = self._GetExpectationVersion( | 103 version = self._GetExpectationVersion( |
| 101 chrome_version, self._GetExpectationVersionList()) | 104 chrome_version, self._GetExpectationVersionList(version_file)) |
| 102 return self._CreateExpectationName(device_type, expectation, version) | 105 return self._CreateExpectationName(device_type, expectation, version) |
| 103 | 106 |
| 104 def _CreateExpectationName(self, device_type, expectation, version): | 107 def _CreateExpectationName(self, device_type, expectation, version): |
| 105 """Create the full expectation name from the expectation and version. | 108 """Create the full expectation name from the expectation and version. |
| 106 | 109 |
| 107 Args: | 110 Args: |
| 108 device_type: string identifier for the device type, example: mako | 111 device_type: string identifier for the device type, example: mako |
| 109 expectation: base name for the expectation, example: google.com | 112 expectation: base name for the expectation, example: google.com |
| 110 version: expectation version, example: 31.0.23.1 | 113 version: expectation version, example: 31.0.23.1 |
| 111 | 114 |
| 112 Returns: | 115 Returns: |
| 113 Full expectation name as a string, example: mako:google.com(31.0.23.1) | 116 Full expectation name as a string, example: mako:google.com(31.0.23.1) |
| 114 """ | 117 """ |
| 115 return '%s:%s(%s)' % (device_type, expectation, version) | 118 return '%s:%s(%s)' % (device_type, expectation, version) |
| 116 | 119 |
| 117 def GenerateExpectation(self, device_type, expectation, chrome_version): | 120 def GenerateExpectation(self, device_type, expectation, chrome_version, |
| 118 """Take screenshots and store as an expectation in I-Spy. | 121 version_file, screenshots): |
| 122 """Create an expectation for I-Spy. |
| 119 | 123 |
| 120 Args: | 124 Args: |
| 121 device_type: string identifier for the device type. | 125 device_type: string identifier for the device type. |
| 122 expectation: name for the expectation to generate. | 126 expectation: name for the expectation to generate. |
| 123 chrome_version: the chrome version as a string of the form "31.0.123.4". | 127 chrome_version: the chrome version as a string of the form "31.0.123.4". |
| 128 screenshots: a list of similar PIL.Images. |
| 124 """ | 129 """ |
| 125 # https://code.google.com/p/chromedriver/issues/detail?id=463 | 130 # https://code.google.com/p/chromedriver/issues/detail?id=463 |
| 126 time.sleep(1) | |
| 127 expectation_with_version = self._CreateExpectationName( | 131 expectation_with_version = self._CreateExpectationName( |
| 128 device_type, expectation, chrome_version) | 132 device_type, expectation, chrome_version) |
| 129 if self._ispy.ExpectationExists(expectation_with_version): | 133 if self._ispy.ExpectationExists(expectation_with_version): |
| 130 logging.warning( | 134 logging.warning( |
| 131 'I-Spy expectation \'%s\' already exists, overwriting.', | 135 'I-Spy expectation \'%s\' already exists, overwriting.', |
| 132 expectation_with_version) | 136 expectation_with_version) |
| 133 screenshots = [self._screenshot_func() for _ in range(8)] | |
| 134 logging.info('Generating I-Spy expectation...') | 137 logging.info('Generating I-Spy expectation...') |
| 135 self._ispy.GenerateExpectation(expectation_with_version, screenshots) | 138 self._ispy.GenerateExpectation(expectation_with_version, screenshots) |
| 136 | 139 |
| 137 def PerformComparison(self, test_run, device_type, expectation, | 140 def PerformComparison(self, test_run, device_type, expectation, |
| 138 chrome_version): | 141 chrome_version, version_file, screenshot): |
| 139 """Take a screenshot and compare it with the given expectation in I-Spy. | 142 """Compare a screenshot with the given expectation in I-Spy. |
| 140 | 143 |
| 141 Args: | 144 Args: |
| 142 test_run: name for the test run. | 145 test_run: name for the test run. |
| 143 device_type: string identifier for the device type. | 146 device_type: string identifier for the device type. |
| 144 expectation: name for the expectation to compare against. | 147 expectation: name for the expectation to compare against. |
| 145 chrome_version: the chrome version as a string of the form "31.0.123.4". | 148 chrome_version: the chrome version as a string of the form "31.0.123.4". |
| 149 screenshot: a PIL.Image to compare. |
| 146 """ | 150 """ |
| 147 # https://code.google.com/p/chromedriver/issues/detail?id=463 | 151 # https://code.google.com/p/chromedriver/issues/detail?id=463 |
| 148 time.sleep(1) | |
| 149 screenshot = self._screenshot_func() | |
| 150 logging.info('Performing I-Spy comparison...') | 152 logging.info('Performing I-Spy comparison...') |
| 151 self._ispy.PerformComparison( | 153 self._ispy.PerformComparison( |
| 152 test_run, | 154 test_run, |
| 153 self._GetExpectationNameWithVersion( | 155 self._GetExpectationNameWithVersion( |
| 154 device_type, expectation, chrome_version), | 156 device_type, expectation, chrome_version, version_file), |
| 155 screenshot) | 157 screenshot) |
| 156 | 158 |
| 157 def GetScriptToWaitForUnchangingDOM(self): | 159 def CanRebaselineToTestRun(self, test_run): |
| 158 """Returns a JavaScript script that waits for the DOM to stop changing.""" | 160 """Returns whether the test run has associated expectations. |
| 159 return self._wait_for_unchanging_dom_script | |
| 160 | 161 |
| 162 Returns: |
| 163 True if RebaselineToTestRun() can be called for this test run. |
| 164 """ |
| 165 if test_run in self._rebaselineable_cache: |
| 166 return True |
| 167 return self._cloud_bucket.FileExists( |
| 168 ispy_utils.GetTestRunPath(test_run, 'rebaseline.txt')) |
| 169 |
| 170 def RebaselineToTestRun(self, test_run): |
| 171 """Update the version file to use expectations associated with |test_run|. |
| 172 |
| 173 Args: |
| 174 test_run: The name of the test run to rebaseline. |
| 175 """ |
| 176 rebaseline_path = ispy_utils.GetTestRunPath(test_run, 'rebaseline.txt') |
| 177 rebaseline_attrib = json.loads( |
| 178 self._cloud_bucket.DownloadFile(rebaseline_path)) |
| 179 self.UpdateExpectationVersion( |
| 180 rebaseline_attrib['version'], rebaseline_attrib['version_file']) |
| 181 self._cloud_bucket.RemoveFile(rebaseline_path) |
| 182 |
| 183 def _SetTestRunRebaselineable(self, test_run, chrome_version, version_file): |
| 184 """Writes a JSON file containing the data needed to rebaseline. |
| 185 |
| 186 Args: |
| 187 test_run: The name of the test run to add the rebaseline file to. |
| 188 chrome_version: the chrome version that can be rebaselined to (must have |
| 189 associated Expectations). |
| 190 version_file: the path of the version file associated with the test run. |
| 191 """ |
| 192 self._rebaselineable_cache[test_run] = True |
| 193 self._cloud_bucket.UploadFile( |
| 194 ispy_utils.GetTestRunPath(test_run, 'rebaseline.txt'), |
| 195 json.dumps({ |
| 196 'version': chrome_version, |
| 197 'version_file': version_file}), |
| 198 'application/json') |
| 199 |
| 200 def PerformComparisonAndPrepareExpectation(self, test_run, device_type, |
| 201 expectation, chrome_version, |
| 202 version_file, screenshots): |
| 203 """Perform comparison and generate an expectation that can used later. |
| 204 |
| 205 The test run web UI will have a button to set the Expectations generated for |
| 206 this version as the expectation for comparison with later versions. |
| 207 |
| 208 Args: |
| 209 test_run: The name of the test run to add the rebaseline file to. |
| 210 device_type: string identifier for the device type. |
| 211 chrome_version: the chrome version that can be rebaselined to (must have |
| 212 associated Expectations). |
| 213 version_file: the path of the version file associated with the test run. |
| 214 screenshot: a list of similar PIL.Images. |
| 215 """ |
| 216 if not self.CanRebaselineToTestRun(test_run): |
| 217 self._SetTestRunRebaselineable(test_run, chrome_version, version_file) |
| 218 expectation_with_version = self._CreateExpectationName( |
| 219 device_type, expectation, chrome_version) |
| 220 self._ispy.GenerateExpectation(expectation_with_version, screenshots) |
| 221 self._ispy.PerformComparison( |
| 222 test_run, |
| 223 self._GetExpectationNameWithVersion( |
| 224 device_type, expectation, chrome_version, version_file), |
| 225 screenshots[-1]) |
| 226 |
| OLD | NEW |