-
Notifications
You must be signed in to change notification settings - Fork 505
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add keyboard class and Element.press()
- Loading branch information
Showing
7 changed files
with
324 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
.. Copyright 2024 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. | ||
.. meta:: | ||
:description: Keyboard | ||
:keywords: splinter, python, tutorial, documentation, selenium integration, selenium keys, keyboard events | ||
|
||
++++++++ | ||
Keyboard | ||
++++++++ | ||
|
||
The browser provides an interface for using the keyboard. | ||
|
||
However, input is limited to the page. You cannot control the browser or your | ||
operating system using this. | ||
|
||
Down | ||
---- | ||
|
||
Hold a key down. | ||
|
||
.. code-block:: python | ||
from splinter import Browser | ||
browser = Browser() | ||
browser.visit("https://duckduckgo.com/") | ||
browser.keyboard.down("CONTROL") | ||
Up | ||
-- | ||
|
||
Release a key. If the key is not held down, this will do nothing. | ||
|
||
.. code-block:: python | ||
from splinter import Browser | ||
browser = Browser() | ||
browser.visit("https://duckduckgo.com/") | ||
browser.keyboard.down("CONTROL") | ||
browser.keyboard.up("CONTROL") | ||
Press | ||
----- | ||
|
||
Hold and then release a key pattern. | ||
|
||
.. code-block:: python | ||
from splinter import Browser | ||
browser = Browser() | ||
browser.visit("https://duckduckgo.com/") | ||
browser.keyboard.press("CONTROL") | ||
Key patterns are keys separated by the '+' symbol. | ||
This allows multiple presses to be chained together: | ||
|
||
.. code-block:: python | ||
from splinter import Browser | ||
browser = Browser() | ||
browser.visit("https://duckduckgo.com/") | ||
browser.keyboard.press("CONTROL+a") | ||
.. warning:: | ||
Although a key pattern such as "SHIFT+awesome" will be accepted, | ||
the press method is designed for single keys. There may be unintended | ||
side effects to using it in place of Element.fill() or Element.type(). | ||
|
||
Element.press() | ||
~~~~~~~~~~~~~~~ | ||
|
||
Elements can be pressed directly. | ||
|
||
.. code-block:: python | ||
from splinter import Browser | ||
browser = Browser() | ||
browser.visit("https://duckduckgo.com/") | ||
elem = browser.find_by_css("#searchbox_input") | ||
elem.fill("splinter python") | ||
elem.press("ENTER") | ||
results = browser.find_by_xpath("//section[@data-testid='mainline']/ol/li") | ||
# Open in a new tab behind the current one. | ||
results.first.press("CONTROL+ENTER") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
from typing import Union | ||
|
||
from selenium.webdriver.common.action_chains import ActionChains | ||
from selenium.webdriver.common.keys import Keys | ||
from selenium.webdriver.remote.webelement import WebElement | ||
|
||
|
||
class Keyboard: | ||
"""Representation of a keyboard. | ||
Requires a WebDriver instance to use. | ||
Arguments: | ||
driver: The WebDriver instance to use. | ||
element: Optionally, a WebElement to act on. | ||
""" | ||
|
||
def __init__(self, driver, element: Union[WebElement, None] = None) -> None: | ||
self.driver = driver | ||
|
||
self.element = element | ||
|
||
def _resolve_key_down_action(self, action_chain: ActionChains, key: str) -> ActionChains: | ||
"""Given the string <key>, select the correct action for key down. | ||
For modifier keys, use ActionChains.key_down(). | ||
For other keys, use ActionChains.send_keys() or ActionChains.send_keys_to_element() | ||
""" | ||
key_value = getattr(Keys, key, None) | ||
|
||
if key_value: | ||
chain = action_chain.key_down(key_value, self.element) | ||
elif self.element: | ||
chain = action_chain.send_keys_to_element(self.element, key) | ||
else: | ||
chain = action_chain.send_keys(key) | ||
|
||
return chain | ||
|
||
def _resolve_key_up_action(self, action_chain: ActionChains, key: str) -> ActionChains: | ||
"""Given the string <key>, select the correct action for key up. | ||
For modifier keys, use ActionChains.key_up(). | ||
For other keys, use ActionChains.send_keys() or ActionChains.send_keys_to_element() | ||
""" | ||
key_value = getattr(Keys, key, None) | ||
|
||
chain = action_chain | ||
if key_value: | ||
chain = action_chain.key_up(key_value, self.element) | ||
|
||
return chain | ||
|
||
def down(self, key: str) -> "Keyboard": | ||
"""Hold down on a key. | ||
Arguments: | ||
key: The name of a key to hold. | ||
Example: | ||
>>> b = Browser() | ||
>>> Keyboard(b.driver).down('SHIFT') | ||
""" | ||
chain = ActionChains(self.driver) | ||
chain = self._resolve_key_down_action(chain, key) | ||
chain.perform() | ||
return self | ||
|
||
def up(self, key: str) -> "Keyboard": | ||
"""Release a held key. | ||
If <key> is not held down, this method has no effect. | ||
Arguments: | ||
key: The name of a key to release. | ||
Example: | ||
>>> b = Browser() | ||
>>> Keyboard(b.driver).down('SHIFT') | ||
>>> Keyboard(b.driver).up('SHIFT') | ||
""" | ||
chain = ActionChains(self.driver) | ||
chain = self._resolve_key_up_action(chain, key) | ||
chain.perform() | ||
return self | ||
|
||
def press(self, key_pattern: str, delay: int = 0) -> "Keyboard": | ||
"""Hold and release a key pattern. | ||
Key patterns are strings of key names separated by '+'. | ||
The following are examples of key patterns: | ||
- 'CONTROL' | ||
- 'CONTROL+a' | ||
- 'CONTROL+a+BACKSPACE+b' | ||
Arguments: | ||
key_pattern: Pattern of keys to hold and release. | ||
delay: Time, in seconds, to wait between the hold and release. | ||
Example: | ||
>>> b = Browser() | ||
>>> Keyboard(b.driver).press('CONTROL+a') | ||
""" | ||
keys_names = key_pattern.split("+") | ||
|
||
chain = ActionChains(self.driver) | ||
|
||
for item in keys_names: | ||
chain = self._resolve_key_down_action(chain, item) | ||
|
||
if delay: | ||
chain = chain.pause(delay) | ||
|
||
for item in keys_names: | ||
chain = self._resolve_key_up_action(chain, item) | ||
|
||
chain.perform() | ||
|
||
return self |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import platform | ||
|
||
from splinter.driver.webdriver import Keyboard | ||
|
||
|
||
def test_keyboard_down_modifier(browser, app_url): | ||
browser.visit(app_url) | ||
|
||
keyboard = Keyboard(browser.driver) | ||
|
||
keyboard.down("CONTROL") | ||
|
||
elem = browser.find_by_css("#keypress_detect") | ||
assert elem.first | ||
|
||
|
||
def test_keyboard_up_modifier(browser, app_url): | ||
browser.visit(app_url) | ||
|
||
keyboard = Keyboard(browser.driver) | ||
|
||
keyboard.down("CONTROL") | ||
keyboard.up("CONTROL") | ||
|
||
elem = browser.find_by_css("#keyup_detect") | ||
assert elem.first | ||
|
||
|
||
def test_keyboard_press_modifier(browser, app_url): | ||
browser.visit(app_url) | ||
|
||
keyboard = Keyboard(browser.driver) | ||
|
||
keyboard.press("CONTROL") | ||
|
||
elem = browser.find_by_css("#keyup_detect") | ||
assert elem.first | ||
|
||
|
||
def test_element_press_combo(browser, app_url): | ||
browser.visit(app_url) | ||
|
||
keyboard = Keyboard(browser.driver) | ||
|
||
keyboard.press("CONTROL+a") | ||
|
||
elem = browser.find_by_css("#keypress_detect_a") | ||
assert elem.first | ||
|
||
|
||
def test_element_copy_paste(browser, app_url): | ||
control_key = "META" if platform.system() == "Darwin" else "CONTROL" | ||
|
||
browser.visit(app_url) | ||
|
||
elem = browser.find_by_name("q") | ||
elem.fill("Copy this value") | ||
elem.press(f"{control_key}+a") | ||
elem.press(f"{control_key}+c") | ||
elem.clear() | ||
|
||
assert elem.first.value == "" | ||
|
||
elem.press(f"{control_key}+v") | ||
|
||
assert elem.first.value == "Copy this Value" |