Skip to content

Commit

Permalink
test: Use real mouse events in tests on Firefox
Browse files Browse the repository at this point in the history
Our years-old `ph_mouse()` helper is cheating: It completely side-steps
the browser UI and directly synthesizes `MouseEvent`s in JavaScript.
This allowed funny things like clicking on a main page element while a
dialog is open (which you can't normally do as the dialog is modal),
clicking an element which is disabled in some non-standard way, or
clicking through a tooltip.

Make this more realistic by using the BiDi API for synthesizing mouse
events. This works fine with Firefox, but is unfortunately completely
broken in iframes with Chromium:
https://issues.chromium.org/issues/359616812

Specifying precise coordinates is not currently implemented.
`ph_mouse()` always clicks on the top left corner, while webdriver's
`pointerMove` puts (0,0) in the center of the element. This can be done
with a little extra calculation of the `getBoundingRect()`, but only
very few tests like `TestStorageLvm2.testMaxLayoutGrowth` rely on that.
For these tests, continue to use `ph_mouse()`.

https://issues.redhat.com/browse/COCKPIT-1158
  • Loading branch information
martinpitt committed Aug 20, 2024
1 parent 65c840b commit c50bf49
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 1 deletion.
13 changes: 13 additions & 0 deletions test/common/test-functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ window.ph_find = function(sel) {
return window.ph_only(els, sel);
};

window.ph_find_scroll_into_view = function(sel) {
const el = window.ph_find(sel);
/* we cannot make this conditional, as there is no way to find out whether
an element is currently visible -- the usual trick to compare getBoundingClientRect() against
window.innerHeight does not work if the element is in e.g. a scrollable dialog area */
return new Promise(resolve => {
el.scrollIntoView({ behaviour: 'instant', block: 'center', inline: 'center' });
// scrolling needs a little bit of time to stabilize, and it's not predictable
// in particular, 'scrollend' is not reliably emitted
window.setTimeout(() => resolve(el), 200);
});
};

window.ph_count = function(sel) {
const els = window.ph_select(sel);
return els.length;
Expand Down
58 changes: 57 additions & 1 deletion test/common/testlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
"ArrowDown": "\uE015",
"Insert": "\uE016",
"Delete": "\uE017",
"Meta": "\uE03D",
}


Expand Down Expand Up @@ -502,7 +503,62 @@ def mouse(
:param metaKey: press the meta key
"""
self.wait_visible(selector)
self.call_js_func('ph_mouse', selector, event, x, y, btn, ctrlKey, shiftKey, altKey, metaKey)

# HACK: Chromium clicks don't work with iframes; use our old "synthesize MouseEvent" approach
# https://issues.chromium.org/issues/359616812
# TODO: x and y are not currently implemented: webdriver (0, 0) is the element's center, not top left corner
if self.browser == "chromium" or x != 0 or y != 0:
self.call_js_func('ph_mouse', selector, event, x, y, btn, ctrlKey, shiftKey, altKey, metaKey)
return

# For Firefox and regular clicks, use the BiDi API, which is more realistic -- it doesn't
# sidestep the browser
element = self.call_js_func('ph_find_scroll_into_view', selector)

actions = [{"type": "pointerMove", "x": 0, "y": 0, "origin": {"type": "element", "element": element}}]
down = {"type": "pointerDown", "button": btn}
up = {"type": "pointerUp", "button": btn}
if event == "click":
actions.extend([down, up])
elif event == "dblclick":
actions.extend([down, up, down, up])
elif event == "mouseenter":
actions.insert(0, {"type": "pointerMove", "x": 0, "y": 0, "origin": "viewport"})
elif event == "mouseleave":
actions.append({"type": "pointerMove", "x": 0, "y": 0, "origin": "viewport"})
else:
raise NotImplementedError(f"unknown event {event}")

# modifier keys
ev_id = f"pointer-{self.driver.last_id}"
keys_pre = []
keys_post = []

def key(type_: str, name: str) -> JsonObject:
return {"type": "key", "id": ev_id + type_, "actions": [{"type": type_, "value": WEBDRIVER_KEYS[name]}]}

if altKey:
keys_pre.append(key("keyDown", "Alt"))
keys_post.append(key("keyUp", "Alt"))
if ctrlKey:
keys_pre.append(key("keyDown", "Control"))
keys_post.append(key("keyUp", "Control"))
if shiftKey:
keys_pre.append(key("keyDown", "Shift"))
keys_post.append(key("keyUp", "Shift"))
if metaKey:
keys_pre.append(key("keyDown", "Meta"))
keys_post.append(key("keyUp", "Meta"))

# the actual mouse event
actions = [{
"id": ev_id,
"type": "pointer",
"parameters": {"pointerType": "mouse"},
"actions": actions,
}]

self.bidi("input.performActions", context=self.driver.context, actions=keys_pre + actions + keys_post)

def click(self, selector: str) -> None:
"""Click on a ui element
Expand Down

0 comments on commit c50bf49

Please sign in to comment.