Selenide Alternative in Practice - Implementation & Lessons learned [SeleniumCamp 2016]

938 views

Published on

Talk given on SeleniumCamp 2016 about:
- Main Selenide Features from Implementation point of view
- Examples of porting these features to python (using simplified Selene sources)

This talk can be considered as a HOWTO on porting Selenide to any language, if the following programming paradigms are considered as tools: Procedural, Modular, OOP.

Published in: Technology
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
938
On SlideShare
0
From Embeds
0
Number of Embeds
255
Actions
Shares
0
Downloads
10
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Selenide Alternative in Practice - Implementation & Lessons learned [SeleniumCamp 2016]

  1. 1. Selenide Alternative in Practice Implementation & Lessons Learned
  2. 2. Preface…
  3. 3. Implementation & Lessons Learned Selenide Alternative in Practice
  4. 4. Selenide
  5. 5. Selenide = ?
  6. 6. Selenide = Effective web test automation tool = tool to automate web UI tests logic not browser (it should be already automated;)
  7. 7. Selenide = Effective web test automation tool = tool to automate web UI tests logic not browser concise API … … …
  8. 8. Selenide = Effective web test automation tool = tool to automate web UI tests logic not browser concise API waiting search … …
  9. 9. Selenide = Effective web test automation tool = tool to automate web UI tests logic not browser concise API waiting search waiting asserts …
  10. 10. Selenide = Effective web test automation tool = tool to automate web UI tests logic not browser concise API waiting search waiting asserts dynamic elements
  11. 11. Selenide = Effective web test automation tool = tool to automate web UI tests logic not browser concise API waiting search waiting asserts dynamic elements
  12. 12. Selenide Alternative? concise API waiting search waiting asserts dynamic elements
  13. 13. Selenide Alternative? concise API waiting search waiting asserts dynamic elements How should it work?
  14. 14. Selenide Alternative? concise API waiting search waiting asserts dynamic elements How should it work? How to build?
  15. 15. With examples in
  16. 16. Proviso Partial sacrificing DRY, privatisation, etc. for simplification of examples
  17. 17. Concise API
  18. 18. driver.find_element(By.XPATH, "//*[contains(text(),'task')]")
 # vs 
 s(by_xpath("//*[contains(text(),'task')]"))
 # vs 
 s(with_text('task'))
  19. 19. driver.find_element(By.XPATH, "//*[contains(text(),'task')]")
 # vs 
 s(by_xpath("//*[contains(text(),'task')]"))
 # vs 
 s(with_text('task')) DRY locator helpers
  20. 20. driver.find_element(By.XPATH, "//*[contains(text(),'task')]")
 # vs 
 s(by_xpath("//*[contains(text(),'task')]"))
 # vs 
 s(with_text('task'))
  21. 21. driver.find_element(By.XPATH, "//*[contains(text(),'task')]")
 # vs 
 s(by_xpath("//*[contains(text(),'task')]"))
 # vs 
 s(with_text('task')) OOP
  22. 22. driver.find_element(By.XPATH, "//*[contains(text(),'task')]")
 # vs 
 s(by_xpath("//*[contains(text(),'task')]"))
 # vs 
 s(with_text('task')) from selenium import webdriver driver = webdriver.Firefox() # ... OOP
  23. 23. driver.find_element(By.XPATH, "//*[contains(text(),'task')]")
 # vs 
 s(by_xpath("//*[contains(text(),'task')]"))
 # vs 
 s(with_text('task')) Modular from selenium import webdriver driver = webdriver.Firefox() # ... OOP
  24. 24. driver.find_element(By.XPATH, "//*[contains(text(),'task')]")
 # vs 
 s(by_xpath("//*[contains(text(),'task')]"))
 # vs 
 s(with_text('task')) Modular from selenium import webdriver driver = webdriver.Firefox() # ... from selene.tools import set_driver, s set_driver(webdriver.Firefox()) # ... OOP
  25. 25. driver.find_element(By.XPATH, "//*[contains(text(),'task')]") # vs 
 s(by_xpath("//*[contains(text(),'task')]")).
 # vs 
 s(with_text('task')) Modular OOP from selenium import webdriver driver = webdriver.Firefox() driver.get(‘http://todomvc4tasj.herokuapp.com’) from selene.tools import set_driver, s set_driver(webdriver.Firefox()) visit(‘http://todomvc4tasj.herokuapp.com’)
  26. 26. driver.find_element(By.XPATH, "//*[contains(text(),'task')]") # vs 
 s(by_xpath("//*[contains(text(),'task')]")).
 # vs 
 s(with_text('task')) Functions over Methods for main actions from selenium import webdriver driver = webdriver.Firefox() driver.get(‘http://todomvc4tasj.herokuapp.com’) from selene.tools import set_driver, s set_driver(webdriver.Firefox()) visit(‘http://todomvc4tasj.herokuapp.com’)
  27. 27. s(by_css(‘#new-todo’)) # vs/or
 s('#new-todo') Convenient defaults
  28. 28. 
 driver.find_element_by_css_selector('#new-todo').clear()
 driver.find_element_by_css_selector(‘#new-todo’) .send_keys(‘task') 
 # vs 
 s(‘#new-todo’).clear().send_keys(‘task’)
  29. 29. 
 driver.find_element_by_css_selector('#new-todo').clear()
 driver.find_element_by_css_selector(‘#new-todo’) .send_keys(‘task') 
 # vs 
 s(‘#new-todo’).clear().send_keys(‘task’) Chainable methods
  30. 30. 
 driver.find_element_by_css_selector('#new-todo')
 .send_keys('task', Keys.ENTER)
 # vs 
 s('#new-todo').send_keys('task').press_enter()
  31. 31. 
 driver.find_element_by_css_selector('#new-todo')
 .send_keys('task', Keys.ENTER)
 # vs 
 s('#new-todo').send_keys('task').press_enter() “Short-cut” methods
  32. 32. s(‘#new-todo’).clear().send_keys(‘task’) 
 # vs 
 s(‘#new-todo’).set(‘task’) “Short-cut” methods
  33. 33. Built in Explicit Waits aka Waiting Asserts WebDriverWait(driver, 4).until( element_to_be_clickable(by_css('#new-todo')))
 
 # vs
 
 s('#new-todo').should_be(clickable)
  34. 34. 
 new_todo = by_css('#new-todo')
 
 visit('http://todomvc4tasj.herokuapp.com')
 
 s(new_todo).set('task 1').press_enter()
 # ...
 s(new_todo).set('task 2').press_enter()
 
 # vs
 
 new_todo = s('#new-todo')
 
 visit('http://todomvc4tasj.herokuapp.com')
 
 new_todo.set('task 1').press_enter()
 # ...
 new_todo.set('task 2').press_enter()
  35. 35. 
 new_todo = by_css('#new-todo')
 
 visit('http://todomvc4tasj.herokuapp.com')
 
 s(new_todo).set('task 1').press_enter()
 # ...
 s(new_todo).set('task 2').press_enter()
 
 # vs
 
 new_todo = s('#new-todo')
 
 visit('http://todomvc4tasj.herokuapp.com')
 
 new_todo.set('task 1').press_enter()
 # ...
 new_todo.set('task 2').press_enter()
  36. 36. 
 new_todo = by_css('#new-todo')
 
 visit('http://todomvc4tasj.herokuapp.com')
 
 s(new_todo).set('task 1').press_enter()
 # ...
 s(new_todo).set('task 2').press_enter()
 
 # vs
 
 new_todo = s('#new-todo')
 
 visit('http://todomvc4tasj.herokuapp.com')
 
 new_todo.set('task 1').press_enter()
 # ...
 new_todo.set('task 2').press_enter() ?
  37. 37. Dynamic Elements
  38. 38. Dynamic Elements aka Lazy Proxy Elements
  39. 39. new_todo = s('#new-todo')
  40. 40. def s(css_selector_or_locator):
 return SElement(css_selector_or_locator) Element Factory
  41. 41. class SElement(...):
 def __init__(self, css_selector_or_locator, context=...):
 self.locator = parse(css_selector_or_locator) # ...
 
 def finder(self):
 return self.context.find_element(*self.locator) def assure(self, condition):
 wait_for(self, condition, timeout)
 return self def do(self, command):
 self.assure(visible)
 command(self.finder()) return self 
 def click(self):
 return self.do(lambda element: element.click())
  42. 42. class SElement(...):
 def __init__(self, css_selector_or_locator, context=...):
 self.locator = parse(css_selector_or_locator) # ...
 
 def finder(self):
 return self.context.find_element(*self.locator) def assure(self, condition):
 wait_for(self, condition, timeout)
 return self def do(self, command):
 self.assure(visible)
 command(self.finder()) return self 
 def click(self):
 return self.do(lambda element: element.click())
  43. 43. class SElement(...):
 def __init__(self, css_selector_or_locator, context=...):
 self.locator = parse(css_selector_or_locator) # ...
 
 def finder(self):
 return self.context.find_element(*self.locator) def assure(self, condition):
 wait_for(self, condition, timeout)
 return self def do(self, command):
 self.assure(visible)
 command(self.finder()) return self 
 def click(self):
 return self.do(lambda element: element.click())
  44. 44. class SElement(...):
 def __init__(self, css_selector_or_locator, context=...):
 self.locator = parse(css_selector_or_locator) # ...
 
 def finder(self):
 return self.context.find_element(*self.locator) def assure(self, condition):
 wait_for(self, condition, timeout)
 return self def do(self, command):
 self.assure(visible)
 command(self.finder()) return self 
 def click(self):
 return self.do(lambda element: element.click())
  45. 45. class SElement(...):
 def __init__(self, css_selector_or_locator, context=...):
 self.locator = parse(css_selector_or_locator) # ...
 
 def finder(self):
 return self.context.find_element(*self.locator) def assure(self, condition):
 wait_for(self, condition, config.timeout)
 return self def do(self, command):
 self.assure(visible)
 command(self.finder()) return self 
 def click(self):
 return self.do(lambda element: element.click())
  46. 46. class SElement(...):
 def __init__(self, css_selector_or_locator, context=...):
 self.locator = parse(css_selector_or_locator) # ...
 
 def finder(self):
 return self.context.find_element(*self.locator) def assure(self, condition):
 wait_for(self, condition, config.timeout)
 return self def do(self, command):
 self.assure(visible)
 command(self.finder()) return self 
 def click(self):
 return self.do(lambda element: element.click()) “waiting search”
  47. 47. class SElement(...):
 def __init__(self, css_selector_or_locator, context=...):
 self.locator = parse(css_selector_or_locator) # ...
 
 def finder(self):
 return self.context.find_element(*self.locator) def assure(self, condition):
 wait_for(self, condition, config.timeout)
 return self def do(self, command):
 self.assure(visible)
 command(self.finder()) return self 
 def click(self):
 return self.do(lambda element: element.click()) “waiting assert”
  48. 48. 
 def wait_for(entity, condition, timeout):
 # ...
 
 end_time = time.time() + timeout
 while True:
 try:
 value = condition(entity)
 if value is not None:
 return value
 except (WebDriverException,) as exc:
 # ...
 time.sleep(config.poll_during_waits)
 if time.time() > end_time:
 break
 raise TimeoutException(
 """
 failed while waiting %s seconds
 to assert %s%s
 """ % (timeout, condition.__class__.__name__, str(condition), ...)
  49. 49. 
 def wait_for(entity, condition, timeout):
 # ...
 
 end_time = time.time() + timeout
 while True:
 try:
 value = condition(entity)
 if value is not None:
 return value
 except (WebDriverException,) as exc:
 # ...
 time.sleep(config.poll_during_waits)
 if time.time() > end_time:
 break
 raise TimeoutException(
 """
 failed while waiting %s seconds
 to assert %s%s
 """ % (timeout, condition.__class__.__name__, str(condition), ...)
  50. 50. 
 def wait_for(entity, condition, timeout):
 # ...
 
 end_time = time.time() + timeout
 while True:
 try:
 value = condition(entity)
 if value is not None:
 return value
 except (WebDriverException,) as exc:
 # ...
 time.sleep(config.poll_during_waits)
 if time.time() > end_time:
 break
 raise TimeoutException(
 """
 failed while waiting %s seconds
 to assert %s%s
 """ % (timeout, method.__class__.__name__, str(condition), ...)
  51. 51. 
 def wait_for(entity, condition, timeout):
 # ...
 
 end_time = time.time() + timeout
 while True:
 try:
 value = condition(entity)
 if value is not None:
 return value
 except (WebDriverException,) as exc:
 # ...
 time.sleep(config.poll_during_waits)
 if time.time() > end_time:
 break
 raise TimeoutException(
 """
 failed while waiting %s seconds
 to assert %s%s
 """ % (timeout, method.__class__.__name__, str(condition), ...)
  52. 52. class Visible(object):
 
 def __call__(self, selement): self.selement = selement
 found = selement.finder()
 return found if found.is_displayed() else None
 
 def __str__(self):
 return """
 for element found by: %s...
 """ % (self.selement.locator, …) 
 visible = Visible()
  53. 53. class Visible(object):
 
 def __call__(self, selement): self.selement = selement
 found = selement.finder()
 return found if found.is_displayed() else None
 
 def __str__(self):
 return """
 for element found by: %s...
 """ % (self.selement.locator, …) 
 visible = Visible()
  54. 54. class Visible(object):
 
 def __call__(self, webelement): self.selement = selement
 found = selement.finder()
 return webelement.is_displayed()
 
 def __str__(self):
 return """
 for element found by: %s...
 """ % (self.selement.locator, …) 
 visible = Visible() Original jSelenide style with selement.finder() being moved to wait_for
  55. 55. class Visible(object):
 
 def __call__(self, webelement): self.selement = selement
 found = selement.finder()
 return webelement.is_displayed()
 
 def __str__(self):
 return """
 for element found by: %s...
 """ % (self.selement.locator, …) 
 visible = Visible() Original jSelenide style with selement.finder() being moved to wait_for more simple
 and secure
 but 
 less powerful
  56. 56. class SElement(...):
 def __init__(self, css_selector_or_locator, context=...):
 self.locator = parse(css_selector_or_locator) # ...
 
 def finder(self):
 return self.context.find_element(*self.locator) def assure(self, condition):
 wait_for(self, condition, config.timeout)
 return self def do(self, command):
 self.assure(visible)
 command(self.finder()) return self 
 def click(self):
 return self.do(lambda element: element.click())
  57. 57. class SElement(...):
 def __init__(self, css_selector_or_locator, context=...):
 self.locator = parse(css_selector_or_locator) # ...
 
 def finder(self):
 return self.context.find_element(*self.locator) def assure(self, condition):
 wait_for(self, condition, config.timeout)
 return self 
 insist = assure
 should = assure
 should_be = assure
 should_have = assure
 def click(self):
 return self.do(lambda element: element.click())
  58. 58. Speed?
  59. 59. class SElement(...):
 def __init__(self, css_selector_or_locator, context=...):
 self.locator = parse(css_selector_or_locator) # ...
 
 def finder(self):
 return self.context.find_element(*self.locator) def assure(self, condition):
 wait_for(self, condition, config.timeout)
 return self def do(self, command):
 self.assure(visible)
 command(self.finder()) return self 
 def click(self):
 return self.do(lambda element: element.click()) redundant finder call
  60. 60. 
 def finder(self):
 return self.context.find_element(*self.locator) def refind(self):
 self.found = self.finder()
 return self.found def assure(self, condition):
 self.found = wait_for(self, condition, config.timeout)
 return self def do(self, command):
 self.assure(visible)
 command()
 return self 
 def click(self):
 return self.do(lambda: self.found.click()) reusing self.found
  61. 61. 
 def finder(self):
 return self.context.find_element(*self.locator) def refind(self):
 self.found = self.finder()
 return self.found def assure(self, condition):
 self.found = wait_for(self, condition, config.timeout)
 return self def do(self, command):
 self.assure(visible)
 command()
 return self 
 def click(self):
 return self.do(lambda: self.found.click()) even when not needed wait always
  62. 62. def refind(self):
 self.found = self.finder()
 return self.found def assure(self, condition):
 self.found = wait_for(self, condition, config.timeout)
 return self def do(self, command):
 try:
 self.refind()
 command()
 except (WebDriverException):
 self.assure(visible)
 command()
 return self 
 def click(self):
 return self.do(lambda: self.found.click()) smarter:)
  63. 63. def refind(self):
 self.found = self.finder()
 return self.found def assure(self, condition):
 self.found = wait_for(self, condition, config.timeout)
 return self def do(self, command):
 try:
 self.refind()
 command()
 except (WebDriverException):
 self.assure(visible)
 command()
 return self 
 def click(self):
 return self.do(lambda: self.found.click()) smarter:) Makes Selene as fast as Selenium “with research” :)
  64. 64. def refind(self):
 self.found = self.finder()
 return self.found def assure(self, condition):
 self.found = wait_for(self, condition, config.timeout)
 return self def do(self, command):
 try:
 self.refind()
 command()
 except (WebDriverException):
 self.assure(visible)
 command()
 return self 
 def click(self):
 return self.do(lambda: self.found.click())
  65. 65. def refind(self):
 self.found = self.finder()
 return self.found def assure(self, condition):
 self.found = wait_for(self, condition, config.timeout)
 return self def do(self, command):
 try:
 self.refind()
 command()
 except (WebDriverException):
 self.assure(visible)
 command()
 return self 
 def click(self):
 return self.do(lambda: self.found.click()) What if I sometimes…
 I want “raw selenium” speed?
  66. 66. 
 def cash(self):
 self.is_cached = True
 self.finder = lambda: self.found
 return self f def do(self, command):
 try: if not self.is_cached:
 self.refind()
 command()
 # ...
 self.assure(visible)
 command()
 return self 
 def click(self):
 return self.do(lambda: self.found.click()) Introducing simple cashing
  67. 67. 
 def cash(self):
 self.is_cached = True
 self.finder = lambda: self.found
 return self f def do(self, command):
 try: if not self.is_cached:
 self.refind()
 command()
 # ...
 self.assure(visible)
 command()
 return self 
 def click(self):
 return self.do(lambda: self.found.click()) Introducing simple cashing Making Selene almost as fast as raw Selenium :)
  68. 68. Proof Demo
  69. 69. Total Laziness
  70. 70. todo_list = s('#todo-list') 
 active_tasks = todo_list.find_all(‘.active’) 
 first_completed_task = todo_list.find(‘.completed')
 
 visit('http://todomvc4tasj.herokuapp.com')
 # ... Usage
  71. 71. tasks = ss(‘#todo-list>li') 
 first_task = tasks.get(1) 
 task_a = tasks.find(exact_text(“a")) 
 visible_tasks = tasks.filter(visible)
 
 visit('http://todomvc4tasj.herokuapp.com')
 # ... Usage
  72. 72. visible_tasks = ss(‘#todo-list>li').filter(visible)
 
 visit('http://todomvc4tasj.herokuapp.com')
 # ...
  73. 73. def ss(css_selector_or_locator):
 return SElementsCollection(css_selector_or_locator)
  74. 74. class SElementsCollection(...):
 def __init__(self, css_selector_or_locator, ..., ...,):
 self.locator = parse(css_selector_or_locator)
 ...
 
 def finder(self):
 return ...
 
 def filter(self, condition):
 return ... ...
  75. 75. class SElementsCollection(...):
 def __init__(self, css_selector_or_locator, ..., ...,):
 self.locator = parse(css_selector_or_locator)
 ...
 
 def finder(self):
 return ...
 
 def filter(self, condition):
 return ... ...
  76. 76. locator = lambda index: '%s[%s]' % (self.locator, index)
 webelements = self.context.find_elements(*self.locator)
 return [SElement(locator(index)).cash_with(element)
 for index, element in enumerate(webelements)] SElementsCollection#finder
  77. 77. locator = lambda index: '%s[%s]' % (self.locator, index)
 webelements = self.context.find_elements(*self.locator)
 return [SElement(locator(index)).cash_with(element)
 for index, element in enumerate(webelements)] SElementsCollection#finder
  78. 78. locator = lambda index: '%s[%s]' % (self.locator, index)
 webelements = self.context.find_elements(*self.locator)
 return [SElement(locator(index)).cash_with(element)
 for index, element in enumerate(webelements)] SElementsCollection#finder
  79. 79. class SElementsCollection(...):
 def __init__(self, css_selector_or_locator, ..., ...,):
 self.locator = parse(css_selector_or_locator)
 ...
 
 def finder(self):
 return ...
 
 def filter(self, condition):
 return FilteredSElementsCollection(self, condition) ...
  80. 80. class FilteredSElementsCollection(SElementsCollection):
 def __init__(self, selements_collection, condition):
 self.coll = selements_collection
 self.condition = condition
 # ...
 
 def finder(self):
 filtered_elements = ...
 return filtered_elements
  81. 81. class FilteredSElementsCollection(SElementsCollection):
 def __init__(self, selements_collection, condition):
 self.coll = selements_collection
 self.condition = condition
 # ...
 
 def finder(self):
 filtered_elements = ...
 return filtered_elements
  82. 82. class FilteredSElementsCollection(SElementsCollection):
 def __init__(self, selements_collection, condition):
 self.coll = selements_collection
 self.condition = condition
 # ...
 
 def finder(self):
 filtered_elements = ...
 return filtered_elements
  83. 83. self.coll = selements_collection
 self.condition = condition
 locator = "(%s).filter(%s)" % (
 self.coll.locator,
 self.condition.__class__.__name__)
 SElementsCollection.__init__(self, ('selene', locator))
 # ... FilteredSElementsCollection#__init__
  84. 84. self.coll = selements_collection
 self.condition = condition
 locator = "(%s).filter(%s)" % (
 self.coll.locator,
 self.condition.__class__.__name__)
 SElementsCollection.__init__(self, ('selene', locator))
 # ... FilteredSElementsCollection#__init__
  85. 85. filtered_elements = [selement for selement in self.coll
 if self.condition(selement)] 
 return filtered_elements FilteredSElementsCollection#finder
  86. 86. Meta-programming?
  87. 87. class SElement(...):
 def __init__(self, ..., context=...): # … def finder(self):
 return self.context.find_element(*self.locator) # ...
 
 class SElementsCollection(...):
 def __init__(self, ..., context=..., selement_class=…): # ...
 # ...
  88. 88. def __init__(self, ..., context=RootSElement()):
  89. 89. class RootSElement(object):
 def __getattr__(self, item):
 return getattr(selene.tools.get_driver(), item) The most innocent example :)
  90. 90. def __getattr__(self, item):
 return self.do(lambda: getattr(self.found, item)) # VS def click(self):
 return self.do(lambda: self.found.click())
 
 def submit(self):
 return self.do(lambda: self.found.submit())
 
 def clear(self):
 return self.do(lambda: self.found.clear()) ... Proxying vs Overriding
  91. 91. def __getattr__(self, item):
 return self.do(lambda: getattr(self.found, item)) # VS def click(self):
 return self.do(lambda: self.found.click())
 
 def submit(self):
 return self.do(lambda: self.found.submit())
 
 def clear(self):
 return self.do(lambda: self.found.clear()) ... Proxying vs Overriding
  92. 92. Widgets
  93. 93. class Task(SElement):
 def delete(self):
 self.hover()
 self.s(".destroy").click()
 
 
 def test_custom_selement():
 given_active("a", "b")
 Task("#todo-list>li:nth-child(1)").delete() Just works :)
  94. 94. class Task(SElement):
 def delete(self):
 self.hover()
 self.s(".destroy").click()
 
 
 def test_custom_selement():
 given_active("a", "b")
 Task("#todo-list>li:nth-child(1)").delete() Just works :)
  95. 95. Because… class SElement(...):
 def __init__(self, ..., context=RootSElement()):
 #...
 def finder(self):
 return self.context.find_element(*self.locator)
 
 def s(self, css_selector_or_locator):
 return SElement(css_selector_or_locator, context=self) #...
  96. 96. Because… class SElement(...):
 def __init__(self, ..., context=RootSElement()):
 #...
 def finder(self):
 return self.context.find_element(*self.locator)
 
 def s(self, css_selector_or_locator):
 return SElement(css_selector_or_locator, context=self) #...
  97. 97. Collection of widgets class Task(SElement):
 def delete(self):
 self.hover()
 self.s(".destroy").click()
 
 def test_selements_collection_of_custom_selements():
 given_active("a", "b", "c")
 for task in ss("#todo-list>li", of=Task): task.delete() ss("#todo-list>li", of=Task).should_be(empty)
  98. 98. Collection of widgets class Task(SElement):
 def delete(self):
 self.hover()
 self.s(".destroy").click()
 
 def test_selements_collection_of_custom_selements():
 given_active("a", "b", "c")
 for task in ss("#todo-list>li", of=Task): task.delete() ss("#todo-list>li", of=Task).should_be(empty)
  99. 99. class SElementsCollection(...): 
 def __init__(self, ..., ...):
 ...
 ...
 ...
 
 def finder(self): ...
 return [ SElement(locator(index)).cash_with(element)
 for index, element in enumerate(webelements)]
 ... recalling basic implementation…
  100. 100. class SElementsCollection(...): 
 def __init__(self, ..., ..., of=SElement):
 ...
 self.wrapper_class = of
 ...
 
 def finder(self): ...
 return [ self.wrapper_class(locator(index)).cash_with(element)
 for index, element in enumerate(webelements)]
 ... needs more…
  101. 101. and sometimes even much more… including meta-programming…
  102. 102. Collection of widgets: selection of one of them visible_tasks = ss("#todo-list>li", of=Task).filter(visible) ... ...
 task = visible_tasks.find(exact_text("a")): task.delete() ...
  103. 103. class SElementsCollectionElementByCondition(SElement):
 def __init__(self, selements_collection, condition):
 self.coll = selements_collection
 self.condition = condition
 locator = "(%s).found_by(%s)" % (
 self.coll.locator,
 self.condition.__class__.__name__)
 ...
 
 def finder(self):
 for selement in self.coll:
 if self.condition(selement):
 return selement.found find(exact_text("a")) =>
  104. 104. self.coll = selements_collection
 self.condition = condition
 locator = "(%s).found_by(%s)" % (
 self.coll.locator,
 self.condition.__class__.__name__)
 SElementsCollectionElementByCondition.__init__(self, (“selene”, locator))
 ... SElementsCollectionElementByCondition#__init__
  105. 105. self.coll = selements_collection
 self.condition = condition
 locator = "(%s).found_by(%s)" % (
 self.coll.locator,
 self.condition.__class__.__name__)
 SElementsCollectionElementByCondition.__init__(self, (“selene”, locator))
 extend(self, self.coll.wrapper_class, ("selene", locator)) SElementsCollectionElementByCondition#__init__
  106. 106. def extend(obj, cls, *init_args, **init_kwargs):
 obj.__class__ = type(obj.__class__.__name__, (obj.__class__, cls), {})
 cls.__init__(obj, *init_args, **init_kwargs) Dynamic class extension
  107. 107. def extend(obj, cls, *init_args, **init_kwargs):
 obj.__class__ = type(obj.__class__.__name__, (obj.__class__, cls), {})
 cls.__init__(obj, *init_args, **init_kwargs) Dynamic class extension
  108. 108. def extend(obj, cls, *init_args, **init_kwargs):
 obj.__class__ = type(obj.__class__.__name__, (obj.__class__, cls), {})
 cls.__init__(obj, *init_args, **init_kwargs) Dynamic class extension e.g. to initialise probable widget’s subelements
  109. 109. class Task(SElement):
 def init(self):
 self.destroy_button = self.s(".destroy")
 ...
 
 ...
 task = ss("#todo-list>li", of=Task).find(text("a")): task.hover() task.destroy_button.click() ... like in this example ;)
  110. 110. __init__ vs init ? o_O
  111. 111. class SElement(...):
 def __init__(self): ...
 if hasattr(self, 'init'):
 self.init() __init__ vs init ? o_O
  112. 112. Proxying vs dynamic extension SElement(...) def __getattr__(self, item):
 return self.do(lambda: getattr(self.found, item)) SElementsCollectionElementByCondition(SElement) __init__ extend(self, self.coll.wrapper_class, ('selene', locator)) # VS SElement(...) def __getattr__(self, item):
 return self.do(lambda: getattr(self.found, item)) SElementsCollectionElementByCondition(SElement) __init__ extend(self, self.coll.wrapper_class, ('selene', locator))
  113. 113. Install & Docs
  114. 114. $ pip install selene
  115. 115. https://github.com/yashaka/selene
  116. 116. visit(‘/questions_and_answers') 
 s('#question').set('<YOUR QUESTION>’).press_enter() 
 ss('.answer').should_not_be(empty)
  117. 117. github.com/yashaka github.com/yashaka/selene yashaka@gmail.com @yashaka Thank You

×