robotframework/PythonRemoteServer

Error importing SeleniumLibrary

hskn opened this issue · 5 comments

hskn commented

As a background, I have to use .net Robot Framework, because some complex testing libraries are .net based. So my idea was to use robotremoteserver to also utilize SeleniumLibrary (not supported by .net rf).

import sys
from robotremoteserver import RobotRemoteServer
from SeleniumLibrary import SeleniumLibrary

RobotRemoteServer(SeleniumLibrary())

This yields

RobotRemoteServer(SeleniumLibrary()) File "/usr/local/lib/python3.7/site-packages/robotremoteserver.py", line 73, in __init__ self._library = RemoteLibraryFactory(library) File "/usr/local/lib/python3.7/site-packages/robotremoteserver.py", line 259, in RemoteLibraryFactory return DynamicRemoteLibrary(library, get_keyword_names, run_keyword) File "/usr/local/lib/python3.7/site-packages/robotremoteserver.py", line 362, in __init__ HybridRemoteLibrary.__init__(self, library, get_keyword_names) File "/usr/local/lib/python3.7/site-packages/robotremoteserver.py", line 355, in __init__ StaticRemoteLibrary.__init__(self, library) File "/usr/local/lib/python3.7/site-packages/robotremoteserver.py", line 280, in __init__ self._names, self._robot_name_index = self._get_keyword_names(library) File "/usr/local/lib/python3.7/site-packages/robotremoteserver.py", line 285, in _get_keyword_names for name, kw in inspect.getmembers(library): File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/inspect.py", line 341, in getmembers value = getattr(object, key) File "/usr/local/lib/python3.7/site-packages/SeleniumLibrary/__init__.py", line 415, in driver raise NoOpenBrowser('No browser is open.') SeleniumLibrary.errors.NoOpenBrowser: No browser is open.

Similar error also with python 2.7.
According to Tatu Aaltonen and Pekka Klärck, there's a bug in remote server.

There are actually two bugs here: blindly using inspect.getmembers() with static libraries and the fact that this method is called at all with hybrid and dynamic libraries such as SeleniumLibrary. I explain them below.

= Using inspect.getmembers() with static libraries =

StaticRemoteLibrary uses inspect.getmembers(library) to find out what attributes the library has to check which of those attributes are actually method implementing keywords. The problem is that this method blindly accesses all properties of the library instance and SeleniunLibrary has some properties that can be successfully used only if a browser is open.

The problem can be avoided by not using inspect.getmembers() and instead getting attributes defined in the class (like properties) directly from the class, not from the instance. Both Robot Framework itself and PythonLibCore project do that and the same approach as used by the latter would probably work here as well.

One thing to take into account is that is that PythonLibCore only allows using new style classes (and modules) as libraries but there are no such restriction here. Thus the exact code linked above doesn't work, but using library.__class__ instead of type(instance) with old style classes ought to fix that.

= Hybdic and dynamic libraries calling inspect.getmembers() =

SeleniumLibrary is a dynamic library and they, similarly as hybrid libraries, expose their keywords using get_keyword_names. With them there's thus no need to get attributes from the library instance in the first place.

This problem happens because DynamicRemoteLibrary inherits HybridRemoteLibrary which inherits StaticRemoteLibrary, and StaticRemoteLibrary ends up calling inspect.getmembers() in its __init__. It's somewhat questionable is this whole inheritance hierarchy useful, but at least code should be changed so that attributes are only accessed with static libraries.

Because there are two bugs, we probably should also have two bug reports. Fixing the problem with inspect.getmembers() would fix the issue with SeleniumLibrary, but avoiding calling that method in the first place with hybrid and dynamic libraries would be even better fix.

281c282,283
<         self._names, self._robot_name_index = self._get_keyword_names(library)
---
>         self._names = None
>         self._robot_name_index = None
283c285
<     def _get_keyword_names(self, library):
---
>     def _construct_keyword_names(self):
286c288
<         for name, kw in inspect.getmembers(library):
---
>         for name, kw in inspect.getmembers(self._library):
295a298,299
>         if self._names is None:
>             self._names, self._robot_name_index = self._construct_keyword_names()
302a307,308
>         if self._names is None:
>             self._names, self._robot_name_index = self._construct_keyword_names()
378c384
<         args = [name, args, kwargs] if kwargs else [name, args]
---
>         args = [name, args, kwargs] if kwargs else [name, args, {}]

Thats the changes I did to make dynamic libs work.