Select2
There is nothing like actually using code that you have written in anger at a client-site. In fact, I think most people who write code should use it in its intended marketplace. Using Py.Saunter over 6 weeks at a client led me to re-write how to interact with Selects were handled.
<pre lang="python">from saunter.po.webdriver.element import Element
from selenium.webdriver.support.select import Select as WebDriverSelect
class Select(Element, WebDriverSelect):
def __set__(self, obj, val):
s = WebDriverSelect(obj.driver.find_element_by_locator(self.locator))
method = val[:val.find("=")]
value = val[val.find("=") + 1:]
if method == "value":
s.select_by_value(value)
elif method == "index":
s.select_by_index(value)
elif method == "text":
s.select_by_visible_text(value)
else:
raise saunter.exceptions.InvalidLocatorString(val)
def __get__(self, obj, cls=None):
try:
s = WebDriverSelect(obj.driver.find_element_by_locator(self.locator))
e = s.first_selected_option
return str(e.text)
except AttributeError as e:
if str(e) == "'SeleniumWrapper' object has no attribute 'connection'":
pass
else:
raise e
Which you then use something like this.
Page Object:
<pre lang="python">from saunter.po.webdriver.select import Select
locators = {
"current search category": 'id=gh-cat'
}
class CurrentSearchCategory(Select):
def __init__(self):
self.locator = locators["current search category"]
class MyPageObject(SaunterTestCase):
current_search_category = CurrentSearchCategory()
def __init__(self, driver):
self.driver = driver
# rest of page object
Script:
<pre lang="python">@pytest.marks('deep', 'ebay', 'select', 'single')
def test_single_set_by_value(self):
s = ShirtPage(self.driver)
s.go_to_mens_dress_shirts()
s.current_search_category = "value=550"
assert(s.current_search_category == "Art")
And that both looks clean and works well in 98% of the situations. Except when you want the list of options in the select box to be return. The, well, it falls on its face. (Care to guess the situation I found myself in while at the client?)
Enter Select2.
Its a crappy name, but there is some precedence for it. Select2 uses properties rather than the descriptor protocol to control the interactions with the browser.
<pre lang="python">class Select2(Element, WebDriverSelect):
def __init__(self, driver, locator):
self.driver = driver
self.locator = locator
@property
def selected(self):
s = WebDriverSelect(self.driver.find_element_by_locator(self.locator))
e = s.first_selected_option
return str(e.text)
@selected.setter
def selected(self, val):
s = WebDriverSelect(self.driver.find_element_by_locator(self.locator))
method = val[:val.find("=")]
value = val[val.find("=") + 1:]
if method == "value":
s.select_by_value(value)
elif method == "index":
s.select_by_index(value)
elif method == "text":
s.select_by_visible_text(value)
else:
raise saunter.exceptions.InvalidLocatorString(val)
@property
def options(self):
s = WebDriverSelect(self.driver.find_element_by_locator(self.locator))
options = s.options
text = [option.text.strip() for option in options]
return text
The interaction in the script looks similar to that of Select, but now you need the .selected in there as well.
<pre lang="python">@pytest.marks('deep', 'ebay', 'select', 'single')
def test_single_set_by_value(self):
s = ShirtPage(self.driver)
s.go_to_mens_dress_shirts()
s.current_search_category.selected = "value=550"
assert(s.current_search_category.selected == "Art")
assert(len(s.current_search_category.options] == 9)
Should I have gone straight to the property method for selects? Maybe … but I didn’t really understand it at the time and the descriptor protocol route looks cleaner I think. I’ll use Select except when I actually need the options.