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