--- a/testing/marionette/client/marionette_driver/addons.py
+++ b/testing/marionette/client/marionette_driver/addons.py
@@ -49,17 +49,18 @@ class Addons(object):
:param temp: Install a temporary addon. Temporary addons will
automatically be uninstalled on shutdown and do not need
to be signed, though they must be restartless.
:returns: The addon ID string of the newly installed addon.
:raises: :exc:`AddonInstallException`
"""
with self._mn.using_context('chrome'):
addon_id, status = self._mn.execute_async_script("""
- let FileUtils = Components.utils.import("resource://gre/modules/FileUtils.jsm").FileUtils;
+ let fileUtils = Components.utils.import("resource://gre/modules/FileUtils.jsm");
+ let FileUtils = fileUtils.FileUtils;
Components.utils.import("resource://gre/modules/AddonManager.jsm");
let listener = {
onInstallEnded: function(install, addon) {
marionetteScriptFinished([addon.id, 0]);
},
onInstallFailed: function(install) {
marionetteScriptFinished([null, install.error]);
--- a/testing/marionette/client/marionette_driver/date_time_value.py
+++ b/testing/marionette/client/marionette_driver/date_time_value.py
@@ -1,12 +1,13 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
class DateTimeValue(object):
"""
Interface for setting the value of HTML5 "date" and "time" input elements.
Simple usage example:
::
@@ -41,9 +42,8 @@ class DateTimeValue(object):
return self.element.get_attribute('value')
# As per the W3C "time" element specification
# (http://dev.w3.org/html5/markup/input.time.html), this value is formatted
# according to RFC 3339: http://tools.ietf.org/html/rfc3339#section-5.6
@time.setter
def time(self, time_value):
self.element.send_keys(time_value.strftime('%H:%M:%S'))
-
--- a/testing/marionette/client/marionette_driver/decorators.py
+++ b/testing/marionette/client/marionette_driver/decorators.py
@@ -3,24 +3,26 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from errors import MarionetteException
from functools import wraps
import socket
import sys
import traceback
+
def _find_marionette_in_args(*args, **kwargs):
try:
m = [a for a in args + tuple(kwargs.values()) if hasattr(a, 'session')][0]
except IndexError:
print("Can only apply decorator to function using a marionette object")
raise
return m
+
def do_crash_check(func, always=False):
"""Decorator which checks for crashes after the function has run.
:param always: If False, only checks for crashes if an exception
was raised. If True, always checks for crashes.
"""
@wraps(func)
def _(*args, **kwargs):
@@ -40,16 +42,17 @@ def do_crash_check(func, always=False):
if not always:
check()
raise exc, val, tb
finally:
if always:
check()
return _
+
def uses_marionette(func):
"""Decorator which creates a marionette session and deletes it
afterwards if one doesn't already exist.
"""
@wraps(func)
def _(*args, **kwargs):
m = _find_marionette_in_args(*args, **kwargs)
delete_session = False
@@ -61,23 +64,24 @@ def uses_marionette(func):
ret = func(*args, **kwargs)
if delete_session:
m.delete_session()
return ret
return _
+
def using_context(context):
"""Decorator which allows a function to execute in certain scope
using marionette.using_context functionality and returns to old
scope once the function exits.
:param context: Either 'chrome' or 'content'.
"""
def wrap(func):
- @wraps(func)
- def inner(*args, **kwargs):
- m = _find_marionette_in_args(*args, **kwargs)
- with m.using_context(context):
- return func(*args, **kwargs)
+ @wraps(func)
+ def inner(*args, **kwargs):
+ m = _find_marionette_in_args(*args, **kwargs)
+ with m.using_context(context):
+ return func(*args, **kwargs)
- return inner
+ return inner
return wrap
--- a/testing/marionette/client/marionette_driver/errors.py
+++ b/testing/marionette/client/marionette_driver/errors.py
@@ -1,14 +1,13 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import traceback
-import types
class InstallGeckoError(Exception):
pass
class MarionetteException(Exception):
@@ -59,16 +58,17 @@ class ElementNotSelectableException(Mari
class InvalidArgumentException(MarionetteException):
status = "invalid argument"
class InvalidSessionIdException(MarionetteException):
status = "invalid session id"
+
class TimeoutException(MarionetteException):
code = (21,)
status = "timeout"
class JavascriptException(MarionetteException):
code = (17,)
status = "javascript error"
@@ -93,18 +93,18 @@ class ScriptTimeoutException(MarionetteE
code = (28,)
status = "script timeout"
class ElementNotVisibleException(MarionetteException):
code = (11,)
status = "element not visible"
- def __init__(
- self, message="Element is not currently visible and may not be manipulated",
+ def __init__(self,
+ message="Element is not currently visible and may not be manipulated",
stacktrace=None, cause=None):
super(ElementNotVisibleException, self).__init__(
message, cause=cause, stacktrace=stacktrace)
class ElementNotAccessibleException(MarionetteException):
code = (56,)
status = "element not accessible"
--- a/testing/marionette/client/marionette_driver/expected.py
+++ b/testing/marionette/client/marionette_driver/expected.py
@@ -10,68 +10,73 @@ from marionette import HTMLElement
"""This file provides a set of expected conditions for common use
cases when writing Marionette tests.
The conditions rely on explicit waits that retries conditions a number
of times until they are either successfully met, or they time out.
"""
+
class element_present(object):
"""Checks that a web element is present in the DOM of the current
context. This does not necessarily mean that the element is
visible.
You can select which element to be checked for presence by
supplying a locator::
el = Wait(marionette).until(expected.element_present(By.ID, "foo"))
Or by using a function/lambda returning an element::
- el = Wait(marionette).until(expected.element_present(lambda m: m.find_element(By.ID, "foo")))
+ el = Wait(marionette).\
+ until(expected.element_present(lambda m: m.find_element(By.ID, "foo")))
:param args: locator or function returning web element
:returns: the web element once it is located, or False
"""
def __init__(self, *args):
if len(args) == 1 and isinstance(args[0], types.FunctionType):
self.locator = args[0]
else:
self.locator = lambda m: m.find_element(*args)
def __call__(self, marionette):
return _find(marionette, self.locator)
+
class element_not_present(element_present):
"""Checks that a web element is not present in the DOM of the current
context.
You can select which element to be checked for lack of presence by
supplying a locator::
r = Wait(marionette).until(expected.element_not_present(By.ID, "foo"))
Or by using a function/lambda returning an element::
- r = Wait(marionette).until(expected.element_present(lambda m: m.find_element(By.ID, "foo")))
+ r = Wait(marionette).\
+ until(expected.element_present(lambda m: m.find_element(By.ID, "foo")))
:param args: locator or function returning web element
:returns: True if element is not present, or False if it is present
"""
def __init__(self, *args):
super(element_not_present, self).__init__(*args)
def __call__(self, marionette):
return not super(element_not_present, self).__call__(marionette)
+
class element_stale(object):
"""Check that the given element is no longer attached to DOM of the
current context.
This can be useful for waiting until an element is no longer
present.
Sample usage::
@@ -92,69 +97,74 @@ class element_stale(object):
def __call__(self, marionette):
try:
# Calling any method forces a staleness check
self.el.is_enabled()
return False
except errors.StaleElementException:
return True
+
class elements_present(object):
"""Checks that web elements are present in the DOM of the current
context. This does not necessarily mean that the elements are
visible.
You can select which elements to be checked for presence by
supplying a locator::
els = Wait(marionette).until(expected.elements_present(By.TAG_NAME, "a"))
Or by using a function/lambda returning a list of elements::
- els = Wait(marionette).until(expected.elements_present(lambda m: m.find_elements(By.TAG_NAME, "a")))
+ els = Wait(marionette).\
+ until(expected.elements_present(lambda m: m.find_elements(By.TAG_NAME, "a")))
:param args: locator or function returning a list of web elements
:returns: list of web elements once they are located, or False
"""
def __init__(self, *args):
if len(args) == 1 and isinstance(args[0], types.FunctionType):
self.locator = args[0]
else:
self.locator = lambda m: m.find_elements(*args)
def __call__(self, marionette):
return _find(marionette, self.locator)
+
class elements_not_present(elements_present):
"""Checks that web elements are not present in the DOM of the
current context.
You can select which elements to be checked for not being present
by supplying a locator::
r = Wait(marionette).until(expected.elements_not_present(By.TAG_NAME, "a"))
Or by using a function/lambda returning a list of elements::
- r = Wait(marionette).until(expected.elements_not_present(lambda m: m.find_elements(By.TAG_NAME, "a")))
+ r = Wait(marionette).\
+ until(expected.elements_not_present(lambda m: m.find_elements(By.TAG_NAME, "a")))
:param args: locator or function returning a list of web elements
:returns: True if elements are missing, False if one or more are
present
"""
def __init__(self, *args):
super(elements_not_present, self).__init__(*args)
def __call__(self, marionette):
return not super(elements_not_present, self).__call__(marionette)
+
class element_displayed(object):
"""An expectation for checking that an element is visible.
Visibility means that the element is not only displayed, but also
has a height and width that is greater than 0 pixels.
Stale elements, meaning elements that have been detached from the
DOM of the current context are treated as not being displayed,
@@ -188,16 +198,17 @@ class element_displayed(object):
self.el = _find(marionette, self.locator)
if not self.el:
return False
try:
return self.el.is_displayed()
except errors.StaleElementException:
return False
+
class element_not_displayed(element_displayed):
"""An expectation for checking that an element is not visible.
Visibility means that the element is not only displayed, but also
has a height and width that is greater than 0 pixels.
Stale elements, meaning elements that have been detached fom the
DOM of the current context are treated as not being displayed,
@@ -220,73 +231,78 @@ class element_not_displayed(element_disp
"""
def __init__(self, *args):
super(element_not_displayed, self).__init__(*args)
def __call__(self, marionette):
return not super(element_not_displayed, self).__call__(marionette)
+
class element_selected(object):
"""An expectation for checking that the given element is selected.
:param element: the element to be selected
:returns: True if element is selected, False otherwise
"""
def __init__(self, element):
self.el = element
def __call__(self, marionette):
return self.el.is_selected()
+
class element_not_selected(element_selected):
"""An expectation for checking that the given element is not
selected.
:param element: the element to not be selected
:returns: True if element is not selected, False if selected
"""
def __init__(self, element):
super(element_not_selected, self).__init__(element)
def __call__(self, marionette):
return not super(element_not_selected, self).__call__(marionette)
+
class element_enabled(object):
"""An expectation for checking that the given element is enabled.
:param element: the element to check if enabled
:returns: True if element is enabled, False otherwise
"""
def __init__(self, element):
self.el = element
def __call__(self, marionette):
return self.el.is_enabled()
+
class element_not_enabled(element_enabled):
"""An expectation for checking that the given element is disabled.
:param element: the element to check if disabled
:returns: True if element is disabled, False if enabled
"""
def __init__(self, element):
super(element_not_enabled, self).__init__(element)
def __call__(self, marionette):
return not super(element_not_enabled, self).__call__(marionette)
+
def _find(marionette, func):
el = None
try:
el = func(marionette)
except errors.NoSuchElementException:
pass
--- a/testing/marionette/client/marionette_driver/geckoinstance.py
+++ b/testing/marionette/client/marionette_driver/geckoinstance.py
@@ -126,18 +126,18 @@ class GeckoInstance(object):
process_args['stream'] = sys.stdout
else:
process_args['logfile'] = self.gecko_log
env = os.environ.copy()
# environment variables needed for crashreporting
# https://developer.mozilla.org/docs/Environment_variables_affecting_crash_reporting
- env.update({ 'MOZ_CRASHREPORTER': '1',
- 'MOZ_CRASHREPORTER_NO_REPORT': '1', })
+ env.update({'MOZ_CRASHREPORTER': '1',
+ 'MOZ_CRASHREPORTER_NO_REPORT': '1'})
self.runner = Runner(
binary=self.bin,
profile=self.profile,
cmdargs=['-no-remote', '-marionette'] + self.app_args,
env=env,
symbols_path=self.symbols_path,
process_args=process_args)
self.runner.start()
@@ -158,16 +158,17 @@ class GeckoInstance(object):
self.profile = None
if prefs:
self.prefs = prefs
else:
self.prefs = None
self.start()
+
class B2GDesktopInstance(GeckoInstance):
def __init__(self, host, port, bin, **kwargs):
# Pass a profile and change the binary to -bin so that
# the built-in gaia profile doesn't get touched.
if kwargs.get('profile', None) is None:
# GeckoInstance.start will clone the profile.
kwargs['profile'] = os.path.join(os.path.dirname(bin),
'gaia',
--- a/testing/marionette/client/marionette_driver/gestures.py
+++ b/testing/marionette/client/marionette_driver/gestures.py
@@ -1,21 +1,25 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from marionette import MultiActions, Actions
-#axis is y or x
-#direction is 0 for positive, and -1 for negative
-#length is the total length we want to scroll
-#increments is how much we want to move per scrolling
-#wait_period is the seconds we wait between scrolling
-#scroll_back is whether we want to scroll back to original view
-def smooth_scroll(marionette_session, start_element, axis, direction, length, increments=None, wait_period=None, scroll_back=None):
+
+def smooth_scroll(marionette_session, start_element, axis, direction,
+ length, increments=None, wait_period=None, scroll_back=None):
+ """
+ :param axis: y or x
+ :param direction: 0 for positive, and -1 for negative
+ :param length: total length of scroll scroll
+ :param increments: Amount to be moved per scrolling
+ :param wait_period: Seconds to wait between scrolling
+ :param scroll_back: Scroll back to original view?
+ """
if axis not in ["x", "y"]:
raise Exception("Axis must be either 'x' or 'y'")
if direction not in [-1, 0]:
raise Exception("Direction must either be -1 negative or 0 positive")
increments = increments or 100
wait_period = wait_period or 0.05
scroll_back = scroll_back or False
current = 0
@@ -37,23 +41,26 @@ def smooth_scroll(marionette_session, st
if scroll_back:
offset = [-value for value in offset]
while (current > 0):
current -= increments
action.move_by_offset(*offset).wait(wait_period)
action.release()
action.perform()
-#element is the target
-#x1,x2 are 1st finger starting position relative to the target
-#x3,y3 are 1st finger ending position relative to the target
-#x2,y2 are 2nd finger starting position relative to the target
-#x4,y4 are 2nd finger ending position relative to the target
-#duration is the amount of time in milliseconds we want to complete the pinch.
+
def pinch(marionette_session, element, x1, y1, x2, y2, x3, y3, x4, y4, duration=200):
+ """
+ :param element: target
+ :param x1, y1: 1st finger starting position relative to the target
+ :param x3, y3: 1st finger ending position relative to the target
+ :param x2, y2: 2nd finger starting position relative to the target
+ :param x4, y4: 2nd finger ending position relative to the target
+ :param duration: Amount of time in milliseconds to complete the pinch.
+ """
time = 0
time_increment = 10
if time_increment >= duration:
time_increment = duration
move_x1 = time_increment*1.0/duration * (x3 - x1)
move_y1 = time_increment*1.0/duration * (y3 - y1)
move_x2 = time_increment*1.0/duration * (x4 - x2)
move_y2 = time_increment*1.0/duration * (y4 - y2)
@@ -65,19 +72,22 @@ def pinch(marionette_session, element, x
while (time < duration):
time += time_increment
action1.move_by_offset(move_x1, move_y1).wait(time_increment/1000)
action2.move_by_offset(move_x2, move_y2).wait(time_increment/1000)
action1.release()
action2.release()
multiAction.add(action1).add(action2).perform()
-#element: The element to press.
-#time_in_seconds: Time in seconds to wait before releasing the press.
-#x: Optional, x-coordinate to tap, relative to the top-left corner of the element.
-#y: Optional, y-coordinate to tap, relative to the top-leftcorner of the element.
+
def long_press_without_contextmenu(marionette_session, element, time_in_seconds, x=None, y=None):
+ """
+ :param element: The element to press.
+ :param time_in_seconds: Time in seconds to wait before releasing the press.
+ #x: Optional, x-coordinate to tap, relative to the top-left corner of the element.
+ #y: Optional, y-coordinate to tap, relative to the top-leftcorner of the element.
+ """
action = Actions(marionette_session)
action.press(element, x, y)
action.move_by_offset(0, 0)
action.wait(time_in_seconds)
action.release()
action.perform()
--- a/testing/marionette/client/marionette_driver/keys.py
+++ b/testing/marionette/client/marionette_driver/keys.py
@@ -11,74 +11,74 @@
# distributed under the License is distributed on an "AS IS" BASIS
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class Keys(object):
- NULL = u'\ue000'
- CANCEL = u'\ue001' # ^break
- HELP = u'\ue002'
- BACK_SPACE = u'\ue003'
- TAB = u'\ue004'
- CLEAR = u'\ue005'
- RETURN = u'\ue006'
- ENTER = u'\ue007'
- SHIFT = u'\ue008'
- LEFT_SHIFT = u'\ue008' # alias
- CONTROL = u'\ue009'
- LEFT_CONTROL = u'\ue009' # alias
- ALT = u'\ue00a'
- LEFT_ALT = u'\ue00a' # alias
- PAUSE = u'\ue00b'
- ESCAPE = u'\ue00c'
- SPACE = u'\ue00d'
- PAGE_UP = u'\ue00e'
- PAGE_DOWN = u'\ue00f'
- END = u'\ue010'
- HOME = u'\ue011'
- LEFT = u'\ue012'
- ARROW_LEFT = u'\ue012' # alias
- UP = u'\ue013'
- ARROW_UP = u'\ue013' # alias
- RIGHT = u'\ue014'
- ARROW_RIGHT = u'\ue014' # alias
- DOWN = u'\ue015'
- ARROW_DOWN = u'\ue015' # alias
- INSERT = u'\ue016'
- DELETE = u'\ue017'
- SEMICOLON = u'\ue018'
- EQUALS = u'\ue019'
+ NULL = u'\ue000'
+ CANCEL = u'\ue001' # ^break
+ HELP = u'\ue002'
+ BACK_SPACE = u'\ue003'
+ TAB = u'\ue004'
+ CLEAR = u'\ue005'
+ RETURN = u'\ue006'
+ ENTER = u'\ue007'
+ SHIFT = u'\ue008'
+ LEFT_SHIFT = u'\ue008' # alias
+ CONTROL = u'\ue009'
+ LEFT_CONTROL = u'\ue009' # alias
+ ALT = u'\ue00a'
+ LEFT_ALT = u'\ue00a' # alias
+ PAUSE = u'\ue00b'
+ ESCAPE = u'\ue00c'
+ SPACE = u'\ue00d'
+ PAGE_UP = u'\ue00e'
+ PAGE_DOWN = u'\ue00f'
+ END = u'\ue010'
+ HOME = u'\ue011'
+ LEFT = u'\ue012'
+ ARROW_LEFT = u'\ue012' # alias
+ UP = u'\ue013'
+ ARROW_UP = u'\ue013' # alias
+ RIGHT = u'\ue014'
+ ARROW_RIGHT = u'\ue014' # alias
+ DOWN = u'\ue015'
+ ARROW_DOWN = u'\ue015' # alias
+ INSERT = u'\ue016'
+ DELETE = u'\ue017'
+ SEMICOLON = u'\ue018'
+ EQUALS = u'\ue019'
- NUMPAD0 = u'\ue01a' # numbe pad keys
- NUMPAD1 = u'\ue01b'
- NUMPAD2 = u'\ue01c'
- NUMPAD3 = u'\ue01d'
- NUMPAD4 = u'\ue01e'
- NUMPAD5 = u'\ue01f'
- NUMPAD6 = u'\ue020'
- NUMPAD7 = u'\ue021'
- NUMPAD8 = u'\ue022'
- NUMPAD9 = u'\ue023'
- MULTIPLY = u'\ue024'
- ADD = u'\ue025'
- SEPARATOR = u'\ue026'
- SUBTRACT = u'\ue027'
- DECIMAL = u'\ue028'
- DIVIDE = u'\ue029'
+ NUMPAD0 = u'\ue01a' # numbe pad keys
+ NUMPAD1 = u'\ue01b'
+ NUMPAD2 = u'\ue01c'
+ NUMPAD3 = u'\ue01d'
+ NUMPAD4 = u'\ue01e'
+ NUMPAD5 = u'\ue01f'
+ NUMPAD6 = u'\ue020'
+ NUMPAD7 = u'\ue021'
+ NUMPAD8 = u'\ue022'
+ NUMPAD9 = u'\ue023'
+ MULTIPLY = u'\ue024'
+ ADD = u'\ue025'
+ SEPARATOR = u'\ue026'
+ SUBTRACT = u'\ue027'
+ DECIMAL = u'\ue028'
+ DIVIDE = u'\ue029'
- F1 = u'\ue031' # function keys
- F2 = u'\ue032'
- F3 = u'\ue033'
- F4 = u'\ue034'
- F5 = u'\ue035'
- F6 = u'\ue036'
- F7 = u'\ue037'
- F8 = u'\ue038'
- F9 = u'\ue039'
- F10 = u'\ue03a'
- F11 = u'\ue03b'
- F12 = u'\ue03c'
+ F1 = u'\ue031' # function keys
+ F2 = u'\ue032'
+ F3 = u'\ue033'
+ F4 = u'\ue034'
+ F5 = u'\ue035'
+ F6 = u'\ue036'
+ F7 = u'\ue037'
+ F8 = u'\ue038'
+ F9 = u'\ue039'
+ F10 = u'\ue03a'
+ F11 = u'\ue03b'
+ F12 = u'\ue03c'
- META = u'\ue03d'
- COMMAND = u'\ue03d'
+ META = u'\ue03d'
+ COMMAND = u'\ue03d'
--- a/testing/marionette/client/marionette_driver/marionette.py
+++ b/testing/marionette/client/marionette_driver/marionette.py
@@ -2,17 +2,16 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import base64
import ConfigParser
import json
import os
import socket
-import StringIO
import traceback
import warnings
from contextlib import contextmanager
from decorators import do_crash_check
from keys import Keys
@@ -223,17 +222,17 @@ class Actions(object):
relative to the top-left corner of the element.
:param element: The element to press on.
:param x: Optional, x-coordinate to tap, relative to the top-left
corner of the element.
:param y: Optional, y-coordinate to tap, relative to the top-left
corner of the element.
'''
- element=element.id
+ element = element.id
self.action_chain.append(['press', element, x, y])
return self
def release(self):
'''
Sends a 'touchend' event to this element.
May only be called if press() has already be called on this element.
@@ -252,39 +251,43 @@ class Actions(object):
def move(self, element):
'''
Sends a 'touchmove' event at the center of the target element.
:param element: Element to move towards.
May only be called if press() has already be called.
'''
- element=element.id
+ element = element.id
self.action_chain.append(['move', element])
return self
def move_by_offset(self, x, y):
'''
- Sends 'touchmove' event to the given x, y coordinates relative to the top-left of the currently touched element.
+ Sends 'touchmove' event to the given x, y coordinates relative to the
+ top-left of the currently touched element.
May only be called if press() has already be called.
:param x: Specifies x-coordinate of move event, relative to the
top-left corner of the element.
:param y: Specifies y-coordinate of move event, relative to the
top-left corner of the element.
'''
self.action_chain.append(['moveByOffset', x, y])
return self
def wait(self, time=None):
'''
Waits for specified time period.
- :param time: Time in seconds to wait. If time is None then this has no effect for a single action chain. If used inside a multi-action chain, then time being None indicates that we should wait for all other currently executing actions that are part of the chain to complete.
+ :param time: Time in seconds to wait. If time is None then this has no effect
+ for a single action chain. If used inside a multi-action chain,
+ then time being None indicates that we should wait for all other
+ currently executing actions that are part of the chain to complete.
'''
self.action_chain.append(['wait', time])
return self
def cancel(self):
'''
Sends 'touchcancel' event to the target of the original 'touchstart' event.
@@ -306,32 +309,32 @@ class Actions(object):
element.
This is equivalent to calling:
::
action.press(element, x, y).release()
'''
- element=element.id
+ element = element.id
self.action_chain.append(['press', element, x, y])
self.action_chain.append(['release'])
return self
def double_tap(self, element, x=None, y=None):
'''
Performs a double tap on the target element.
:param element: The element to double tap.
:param x: Optional, x-coordinate of double tap, relative to the
top-left corner of the element.
:param y: Optional, y-coordinate of double tap, relative to the
top-left corner of the element.
'''
- element=element.id
+ element = element.id
self.action_chain.append(['press', element, x, y])
self.action_chain.append(['release'])
self.action_chain.append(['press', element, x, y])
self.action_chain.append(['release'])
return self
def click(self, element, button=MouseButton.LEFT, count=1):
'''
@@ -448,16 +451,17 @@ class Actions(object):
def perform(self):
"""Sends the action chain built so far to the server side for
execution and clears the current chain of actions."""
body = {"chain": self.action_chain, "nextId": self.current_id}
self.current_id = self.marionette._send_message("actionChain", body, key="value")
self.action_chain = []
return self
+
class MultiActions(object):
'''
A MultiActions object represents a sequence of actions that may be
performed at the same time. Its intent is to allow the simulation
of multi-touch gestures.
Usage example:
::
@@ -482,17 +486,17 @@ class MultiActions(object):
def add(self, action):
'''
Adds a set of actions to perform.
:param action: An Actions object.
'''
self.multi_actions.append(action.action_chain)
if len(action.action_chain) > self.max_length:
- self.max_length = len(action.action_chain)
+ self.max_length = len(action.action_chain)
return self
def perform(self):
"""Perform all the actions added to this object."""
body = {"value": self.multi_actions, "max_length": self.max_length}
self.marionette._send_message("multiAction", body)
@@ -526,18 +530,18 @@ class Alert(object):
tab modal dialog."""
body = {"value": Marionette.convert_keys(*string)}
self.marionette._send_message("sendKeysToDialog", body)
class Marionette(object):
"""Represents a Marionette connection to a browser or device."""
- CONTEXT_CHROME = 'chrome' # non-browser content: windows, dialogs, etc.
- CONTEXT_CONTENT = 'content' # browser content: iframes, divs, etc.
+ CONTEXT_CHROME = 'chrome' # non-browser content: windows, dialogs, etc.
+ CONTEXT_CONTENT = 'content' # browser content: iframes, divs, etc.
TIMEOUT_SEARCH = 'implicit'
TIMEOUT_SCRIPT = 'script'
TIMEOUT_PAGE = 'page load'
DEFAULT_SOCKET_TIMEOUT = 360
DEFAULT_STARTUP_TIMEOUT = 120
def __init__(self, host='localhost', port=2828, app=None, app_args=None,
bin=None, profile=None, addons=None,
@@ -633,17 +637,16 @@ class Marionette(object):
timeout = timeout or self.DEFAULT_STARTUP_TIMEOUT
return transport.wait_for_port(self.host, self.port, timeout=timeout)
@do_crash_check
def raise_for_port(self, port_obtained):
if not port_obtained:
raise IOError("Timed out waiting for port!")
-
@do_crash_check
def _send_message(self, name, params=None, key=None):
"""Send a blocking message to the server.
Marionette provides an asynchronous, non-blocking interface and
this attempts to paper over this by providing a synchronous API
to the user.
@@ -688,18 +691,18 @@ class Marionette(object):
self._handle_error(err)
if key is not None:
return self._unwrap_response(res.get(key))
else:
return self._unwrap_response(res)
def _unwrap_response(self, value):
- if isinstance(value, dict) and \
- (WEBELEMENT_KEY in value or W3C_WEBELEMENT_KEY in value):
+ if isinstance(value, dict) and (WEBELEMENT_KEY in value or
+ W3C_WEBELEMENT_KEY in value):
if value.get(WEBELEMENT_KEY):
return HTMLElement(self, value.get(WEBELEMENT_KEY))
else:
return HTMLElement(self, value.get(W3C_WEBELEMENT_KEY))
elif isinstance(value, list):
return list(self._unwrap_response(item) for item in value)
else:
return value
@@ -733,17 +736,17 @@ class Marionette(object):
name = None
crashed = False
if self.instance:
if self.instance.runner.check_for_crashes(
test_name=self.test_name):
crashed = True
if returncode is not None:
print ('PROCESS-CRASH | %s | abnormal termination with exit code %d' %
- (name, returncode))
+ (name, returncode))
return crashed
@staticmethod
def convert_keys(*string):
typing = []
for val in string:
if isinstance(val, Keys):
typing.append(val)
@@ -752,97 +755,103 @@ class Marionette(object):
for i in range(len(val)):
typing.append(val[i])
else:
for i in range(len(val)):
typing.append(val[i])
return typing
def get_permission(self, perm):
+ script = """
+ let value = {
+ 'url': document.nodePrincipal.URI.spec,
+ 'appId': document.nodePrincipal.appId,
+ 'isInIsolatedMozBrowserElement': document.nodePrincipal.isInIsolatedMozBrowserElement,
+ 'type': arguments[0]
+ };
+ return value;"""
with self.using_context('content'):
- value = self.execute_script("""
- let value = {
- 'url': document.nodePrincipal.URI.spec,
- 'appId': document.nodePrincipal.appId,
- 'isInIsolatedMozBrowserElement': document.nodePrincipal.isInIsolatedMozBrowserElement,
- 'type': arguments[0]
- };
- return value;
- """, script_args=[perm], sandbox='system')
+ value = self.execute_script(script, script_args=[perm], sandbox='system')
with self.using_context('chrome'):
permission = self.execute_script("""
Components.utils.import("resource://gre/modules/Services.jsm");
let perm = arguments[0];
let secMan = Services.scriptSecurityManager;
- let attrs = {appId: perm.appId, inIsolatedMozBrowser: perm.isInIsolatedMozBrowserElement};
+ let attrs = {appId: perm.appId,
+ inIsolatedMozBrowser: perm.isInIsolatedMozBrowserElement};
let principal = secMan.createCodebasePrincipal(
Services.io.newURI(perm.url, null, null),
attrs);
let testPerm = Services.perms.testPermissionFromPrincipal(
principal, perm.type);
return testPerm;
""", script_args=[value])
return permission
def push_permission(self, perm, allow):
- with self.using_context('content'):
- perm = self.execute_script("""
- let allow = arguments[0];
- if (typeof(allow) == "boolean") {
- if (allow) {
- allow = Components.interfaces.nsIPermissionManager.ALLOW_ACTION;
- }
- else {
- allow = Components.interfaces.nsIPermissionManager.DENY_ACTION;
- }
- }
- let perm_type = arguments[1];
+ script = """
+ let allow = arguments[0];
+ if (typeof(allow) == "boolean") {
+ if (allow) {
+ allow = Components.interfaces.nsIPermissionManager.ALLOW_ACTION;
+ }
+ else {
+ allow = Components.interfaces.nsIPermissionManager.DENY_ACTION;
+ }
+ }
+ let perm_type = arguments[1];
- Components.utils.import("resource://gre/modules/Services.jsm");
- window.wrappedJSObject.permChanged = false;
- window.wrappedJSObject.permObserver = function(subject, topic, data) {
- if (topic == "perm-changed") {
- let permission = subject.QueryInterface(Components.interfaces.nsIPermission);
- if (perm_type == permission.type) {
- Services.obs.removeObserver(window.wrappedJSObject.permObserver, "perm-changed");
- window.wrappedJSObject.permChanged = true;
- }
- }
- };
- Services.obs.addObserver(window.wrappedJSObject.permObserver,
- "perm-changed", false);
+ Components.utils.import("resource://gre/modules/Services.jsm");
+ window.wrappedJSObject.permChanged = false;
+ window.wrappedJSObject.permObserver = function(subject, topic, data) {
+ if (topic == "perm-changed") {
+ let permission = subject.QueryInterface(Components.interfaces.nsIPermission);
+ if (perm_type == permission.type) {
+ Services.obs.removeObserver(window.wrappedJSObject.permObserver,
+ "perm-changed");
+ window.wrappedJSObject.permChanged = true;
+ }
+ }
+ };
+ Services.obs.addObserver(window.wrappedJSObject.permObserver,
+ "perm-changed", false);
- let value = {
- 'url': document.nodePrincipal.URI.spec,
- 'appId': document.nodePrincipal.appId,
- 'isInIsolatedMozBrowserElement': document.nodePrincipal.isInIsolatedMozBrowserElement,
- 'type': perm_type,
- 'action': allow
- };
- return value;
- """, script_args=[allow, perm], sandbox='system')
+ let value = {
+ 'url': document.nodePrincipal.URI.spec,
+ 'appId': document.nodePrincipal.appId,
+ 'isInIsolatedMozBrowserElement': document.nodePrincipal.isInIsolatedMozBrowserElement,
+ 'type': perm_type,
+ 'action': allow
+ };
+ return value;
+ """
+ with self.using_context('content'):
+ perm = self.execute_script(script, script_args=[allow, perm], sandbox='system')
current_perm = self.get_permission(perm['type'])
if current_perm == perm['action']:
with self.using_context('content'):
self.execute_script("""
Components.utils.import("resource://gre/modules/Services.jsm");
- Services.obs.removeObserver(window.wrappedJSObject.permObserver, "perm-changed");
+ Services.obs.removeObserver(window.wrappedJSObject.permObserver,
+ "perm-changed");
""", sandbox='system')
return
with self.using_context('chrome'):
self.execute_script("""
Components.utils.import("resource://gre/modules/Services.jsm");
let perm = arguments[0];
let secMan = Services.scriptSecurityManager;
- let attrs = {appId: perm.appId, inIsolatedMozBrowser: perm.isInIsolatedMozBrowserElement};
- let principal = secMan.createCodebasePrincipal(Services.io.newURI(perm.url, null, null),
- attrs);
+ let attrs = {appId: perm.appId,
+ inIsolatedMozBrowser: perm.isInIsolatedMozBrowserElement};
+ let principal = secMan.createCodebasePrincipal(Services.io.newURI(perm.url,
+ null, null),
+ attrs);
Services.perms.addFromPrincipal(principal, perm.type, perm.action);
return true;
""", script_args=[perm])
with self.using_context("content"):
self.execute_async_script("""
let start = new Date();
let end = new Date(start.valueOf() + 5000);
@@ -957,17 +966,17 @@ class Marionette(object):
def enforce_gecko_prefs(self, prefs):
"""
Checks if the running instance has the given prefs. If not, it will kill the
currently running instance, and spawn a new instance with the requested preferences.
: param prefs: A dictionary whose keys are preference names.
"""
if not self.instance:
- raise errors.MarionetteException("enforce_gecko_prefs can only be called " \
+ raise errors.MarionetteException("enforce_gecko_prefs can only be called "
"on gecko instances launched by Marionette")
pref_exists = True
self.set_context(self.CONTEXT_CHROME)
for pref, value in prefs.iteritems():
if type(value) is not str:
value = json.dumps(value)
pref_exists = self.execute_script("""
let prefInterface = Components.classes["@mozilla.org/preferences-service;1"]
@@ -1004,24 +1013,24 @@ class Marionette(object):
: param clean: If False the same profile will be used after the restart. Note
that the in app initiated restart always maintains the same
profile.
: param in_app: If True, marionette will cause a restart from within the
browser. Otherwise the browser will be restarted immediately
by killing the process.
"""
if not self.instance:
- raise errors.MarionetteException("restart can only be called " \
+ raise errors.MarionetteException("restart can only be called "
"on gecko instances launched by Marionette")
if in_app:
if clean:
raise ValueError
# Values here correspond to constants in nsIAppStartup.
- # See https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIAppStartup
+ # See http://mzl.la/1X0JZsC
restart_flags = [
"eForceQuit",
"eRestart",
]
self._send_message("quitApplication", {"flags": restart_flags})
self.client.close()
# The instance is restarting itself; we will no longer be able to
# track it by pid, so mark it as 'detached'.
@@ -1556,17 +1565,17 @@ class Marionette(object):
result = self.marionette.execute_script("return global.test1;", new_sandbox=False)
assert result == "foo"
"""
if script_args is None:
script_args = []
args = self.wrapArguments(script_args)
stack = traceback.extract_stack()
- frame = stack[-2:-1][0] # grab the second-to-last frame
+ frame = stack[-2:-1][0] # grab the second-to-last frame
body = {"script": script,
"args": args,
"newSandbox": new_sandbox,
"sandbox": sandbox,
"scriptTimeout": script_timeout,
"line": int(frame[1]),
"filename": os.path.basename(frame[0])}
rv = self._send_message("executeScript", body, key="value")
@@ -1606,17 +1615,17 @@ class Marionette(object):
}, 5000);
''')
assert result == 1
"""
if script_args is None:
script_args = []
args = self.wrapArguments(script_args)
stack = traceback.extract_stack()
- frame = stack[-2:-1][0] # grab the second-to-last frame
+ frame = stack[-2:-1][0] # grab the second-to-last frame
body = {"script": script,
"args": args,
"newSandbox": new_sandbox,
"sandbox": sandbox,
"scriptTimeout": script_timeout,
"line": int(frame[1]),
"filename": os.path.basename(frame[0]),
"debug_script": debug_script}
--- a/testing/marionette/client/marionette_driver/transport.py
+++ b/testing/marionette/client/marionette_driver/transport.py
@@ -2,17 +2,16 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import datetime
import errno
import json
import socket
import time
-import types
class SocketTimeout(object):
def __init__(self, socket, timeout):
self.sock = socket
self.timeout = timeout
self.old_timeout = None
@@ -62,18 +61,18 @@ class Response(Message):
Message.__init__(self, msgid)
self.error = error
self.result = result
def __str__(self):
return "<Response id=%s, error=%s, result=%s>" % (self.id, self.error, self.result)
def to_msg(self):
- msg = [Response.TYPE, self.id, self.error, self.result]
- return json.dumps(msg)
+ msg = [Response.TYPE, self.id, self.error, self.result]
+ return json.dumps(msg)
@staticmethod
def from_msg(payload):
data = json.loads(payload)
assert data[0] == Response.TYPE
return Response(data[1], data[2], data[3])
@@ -112,17 +111,17 @@ class TcpTransport(object):
7:MESSAGE
On top of this protocol it uses a Marionette message format, that
depending on the protocol level offered by the remote server, varies.
Supported protocol levels are 1 and above.
"""
max_packet_length = 4096
- connection_lost_msg = "Connection to Marionette server is lost. Check gecko.log (desktop firefox) or logcat (b2g) for errors."
+ connection_lost_msg = "Connection to Marionette server is lost. Check gecko.log for errors."
def __init__(self, addr, port, socket_timeout=360.0):
"""If `socket_timeout` is `0` or `0.0`, non-blocking socket mode
will be used. Setting it to `1` or `None` disables timeouts on
socket operations altogether.
"""
self.addr = addr
self.port = port
@@ -238,26 +237,27 @@ class TcpTransport(object):
data = json.dumps(obj)
payload = "%s:%s" % (len(data), data)
totalsent = 0
while totalsent < len(payload):
try:
sent = self.sock.send(payload[totalsent:])
if sent == 0:
- raise IOError("socket error after sending %d of %d bytes" % \
- (totalsent, len(payload)))
+ raise IOError("socket error after sending %d of %d bytes" %
+ (totalsent, len(payload)))
else:
totalsent += sent
except IOError as e:
if e.errno == errno.EPIPE:
raise IOError("%s: %s" % (str(e), self.connection_lost_msg))
else:
raise e
+
def respond(self, obj):
"""Send a response to a command. This can be an arbitrary JSON
serialisable object or an ``Exception``.
"""
res, err = None, None
if isinstance(obj, Exception):
err = obj
else:
--- a/testing/marionette/client/marionette_driver/wait.py
+++ b/testing/marionette/client/marionette_driver/wait.py
@@ -5,16 +5,17 @@
import collections
import errors
import sys
import time
DEFAULT_TIMEOUT = 5
DEFAULT_INTERVAL = 0.1
+
class Wait(object):
"""An explicit conditional utility class for waiting until a condition
evaluates to true or not null.
This will repeatedly evaluate a condition in anticipation for a
truthy return value, or its timeout to expire, or its waiting
predicate to become true.
@@ -137,19 +138,21 @@ class Wait(object):
if message:
message = " with message: %s" % message
raise errors.TimeoutException(
"Timed out after %s seconds%s" %
(round((self.clock.now - start), 1), message if message else ""),
cause=last_exc)
+
def until_pred(clock, end):
return clock.now >= end
+
class SystemClock(object):
def __init__(self):
self._time = time
def sleep(self, duration):
self._time.sleep(duration)
@property