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 """Runs a Google Maps pixel test. | 5 """Runs a Google Maps pixel test. |
6 Performs several common navigation actions on the map (pan, zoom, rotate) then | 6 Performs several common navigation actions on the map (pan, zoom, rotate) then |
7 captures a screenshot and compares selected pixels against expected values""" | 7 captures a screenshot and compares selected pixels against expected values""" |
8 | 8 |
9 import json | 9 import json |
| 10 import optparse |
10 import os | 11 import os |
11 import re | 12 import re |
12 | 13 |
| 14 import maps_expectations |
| 15 |
13 from telemetry import test | 16 from telemetry import test |
14 from telemetry.core.backends import png_bitmap | 17 from telemetry.core.backends import png_bitmap |
15 from telemetry.core import util | 18 from telemetry.core import util |
16 from telemetry.page import page_test | 19 from telemetry.page import page_test |
17 from telemetry.page import page_set | 20 from telemetry.page import page_set |
18 | 21 |
| 22 test_data_dir = os.path.abspath(os.path.join( |
| 23 os.path.dirname(__file__), '..', '..', 'data', 'gpu')) |
| 24 |
| 25 default_generated_data_dir = os.path.join(test_data_dir, 'generated') |
| 26 |
19 class MapsValidator(page_test.PageTest): | 27 class MapsValidator(page_test.PageTest): |
20 def __init__(self): | 28 def __init__(self): |
21 super(MapsValidator, self).__init__('ValidatePage') | 29 super(MapsValidator, self).__init__('ValidatePage') |
22 | 30 |
23 def CustomizeBrowserOptions(self, options): | 31 def CustomizeBrowserOptions(self, options): |
24 options.AppendExtraBrowserArgs('--enable-gpu-benchmarking') | 32 options.AppendExtraBrowserArgs('--enable-gpu-benchmarking') |
25 | 33 |
26 def ValidatePage(self, page, tab, results): | 34 def ValidatePage(self, page, tab, results): |
| 35 # TODO: This should not be necessary, but it's not clear if the test is |
| 36 # failing on the bots in it's absence. Remove once we can verify that it's |
| 37 # safe to do so. |
| 38 MapsValidator.SpinWaitOnRAF(tab, 3) |
| 39 |
27 if not tab.screenshot_supported: | 40 if not tab.screenshot_supported: |
28 raise page_test.Failure('Browser does not support screenshot capture') | 41 raise page_test.Failure('Browser does not support screenshot capture') |
29 screenshot = tab.Screenshot(5) | 42 screenshot = tab.Screenshot(5) |
30 if not screenshot: | 43 if not screenshot: |
31 raise page_test.Failure('Could not capture screenshot') | 44 raise page_test.Failure('Could not capture screenshot') |
32 | 45 |
| 46 dpr = tab.EvaluateJavaScript('window.devicePixelRatio') |
33 expected = MapsValidator.ReadPixelExpectations(page) | 47 expected = MapsValidator.ReadPixelExpectations(page) |
34 MapsValidator.CompareToExpectations(screenshot, expected) | 48 |
| 49 try: |
| 50 MapsValidator.CompareToExpectations(screenshot, expected, dpr) |
| 51 except page_test.Failure: |
| 52 image_name = MapsValidator.UrlToImageName(page.display_name) |
| 53 MapsValidator.WriteErrorImage(self.options.generated_dir, |
| 54 image_name, self.options.build_revision, screenshot) |
| 55 raise |
| 56 |
| 57 @staticmethod |
| 58 def SpinWaitOnRAF(tab, iterations, timeout = 60): |
| 59 waitScript = r""" |
| 60 window.__spinWaitOnRAFDone = false; |
| 61 var iterationsLeft = %d; |
| 62 |
| 63 function spin() { |
| 64 iterationsLeft--; |
| 65 if (iterationsLeft == 0) { |
| 66 window.__spinWaitOnRAFDone = true; |
| 67 return; |
| 68 } |
| 69 window.requestAnimationFrame(spin); |
| 70 } |
| 71 window.requestAnimationFrame(spin); |
| 72 """ % iterations |
| 73 |
| 74 def IsWaitComplete(): |
| 75 return tab.EvaluateJavaScript('window.__spinWaitOnRAFDone') |
| 76 |
| 77 tab.ExecuteJavaScript(waitScript) |
| 78 util.WaitFor(IsWaitComplete, timeout) |
35 | 79 |
36 @staticmethod | 80 @staticmethod |
37 def ReadPixelExpectations(page): | 81 def ReadPixelExpectations(page): |
38 expectations_path = os.path.join(page._base_dir, page.pixel_expectations) | 82 expectations_path = os.path.join(page._base_dir, page.pixel_expectations) |
39 with open(expectations_path, 'r') as f: | 83 with open(expectations_path, 'r') as f: |
40 json_contents = json.load(f) | 84 json_contents = json.load(f) |
41 return json_contents | 85 return json_contents |
42 | 86 |
43 @staticmethod | 87 @staticmethod |
44 def CompareToExpectations(screenshot, expectations): | 88 def CompareToExpectations(screenshot, expectations, devicePixelRatio): |
45 for expectation in expectations: | 89 for expectation in expectations: |
46 location = expectation["location"] | 90 location = expectation["location"] |
47 pixel_color = screenshot.GetPixelColor(location[0], location[1]) | 91 pixel_color = screenshot.GetPixelColor( |
| 92 location[0] * devicePixelRatio, |
| 93 location[1] * devicePixelRatio) |
48 expect_color = png_bitmap.PngColor( | 94 expect_color = png_bitmap.PngColor( |
49 expectation["color"][0], | 95 expectation["color"][0], |
50 expectation["color"][1], | 96 expectation["color"][1], |
51 expectation["color"][2]) | 97 expectation["color"][2]) |
52 iter_result = pixel_color.IsEqual(expect_color, expectation["tolerance"]) | 98 iter_result = pixel_color.IsEqual(expect_color, expectation["tolerance"]) |
53 if not iter_result: | 99 if not iter_result: |
54 raise page_test.Failure('FAILURE: Expected pixel at ' + str(location) + | 100 raise page_test.Failure('FAILURE: Expected pixel at ' + str(location) + |
55 ' to be ' + | 101 ' to be ' + |
56 str(expectation["color"]) + " but got [" + | 102 str(expectation["color"]) + " but got [" + |
57 str(pixel_color.r) + ", " + | 103 str(pixel_color.r) + ", " + |
58 str(pixel_color.g) + ", " + | 104 str(pixel_color.g) + ", " + |
59 str(pixel_color.b) + "]") | 105 str(pixel_color.b) + "]") |
60 | 106 |
| 107 @staticmethod |
| 108 def UrlToImageName(url): |
| 109 image_name = re.sub(r'^(http|https|file)://(/*)', '', url) |
| 110 image_name = re.sub(r'\.\./', '', image_name) |
| 111 image_name = re.sub(r'(\.|/|-)', '_', image_name) |
| 112 return image_name |
| 113 |
| 114 @staticmethod |
| 115 def WriteErrorImage(img_dir, img_name, build_revision, screenshot): |
| 116 full_image_name = img_name + '_' + str(build_revision) |
| 117 full_image_name = full_image_name + '.png' |
| 118 |
| 119 # This is a nasty and temporary hack: The pixel test archive step will copy |
| 120 # DIFF images directly, but for FAIL images it also requires a ref. This |
| 121 # allows us to archive the erronous image while the archiving step is being |
| 122 # refactored |
| 123 image_path = os.path.join(img_dir, 'DIFF_' + full_image_name) |
| 124 |
| 125 output_dir = os.path.dirname(image_path) |
| 126 if not os.path.exists(output_dir): |
| 127 os.makedirs(output_dir) |
| 128 |
| 129 screenshot.WriteFile(image_path) |
| 130 |
61 class Maps(test.Test): | 131 class Maps(test.Test): |
62 """Google Maps pixel tests.""" | 132 """Google Maps pixel tests.""" |
63 test = MapsValidator | 133 test = MapsValidator |
64 | 134 |
| 135 @staticmethod |
| 136 def AddTestCommandLineOptions(parser): |
| 137 group = optparse.OptionGroup(parser, 'Maps test options') |
| 138 group.add_option('--generated-dir', |
| 139 help='Overrides the default location for generated test images that ' |
| 140 'fail expectations checks', |
| 141 default=default_generated_data_dir) |
| 142 group.add_option('--build-revision', |
| 143 help='Chrome revision being tested.', |
| 144 default="unknownrev") |
| 145 parser.add_option_group(group) |
| 146 |
| 147 def CreateExpectations(self, page_set): |
| 148 return maps_expectations.MapsExpectations() |
| 149 |
65 def CreatePageSet(self, options): | 150 def CreatePageSet(self, options): |
66 page_set_path = os.path.join( | 151 page_set_path = os.path.join( |
67 util.GetChromiumSrcDir(), 'content', 'test', 'gpu', 'page_sets') | 152 util.GetChromiumSrcDir(), 'content', 'test', 'gpu', 'page_sets') |
68 page_set_dict = { | 153 page_set_dict = { |
69 'archive_data_file': 'data/maps.json', | 154 'archive_data_file': 'data/maps.json', |
70 'make_javascript_deterministic': False, | 155 'make_javascript_deterministic': False, |
71 'pages': [ | 156 'pages': [ |
72 { | 157 { |
| 158 'name': 'Maps.maps_001', |
73 'url': 'http://localhost:10020/tracker.html', | 159 'url': 'http://localhost:10020/tracker.html', |
74 'navigate_steps': [ | 160 'navigate_steps': [ |
75 { 'action': 'navigate' }, | 161 { 'action': 'navigate' }, |
76 { 'action': 'wait', 'javascript': 'window.testDone' } | 162 { 'action': 'wait', 'javascript': 'window.testDone' } |
77 ], | 163 ], |
78 'pixel_expectations': 'data/maps_001_expectations.json' | 164 'pixel_expectations': 'data/maps_001_expectations.json' |
79 } | 165 } |
80 ] | 166 ] |
81 } | 167 } |
82 | 168 |
83 return page_set.PageSet.FromDict(page_set_dict, page_set_path) | 169 return page_set.PageSet.FromDict(page_set_dict, page_set_path) |
OLD | NEW |