diff --git a/bidi-test.py b/bidi-test.py index 5a7ac42..fed7b20 100755 --- a/bidi-test.py +++ b/bidi-test.py @@ -126,8 +126,25 @@ class Browser: # TODO: wait for value time.sleep(0.2) - def mouse(self, selector: str, button: int = 0, click_count: int = 1) -> None: + def click(self, selector: str, button: int = 0, click_count: int = 1) -> None: self.wait_visible(selector) + self.bidi("script.evaluate", expression=f"window.ph_find({jsquote(selector)}).scrollIntoView()", + awaitPromise=False, target={"context": self.driver.context}) + + # HACK: Chromium mis-clicks to wrong position with iframes; use our old "synthesize MouseEvent" approach + # TODO: file/find bug + if isinstance(self.driver, bidi.ChromiumBidi): + if click_count == 1: + _type = "click" + elif click_count == 2: + _type = "dblclick" + else: + raise bidi.Error("only click_count=1 or 2 are supported with Chromium") + self.bidi("script.evaluate", + expression=f"window.ph_mouse({jsquote(selector)}, '{_type}', 0, 0, {button})", + awaitPromise=False, target={"context": self.driver.context}) + return + element = self.bidi("script.evaluate", expression=f"window.ph_find({jsquote(selector)})", awaitPromise=False, target={"context": self.driver.context})["result"] @@ -138,16 +155,13 @@ class Browser: self.bidi("input.performActions", context=self.driver.context, actions=[ { - "id": "pointer-0", + "id": f"pointer-{self.driver.last_id}", "type": "pointer", "parameters": {"pointerType": "mouse"}, "actions": actions, } ]) - def click(self, selector: str) -> None: - return self.mouse(selector) - def wait_text(self, selector: str, text: str) -> None: self.wait_visible(selector) self.wait_js_cond(f"window.ph_text({jsquote(selector)}) == {jsquote(text)}", @@ -177,11 +191,17 @@ try: b.set_input_text("#login-user-input", "admin") b.set_input_text("#login-password-input", "foobar") # either works - # b.click("#login-button") - b.key(KEY_ENTER) + b.click("#login-button") + # b.key(KEY_ENTER) b.wait_text("#super-user-indicator", "Limited access") b.switch_to_frame("cockpit1:localhost/system") b.wait_in_text(".system-configuration", "Join domain") + + b.switch_to_top() + b.click("#host-apps a[href='/system/services']") + b.switch_to_frame("cockpit1:localhost/system/services") + b.click("tr[data-goto-unit='virtqemud.service'] a") + b.wait_in_text("#service-details-unit", "Automatically starts") finally: b.close() diff --git a/bidi.py b/bidi.py index 7e46e49..3fa5c9e 100755 --- a/bidi.py +++ b/bidi.py @@ -140,10 +140,6 @@ class WebdriverBidi: else: raise WebdriverError("timed out waiting for default realm") - # avoid not seeing elements due to too small window - # await self.bidi("browsingContext.setViewport", context=self.top_context, - # viewport={"width": 1024, "height": 5000}) - async def __aenter__(self): await self.start_session() return self diff --git a/test-functions.js b/test-functions.js index 50e0c55..633e3fe 100644 --- a/test-functions.js +++ b/test-functions.js @@ -73,3 +73,59 @@ window.ph_wait_cond = function (cond, timeout, error_description) { step(); }); }; + +// we only need this for Chromium, which mis-handles pointerMove with frames +window.ph_mouse = function(sel, type, x, y, btn, ctrlKey, shiftKey, altKey, metaKey) { + const el = window.ph_find(sel); + + /* The element has to be visible, and not collapsed */ + if (el.offsetWidth <= 0 && el.offsetHeight <= 0 && el.tagName !== 'svg') + throw new Error(sel + " is not visible"); + + /* The event has to actually work */ + let processed = false; + function handler() { + processed = true; + } + + el.addEventListener(type, handler, true); + + let elp = el; + let left = elp.offsetLeft || 0; + let top = elp.offsetTop || 0; + while (elp.offsetParent) { + elp = elp.offsetParent; + left += elp.offsetLeft; + top += elp.offsetTop; + } + + let detail = 0; + if (["click", "mousedown", "mouseup"].indexOf(type) > -1) + detail = 1; + else if (type === "dblclick") + detail = 2; + + const ev = new MouseEvent(type, { + bubbles: true, + cancelable: true, + view: window, + detail, + screenX: left + x, + screenY: top + y, + clientX: left + x, + clientY: top + y, + button: btn, + ctrlKey: ctrlKey || false, + shiftKey: shiftKey || false, + altKey: altKey || false, + metaKey: metaKey || false + }); + + el.dispatchEvent(ev); + + el.removeEventListener(type, handler, true); + + /* It really had to work */ + if (!processed) + throw new Error(sel + " is disabled or somehow doesn't process events"); +};