From fe025892c088b13b6e8855f1a186e84b53eeff67 Mon Sep 17 00:00:00 2001 From: Yuri Verweij Date: Mon, 4 May 2026 19:18:13 +0200 Subject: [PATCH 1/5] feat: add support for Secret type in input handling and create helper for backward compatibility --- .../keywords/textfields_secret.robot | 47 +++++++++++++++++++ atest/resources/secret_helper.py | 21 +++++++++ src/SeleniumLibrary/keywords/formelement.py | 4 +- src/SeleniumLibrary/utils/types.py | 24 ++++++++-- 4 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 atest/acceptance/keywords/textfields_secret.robot create mode 100644 atest/resources/secret_helper.py diff --git a/atest/acceptance/keywords/textfields_secret.robot b/atest/acceptance/keywords/textfields_secret.robot new file mode 100644 index 000000000..ae1c15d8d --- /dev/null +++ b/atest/acceptance/keywords/textfields_secret.robot @@ -0,0 +1,47 @@ +*** Settings *** +Documentation Verifies Input Text and Input Password accept Robot Framework +... 7.4+ Secret type without ValueError. See issue #1966. +Suite Setup Open Browser To Start Page Disabling Chrome Leaked Password Detection +Test Setup Go To Page "forms/login.html" +Resource ../resource.robot +Library ${CURDIR}/../../resources/secret_helper.py +Force Tags Known Issue Internet Explorer + +*** Test Cases *** +Input Password Accepts Secret Type + [Tags] require-rf-7.4 + Skip If not ${SECRET_AVAILABLE} RF Secret type requires Robot Framework 7.4+ + ${pw}= Make Secret s3cret-pass + Input Text username_field yuri + 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 + Skip If not ${SECRET_AVAILABLE} RF Secret type requires Robot Framework 7.4+ + ${user}= Make Secret yuri + Input Text username_field ${user} + ${value}= Get Value username_field + Should Be Equal ${value} yuri + +Input Password With Plain String Still Works + [Documentation] Backwards compatibility — plain str must still be accepted. + Input Text username_field yuri + 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 + [Documentation] + ... LOG 2:1 INFO Typing password into text field 'password_field'. + Skip If not ${SECRET_AVAILABLE} RF Secret type requires Robot Framework 7.4+ + ${pw}= Make Secret must-not-leak + Input Password password_field ${pw} + +*** Keywords *** +Open Browser To Start Page Disabling Chrome Leaked Password Detection + [Arguments] ${alias}=${None} + Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} + ... options=add_experimental_option("prefs", {"profile.password_manager_leak_detection": False}) alias=${alias} \ No newline at end of file diff --git a/atest/resources/secret_helper.py b/atest/resources/secret_helper.py new file mode 100644 index 000000000..ced7b0239 --- /dev/null +++ b/atest/resources/secret_helper.py @@ -0,0 +1,21 @@ +"""Test helper for building Secret objects in acceptance tests. + +Robot Framework's Secret type is only available in RF 7.4+. This helper +exposes ``Make Secret`` plus a ``SECRET_AVAILABLE`` boolean so tests can +skip cleanly on older versions. +""" + +try: + from robot.api.types import Secret + + SECRET_AVAILABLE = True +except ImportError: # RF < 7.4 + Secret = None # type: ignore[assignment,misc] + SECRET_AVAILABLE = False + + +def make_secret(value: str): + """Wrap *value* in a Robot Framework Secret object.""" + if Secret is None: + raise RuntimeError("Secret type requires Robot Framework 7.4+") + return Secret(value) \ No newline at end of file diff --git a/src/SeleniumLibrary/keywords/formelement.py b/src/SeleniumLibrary/keywords/formelement.py index cbf774190..7141a623d 100644 --- a/src/SeleniumLibrary/keywords/formelement.py +++ b/src/SeleniumLibrary/keywords/formelement.py @@ -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): @@ -238,7 +238,7 @@ 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 diff --git a/src/SeleniumLibrary/utils/types.py b/src/SeleniumLibrary/utils/types.py index e12657d53..0d9d1879d 100644 --- a/src/SeleniumLibrary/utils/types.py +++ b/src/SeleniumLibrary/utils/types.py @@ -20,9 +20,27 @@ from robot.utils import is_falsy, is_truthy, timestr_to_secs # noqa from selenium.webdriver.remote.webelement import WebElement -# Need only for unit tests and can be removed when Approval tests fixes: -# https://github.com/approvals/ApprovalTests.Python/issues/41 -WINDOWS = os.name == "nt" +try: + from robot.api.types import Secret +except ImportError: + # Robot Framework < 7.4 does not have the Secret type. + # This shim lets users on older RF versions still import it without + # errors. It can be removed once RF 7.4+ is the minimum requirement. + class Secret: # type: ignore[no-redef] + """Backport shim for ``robot.api.types.Secret`` (RF 7.4+). + + Mirrors the public interface of the real class so that ``isinstance`` + checks and ``.value`` access work uniformly across RF versions. + """ + + def __init__(self, value: str): + self.value = value + + def __str__(self) -> str: + return "" + + def __repr__(self) -> str: + return f"{type(self).__name__}(value=)" Locator: TypeAlias = WebElement | str | list["Locator"] From 42b0e6bac4f18884b0a3333aef2a950d5ed95a92 Mon Sep 17 00:00:00 2001 From: Yuri Verweij Date: Wed, 6 May 2026 07:18:30 +0200 Subject: [PATCH 2/5] moved test back with other textfield tests. Fixed bug, not using value of secret, but --- atest/acceptance/keywords/textfields.robot | 42 ++++++++++++++++- .../keywords/textfields_secret.robot | 47 ------------------- atest/resources/secret_helper.py | 21 --------- atest/resources/testlibs/secret_helper.py | 16 +++++++ src/SeleniumLibrary/keywords/element.py | 11 ++--- src/SeleniumLibrary/keywords/formelement.py | 8 ++-- src/SeleniumLibrary/utils/types.py | 13 ++--- 7 files changed, 74 insertions(+), 84 deletions(-) delete mode 100644 atest/acceptance/keywords/textfields_secret.robot delete mode 100644 atest/resources/secret_helper.py create mode 100644 atest/resources/testlibs/secret_helper.py diff --git a/atest/acceptance/keywords/textfields.robot b/atest/acceptance/keywords/textfields.robot index b8f225e1a..c64e6d447 100644 --- a/atest/acceptance/keywords/textfields.robot +++ b/atest/acceptance/keywords/textfields.robot @@ -2,6 +2,7 @@ Suite Setup Open Browser To Start Page Disabling Chrome Leaked Password Detection Test Setup Go To Page "forms/prefilled_email_form.html" Resource ../resource.robot +Library ../../resources/testlibs/secret_helper.py Force Tags Known Issue Internet Explorer *** Test Cases *** @@ -77,6 +78,45 @@ 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" + Skip If No Secret + 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" + Skip If No Secret + 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 2:1 INFO Typing password into text field 'password_field'. + Skip If No Secret + 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 @@ -87,4 +127,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 \ No newline at end of file + END diff --git a/atest/acceptance/keywords/textfields_secret.robot b/atest/acceptance/keywords/textfields_secret.robot deleted file mode 100644 index ae1c15d8d..000000000 --- a/atest/acceptance/keywords/textfields_secret.robot +++ /dev/null @@ -1,47 +0,0 @@ -*** Settings *** -Documentation Verifies Input Text and Input Password accept Robot Framework -... 7.4+ Secret type without ValueError. See issue #1966. -Suite Setup Open Browser To Start Page Disabling Chrome Leaked Password Detection -Test Setup Go To Page "forms/login.html" -Resource ../resource.robot -Library ${CURDIR}/../../resources/secret_helper.py -Force Tags Known Issue Internet Explorer - -*** Test Cases *** -Input Password Accepts Secret Type - [Tags] require-rf-7.4 - Skip If not ${SECRET_AVAILABLE} RF Secret type requires Robot Framework 7.4+ - ${pw}= Make Secret s3cret-pass - Input Text username_field yuri - 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 - Skip If not ${SECRET_AVAILABLE} RF Secret type requires Robot Framework 7.4+ - ${user}= Make Secret yuri - Input Text username_field ${user} - ${value}= Get Value username_field - Should Be Equal ${value} yuri - -Input Password With Plain String Still Works - [Documentation] Backwards compatibility — plain str must still be accepted. - Input Text username_field yuri - 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 - [Documentation] - ... LOG 2:1 INFO Typing password into text field 'password_field'. - Skip If not ${SECRET_AVAILABLE} RF Secret type requires Robot Framework 7.4+ - ${pw}= Make Secret must-not-leak - Input Password password_field ${pw} - -*** Keywords *** -Open Browser To Start Page Disabling Chrome Leaked Password Detection - [Arguments] ${alias}=${None} - Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} - ... options=add_experimental_option("prefs", {"profile.password_manager_leak_detection": False}) alias=${alias} \ No newline at end of file diff --git a/atest/resources/secret_helper.py b/atest/resources/secret_helper.py deleted file mode 100644 index ced7b0239..000000000 --- a/atest/resources/secret_helper.py +++ /dev/null @@ -1,21 +0,0 @@ -"""Test helper for building Secret objects in acceptance tests. - -Robot Framework's Secret type is only available in RF 7.4+. This helper -exposes ``Make Secret`` plus a ``SECRET_AVAILABLE`` boolean so tests can -skip cleanly on older versions. -""" - -try: - from robot.api.types import Secret - - SECRET_AVAILABLE = True -except ImportError: # RF < 7.4 - Secret = None # type: ignore[assignment,misc] - SECRET_AVAILABLE = False - - -def make_secret(value: str): - """Wrap *value* in a Robot Framework Secret object.""" - if Secret is None: - raise RuntimeError("Secret type requires Robot Framework 7.4+") - return Secret(value) \ No newline at end of file diff --git a/atest/resources/testlibs/secret_helper.py b/atest/resources/testlibs/secret_helper.py new file mode 100644 index 000000000..b8754374c --- /dev/null +++ b/atest/resources/testlibs/secret_helper.py @@ -0,0 +1,16 @@ +"""Test helper for Secret type acceptance tests.""" + +try: + from robot.api import types as _robot_api_types + + _SECRET_AVAILABLE = hasattr(_robot_api_types, "Secret") +except ImportError: # RF < 7.4 + _SECRET_AVAILABLE = False + + +def skip_if_no_secret(): + """Skip the current test if ``robot.api.types.Secret`` is unavailable (RF < 7.4).""" + if not _SECRET_AVAILABLE: + from robot.libraries.BuiltIn import BuiltIn + + BuiltIn().skip("RF Secret type requires Robot Framework 7.4+") diff --git a/src/SeleniumLibrary/keywords/element.py b/src/SeleniumLibrary/keywords/element.py index e08187646..4d482359b 100644 --- a/src/SeleniumLibrary/keywords/element.py +++ b/src/SeleniumLibrary/keywords/element.py @@ -1293,14 +1293,13 @@ def get_css_property_value(self, locator: Locator, css_property: str) -> str: """ return self.find_element(locator).value_of_css_property(css_property) - @keyword("Drag And Drop Across Frames") def drag_and_drop_across_frames( - self, - locator: Locator, - target: Locator, - target_frame: Locator, - source_frame: Locator | None = None, + self, + locator: Locator, + target: Locator, + target_frame: Locator, + source_frame: Locator | None = None, ) -> None: """ Drags an element and drops it onto a target element across frame boundaries. diff --git a/src/SeleniumLibrary/keywords/formelement.py b/src/SeleniumLibrary/keywords/formelement.py index 7bf8aa6f0..ca805c9da 100644 --- a/src/SeleniumLibrary/keywords/formelement.py +++ b/src/SeleniumLibrary/keywords/formelement.py @@ -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 | Secret, 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 @@ -266,7 +268,7 @@ def input_password(self, locator: Locator, password: str | Secret, clear: bool = 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 @@ -502,7 +504,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) diff --git a/src/SeleniumLibrary/utils/types.py b/src/SeleniumLibrary/utils/types.py index 5e1944687..c2d9edc82 100644 --- a/src/SeleniumLibrary/utils/types.py +++ b/src/SeleniumLibrary/utils/types.py @@ -22,14 +22,14 @@ try: from robot.api.types import Secret except ImportError: - # Robot Framework < 7.4 does not have the Secret type. - # This shim lets users on older RF versions still import it without - # errors. It can be removed once RF 7.4+ is the minimum requirement. + # 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] - """Backport shim for ``robot.api.types.Secret`` (RF 7.4+). + """Stand-in for ``robot.api.types.Secret`` on Robot Framework < 7.4. - Mirrors the public interface of the real class so that ``isinstance`` - checks and ``.value`` access work uniformly across RF versions. + 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): @@ -41,6 +41,7 @@ def __str__(self) -> str: def __repr__(self) -> str: return f"{type(self).__name__}(value=)" + Locator: TypeAlias = WebElement | str | list["Locator"] From b2e8aed9d44e6ed41f3a22ab5662f040b71d1deb Mon Sep 17 00:00:00 2001 From: Yuri Verweij Date: Wed, 6 May 2026 15:20:47 +0200 Subject: [PATCH 3/5] Enhance password handling documentation for Secret type support in Input Password keyword --- src/SeleniumLibrary/keywords/formelement.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/SeleniumLibrary/keywords/formelement.py b/src/SeleniumLibrary/keywords/formelement.py index ca805c9da..2706a164d 100644 --- a/src/SeleniumLibrary/keywords/formelement.py +++ b/src/SeleniumLibrary/keywords/formelement.py @@ -258,8 +258,15 @@ def input_password( | 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. @@ -276,6 +283,12 @@ def input_text(self, locator: Locator, text: str | Secret, clear: bool = True): 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 From c25e213d94a43c001cbceefe3cdb3be17af9628b Mon Sep 17 00:00:00 2001 From: Yuri Verweij Date: Wed, 6 May 2026 15:38:31 +0200 Subject: [PATCH 4/5] Remove secret_helper.py and update tests to exclude require-rf-7.4 for compatibility with older Robot Framework versions --- atest/acceptance/keywords/textfields.robot | 6 +----- atest/resources/testlibs/secret_helper.py | 16 ---------------- atest/run.py | 3 +++ 3 files changed, 4 insertions(+), 21 deletions(-) delete mode 100644 atest/resources/testlibs/secret_helper.py diff --git a/atest/acceptance/keywords/textfields.robot b/atest/acceptance/keywords/textfields.robot index c64e6d447..4f97790b7 100644 --- a/atest/acceptance/keywords/textfields.robot +++ b/atest/acceptance/keywords/textfields.robot @@ -2,7 +2,6 @@ Suite Setup Open Browser To Start Page Disabling Chrome Leaked Password Detection Test Setup Go To Page "forms/prefilled_email_form.html" Resource ../resource.robot -Library ../../resources/testlibs/secret_helper.py Force Tags Known Issue Internet Explorer *** Test Cases *** @@ -81,7 +80,6 @@ Attempt Clear Element Text On Non-Editable Field Input Password Accepts Secret Type [Tags] require-rf-7.4 [Setup] Go To Page "forms/login.html" - Skip If No Secret Set Environment Variable TEST_PASSWORD s3cret-pass VAR ${pw: Secret} %{TEST_PASSWORD} Input Text username_field my_username @@ -92,7 +90,6 @@ Input Password Accepts Secret Type Input Text Accepts Secret Type [Tags] require-rf-7.4 [Setup] Go To Page "forms/login.html" - Skip If No Secret Set Environment Variable TEST_USERNAME my_username VAR ${user: Secret} %{TEST_USERNAME} Input Text username_field ${user} @@ -111,8 +108,7 @@ Input Password Does Not Log Secret Value [Tags] require-rf-7.4 NoGrid [Setup] Go To Page "forms/login.html" [Documentation] - ... LOG 2:1 INFO Typing password into text field 'password_field'. - Skip If No Secret + ... LOG 4: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} diff --git a/atest/resources/testlibs/secret_helper.py b/atest/resources/testlibs/secret_helper.py deleted file mode 100644 index b8754374c..000000000 --- a/atest/resources/testlibs/secret_helper.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Test helper for Secret type acceptance tests.""" - -try: - from robot.api import types as _robot_api_types - - _SECRET_AVAILABLE = hasattr(_robot_api_types, "Secret") -except ImportError: # RF < 7.4 - _SECRET_AVAILABLE = False - - -def skip_if_no_secret(): - """Skip the current test if ``robot.api.types.Secret`` is unavailable (RF < 7.4).""" - if not _SECRET_AVAILABLE: - from robot.libraries.BuiltIn import BuiltIn - - BuiltIn().skip("RF Secret type requires Robot Framework 7.4+") diff --git a/atest/run.py b/atest/run.py index 637e86547..b0e2de2ba 100755 --- a/atest/run.py +++ b/atest/run.py @@ -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 @@ -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 += [ From 56ca2af307e4a0401e548222c5f4809b1c8751d7 Mon Sep 17 00:00:00 2001 From: Yuri Verweij Date: Wed, 6 May 2026 15:46:55 +0200 Subject: [PATCH 5/5] Fix log line number in Input Password Does Not Log Secret Value test case --- atest/acceptance/keywords/textfields.robot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atest/acceptance/keywords/textfields.robot b/atest/acceptance/keywords/textfields.robot index 4f97790b7..b957b1f97 100644 --- a/atest/acceptance/keywords/textfields.robot +++ b/atest/acceptance/keywords/textfields.robot @@ -108,7 +108,7 @@ Input Password Does Not Log Secret Value [Tags] require-rf-7.4 NoGrid [Setup] Go To Page "forms/login.html" [Documentation] - ... LOG 4:1 INFO Typing password into text field 'password_field'. + ... 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}