Skip to content

Commit

Permalink
[#515] NEW: add command.js.click with offset params
Browse files Browse the repository at this point in the history
  • Loading branch information
yashaka committed Mar 4, 2024
1 parent 39481af commit f600d39
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 38 deletions.
155 changes: 117 additions & 38 deletions selene/core/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,12 @@
# SOFTWARE.
from __future__ import annotations
import sys
from typing import Union, Optional
from typing import Union, Optional, overload

import typing
from selenium.webdriver import Keys
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait

from selene.core._actions import _Actions
from selene.core.entity import Element, Collection
from selene.core._browser import Browser
from selene.core.exceptions import _SeleneError
Expand All @@ -39,6 +37,9 @@
from selenium.webdriver.common.actions.pointer_input import PointerInput


# TODO: refactor to be of same style as __ClickWithOffset
# in order to make autocomplete work properly
# do it for save_screenshot and all other similar impls
def save_screenshot(path: Optional[str] = None) -> Command[Browser]:
command: Command[Browser] = Command(
'save screenshot',
Expand Down Expand Up @@ -229,41 +230,119 @@ def func(element: Element):
lambda element: element.execute_script('element.scrollIntoView(true)'),
)

click: Command[Element] = Command(
'click',
# TODO: should we process collections too? i.e. click through all elements?
lambda element: element.execute_script(
'''
const offsetX = arguments[0]
const offsetY = arguments[1]
const rect = element.getBoundingClientRect()
function mouseEvent() {
if (typeof (Event) === 'function') {
return new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + rect.width / 2 + offsetX,
clientY: rect.top + rect.height / 2 + offsetY
})
}
else {
const event = document.createEvent('MouseEvent')
event.initEvent('click', true, true)
event.type = 'click'
event.view = window
event.clientX = rect.left + rect.width / 2 + offsetX
event.clientY = rect.top + rect.height / 2 + offsetY
return event
}
}
element.dispatchEvent(mouseEvent())
''',
0,
0,
),
)
# TODO: should we process collections too? i.e. click through all elements?
@staticmethod
def __click(self=None, /, *, xoffset=0, yoffset=0) -> Command[Element]:
def func(element: Element):
element.execute_script(
'''
const offsetX = arguments[0]
const offsetY = arguments[1]
const rect = element.getBoundingClientRect()
function mouseEvent() {
if (typeof (Event) === 'function') {
return new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + rect.width / 2 + offsetX,
clientY: rect.top + rect.height / 2 + offsetY
})
}
else {
const event = document.createEvent('MouseEvent')
event.initEvent('click', true, true)
event.type = 'click'
event.view = window
event.clientX = rect.left + rect.width / 2 + offsetX
event.clientY = rect.top + rect.height / 2 + offsetY
return event
}
}
element.dispatchEvent(mouseEvent())
''',
xoffset,
yoffset,
)

command: Command[Element] = Command(
(
'click'
if (not xoffset and not yoffset)
else f'click(xoffset={xoffset},yoffset={yoffset})'
),
func,
)

if isinstance(self, Element):
# somebody passed command as `.perform(command.js.click)`
# not as `.perform(command.js.click())`
element = self
command.__call__(element)

return command

class __ClickWithOffset(Command[Element]):
def __init__(self):
self._description = 'click'

@overload
def __call__(self, element: Element) -> None: ...

@overload
def __call__(self, *, xoffset=0, yoffset=0) -> Command[Element]: ...

def __call__(self, element: Element | None = None, *, xoffset=0, yoffset=0):
def func(element: Element):
element.execute_script(
'''
const offsetX = arguments[0]
const offsetY = arguments[1]
const rect = element.getBoundingClientRect()
function mouseEvent() {
if (typeof (Event) === 'function') {
return new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + rect.width / 2 + offsetX,
clientY: rect.top + rect.height / 2 + offsetY
})
}
else {
const event = document.createEvent('MouseEvent')
event.initEvent('click', true, true)
event.type = 'click'
event.view = window
event.clientX = rect.left + rect.width / 2 + offsetX
event.clientY = rect.top + rect.height / 2 + offsetY
return event
}
}
element.dispatchEvent(mouseEvent())
''',
xoffset,
yoffset,
)

if element is not None:
# somebody passed command as `.perform(command.js.click)`
# not as `.perform(command.js.click())`
func(element)
return None

return Command(
(
self.__str__()
if (not xoffset and not yoffset)
else f'click(xoffset={xoffset},yoffset={yoffset})'
),
func,
)

click = __ClickWithOffset()

clear_local_storage: Command[Browser] = Command(
'clear local storage',
Expand Down
42 changes: 42 additions & 0 deletions tests/integration/element__perform__js__click_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,45 @@ def test_command_js_click_does_not_wait_for_no_overlay(session_browser):
time_diff = time.time() - before_call
assert time_diff < 0.25
assert "second" in browser.driver.current_url


def test_command_js_click__call__is_still_same_command(session_browser):
browser = session_browser.with_(timeout=0.5)
page = GivenPage(browser.driver)
page.opened_with_body(
'''
<div
id="overlay"
style='
display:block;
position: fixed;
display: block;
width: 100%;
height: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0,0,0,0.1);
z-index: 2;
cursor: pointer;
'
>
</div>
<a href="#second">go to Heading 2</a>
<h2 id="second">Heading 2</h2>
'''
)
before_call = time.time()
page.execute_script_with_timeout(
'''
document.getElementById('overlay').style.display='none'
''',
0.75,
)

browser.element('a').perform(command.js.click())

time_diff = time.time() - before_call
assert time_diff < 0.25
assert "second" in browser.driver.current_url
Empty file added tests/unit/core/__init__.py
Empty file.
9 changes: 9 additions & 0 deletions tests/unit/core/command__js__click_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from selene import command, Element
from selene.core.wait import Command
import inspect


def test_command_js_click__is_a_command_on_element_instance():

assert Command in inspect.getmro(command.js.click.__class__)
# TODO: what else can we check? How can we check that it is Command[Element]?

0 comments on commit f600d39

Please sign in to comment.