blob: b6135db40dae1c85b24b0db25d4a169e41518aef [file] [log] [blame]
# Copyright 2012 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 os
from telemetry.core import exceptions
from telemetry.core import util
DEFAULT_WEB_CONTENTS_TIMEOUT = 90
# TODO(achuith, dtu, nduca): Add unit tests specifically for WebContents,
# independent of Tab.
class WebContents(object):
"""Represents web contents in the browser"""
def __init__(self, inspector_backend):
self._inspector_backend = inspector_backend
with open(os.path.join(os.path.dirname(__file__),
'network_quiescence.js')) as f:
self._quiescence_js = f.read()
@property
def id(self):
"""Return the unique id string for this tab object."""
return self._inspector_backend.id
def GetUrl(self):
"""Returns the URL to which the WebContents is connected.
Raises:
exceptions.Error: If there is an error in inspector backend connection.
"""
return self._inspector_backend.url
def GetWebviewContexts(self):
"""Returns a list of webview contexts within the current inspector backend.
Returns:
A list of WebContents objects representing the webview contexts.
Raises:
exceptions.Error: If there is an error in inspector backend connection.
"""
webviews = []
inspector_backends = self._inspector_backend.GetWebviewInspectorBackends()
for inspector_backend in inspector_backends:
webviews.append(WebContents(inspector_backend))
return webviews
def WaitForDocumentReadyStateToBeComplete(self,
timeout=DEFAULT_WEB_CONTENTS_TIMEOUT):
"""Waits for the document to finish loading.
Raises:
exceptions.Error: See WaitForJavaScriptExpression() for a detailed list
of possible exceptions.
"""
self.WaitForJavaScriptExpression(
'document.readyState == "complete"', timeout)
def WaitForDocumentReadyStateToBeInteractiveOrBetter(self,
timeout=DEFAULT_WEB_CONTENTS_TIMEOUT):
"""Waits for the document to be interactive.
Raises:
exceptions.Error: See WaitForJavaScriptExpression() for a detailed list
of possible exceptions.
"""
self.WaitForJavaScriptExpression(
'document.readyState == "interactive" || '
'document.readyState == "complete"', timeout)
def WaitForJavaScriptExpression(self, expr, timeout):
"""Waits for the given JavaScript expression to be True.
This method is robust against any given Evaluation timing out.
Args:
expr: The expression to evaluate.
timeout: The number of seconds to wait for the expression to be True.
Raises:
exceptions.TimeoutException: On a timeout.
exceptions.Error: See EvaluateJavaScript() for a detailed list of
possible exceptions.
"""
def IsJavaScriptExpressionTrue():
try:
return bool(self.EvaluateJavaScript(expr))
except exceptions.TimeoutException:
# If the main thread is busy for longer than Evaluate's timeout, we
# may time out here early. Instead, we want to wait for the full
# timeout of this method.
return False
try:
util.WaitFor(IsJavaScriptExpressionTrue, timeout)
except exceptions.TimeoutException as e:
# Try to make timeouts a little more actionable by dumping console output.
debug_message = None
try:
debug_message = (
'Console output:\n%s' %
self._inspector_backend.GetCurrentConsoleOutputBuffer())
except Exception as e:
debug_message = (
'Exception thrown when trying to capture console output: %s' %
repr(e))
raise exceptions.TimeoutException(
e.message + '\n' + debug_message)
def HasReachedQuiescence(self):
"""Determine whether the page has reached quiescence after loading.
Returns:
True if 2 seconds have passed since last resource received, false
otherwise.
Raises:
exceptions.Error: See EvaluateJavaScript() for a detailed list of
possible exceptions.
"""
# Inclusion of the script that provides
# window.__telemetry_testHasReachedNetworkQuiescence()
# is idempotent, it's run on every call because WebContents doesn't track
# page loads and we need to execute anew for every newly loaded page.
has_reached_quiescence = (
self.EvaluateJavaScript(self._quiescence_js +
"window.__telemetry_testHasReachedNetworkQuiescence()"))
return has_reached_quiescence
def ExecuteJavaScript(self, statement, timeout=DEFAULT_WEB_CONTENTS_TIMEOUT):
"""Executes statement in JavaScript. Does not return the result.
If the statement failed to evaluate, EvaluateException will be raised.
Raises:
exceptions.Error: See ExecuteJavaScriptInContext() for a detailed list of
possible exceptions.
"""
return self.ExecuteJavaScriptInContext(
statement, context_id=None, timeout=timeout)
def EvaluateJavaScript(self, expr, timeout=DEFAULT_WEB_CONTENTS_TIMEOUT):
"""Evalutes expr in JavaScript and returns the JSONized result.
Consider using ExecuteJavaScript for cases where the result of the
expression is not needed.
If evaluation throws in JavaScript, a Python EvaluateException will
be raised.
If the result of the evaluation cannot be JSONized, then an
EvaluationException will be raised.
Raises:
exceptions.Error: See EvaluateJavaScriptInContext() for a detailed list
of possible exceptions.
"""
return self.EvaluateJavaScriptInContext(
expr, context_id=None, timeout=timeout)
def ExecuteJavaScriptInContext(self, expr, context_id,
timeout=DEFAULT_WEB_CONTENTS_TIMEOUT):
"""Similar to ExecuteJavaScript, except context_id can refer to an iframe.
The main page has context_id=1, the first iframe context_id=2, etc.
Raises:
exceptions.EvaluateException
exceptions.WebSocketDisconnected
exceptions.TimeoutException
exceptions.DevtoolsTargetCrashException
"""
return self._inspector_backend.ExecuteJavaScript(
expr, context_id=context_id, timeout=timeout)
def EvaluateJavaScriptInContext(self, expr, context_id,
timeout=DEFAULT_WEB_CONTENTS_TIMEOUT):
"""Similar to ExecuteJavaScript, except context_id can refer to an iframe.
The main page has context_id=1, the first iframe context_id=2, etc.
Raises:
exceptions.EvaluateException
exceptions.WebSocketDisconnected
exceptions.TimeoutException
exceptions.DevtoolsTargetCrashException
"""
return self._inspector_backend.EvaluateJavaScript(
expr, context_id=context_id, timeout=timeout)
def EnableAllContexts(self):
"""Enable all contexts in a page. Returns the number of available contexts.
Raises:
exceptions.WebSocketDisconnected
exceptions.TimeoutException
exceptions.DevtoolsTargetCrashException
"""
return self._inspector_backend.EnableAllContexts()
def WaitForNavigate(self, timeout=DEFAULT_WEB_CONTENTS_TIMEOUT):
"""Waits for the navigation to complete.
The current page is expect to be in a navigation.
This function returns when the navigation is complete or when
the timeout has been exceeded.
Raises:
exceptions.TimeoutException
exceptions.DevtoolsTargetCrashException
"""
self._inspector_backend.WaitForNavigate(timeout)
def Navigate(self, url, script_to_evaluate_on_commit=None,
timeout=DEFAULT_WEB_CONTENTS_TIMEOUT):
"""Navigates to url.
If |script_to_evaluate_on_commit| is given, the script source string will be
evaluated when the navigation is committed. This is after the context of
the page exists, but before any script on the page itself has executed.
Raises:
exceptions.TimeoutException
exceptions.DevtoolsTargetCrashException
"""
self._inspector_backend.Navigate(url, script_to_evaluate_on_commit, timeout)
def IsAlive(self):
"""Whether the WebContents is still operating normally.
Since WebContents function asynchronously, this method does not guarantee
that the WebContents will still be alive at any point in the future.
Returns:
A boolean indicating whether the WebContents is opearting normally.
"""
return self._inspector_backend.IsInspectable()
def CloseConnections(self):
"""Closes all TCP sockets held open by the browser.
Raises:
exceptions.DevtoolsTargetCrashException if the tab is not alive.
"""
if not self.IsAlive():
raise exceptions.DevtoolsTargetCrashException
self.ExecuteJavaScript('window.chrome && chrome.benchmarking &&'
'chrome.benchmarking.closeConnections()')
def SynthesizeScrollGesture(self, x=100, y=800, xDistance=0, yDistance=-500,
xOverscroll=None, yOverscroll=None,
preventFling=True, speed=None,
gestureSourceType=None, repeatCount=None,
repeatDelayMs=None, interactionMarkerName=None,
timeout=60):
"""Runs an inspector command that causes a repeatable browser driven scroll.
Args:
x: X coordinate of the start of the gesture in CSS pixels.
y: Y coordinate of the start of the gesture in CSS pixels.
xDistance: Distance to scroll along the X axis (positive to scroll left).
yDistance: Ddistance to scroll along the Y axis (positive to scroll up).
xOverscroll: Number of additional pixels to scroll back along the X axis.
xOverscroll: Number of additional pixels to scroll back along the Y axis.
preventFling: Prevents a fling gesture.
speed: Swipe speed in pixels per second.
gestureSourceType: Which type of input events to be generated.
repeatCount: Number of additional repeats beyond the first scroll.
repeatDelayMs: Number of milliseconds delay between each repeat.
interactionMarkerName: The name of the interaction markers to generate.
Raises:
exceptions.TimeoutException
exceptions.DevtoolsTargetCrashException
"""
return self._inspector_backend.SynthesizeScrollGesture(
x=x, y=y, xDistance=xDistance, yDistance=yDistance,
xOverscroll=xOverscroll, yOverscroll=yOverscroll,
preventFling=preventFling, speed=speed,
gestureSourceType=gestureSourceType, repeatCount=repeatCount,
repeatDelayMs=repeatDelayMs,
interactionMarkerName=interactionMarkerName,
timeout=timeout)