Skip to content

Commit c23556e

Browse files
authored
Merge pull request #1985 from yuriverweij/feature/add-secret-type
Feature/add secret type
2 parents 91700c4 + 5bbfb6e commit c23556e

4 files changed

Lines changed: 91 additions & 8 deletions

File tree

atest/acceptance/keywords/textfields.robot

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,42 @@ Press Key
7777
Attempt Clear Element Text On Non-Editable Field
7878
Run Keyword And Expect Error * Clear Element Text can_send_email
7979

80+
Input Password Accepts Secret Type
81+
[Tags] require-rf-7.4
82+
[Setup] Go To Page "forms/login.html"
83+
Set Environment Variable TEST_PASSWORD s3cret-pass
84+
VAR ${pw: Secret} %{TEST_PASSWORD}
85+
Input Text username_field my_username
86+
Input Password password_field ${pw}
87+
${value}= Get Value password_field
88+
Should Be Equal ${value} s3cret-pass
89+
90+
Input Text Accepts Secret Type
91+
[Tags] require-rf-7.4
92+
[Setup] Go To Page "forms/login.html"
93+
Set Environment Variable TEST_USERNAME my_username
94+
VAR ${user: Secret} %{TEST_USERNAME}
95+
Input Text username_field ${user}
96+
${value}= Get Value username_field
97+
Should Be Equal ${value} my_username
98+
99+
Input Password With Plain String Still Works
100+
[Setup] Go To Page "forms/login.html"
101+
[Documentation] Backwards compatibility — plain str must still be accepted.
102+
Input Text username_field my_username
103+
Input Password password_field plain-pass
104+
${value}= Get Value password_field
105+
Should Be Equal ${value} plain-pass
106+
107+
Input Password Does Not Log Secret Value
108+
[Tags] require-rf-7.4 NoGrid
109+
[Setup] Go To Page "forms/login.html"
110+
[Documentation]
111+
... LOG 3:1 INFO Typing password into text field 'password_field'.
112+
Set Environment Variable TEST_PASSWORD must-not-leak
113+
VAR ${pw: Secret} %{TEST_PASSWORD}
114+
Input Password password_field ${pw}
115+
80116
*** Keywords ***
81117

82118
Open Browser To Start Page Disabling Chrome Leaked Password Detection
@@ -87,4 +123,4 @@ Open Browser To Start Page Disabling Chrome Leaked Password Detection
87123
... options=add_experimental_option("prefs", {"profile.password_manager_leak_detection": False}) alias=${alias}
88124
ELSE
89125
Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} alias=${alias}
90-
END
126+
END

