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. 4import inspect 5import logging 6import os 7import urlparse 8 9from catapult_base import cloud_storage # pylint: disable=import-error 10 11from telemetry import story 12from telemetry.page import shared_page_state 13from telemetry.page import action_runner as action_runner_module 14 15 16class 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