1# Copyright 2012 The Chromium 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 5from telemetry.core import command_line 6 7from telemetry.page import test_expectations 8from telemetry.page.actions import action_runner as action_runner_module 9 10 11class Failure(Exception): 12 """Exception that can be thrown from PageMeasurement to indicate an 13 undesired but designed-for problem.""" 14 15 16class TestNotSupportedOnPlatformFailure(Failure): 17 """Exception that can be thrown to indicate that a certain feature required 18 to run the test is not available on the platform, hardware configuration, or 19 browser version.""" 20 21 22class PageTest(command_line.Command): 23 """A class styled on unittest.TestCase for creating page-specific tests.""" 24 25 options = {} 26 27 def __init__(self, 28 action_name_to_run='', 29 needs_browser_restart_after_each_page=False, 30 discard_first_result=False, 31 clear_cache_before_each_run=False, 32 attempts=3, 33 max_failures=None, 34 max_errors=None, 35 is_action_name_to_run_optional=False): 36 super(PageTest, self).__init__() 37 38 self.options = None 39 if action_name_to_run: 40 assert action_name_to_run.startswith('Run') \ 41 and '_' not in action_name_to_run, \ 42 ('Wrong way of naming action_name_to_run. By new convention,' 43 'action_name_to_run must start with Run- prefix and in CamelCase.') 44 self._action_name_to_run = action_name_to_run 45 self._needs_browser_restart_after_each_page = ( 46 needs_browser_restart_after_each_page) 47 self._discard_first_result = discard_first_result 48 self._clear_cache_before_each_run = clear_cache_before_each_run 49 self._close_tabs_before_run = True 50 self._attempts = attempts 51 self._max_failures = max_failures 52 self._max_errors = max_errors 53 self._is_action_name_to_run_optional = is_action_name_to_run_optional 54 assert self._attempts > 0, 'Test attempts must be greater than 0' 55 # If the test overrides the TabForPage method, it is considered a multi-tab 56 # test. The main difference between this and a single-tab test is that we 57 # do not attempt recovery for the former if a tab or the browser crashes, 58 # because we don't know the current state of tabs (how many are open, etc.) 59 self.is_multi_tab_test = (self.__class__ is not PageTest and 60 self.TabForPage.__func__ is not 61 self.__class__.__bases__[0].TabForPage.__func__) 62 # _exit_requested is set to true when the test requests an early exit. 63 self._exit_requested = False 64 65 @classmethod 66 def SetArgumentDefaults(cls, parser): 67 parser.set_defaults(**cls.options) 68 69 @property 70 def discard_first_result(self): 71 """When set to True, the first run of the test is discarded. This is 72 useful for cases where it's desirable to have some test resource cached so 73 the first run of the test can warm things up. """ 74 return self._discard_first_result 75 76 @discard_first_result.setter 77 def discard_first_result(self, discard): 78 self._discard_first_result = discard 79 80 @property 81 def clear_cache_before_each_run(self): 82 """When set to True, the browser's disk and memory cache will be cleared 83 before each run.""" 84 return self._clear_cache_before_each_run 85 86 @property 87 def close_tabs_before_run(self): 88 """When set to True, all tabs are closed before running the test for the 89 first time.""" 90 return self._close_tabs_before_run 91 92 @close_tabs_before_run.setter 93 def close_tabs_before_run(self, close_tabs): 94 self._close_tabs_before_run = close_tabs 95 96 @property 97 def attempts(self): 98 """Maximum number of times test will be attempted.""" 99 return self._attempts 100 101 @attempts.setter 102 def attempts(self, count): 103 assert self._attempts > 0, 'Test attempts must be greater than 0' 104 self._attempts = count 105 106 @property 107 def max_failures(self): 108 """Maximum number of failures allowed for the page set.""" 109 return self._max_failures 110 111 @max_failures.setter 112 def max_failures(self, count): 113 self._max_failures = count 114 115 @property 116 def max_errors(self): 117 """Maximum number of errors allowed for the page set.""" 118 return self._max_errors 119 120 @max_errors.setter 121 def max_errors(self, count): 122 self._max_errors = count 123 124 def Run(self, args): 125 # Define this method to avoid pylint errors. 126 # TODO(dtu): Make this actually run the test with args.page_set. 127 pass 128 129 def RestartBrowserBeforeEachPage(self): 130 """ Should the browser be restarted for the page? 131 132 This returns true if the test needs to unconditionally restart the 133 browser for each page. It may be called before the browser is started. 134 """ 135 return self._needs_browser_restart_after_each_page 136 137 def StopBrowserAfterPage(self, browser, page): # pylint: disable=W0613 138 """Should the browser be stopped after the page is run? 139 140 This is called after a page is run to decide whether the browser needs to 141 be stopped to clean up its state. If it is stopped, then it will be 142 restarted to run the next page. 143 144 A test that overrides this can look at both the page and the browser to 145 decide whether it needs to stop the browser. 146 """ 147 return False 148 149 def CustomizeBrowserOptions(self, options): 150 """Override to add test-specific options to the BrowserOptions object""" 151 152 def CustomizeBrowserOptionsForSinglePage(self, page, options): 153 """Set options specific to the test and the given page. 154 155 This will be called with the current page when the browser is (re)started. 156 Changing options at this point only makes sense if the browser is being 157 restarted for each page. Note that if page has a startup_url, the browser 158 will always be restarted for each run. 159 """ 160 if page.startup_url: 161 options.browser_options.startup_url = page.startup_url 162 163 def WillStartBrowser(self, browser): 164 """Override to manipulate the browser environment before it launches.""" 165 166 def DidStartBrowser(self, browser): 167 """Override to customize the browser right after it has launched.""" 168 169 def CanRunForPage(self, page): # pylint: disable=W0613 170 """Override to customize if the test can be ran for the given page.""" 171 if self._action_name_to_run and not self._is_action_name_to_run_optional: 172 return hasattr(page, self._action_name_to_run) 173 return True 174 175 def WillRunTest(self, options): 176 """Override to do operations before the page set(s) are navigated.""" 177 self.options = options 178 179 def DidRunTest(self, browser, results): # pylint: disable=W0613 180 """Override to do operations after all page set(s) are completed. 181 182 This will occur before the browser is torn down. 183 """ 184 self.options = None 185 186 def WillRunPageRepeats(self, page): 187 """Override to do operations before each page is iterated over.""" 188 189 def DidRunPageRepeats(self, page): 190 """Override to do operations after each page is iterated over.""" 191 192 def DidStartHTTPServer(self, tab): 193 """Override to do operations after the HTTP server is started.""" 194 195 def WillNavigateToPage(self, page, tab): 196 """Override to do operations before the page is navigated, notably Telemetry 197 will already have performed the following operations on the browser before 198 calling this function: 199 * Ensure only one tab is open. 200 * Call WaitForDocumentReadyStateToComplete on the tab.""" 201 202 def DidNavigateToPage(self, page, tab): 203 """Override to do operations right after the page is navigated and after 204 all waiting for completion has occurred.""" 205 206 def WillRunActions(self, page, tab): 207 """Override to do operations before running the actions on the page.""" 208 209 def DidRunActions(self, page, tab): 210 """Override to do operations after running the actions on the page.""" 211 212 def CleanUpAfterPage(self, page, tab): 213 """Called after the test run method was run, even if it failed.""" 214 215 def CreateExpectations(self, page_set): # pylint: disable=W0613 216 """Override to make this test generate its own expectations instead of 217 any that may have been defined in the page set.""" 218 return test_expectations.TestExpectations() 219 220 def TabForPage(self, page, browser): # pylint: disable=W0613 221 """Override to select a different tab for the page. For instance, to 222 create a new tab for every page, return browser.tabs.New().""" 223 return browser.tabs[0] 224 225 def ValidatePageSet(self, page_set): 226 """Override to examine the page set before the test run. Useful for 227 example to validate that the pageset can be used with the test.""" 228 229 def ValidatePage(self, page, tab, results): 230 """Override to check the actual test assertions. 231 232 This is where most your test logic should go.""" 233 raise NotImplementedError() 234 235 def RunPage(self, page, tab, results): 236 # Run actions. 237 interactive = self.options and self.options.interactive 238 action_runner = action_runner_module.ActionRunner(tab) 239 self.WillRunActions(page, tab) 240 if interactive: 241 action_runner.PauseInteractive() 242 else: 243 self._RunMethod(page, self._action_name_to_run, action_runner) 244 self.DidRunActions(page, tab) 245 246 # Run validator. 247 self.ValidatePage(page, tab, results) 248 249 def _RunMethod(self, page, method_name, action_runner): 250 if hasattr(page, method_name): 251 run_method = getattr(page, method_name) 252 run_method(action_runner) 253 254 def RunNavigateSteps(self, page, tab): 255 """Navigates the tab to the page URL attribute. 256 257 Runs the 'navigate_steps' page attribute as a compound action. 258 """ 259 action_runner = action_runner_module.ActionRunner(tab) 260 page.RunNavigateSteps(action_runner) 261 262 def IsExiting(self): 263 return self._exit_requested 264 265 def RequestExit(self): 266 self._exit_requested = True 267 268 @property 269 def action_name_to_run(self): 270 return self._action_name_to_run 271