Skip to content

Document how to tweak the library without touching the library #91

Closed

Description

@emanlove

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).

Activity

  1. know1nos commented on Aug 29, 2012

    @know1nos

    bump

  2. Softworks-Consultancy commented on Aug 29, 2012

    @Softworks-Consultancy

    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)
    
    
  3. Harri commented on Oct 16, 2012

    @Harri
    Contributor

    Any help on this? I created a file like this:

    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
    
  4. ombre42 commented on Oct 16, 2012

    @ombre42
    Contributor

    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.

  5. ombre42 commented on Oct 12, 2014

    @ombre42
    Contributor

    #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.

  6. pekkaklarck commented on Oct 13, 2014

    @pekkaklarck
    Member
    1. I like the idea to make extending easier in general.

    2. More examples showing how everything works would be nice.

    3. 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.

    4. Where is the documentation going to be hosted? http://pythonhosted.org/, https://readthedocs.org/, or something else?

    5. 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.

  7. dpsfrishberg commented on Oct 14, 2014

    @dpsfrishberg
    Contributor

    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.

  8. ombre42 commented on Oct 14, 2014

    @ombre42
    Contributor

    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.

  9. dpsfrishberg commented on Oct 15, 2014

    @dpsfrishberg
    Contributor

    Understood, thanks.

    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

    OK, thanks, I understand.

  10. ombre42 commented on Oct 15, 2014

    @ombre42
    Contributor

    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.

  11. dpsfrishberg commented on Oct 15, 2014

    @dpsfrishberg
    Contributor

    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.

  12. ombre42 commented on Oct 16, 2014

    @ombre42
    Contributor

    @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.

  13. pekkaklarck commented on Oct 17, 2014

    @pekkaklarck
    Member

    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.

  14. ombre42 commented on Oct 18, 2014

    @ombre42
    Contributor

    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.

  15. added
    taskGeneric task not listed in release notes
    and removed on Sep 13, 2017
  16. removed this from the milestone on Sep 13, 2017
  17. aaltat commented on Nov 25, 2017

    @aaltat
    Contributor

    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

  18. added and removed
    taskGeneric task not listed in release notes
    on Nov 25, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions