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

Issue 1768 #1819

Merged
merged 5 commits into from
Apr 27, 2023
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
1 change: 1 addition & 0 deletions atest/acceptance/keywords/alerts.robot
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ Handle Alert when popup window closes
[Documentation] Popup window is closed by javascript while
... 'Handle Alert' keyword is waiting for alert
... FAIL GLOB: An exception occurred waiting for alert*
[Tags] Triage
[Setup] Go To Page "javascript/self_closing_popup.html"
Click Button Self Closing
${handle} = Switch Window NEW
Expand Down
6 changes: 5 additions & 1 deletion src/SeleniumLibrary/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
)
from SeleniumLibrary.keywords.screenshot import EMBED
from SeleniumLibrary.locators import ElementFinder
from SeleniumLibrary.utils import LibraryListener, is_truthy, _convert_timeout
from SeleniumLibrary.utils import LibraryListener, is_truthy, _convert_timeout, _convert_delay


__version__ = "6.1.0.dev1"
Expand Down Expand Up @@ -447,6 +447,7 @@ def __init__(
plugins: Optional[str] = None,
event_firing_webdriver: Optional[str] = None,
page_load_timeout=timedelta(minutes=5),
action_chain_delay=timedelta(seconds=0.25),
):
"""SeleniumLibrary can be imported with several optional arguments.

Expand All @@ -467,9 +468,12 @@ def __init__(
[https://seleniumhq.github.io/selenium/docs/api/py/webdriver_support/selenium.webdriver.support.event_firing_webdriver.html#module-selenium.webdriver.support.event_firing_webdriver|EventFiringWebDriver]
- ``page_load_timeout``:
Default value to wait for page load to complete until error is raised.
- ``action_chain_delay``:
Default value for `ActionChains` delay to wait in between actions.
"""
self.timeout = _convert_timeout(timeout)
self.implicit_wait = _convert_timeout(implicit_wait)
self.action_chain_delay = _convert_delay(action_chain_delay)
self.page_load_timeout = _convert_timeout(page_load_timeout)
self.speed = 0.0
self.run_on_failure_keyword = RunOnFailureKeywords.resolve_keyword(
Expand Down
2 changes: 1 addition & 1 deletion src/SeleniumLibrary/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.remote.webelement import WebElement

class SeleniumLibrary:
def __init__(self, timeout = timedelta(seconds=5.0), implicit_wait = timedelta(seconds=0.0), run_on_failure = 'Capture Page Screenshot', screenshot_root_directory: Optional[Union[str, None]] = None, plugins: Optional[Union[str, None]] = None, event_firing_webdriver: Optional[Union[str, None]] = None): ...
def __init__(self, timeout = timedelta(seconds=5.0), implicit_wait = timedelta(seconds=0.0), action_chain_delay(seconds=0.25)), run_on_failure = 'Capture Page Screenshot', screenshot_root_directory: Optional[Union[str, None]] = None, plugins: Optional[Union[str, None]] = None, event_firing_webdriver: Optional[Union[str, None]] = None): ...
def add_cookie(self, name: str, value: str, path: Optional[Union[str, None]] = None, domain: Optional[Union[str, None]] = None, secure: Optional[Union[bool, None]] = None, expiry: Optional[Union[str, None]] = None): ...
def add_location_strategy(self, strategy_name: str, strategy_keyword: str, persist: bool = False): ...
def alert_should_be_present(self, text: str = '', action: str = 'ACCEPT', timeout: Optional[Union[datetime.timedelta, None]] = None): ...
Expand Down
24 changes: 23 additions & 1 deletion src/SeleniumLibrary/keywords/browsermanagement.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

from SeleniumLibrary.base import keyword, LibraryComponent
from SeleniumLibrary.locators import WindowManager
from SeleniumLibrary.utils import secs_to_timestr, _convert_timeout
from SeleniumLibrary.utils import timestr_to_secs, secs_to_timestr, _convert_timeout, _convert_delay

from .webdrivertools import WebDriverCreator

Expand Down Expand Up @@ -705,6 +705,28 @@ def set_selenium_implicit_wait(self, value: timedelta) -> str:
driver.implicitly_wait(self.ctx.implicit_wait)
return old_wait

@keyword
def set_action_chain_delay(self, value: timedelta) -> str:
"""Sets the duration of delay in ActionChains() used by SeleniumLibrary.

The value can be given as a number that is considered to be
seconds or as a human-readable string like ``1 second``.

Value is always stored as milliseconds internally.

The previous value is returned and can be used to restore
the original value later if needed.
"""
old_action_chain_delay = self.ctx.action_chain_delay
self.ctx.action_chain_delay = _convert_delay(value)
return timestr_to_secs(f"{old_action_chain_delay} milliseconds")

