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
5import logging
6
7from py_trace_event import trace_event
8
9from telemetry.core import exceptions
10from telemetry.internal.actions import action_runner as action_runner_module
11
12# Export story_test.Failure to this page_test module
13from telemetry.web_perf.story_test import Failure
14
15
16class TestNotSupportedOnPlatformError(Exception):
17  """LegacyPageTest Exception raised when a required feature is unavailable.
18
19  The feature required to run the test could be part of the platform,
20  hardware configuration, or browser.
21  """
22
23
24class MultiTabTestAppCrashError(Exception):
25  """Exception raised after browser or tab crash for multi-tab tests.
26
27  Used to abort the test rather than try to recover from an unknown state.
28  """
29
30
31class MeasurementFailure(Failure):
32  """Exception raised when an undesired but designed-for problem."""
33
34
35class LegacyPageTest(object):
36  """A class styled on unittest.TestCase for creating page-specific tests.
37
38  Note that this method of measuring browser's performance is obsolete and only
39  here for "historical" reason. For your performance measurement need, please
40  use TimelineBasedMeasurement instead: https://goo.gl/eMvikK
41
42  For correctness testing, please use
43  serially_executed_browser_test_case.SeriallyExecutedBrowserTestCase
44  instead. See examples in:
45  https://github.com/catapult-project/catapult/tree/master/telemetry/examples/browser_tests
46
47  Test should override ValidateAndMeasurePage to perform test
48  validation and page measurement as necessary.
49
50     class BodyChildElementMeasurement(LegacyPageTest):
51       def ValidateAndMeasurePage(self, page, tab, results):
52         body_child_count = tab.EvaluateJavaScript(
53             'document.body.children.length')
54         results.AddValue(scalar.ScalarValue(
55             page, 'body_children', 'count', body_child_count))
56  """
57
58  __metaclass__ = trace_event.TracedMetaClass
59
60  def __init__(self,
61               needs_browser_restart_after_each_page=False,
62               clear_cache_before_each_run=False):
63    super(LegacyPageTest, self).__init__()
64
65    self.options = None
66    self._needs_browser_restart_after_each_page = (
67        needs_browser_restart_after_each_page)
68    self._clear_cache_before_each_run = clear_cache_before_each_run
69    self._close_tabs_before_run = True
70
71  @property
72  def is_multi_tab_test(self):
73    """Returns True if the test opens multiple tabs.
74
75    If the test overrides TabForPage, it is deemed a multi-tab test.
76    Multi-tab tests do not retry after tab or browser crashes, whereas,
77    single-tab tests too. That is because the state of multi-tab tests
78    (e.g., how many tabs are open, etc.) is unknown after crashes.
79    """
80    return self.TabForPage.__func__ is not LegacyPageTest.TabForPage.__func__
81
82  @property
83  def clear_cache_before_each_run(self):
84    """When set to True, the browser's disk and memory cache will be cleared
85    before each run."""
86    return self._clear_cache_before_each_run
87
88  @property
89  def close_tabs_before_run(self):
90    """When set to True, all tabs are closed before running the test for the
91    first time."""
92    return self._close_tabs_before_run
93
94  @close_tabs_before_run.setter
95  def close_tabs_before_run(self, close_tabs):
96    self._close_tabs_before_run = close_tabs
97
98  def RestartBrowserBeforeEachPage(self):
99    """ Should the browser be restarted for the page?
100
101    This returns true if the test needs to unconditionally restart the
102    browser for each page. It may be called before the browser is started.
103    """
104    return self._needs_browser_restart_after_each_page
105
106  def StopBrowserAfterPage(self, browser, page):
107    """Should the browser be stopped after the page is run?
108
109    This is called after a page is run to decide whether the browser needs to
110    be stopped to clean up its state. If it is stopped, then it will be
111    restarted to run the next page.
112
113    A test that overrides this can look at both the page and the browser to
114    decide whether it needs to stop the browser.
115    """
116    del browser, page  # unused
117    return False
118
119  def CustomizeBrowserOptions(self, options):
120    """Override to add test-specific options to the BrowserOptions object"""
121
122  def WillStartBrowser(self, platform):
123    """Override to manipulate the browser environment before it launches."""
124
125  def DidStartBrowser(self, browser):
126    """Override to customize the browser right after it has launched."""
127
128  def SetOptions(self, options):
129    """Sets the BrowserFinderOptions instance to use."""
130    self.options = options
131
132  def WillNavigateToPage(self, page, tab):
133    """Override to do operations before the page is navigated, notably Telemetry
134    will already have performed the following operations on the browser before
135    calling this function:
136    * Ensure only one tab is open.
137    * Call WaitForDocumentReadyStateToComplete on the tab."""
138
139  def DidNavigateToPage(self, page, tab):
140    """Override to do operations right after the page is navigated and after
141    all waiting for completion has occurred."""
142
143  def DidRunPage(self, platform):
144    """Called after the test run method was run, even if it failed."""
145
146  def TabForPage(self, page, browser):   # pylint: disable=unused-argument
147    """Override to select a different tab for the page.  For instance, to
148    create a new tab for every page, return browser.tabs.New()."""
149    try:
150      return browser.tabs[0]
151    # The tab may have gone away in some case, so we create a new tab and retry
152    # (See crbug.com/496280)
153    except exceptions.DevtoolsTargetCrashException as e:
154      logging.error('Tab may have crashed: %s' % str(e))
155      browser.tabs.New()
156      # See comment in shared_page_state.WillRunStory for why this waiting
157      # is needed.
158      browser.tabs[0].WaitForDocumentReadyStateToBeComplete()
159      return browser.tabs[0]
160
161  def ValidateAndMeasurePage(self, page, tab, results):
162    """Override to check test assertions and perform measurement.
163
164    When adding measurement results, call results.AddValue(...) for
165    each result. Raise an exception or add a failure.FailureValue on
166    failure. legacy_page_test.py also provides several base exception classes
167    to use.
168
169    Prefer metric value names that are in accordance with python
170    variable style. e.g., metric_name. The name 'url' must not be used.
171
172    Put together:
173      def ValidateAndMeasurePage(self, page, tab, results):
174        res = tab.EvaluateJavaScript('2+2')
175        if res != 4:
176          raise Exception('Oh, wow.')
177        results.AddValue(scalar.ScalarValue(
178            page, 'two_plus_two', 'count', res))
179
180    Args:
181      page: A telemetry.page.Page instance.
182      tab: A telemetry.core.Tab instance.
183      results: A telemetry.results.PageTestResults instance.
184    """
185    raise NotImplementedError
186
187  # Deprecated: do not use this hook. (crbug.com/470147)
188  def RunNavigateSteps(self, page, tab):
189    """Navigates the tab to the page URL attribute.
190
191    Runs the 'navigate_steps' page attribute as a compound action.
192    """
193    action_runner = action_runner_module.ActionRunner(
194        tab, skip_waits=page.skip_waits)
195    page.RunNavigateSteps(action_runner)
196