1 # Copyright (c) 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.
6 from telemetry.page import test_expectations
7 from telemetry.page.actions import all_page_actions
8 from telemetry.page.actions import interact
9 from telemetry.page.actions import navigate
10 from telemetry.page.actions import page_action
13 def _GetActionFromData(action_data):
14 action_name = action_data['action']
15 action = all_page_actions.FindClassWithName(action_name)
17 logging.critical('Could not find an action named %s.', action_name)
18 logging.critical('Check the page set for a typo and check the error '
19 'log for possible Python loading/compilation errors.')
20 raise Exception('Action "%s" not found.' % action_name)
21 return action(action_data)
24 def GetSubactionFromData(page, subaction_data, interactive):
25 subaction_name = subaction_data['action']
26 if hasattr(page, subaction_name):
27 return GetCompoundActionFromPage(page, subaction_name, interactive)
29 return [_GetActionFromData(subaction_data)]
32 def GetCompoundActionFromPage(page, action_name, interactive=False):
34 return [interact.InteractAction()]
39 action_data_list = getattr(page, action_name)
40 if not isinstance(action_data_list, list):
41 action_data_list = [action_data_list]
44 for subaction_data in action_data_list:
45 for _ in xrange(subaction_data.get('repeat', 1)):
46 action_list += GetSubactionFromData(page, subaction_data, interactive)
50 class Failure(Exception):
51 """Exception that can be thrown from PageMeasurement to indicate an
52 undesired but designed-for problem."""
56 class PageTest(object):
57 """A class styled on unittest.TestCase for creating page-specific tests."""
61 action_name_to_run='',
62 needs_browser_restart_after_each_run=False,
63 discard_first_result=False,
64 clear_cache_before_each_run=False,
68 self._test_method = getattr(self, test_method_name)
69 except AttributeError:
70 raise ValueError, 'No such method %s.%s' % (
71 self.__class_, test_method_name) # pylint: disable=E1101
72 self._action_name_to_run = action_name_to_run
73 self._needs_browser_restart_after_each_run = (
74 needs_browser_restart_after_each_run)
75 self._discard_first_result = discard_first_result
76 self._clear_cache_before_each_run = clear_cache_before_each_run
77 self._close_tabs_before_run = True
78 self._attempts = attempts
79 assert self._attempts > 0, 'Test attempts must be greater than 0'
80 # If the test overrides the TabForPage method, it is considered a multi-tab
81 # test. The main difference between this and a single-tab test is that we
82 # do not attempt recovery for the former if a tab or the browser crashes,
83 # because we don't know the current state of tabs (how many are open, etc.)
84 self.is_multi_tab_test = (self.__class__ is not PageTest and
85 self.TabForPage.__func__ is not
86 self.__class__.__bases__[0].TabForPage.__func__)
87 # _exit_requested is set to true when the test requests an early exit.
88 self._exit_requested = False
91 def discard_first_result(self):
92 """When set to True, the first run of the test is discarded. This is
93 useful for cases where it's desirable to have some test resource cached so
94 the first run of the test can warm things up. """
95 return self._discard_first_result
97 @discard_first_result.setter
98 def discard_first_result(self, discard):
99 self._discard_first_result = discard
102 def clear_cache_before_each_run(self):
103 """When set to True, the browser's disk and memory cache will be cleared
105 return self._clear_cache_before_each_run
108 def close_tabs_before_run(self):
109 """When set to True, all tabs are closed before running the test for the
111 return self._close_tabs_before_run
113 @close_tabs_before_run.setter
114 def close_tabs_before_run(self, close_tabs):
115 self._close_tabs_before_run = close_tabs
119 """Maximum number of times test will be attempted."""
120 return self._attempts
123 def attempts(self, count):
124 assert self._attempts > 0, 'Test attempts must be greater than 0'
125 self._attempts = count
127 def RestartBrowserBeforeEachPage(self):
128 """ Should the browser be restarted for the page?
130 This returns true if the test needs to unconditionally restart the
131 browser for each page. It may be called before the browser is started.
133 return self._needs_browser_restart_after_each_run
135 def StopBrowserAfterPage(self, browser, page): # pylint: disable=W0613
136 """Should the browser be stopped after the page is run?
138 This is called after a page is run to decide whether the browser needs to
139 be stopped to clean up its state. If it is stopped, then it will be
140 restarted to run the next page.
142 A test that overrides this can look at both the page and the browser to
143 decide whether it needs to stop the browser.
147 def AddCommandLineOptions(self, parser):
148 """Override to expose command-line options for this test.
150 The provided parser is an optparse.OptionParser instance and accepts all
151 normal results. The parsed options are available in Run as
155 def CustomizeBrowserOptions(self, options):
156 """Override to add test-specific options to the BrowserOptions object"""
159 def CustomizeBrowserOptionsForPageSet(self, page_set, options):
160 """Set options required for this page set.
162 These options will be used every time the browser is started while running
163 this page set. They may, however, be further modified by
164 CustomizeBrowserOptionsForSinglePage or by the profiler.
166 for page in page_set:
167 if not self.CanRunForPage(page):
169 interactive = options and options.interactive
170 for action in GetCompoundActionFromPage(
171 page, self._action_name_to_run, interactive):
172 action.CustomizeBrowserOptionsForPageSet(options)
174 def CustomizeBrowserOptionsForSinglePage(self, page, options):
175 """Set options specific to the test and the given page.
177 This will be called with the current page when the browser is (re)started.
178 Changing options at this point only makes sense if the browser is being
179 restarted for each page.
181 interactive = options and options.interactive
182 for action in GetCompoundActionFromPage(
183 page, self._action_name_to_run, interactive):
184 action.CustomizeBrowserOptionsForSinglePage(options)
186 def WillStartBrowser(self, browser):
187 """Override to manipulate the browser environment before it launches."""
190 def DidStartBrowser(self, browser):
191 """Override to customize the browser right after it has launched."""
194 def CanRunForPage(self, page): # pylint: disable=W0613
195 """Override to customize if the test can be ran for the given page."""
198 def WillRunTest(self):
199 """Override to do operations before the page set(s) are navigated."""
202 def DidRunTest(self, browser, results):
203 """Override to do operations after all page set(s) are completed.
205 This will occur before the browser is torn down.
209 def WillRunPageRepeats(self, page):
210 """Override to do operations before each page is iterated over."""
213 def DidRunPageRepeats(self, page):
214 """Override to do operations after each page is iterated over."""
217 def DidStartHTTPServer(self, tab):
218 """Override to do operations after the HTTP server is started."""
221 def WillNavigateToPage(self, page, tab):
222 """Override to do operations before the page is navigated, notably Telemetry
223 will already have performed the following operations on the browser before
224 calling this function:
225 * Ensure only one tab is open.
226 * Call WaitForDocumentReadyStateToComplete on the tab."""
229 def DidNavigateToPage(self, page, tab):
230 """Override to do operations right after the page is navigated and after
231 all waiting for completion has occurred."""
234 def WillRunActions(self, page, tab):
235 """Override to do operations before running the actions on the page."""
238 def DidRunActions(self, page, tab):
239 """Override to do operations after running the actions on the page."""
242 def WillRunAction(self, page, tab, action):
243 """Override to do operations before running the action on the page."""
246 def DidRunAction(self, page, tab, action):
247 """Override to do operations after running the action on the page."""
250 def CreatePageSet(self, args, options): # pylint: disable=W0613
251 """Override to make this test generate its own page set instead of
252 allowing arbitrary page sets entered from the command-line."""
255 def CreateExpectations(self, page_set): # pylint: disable=W0613
256 """Override to make this test generate its own expectations instead of
257 any that may have been defined in the page set."""
258 return test_expectations.TestExpectations()
260 def TabForPage(self, page, browser): # pylint: disable=W0613
261 """Override to select a different tab for the page. For instance, to
262 create a new tab for every page, return browser.tabs.New()."""
263 return browser.tabs[0]
265 def ValidatePageSet(self, page_set):
266 """Override to examine the page set before the test run. Useful for
267 example to validate that the pageset can be used with the test."""
270 def Run(self, options, page, tab, results):
271 self.options = options
272 interactive = options and options.interactive
273 compound_action = GetCompoundActionFromPage(
274 page, self._action_name_to_run, interactive)
275 self.WillRunActions(page, tab)
276 self._RunCompoundAction(page, tab, compound_action)
277 self.DidRunActions(page, tab)
279 self._test_method(page, tab, results)
283 def _RunCompoundAction(self, page, tab, actions, run_setup_methods=True):
284 for i, action in enumerate(actions):
285 prev_action = actions[i - 1] if i > 0 else None
286 next_action = actions[i + 1] if i < len(actions) - 1 else None
288 if (action.RunsPreviousAction() and
289 next_action and next_action.RunsPreviousAction()):
290 raise page_action.PageActionFailed('Consecutive actions cannot both '
291 'have RunsPreviousAction() == True.')
293 if not (next_action and next_action.RunsPreviousAction()):
294 action.WillRunAction(page, tab)
295 if run_setup_methods:
296 self.WillRunAction(page, tab, action)
298 action.RunAction(page, tab, prev_action)
300 if run_setup_methods:
301 self.DidRunAction(page, tab, action)
303 # Note that we must not call util.CloseConnections here. Many tests
304 # navigate to a URL in the first action and then wait for a condition
305 # in the second action. Calling util.CloseConnections here often
306 # aborts resource loads performed by the page.
308 def RunNavigateSteps(self, page, tab):
309 """Navigates the tab to the page URL attribute.
311 Runs the 'navigate_steps' page attribute as a compound action.
313 navigate_actions = GetCompoundActionFromPage(page, 'navigate_steps')
314 if not any(isinstance(action, navigate.NavigateAction)
315 for action in navigate_actions):
316 raise page_action.PageActionFailed(
317 'No NavigateAction in navigate_steps')
319 self._RunCompoundAction(page, tab, navigate_actions, False)
322 return self._exit_requested
324 def RequestExit(self):
325 self._exit_requested = True
328 def action_name_to_run(self):
329 return self._action_name_to_run