Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Actions in executorselenium #10312

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/_writing-tests/testdriver.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,20 @@ Note that if the element that's keys need to be send to does not have
a unique ID, the document must not have any DOM mutations made
between the function being called and the promise settling.

### `testdriver.action_chain(chain)`
- `chain` <[Array]<[Object]>> An array of actions to chain
- `action_object` <[Object]> A single action
- `type` <[string]> the type of action, one of 'click', 'click_and_hold', 'context_click', 'double_click', 'drag_and_drop', 'drag_and_drop_by_offset', 'key_down', 'key_up', 'move_by_offset',
'move_to_element', 'move_to_element_with_offset', 'pause', 'perform', 'release', 'reset_actions', 'send_keys', 'send_keys_to_element'
- `args` <[Object]> a list of arguments to pass for the current action
- `type` <[string]> the type of arg
- `arg` <[Object]> the argument to the action

This function causes a sequence of actions to occur which can be any of 'click', 'click_and_hold', 'context_click', 'double_click', 'drag_and_drop', 'drag_and_drop_by_offset', 'key_down', 'key_up', 'move_by_offset', 'move_to_element', 'move_to_element_with_offset', 'pause', 'perform', 'release', 'reset_actions', 'send_keys', 'send_keys_to_element'.

Note that if any of the elements passed in as arguments do not have a unique ID, the
document must not have any DOM mutations made between the function
being called and the promise settling.


[testharness]: {{ site.baseurl }}{% link _writing-tests/testharness.md %}
22 changes: 22 additions & 0 deletions infrastructure/testdriver/actions/pointermove.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>TestDriver actions method</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>

<input id="checkbox" type="checkbox">

<script>
async_test(function(t) {
var target = document.getElementById("checkbox");

target.addEventListener("pointermove", t.step_func_done(function() {
t.done();
}));

test_driver.actions([{"type": "move_to_element", "args": [{"type": "element", "arg": target}]}, {"type": "perform"}])
.catch(t.unreached_func("actions failed"));
});
</script>
8 changes: 8 additions & 0 deletions resources/testdriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@
y: centerPoint[1]});
},

actions: function(chain) {
return window.test_driver_internal.actions(chain);
},

