| 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 |