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 navigate
9 from telemetry.page.actions import page_action
12 def _GetActionFromData(action_data):
13 action_name = action_data['action']
14 action = all_page_actions.FindClassWithName(action_name)
16 logging.critical('Could not find an action named %s.', action_name)
17 logging.critical('Check the page set for a typo and check the error '
18 'log for possible Python loading/compilation errors.')
19 raise Exception('Action "%s" not found.' % action_name)
20 return action(action_data)
23 def GetCompoundActionFromPage(page, action_name):
27 action_data_list = getattr(page, action_name)
28 if not isinstance(action_data_list, list):
29 action_data_list = [action_data_list]
32 for subaction_data in action_data_list:
33 subaction_name = subaction_data['action']
34 if hasattr(page, subaction_name):
35 subaction = GetCompoundActionFromPage(page, subaction_name)
37 subaction = [_GetActionFromData(subaction_data)]
38 action_list += subaction * subaction_data.get('repeat', 1)
42 class Failure(Exception):
43 """Exception that can be thrown from PageMeasurement to indicate an
44 undesired but designed-for problem."""
48 class PageTest(object):
49 """A class styled on unittest.TestCase for creating page-specific tests."""
53 action_name_to_run='',
54 needs_browser_restart_after_each_run=False,
55 discard_first_result=False,
56 clear_cache_before_each_run=False):
59 self._test_method = getattr(self, test_method_name)
60 except AttributeError:
61 raise ValueError, 'No such method %s.%s' % (
62 self.__class_, test_method_name) # pylint: disable=E1101
63 self._action_name_to_run = action_name_to_run
64 self._needs_browser_restart_after_each_run = (
65 needs_browser_restart_after_each_run)
66 self._discard_first_result = discard_first_result
67 self._clear_cache_before_each_run = clear_cache_before_each_run
68 self._close_tabs_before_run = True
69 # If the test overrides the TabForPage method, it is considered a multi-tab
70 # test. The main difference between this and a single-tab test is that we
71 # do not attempt recovery for the former if a tab or the browser crashes,
72 # because we don't know the current state of tabs (how many are open, etc.)
73 self.is_multi_tab_test = (self.__class__ is not PageTest and
74 self.TabForPage.__func__ is not
75 self.__class__.__bases__[0].TabForPage.__func__)
76 # _exit_requested is set to true when the test requests an early exit.
77 self._exit_requested = False
80 def discard_first_result(self):
81 """When set to True, the first run of the test is discarded. This is
82 useful for cases where it's desirable to have some test resource cached so
83 the first run of the test can warm things up. """
84 return self._discard_first_result
86 @discard_first_result.setter
87 def discard_first_result(self, discard):
88 self._discard_first_result = discard
91 def clear_cache_before_each_run(self):
92 """When set to True, the browser's disk and memory cache will be cleared
94 return self._clear_cache_before_each_run
97 def close_tabs_before_run(self):
98 """When set to True, all tabs are closed before running the test for the
100 return self._close_tabs_before_run
102 @close_tabs_before_run.setter
103 def close_tabs_before_run(self, close_tabs):
104 self._close_tabs_before_run = close_tabs
106 def NeedsBrowserRestartAfterEachRun(self, browser): # pylint: disable=W0613
107 """Override to specify browser restart after each run."""
108 return self._needs_browser_restart_after_each_run
110 def AddCommandLineOptions(self, parser):
111 """Override to expose command-line options for this test.
113 The provided parser is an optparse.OptionParser instance and accepts all
114 normal results. The parsed options are available in Run as
118 def CustomizeBrowserOptions(self, options):
119 """Override to add test-specific options to the BrowserOptions object"""
122 def CustomizeBrowserOptionsForPage(self, page, options):
123 """Add options specific to the test and the given page."""
124 if not self.CanRunForPage(page):
126 for action in GetCompoundActionFromPage(page, self._action_name_to_run):
127 action.CustomizeBrowserOptions(options)
129 def WillStartBrowser(self, browser):
130 """Override to manipulate the browser environment before it launches."""
133 def DidStartBrowser(self, browser):
134 """Override to customize the browser right after it has launched."""
137 def CanRunForPage(self, page): # pylint: disable=W0613
138 """Override to customize if the test can be ran for the given page."""
141 def WillRunTest(self):
142 """Override to do operations before the page set(s) are navigated."""
145 def DidRunTest(self, browser, results):
146 """Override to do operations after all page set(s) are completed.
148 This will occur before the browser is torn down.
152 def WillRunPageRepeats(self, page):
153 """Override to do operations before each page is iterated over."""
156 def DidRunPageRepeats(self, page):
157 """Override to do operations after each page is iterated over."""
160 def DidStartHTTPServer(self, tab):
161 """Override to do operations after the HTTP server is started."""
164 def WillNavigateToPage(self, page, tab):
165 """Override to do operations before the page is navigated, notably Telemetry
166 will already have performed the following operations on the browser before
167 calling this function:
168 * Ensure only one tab is open.
169 * Call WaitForDocumentReadyStateToComplete on the tab."""
172 def DidNavigateToPage(self, page, tab):
173 """Override to do operations right after the page is navigated and after
174 all waiting for completion has occurred."""
177 def WillRunAction(self, page, tab, action):
178 """Override to do operations before running the action on the page."""
181 def DidRunAction(self, page, tab, action):
182 """Override to do operations after running the action on the page."""
185 def CreatePageSet(self, args, options): # pylint: disable=W0613
186 """Override to make this test generate its own page set instead of
187 allowing arbitrary page sets entered from the command-line."""
190 def CreateExpectations(self, page_set): # pylint: disable=W0613
191 """Override to make this test generate its own expectations instead of
192 any that may have been defined in the page set."""
193 return test_expectations.TestExpectations()
195 def TabForPage(self, page, browser): # pylint: disable=W0613
196 """Override to select a different tab for the page. For instance, to
197 create a new tab for every page, return browser.tabs.New()."""
198 return browser.tabs[0]
200 def ValidatePageSet(self, page_set):
201 """Override to examine the page set before the test run. Useful for
202 example to validate that the pageset can be used with the test."""
205 def Run(self, options, page, tab, results):
206 self.options = options
207 compound_action = GetCompoundActionFromPage(page, self._action_name_to_run)
208 self._RunCompoundAction(page, tab, compound_action)
210 self._test_method(page, tab, results)
214 def _RunCompoundAction(self, page, tab, actions, run_setup_methods=True):
215 for i, action in enumerate(actions):
216 prev_action = actions[i - 1] if i > 0 else None
217 next_action = actions[i + 1] if i < len(actions) - 1 else None
219 if (action.RunsPreviousAction() and
220 next_action and next_action.RunsPreviousAction()):
221 raise page_action.PageActionFailed('Consecutive actions cannot both '
222 'have RunsPreviousAction() == True.')
224 if not (next_action and next_action.RunsPreviousAction()):
225 action.WillRunAction(page, tab)
226 if run_setup_methods:
227 self.WillRunAction(page, tab, action)
229 action.RunAction(page, tab, prev_action)
231 if run_setup_methods:
232 self.DidRunAction(page, tab, action)
234 # Note that we must not call util.CloseConnections here. Many tests
235 # navigate to a URL in the first action and then wait for a condition
236 # in the second action. Calling util.CloseConnections here often
237 # aborts resource loads performed by the page.
239 def RunNavigateSteps(self, page, tab):
240 """Navigates the tab to the page URL attribute.
242 Runs the 'navigate_steps' page attribute as a compound action.
244 navigate_actions = GetCompoundActionFromPage(page, 'navigate_steps')
245 if not any(isinstance(action, navigate.NavigateAction)
246 for action in navigate_actions):
247 raise page_action.PageActionFailed(
248 'No NavigateAction in navigate_steps')
250 self._RunCompoundAction(page, tab, navigate_actions, False)
253 return self._exit_requested
255 def RequestExit(self):
256 self._exit_requested = True
259 def action_name_to_run(self):
260 return self._action_name_to_run