/**
* Send keys to an element
*
Expand Down Expand Up @@ -133,6 +137,10 @@
click: function(element, coords) {
return Promise.reject(new Error("unimplemented"));
},

actions: function(chain) {
return Promise.reject(new Error("unimplemented"));
},

/**
* Triggers a user-initated click
Expand Down
212 changes: 211 additions & 1 deletion tools/wptrunner/wptrunner/executors/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,8 @@ def __init__(self, logger, protocol, test_window):

self.actions = {
"click": ClickAction(self.logger, self.protocol),
"send_keys": SendKeysAction(self.logger, self.protocol)
"send_keys": SendKeysAction(self.logger, self.protocol),
"actions": ActionsAction(self.logger, self.protocol)
}

def __call__(self, result):
Expand Down Expand Up @@ -575,3 +576,212 @@ def __call__(self, payload):
raise ValueError("Selector matches multiple elements")
self.logger.debug("Sending keys to element: %s" % selector)
self.protocol.send_keys.send_keys(elements[0], keys)

class ActionsAction(object):
def __init__(self, logger, protocol):
self.logger = logger
self.protocol = protocol

def __call__(self, payload):
actions = payload["actions_list"]
args = payload["args_list"]

# create the initial ActionChains object
action_chain = self.protocol.actions.actions()
for sub_action, sub_args in zip(actions, args):
if sub_action == "click":
if len(sub_args) == 0:
action_chain.click()
elif len(sub_args) == 1:
element = self.protocol.select.elements_by_selector(sub_args[0])[0]
action_chain.click(element)
else:
raise ValueError("Adding action failed: {}, not the right number of arguments!".format("click"))

self.logger.debug("Added action to the current chain: {}, with args: {}".format("click", str(sub_args)))

elif sub_action == "click_and_hold":
if len(sub_args) == 0:
action_chain.click_and_hold()
elif len(sub_args) == 1:
element = self.elements_by_selector(sub_args[0])[0]
action_chain.click_and_hold(element)
else:
self._send_message("complete",
"failure")
raise ValueError("Adding action failed: {}, not the right number of arguments!".format("click_and_hold"))

self.logger.debug("Added action to the current chain: {}, with args: {}".format("click_and_hold", str(sub_args)))

elif sub_action == "context_click":
if len(sub_args) == 0:
action_chain.context_click()
elif len(sub_args) == 1:
element = self.elements_by_selector(sub_args[0])[0]
action_chain.context_click(element)
else:
self._send_message("complete",
"failure")
raise ValueError("Adding action failed: {}, not the right number of arguments!".format("context_click"))

self.logger.debug("Added action to the current chain: {}, with args: {}".format("context_click", str(sub_args)))

elif sub_action == "double_click":
if len(sub_args) == 0:
action_chain.double_click()
elif len(sub_args) == 1:
element = self.elements_by_selector(sub_args[0])[0]
action_chain.double_click(element)
else:
self._send_message("complete",
"failure")
raise ValueError("Adding action failed: {}, not the right number of arguments!".format("double_click"))

self.logger.debug("Added action to the current chain: {}, with args: {}".format("double_click", str(sub_args)))

elif sub_action == "drag_and_drop":
if len(sub_args) != 2:
self._send_message("complete",
"failure")
raise ValueError("Adding action failed: {}, not the right number of arguments!".format("drag_and_drop"))

source_element = self.protocol.select.elements_by_selector(sub_args[0])[0]
target_element = self.protocol.select.elements_by_selector(sub_args[1])[0]
action_chain.drag_and_drop(source_element, target_element)
self.logger.debug("Added action to the current chain: {}, with args: {}".format("drag_and_drop", str(sub_args)))

elif sub_action == "drag_and_drop_by_offset":
if len(sub_args) != 3:
self._send_message("complete",
"failure")
raise ValueError("Adding action failed: {}, not the right number of arguments!".format("drag_and_drop_by_offset"))

css, x_offset, y_offset = sub_args
element = self.protocol.select.elements_by_selector(css)
action_chain.drag_and_drop_by_offset(element, x_offset, y_offset)
self.logger.debug("Added action to the current chain: {}, with args: {}".format("drag_and_drop", str(sub_args)))

elif sub_action == "key_down":
if len(sub_args) == 1:
value = sub_args[0]
action_chain.key_down(value)
elif len(sub_args) == 2:
value, css = sub_args
element = self.protocol.select.elements_by_selector(css)
action_chain.key_down(value, element)
else:
self._send_message("complete",
"failure")
raise ValueError("Adding action failed: {}, not the right number of arguments!".format("key_down"))

self.logger.debug("Added action to the current chain: {}, with args: {}".format("key_down", str(sub_args)))

elif sub_action == "key_up":
if len(sub_args) == 1:
value = sub_args[0]
action_chain.key_up(value)
elif len(sub_args) == 2:
value, css = sub_args
element = self.protocol.select.elements_by_selector(css)
action_chain.key_up(value, element)
else:
self._send_message("complete",
"failure")
raise ValueError("Adding action failed: {}, not the right number of arguments!".format("key_up"))

self.logger.debug("Added action to the current chain: {}, with args: {}".format("key_up", str(sub_args)))

elif sub_action == "move_by_offset":
if len(sub_args) != 2:
self._send_message("complete",
"failure")
raise ValueError("Adding action failed: {}, not the right number of arguments!".format("move_by_offset"))

x_offset, y_offset = sub_args
action_chain.move_by_offset(x_offset, y_offset)
self.logger.debug("Added action to the current chain: {}, with args: {}".format("move_by_offset", str(sub_args)))

elif sub_action == "move_to_element":
if len(sub_args) != 1:
raise ValueError("Adding action failed: {}, not the right number of arguments!".format("move_to_element"))

element = self.protocol.select.elements_by_selector(sub_args[0])[0]
action_chain.move_to_element(element)
self.logger.debug("Added action to the current chain: {}, with args: {}".format("move_by_offset", str(sub_args)))

elif sub_action == "move_to_element_with_offset":
if len(sub_args) != 3:
self._send_message("complete",
"failure")
raise ValueError("Adding action failed: {}, not the right number of arguments!".format("drag_and_drop_by_offset"))

css, x_offset, y_offset = sub_args
element = self.protocol.select.elements_by_selector(css)[0]
action_chain.move_to_element_with_offset(element, x_offset, y_offset)
self.logger.debug("Added action to the current chain: {}, with args: {}".format("move_to_element_with_offset", str(sub_args)))

elif sub_action == "pause":
if len(sub_args) != 1:
self._send_message("complete",
"failure")
raise ValueError("Adding action failed: {}, not the right number of arguments!".format("pause"))

seconds = sub_args[0]
action_chain.pause(seconds)
self.logger.debug("Added action to the current chain: {}, with args: {}".format("pause", str(sub_args)))

elif sub_action == "perform":
if len(sub_args) != 0:
self._send_message("complete",
"failure")
raise ValueError("Adding action failed: {}, not the right number of arguments!".format("perform"))

action_chain.perform()
self.logger.debug("Performed the current action chain")

elif sub_action == "release":
if len(sub_args) == 0:
action_chain.release()
elif len(sub_args) == 1:
element = protocol.select.elements_by_selector(sub_args[0])[0]
action_chain.release(element)
else:
self._send_message("complete",
"failure")
raise ValueError("Adding action failed: {}, not the right number of arguments!".format("release"))

self.logger.debug("Added action to the current chain: {}, with args: {}".format("pause", str(sub_args)))

elif sub_action == "reset_actions":
if len(sub_args) != 0:
self._send_message("complete",
"failure")
raise ValueError("Adding action failed: {}, not the right number of arguments!".format("reset_actions"))

action_chain.reset_actions()
self.logger.debug("Reset the current action chain")

elif sub_action == "send_keys":
if len(sub_args) != 1:
self._send_message("complete",
"failure")
raise ValueError("Adding action failed: {}, not the right number of arguments!".format("send_keys"))

text = sub_args[0]
action_chain.send_keys(text)
self.logger.debug("Added action to the current chain: {}, with args: {}".format("send_keys", str(sub_args)))

elif sub_action == "send_keys_to_element":
if len(sub_args) != 2:
self._send_message("complete",
"failure")
raise ValueError("Adding action failed: {}, not the right number of arguments!".format("perform"))

css, text = sub_args
element = self.protocol.select.elements_by_selector(css)
action_chain.send_keys_to_element(element, text)
self.logger.debug("Added action to the current chain: {}, with args: {}".format("send_keys_to_element", str(sub_args)))

else:
raise ValueError("Unknown action: {}".format(sub_action))

2 changes: 1 addition & 1 deletion tools/wptrunner/wptrunner/executors/executormarionette.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
from ..testrunner import Stop
from ..webdriver_server import GeckoDriverServer


def do_delayed_imports():
global errors, marionette

Expand All @@ -46,6 +45,7 @@ def do_delayed_imports():
try:
import marionette
from marionette import errors

except ImportError:
from marionette_driver import marionette, errors

Expand Down
11 changes: 10 additions & 1 deletion tools/wptrunner/wptrunner/executors/executorselenium.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
Protocol,
SelectorProtocolPart,
ClickProtocolPart,
ActionsProtocolPart,
SendKeysProtocolPart,
TestDriverProtocolPart)
from ..testrunner import Stop
from selenium.webdriver.common.action_chains import ActionChains

here = os.path.join(os.path.split(__file__)[0])

Expand All @@ -38,7 +40,6 @@ def do_delayed_imports():
from selenium.common import exceptions
from selenium.webdriver.remote.remote_connection import RemoteConnection


class SeleniumBaseProtocolPart(BaseProtocolPart):
def setup(self):
self.webdriver = self.parent.webdriver
Expand Down Expand Up @@ -135,6 +136,13 @@ def setup(self):
def element(self, element):
return element.click()

class SeleniumActionsProtocolPart(ActionsProtocolPart):
def setup(self):
self.webdriver = self.parent.webdriver

def actions(self):
return ActionChains(self.webdriver)

class SeleniumSendKeysProtocolPart(SendKeysProtocolPart):
def setup(self):
self.webdriver = self.parent.webdriver
Expand Down Expand Up @@ -162,6 +170,7 @@ class SeleniumProtocol(Protocol):
SeleniumTestharnessProtocolPart,
SeleniumSelectorProtocolPart,
SeleniumClickProtocolPart,
SeleniumActionsProtocolPart,
SeleniumSendKeysProtocolPart,
SeleniumTestDriverProtocolPart]

Expand Down
11 changes: 11 additions & 0 deletions tools/wptrunner/wptrunner/executors/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,17 @@ def element(self, element):
:param element: A protocol-specific handle to an element."""
pass

class ActionsProtocolPart(ProtocolPart):
"""Protocol part for performing trusted clicks"""
__metaclass__ = ABCMeta

name = "actions"

@abstractmethod
def actions(self):
pass


class SendKeysProtocolPart(ProtocolPart):
"""Protocol part for performing trusted clicks"""
__metaclass__ = ABCMeta
Expand Down
Loading