You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Document how to tweak the library without touching the library #91
I've seen several people who want to modify the library without touching the library source but by possibly overwriting the library. Not sure if this is best practice or we should even recommend this. But I wanted to note this as something to investigate and document (whether that be how to do this or recommendation to not do this).
This is what I do for my current project. I simply create a new class and inherit from Selenium2Library that way I can add / overwrite whatever I like. Then from the RobotFramework script I import my new library.
for reference my init looks something like this:
class EWSLibrary( Selenium2Library, EWSButtons, EWSInputs, EWSRadioCheck, Waits,
EWSTables, EWSText, EWSLinks, EWSTrees, EWSCalendar, EWSMessageBox, Flows, Flows_PCR):
'''This is the main class for a library for RobotFramework that uses Selenium 2.0 WebDriver
in the back-end. It exposes the EWSLibrary to robot framework
'''
def __init__(self, timeout=0.0, client=None, run_on_failure='Capture Page Screenshot',
super( EWSLibrary, self).__init__()
...
Selenium2Library.__init__(self,timeout=timeout, implicit_wait=timeout,
run_on_failure=run_on_failure)
from Selenium2Library import Selenium2Library
class Selenium2Extensions(Selenium2Library):
'''Sometimes Selenium2Library just dont go far enough.'''
def __init__(self, timeout=5.0, implicit_wait=0.0, run_on_failure='Capture Page Screenshot'):
super(Selenium2Library, self).__init__()
def set_value_to_element(self, locator, value):
"""
Sets value attribute to element identified by `locator`.
Example:
| Set Value To Element | number_field | 42
"""
element = self._element_find(locator, True, True)
self._current_browser().execute_script("arguments[0].value = '%s';" % value, element)
But then trying to use the Set Value To Element KW, following error is shown:
KEYWORD: Selenium2Extensions.Set Value To Element ${locator}, ${text} Expand All
Documentation:
Sets value attribute to element identified by `locator`.
Start / End / Elapsed: 20121016 13:42:51.415 / 20121016 13:42:51.417 / 00:00:00.002
13:42:51.416 FAIL AttributeError: 'Selenium2Extensions' object has no attribute '_cache'
After removing init from the file, the error is like this:
KEYWORD: Selenium2Extensions.Set Value To Element ${locator}, ${text} Expand All
Documentation:
Sets value attribute to element identified by `locator`.
Start / End / Elapsed: 20121016 13:47:37.197 / 20121016 13:47:37.586 / 00:00:00.389
+
KEYWORD: Selenium2Library.Capture Page Screenshot Expand All
13:47:37.586 FAIL No browser is open
subclass should be passed to super not the superclass:
super(Selenium2Library, self).init() should be super(Selenium2Extensions, self).init()
If you are going to architect Selenium2Extensions that way, than you should not be calling or importing Selenium2Library. I suspect you are calling Selenium2Library.Open Browser and calling Selenium2Extensions.Set To Value. Both library instances would have their own browser cache. If you are going inherit from Selenium2Library like that you should use your library exclusively.
#341 made me think about my experience with creating extensions to S2L and the trouble I had with on-failure. I believe the best way to extend stateful libraries is accessing the current instance as documented in the user guide.
I think we can go one more than just documenting how to extend the library and provide some useful components to make creating library extensions easier. To that end I have created some things to handle the on-failure issues (double on-failure can occur in user library that extends KeywordGroup) and also some commonly needing things like accessing Selenium2Library, finding elements, and accessing the current WebDriver. I even went as far as supporting extending an subclassed or aliased Selenium2Library.
Please see initial implementation: 3e233ac. @emanlove@pekkaklarck@j1z0 I would love some input. I will hold off further development until I get some buy-in.
If everyone would rather access driver/s2l through a method rather than a property, I can change that. To me, properties make more sense, but I can tell from webdrivermonkey patches that someone does not like properties. Properties cannot be mocked with mockito, but they can be with mock and maybe others.
There may be a cleaner solution to decorating keywords in a module-based library - let me know if know of one.
In the element finding methods, I did not expose the tag argument used to filter elements based on element tags and attributes. Should I?
When a failure occurs inside a user library, the traceback is pretty long because there are two on-failure decorators in scope.
I like the idea to make extending easier in general.
More examples showing how everything works would be nice.
I generally prefer propertys over getters, but getters have a benefit that they can later get optional arguments without breaking existing code. If we go with s2l property, it's obviously easy to add get_s2l() getter later if there actually are needs for arguments.
It might be a good idea to consider converting Se2Lib keyword docs to use reStructuredText in general. That would make it possible to host keywords docs in the same service as other docs, and normal kw docs could still be created because Libdoc supports reST too. This obviously requires a separate issue and more thinking about pros and cons in general.
For what it's worth, we struggled with whether to extend Selenium2Library or access its instance for our implementation of page objects on top of Selenium2Library. The nice thing about extending it is that, through simple OO inheritance, you get all of Selenium2Library's methods. From a Robot test (which of course Selenium2Library is designed for), this distinction does not really matter, as long as Selenium2Library is imported. However, it means that:
You have to import both libraries from your tests. (Yes, you can programmatically import Selenium2Library when your library is instantiated, but this only happens for the first test suite (file). If you have two suites, Selenium2Library will not be imported for the second suite.)
You cannot override Selenium2Library methods with your own implementation. You have to redefine them, and disambiguate between the two libraries in your test.
Selenium2Library, because it is decoupled well from Robot Framework, can be used outside Robot Framework, including with the Python unittest package. We actually do this with some of our tests, calling our libraries from Python tests. It would be nice not to have those tests have to know about delegation through an s2l property, but just to be able to call methods on the library.
For these reasons, I would love to continue to be able to extend Selenium2Library through simple OO.
No one here is going to attempt to stop you from extending Selenium2Library :)
Thank you for your comments.
If you want to write a keyword that is specific to one part of an app, but needs to use advanced things like ActionChains, do you put it inside Selenium2Library?
I would not like that because it pollutes the namespace - in a large automation project, I would not like to be forced to have keywords from an unrelated area of functionality in scope.
Importing multiple libraries has never been an issue for me because I always import libraries through a resource file.
Ideally, one should not rely on transient dependencies, but in practice it works out fine.
You can still override S2L methods through monkey patching. We do that, but I would say it should be avoided if possible. However its done, the docstring should mention its not the original.
There are definitely trade-offs for any approach to extending a library. My contribution is meant to provide options that make extending the library easier. Inheriting a class is simple enough - nothing we can do except provide an example
Also note that I am supporting extensions that work with a subclassed Selenium2Library, so you could choose whichever approach works better for the problem at hand.
If you want to write a keyword that is specific to one part of an app, but needs to use advanced things like ActionChains, do you put it inside Selenium2Library?
We just import whatever we need and put the methods inside the page objects.
There are definitely trade-offs for any approach to extending a library. My contribution is meant to provide options that make extending the library easier. Inheriting a class is simple enough - nothing we can do except provide an example
Do your page objects inherit from S2L, but hide the S2L keywords by using the dynamic library api to only expose your page methods? Do you override _current_browser() to point to the S2L instance created by RF?
I looked at @kahunacohen's comment, but this part was not shown.
Do your page objects inherit from S2L, but hide the S2L keywords by using the dynamic library api to only expose your page methods?
Yes, basically. Though I am experimenting with taking out the part that hides S2L's keywords, because we also, in run_keyword, call BuiltIn().set_library_search_order("____"), passing it the name of the "current page". By default, that's S2L, but once you've called the Open <Page Name> keyword (which we expose in get_keyword_names for each page object class) for a given page - e.g. "Open Google Home Page" - we call set_library_search_order with the name of the page you've chosen. That way, even though multiple libraries can expose the same keyword, the disambiguation is done the way we want it.
We don't override _current_browser(); we just call super's open_browser() from our Open keyword and let Selenium2Library's cache handle the current browser instance when we navigate to a new page.
@pekkaklarck thanks for the feedback. Haven't thought of where to host. If its just the extensions, we could put on GitHub pages, but the whole project than one of the ones you mentioned would be more desirable.
I differed proper documentation until we at least had some consensus.
Thanks for the tips on reST doc-style.
Do you think providing setters for the properties is a good idea? The use case being usage outside of RF, such as unit tests where someone may want to use mocks. In that case, once set, s2l's getter would always return whatever was sent to the setter instead of calling get_library_instance.
I would say that adding setters only to ease mocking sounds a bit weird, especially if there are no concrete use cases that require it. I'm surprised not all mock frameworks support mocking properties out-of-the-box.
Thanks. I will forget about supplying setters. mockito is a port from Java and I think is based on __call__, so that's why it does not support it as far as I know. Also it has not been updated in 4 yearys. Will go with readthedocs then.
The library architecture has changed quite a lot since the last post. Although original idea is valid on, I am going to close this one on favor of #642
Activity
know1nos commentedon Aug 29, 2012
bump
Softworks-Consultancy commentedon Aug 29, 2012
This is what I do for my current project. I simply create a new class and inherit from Selenium2Library that way I can add / overwrite whatever I like. Then from the RobotFramework script I import my new library.
for reference my init looks something like this:
Harri commentedon Oct 16, 2012
Any help on this? I created a file like this:
But then trying to use the Set Value To Element KW, following error is shown:
After removing init from the file, the error is like this:
ombre42 commentedon Oct 16, 2012
subclass should be passed to super not the superclass:
super(Selenium2Library, self).init() should be super(Selenium2Extensions, self).init()
If you are going to architect Selenium2Extensions that way, than you should not be calling or importing Selenium2Library. I suspect you are calling Selenium2Library.Open Browser and calling Selenium2Extensions.Set To Value. Both library instances would have their own browser cache. If you are going inherit from Selenium2Library like that you should use your library exclusively.
ombre42 commentedon Oct 12, 2014
#341 made me think about my experience with creating extensions to S2L and the trouble I had with on-failure. I believe the best way to extend stateful libraries is accessing the current instance as documented in the user guide.
I think we can go one more than just documenting how to extend the library and provide some useful components to make creating library extensions easier. To that end I have created some things to handle the on-failure issues (double on-failure can occur in user library that extends KeywordGroup) and also some commonly needing things like accessing Selenium2Library, finding elements, and accessing the current WebDriver. I even went as far as supporting extending an subclassed or aliased Selenium2Library.
Please see initial implementation: 3e233ac.
@emanlove @pekkaklarck @j1z0 I would love some input. I will hold off further development until I get some buy-in.
If everyone would rather access driver/s2l through a method rather than a property, I can change that. To me, properties make more sense, but I can tell from webdrivermonkey patches that someone does not like properties. Properties cannot be mocked with mockito, but they can be with mock and maybe others.
There may be a cleaner solution to decorating keywords in a module-based library - let me know if know of one.
In the element finding methods, I did not expose the
tagargument used to filter elements based on element tags and attributes. Should I?When a failure occurs inside a user library, the traceback is pretty long because there are two on-failure decorators in scope.
pekkaklarck commentedon Oct 13, 2014
I like the idea to make extending easier in general.
More examples showing how everything works would be nice.
I generally prefer propertys over getters, but getters have a benefit that they can later get optional arguments without breaking existing code. If we go with
s2lproperty, it's obviously easy to addget_s2l()getter later if there actually are needs for arguments.Where is the documentation going to be hosted? http://pythonhosted.org/, https://readthedocs.org/, or something else?
It might be a good idea to consider converting Se2Lib keyword docs to use reStructuredText in general. That would make it possible to host keywords docs in the same service as other docs, and normal kw docs could still be created because Libdoc supports reST too. This obviously requires a separate issue and more thinking about pros and cons in general.
dpsfrishberg commentedon Oct 14, 2014
For what it's worth, we struggled with whether to extend Selenium2Library or access its instance for our implementation of page objects on top of Selenium2Library. The nice thing about extending it is that, through simple OO inheritance, you get all of Selenium2Library's methods. From a Robot test (which of course Selenium2Library is designed for), this distinction does not really matter, as long as Selenium2Library is imported. However, it means that:
s2lproperty, but just to be able to call methods on the library.For these reasons, I would love to continue to be able to extend Selenium2Library through simple OO.
ombre42 commentedon Oct 14, 2014
No one here is going to attempt to stop you from extending Selenium2Library :)
Thank you for your comments.
If you want to write a keyword that is specific to one part of an app, but needs to use advanced things like ActionChains, do you put it inside Selenium2Library?
I would not like that because it pollutes the namespace - in a large automation project, I would not like to be forced to have keywords from an unrelated area of functionality in scope.
Importing multiple libraries has never been an issue for me because I always import libraries through a resource file.
Ideally, one should not rely on transient dependencies, but in practice it works out fine.
You can still override S2L methods through monkey patching. We do that, but I would say it should be avoided if possible. However its done, the docstring should mention its not the original.
There are definitely trade-offs for any approach to extending a library. My contribution is meant to provide options that make extending the library easier. Inheriting a class is simple enough - nothing we can do except provide an example
Also note that I am supporting extensions that work with a subclassed Selenium2Library, so you could choose whichever approach works better for the problem at hand.
dpsfrishberg commentedon Oct 15, 2014
Understood, thanks.
We just import whatever we need and put the methods inside the page objects.
OK, thanks, I understand.
ombre42 commentedon Oct 15, 2014
Do your page objects inherit from S2L, but hide the S2L keywords by using the dynamic library api to only expose your page methods? Do you override
_current_browser()to point to the S2L instance created by RF?I looked at @kahunacohen's comment, but this part was not shown.
dpsfrishberg commentedon Oct 15, 2014
Yes, basically. Though I am experimenting with taking out the part that hides S2L's keywords, because we also, in
run_keyword, callBuiltIn().set_library_search_order("____"), passing it the name of the "current page". By default, that's S2L, but once you've called theOpen <Page Name>keyword (which we expose inget_keyword_namesfor each page object class) for a given page - e.g. "Open Google Home Page" - we callset_library_search_orderwith the name of the page you've chosen. That way, even though multiple libraries can expose the same keyword, the disambiguation is done the way we want it.We don't override
_current_browser(); we just call super'sopen_browser()from ourOpenkeyword and let Selenium2Library's cache handle the current browser instance when we navigate to a new page.ombre42 commentedon Oct 16, 2014
@pekkaklarck thanks for the feedback. Haven't thought of where to host. If its just the extensions, we could put on GitHub pages, but the whole project than one of the ones you mentioned would be more desirable.
I differed proper documentation until we at least had some consensus.
Thanks for the tips on reST doc-style.
Do you think providing setters for the properties is a good idea? The use case being usage outside of RF, such as unit tests where someone may want to use mocks. In that case, once set, s2l's getter would always return whatever was sent to the setter instead of calling
get_library_instance.pekkaklarck commentedon Oct 17, 2014
The problem with reST and Sphinx on GitHub pages is that you need to handle HTML conversion yourself. Using http://pythonhosted.org/ or https://readthedocs.org/ avoids that problem.
I would say that adding setters only to ease mocking sounds a bit weird, especially if there are no concrete use cases that require it. I'm surprised not all mock frameworks support mocking properties out-of-the-box.
ombre42 commentedon Oct 18, 2014
Thanks. I will forget about supplying setters. mockito is a port from Java and I think is based on
__call__, so that's why it does not support it as far as I know. Also it has not been updated in 4 yearys. Will go with readthedocs then.aaltat commentedon Nov 25, 2017
The library architecture has changed quite a lot since the last post. Although original idea is valid on, I am going to close this one on favor of #642