Repository navigation
Description
Per discussion with @pekkaklarck on the Slack group...
Essential Issue
Having created some Python helper functions, using the Selenium2Library, I have been able to leverage some of the private methods to accomplish some useful things for my testing framework. An example below shows a function get_column_number which used to be able to return the column number of whatever (valid) column name was passed in:
from robot.libraries.BuiltIn import BuiltIn
def _get_sel2lib():
"""== Gets Current Selenium 2 Instance from Robot Framework ==
- Uses the Robot Framework API to get an object of the current Selenium 2 instance.
- This is an internal helper function only.
== Calling ==
| *Args* | [none] | |
| *Returns* | ``object`` | An object instance of the current Selenium 2 library.
| *Raises* | [none] | |
"""
return BuiltIn().get_library_instance('Selenium2Library')
def get_column_number(table_locator, col_text, loglevel='INFO'):
"""== Returns Number of Specified Column ==
Returns the number of the first column found which contains the ``col_text`` string.
- Does not require an exact match; if there is a column named ``Foobar`` and you input ``Foo`` then it will match.
=== Calling ===
| *Args* | ``table_locator`` (str) | The table locator containing the column whose number you desire. |
| | ``col_text`` (str) | The string of the column you want to locate. |
| *Returns* | ``int`` | Number of column containing string ``col_name``. |
| *Raises* | AssertionError | If ``col_name`` cannot be found in the table specified. |
=== Example in Robot ===
| ${column_number} = Get Column Number xpath=//*[@id="deviceListHeader"] Unsafe
"""
sel2lib = _get_sel2lib()
locators = sel2lib._table_element_finder._parse_table_locator(table_locator, 'header')
found_it = False
for locator in locators:
elements = sel2lib._element_finder.find(sel2lib._current_browser(), locator)
counter = 0
for element in elements:
counter += 1
if col_text in element.text:
output = counter
found_it = True
break
if found_it is False:
sel2lib.log_source(loglevel)
raise AssertionError("No column containing '{}' found in the table identified via {}!".format(col_text, table_locator))
return outputWith the new SeleniumLibrary, I get an error: AttributeError: 'SeleniumLibrary' object has no attribute '_table_element_finder' as that structure doesn't exist as-is. I do notice, however, that in tableelementfinder.py there is a method named find_by_header which I could leverage if I could access it.
General Request
Provide a means to access some of these methods to get more granular access to some of the inner workings of the Selenium library.
Activity
aaltat commentedon Sep 21, 2017
Using the private methods is always a risky, because they are private and not meant to used publicly. But also I have to admit that the old architecture was not very good and many things which where private should have been public. Let's think what can be done with your problem and can we make some of the methods public. Ed example had a need for the
ElementFinderclass, because he did want to overwrite some methods to better support AngularJS extension.@GLMeece could you find/grep your code and see which private methods you need from the S2L? Then we would have a better picture which private methods you actually need and can better decide how to solve the problem. As a quick thought, perhaps we could do something similar than what we did do with the
ElementFinder. One can access the class from the library instance, it is here: https://github.com/robotframework/SeleniumLibrary/blob/master/src/SeleniumLibrary/__init__.py#L262If we provide access in similar way, would it satisfy your needs?
But please look into your code and tell if there is a need for other private methods.
GLMeece commentedon Sep 21, 2017
Thanks @aaltat - I only recently returned to the project containing these methods and encountered the issues indicated. I will look through my code and see where my particular pain points are, and then try to extrapolate to more general "rules" as to what makes sense to expose publicly. Thanks for your thoughts and help on this.
pekkaklarck commentedon Sep 21, 2017
Yeah, we should have a goal to expose stuff needed when extending the library publicly. This includes adding tests to ensure we don't break the public API in the future.
We also can add, and have added, some of the old private APIs back for the time being with appropriate deprecation warning. I doubt that's feasible with all APIs, though.
aaltat commentedon Sep 21, 2017
By looking that code snippet, if SL library
__init__would have thisself.table_element_finder = TableElementFinder(self), then @GLMeece could access the methods he needs from the SL and TableElementFinder. Only thing he would have to change in his code would be_table_element_findertotable_element_finder.aaltat commentedon Sep 21, 2017
The #912 proposes a fix for the problem.
Also did notice that @GLMeece needs access to the
ElementFinderbut that is already available in the public API. Only thing need which needs change, in @GLMeece side, issel2lib._element_finder.find(sel2lib._current_browser(), locator)tosel2lib._element_finder.find(locator)GLMeece commentedon Sep 22, 2017
OK, never mind my P.R. Yours does effectively the same thing plus a test.
GLMeece commentedon Sep 22, 2017
FWIW, my revised function using the P.R. that @aaltat created:
aaltat commentedon Sep 22, 2017
@GLMeece did you have time to take a look in your code base to find out other possible API which you may need?
pekkaklarck commentedon Sep 22, 2017
The new
get_column_numberfunction is so much cleaner when it doesn't need to touch_private_parts!That function actually looks pretty generic. Stuff like this could be easily added to SL itself.
GLMeece commentedon Sep 22, 2017
@aaltat - I was working late at work on this particular issue as well as some others, so I was not able to finish examining the rest of the code base. However, taking a look at it now - most of what I have cared about has been for table-related things, so for my limited use-cases, this enhancement is good! 👏
GLMeece commentedon Sep 22, 2017
BTW, @pekkaklarck - you are more than welcome to modify what I've done and include it in 🤖 I'd be happy to make even small contributions to a project (and the people behind it) that has helped me so much!
2 remaining items
aaltat commentedon Sep 22, 2017
We did discuss with @pekkaklarck about the code quality in the
TableElementFinderand because the code is, hmm interesting (in a bad way), we decided to make theTableElementFinderas a private method for the library. This mean that after 3.0.0 release, there will be some thinking, designing and refactoring (read rewrite) of the table keywords. Therefore we do not want theTableElementFinderas public method in the library, more details for the future enhancement can be fond in #914aaltat commentedon Sep 22, 2017
Did forget to mention that keyword API is not intended to change, but the changes are planned in the underlying code.
GLMeece commentedon Sep 22, 2017
I realize that RF and related libraries are for testing and assertions. Regardless, there's still a lot of utility that can be leveraged (and often has to be leveraged) by a test engineer. That is why I wrote this and similar routines in the first place.
A typical scenario: depending on various factors (user login, environmental factors, changing design), the specific column number might change quite rapidly, but the text itself might be much more stable. As a result, I've had to create this utility so I might know which column to look at for a given value.
Even if the code is "less than optimal", having access to some of these helper functions, such as those listed in tableelementfinder.py seem very helpful - to me, at least. 🙂
aaltat commentedon Sep 23, 2017
Created issue #915 where discussion about the table keywords should continue.
[-]Continue to allow access to private methods?[/-][+]Continue to allow access to _table_element_finder class[/+]aaltat commentedon Sep 23, 2017
The PR #912 is now merged in master, which provides a fix for this problem. Therefore closing the issue.
@GLMeece if you want to try out the changes, then you can install from source, example like this: https://stackoverflow.com/questions/15268953/how-to-install-python-package-from-github
pekkaklarck commentedon Sep 23, 2017
@GLMeece It's fine that libraries are used programmatically by other libraries or any other code. We just need to make the related APIs public and stable. Whenever you are using something that
_starts_with_an_underscorethere's a risk that the API changes in the future. It's great that Python doesn't prevent you from using such private APIs, but you should acknowledge the risk. Probably the best idea would be submitting an issue and proposing making that part of the API stable. In the end the best solution might then be adding a new keyword that exposes the functionality directly to everyone.pekkaklarck commentedon Sep 23, 2017
@aaltat Shouldn't this issue actually be marked as duplicate of #882? Not sure do we need both of these in the release notes.
aaltat commentedon Sep 23, 2017
Ah I did remember that one, difficult question. I did already put this issue number in PR text and the PR is already merged. It is true that this kind of duplicate of the #882. If we want to mark this as duplicate, then we should put the information in #882.
pekkaklarck commentedon Sep 23, 2017
Marking this as a dupe for #882 would basically mean removing milestone and adding
duplicatelabel.aaltat commentedon Sep 26, 2017
@GLMeece when where looking at the library documentation and the
TableElementFinderclass, we decided to refactor theTableElementFinderandElementFinderclasses. This was done to unify the locator strategy with table keywords to match with the rest of the library, PR in #921This refactoring caused that some of the internal methods which your original code did rely on, do not anymore exist. Example the
_parse_table_locatoris removed and_search_in_locatorsarguments have changed (+also the logic too). But more details in #921.