atest/run.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import zipfile
4444
from contextlib import contextmanager
4545
import os
46+
import re
4647
import sys
4748
import argparse
4849
import textwrap
@@ -223,6 +224,11 @@ def execute_tests(interpreter, browser, rf_options, grid, event_firing, port):
223224
"--exclude",
224225
"triage",
225226
]
227+
rf_ver = tuple(
228+
int(re.match(r"\d+", x).group()) for x in robot_version.split(".")[:2]
229+
)
230+
if rf_ver < (7, 4):
231+
options.extend(["--exclude", "require-rf-7.4"])
226232
command = runner
227233
if grid:
228234
command += [

src/SeleniumLibrary/keywords/formelement.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
from SeleniumLibrary.base import LibraryComponent, keyword
2222
from SeleniumLibrary.errors import ElementNotFound
23-
from SeleniumLibrary.utils.types import Locator
23+
from SeleniumLibrary.utils.types import Locator, Secret
2424

2525

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

240240
@keyword
241-
def input_password(self, locator: Locator, password: str, clear: bool = True):
241+
def input_password(
242+
self, locator: Locator, password: str | Secret, clear: bool = True
243+
):
242244
"""Types the given password into the text field identified by ``locator``.
243245
244246
See the `Locating elements` section for details about the locator
@@ -256,8 +258,15 @@ def input_password(self, locator: Locator, password: str, clear: bool = True):
256258
| Input Password | password_field | ${PASSWORD} |
257259
258260
Please notice that Robot Framework logs all arguments using
259-
the TRACE level and tests must not be executed using level below
260-
DEBUG if the password should not be logged in any format.
261+
the TRACE level. When not using the ``Secret`` type, tests must
262+
not be executed using level below DEBUG if the password should
263+
not be logged in any format.
264+
265+
This keyword supports Robot Framework 7.4
266+
[https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#secret-variables|Secret]
267+
variable type. When a ``Secret`` is passed, the value is protected
268+
from Robot Framework logs and Selenium internal logging is also
269+
suppressed during typing.
261270
262271
The `clear` argument is new in SeleniumLibrary 4.0. Hiding password
263272
logging from Selenium logs is new in SeleniumLibrary 4.2.
@@ -266,14 +275,21 @@ def input_password(self, locator: Locator, password: str, clear: bool = True):
266275
self._input_text_into_text_field(locator, password, clear, disable_log=True)
267276

268277
@keyword
269-
def input_text(self, locator: Locator, text: str, clear: bool = True):
278+
def input_text(self, locator: Locator, text: str | Secret, clear: bool = True):
270279
"""Types the given ``text`` into the text field identified by ``locator``.
271280
272281
When ``clear`` is true, the input element is cleared before
273282
the text is typed into the element. When false, the previous text
274283
is not cleared from the element. Use `Input Password` if you
275284
do not want the given ``text`` to be logged.
276285
286+
This keyword supports Robot Framework 7.4
287+
[https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#secret-variables|Secret]
288+
variable type. When a ``Secret`` is passed, the value is masked in
289+
Robot Framework logs and Selenium's internal logs are suppressed during
290+
typing. When a plain string is passed, Selenium's internal logs are not
291+
suppressed.
292+
277293
If [https://github.com/SeleniumHQ/selenium/wiki/Grid2|Selenium Grid]
278294
is used and the ``text`` argument points to a file in the file system,
279295
then this keyword prevents the Selenium to transfer the file to the
@@ -289,7 +305,9 @@ def input_text(self, locator: Locator, text: str, clear: bool = True):
289305
argument are new in SeleniumLibrary 4.0
290306
"""
291307
self.info(f"Typing text '{text}' into text field '{locator}'.")
292-
self._input_text_into_text_field(locator, text, clear)
308+
self._input_text_into_text_field(
309+
locator, text, clear, disable_log=isinstance(text, Secret)
310+
)
293311

294312
@keyword
295313
def page_should_contain_textfield(
@@ -502,7 +520,7 @@ def _input_text_into_text_field(self, locator, text, clear=True, disable_log=Fal
502520
self.info("Temporally setting log level to: NONE")
503521
previous_level = BuiltIn().set_log_level("NONE")
504522
try:
505-
element.send_keys(text)
523+
element.send_keys(text.value if isinstance(text, Secret) else text)
506524
finally:
507525
if disable_log:
508526
BuiltIn().set_log_level(previous_level)

src/SeleniumLibrary/utils/types.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,29 @@
1919
from robot.utils import is_falsy, is_truthy, timestr_to_secs # noqa
2020
from selenium.webdriver.remote.webelement import WebElement
2121

22+
try:
23+
from robot.api.types import Secret
24+
except ImportError:
25+
# Secret was introduced in Robot Framework 7.4. On older versions we
26+
# provide a minimal stand-in so that the type hint ``str | Secret`` and
27+
# ``isinstance`` checks work without requiring an upgrade.
28+
class Secret: # type: ignore[no-redef]
29+
"""Stand-in for ``robot.api.types.Secret`` on Robot Framework < 7.4.
30+
31+
Exposes the same ``.value`` attribute and masked string representation
32+
as the real class, so keyword code can treat both identically.
33+
"""
34+
35+
def __init__(self, value: str):
36+
self.value = value
37+
38+
def __str__(self) -> str:
39+
return "<secret>"
40+
41+
def __repr__(self) -> str:
42+
return f"{type(self).__name__}(value=<secret>)"
43+
44+
2245
Locator: TypeAlias = WebElement | str | list["Locator"]
2346

2447

0 commit comments

Comments
 (0)