blob: 28282024440946b9755f4930d6df609739af779b [file] [log] [blame]
# Copyright 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from telemetry.core import command_line
from telemetry.page import test_expectations
from telemetry.page.actions import action_runner as action_runner_module
class Failure(Exception):
"""Exception that can be thrown from PageTest to indicate an
undesired but designed-for problem."""
class TestNotSupportedOnPlatformFailure(Failure):
"""Exception that can be thrown to indicate that a certain feature required
to run the test is not available on the platform, hardware configuration, or
browser version."""
class MeasurementFailure(Failure):
"""Exception that can be thrown from MeasurePage to indicate an undesired but
designed-for problem."""
class PageTest(command_line.Command):
"""A class styled on unittest.TestCase for creating page-specific tests.
Test should override ValidateAndMeasurePage to perform test
validation and page measurement as necessary.
class BodyChildElementMeasurement(PageTest):
def ValidateAndMeasurePage(self, page, tab, results):
body_child_count = tab.EvaluateJavaScript(
'document.body.children.length')
results.AddValue(scalar.ScalarValue(
page, 'body_children', 'count', body_child_count))
The class also provide hooks to add test-specific options. Here is
an example:
class BodyChildElementMeasurement(PageTest):
def AddCommandLineArgs(parser):
parser.add_option('--element', action='store', default='body')
def ValidateAndMeasurePage(self, page, tab, results):
body_child_count = tab.EvaluateJavaScript(
'document.querySelector('%s').children.length')
results.AddValue(scalar.ScalarValue(
page, 'children', 'count', child_count))
Args:
action_name_to_run: This is the method name in telemetry.page.Page
subclasses to run.
discard_first_run: Discard the first run of this page. This is
usually used with page_repeat and pageset_repeat options.
max_failures: The number of page failures allowed before we stop
running other pages.
is_action_name_to_run_optional: Determines what to do if
action_name_to_run is not empty but the page doesn't have that
action. The page will run (without any action) if
is_action_name_to_run_optional is True, otherwise the page
will fail.
"""
options = {}
def __init__(self,
action_name_to_run='',
needs_browser_restart_after_each_page=False,
discard_first_result=False,
clear_cache_before_each_run=False,
max_failures=None,
is_action_name_to_run_optional=False):
super(PageTest, self).__init__()
self.options = None
if action_name_to_run:
assert action_name_to_run.startswith('Run') \
and '_' not in action_name_to_run, \
('Wrong way of naming action_name_to_run. By new convention,'
'action_name_to_run must start with Run- prefix and in CamelCase.')
self._action_name_to_run = action_name_to_run
self._needs_browser_restart_after_each_page = (
needs_browser_restart_after_each_page)
self._discard_first_result = discard_first_result
self._clear_cache_before_each_run = clear_cache_before_each_run
self._close_tabs_before_run = True
self._max_failures = max_failures
self._is_action_name_to_run_optional = is_action_name_to_run_optional
# If the test overrides the TabForPage method, it is considered a multi-tab
# test. The main difference between this and a single-tab test is that we
# do not attempt recovery for the former if a tab or the browser crashes,
# because we don't know the current state of tabs (how many are open, etc.)
self.is_multi_tab_test = (self.__class__ is not PageTest and
self.TabForPage.__func__ is not
self.__class__.__bases__[0].TabForPage.__func__)
# _exit_requested is set to true when the test requests an early exit.
self._exit_requested = False
@classmethod
def SetArgumentDefaults(cls, parser):
parser.set_defaults(**cls.options)
@property
def discard_first_result(self):
"""When set to True, the first run of the test is discarded. This is
useful for cases where it's desirable to have some test resource cached so
the first run of the test can warm things up. """
return self._discard_first_result
@discard_first_result.setter
def discard_first_result(self, discard):
self._discard_first_result = discard
@property
def clear_cache_before_each_run(self):
"""When set to True, the browser's disk and memory cache will be cleared
before each run."""
return self._clear_cache_before_each_run
@property
def close_tabs_before_run(self):
"""When set to True, all tabs are closed before running the test for the
first time."""
return self._close_tabs_before_run
@close_tabs_before_run.setter
def close_tabs_before_run(self, close_tabs):
self._close_tabs_before_run = close_tabs
@property
def attempts(self):
"""Maximum number of times test will be attempted."""
# Do NOT override this method (crbug.com/422339).
return 1
@property
def max_failures(self):
"""Maximum number of failures allowed for the page set."""
return self._max_failures
@max_failures.setter
def max_failures(self, count):
self._max_failures = count
def Run(self, args):
# Define this method to avoid pylint errors.
# TODO(dtu): Make this actually run the test with args.page_set.
pass
def RestartBrowserBeforeEachPage(self):
""" Should the browser be restarted for the page?
This returns true if the test needs to unconditionally restart the
browser for each page. It may be called before the browser is started.
"""
return self._needs_browser_restart_after_each_page
def StopBrowserAfterPage(self, browser, page): # pylint: disable=W0613
"""Should the browser be stopped after the page is run?
This is called after a page is run to decide whether the browser needs to
be stopped to clean up its state. If it is stopped, then it will be
restarted to run the next page.
A test that overrides this can look at both the page and the browser to
decide whether it needs to stop the browser.
"""
return False
def CustomizeBrowserOptions(self, options):
"""Override to add test-specific options to the BrowserOptions object"""
def CustomizeBrowserOptionsForSinglePage(self, page, options):
"""Set options specific to the test and the given page.
This will be called with the current page when the browser is (re)started.
Changing options at this point only makes sense if the browser is being
restarted for each page. Note that if page has a startup_url, the browser
will always be restarted for each run.
"""
if page.startup_url:
options.browser_options.startup_url = page.startup_url
def WillStartBrowser(self, platform):
"""Override to manipulate the browser environment before it launches."""
def DidStartBrowser(self, browser):
"""Override to customize the browser right after it has launched."""
def CanRunForPage(self, page): # pylint: disable=W0613
"""Override to customize if the test can be ran for the given page."""
if self._action_name_to_run and not self._is_action_name_to_run_optional:
return hasattr(page, self._action_name_to_run)
return True
def WillRunTest(self, options):
"""Override to do operations before the page set(s) are navigated."""
self.options = options
def DidRunTest(self, browser, results): # pylint: disable=W0613
"""Override to do operations after all page set(s) are completed.
This will occur before the browser is torn down.
"""
self.options = None
def WillNavigateToPage(self, page, tab):
"""Override to do operations before the page is navigated, notably Telemetry
will already have performed the following operations on the browser before
calling this function:
* Ensure only one tab is open.
* Call WaitForDocumentReadyStateToComplete on the tab."""
def DidNavigateToPage(self, page, tab):
"""Override to do operations right after the page is navigated and after
all waiting for completion has occurred."""
def WillRunActions(self, page, tab):
"""Override to do operations before running the actions on the page."""
def DidRunActions(self, page, tab):
"""Override to do operations after running the actions on the page."""
def CleanUpAfterPage(self, page, tab):
"""Called after the test run method was run, even if it failed."""
def CreateExpectations(self, page_set): # pylint: disable=W0613
"""Override to make this test generate its own expectations instead of
any that may have been defined in the page set."""
return test_expectations.TestExpectations()
def TabForPage(self, page, browser): # pylint: disable=W0613
"""Override to select a different tab for the page. For instance, to
create a new tab for every page, return browser.tabs.New()."""
return browser.tabs[0]
def ValidatePageSet(self, page_set):
"""Override to examine the page set before the test run. Useful for
example to validate that the pageset can be used with the test."""
def ValidateAndMeasurePage(self, page, tab, results):
"""Override to check test assertions and perform measurement.
When adding measurement results, call results.AddValue(...) for
each result. Raise an exception or add a failure.FailureValue on
failure. page_test.py also provides several base exception classes
to use.
Prefer metric value names that are in accordance with python
variable style. e.g., metric_name. The name 'url' must not be used.
Put together:
def ValidateAndMeasurePage(self, page, tab, results):
res = tab.EvaluateJavaScript('2+2')
if res != 4:
raise Exception('Oh, wow.')
results.AddValue(scalar.ScalarValue(
page, 'two_plus_two', 'count', res))
Args:
page: A telemetry.page.Page instance.
tab: A telemetry.core.Tab instance.
results: A telemetry.results.PageTestResults instance.
"""
# TODO(chrishenry): Switch to raise NotImplementedError() when
# subclasses no longer override ValidatePage/MeasurePage.
self.ValidatePage(page, tab, results)
def ValidatePage(self, page, tab, results):
"""DEPRECATED: Use ValidateAndMeasurePage instead."""
self.MeasurePage(page, tab, results)
def MeasurePage(self, page, tab, results):
"""DEPRECATED: Use ValidateAndMeasurePage instead."""
def RunPage(self, page, tab, results):
# Run actions.
interactive = self.options and self.options.interactive
action_runner = action_runner_module.ActionRunner(
tab, skip_waits=page.skip_waits)
self.WillRunActions(page, tab)
if interactive:
action_runner.PauseInteractive()
else:
self._RunMethod(page, self._action_name_to_run, action_runner)
self.DidRunActions(page, tab)
self.ValidateAndMeasurePage(page, tab, results)
def _RunMethod(self, page, method_name, action_runner):
if hasattr(page, method_name):
run_method = getattr(page, method_name)
run_method(action_runner)
def RunNavigateSteps(self, page, tab):
"""Navigates the tab to the page URL attribute.
Runs the 'navigate_steps' page attribute as a compound action.
"""
action_runner = action_runner_module.ActionRunner(
tab, skip_waits=page.skip_waits)
page.RunNavigateSteps(action_runner)
def IsExiting(self):
return self._exit_requested
def RequestExit(self):
self._exit_requested = True
@property
def action_name_to_run(self):
return self._action_name_to_run