1 # Copyright 2014 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.
17 from telemetry
.page
import profile_creator
21 from telemetry
import benchmark
22 from telemetry
.page
import page_test
23 from telemetry
.page
import test_expectations
24 from telemetry
.results
import results_options
25 from telemetry
.user_story
import user_story_runner
27 class _ExtensionPageTest(page_test
.PageTest
):
28 """This page test verified that extensions were automatically installed."""
30 super(_ExtensionPageTest
, self
).__init
__()
31 self
._user
_story
_set
= page_sets
.Typical25PageSet()
33 # No matter how many pages in the PageSet, just perform two test iterations.
34 for user_story
in self
._user
_story
_set
[2:]:
35 self
._user
_story
_set
.RemoveUserStory(user_story
)
37 # Have the extensions been installed yet?
38 self
._extensions
_installed
= False
41 self
._expected
_extension
_count
= 0
43 def ValidateAndMeasurePage(self
, _
, tab
, results
):
44 # Superclass override.
45 # Profile setup works in 2 phases:
46 # Phase 1: When the first page is loaded: we wait for a timeout to allow
47 # all extensions to install and to prime safe browsing and other
48 # caches. Extensions may open tabs as part of the install process.
49 # Phase 2: When the second page loads, user_story_runner closes all tabs -
50 # we are left with one open tab, wait for that to finish loading.
52 # Sleep for a bit to allow safe browsing and other data to load +
53 # extensions to install.
54 if not self
._extensions
_installed
:
56 logging
.info("Sleeping for %d seconds." % sleep_seconds
)
57 time
.sleep(sleep_seconds
)
58 self
._extensions
_installed
= True
60 # Phase 2: Wait for tab to finish loading.
61 for i
in xrange(len(tab
.browser
.tabs
)):
62 t
= tab
.browser
.tabs
[i
]
63 t
.WaitForDocumentReadyStateToBeComplete()
65 def DidRunTest(self
, browser
, results
):
66 """Superclass override."""
67 super(_ExtensionPageTest
, self
).DidRunTest(browser
,
69 # Do some basic sanity checks to make sure the profile is complete.
70 installed_extensions
= browser
.extensions
.keys()
71 if not len(installed_extensions
) == self
._expected
_extension
_count
:
73 # Too many extensions: Managed environment may be installing additional
75 raise Exception("Unexpected number of extensions installed in browser",
79 def _ExternalExtensionsPath(profile_path
):
80 """Returns the OS-dependent path at which to install the extension deployment
83 |profile_path| is the path of the profile that will be used to launch the
86 if platform
.system() == 'Darwin':
87 return str(profile_path
) + '/External Extensions'
90 raise NotImplementedError('Extension install on %s is not yet supported' %
94 def _DownloadExtension(extension_id
, output_dir
):
95 """Download an extension to disk.
98 extension_id: the extension id.
99 output_dir: Directory to download into.
102 Extension file downloaded."""
103 extension_download_path
= os
.path
.join(output_dir
, "%s.crx" % extension_id
)
105 # Ideally, the Chrome version would be dynamically extracted from the binary.
106 # Instead, we use a Chrome version whose release date is expected to be
107 # about a hundred years in the future.
108 chrome_version
= '1000.0.0.0'
110 "https://clients2.google.com/service/update2/crx?response=redirect"
111 "&prodversion=%s&x=id%%3D%s%%26lang%%3Den-US%%26uc"
112 % (chrome_version
, extension_id
))
113 response
= urllib2
.urlopen(extension_url
)
114 assert(response
.getcode() == 200)
116 with
open(extension_download_path
, "w") as f
:
117 f
.write(response
.read())
119 return extension_download_path
122 def _GetExtensionInfoFromCRX(crx_path
):
123 """Parse an extension archive and return information.
126 The extension name returned by this function may not be valid
127 (e.g. in the case of a localized extension name). It's use is just
128 meant to be informational.
131 crx_path: path to crx archive to look at.
135 (crx_version, extension_name)"""
136 crx_zip
= zipfile
.ZipFile(crx_path
)
137 manifest_contents
= crx_zip
.read('manifest.json')
138 decoded_manifest
= json
.loads(manifest_contents
)
139 crx_version
= decoded_manifest
['version']
140 extension_name
= decoded_manifest
['name']
142 return (crx_version
, extension_name
)
145 class ExtensionsProfileCreator(profile_creator
.ProfileCreator
):
146 """Abstract base class for profile creators that install extensions.
148 Extensions are installed using the mechanism described in
149 https://developer.chrome.com/extensions/external_extensions.html .
151 Subclasses are meant to be run interactively.
153 def __init__(self
, extensions_to_install
=None, theme_to_install
=None):
154 super(ExtensionsProfileCreator
, self
).__init
__()
156 # List of extensions to install.
157 self
._extensions
_to
_install
= []
158 if extensions_to_install
:
159 self
._extensions
_to
_install
.extend(extensions_to_install
)
161 self
._extensions
_to
_install
.append(theme_to_install
)
163 # Directory to download extension files into.
164 self
._extension
_download
_dir
= None
166 # List of files to delete after run.
167 self
._files
_to
_cleanup
= []
169 def Run(self
, options
):
170 # Installing extensions requires that the profile directory exist before
171 # the browser is launched.
172 if not options
.browser_options
.profile_dir
:
173 options
.browser_options
.profile_dir
= tempfile
.mkdtemp()
174 options
.browser_options
.disable_default_apps
= False
176 self
._PrepareExtensionInstallFiles
(options
.browser_options
.profile_dir
)
178 expectations
= test_expectations
.TestExpectations()
179 results
= results_options
.CreateResults(
180 benchmark
.BenchmarkMetadata(profile_creator
.__class
__.__name
__),
182 extension_page_test
= _ExtensionPageTest()
183 extension_page_test
._expected
_extension
_count
= len(
184 self
._extensions
_to
_install
)
185 user_story_runner
.Run(
186 extension_page_test
, extension_page_test
._user
_story
_set
,
187 expectations
, options
, results
)
189 self
._CleanupExtensionInstallFiles
()
191 # Check that files on this list exist and have content.
193 os
.path
.join('Default', 'Network Action Predictor')]
194 for filename
in expected_files
:
195 filename
= os
.path
.join(options
.output_profile_path
, filename
)
196 if not os
.path
.getsize(filename
) > 0:
197 raise Exception("Profile not complete: %s is zero length." % filename
)
200 logging
.warning('Some pages failed.')
201 logging
.warning('Failed pages:\n%s',
202 '\n'.join(map(str, results
.pages_that_failed
)))
203 raise Exception('ExtensionsProfileCreator failed.')
205 def _PrepareExtensionInstallFiles(self
, profile_path
):
206 """Download extension archives and create extension install files."""
207 extensions_to_install
= self
._extensions
_to
_install
208 if not extensions_to_install
:
209 raise ValueError("No extensions or themes to install:",
210 extensions_to_install
)
212 # Create external extensions path if it doesn't exist already.
213 external_extensions_dir
= _ExternalExtensionsPath(profile_path
)
214 if not os
.path
.isdir(external_extensions_dir
):
215 os
.makedirs(external_extensions_dir
)
217 self
._extension
_download
_dir
= tempfile
.mkdtemp()
219 num_extensions
= len(extensions_to_install
)
220 for i
in range(num_extensions
):
221 extension_id
= extensions_to_install
[i
]
222 logging
.info("Downloading %s - %d/%d" % (
223 extension_id
, (i
+ 1), num_extensions
))
224 extension_path
= _DownloadExtension(extension_id
,
225 self
._extension
_download
_dir
)
226 (version
, name
) = _GetExtensionInfoFromCRX(extension_path
)
227 extension_info
= {'external_crx' : extension_path
,
228 'external_version' : version
,
230 extension_json_path
= os
.path
.join(external_extensions_dir
,
231 "%s.json" % extension_id
)
232 with
open(extension_json_path
, 'w') as f
:
233 f
.write(json
.dumps(extension_info
))
234 self
._files
_to
_cleanup
.append(extension_json_path
)
236 def _CleanupExtensionInstallFiles(self
):
237 """Cleanup stray files before exiting."""
238 logging
.info("Cleaning up stray files")
239 for filename
in self
._files
_to
_cleanup
:
242 if self
._extension
_download
_dir
:
243 # Simple sanity check to lessen the impact of a stray rmtree().
244 if len(self
._extension
_download
_dir
.split(os
.sep
)) < 3:
245 raise Exception("Path too shallow: %s" % self
._extension
_download
_dir
)
246 shutil
.rmtree(self
._extension
_download
_dir
)
247 self
._extension
_download
_dir
= None