Skip to content
Merged
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
26 changes: 13 additions & 13 deletions tools/wptrunner/wptrunner/executors/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ def __init__(self, logger, protocol):
self.protocol = protocol

def __call__(self, payload):
selector = payload["selector"]
element = self.protocol.select.element_by_selector(selector)
self.logger.debug("Clicking element: %s" % selector)
selectors = payload["selectors"]
element = self.protocol.select.element_by_selector_array(selectors)
self.logger.debug("Clicking element: %s" % selectors)
self.protocol.click.element(element)


Expand Down Expand Up @@ -46,8 +46,8 @@ def __init__(self, logger, protocol):
self.protocol = protocol

def __call__(self, payload):
selector = payload["selector"]
element = self.protocol.select.element_by_selector(selector)
selectors = payload["selectors"]
element = self.protocol.select.element_by_selector_array(selectors)
self.logger.debug("Getting computed label for element: %s" % element)
return self.protocol.accessibility.get_computed_label(element)

Expand All @@ -60,8 +60,8 @@ def __init__(self, logger, protocol):
self.protocol = protocol

def __call__(self, payload):
selector = payload["selector"]
element = self.protocol.select.element_by_selector(selector)
selectors = payload["selectors"]
element = self.protocol.select.element_by_selector_array(selectors)
self.logger.debug("Getting computed role for element: %s" % element)
return self.protocol.accessibility.get_computed_role(element)

Expand All @@ -87,10 +87,10 @@ def __init__(self, logger, protocol):
self.protocol = protocol

def __call__(self, payload):
selector = payload["selector"]
selectors = payload["selectors"]
keys = payload["keys"]
element = self.protocol.select.element_by_selector(selector)
self.logger.debug("Sending keys to element: %s" % selector)
element = self.protocol.select.element_by_selector_array(selectors)
self.logger.debug("Sending keys to element: %s" % selectors)
self.protocol.send_keys.send_keys(element, keys)


Expand Down Expand Up @@ -145,11 +145,11 @@ def __call__(self, payload):
for action in actionSequence["actions"]:
if (action["type"] == "pointerMove" and
isinstance(action["origin"], dict)):
action["origin"] = self.get_element(action["origin"]["selector"])
action["origin"] = self.get_element(action["origin"]["selectors"])
self.protocol.action_sequence.send_actions({"actions": actions})

def get_element(self, element_selector):
return self.protocol.select.element_by_selector(element_selector)
def get_element(self, element_selectors):
return self.protocol.select.element_by_selector_array(element_selectors)

def reset(self):
self.protocol.action_sequence.release()
Expand Down
20 changes: 20 additions & 0 deletions tools/wptrunner/wptrunner/executors/executormarionette.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,26 @@ class MarionetteSelectorProtocolPart(SelectorProtocolPart):
def setup(self):
self.marionette = self.parent.marionette

def elements_by_selector_array(self, selectors):
shadow_roots = []
selectors = selectors.copy()
selectors.reverse()

while selectors:
selector = selectors.pop()
intermediate = []
if not shadow_roots:
intermediate = self.marionette.find_elements("css selector", selector)
else:
for root in shadow_roots:
intermediate.extend(root.find_elements("css selector", selector))

if (selectors):
shadow_roots = [element.shadow_root for element in intermediate]
shadow_roots = [root for root in shadow_roots if root is not None]
else:
return intermediate

def elements_by_selector(self, selector):
return self.marionette.find_elements("css selector", selector)

Expand Down
6 changes: 6 additions & 0 deletions tools/wptrunner/wptrunner/executors/executorselenium.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@ class SeleniumSelectorProtocolPart(SelectorProtocolPart):
def setup(self):
self.webdriver = self.parent.webdriver

def elements_by_selector_array(self, selectors):
if len(selectors) == 1:
return self.elements_by_selector(selectors[0])

raise NotImplementedError()

def elements_by_selector(self, selector):
return self.webdriver.find_elements_by_css_selector(selector)

Expand Down
6 changes: 6 additions & 0 deletions tools/wptrunner/wptrunner/executors/executorwebdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,12 @@ class WebDriverSelectorProtocolPart(SelectorProtocolPart):
def setup(self):
self.webdriver = self.parent.webdriver

def elements_by_selector_array(self, selectors):
if len(selectors) == 1:
return self.elements_by_selector(selectors[0])

raise NotImplementedError()

def elements_by_selector(self, selector):
return self.webdriver.find.css(selector)

