blob: 036a9297fd10d633a4b4bc7f24c863a41f6b0355 [file] [log] [blame]
# Copyright 2016 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.
import collections
import logging
import re
import sys
import urllib
from telemetry import decorators
from telemetry import story as story_module
# TODO(perezju): Remove references to telementry.internal when
# https://github.com/catapult-project/catapult/issues/2102 is resolved.
from telemetry.internal.browser import browser_finder
from telemetry.internal.browser import browser_finder_exceptions
from telemetry.util import wpr_modes
from page_sets.top_10_mobile import URL_LIST
GOOGLE_SEARCH = 'https://www.google.com/search?'
SEARCH_QUERIES = [
'science',
'cat pictures',
'1600 Amphitheatre Pkwy, Mountain View, CA',
'tom hanks',
'weather 94110',
'goog',
'population of california',
'sfo jfk flights',
'movies 94110',
'tip on 100 bill'
]
def _OptionsForBrowser(browser_type, finder_options):
"""Return options used to get a browser of the given type.
TODO(perezju): Currently this clones the finder_options passed via the
command line to telemetry. When browser_options are split appart from
finder_options (crbug.com/570348) we will be able to construct our own
browser_options as needed.
"""
finder_options = finder_options.Copy()
finder_options.browser_type = browser_type
finder_options.browser_executable = None
finder_options.browser_options.browser_type = browser_type
return finder_options
class MultiBrowserSharedState(story_module.SharedState):
def __init__(self, test, finder_options, story_set):
"""A shared state to run a test involving multiple browsers.
The story_set is expected to include SinglePage instances (class defined
below) mapping each page to a browser on which to run. The state
requires at least one page to run on the 'default' browser, i.e. the
browser selected from the command line by the user.
"""
super(MultiBrowserSharedState, self).__init__(
test, finder_options, story_set)
self._platform = None
self._story_set = story_set
self._possible_browsers = {}
# We use an ordered dict to request memory dumps in a deterministic order.
self._browsers = collections.OrderedDict()
self._current_story = None
self._current_browser = None
self._current_tab = None
possible_browser = self._PrepareBrowser('default', finder_options)
if not possible_browser:
raise browser_finder_exceptions.BrowserFinderException(
'No browser found.\n\nAvailable browsers:\n%s\n' %
'\n'.join(browser_finder.GetAllAvailableBrowserTypes(finder_options)))
if not finder_options.run_disabled_tests:
self._CheckTestEnabled(test, possible_browser)
extra_browser_types = set(story.browser_type for story in story_set)
extra_browser_types.remove('default') # Must include 'default' browser.
for browser_type in extra_browser_types:
options = _OptionsForBrowser(browser_type, finder_options)
if not self._PrepareBrowser(browser_type, options):
logging.warning('Skipping %s (%s) because %s browser is not available',
test.__name__, str(test), browser_type)
logging.warning('Install %s to be able to run the test.', browser_type)
sys.exit(0)
# TODO(crbug/404771): Move network controller options out of
# browser_options and into finder_options.
browser_options = finder_options.browser_options
if finder_options.use_live_sites:
wpr_mode = wpr_modes.WPR_OFF
elif browser_options.wpr_mode == wpr_modes.WPR_RECORD:
wpr_mode = wpr_modes.WPR_RECORD
else:
wpr_mode = wpr_modes.WPR_REPLAY
self.platform.network_controller.Open(wpr_mode,
browser_options.extra_wpr_args)
# Make sure all browsers are open before starting.
for story in story_set:
if story.browser_type not in self._browsers:
self._SetCurrentBrowser(story.browser_type)
@property
def current_tab(self):
return self._current_tab
@property
def platform(self):
return self._platform
def _CheckTestEnabled(self, test, possible_browser):
should_skip, msg = decorators.ShouldSkip(test, possible_browser)
if should_skip:
logging.warning(msg)
logging.warning('You are trying to run a disabled test.')
logging.warning(
'Pass --also-run-disabled-tests to squelch this message.')
sys.exit(0)
def _PrepareBrowser(self, browser_type, options):
"""Add a browser to the dict of possible browsers.
TODO(perezju): When available, use the GetBrowserForPlatform API instead.
See: crbug.com/570348
Returns:
The possible browser if found, or None otherwise.
"""
possible_browser = browser_finder.FindBrowser(options)
if possible_browser is None:
return None
if self._platform is None:
self._platform = possible_browser.platform
else:
assert self._platform is possible_browser.platform
self._possible_browsers[browser_type] = (possible_browser, options)
return possible_browser
def _SetCurrentBrowser(self, browser_type):
"""Select a browser of the given type and bring it to the foreground.
This launches the browser if it does not exist already or, otherwise, moves
the existing browser to the foreground.
"""
if not browser_type in self._browsers:
possible_browser, options = self._possible_browsers[browser_type]
self._current_browser = possible_browser.Create(options)
self._browsers[browser_type] = self._current_browser
else:
self._current_browser = self._browsers[browser_type]
self._current_browser.Foreground()
def CanRunStory(self, _):
return True
def WillRunStory(self, story):
self._current_story = story
self.platform.network_controller.StartReplay(
self._story_set.WprFilePathForStory(story),
story.make_javascript_deterministic)
self._SetCurrentBrowser(self._current_story.browser_type)
self._current_tab = self._current_browser.foreground_tab
def RunStory(self, _):
self._current_story.Run(self)
def DidRunStory(self, _):
self._current_story = None
def TakeMemoryMeasurement(self):
self.current_tab.action_runner.ForceGarbageCollection()
self.platform.FlushEntireSystemCache()
if not self.platform.tracing_controller.is_tracing_running:
return # Tracing is not running, e.g., when recording a WPR archive.
for browser_type, browser in self._browsers.iteritems():
if not browser.DumpMemory():
logging.error('Unable to dump memory for %s', browser_type)
def TearDownState(self):
self.platform.network_controller.Close()
for browser_type, browser in self._browsers.iteritems():
try:
browser.Close()
except Exception:
logging.exception('Error while closing browser: %s', browser_type)
self._browsers = None # Not usable after tearing down.
class SinglePage(story_module.Story):
def __init__(self, name, url, browser_type, phase):
"""A story that associates a particular page with a browser to view it.
Args:
name: A string with the name of the page as it will appear reported,
e.g., on results and dashboards.
url: A string with the url of the page to load.
browser_type: A string identifying the browser where this page should be
displayed. Accepts the same strings as the command line --browser
option (e.g. 'android-webview'), and the special value 'default' to
select the browser chosen by the user on the command line.
"""
super(SinglePage, self).__init__(MultiBrowserSharedState, name=name)
self._url = url
self._browser_type = browser_type
self.grouping_keys['phase'] = phase
@property
def url(self):
return self._url
@property
def browser_type(self):
return self._browser_type
def Run(self, shared_state):
shared_state.current_tab.Navigate(self._url)
shared_state.current_tab.WaitForDocumentReadyStateToBeComplete()
shared_state.TakeMemoryMeasurement()
class DualBrowserStorySet(story_module.StorySet):
"""A story set that switches back and forth between two browsers."""
def __init__(self):
super(DualBrowserStorySet, self).__init__(
archive_data_file='data/dual_browser_story.json',
cloud_storage_bucket=story_module.PARTNER_BUCKET)
for query, url in zip(SEARCH_QUERIES, URL_LIST):
# Stories that run on the android-webview browser.
self.AddStory(SinglePage(
name='google_%s' % re.sub('\W+', '_', query.lower()),
url=GOOGLE_SEARCH + urllib.urlencode({'q': query}),
browser_type='android-webview',
phase='on_webview'))
# Stories that run on the browser selected by command line options.
self.AddStory(SinglePage(
name=re.sub('\W+', '_', url),
url=url,
browser_type='default',
phase='on_chrome'))