Context:
We recently switched to “RobotCode” extension and the linter reports a lot of errors when initializing our Python libraries: “RobotNotRunningError”, “Variable not found” when using unpacked arguments, and much more…
We fully understand the meaning of those errors but it wouldn’t be relevant to fix some of them (unpacked settings, for instance) because:
Some Python libraries must stay independant from Robot Framework.
Some errors are not related to bad scripting mistakes. Fix them would mean that we modify a lot of our our code only to fit with RobotCode linter.
Since all those errors are only raised by the linter and are never encountered while running tests, we decided to set the below option and use only autocompletion:
Issue:
With or without the “ignore *” option: when the linter detects an error during library initialization the autocompletion is disabled for this library.
My question:
Is it possible to keep the autocompletion enabled for libraries keywords, even when “linter-only” errors are encountered during libraries initialization.
If not, could it be relevant to add this as a feature in RobotCode ?
Setup:
VSCode: 1.97.2
Robot Framework: 7.0
Python: 3.12.3
RobotCode: 0.109.3
Please refer to attached screenshot for more information.
If linter/lsp is causing “RobotNotRunning” exception, i’d say its a bug on how that particular library is implemented and I would strongly advice to fix that.
Point being, since the library can have dynamic behaviour at construction time to define what keywords it would pass to robot framework, LSP needs to instantiate the library. In your case, your implementation most likely does something like BuiltIn().get_library_instance() or similar and if robot is not running, exception is thrown. Fix for this should be something like lazy evaluation that the instance is fetched only before it is really needed, not during the call to the constructor.
In past, i’ve used a decorator for the actual keyword to do the init of the required variables.
@rasjani basically mentioned it all. I want to highlight further aspects.
For autocompletion, the keywords must be known. For the keywords to be known, the library has to instantiated by Robotcode. And if the library is missing a parameter, then there is no instance and then tere are no keywords to be suggested.
Since you mentioned that you switchted to Robotcode: other plugins (like the deprecated LSP from Robocorp) don’t use the Robot Framework parser in their backends, but do (mostly correct) guessing. Robotcode uses actual Robot Framework in its backend. So “fixing” Robotcode in that sense would make it actually more error prone as it would require its own parser that has to magically be in sync with the original.
And it’s not only Robotcode that uses Robot Framework backend. libdoc does, too. So you probably cannot generate keyword documentation from your libraries either, when they have mandatory arguments in their constructor.
So although you mentioned it is much work to fix the libraries, i want to highlight there are more reasons than only linting. Robot Framework actually expects that libraries can be instantiated with a default constructor and if they fail, then Robotcode is only one of the tools that can’t work with them.
First of all, thank you very much for your quick and detailed answers.
Concerning the “RobotNotRunning” exception, we are aware that we should fix it…and we will, since those libraries are already Robot Framework dependant.
However, I would like to specify that the “RobotNotRunning” error is NOT my point here. It was only an example of the errors we are experiencing among multiple.
Please find attached screenshots of 2 very basic kind of error that we are experiencing and that breaks our autocompletion. I reduced one of our libraries to as few lines as possible to really show it is not coming from our way of scripting:
Microsoft’s playwright start() method raises an error. This error is not reported in Pyhon linter. I am aware that Browser exists, but we won’t use it for specific technical reasons.
Importing variables from yaml file raises an error during library initialization. If I remove all keywords from the library, the “no keywords in library” warning takes priority and the error is not reported anymore.
You initialize Playwright directly in the constructor. Playwright Python then tries to start a Node process with the real Playwright, and among other things, it attempts to obtain a fileno from the stderr stream. Since RobotCode has already captured stderr at the time of the call, this approach fails. (Perhaps this could be handled differently—I’ll make a note of it.)
The major problem is that a Robot Framework Library isn’t merely parsed and converted into executable code—a Robot Framework Library must be instantiated. In your case, Robot starts a Playwright instance when it encounters the Library statement in the settings. However, a good constructor should only initialize a class (i.e., set properties/fields and perform simple tasks) rather than execute complex operations such as establishing network connections or starting Playwright. I don’t have a specific reference at hand, but you can look up best practices online or consult an AI on what constitutes a well-designed constructor.
I recommend dynamically initializing the Playwright instance so that it is only instantiated when actually needed. For example:
from playwright.sync_api import sync_playwright
from functools import cached_property
class Playwright:
ROBOT_LIBRARY_SCOPE = 'SUITE'
def __init__(self):
# perform simple initialization here
pass
@cached_property
def playwright(self):
return sync_playwright().start()
@cached_property
def browser(self):
return self.playwright.chromium.launch(headless=False)
@cached_property
def page(self):
return self.browser.new_page()
def goto(self, url):
self.page.goto(url)
This approach avoids any dependencies on robot in your code.
The cached_property decorator provides a straightforward way to delay instantiation until the property is actually accessed. Alternatively, you could use a more manual approach:
class Playwright:
ROBOT_LIBRARY_SCOPE = 'SUITE'
def __init__(self):
self._playwright = None
pass
@property
def playwright(self):
if self._playwright is None:
self._playwright = sync_playwright().start()
return self._playwright
I hope this clarifies the concept and helps you improve your implementation.
And just out of curiosity, which features do you miss in the Browser Library?
Thank you for your advice. I tried some of them and I was able to remove two of the errors. I guess most of them are related to constructor implementation.
I wouldn’t say everything is solved, because we’ll need more time to know but it is a good start !
About Browser, this is not related to Browser itself. It is about Robot Framework.
In short, we have several automation teams that work on different projects but they have to keep the same tools.
Some teams have very complex projects and pure Python with Playwright is more adapted for Page Object Model implementation in those kinds of projects.
Some teams want to keep Robot Framework because it is easier and more “QA testers-friendly”.
“Pure Python” teams should be able to move to pure Python anytime when they need to. That’s why we removed Robot Framework dependency from most of our libraries.