Skip to content

Commit

Permalink
Add keyboard class and Element.press()
Browse files Browse the repository at this point in the history
  • Loading branch information
jsfehler committed Jun 11, 2024
1 parent c514c4a commit e81888a
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 21 deletions.
17 changes: 17 additions & 0 deletions splinter/driver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,23 @@ def type(self, value: str, slowly: bool = False) -> str: # NOQA: A003
"""
raise NotImplementedError

def press(self, key: str, delay: int = 0) -> None:
"""Focus the element and press the specified key pattern.
Key names are case sensitive.
Arguments:
key_name: Name of the key to press.
delay: Time, in seconds, to wait between key down and key up.
Example:
>>> browser.find_by_css('.my_element').press('ENTER')
>>> browser.find_by_css('.my_element').press('SHIFT+awesome')
"""
raise NotImplementedError

def select(self, value: str, slowly: bool = False) -> None:
"""
Select an ``<option>`` element in the element using the ``value`` of the ``<option>``.
Expand Down
30 changes: 30 additions & 0 deletions splinter/driver/webdriver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.alert import Alert
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as EC # NOQA: N812
from selenium.webdriver.support.ui import WebDriverWait

from splinter.driver import DriverAPI
from splinter.driver import ElementAPI
from splinter.driver.find_links import FindLinks
from splinter.driver.webdriver.cookie_manager import CookieManager
from splinter.driver.webdriver.keyboard import Keyboard
from splinter.driver.xpath_utils import _concat_xpath_from_str
from splinter.element_list import ElementList
from splinter.exceptions import ElementDoesNotExist
Expand Down Expand Up @@ -265,6 +267,8 @@ class BaseWebDriver(DriverAPI):
def __init__(self, driver=None, wait_time=2):
self.wait_time = wait_time

self.keyboard = Keyboard(self)

self.links = FindLinks(self)

self.driver = driver
Expand Down Expand Up @@ -792,6 +796,32 @@ def type(self, value, slowly=False): # NOQA: A003
self._element.send_keys(value)
return value

def press(self, key: str, delay: int = 0) -> None:
key_pattern = key.split("+")

chain = ActionChains(self.driver)

for item in key_pattern:
# If in selenium keys, use it, if not, assume literal.
key_value = getattr(Keys, item)

if key_value:
chain = chain.key_down(key_value, self._element)
else:
chain = chain.send_keys(key, self._element)

if delay:
chain = chain.pause(delay)

for item in key_pattern:
# If in selenium keys, use it, if not, assume literal.
key_value = getattr(Keys, item)

if key_value:
chain = chain.key_up(key_value, self._element)

chain.perform()

def click(self):
"""Click an element.
Expand Down
47 changes: 47 additions & 0 deletions splinter/driver/webdriver/keyboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys


class Keyboard:
def __init__(self, driver) -> None:
self.driver = driver

def down(self, key: str) -> None:
key_value = getattr(Keys, key)

chain = ActionChains(self.driver)
chain.key_down(key_value)
chain.perform()

def up(self, key: str) -> None:
key_value = getattr(Keys, key)

chain = ActionChains(self.driver)
chain.key_up(key_value)
chain.perform()

def press(self, key: str, delay: int = 0) -> None:
key_pattern = key.split("+")

chain = ActionChains(self.driver)

for item in key_pattern:
# If in selenium keys, use it, if not, assume literal.
key_value = getattr(Keys, item)

if key_value:
chain = chain.key_down(key_value) # , self._element)
else:
chain = chain.send_keys(key) # , self._element)

if delay:
chain = chain.pause(delay)

for item in key_pattern:
# If in selenium keys, use it, if not, assume literal.
key_value = getattr(Keys, item)

if key_value:
chain = chain.key_up(key_value) # , self._element)

chain.perform()
3 changes: 2 additions & 1 deletion tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from .is_text_present import IsTextPresentTest
from .type import SlowlyTypeTest
from .get_browser import get_browser

from .keyboard import KeyboardTest

supported_browsers = [
"chrome",
Expand Down Expand Up @@ -138,6 +138,7 @@ class WebDriverTests(
BaseBrowserTests,
ElementDoestNotExistTest,
IsElementPresentTest,
KeyboardTest,
):
def test_status_code(self):
with pytest.raises(NotImplementedError):
Expand Down
23 changes: 23 additions & 0 deletions tests/element.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright 2012 splinter authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
from .skip_if import skip_if_zope, skip_if_django, skip_if_flask


class ElementTest:
Expand Down Expand Up @@ -29,3 +30,25 @@ def test_element_html(self):
assert (
self.browser.find_by_id("html-property").html == 'inner <div class="inner-html">inner text</div> html test'
)

@skip_if_zope
@skip_if_django
@skip_if_flask
def test_element_press_tab(self):
elem = self.browser.find_by_css("[name='q']")
next_elem = self.browser.find_by_css("[name='input1']").first

assert not next_elem.is_selected()

elem.press("TAB")

assert next_elem.is_selected()

@skip_if_zope
@skip_if_django
@skip_if_flask
def test_element_press_combo(self):
elem = self.browser.find_by_css("[name='q']")
elem.press("SHIFT+a+b+c+BACKSPACE")

assert elem.text == "AB"
21 changes: 1 addition & 20 deletions tests/form_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,7 @@
import pytest

from splinter.exceptions import ElementDoesNotExist


def skip_if_zope(f):
def wrapper(self, *args, **kwargs):
if self.__class__.__name__ == "TestZopeTestBrowserDriver":
return pytest.skip("skipping this test for zope testbrowser")
else:
f(self, *args, **kwargs)

return wrapper


def skip_if_django(f):
def wrapper(self, *args, **kwargs):
if self.__class__.__name__ == "TestDjangoClientDriver":
return pytest.skip("skipping this test for django")
else:
f(self, *args, **kwargs)

return wrapper
from .skip_if import skip_if_zope, skip_if_django


class FormElementsTest:
Expand Down
13 changes: 13 additions & 0 deletions tests/keyboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from splinter.driver.webdriver import Keyboard


class KeyboardTest:
def test_keyboard_down(self):
keyboard = Keyboard(self.browser)

keyboard.down("SHIFT")

elem = self.browser.find_by_css("[name='q']")
elem.fill("hello")

assert elem.text == "HELLO"
31 changes: 31 additions & 0 deletions tests/skip_if.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import pytest


def skip_if_zope(f):
def wrapper(self, *args, **kwargs):
if self.__class__.__name__ == "TestZopeTestBrowserDriver":
return pytest.skip("skipping this test for zope testbrowser")
else:
f(self, *args, **kwargs)

return wrapper


def skip_if_django(f):
def wrapper(self, *args, **kwargs):
if self.__class__.__name__ == "TestDjangoClientDriver":
return pytest.skip("skipping this test for django")
else:
f(self, *args, **kwargs)

return wrapper


def skip_if_flask(f):
def wrapper(self, *args, **kwargs):
if self.__class__.__name__ == "TestFlaskClientDriver":
return pytest.skip("skipping this test for flask")
else:
f(self, *args, **kwargs)

return wrapper

0 comments on commit e81888a

Please sign in to comment.