Home | History | Annotate | Download | only in page
      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 import inspect
      5 import logging
      6 import os
      7 import urlparse
      8 
      9 from catapult_base import cloud_storage  # pylint: disable=import-error
     10 
     11 from telemetry import story
     12 from telemetry.page import shared_page_state
     13 from telemetry.page import action_runner as action_runner_module
     14 
     15 
     16 class Page(story.Story):
     17 
     18   def __init__(self, url, page_set=None, base_dir=None, name='',
     19                credentials_path=None,
     20                credentials_bucket=cloud_storage.PUBLIC_BUCKET, labels=None,
     21                startup_url='', make_javascript_deterministic=True,
     22                shared_page_state_class=shared_page_state.SharedPageState):
     23     self._url = url
     24 
     25     super(Page, self).__init__(
     26         shared_page_state_class, name=name, labels=labels,
     27         is_local=self._scheme in ['file', 'chrome', 'about'],
     28         make_javascript_deterministic=make_javascript_deterministic)
     29 
     30     self._page_set = page_set
     31     # Default value of base_dir is the directory of the file that defines the
     32     # class of this page instance.
     33     if base_dir is None:
     34       base_dir = os.path.dirname(inspect.getfile(self.__class__))
     35     self._base_dir = base_dir
     36     self._name = name
     37     if credentials_path:
     38       credentials_path = os.path.join(self._base_dir, credentials_path)
     39       cloud_storage.GetIfChanged(credentials_path, credentials_bucket)
     40       if not os.path.exists(credentials_path):
     41         logging.error('Invalid credentials path: %s' % credentials_path)
     42         credentials_path = None
     43     self._credentials_path = credentials_path
     44 
     45     # Whether to collect garbage on the page before navigating & performing
     46     # page actions.
     47     self._collect_garbage_before_run = True
     48 
     49     # These attributes can be set dynamically by the page.
     50     self.synthetic_delays = dict()
     51     self._startup_url = startup_url
     52     self.credentials = None
     53     self.skip_waits = False
     54     self.script_to_evaluate_on_commit = None
     55     self._SchemeErrorCheck()
     56 
     57   @property
     58   def credentials_path(self):
     59     return self._credentials_path
     60 
     61   @property
     62   def startup_url(self):
     63     return self._startup_url
     64 
     65   def _SchemeErrorCheck(self):
     66     if not self._scheme:
     67       raise ValueError('Must prepend the URL with scheme (e.g. file://)')
     68 
     69     if self.startup_url:
     70       startup_url_scheme = urlparse.urlparse(self.startup_url).scheme
     71       if not startup_url_scheme:
     72         raise ValueError('Must prepend the URL with scheme (e.g. http://)')
     73       if startup_url_scheme == 'file':
     74         raise ValueError('startup_url with local file scheme is not supported')
     75 
     76   def Run(self, shared_state):
     77     current_tab = shared_state.current_tab
     78     # Collect garbage from previous run several times to make the results more
     79     # stable if needed.
     80     if self._collect_garbage_before_run:
     81       for _ in xrange(0, 5):
     82         current_tab.CollectGarbage()
     83     shared_state.page_test.WillNavigateToPage(self, current_tab)
     84     shared_state.page_test.RunNavigateSteps(self, current_tab)
     85     shared_state.page_test.DidNavigateToPage(self, current_tab)
     86     action_runner = action_runner_module.ActionRunner(
     87         current_tab, skip_waits=self.skip_waits)
     88     self.RunPageInteractions(action_runner)
     89 
     90   def RunNavigateSteps(self, action_runner):
     91     url = self.file_path_url_with_scheme if self.is_file else self.url
     92     action_runner.Navigate(
     93         url, script_to_evaluate_on_commit=self.script_to_evaluate_on_commit)
     94 
     95   def RunPageInteractions(self, action_runner):
     96     """Override this to define custom interactions with the page.
     97     e.g:
     98       def RunPageInteractions(self, action_runner):
     99         action_runner.ScrollPage()
    100         action_runner.TapElement(text='Next')
    101     """
    102     pass
    103 
    104   def AsDict(self):
    105     """Converts a page object to a dict suitable for JSON output."""
    106     d = {
    107         'id': self._id,
    108         'url': self._url,
    109     }
    110     if self._name:
    111       d['name'] = self._name
    112     return d
    113 
    114   @property
    115   def story_set(self):
    116     return self._page_set
    117 
    118   # TODO(nednguyen, aiolos): deprecate this property.
    119   @property
    120   def page_set(self):
    121     return self._page_set
    122 
    123   @property
    124   def url(self):
    125     return self._url
    126 
    127   def GetSyntheticDelayCategories(self):
    128     result = []
    129     for delay, options in self.synthetic_delays.items():
    130       options = '%f;%s' % (options.get('target_duration', 0),
    131                            options.get('mode', 'static'))
    132       result.append('DELAY(%s;%s)' % (delay, options))
    133     return result
    134 
    135   def __lt__(self, other):
    136     return self.url < other.url
    137 
    138   def __cmp__(self, other):
    139     x = cmp(self.name, other.name)
    140     if x != 0:
    141       return x
    142     return cmp(self.url, other.url)
    143 
    144   def __str__(self):
    145     return self.url
    146 
    147   def AddCustomizeBrowserOptions(self, options):
    148     """ Inherit page overrides this to add customized browser options."""
    149     pass
    150 
    151   @property
    152   def _scheme(self):
    153     return urlparse.urlparse(self.url).scheme
    154 
    155   @property
    156   def is_file(self):
    157     """Returns True iff this URL points to a file."""
    158     return self._scheme == 'file'
    159 
    160   @property
    161   def file_path(self):
    162     """Returns the path of the file, stripping the scheme and query string."""
    163     assert self.is_file
    164     # Because ? is a valid character in a filename,
    165     # we have to treat the URL as a non-file by removing the scheme.
    166     parsed_url = urlparse.urlparse(self.url[7:])
    167     return os.path.normpath(os.path.join(
    168         self._base_dir, parsed_url.netloc + parsed_url.path))
    169 
    170   @property
    171   def base_dir(self):
    172     return self._base_dir
    173 
    174   @property
    175   def file_path_url(self):
    176     """Returns the file path, including the params, query, and fragment."""
    177     assert self.is_file
    178     file_path_url = os.path.normpath(
    179         os.path.join(self._base_dir, self.url[7:]))
    180     # Preserve trailing slash or backslash.
    181     # It doesn't matter in a file path, but it does matter in a URL.
    182     if self.url.endswith('/'):
    183       file_path_url += os.sep
    184     return file_path_url
    185 
    186   @property
    187   def file_path_url_with_scheme(self):
    188     return 'file://' + self.file_path_url
    189 
    190   @property
    191   def serving_dir(self):
    192     if not self.is_file:
    193       return None
    194     file_path = os.path.realpath(self.file_path)
    195     if os.path.isdir(file_path):
    196       return file_path
    197     else:
    198       return os.path.dirname(file_path)
    199 
    200   @property
    201   def display_name(self):
    202     if self.name:
    203       return self.name
    204     if not self.is_file:
    205       return self.url
    206     all_urls = [p.url.rstrip('/') for p in self.page_set if p.is_file]
    207     common_prefix = os.path.dirname(os.path.commonprefix(all_urls))
    208     return self.url[len(common_prefix):].strip('/')
    209