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.
5 from telemetry.core import command_line
6 from telemetry.page import test_expectations
7 from telemetry.page.actions import action_runner as action_runner_module
10 class Failure(Exception):
11 """Exception that can be thrown from PageTest to indicate an
12 undesired but designed-for problem."""
15 class TestNotSupportedOnPlatformFailure(Failure):
16 """Exception that can be thrown to indicate that a certain feature required
17 to run the test is not available on the platform, hardware configuration, or
21 class MeasurementFailure(Failure):
22 """Exception that can be thrown from MeasurePage to indicate an undesired but
23 designed-for problem."""
26 class PageTest(command_line.Command):
27 """A class styled on unittest.TestCase for creating page-specific tests.
29 Test should override ValidateAndMeasurePage to perform test
30 validation and page measurement as necessary.
32 class BodyChildElementMeasurement(PageTest):
33 def ValidateAndMeasurePage(self, page, tab, results):
34 body_child_count = tab.EvaluateJavaScript(
35 'document.body.children.length')
36 results.AddValue(scalar.ScalarValue(
37 page, 'body_children', 'count', body_child_count))
39 The class also provide hooks to add test-specific options. Here is
42 class BodyChildElementMeasurement(PageTest):
43 def AddCommandLineArgs(parser):
44 parser.add_option('--element', action='store', default='body')
46 def ValidateAndMeasurePage(self, page, tab, results):
47 body_child_count = tab.EvaluateJavaScript(
48 'document.querySelector('%s').children.length')
49 results.AddValue(scalar.ScalarValue(
50 page, 'children', 'count', child_count))
53 action_name_to_run: This is the method name in telemetry.page.Page
55 discard_first_run: Discard the first run of this page. This is
56 usually used with page_repeat and pageset_repeat options.
57 attempts: The number of attempts to run if we encountered
58 infrastructure problems (as opposed to test issues), such as
60 max_failures: The number of page failures allowed before we stop
62 is_action_name_to_run_optional: Determines what to do if
63 action_name_to_run is not empty but the page doesn't have that
64 action. The page will run (without any action) if
65 is_action_name_to_run_optional is True, otherwise the page
72 action_name_to_run='',
73 needs_browser_restart_after_each_page=False,
74 discard_first_result=False,
75 clear_cache_before_each_run=False,
78 is_action_name_to_run_optional=False):
79 super(PageTest, self).__init__()
82 if action_name_to_run:
83 assert action_name_to_run.startswith('Run') \
84 and '_' not in action_name_to_run, \
85 ('Wrong way of naming action_name_to_run. By new convention,'
86 'action_name_to_run must start with Run- prefix and in CamelCase.')
87 self._action_name_to_run = action_name_to_run
88 self._needs_browser_restart_after_each_page = (
89 needs_browser_restart_after_each_page)
90 self._discard_first_result = discard_first_result
91 self._clear_cache_before_each_run = clear_cache_before_each_run
92 self._close_tabs_before_run = True
93 self._attempts = attempts
94 self._max_failures = max_failures
95 self._is_action_name_to_run_optional = is_action_name_to_run_optional
96 assert self._attempts > 0, 'Test attempts must be greater than 0'
97 # If the test overrides the TabForPage method, it is considered a multi-tab
98 # test. The main difference between this and a single-tab test is that we
99 # do not attempt recovery for the former if a tab or the browser crashes,
100 # because we don't know the current state of tabs (how many are open, etc.)
101 self.is_multi_tab_test = (self.__class__ is not PageTest and
102 self.TabForPage.__func__ is not
103 self.__class__.__bases__[0].TabForPage.__func__)
104 # _exit_requested is set to true when the test requests an early exit.
105 self._exit_requested = False
108 def SetArgumentDefaults(cls, parser):
109 parser.set_defaults(**cls.options)
112 def discard_first_result(self):
113 """When set to True, the first run of the test is discarded. This is
114 useful for cases where it's desirable to have some test resource cached so
115 the first run of the test can warm things up. """
116 return self._discard_first_result
118 @discard_first_result.setter
119 def discard_first_result(self, discard):
120 self._discard_first_result = discard
123 def clear_cache_before_each_run(self):
124 """When set to True, the browser's disk and memory cache will be cleared
126 return self._clear_cache_before_each_run
129 def close_tabs_before_run(self):
130 """When set to True, all tabs are closed before running the test for the
132 return self._close_tabs_before_run
134 @close_tabs_before_run.setter
135 def close_tabs_before_run(self, close_tabs):
136 self._close_tabs_before_run = close_tabs
140 """Maximum number of times test will be attempted."""
141 return self._attempts
144 def attempts(self, count):
145 assert self._attempts > 0, 'Test attempts must be greater than 0'
146 self._attempts = count
149 def max_failures(self):
150 """Maximum number of failures allowed for the page set."""
151 return self._max_failures
154 def max_failures(self, count):
155 self._max_failures = count
158 # Define this method to avoid pylint errors.
159 # TODO(dtu): Make this actually run the test with args.page_set.
162 def RestartBrowserBeforeEachPage(self):
163 """ Should the browser be restarted for the page?
165 This returns true if the test needs to unconditionally restart the
166 browser for each page. It may be called before the browser is started.
168 return self._needs_browser_restart_after_each_page
170 def StopBrowserAfterPage(self, browser, page): # pylint: disable=W0613
171 """Should the browser be stopped after the page is run?
173 This is called after a page is run to decide whether the browser needs to
174 be stopped to clean up its state. If it is stopped, then it will be
175 restarted to run the next page.
177 A test that overrides this can look at both the page and the browser to
178 decide whether it needs to stop the browser.
182 def CustomizeBrowserOptions(self, options):
183 """Override to add test-specific options to the BrowserOptions object"""
185 def CustomizeBrowserOptionsForSinglePage(self, page, options):
186 """Set options specific to the test and the given page.
188 This will be called with the current page when the browser is (re)started.
189 Changing options at this point only makes sense if the browser is being
190 restarted for each page. Note that if page has a startup_url, the browser
191 will always be restarted for each run.
194 options.browser_options.startup_url = page.startup_url
196 def WillStartBrowser(self, platform):
197 """Override to manipulate the browser environment before it launches."""
199 def DidStartBrowser(self, browser):
200 """Override to customize the browser right after it has launched."""
202 def CanRunForPage(self, page): # pylint: disable=W0613
203 """Override to customize if the test can be ran for the given page."""
204 if self._action_name_to_run and not self._is_action_name_to_run_optional:
205 return hasattr(page, self._action_name_to_run)
208 def WillRunTest(self, options):
209 """Override to do operations before the page set(s) are navigated."""
210 self.options = options
212 def DidRunTest(self, browser, results): # pylint: disable=W0613
213 """Override to do operations after all page set(s) are completed.
215 This will occur before the browser is torn down.
219 def WillNavigateToPage(self, page, tab):
220 """Override to do operations before the page is navigated, notably Telemetry
221 will already have performed the following operations on the browser before
222 calling this function:
223 * Ensure only one tab is open.
224 * Call WaitForDocumentReadyStateToComplete on the tab."""
226 def DidNavigateToPage(self, page, tab):
227 """Override to do operations right after the page is navigated and after
228 all waiting for completion has occurred."""
230 def WillRunActions(self, page, tab):
231 """Override to do operations before running the actions on the page."""
233 def DidRunActions(self, page, tab):
234 """Override to do operations after running the actions on the page."""
236 def CleanUpAfterPage(self, page, tab):
237 """Called after the test run method was run, even if it failed."""
239 def CreateExpectations(self, page_set): # pylint: disable=W0613
240 """Override to make this test generate its own expectations instead of
241 any that may have been defined in the page set."""
242 return test_expectations.TestExpectations()
244 def TabForPage(self, page, browser): # pylint: disable=W0613
245 """Override to select a different tab for the page. For instance, to
246 create a new tab for every page, return browser.tabs.New()."""
247 return browser.tabs[0]
249 def ValidatePageSet(self, page_set):
250 """Override to examine the page set before the test run. Useful for
251 example to validate that the pageset can be used with the test."""
253 def ValidateAndMeasurePage(self, page, tab, results):
254 """Override to check test assertions and perform measurement.
256 When adding measurement results, call results.AddValue(...) for
257 each result. Raise an exception or add a failure.FailureValue on
258 failure. page_test.py also provides several base exception classes
261 Prefer metric value names that are in accordance with python
262 variable style. e.g., metric_name. The name 'url' must not be used.
265 def ValidateAndMeasurePage(self, page, tab, results):
266 res = tab.EvaluateJavaScript('2+2')
268 raise Exception('Oh, wow.')
269 results.AddValue(scalar.ScalarValue(
270 page, 'two_plus_two', 'count', res))
273 page: A telemetry.page.Page instance.
274 tab: A telemetry.core.Tab instance.
275 results: A telemetry.results.PageTestResults instance.
277 # TODO(chrishenry): Switch to raise NotImplementedError() when
278 # subclasses no longer override ValidatePage/MeasurePage.
279 self.ValidatePage(page, tab, results)
281 def ValidatePage(self, page, tab, results):
282 """DEPRECATED: Use ValidateAndMeasurePage instead."""
283 self.MeasurePage(page, tab, results)
285 def MeasurePage(self, page, tab, results):
286 """DEPRECATED: Use ValidateAndMeasurePage instead."""
288 def RunPage(self, page, tab, results):
290 interactive = self.options and self.options.interactive
291 action_runner = action_runner_module.ActionRunner(
292 tab, skip_waits=page.skip_waits)
293 self.WillRunActions(page, tab)
295 action_runner.PauseInteractive()
297 self._RunMethod(page, self._action_name_to_run, action_runner)
298 self.DidRunActions(page, tab)
300 self.ValidateAndMeasurePage(page, tab, results)
302 def _RunMethod(self, page, method_name, action_runner):
303 if hasattr(page, method_name):
304 run_method = getattr(page, method_name)
305 run_method(action_runner)
307 def RunNavigateSteps(self, page, tab):
308 """Navigates the tab to the page URL attribute.
310 Runs the 'navigate_steps' page attribute as a compound action.
312 action_runner = action_runner_module.ActionRunner(
313 tab, skip_waits=page.skip_waits)
314 page.RunNavigateSteps(action_runner)
317 return self._exit_requested
319 def RequestExit(self):
320 self._exit_requested = True
323 def action_name_to_run(self):
324 return self._action_name_to_run