blob: 4b02ee860de37193b7a87b2031a7f7f46e6b1dbc [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 __future__ import absolute_import
import inspect
import logging
import os
import six.moves.urllib.parse # pylint: disable=import-error
from py_utils import cloud_storage # pylint: disable=import-error
from telemetry import story
from telemetry.page import cache_temperature as cache_temperature_module
from telemetry.page import shared_page_state
from telemetry.page import traffic_setting as traffic_setting_module
from telemetry.internal.actions import action_runner as action_runner_module
class Page(story.Story):
def __init__(self, url, page_set=None, base_dir=None, name='',
tags=None, make_javascript_deterministic=True,
shared_page_state_class=shared_page_state.SharedPageState,
grouping_keys=None,
cache_temperature=cache_temperature_module.ANY,
traffic_setting=traffic_setting_module.NONE,
platform_specific=False,
extra_browser_args=None,
perform_final_navigation=False):
self._url = url
self._SchemeErrorCheck()
super(Page, self).__init__(
shared_page_state_class, name=name, tags=tags,
is_local=self._scheme in ['file', 'chrome', 'about'],
make_javascript_deterministic=make_javascript_deterministic,
grouping_keys=grouping_keys, platform_specific=platform_specific)
self._page_set = page_set
# Default value of base_dir is the directory of the file that defines the
# class of this page instance.
if base_dir is None:
base_dir = os.path.dirname(inspect.getfile(self.__class__))
self._base_dir = base_dir
self._name = name
self._cache_temperature = cache_temperature
# A "final navigation" is a navigation to about:blank after the
# test in order to flush metrics. Some UMA metrics are not output to traces
# until the page is navigated away from.
self._perform_final_navigation = perform_final_navigation
assert traffic_setting in traffic_setting_module.NETWORK_CONFIGS, (
'Invalid traffic setting: %s' % traffic_setting)
self._traffic_setting = traffic_setting
# These attributes can be set dynamically by the page.
self.synthetic_delays = dict()
self.skip_waits = False
self.script_to_evaluate_on_commit = None
self._extra_browser_args = extra_browser_args or []
@property
def cache_temperature(self):
return self._cache_temperature
@property
def traffic_setting(self):
return self._traffic_setting
@property
def extra_browser_args(self):
return self._extra_browser_args
def _SchemeErrorCheck(self):
if not self._scheme:
raise ValueError('Must prepend the URL with scheme (e.g. file://)')
def Run(self, shared_state):
current_tab = shared_state.current_tab
# Collect garbage from previous run several times to make the results more
# stable if needed.
for _ in range(0, 5):
current_tab.CollectGarbage()
action_runner = action_runner_module.ActionRunner(
current_tab, skip_waits=self.skip_waits)
with shared_state.interval_profiling_controller.SamplePeriod(
'story_run', action_runner):
shared_state.NavigateToPage(action_runner, self)
shared_state.RunPageInteractions(action_runner, self)
# Navigate to about:blank in order to force previous page's metrics to
# flush. Needed for many UMA metrics reported from PageLoadMetricsObserver.
if self._perform_final_navigation:
action_runner.Navigate('about:blank')
def RunNavigateSteps(self, action_runner):
url = self.file_path_url_with_scheme if self.is_file else self.url
action_runner.Navigate(
url, script_to_evaluate_on_commit=self.script_to_evaluate_on_commit)
def RunPageInteractions(self, action_runner):
"""Override this to define custom interactions with the page.
e.g:
def RunPageInteractions(self, action_runner):
action_runner.ScrollPage()
action_runner.TapElement(text='Next')
"""
pass
def AsDict(self):
"""Converts a page object to a dict suitable for JSON output."""
d = {
'id': self._id,
'url': self._url,
}
if self._name:
d['name'] = self._name
return d
@property
def story_set(self):
return self._page_set
@property
def url(self):
return self._url
def GetSyntheticDelayCategories(self):
result = []
for delay, options in self.synthetic_delays.items():
options = '%f;%s' % (options.get('target_duration', 0),
options.get('mode', 'static'))
result.append('DELAY(%s;%s)' % (delay, options))
return result
def __lt__(self, other):
return self.url < other.url
def __cmp__(self, other):
x = cmp(self.name, other.name)
if x != 0:
return x
return cmp(self.url, other.url)
def __eq__(self, other):
return self.name == other.name and self.url == other.url
def __hash__(self):
return hash((self.name, self.url))
def __str__(self):
return self.url
@property
def _scheme(self):
return six.moves.urllib.parse.urlparse(self.url).scheme
@property
def is_file(self):
"""Returns True iff this URL points to a file."""
return self._scheme == 'file'
@property
def file_path(self):
"""Returns the path of the file, stripping the scheme and query string."""
assert self.is_file
# Because ? is a valid character in a filename,
# we have to treat the URL as a non-file by removing the scheme.
parsed_url = six.moves.urllib.parse.urlparse(self.url[7:])
return os.path.normpath(os.path.join(
self._base_dir, parsed_url.netloc + parsed_url.path))
@property
def base_dir(self):
return self._base_dir
@property
def file_path_url(self):
"""Returns the file path, including the params, query, and fragment."""
assert self.is_file
file_path_url = os.path.normpath(
os.path.join(self._base_dir, self.url[7:]))
# Preserve trailing slash or backslash.
# It doesn't matter in a file path, but it does matter in a URL.
if self.url.endswith('/'):
file_path_url += os.sep
return file_path_url
@property
def file_path_url_with_scheme(self):
return 'file://' + self.file_path_url
@property
def serving_dir(self):
if not self.is_file:
return None
file_path = os.path.realpath(self.file_path)
if os.path.isdir(file_path):
return file_path
else:
return os.path.dirname(file_path)