Expand Down
18 changes: 18 additions & 0 deletions tools/wptrunner/wptrunner/executors/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,24 @@ class SelectorProtocolPart(ProtocolPart):

name = "select"

def element_by_selector_array(self, element_selectors):
elements = self.elements_by_selector_array(element_selectors)
if len(elements) == 0:
raise ValueError(f"Selector array '{element_selectors}' matches no elements")
elif len(elements) > 1:
raise ValueError(f"Selector array '{element_selectors}' matches multiple elements")
return elements[0]

@abstractmethod
def elements_by_selector_array(self, selectors):
"""Select elements matching an array of selectors, such that the first
selector matches an element in the document root, and each successive
selector matches an element inside the shadow root of the previous.

:param List[str] selectors: The CSS selectors
:returns: A list of protocol-specific handles to elements"""
pass

def element_by_selector(self, element_selector):
elements = self.elements_by_selector(element_selector)
if len(elements) == 0:
Expand Down
48 changes: 32 additions & 16 deletions tools/wptrunner/wptrunner/testdriver-extra.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
} else {
if (bits >= 1 && bits <= 30) {
return 0 | ((1 << bits) * Math.random());
} else {
} else {
var high = (0 | ((1 << (bits - 30)) * Math.random())) * (1 << 30);
var low = 0 | ((1 << 30) * Math.random());
return high + low;
Expand Down Expand Up @@ -134,14 +134,18 @@
} else {
// push and then reverse to avoid O(n) unshift in the loop
let segments = [];
for (let node = element;
node.parentElement;
node = node.parentElement) {
let segment = "*|" + node.localName;
let nth = Array.prototype.indexOf.call(node.parentElement.children, node) + 1;
let el = element;
while (el && el.parentElement) {
let segment = "*|" + el.localName;
let nth = Array.prototype.indexOf.call(el.parentNode.children, el) + 1;
segments.push(segment + ":nth-child(" + nth + ")");
el = el.parentElement;
}
if (element.getRootNode() == element.ownerDocument) {
segments.push(":root");
} else {
segments.push(":scope");
}
segments.push(":root");
segments.reverse();

selector = segments.join(" > ");
Expand All @@ -150,6 +154,18 @@
return selector;
};

const get_selector_array = function(element) {
let selectors = [];
let current = element;

do {
selectors.push(get_selector(current));
current = current.getRootNode().host;
} while (current);

return selectors.reverse();
};

/**
* Create an action and return a promise that resolves when the action is complete.
* @param name: The name of the action to create.
Expand Down Expand Up @@ -460,9 +476,9 @@
};

window.test_driver_internal.click = function(element) {
const selector = get_selector(element);
const selectors = get_selector_array(element);
const context = get_context(element);
return create_context_action("click", context, {selector});
return create_context_action("click", context, {selectors});
};

window.test_driver_internal.delete_all_cookies = function(context=null) {
Expand All @@ -482,15 +498,15 @@
}

window.test_driver_internal.get_computed_label = function(element) {
const selector = get_selector(element);
const selectors = get_selector_array(element);
const context = get_context(element);
return create_context_action("get_computed_label", context, {selector});
return create_context_action("get_computed_label", context, {selectors});
};

window.test_driver_internal.get_computed_role = function(element) {
const selector = get_selector(element);
const selectors = get_selector_array(element);
const context = get_context(element);
return create_context_action("get_computed_role", context, {selector});
return create_context_action("get_computed_role", context, {selectors});
};

window.test_driver_internal.get_named_cookie = function(name, context=null) {
Expand All @@ -510,9 +526,9 @@
};

window.test_driver_internal.send_keys = function(element, keys) {
const selector = get_selector(element);
const selectors = get_selector_array(element);
const context = get_context(element);
return create_context_action("send_keys", context, {selector, keys});
return create_context_action("send_keys", context, {selectors, keys});
};

window.test_driver_internal.action_sequence = function(actions, context=null) {
Expand All @@ -522,7 +538,7 @@
// The origin of each action can only be an element or a string of a value "viewport" or "pointer".
if (action.type == "pointerMove" && typeof(action.origin) != 'string') {
let action_context = get_context(action.origin);
action.origin = {selector: get_selector(action.origin)};
action.origin = {selectors: get_selector_array(action.origin)};
if (context !== null && action_context !== context) {
throw new Error("Actions must be in a single context");
}
Expand Down