@keyword
def get_action_chain_delay(self):
"""Gets the currently stored value for chain_delay_value in timestr format.
"""
return timestr_to_secs(f"{self.ctx.action_chain_delay} milliseconds")

@keyword
def set_browser_implicit_wait(self, value: timedelta):
"""Sets the implicit wait value used by Selenium.
Expand Down
32 changes: 16 additions & 16 deletions src/SeleniumLibrary/keywords/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,7 @@ def click_element(

def _click_with_action_chain(self, locator: Union[WebElement, str]):
self.info(f"Clicking '{locator}' using an action chain.")
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
element = self.find_element(locator)
action.move_to_element(element)
action.click()
Expand All @@ -669,7 +669,7 @@ def _click_with_modifier(self, locator, tag, modifier):
f"Clicking {tag if tag[0] else 'element'} '{locator}' with {modifier}."
)
modifier = self.parse_modifier(modifier)
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
for item in modifier:
action.key_down(item)
element = self.find_element(locator, tag=tag[0], required=False)
Expand All @@ -696,7 +696,7 @@ def click_element_at_coordinates(
f"Clicking element '{locator}' at coordinates x={xoffset}, y={yoffset}."
)
element = self.find_element(locator)
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
action.move_to_element(element)
action.move_by_offset(xoffset, yoffset)
action.click()
Expand All @@ -711,7 +711,7 @@ def double_click_element(self, locator: Union[WebElement, str]):
"""
self.info(f"Double clicking element '{locator}'.")
element = self.find_element(locator)
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
action.double_click(element).perform()

@keyword
Expand All @@ -736,7 +736,7 @@ def scroll_element_into_view(self, locator: Union[WebElement, str]):
New in SeleniumLibrary 3.2.0
"""
element = self.find_element(locator)
ActionChains(self.driver).move_to_element(element).perform()
ActionChains(self.driver, duration=self.ctx.action_chain_delay).move_to_element(element).perform()

@keyword
def drag_and_drop(
Expand All @@ -753,7 +753,7 @@ def drag_and_drop(
"""
element = self.find_element(locator)
target = self.find_element(target)
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
action.drag_and_drop(element, target).perform()

@keyword
Expand All @@ -772,7 +772,7 @@ def drag_and_drop_by_offset(
| `Drag And Drop By Offset` | myElem | 50 | -35 | # Move myElem 50px right and 35px down |
"""
element = self.find_element(locator)
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
action.drag_and_drop_by_offset(element, xoffset, yoffset)
action.perform()

Expand All @@ -790,7 +790,7 @@ def mouse_down(self, locator: Union[WebElement, str]):
"""
self.info(f"Simulating Mouse Down on element '{locator}'.")
element = self.find_element(locator)
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
action.click_and_hold(element).perform()

@keyword
Expand All @@ -805,7 +805,7 @@ def mouse_out(self, locator: Union[WebElement, str]):
size = element.size
offsetx = (size["width"] / 2) + 1
offsety = (size["height"] / 2) + 1
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
action.move_to_element(element)
action.move_by_offset(offsetx, offsety)
action.perform()
Expand All @@ -819,7 +819,7 @@ def mouse_over(self, locator: Union[WebElement, str]):
"""
self.info(f"Simulating Mouse Over on element '{locator}'.")
element = self.find_element(locator)
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
action.move_to_element(element).perform()

@keyword
Expand All @@ -831,13 +831,13 @@ def mouse_up(self, locator: Union[WebElement, str]):
"""
self.info(f"Simulating Mouse Up on element '{locator}'.")
element = self.find_element(locator)
ActionChains(self.driver).release(element).perform()
ActionChains(self.driver, duration=self.ctx.action_chain_delay).release(element).perform()

@keyword
def open_context_menu(self, locator: Union[WebElement, str]):
"""Opens the context menu on the element identified by ``locator``."""
element = self.find_element(locator)
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
action.context_click(element).perform()

@keyword
Expand Down Expand Up @@ -925,12 +925,12 @@ def press_keys(self, locator: Union[WebElement, None, str] = None, *keys: str):
if not is_noney(locator):
self.info(f"Sending key(s) {keys} to {locator} element.")
element = self.find_element(locator)
ActionChains(self.driver).click(element).perform()
ActionChains(self.driver, duration=self.ctx.action_chain_delay).click(element).perform()
else:
self.info(f"Sending key(s) {keys} to page.")
element = None
for parsed_key in parsed_keys:
actions = ActionChains(self.driver)
actions = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
for key in parsed_key:
if key.special:
self._press_keys_special_keys(actions, element, parsed_key, key)
Expand Down Expand Up @@ -978,7 +978,7 @@ def mouse_down_on_link(self, locator: Union[WebElement, str]):
using ``id``, ``name``, ``href`` and the link text.
"""
element = self.find_element(locator, tag="link")
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
action.click_and_hold(element).perform()

@keyword
Expand Down Expand Up @@ -1026,7 +1026,7 @@ def mouse_down_on_image(self, locator: Union[WebElement, str]):
using ``id``, ``name``, ``src`` and ``alt``.
"""
element = self.find_element(locator, tag="image")
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
action.click_and_hold(element).perform()

@keyword
Expand Down
1 change: 1 addition & 0 deletions src/SeleniumLibrary/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
is_truthy,
WINDOWS,
_convert_timeout,
_convert_delay,
) # noqa


