Three listener hooks compare the library name against the literal string "Browser":
Browser/browser.py:935 — or attrs["libname"] != "Browser" in _start_keyword, gating self._show_keyword_call(attrs)
Browser/browser.py:939 — if "secret" in attrs["kwname"].lower() and attrs["libname"] == "Browser": → self._set_logging(False)
Browser/browser.py:1031 — the same condition in _end_keyword, restoring the log level
The listener itself is registered as self (Browser/browser.py:509), so the hooks do fire in these cases — only the name comparison fails.
That comparison is false in two ordinary situations:
Library Browser AS PW
- a user library built by subclassing
Browser, imported under its own name
1. Keyword-call banner is silently a no-op — confirmed
The banner is a <style id="kwCallBanner"> element injected into <head> (KW_CALL_BANNER_FUNCTION, Browser/browser.py:109). Same suite, same resource file, show_keyword_call_banner=True, real robot runs, webkit headless, probed with Get Element Count css=style#kwCallBanner:
| Import |
Banner element count |
Library Browser |
1 — textContent starts body::before {\n content: 'Get Property css=style#kwCallBanner textContent'; |
Library Browser AS PW |
0 |
Library MyLib where class MyLib(Browser) |
0 |
The element is never created. Nothing appears in the page, in videos, in traces, or in the log, and no warning is issued — show_keyword_call_banner=True and Enable Presenter Mode simply do nothing for anyone using an alias or a subclass.
2. Secret log suppression is skipped — but no secret was disclosed
The _set_logging suppression at :939 / :1031 genuinely does not run for those imports. Observed in output.xml for the same three keywords and arguments:
Library Browser: -- Fill Secret owner=Browser args=['id=pw', '$PWD'] (no messages logged)
Library MyLib: -- Fill Secret owner=MyLib args=['id=pw', '$PWD']
[ TRACE ] Arguments: [ 'id=pw' | '$PWD' ]
[ TRACE ] Presenter mode: False, selector: id=pw, strict: True
[ TRACE ] Return: None
The secret value itself never appeared in clear text in either run. Grepping output.xml and log.html for both secret values, at TRACE:INFO, gave 0 occurrences in every configuration, including a forced failure path (Fill Secret against a missing selector, so the timeout error and the on-failure screenshot were exercised).
Three independent defences already keep the value out: the Robot Framework argument is the unresolved literal $PWD / %ENV (resolution happens inside resolve_secret, Browser/base/librarycomponent.py:243), an RF 7.4 Secret renders as Secret(value=<secret>), and fill_secret / type_secret pass log_response=False (Browser/keywords/interaction.py:846). _set_logging is belt-and-braces on top of those.
So this half is a consistency and robustness problem rather than a disclosure: any future code path that does log a resolved secret would be unprotected for aliased and subclassed imports. Worth noting that losing the suppression also changes the failure output in the other direction — plain Browser swallows the timeout traceback entirely, while the aliased/subclassed import shows it.
Suggested fix
Compare identity rather than the name — resolve the library instance for attrs["libname"] and check isinstance(instance, Browser), or compare against the instance's own resolved name — so aliases and subclasses behave like Browser. Browser/keywords/waiter.py:299-304 already does the isinstance form for Wait For Condition and can serve as the pattern.
Environment
Browser 20.3.0 (3b3ec7a9), Robot Framework 7.4.1, Python 3.14.7, macOS, webkit.
Three listener hooks compare the library name against the literal string
"Browser":Browser/browser.py:935—or attrs["libname"] != "Browser"in_start_keyword, gatingself._show_keyword_call(attrs)Browser/browser.py:939—if "secret" in attrs["kwname"].lower() and attrs["libname"] == "Browser":→self._set_logging(False)Browser/browser.py:1031— the same condition in_end_keyword, restoring the log levelThe listener itself is registered as
self(Browser/browser.py:509), so the hooks do fire in these cases — only the name comparison fails.That comparison is false in two ordinary situations:
Library Browser AS PWBrowser, imported under its own name1. Keyword-call banner is silently a no-op — confirmed
The banner is a
<style id="kwCallBanner">element injected into<head>(KW_CALL_BANNER_FUNCTION,Browser/browser.py:109). Same suite, same resource file,show_keyword_call_banner=True, realrobotruns, webkit headless, probed withGet Element Count css=style#kwCallBanner:Library BrowsertextContentstartsbody::before {\n content: 'Get Property css=style#kwCallBanner textContent';Library Browser AS PWLibrary MyLibwhereclass MyLib(Browser)The element is never created. Nothing appears in the page, in videos, in traces, or in the log, and no warning is issued —
show_keyword_call_banner=TrueandEnable Presenter Modesimply do nothing for anyone using an alias or a subclass.2. Secret log suppression is skipped — but no secret was disclosed
The
_set_loggingsuppression at:939/:1031genuinely does not run for those imports. Observed inoutput.xmlfor the same three keywords and arguments:The secret value itself never appeared in clear text in either run. Grepping
output.xmlandlog.htmlfor both secret values, atTRACE:INFO, gave 0 occurrences in every configuration, including a forced failure path (Fill Secretagainst a missing selector, so the timeout error and the on-failure screenshot were exercised).Three independent defences already keep the value out: the Robot Framework argument is the unresolved literal
$PWD/%ENV(resolution happens insideresolve_secret,Browser/base/librarycomponent.py:243), an RF 7.4Secretrenders asSecret(value=<secret>), andfill_secret/type_secretpasslog_response=False(Browser/keywords/interaction.py:846)._set_loggingis belt-and-braces on top of those.So this half is a consistency and robustness problem rather than a disclosure: any future code path that does log a resolved secret would be unprotected for aliased and subclassed imports. Worth noting that losing the suppression also changes the failure output in the other direction — plain
Browserswallows the timeout traceback entirely, while the aliased/subclassed import shows it.Suggested fix
Compare identity rather than the name — resolve the library instance for
attrs["libname"]and checkisinstance(instance, Browser), or compare against the instance's own resolved name — so aliases and subclasses behave likeBrowser.Browser/keywords/waiter.py:299-304already does theisinstanceform forWait For Conditionand can serve as the pattern.Environment
Browser 20.3.0 (
3b3ec7a9), Robot Framework 7.4.1, Python 3.14.7, macOS, webkit.