OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright 2012 The Chromium Authors. All rights reserved. | 2 # Copyright 2012 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 import logging | 5 import logging |
6 import os | 6 import os |
7 import sys | 7 import sys |
8 import tempfile | 8 import tempfile |
9 import time | 9 import time |
10 | 10 |
11 from telemetry import benchmark | 11 from telemetry import benchmark |
12 from telemetry.core import browser_options | 12 from telemetry.core import browser_options |
13 from telemetry.core import discover | 13 from telemetry.core import discover |
14 from telemetry.core import wpr_modes | 14 from telemetry.core import wpr_modes |
15 from telemetry.page import page_measurement | 15 from telemetry.page import page_measurement |
16 from telemetry.page import page_runner | 16 from telemetry.page import page_runner |
17 from telemetry.page import page_set | 17 from telemetry.page import page_set |
18 from telemetry.page import page_test | 18 from telemetry.page import page_test |
19 from telemetry.page import profile_creator | 19 from telemetry.page import profile_creator |
20 from telemetry.page import test_expectations | 20 from telemetry.page import test_expectations |
21 from telemetry.page.actions import action_runner as action_runner_module | |
22 from telemetry.results import page_measurement_results | 21 from telemetry.results import page_measurement_results |
23 | 22 |
24 | 23 |
25 class RecordPage(page_test.PageTest): # pylint: disable=W0223 | 24 class RecorderPageTest(page_test.PageTest): # pylint: disable=W0223 |
26 def __init__(self, measurements): | 25 def __init__(self, action_names): |
27 # This class overwrites PageTest.Run, so that the test method name is not | 26 super(RecorderPageTest, self).__init__() |
28 # really used (except for throwing an exception if it doesn't exist). | 27 self._action_names = action_names |
29 super(RecordPage, self).__init__('Run') | 28 self.page_test = None |
30 self._action_names = set( | |
31 [measurement().action_name_to_run | |
32 for measurement in measurements.values() | |
33 if measurement().action_name_to_run]) | |
34 self.test = None | |
35 | 29 |
36 def CanRunForPage(self, page): | 30 def CanRunForPage(self, page): |
37 return page.url.startswith('http') | 31 return page.url.startswith('http') |
38 | 32 |
39 def WillNavigateToPage(self, page, tab): | 33 def WillNavigateToPage(self, page, tab): |
40 """Override to ensure all resources are fetched from network.""" | 34 """Override to ensure all resources are fetched from network.""" |
41 tab.ClearCache(force=False) | 35 tab.ClearCache(force=False) |
42 if self.test: | 36 if self.page_test: |
43 self.test.options = self.options | 37 self.page_test.options = self.options |
44 self.test.WillNavigateToPage(page, tab) | 38 self.page_test.WillNavigateToPage(page, tab) |
45 | 39 |
46 def DidNavigateToPage(self, page, tab): | 40 def DidNavigateToPage(self, page, tab): |
47 """Forward the call to the test.""" | 41 if self.page_test: |
48 if self.test: | 42 self.page_test.DidNavigateToPage(page, tab) |
49 self.test.DidNavigateToPage(page, tab) | 43 |
| 44 def WillRunActions(self, page, tab): |
| 45 if self.page_test: |
| 46 self.page_test.WillRunActions(page, tab) |
| 47 |
| 48 def DidRunActions(self, page, tab): |
| 49 if self.page_test: |
| 50 self.page_test.DidRunActions(page, tab) |
| 51 |
| 52 def ValidatePage(self, page, tab, results): |
| 53 if self.page_test: |
| 54 self.page_test.ValidatePage(page, tab, results) |
50 | 55 |
51 def RunPage(self, page, tab, results): | 56 def RunPage(self, page, tab, results): |
52 tab.WaitForDocumentReadyStateToBeComplete() | 57 tab.WaitForDocumentReadyStateToBeComplete() |
53 | 58 |
54 # When recording, sleep to catch any resources that load post-onload. | 59 # When recording, sleep to catch any resources that load post-onload. |
55 # TODO(tonyg): This should probably monitor resource timing for activity | 60 # TODO(tonyg): This should probably monitor resource timing for activity |
56 # and sleep until 2s since the last network event with some timeout like | 61 # and sleep until 2s since the last network event with some timeout like |
57 # 20s. We could wrap this up as WaitForNetworkIdle() and share with the | 62 # 20s. We could wrap this up as WaitForNetworkIdle() and share with the |
58 # speed index metric. | 63 # speed index metric. |
59 time.sleep(3) | 64 time.sleep(3) |
60 | 65 |
61 # Run the actions for all measurements. Reload the page between | 66 # When running record_wpr, results is a GTestTestResults, so we create a |
62 # actions. | 67 # dummy PageMeasurementResults that implements the functions we use. |
| 68 # TODO(chrishenry): Fix the need for a dummy_results object. |
| 69 dummy_results = page_measurement_results.PageMeasurementResults() |
| 70 |
| 71 if self.page_test: |
| 72 self._action_name_to_run = self.page_test.action_name_to_run |
| 73 self.page_test.RunPage(page, tab, dummy_results) |
| 74 return |
| 75 |
63 should_reload = False | 76 should_reload = False |
64 interactive = self.options and self.options.interactive | 77 # Run the actions on the page for all available measurements. |
65 for action_name in self._action_names: | 78 for action_name in self._action_names: |
| 79 # Skip this action if it is not defined |
66 if not hasattr(page, action_name): | 80 if not hasattr(page, action_name): |
67 continue | 81 continue |
| 82 # Reload the page between actions to start with a clean slate. |
68 if should_reload: | 83 if should_reload: |
69 self.RunNavigateSteps(page, tab) | 84 self.RunNavigateSteps(page, tab) |
70 action_runner = action_runner_module.ActionRunner(tab) | 85 self._action_name_to_run = action_name |
71 if interactive: | 86 super(RecorderPageTest, self).RunPage(page, tab, dummy_results) |
72 action_runner.PauseInteractive() | |
73 else: | |
74 self._RunMethod(page, action_name, action_runner) | |
75 should_reload = True | 87 should_reload = True |
76 | 88 |
77 # Run the PageTest's validator, so that we capture any additional resources | 89 def RunNavigateSteps(self, page, tab): |
78 # that are loaded by the test. | 90 if self.page_test: |
79 if self.test: | 91 self.page_test.RunNavigateSteps(page, tab) |
80 dummy_results = page_measurement_results.PageMeasurementResults() | 92 else: |
81 self.test.ValidatePage(page, tab, dummy_results) | 93 super(RecorderPageTest, self).RunNavigateSteps(page, tab) |
| 94 |
| 95 |
| 96 def FindAllActionNames(base_dir): |
| 97 """Returns a set of of all action names used in our measurements.""" |
| 98 action_names = set() |
| 99 # Get all PageMeasurements except for ProfileCreators (see crbug.com/319573) |
| 100 for _, cls in discover.DiscoverClasses( |
| 101 base_dir, base_dir, page_measurement.PageMeasurement).items(): |
| 102 if not issubclass(cls, profile_creator.ProfileCreator): |
| 103 action_name = cls().action_name_to_run |
| 104 if action_name: |
| 105 action_names.add(action_name) |
| 106 return action_names |
| 107 |
| 108 |
| 109 def _MaybeGetInstanceOfClass(target, base_dir, cls): |
| 110 if isinstance(target, cls): |
| 111 return target |
| 112 classes = discover.DiscoverClasses(base_dir, base_dir, cls, |
| 113 index_by_class_name=True) |
| 114 return classes[target]() if target in classes else None |
| 115 |
| 116 |
| 117 class WprRecorder(object): |
| 118 |
| 119 def __init__(self, base_dir, target, extra_args=None): |
| 120 action_names_to_run = FindAllActionNames(base_dir) |
| 121 self._record_page_test = RecorderPageTest(action_names_to_run) |
| 122 self._temp_target_wpr_file_path = tempfile.mkstemp()[1] |
| 123 self._options = self._CreateOptions() |
| 124 |
| 125 self._benchmark = _MaybeGetInstanceOfClass(target, base_dir, |
| 126 benchmark.Benchmark) |
| 127 if self._benchmark is not None: |
| 128 self._record_page_test.page_test = self._benchmark.test() |
| 129 self._parser = self._options.CreateParser(usage='%prog <PageSet|Benchmark>') |
| 130 self._AddCommandLineArgs() |
| 131 self._ParseArgs(extra_args) |
| 132 self._ProcessCommandLineArgs() |
| 133 self._page_set = self._GetPageSet(base_dir, target) |
| 134 |
| 135 @property |
| 136 def options(self): |
| 137 return self._options |
| 138 |
| 139 def _CreateOptions(self): |
| 140 options = browser_options.BrowserFinderOptions() |
| 141 options.browser_options.wpr_mode = wpr_modes.WPR_RECORD |
| 142 options.browser_options.no_proxy_server = True |
| 143 return options |
| 144 |
| 145 def _AddCommandLineArgs(self): |
| 146 page_runner.AddCommandLineArgs(self._parser) |
| 147 if self._benchmark is not None: |
| 148 self._benchmark.AddCommandLineArgs(self._parser) |
| 149 self._benchmark.SetArgumentDefaults(self._parser) |
| 150 |
| 151 def _ParseArgs(self, extra_args=None): |
| 152 args = sys.argv[1:] |
| 153 if extra_args is not None: |
| 154 args += extra_args |
| 155 self._parser.parse_args(args) |
| 156 |
| 157 def _ProcessCommandLineArgs(self): |
| 158 page_runner.ProcessCommandLineArgs(self._parser, self._options) |
| 159 if self._benchmark is not None: |
| 160 self._benchmark.ProcessCommandLineArgs(self._parser, self._options) |
| 161 |
| 162 def _GetPageSet(self, base_dir, target): |
| 163 if self._benchmark is not None: |
| 164 return self._benchmark.CreatePageSet(self._options) |
| 165 ps = _MaybeGetInstanceOfClass(target, base_dir, page_set.PageSet) |
| 166 if ps is None: |
| 167 self._parser.print_usage() |
| 168 sys.exit(1) |
| 169 return ps |
| 170 |
| 171 def Record(self): |
| 172 self._page_set.wpr_archive_info.AddNewTemporaryRecording( |
| 173 self._temp_target_wpr_file_path) |
| 174 self._record_page_test.CustomizeBrowserOptions(self._options) |
| 175 return page_runner.Run(self._record_page_test, self._page_set, |
| 176 test_expectations.TestExpectations(), self._options) |
| 177 |
| 178 def HandleResults(self, results): |
| 179 if results.failures or results.skipped: |
| 180 logging.warning('Some pages failed and/or were skipped. The recording ' |
| 181 'has not been updated for these pages.') |
| 182 results.PrintSummary() |
| 183 |
| 184 if results.successes: |
| 185 # Update the metadata for the pages which were recorded. |
| 186 self._page_set.wpr_archive_info.AddRecordedPages(results.successes) |
| 187 else: |
| 188 os.remove(self._temp_target_wpr_file_path) |
82 | 189 |
83 | 190 |
84 def Main(base_dir): | 191 def Main(base_dir): |
85 measurements = { | |
86 n: cls for n, cls in discover.DiscoverClasses( | |
87 base_dir, base_dir, page_measurement.PageMeasurement).items() | |
88 # Filter out unneeded ProfileCreators (crbug.com/319573). | |
89 if not issubclass(cls, profile_creator.ProfileCreator) | |
90 } | |
91 tests = discover.DiscoverClasses(base_dir, base_dir, benchmark.Benchmark, | |
92 index_by_class_name=True) | |
93 | |
94 options = browser_options.BrowserFinderOptions() | |
95 parser = options.CreateParser('%prog <PageSet|Test|URL>') | |
96 page_runner.AddCommandLineArgs(parser) | |
97 | |
98 recorder = RecordPage(measurements) | |
99 recorder.AddCommandLineArgs(parser) | |
100 | |
101 quick_args = [a for a in sys.argv[1:] if not a.startswith('-')] | 192 quick_args = [a for a in sys.argv[1:] if not a.startswith('-')] |
102 if len(quick_args) != 1: | 193 if len(quick_args) != 1: |
103 parser.print_usage() | 194 print >> sys.stderr, 'Usage: record_wpr <PageSet|Benchmark>\n' |
104 sys.exit(1) | 195 sys.exit(1) |
105 target = quick_args[0] | 196 target = quick_args.pop() |
106 if target in tests: | 197 wpr_recorder = WprRecorder(base_dir, target) |
107 recorder.test = tests[target]().test() | 198 results = wpr_recorder.Record() |
108 recorder.test.AddCommandLineArgs(parser) | 199 wpr_recorder.HandleResults(results) |
109 recorder.test.SetArgumentDefaults(parser) | |
110 parser.parse_args() | |
111 recorder.test.ProcessCommandLineArgs(parser, options) | |
112 ps = tests[target]().CreatePageSet(options) | |
113 elif discover.IsPageSetFile(target): | |
114 parser.parse_args() | |
115 ps = page_set.PageSet.FromFile(target) | |
116 else: | |
117 parser.print_usage() | |
118 sys.exit(1) | |
119 | |
120 page_runner.ProcessCommandLineArgs(parser, options) | |
121 recorder.ProcessCommandLineArgs(parser, options) | |
122 | |
123 expectations = test_expectations.TestExpectations() | |
124 | |
125 # Set the archive path to something temporary. | |
126 temp_target_wpr_file_path = tempfile.mkstemp()[1] | |
127 ps.wpr_archive_info.AddNewTemporaryRecording(temp_target_wpr_file_path) | |
128 | |
129 # Do the actual recording. | |
130 options.browser_options.wpr_mode = wpr_modes.WPR_RECORD | |
131 options.browser_options.no_proxy_server = True | |
132 recorder.CustomizeBrowserOptions(options) | |
133 results = page_runner.Run(recorder, ps, expectations, options) | |
134 | |
135 if results.failures: | |
136 logging.warning('Some pages failed. The recording has not been updated for ' | |
137 'these pages.') | |
138 logging.warning('Failed pages:\n%s', '\n'.join( | |
139 p.display_name for p in results.pages_that_had_failures)) | |
140 | |
141 if results.skipped: | |
142 logging.warning('Some pages were skipped. The recording has not been ' | |
143 'updated for these pages.') | |
144 logging.warning('Skipped pages:\n%s', '\n'.join( | |
145 p.display_name for p in zip(*results.skipped)[0])) | |
146 | |
147 if results.successes: | |
148 # Update the metadata for the pages which were recorded. | |
149 ps.wpr_archive_info.AddRecordedPages(results.successes) | |
150 else: | |
151 os.remove(temp_target_wpr_file_path) | |
152 | |
153 return min(255, len(results.failures)) | 200 return min(255, len(results.failures)) |
OLD | NEW |