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.
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:
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:
@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.
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.
@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.
Post a Comment