Expand Down
7 changes: 7 additions & 0 deletions src/SeleniumLibrary/utils/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@
def is_noney(item):
return item is None or is_string(item) and item.upper() == "NONE"

def _convert_delay(delay):
if isinstance(delay, timedelta):
return delay.microseconds // 1000
else:
x = timestr_to_secs(delay)
return int( x * 1000)


def _convert_timeout(timeout):
if isinstance(timeout, timedelta):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ SeleniumLibrary can be imported with several optional arguments.
[https://seleniumhq.github.io/selenium/docs/api/py/webdriver_support/selenium.webdriver.support.event_firing_webdriver.html#module-selenium.webdriver.support.event_firing_webdriver|EventFiringWebDriver]
- ``page_load_timeout``:
Default value to wait for page load to complete until error is raised.
- ``action_chain_delay``:
Default value for `ActionChains` delay to wait in between actions.
2 changes: 1 addition & 1 deletion utest/test/api/test_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def setUpClass(cls):
def test_no_libraries(self):
for item in [None, "None", ""]:
sl = SeleniumLibrary(plugins=item)
self.assertEqual(len(sl.get_keyword_names()), 175)
self.assertEqual(len(sl.get_keyword_names()), 177)

def test_parse_library(self):
plugin = "path.to.MyLibrary"
Expand Down
19 changes: 19 additions & 0 deletions utest/test/keywords/test_browsermanagement.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,25 @@ def test_set_selenium_timeout_only_affects_open_browsers():
verifyNoMoreInteractions(second_browser)


def test_action_chain_delay_default():
sl = SeleniumLibrary()
assert sl.action_chain_delay == 250, f"Delay should have 250"


def test_set_action_chain_delay_default():
sl = SeleniumLibrary()
sl.set_action_chain_delay("3.0")
assert sl.action_chain_delay == 3000, f"Delay should have 3000"

sl.set_action_chain_delay("258 milliseconds")
assert sl.action_chain_delay == 258, f"Delay should have 258"


def test_get_action_chain_delay_default():
sl = SeleniumLibrary()
sl.set_action_chain_delay("300 milliseconds")
assert sl.get_action_chain_delay() == 0.3

def test_selenium_implicit_wait_default():
sl = SeleniumLibrary()
assert sl.implicit_wait == 0.0, "Wait should have 0.0"
Expand Down
22 changes: 20 additions & 2 deletions utest/test/keywords/test_keyword_arguments_element.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import pytest
from mockito import mock, unstub, when

from mockito import mock, unstub, when, matchers
from SeleniumLibrary.keywords import ElementKeywords
import SeleniumLibrary.keywords.element as SUT


@pytest.fixture(scope="function")
def element():
ctx = mock()
ctx._browser = mock()
ctx.action_chain_delay = 251
return ElementKeywords(ctx)


Expand All @@ -27,3 +28,20 @@ def test_element_text_should_be(element):
with pytest.raises(AssertionError) as error:
element.element_text_should_be(locator, "not text", "foobar")
assert "foobar" in str(error.value)



def test_action_chain_delay_in_elements(element):
locator = "//div"
webelement = mock()
when(element).find_element(locator).thenReturn(webelement)

chain_mock = mock()
expected_delay_in_ms = 1000
element.ctx.action_chain_delay = expected_delay_in_ms
when(chain_mock).move_to_element(matchers.ANY).thenReturn(mock())
when(SUT).ActionChains(matchers.ANY, duration=expected_delay_in_ms).thenReturn(chain_mock)
element.scroll_element_into_view(locator)