Skip to content
38 changes: 37 additions & 1 deletion atest/acceptance/keywords/textfields.robot
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,42 @@ Press Key
Attempt Clear Element Text On Non-Editable Field
Run Keyword And Expect Error * Clear Element Text can_send_email

Input Password Accepts Secret Type
[Tags] require-rf-7.4
[Setup] Go To Page "forms/login.html"
Set Environment Variable TEST_PASSWORD s3cret-pass
VAR ${pw: Secret} %{TEST_PASSWORD}
Input Text username_field my_username
Input Password password_field ${pw}
${value}= Get Value password_field
Should Be Equal ${value} s3cret-pass

Input Text Accepts Secret Type
[Tags] require-rf-7.4
[Setup] Go To Page "forms/login.html"
Set Environment Variable TEST_USERNAME my_username
VAR ${user: Secret} %{TEST_USERNAME}
Input Text username_field ${user}
${value}= Get Value username_field
Should Be Equal ${value} my_username

Input Password With Plain String Still Works
[Setup] Go To Page "forms/login.html"
[Documentation] Backwards compatibility — plain str must still be accepted.
Input Text username_field my_username
Input Password password_field plain-pass
${value}= Get Value password_field
Should Be Equal ${value} plain-pass

Input Password Does Not Log Secret Value
[Tags] require-rf-7.4 NoGrid
[Setup] Go To Page "forms/login.html"
[Documentation]
... LOG 3:1 INFO Typing password into text field 'password_field'.
Set Environment Variable TEST_PASSWORD must-not-leak
VAR ${pw: Secret} %{TEST_PASSWORD}
Input Password password_field ${pw}

*** Keywords ***

Open Browser To Start Page Disabling Chrome Leaked Password Detection
Expand All @@ -87,4 +123,4 @@ Open Browser To Start Page Disabling Chrome Leaked Password Detection
... options=add_experimental_option("prefs", {"profile.password_manager_leak_detection": False}) alias=${alias}
ELSE
Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} alias=${alias}
END
END
3 changes: 3 additions & 0 deletions atest/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import subprocess
import tempfile

from packaging.version import Version
from robot import rebot_cli
from robot import __version__ as robot_version
from selenium import __version__ as selenium_version
Expand Down Expand Up @@ -223,6 +224,8 @@ def execute_tests(interpreter, browser, rf_options, grid, event_firing, port):
"--exclude",
"triage",
]
if Version(robot_version) < Version("7.4"):
options.extend(["--exclude", "require-rf-7.4"])
command = runner
if grid:
command += [
Expand Down
27 changes: 21 additions & 6 deletions src/SeleniumLibrary/keywords/formelement.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

from SeleniumLibrary.base import LibraryComponent, keyword
from SeleniumLibrary.errors import ElementNotFound
from SeleniumLibrary.utils.types import Locator
from SeleniumLibrary.utils.types import Locator, Secret


class FormElementKeywords(LibraryComponent):
Expand Down Expand Up @@ -238,7 +238,9 @@ def choose_file(self, locator: Locator, file_path: str):
self.ctx._running_keyword = None

@keyword
def input_password(self, locator: Locator, password: str, clear: bool = True):
def input_password(
self, locator: Locator, password: str | Secret, clear: bool = True
):
"""Types the given password into the text field identified by ``locator``.

See the `Locating elements` section for details about the locator
Expand All @@ -256,8 +258,15 @@ def input_password(self, locator: Locator, password: str, clear: bool = True):
| Input Password | password_field | ${PASSWORD} |

Please notice that Robot Framework logs all arguments using
the TRACE level and tests must not be executed using level below
DEBUG if the password should not be logged in any format.
the TRACE level. When not using the ``Secret`` type, tests must
not be executed using level below DEBUG if the password should
not be logged in any format.``

This keyword supports Robot Framework 7.4
[https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#secret-variables|Secret]
variable type. When a ``Secret`` is passed, the value is protected
from Robot Framework logs and Selenium internal logging is also
suppressed during typing.

The `clear` argument is new in SeleniumLibrary 4.0. Hiding password
logging from Selenium logs is new in SeleniumLibrary 4.2.
Expand All @@ -266,14 +275,20 @@ def input_password(self, locator: Locator, password: str, clear: bool = True):
self._input_text_into_text_field(locator, password, clear, disable_log=True)

@keyword
def input_text(self, locator: Locator, text: str, clear: bool = True):
def input_text(self, locator: Locator, text: str | Secret, clear: bool = True):
"""Types the given ``text`` into the text field identified by ``locator``.

When ``clear`` is true, the input element is cleared before
the text is typed into the element. When false, the previous text
is not cleared from the element. Use `Input Password` if you
do not want the given ``text`` to be logged.

This keyword supports Robot Framework 7.4
[https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#secret-variables|Secret]
variable type. When a ``Secret`` is passed, the value is masked in
Robot Framework logs. Note that unlike `Input Password`, Selenium's
internal logs are not suppressed during typing.

If [https://github.com/SeleniumHQ/selenium/wiki/Grid2|Selenium Grid]
is used and the ``text`` argument points to a file in the file system,
then this keyword prevents the Selenium to transfer the file to the
Expand Down Expand Up @@ -502,7 +517,7 @@ def _input_text_into_text_field(self, locator, text, clear=True, disable_log=Fal
self.info("Temporally setting log level to: NONE")
previous_level = BuiltIn().set_log_level("NONE")
try:
element.send_keys(text)
element.send_keys(text.value if isinstance(text, Secret) else text)
finally:
if disable_log:
BuiltIn().set_log_level(previous_level)
23 changes: 23 additions & 0 deletions src/SeleniumLibrary/utils/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,29 @@
from robot.utils import is_falsy, is_truthy, timestr_to_secs # noqa
from selenium.webdriver.remote.webelement import WebElement

try:
from robot.api.types import Secret
except ImportError:
# Secret was introduced in Robot Framework 7.4. On older versions we
# provide a minimal stand-in so that the type hint ``str | Secret`` and
# ``isinstance`` checks work without requiring an upgrade.
class Secret: # type: ignore[no-redef]
"""Stand-in for ``robot.api.types.Secret`` on Robot Framework < 7.4.

Exposes the same ``.value`` attribute and masked string representation
as the real class, so keyword code can treat both identically.
"""

def __init__(self, value: str):
self.value = value

def __str__(self) -> str:
return "<secret>"

def __repr__(self) -> str:
return f"{type(self).__name__}(value=<secret>)"


Locator: TypeAlias = WebElement | str | list["Locator"]


Expand Down
Loading