| # 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) |