Bug 1533786 - [marionette] Add support for the WebDriver Actions API. r=ato

Differential Revision: https://phabricator.services.mozilla.com/D22757

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Henrik Skupin 2019-03-11 20:33:49 +00:00
parent 6b4ae50927
commit 4b889bad47
15 changed files with 614 additions and 867 deletions

View File

@ -6,7 +6,7 @@
import string
from marionette_driver.by import By
from marionette_driver.marionette import Actions
from marionette_driver.legacy_actions import Actions
from marionette_driver.selection import SelectionManager
from marionette_harness.marionette_test import (
MarionetteTestCase,

View File

@ -6,7 +6,7 @@
import re
from marionette_driver.by import By
from marionette_driver.marionette import Actions
from marionette_driver.legacy_actions import Actions
from marionette_driver.selection import SelectionManager
from marionette_harness.marionette_test import (
MarionetteTestCase,

View File

@ -15,7 +15,7 @@ import shutil
import tempfile
from marionette_harness import MarionetteTestCase
from marionette_driver import Actions
from marionette_driver.legacy_actions import Actions
from marionette_driver.errors import JavascriptException, ScriptTimeoutException
import mozlog.structured
from marionette_driver.keys import Keys

View File

@ -6,43 +6,16 @@ Actions
Action Sequences
----------------
:class:`Actions` are designed as a way to simulate user input as closely as possible
on a touch device like a smart phone. A common operation is to tap the screen
and drag your finger to another part of the screen and lift it off.
:class:`Actions` are designed as a way to simulate user input like a keyboard
or a pointer device as closely as possible. For multiple interactions an
action sequence can be used::
This can be simulated using an Action::
element = marionette.find_element("id", "input")
element.click()
from marionette_driver.marionette import Actions
key_chain = self.marionette.actions.sequence("key", "keyboard1")
key_chain.send_keys("fooba").pause(100).key_down("r").perform()
start_element = marionette.find_element('id', 'start')
end_element = marionette.find_element('id', 'end')
action = Actions(marionette)
action.press(start_element).wait(1).move(end_element).release()
action.perform()
This will simulate pressing an element, waiting for one second, moving the
finger over to another element and then lifting the finger off the screen. The
wait is optional in this case, but can be useful for simulating delays typical
to a users behaviour.
Multi-Action Sequences
----------------------
Sometimes it may be necessary to simulate multiple actions at the same time.
For example a user may be dragging one finger while tapping another. This is
where :class:`MultiActions` come in. MultiActions are simply a way of combining
two or more actions together and performing them all at the same time::
from marionette_driver.marionette import Actions, MultiActions
action1 = Actions(marionette)
action1.press(start_element).move(end_element).release()
action2 = Actions(marionette)
action2.press(another_element).wait(1).release()
multi = MultiActions(marionette)
multi.add(action1)
multi.add(action2)
multi.perform()
This will simulate entering "fooba" into the input field, waiting for 100ms,
and pressing the key "r". The pause is optional in this case, but can be useful
for simulating delays typical to a users behaviour.

View File

@ -26,12 +26,6 @@ Actions
.. autoclass:: marionette_driver.marionette.Actions
:members:
MultiActions
------------
.. py:currentmodule:: marionette_driver.marionette.MultiActions
.. autoclass:: marionette_driver.marionette.MultiActions
:members:
Alert
-----
.. py:currentmodule:: marionette_driver.marionette.Alert

View File

@ -24,5 +24,4 @@ from marionette_driver import (
from marionette_driver.by import By
from marionette_driver.date_time_value import DateTimeValue
from marionette_driver.gestures import smooth_scroll, pinch
from marionette_driver.marionette import Actions
from marionette_driver.wait import Wait

View File

@ -4,7 +4,7 @@
from __future__ import absolute_import
from .marionette import MultiActions, Actions
from .legacy_actions import MultiActions, Actions
def smooth_scroll(marionette_session, start_element, axis, direction,

View File

@ -0,0 +1,329 @@
# 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 __future__ import absolute_import
from . import errors
from .marionette import MouseButton
class Actions(object):
"""Represent a set of actions that are executed in a particular order.
All action methods (press, etc.) return the Actions object itself, to make
it easy to create a chain of events.
Example usage::
# get html file
testAction = marionette.absolute_url("testFool.html")
# navigate to the file
marionette.navigate(testAction)
# find element1 and element2
element1 = marionette.find_element(By.ID, "element1")
element2 = marionette.find_element(By.ID, "element2")
# create action object
action = Actions(marionette)
# add actions (press, wait, move, release) into the object
action.press(element1).wait(5). move(element2).release()
# fire all the added events
action.perform()
"""
def __init__(self, marionette):
self.action_chain = []
self.marionette = marionette
self.current_id = None
def press(self, element, x=None, y=None):
'''
Sends a 'touchstart' event to this element.
If no coordinates are given, it will be targeted at the center of the
element. If given, it will be targeted at the (x,y) coordinates
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
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 :func:`press` has already be called on this element.
If press and release are chained without a move action between them,
then it will be processed as a 'tap' event, and will dispatch the
expected mouse events ('mousemove' (if necessary), 'mousedown',
'mouseup', 'mouseclick') after the touch events. If there is a wait
period between press and release that will trigger a contextmenu,
then the 'contextmenu' menu event will be fired instead of the
touch/mouse events.
'''
self.action_chain.append(['release'])
return self
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 :func:`press` has already be called.
'''
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.
May only be called if :func:`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.
'''
self.action_chain.append(['wait', time])
return self
def cancel(self):
'''
Sends 'touchcancel' event to the target of the original 'touchstart' event.
May only be called if :func:`press` has already be called.
'''
self.action_chain.append(['cancel'])
return self
def tap(self, element, x=None, y=None):
'''
Performs a quick tap on the target element.
:param element: The element to tap.
:param x: Optional, x-coordinate of tap, relative to the top-left
corner of the element. If not specified, default to center of
element.
:param y: Optional, y-coordinate of tap, relative to the top-left
corner of the element. If not specified, default to center of
element.
This is equivalent to calling:
::
action.press(element, x, y).release()
'''
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
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):
'''
Performs a click with additional parameters to allow for double clicking,
right click, middle click, etc.
:param element: The element to click.
:param button: The mouse button to click (indexed from 0, left to right).
:param count: Optional, the count of clicks to synthesize (for double
click events).
'''
el = element.id
self.action_chain.append(['click', el, button, count])
return self
def context_click(self, element):
'''
Performs a context click on the specified element.
:param element: The element to context click.
'''
return self.click(element, button=MouseButton.RIGHT)
def middle_click(self, element):
'''
Performs a middle click on the specified element.
:param element: The element to middle click.
'''
return self.click(element, button=MouseButton.MIDDLE)
def double_click(self, element):
'''
Performs a double click on the specified element.
:param element: The element to double click.
'''
return self.click(element, count=2)
def flick(self, element, x1, y1, x2, y2, duration=200):
'''
Performs a flick gesture on the target element.
:param element: The element to perform the flick gesture on.
:param x1: Starting x-coordinate of flick, relative to the top left
corner of the element.
:param y1: Starting y-coordinate of flick, relative to the top left
corner of the element.
:param x2: Ending x-coordinate of flick, relative to the top left
corner of the element.
:param y2: Ending y-coordinate of flick, relative to the top left
corner of the element.
:param duration: Time needed for the flick gesture for complete (in
milliseconds).
'''
element = element.id
elapsed = 0
time_increment = 10
if time_increment >= duration:
time_increment = duration
move_x = time_increment*1.0/duration * (x2 - x1)
move_y = time_increment*1.0/duration * (y2 - y1)
self.action_chain.append(['press', element, x1, y1])
while elapsed < duration:
elapsed += time_increment
self.action_chain.append(['moveByOffset', move_x, move_y])
self.action_chain.append(['wait', time_increment/1000])
self.action_chain.append(['release'])
return self
def long_press(self, element, time_in_seconds, x=None, y=None):
'''
Performs a long press gesture on the target element.
:param element: The element to press.
:param time_in_seconds: Time in seconds to wait before releasing the press.
: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.
This is equivalent to calling:
::
action.press(element, x, y).wait(time_in_seconds).release()
'''
element = element.id
self.action_chain.append(['press', element, x, y])
self.action_chain.append(['wait', time_in_seconds])
self.action_chain.append(['release'])
return self
def key_down(self, key_code):
"""
Perform a "keyDown" action for the given key code. Modifier keys are
respected by the server for the course of an action chain.
:param key_code: The key to press as a result of this action.
"""
self.action_chain.append(['keyDown', key_code])
return self
def key_up(self, key_code):
"""
Perform a "keyUp" action for the given key code. Modifier keys are
respected by the server for the course of an action chain.
:param key_up: The key to release as a result of this action.
"""
self.action_chain.append(['keyUp', key_code])
return self
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}
try:
self.current_id = self.marionette._send_message("Marionette:ActionChain",
body, key="value")
except errors.UnknownCommandException:
self.current_id = self.marionette._send_message("actionChain",
body, key="value")
self.action_chain = []
return self
class MultiActions(object):
"""Represent 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::
# create multiaction object
multitouch = MultiActions(marionette)
# create several action objects
action_1 = Actions(marionette)
action_2 = Actions(marionette)
# add actions to each action object/finger
action_1.press(element1).move_to(element2).release()
action_2.press(element3).wait().release(element3)
# fire all the added events
multitouch.add(action_1).add(action_2).perform()
"""
def __init__(self, marionette):
self.multi_actions = []
self.max_length = 0
self.marionette = marionette
def add(self, action):
"""Add 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)
return self
def perform(self):
"""Perform all the actions added to this object."""
body = {"value": self.multi_actions, "max_length": self.max_length}
try:
self.marionette._send_message("Marionette:MultiAction", body)
except errors.UnknownCommandException:
self.marionette._send_message("multiAction", body)

View File

@ -30,6 +30,168 @@ WEB_ELEMENT_KEY = "element-6066-11e4-a52e-4f735466cecf"
WINDOW_KEY = "window-fcc6-11e5-b4f8-330a88ab9d7f"
class MouseButton(object):
"""Enum-like class for mouse button constants."""
LEFT = 0
MIDDLE = 1
RIGHT = 2
class ActionSequence(object):
r"""API for creating and performing action sequences.
Each action method adds one or more actions to a queue. When perform()
is called, the queued actions fire in order.
May be chained together as in::
ActionSequence(self.marionette, "key", id) \
.key_down("a") \
.key_up("a") \
.perform()
"""
def __init__(self, marionette, action_type, input_id, pointer_params=None):
self.marionette = marionette
self._actions = []
self._id = input_id
self._pointer_params = pointer_params
self._type = action_type
@property
def dict(self):
d = {
"type": self._type,
"id": self._id,
"actions": self._actions,
}
if self._pointer_params is not None:
d["parameters"] = self._pointer_params
return d
def perform(self):
"""Perform all queued actions."""
self.marionette.actions.perform([self.dict])
def _key_action(self, subtype, value):
self._actions.append({"type": subtype, "value": value})
def _pointer_action(self, subtype, button):
self._actions.append({"type": subtype, "button": button})
def pause(self, duration):
self._actions.append({"type": "pause", "duration": duration})
return self
def pointer_move(self, x, y, duration=None, origin=None):
"""Queue a pointerMove action.
:param x: Destination x-axis coordinate of pointer in CSS pixels.
:param y: Destination y-axis coordinate of pointer in CSS pixels.
:param duration: Number of milliseconds over which to distribute the
move. If None, remote end defaults to 0.
:param origin: Origin of coordinates, either "viewport", "pointer" or
an Element. If None, remote end defaults to "viewport".
"""
action = {
"type": "pointerMove",
"x": x,
"y": y
}
if duration is not None:
action["duration"] = duration
if origin is not None:
if isinstance(origin, HTMLElement):
action["origin"] = {WEB_ELEMENT_KEY: origin.id}
else:
action["origin"] = origin
self._actions.append(action)
return self
def pointer_up(self, button=MouseButton.LEFT):
"""Queue a pointerUp action for `button`.
:param button: Pointer button to perform action with.
Default: 0, which represents main device button.
"""
self._pointer_action("pointerUp", button)
return self
def pointer_down(self, button=MouseButton.LEFT):
"""Queue a pointerDown action for `button`.
:param button: Pointer button to perform action with.
Default: 0, which represents main device button.
"""
self._pointer_action("pointerDown", button)
return self
def click(self, element=None, button=MouseButton.LEFT):
"""Queue a click with the specified button.
If an element is given, move the pointer to that element first,
otherwise click current pointer coordinates.
:param element: Optional element to click.
:param button: Integer representing pointer button to perform action
with. Default: 0, which represents main device button.
"""
if element:
self.pointer_move(0, 0, origin=element)
return self.pointer_down(button).pointer_up(button)
def key_down(self, value):
"""Queue a keyDown action for `value`.
:param value: Single character to perform key action with.
"""
self._key_action("keyDown", value)
return self
def key_up(self, value):
"""Queue a keyUp action for `value`.
:param value: Single character to perform key action with.
"""
self._key_action("keyUp", value)
return self
def send_keys(self, keys):
"""Queue a keyDown and keyUp action for each character in `keys`.
:param keys: String of keys to perform key actions with.
"""
for c in keys:
self.key_down(c)
self.key_up(c)
return self
class Actions(object):
def __init__(self, marionette):
self.marionette = marionette
def perform(self, actions=None):
"""Perform actions by tick from each action sequence in `actions`.
:param actions: List of input source action sequences. A single action
sequence may be created with the help of
``ActionSequence.dict``.
"""
body = {"actions": [] if actions is None else actions}
return self.marionette._send_message("WebDriver:PerformActions", body)
def release(self):
return self.marionette._send_message("WebDriver:ReleaseActions")
def sequence(self, *args, **kwargs):
"""Return an empty ActionSequence of the designated type.
See ActionSequence for parameter list.
"""
return ActionSequence(self.marionette, *args, **kwargs)
class HTMLElement(object):
"""Represents a DOM Element."""
@ -194,340 +356,6 @@ class HTMLElement(object):
raise ValueError("Unrecognised web element")
class MouseButton(object):
"""Enum-like class for mouse button constants."""
LEFT = 0
MIDDLE = 1
RIGHT = 2
class Actions(object):
'''
An Action object represents a set of actions that are executed in a particular order.
All action methods (press, etc.) return the Actions object itself, to make
it easy to create a chain of events.
Example usage:
::
# get html file
testAction = marionette.absolute_url("testFool.html")
# navigate to the file
marionette.navigate(testAction)
# find element1 and element2
element1 = marionette.find_element(By.ID, "element1")
element2 = marionette.find_element(By.ID, "element2")
# create action object
action = Actions(marionette)
# add actions (press, wait, move, release) into the object
action.press(element1).wait(5). move(element2).release()
# fire all the added events
action.perform()
'''
def __init__(self, marionette):
self.action_chain = []
self.marionette = marionette
self.current_id = None
def press(self, element, x=None, y=None):
'''
Sends a 'touchstart' event to this element.
If no coordinates are given, it will be targeted at the center of the
element. If given, it will be targeted at the (x,y) coordinates
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
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 :func:`press` has already be called on this element.
If press and release are chained without a move action between them,
then it will be processed as a 'tap' event, and will dispatch the
expected mouse events ('mousemove' (if necessary), 'mousedown',
'mouseup', 'mouseclick') after the touch events. If there is a wait
period between press and release that will trigger a contextmenu,
then the 'contextmenu' menu event will be fired instead of the
touch/mouse events.
'''
self.action_chain.append(['release'])
return self
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 :func:`press` has already be called.
'''
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.
May only be called if :func:`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.
'''
self.action_chain.append(['wait', time])
return self
def cancel(self):
'''
Sends 'touchcancel' event to the target of the original 'touchstart' event.
May only be called if :func:`press` has already be called.
'''
self.action_chain.append(['cancel'])
return self
def tap(self, element, x=None, y=None):
'''
Performs a quick tap on the target element.
:param element: The element to tap.
:param x: Optional, x-coordinate of tap, relative to the top-left
corner of the element. If not specified, default to center of
element.
:param y: Optional, y-coordinate of tap, relative to the top-left
corner of the element. If not specified, default to center of
element.
This is equivalent to calling:
::
action.press(element, x, y).release()
'''
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
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):
'''
Performs a click with additional parameters to allow for double clicking,
right click, middle click, etc.
:param element: The element to click.
:param button: The mouse button to click (indexed from 0, left to right).
:param count: Optional, the count of clicks to synthesize (for double
click events).
'''
el = element.id
self.action_chain.append(['click', el, button, count])
return self
def context_click(self, element):
'''
Performs a context click on the specified element.
:param element: The element to context click.
'''
return self.click(element, button=MouseButton.RIGHT)
def middle_click(self, element):
'''
Performs a middle click on the specified element.
:param element: The element to middle click.
'''
return self.click(element, button=MouseButton.MIDDLE)
def double_click(self, element):
'''
Performs a double click on the specified element.
:param element: The element to double click.
'''
return self.click(element, count=2)
def flick(self, element, x1, y1, x2, y2, duration=200):
'''
Performs a flick gesture on the target element.
:param element: The element to perform the flick gesture on.
:param x1: Starting x-coordinate of flick, relative to the top left
corner of the element.
:param y1: Starting y-coordinate of flick, relative to the top left
corner of the element.
:param x2: Ending x-coordinate of flick, relative to the top left
corner of the element.
:param y2: Ending y-coordinate of flick, relative to the top left
corner of the element.
:param duration: Time needed for the flick gesture for complete (in
milliseconds).
'''
element = element.id
elapsed = 0
time_increment = 10
if time_increment >= duration:
time_increment = duration
move_x = time_increment*1.0/duration * (x2 - x1)
move_y = time_increment*1.0/duration * (y2 - y1)
self.action_chain.append(['press', element, x1, y1])
while elapsed < duration:
elapsed += time_increment
self.action_chain.append(['moveByOffset', move_x, move_y])
self.action_chain.append(['wait', time_increment/1000])
self.action_chain.append(['release'])
return self
def long_press(self, element, time_in_seconds, x=None, y=None):
'''
Performs a long press gesture on the target element.
:param element: The element to press.
:param time_in_seconds: Time in seconds to wait before releasing the press.
: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.
This is equivalent to calling:
::
action.press(element, x, y).wait(time_in_seconds).release()
'''
element = element.id
self.action_chain.append(['press', element, x, y])
self.action_chain.append(['wait', time_in_seconds])
self.action_chain.append(['release'])
return self
def key_down(self, key_code):
"""
Perform a "keyDown" action for the given key code. Modifier keys are
respected by the server for the course of an action chain.
:param key_code: The key to press as a result of this action.
"""
self.action_chain.append(['keyDown', key_code])
return self
def key_up(self, key_code):
"""
Perform a "keyUp" action for the given key code. Modifier keys are
respected by the server for the course of an action chain.
:param key_up: The key to release as a result of this action.
"""
self.action_chain.append(['keyUp', key_code])
return self
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}
try:
self.current_id = self.marionette._send_message("Marionette:ActionChain",
body, key="value")
except errors.UnknownCommandException:
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:
::
# create multiaction object
multitouch = MultiActions(marionette)
# create several action objects
action_1 = Actions(marionette)
action_2 = Actions(marionette)
# add actions to each action object/finger
action_1.press(element1).move_to(element2).release()
action_2.press(element3).wait().release(element3)
# fire all the added events
multitouch.add(action_1).add(action_2).perform()
'''
def __init__(self, marionette):
self.multi_actions = []
self.max_length = 0
self.marionette = marionette
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)
return self
def perform(self):
"""Perform all the actions added to this object."""
body = {"value": self.multi_actions, "max_length": self.max_length}
try:
self.marionette._send_message("Marionette:MultiAction", body)
except errors.UnknownCommandException:
self.marionette._send_message("multiAction", body)
class Alert(object):
"""A class for interacting with alerts.
@ -633,6 +461,7 @@ class Marionette(object):
app, host=self.host, port=self.port, bin=self.bin, **instance_args)
self.start_binary(self.startup_timeout)
self.actions = Actions(self)
self.timeout = Timeouts(self)
@property

View File

@ -1,133 +0,0 @@
from __future__ import absolute_import
from marionette_driver.marionette import Actions
from marionette_driver.errors import TimeoutException
from marionette_driver.by import By
def wait_for_condition_else_raise(marionette, wait_for_condition, expected, script):
try:
wait_for_condition(lambda m: expected in m.execute_script(script))
except TimeoutException as e:
raise TimeoutException("{0} got {1} instead of {2}".format(
e.message, marionette.execute_script(script), expected))
def press_release(marionette, times, wait_for_condition, expected):
testAction = marionette.absolute_url("testAction.html")
marionette.navigate(testAction)
action = Actions(marionette)
button = marionette.find_element(By.ID, "button1")
action.press(button).release()
# Insert wait between each press and release chain.
for _ in range(times-1):
action.wait(0.1)
action.press(button).release()
action.perform()
wait_for_condition_else_raise(marionette, wait_for_condition, expected, "return document.getElementById('button1').innerHTML;")
def move_element(marionette, wait_for_condition, expected1, expected2):
testAction = marionette.absolute_url("testAction.html")
marionette.navigate(testAction)
ele = marionette.find_element(By.ID, "button1")
drop = marionette.find_element(By.ID, "button2")
action = Actions(marionette)
action.press(ele).move(drop).release()
action.perform()
wait_for_condition_else_raise(marionette, wait_for_condition, expected1, "return document.getElementById('button1').innerHTML;")
wait_for_condition_else_raise(marionette, wait_for_condition, expected2, "return document.getElementById('button2').innerHTML;")
def move_element_offset(marionette, wait_for_condition, expected1, expected2):
testAction = marionette.absolute_url("testAction.html")
marionette.navigate(testAction)
ele = marionette.find_element(By.ID, "button1")
action = Actions(marionette)
action.press(ele).move_by_offset(0,150).move_by_offset(0, 150).release()
action.perform()
wait_for_condition_else_raise(marionette, wait_for_condition, expected1, "return document.getElementById('button1').innerHTML;")
wait_for_condition_else_raise(marionette, wait_for_condition, expected2, "return document.getElementById('button2').innerHTML;")
def chain(marionette, wait_for_condition, expected1, expected2):
testAction = marionette.absolute_url("testAction.html")
marionette.navigate(testAction)
marionette.timeout.implicit = 15
action = Actions(marionette)
button1 = marionette.find_element(By.ID, "button1")
action.press(button1).perform()
button2 = marionette.find_element(By.ID, "delayed")
wait_for_condition_else_raise(marionette, wait_for_condition, expected1, "return document.getElementById('button1').innerHTML;")
action.move(button2).release().perform()
wait_for_condition_else_raise(marionette, wait_for_condition, expected2, "return document.getElementById('delayed').innerHTML;")
def chain_flick(marionette, wait_for_condition, expected1, expected2):
testAction = marionette.absolute_url("testAction.html")
marionette.navigate(testAction)
button = marionette.find_element(By.ID, "button1")
action = Actions(marionette)
action.flick(button, 0, 0, 0, 200).perform()
wait_for_condition_else_raise(marionette, wait_for_condition, expected1,"return document.getElementById('button1').innerHTML;")
wait_for_condition_else_raise(marionette, wait_for_condition, expected2,"return document.getElementById('buttonFlick').innerHTML;")
def wait(marionette, wait_for_condition, expected):
testAction = marionette.absolute_url("testAction.html")
marionette.navigate(testAction)
action = Actions(marionette)
button = marionette.find_element(By.ID, "button1")
action.press(button).wait().release().perform()
wait_for_condition_else_raise(marionette, wait_for_condition, expected, "return document.getElementById('button1').innerHTML;")
def wait_with_value(marionette, wait_for_condition, expected):
testAction = marionette.absolute_url("testAction.html")
marionette.navigate(testAction)
button = marionette.find_element(By.ID, "button1")
action = Actions(marionette)
action.press(button).wait(0.01).release()
action.perform()
wait_for_condition_else_raise(marionette, wait_for_condition, expected, "return document.getElementById('button1').innerHTML;")
def context_menu(marionette, wait_for_condition, expected1, expected2):
testAction = marionette.absolute_url("testAction.html")
marionette.navigate(testAction)
button = marionette.find_element(By.ID, "button1")
action = Actions(marionette)
action.press(button).wait(5).perform()
wait_for_condition_else_raise(marionette, wait_for_condition, expected1, "return document.getElementById('button1').innerHTML;")
action.release().perform()
wait_for_condition_else_raise(marionette, wait_for_condition, expected2, "return document.getElementById('button1').innerHTML;")
def long_press_action(marionette, wait_for_condition, expected):
testAction = marionette.absolute_url("testAction.html")
marionette.navigate(testAction)
button = marionette.find_element(By.ID, "button1")
action = Actions(marionette)
action.long_press(button, 5).perform()
wait_for_condition_else_raise(marionette, wait_for_condition, expected, "return document.getElementById('button1').innerHTML;")
def long_press_on_xy_action(marionette, wait_for_condition, expected):
testAction = marionette.absolute_url("testAction.html")
marionette.navigate(testAction)
html = marionette.find_element(By.TAG_NAME, "html")
button = marionette.find_element(By.ID, "button1")
action = Actions(marionette)
# Press the center of the button with respect to html.
x = button.rect['x'] + button.rect['width'] / 2.0
y = button.rect['y'] + button.rect['height'] / 2.0
action.long_press(html, 5, x, y).perform()
wait_for_condition_else_raise(marionette, wait_for_condition, expected, "return document.getElementById('button1').innerHTML;")
def single_tap(marionette, wait_for_condition, expected):
testAction = marionette.absolute_url("testAction.html")
marionette.navigate(testAction)
button = marionette.find_element(By.ID, "button1")
action = Actions(marionette)
action.tap(button).perform()
wait_for_condition_else_raise(marionette, wait_for_condition, expected, "return document.getElementById('button1').innerHTML;")
def double_tap(marionette, wait_for_condition, expected):
testAction = marionette.absolute_url("testAction.html")
marionette.navigate(testAction)
button = marionette.find_element(By.ID, "button1")
action = Actions(marionette)
action.double_tap(button).perform()
wait_for_condition_else_raise(marionette, wait_for_condition, expected, "return document.getElementById('button1').innerHTML;")

View File

@ -8,8 +8,6 @@ import urllib
from marionette_driver.by import By
from marionette_driver.keys import Keys
from marionette_driver.marionette import Actions
from marionette_harness import MarionetteTestCase, WindowManagerMixin
@ -21,59 +19,66 @@ class TestKeyActions(WindowManagerMixin, MarionetteTestCase):
def setUp(self):
super(TestKeyActions, self).setUp()
self.key_chain = self.marionette.actions.sequence("key", "keyboard_id")
if self.marionette.session_capabilities["platformName"] == "mac":
self.mod_key = Keys.META
else:
self.mod_key = Keys.CONTROL
test_html = self.marionette.absolute_url("keyboard.html")
self.marionette.navigate(test_html)
self.reporter_element = self.marionette.find_element(By.ID, "keyReporter")
self.reporter_element.click()
self.key_action = Actions(self.marionette)
def tearDown(self):
self.marionette.actions.release()
super(TestKeyActions, self).tearDown()
@property
def key_reporter_value(self):
return self.reporter_element.get_property("value")
def test_key_action_basic_input(self):
self.key_action.key_down("a").key_down("b").key_down("c").perform()
def test_basic_input(self):
self.key_chain.key_down("a").key_down("b").key_down("c").perform()
self.assertEqual(self.key_reporter_value, "abc")
def test_upcase_input(self):
(self.key_action.key_down(Keys.SHIFT)
.key_down("a")
.key_up(Keys.SHIFT)
.key_down("b")
.key_down("c")
.perform())
self.key_chain.key_down(Keys.SHIFT) \
.key_down("a") \
.key_up(Keys.SHIFT) \
.key_down("b") \
.key_down("c") \
.perform()
self.assertEqual(self.key_reporter_value, "Abc")
def test_replace_input(self):
self.key_action.key_down("a").key_down("b").key_down("c").perform()
self.key_chain.key_down("a").key_down("b").key_down("c").perform()
self.assertEqual(self.key_reporter_value, "abc")
(self.key_action.key_down(self.mod_key)
.key_down("a")
.key_up(self.mod_key)
.key_down("x")
.perform())
self.key_chain.key_down(self.mod_key) \
.key_down("a") \
.key_up(self.mod_key) \
.key_down("x") \
.perform()
self.assertEqual(self.key_reporter_value, "x")
def test_clear_input(self):
self.key_action.key_down("a").key_down("b").key_down("c").perform()
self.assertEqual(self.key_reporter_value, "abc")
(self.key_action.key_down(self.mod_key)
.key_down("a")
.key_down("x")
.perform())
self.assertEqual(self.key_reporter_value, "")
self.key_action.key_down("a").key_down("b").key_down("c").perform()
self.key_chain.key_down("a").key_down("b").key_down("c").perform()
self.assertEqual(self.key_reporter_value, "abc")
def test_input_with_wait(self):
self.key_action.key_down("a").key_down("b").key_down("c").perform()
(self.key_action.key_down(self.mod_key)
.key_down("a")
.wait(.5)
.key_down("x")
.perform())
self.key_chain.key_down(self.mod_key) \
.key_down("a") \
.key_down("x") \
.perform()
self.assertEqual(self.key_reporter_value, "")
def test_input_with_wait(self):
self.key_chain.key_down("a").key_down("b").key_down("c").perform()
self.key_chain.key_down(self.mod_key) \
.key_down("a") \
.pause(250) \
.key_down("x") \
.perform()
self.assertEqual(self.key_reporter_value, "")

View File

@ -1,139 +0,0 @@
# 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 __future__ import absolute_import
import urllib
from marionette_driver.by import By
from marionette_driver.keys import Keys
from marionette_driver.marionette import Actions
from marionette_harness import MarionetteTestCase
def inline(doc):
return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
class BaseLegacyMouseAction(MarionetteTestCase):
def setUp(self):
super(BaseLegacyMouseAction, self).setUp()
if self.marionette.session_capabilities["platformName"] == "mac":
self.mod_key = Keys.META
else:
self.mod_key = Keys.CONTROL
self.action = Actions(self.marionette)
class TestLegacyMouseAction(BaseLegacyMouseAction):
def test_click_action(self):
test_html = self.marionette.absolute_url("test.html")
self.marionette.navigate(test_html)
link = self.marionette.find_element(By.ID, "mozLink")
self.action.click(link).perform()
self.assertEqual("Clicked", self.marionette.execute_script(
"return document.getElementById('mozLink').innerHTML"))
def test_clicking_element_out_of_view_succeeds(self):
# The action based click doesn"t check for visibility.
self.marionette.navigate(inline("""
<div style="position:relative;top:200vh;">foo</div>
"""))
el = self.marionette.find_element(By.TAG_NAME, "div")
self.action.click(el).perform()
def test_double_click_action(self):
self.marionette.navigate(inline("""
<div contenteditable>zyxw</div><input type="text"/>
"""))
el = self.marionette.find_element(By.CSS_SELECTOR, "div")
self.action.double_click(el).perform()
el.send_keys(self.mod_key + "c")
rel = self.marionette.find_element(By.CSS_SELECTOR, "input")
rel.send_keys(self.mod_key + "v")
self.assertEqual("zyxw", rel.get_property("value"))
def test_context_click_action(self):
test_html = self.marionette.absolute_url("clicks.html")
self.marionette.navigate(test_html)
click_el = self.marionette.find_element(By.ID, "normal")
def context_menu_state():
with self.marionette.using_context("chrome"):
cm_el = self.marionette.find_element(By.ID, "contentAreaContextMenu")
return cm_el.get_property("state")
self.assertEqual("closed", context_menu_state())
self.action.context_click(click_el).perform()
self.wait_for_condition(lambda _: context_menu_state() == "open")
with self.marionette.using_context("chrome"):
self.marionette.find_element(By.ID, "main-window").send_keys(Keys.ESCAPE)
self.wait_for_condition(lambda _: context_menu_state() == "closed")
def test_middle_click_action(self):
test_html = self.marionette.absolute_url("clicks.html")
self.marionette.navigate(test_html)
self.marionette.find_element(By.ID, "addbuttonlistener").click()
el = self.marionette.find_element(By.ID, "showbutton")
self.action.middle_click(el).perform()
self.wait_for_condition(lambda _: el.get_property("innerHTML") == "1")
def test_chrome_click(self):
self.marionette.navigate("about:blank")
data_uri = "data:text/html,<html></html>"
with self.marionette.using_context("chrome"):
urlbar = self.marionette.find_element(By.ID, "urlbar")
urlbar.send_keys(data_uri)
go_button = self.marionette.execute_script("return gURLBar.goButton")
self.action.click(go_button).perform()
self.wait_for_condition(lambda mn: mn.get_url() == data_uri)
class TestChromeLegacyMouseAction(BaseLegacyMouseAction):
def setUp(self):
super(TestChromeLegacyMouseAction, self).setUp()
self.marionette.set_context("chrome")
def test_chrome_double_click(self):
test_word = "quux"
with self.marionette.using_context("content"):
self.marionette.navigate("about:blank")
urlbar = self.marionette.find_element(By.ID, "urlbar")
self.assertEqual("", urlbar.get_property("value"))
urlbar.send_keys(test_word)
self.assertEqual(urlbar.get_property("value"), test_word)
(self.action.double_click(urlbar).perform()
.key_down(self.mod_key)
.key_down("x").perform())
self.assertEqual(urlbar.get_property("value"), "")
def test_chrome_context_click_action(self):
def context_menu_state():
cm_el = self.marionette.find_element(By.ID, "tabContextMenu")
return cm_el.get_property("state")
currtab = self.marionette.execute_script("return gBrowser.selectedTab")
self.assertEqual("closed", context_menu_state())
self.action.context_click(currtab).perform()
self.wait_for_condition(lambda _: context_menu_state() == "open")
(self.marionette.find_element(By.ID, "main-window")
.send_keys(Keys.ESCAPE))
self.wait_for_condition(lambda _: context_menu_state() == "closed")

View File

@ -8,71 +8,29 @@ import urllib
from marionette_driver import By, errors, Wait
from marionette_driver.keys import Keys
from marionette_driver.marionette import WEB_ELEMENT_KEY
from marionette_harness import MarionetteTestCase
from marionette_harness import MarionetteTestCase, skip_if_mobile
def inline(doc):
return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
class Actions(object):
"""Temporary class until Marionette client supports the WebDriver actions."""
def __init__(self, marionette):
self.marionette = marionette
self.action_chain = []
def perform(self):
params = {"actions": [{
"actions": self.action_chain,
"id": "mouse",
"parameters": {
"pointerType": "mouse"
},
"type": "pointer"
}]}
return self.marionette._send_message("WebDriver:PerformActions", params=params)
def move(self, element, x=0, y=0, duration=250):
self.action_chain.append({
"duration": duration,
"origin": {WEB_ELEMENT_KEY: element.id},
"type": "pointerMove",
"x": x,
"y": y,
})
return self
def click(self):
self.action_chain.extend([{
"button": 0,
"type": "pointerDown"
}, {
"button": 0,
"type": "pointerUp"
}])
return self
class BaseMouseAction(MarionetteTestCase):
def setUp(self):
super(BaseMouseAction, self).setUp()
self.mouse_chain = self.marionette.actions.sequence(
"pointer", "pointer_id", {"pointerType": "mouse"})
if self.marionette.session_capabilities["platformName"] == "mac":
self.mod_key = Keys.META
else:
self.mod_key = Keys.CONTROL
self.action = Actions(self.marionette)
def tearDown(self):
self.marionette.actions.release()
super(BaseMouseAction, self).tearDown()
@property
@ -90,6 +48,67 @@ class BaseMouseAction(MarionetteTestCase):
}
class TestPointerActions(BaseMouseAction):
def test_click_action(self):
test_html = self.marionette.absolute_url("test.html")
self.marionette.navigate(test_html)
link = self.marionette.find_element(By.ID, "mozLink")
self.mouse_chain.click(element=link).perform()
self.assertEqual("Clicked", self.marionette.execute_script(
"return document.getElementById('mozLink').innerHTML"))
def test_clicking_element_out_of_view(self):
self.marionette.navigate(inline("""
<div style="position:relative;top:200vh;">foo</div>
"""))
el = self.marionette.find_element(By.TAG_NAME, "div")
with self.assertRaises(errors.MoveTargetOutOfBoundsException):
self.mouse_chain.click(element=el).perform()
def test_double_click_action(self):
self.marionette.navigate(inline("""
<script>window.eventCount = 0;</script>
<button onclick="window.eventCount++">foobar</button>
"""))
el = self.marionette.find_element(By.CSS_SELECTOR, "button")
self.mouse_chain.click(el).pause(100).click(el).perform()
event_count = self.marionette.execute_script("return window.eventCount", sandbox=None)
self.assertEqual(event_count, 2)
@skip_if_mobile("There is no context menu available on mobile")
def test_context_click_action(self):
test_html = self.marionette.absolute_url("clicks.html")
self.marionette.navigate(test_html)
click_el = self.marionette.find_element(By.ID, "normal")
def context_menu_state():
with self.marionette.using_context("chrome"):
cm_el = self.marionette.find_element(By.ID, "contentAreaContextMenu")
return cm_el.get_property("state")
self.assertEqual("closed", context_menu_state())
self.mouse_chain.click(element=click_el, button=2).perform()
self.wait_for_condition(lambda _: context_menu_state() == "open")
with self.marionette.using_context("chrome"):
self.marionette.find_element(By.ID, "main-window").send_keys(Keys.ESCAPE)
self.wait_for_condition(lambda _: context_menu_state() == "closed")
def test_middle_click_action(self):
test_html = self.marionette.absolute_url("clicks.html")
self.marionette.navigate(test_html)
self.marionette.find_element(By.ID, "addbuttonlistener").click()
el = self.marionette.find_element(By.ID, "showbutton")
self.mouse_chain.click(element=el, button=1).perform()
self.wait_for_condition(lambda _: el.get_property("innerHTML") == "1")
class TestNonSpecCompliantPointerOrigin(BaseMouseAction):
def setUp(self):
@ -112,7 +131,7 @@ class TestNonSpecCompliantPointerOrigin(BaseMouseAction):
elem = self.marionette.find_element(By.ID, "div")
elem_center_point = self.get_element_center_point(elem)
self.action.move(elem).click().perform()
self.mouse_chain.click(element=elem).perform()
click_position = Wait(self.marionette).until(lambda _: self.click_position,
message="No click event has been detected")
self.assertAlmostEqual(click_position["x"], elem_center_point["x"], delta=1)
@ -126,12 +145,13 @@ class TestNonSpecCompliantPointerOrigin(BaseMouseAction):
elem = self.marionette.find_element(By.ID, "div")
elem_center_point = self.get_element_center_point(elem)
self.action.move(elem).click().perform()
self.mouse_chain.click(element=elem).perform()
click_position = Wait(self.marionette).until(lambda _: self.click_position,
message="No click event has been detected")
self.assertAlmostEqual(click_position["x"], elem_center_point["x"], delta=1)
self.assertAlmostEqual(click_position["y"], elem_center_point["y"], delta=1)
@skip_if_mobile("Bug 1534291 - Missing MoveTargetOutOfBoundsException")
def test_click_element_larger_than_viewport_with_center_point_outside(self):
self.marionette.navigate(inline("""
<div id="div" style="width: 300vw; height: 300vh; background: green;"
@ -140,4 +160,4 @@ class TestNonSpecCompliantPointerOrigin(BaseMouseAction):
elem = self.marionette.find_element(By.ID, "div")
with self.assertRaises(errors.MoveTargetOutOfBoundsException):
self.action.move(elem).click().perform()
self.mouse_chain.click(element=elem).perform()

View File

@ -1,125 +0,0 @@
# 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 __future__ import absolute_import
import os
import sys
from marionette_driver.errors import MarionetteException
from marionette_driver import Actions, By
from marionette_harness import MarionetteTestCase, skip
# add this directory to the path
sys.path.append(os.path.dirname(__file__))
from single_finger_functions import (
chain, chain_flick, context_menu, double_tap,
long_press_action, long_press_on_xy_action,
move_element, move_element_offset, press_release, single_tap, wait,
wait_with_value
)
class testSingleFingerMouse(MarionetteTestCase):
def setUp(self):
super(MarionetteTestCase, self).setUp()
# set context menu related preferences needed for some tests
self.marionette.set_context("chrome")
self.enabled = self.marionette.execute_script("""
let prefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
let value = false;
try {
value = prefs.getBoolPref("ui.click_hold_context_menus");
}
catch (e) {}
prefs.setBoolPref("ui.click_hold_context_menus", true);
return value;
""")
self.wait_time = self.marionette.execute_script("""
let prefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
let value = 750;
try {
value = prefs.getIntPref("ui.click_hold_context_menus.delay");
}
catch (e) {}
prefs.setIntPref("ui.click_hold_context_menus.delay", value);
return value;
""")
self.marionette.set_context("content")
def tearDown(self):
self.marionette.set_context("chrome")
self.marionette.execute_script(
"""
let prefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
prefs.setBoolPref("ui.click_hold_context_menus", arguments[0]);
""", [self.enabled])
self.marionette.execute_script(
"""
let prefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
prefs.setIntPref("ui.click_hold_context_menus.delay", arguments[0]);
""", [self.wait_time])
self.marionette.set_context("content")
super(MarionetteTestCase, self).tearDown()
def test_press_release(self):
press_release(self.marionette, 1, self.wait_for_condition, "button1-mousemove-mousedown-mouseup-click")
def test_press_release_twice(self):
press_release(self.marionette, 2, self.wait_for_condition, "button1-mousemove-mousedown-mouseup-click-mousemove-mousedown-mouseup-click")
def test_move_element(self):
move_element(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown", "button2-mousemove-mouseup")
def test_move_by_offset(self):
move_element_offset(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown", "button2-mousemove-mouseup")
def test_wait(self):
wait(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown-mouseup-click")
def test_wait_with_value(self):
wait_with_value(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown-mouseup-click")
@skip("Bug 1191066")
def test_context_menu(self):
context_menu(self.marionette, self.wait_for_condition,
"button1-mousemove-mousedown-contextmenu",
"button1-mousemove-mousedown-contextmenu-mouseup-click")
@skip("Bug 1191066")
def test_long_press_action(self):
long_press_action(self.marionette, self.wait_for_condition,
"button1-mousemove-mousedown-contextmenu-mouseup-click")
@skip("Bug 1191066")
def test_long_press_on_xy_action(self):
long_press_on_xy_action(self.marionette, self.wait_for_condition,
"button1-mousemove-mousedown-contextmenu-mouseup-click")
@skip("Bug 865334")
def test_long_press_fail(self):
testAction = self.marionette.absolute_url("testAction.html")
self.marionette.navigate(testAction)
button = self.marionette.find_element(By.ID, "button1Copy")
action = Actions(self.marionette)
action.press(button).long_press(button, 5)
self.assertRaises(MarionetteException, action.perform)
def test_chain(self):
chain(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown", "delayed-mousemove-mouseup")
def test_chain_flick(self):
chain_flick(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown-mousemove", "buttonFlick-mousemove-mouseup")
def test_single_tap(self):
single_tap(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown-mouseup-click")
def test_double_tap(self):
double_tap(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown-mouseup-click-mousemove-mousedown-mouseup-click")

View File

@ -40,9 +40,6 @@ skip-if = appname == 'fennec'
[test_navigation.py]
[test_timeouts.py]
[test_single_finger_desktop.py]
skip-if = appname == 'fennec' || os == "win" # Bug 1025040
[test_anonymous_content.py]
skip-if = appname == 'fennec'
[test_switch_frame.py]
@ -100,10 +97,8 @@ skip-if = appname == 'fennec' # Bug 1325738
skip-if = appname == 'fennec' # Bug 1325738
[test_key_actions.py]
[test_legacy_mouse_action.py]
skip-if = appname == 'fennec'
[test_mouse_action.py]
skip-if = appname == 'fennec'
[test_teardown_context_preserved.py]
[test_file_upload.py]
skip-if = appname == 'fennec' || os == "win" # http://bugs.python.org/issue14574