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