Reputation: 1
My project is using Robot Framework and Selenium2Library for testing a web system.
I need to use a specific element from the list of elements found by a css locator. For example, .innerTable>tbody>tr>td will find 10 elements and I will need the second element from those ones. In my previous project I could do the following to refer to a desired element:
specific_cell = driver.find_elements(By.CSS_SELECTOR, ".innerTable>tbody>tr>td")[1]
Meaning that it creates a list of elements and using index I can select a specific one. But does anyone know if I can do something similar with Robot framework + Selenium2Library?
I was trying to understand if there is a way to reach the result by using only Selenium2Library keywords. Apparently, it is not possible with existing keywords.
In order to implement the possibility of using one specific element from many identified by a locator (in original question those were table cells found by css selector), we need to go down to Selenium2Library libraries and make new methods in there.
In C:\Python27\Lib\site-packages\Selenium2Library\keywords_element.py one may add such a method
def return_elements(self, locator):
"""Returns a list of elements identified by a given locator.
"""
elements = self._element_find(locator, False, True)
if elements is None:
raise AssertionError("Could not find element(s) '%s'" % (locator))
return elements
This will return a list of web elements objects and this list can be used to point out a specific element and then conduct some action on that. But if you need such actions like click, get text or type, then additional methods have to be created in the same class. Well, this doesn't look as an optimal way, but at least it does a job. Another option is to use xpath and don't worry about above writing, but once you imaging huge pages with great number of similar elements and add here a requirement to use Internet explorer, then xpath might be something you want to avoid
Upvotes: 0
Views: 4251
Reputation: 2384
If you are not locating an element that is preceded by many siblings, you can use adjacent sibling selectors. For example, the following locator selects the second item in an ordered list:
css= ol > li:first-child + li
One keyword you might consider creating would be Assign Id To Nth Element. It would be the same as Assign Id To Element except the ID is assigned to the element that is the Nth in the element collection returned using the given locator. This would allow you to use other keywords without modifying Selenium2Library. For example:
Assign Id To Nth Element css= ol > li 27th_item 27
Element Text Should Be id= 27th_item some text
That would make selecting a child preceded by many siblings easier. With adjacent sibling selectors, the selector would be huge in that scenario because nth-child is not available until CSS3.
Here is an implementation of Assign Id To Nth Element:
from robot.libraries.BuiltIn import BuiltIn
class S2LExt(object):
def assign_id_to_nth_element(self, locator, id, n):
"""Assigns a temporary identifier to the Nth element found.
This is mainly useful if the locator is complicated/slow XPath expression.
Identifier expires when the page is reloaded. `n` is 1-based index of the
element in the set found with the given `locator` to assign an ID to.
Example:
| Assign ID to Nth Element | xpath=//div[@id="first_div"] | my id | 5 |
| Page Should Contain Element | my id |
"""
lib = BuiltIn().get_library_instance('Selenium2Library')
n = int(n)
elements = lib._element_find(locator, False, True)
if len(elements) < n:
raise IndexError("Only %d elements found using locator '%s'." % (len(elements), locator))
lib._info("Assigning temporary id '%s' to element '%s'" % (id, locator))
lib._current_browser().execute_script("arguments[0].id = '%s';" % id, elements[n-1])
Upvotes: 2
Reputation: 7119
I always recommend not grabbing one tiny element and giving it a name. Create a larger body of elements, and let those using your element define what they want.
class TablePage(object):
"""A page with a table on it."""
def __init__(self):
# I would call super() on the `BasePage` here.
pass
@property
def table_element(self):
# factored out: '.innerTable td'
css = '.innerTable > tbody > tr > td'
return self.driver.find_elements(By.CSS_SELECTOR, css)
def find_in_table_element(self, table_text):
for entry in self.table_element:
# you may want case-sensitive here
if entry.text.lower == table_text.lower
return entry
return [] # or None...
You could use it a bit more fluidly here, relying on your application code to do the actual work, and hopefully resulting in less maintenance for you. Web pages change all the time!
You might implement it this way:
page_with_table = TablePage()
second_entry = page_with_table.table_element[1]
table_entry_by_text = page_with_table.find_in_table_element('specific text')
assertEquals(second_entry.text, table_entry_by_text.text) # or whatever
Upvotes: 0