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

Preview:

Citation preview

Selenide Alternative in PracticeImplementation & Lessons Learned

Preface…

Implementation & Lessons Learned

Selenide Alternative in Practice

Selenide

Selenide = ?

Selenide = Effectiveweb test automation tool

=

tool to automate web UI tests logic

not browser(it should be already

automated;)

Selenide = Effectiveweb test automation tool

=

tool to automate web UI tests logic

not browser

concise API

Selenide = Effectiveweb test automation tool

=

tool to automate web UI tests logic

not browser

concise API

waiting search

Selenide = Effectiveweb test automation tool

=

tool to automate web UI tests logic

not browser

concise API

waiting search

waiting asserts

Selenide = Effectiveweb test automation tool

=

tool to automate web UI tests logic

not browser

concise API

waiting search

waiting asserts

dynamic elements

Selenide = Effectiveweb test automation tool

=

tool to automate web UI tests logic

not browser

concise API

waiting search

waiting asserts

dynamic elements

Selenide Alternative?

concise API

waiting search

waiting asserts

dynamic elements

Selenide Alternative?

concise API

waiting search

waiting asserts

dynamic elements

How should it work?

Selenide Alternative?

concise API

waiting search

waiting asserts

dynamic elements

How should it work? How to build?

With examples in

ProvisoPartial sacrificing

DRY, privatisation, etc. for simplification of examples

Concise API

driver.find_element(By.XPATH, "//*[contains(text(),'task')]")

# vs

s(by_xpath("//*[contains(text(),'task')]"))

# vs

s(with_text('task'))

driver.find_element(By.XPATH, "//*[contains(text(),'task')]")

# vs

s(by_xpath("//*[contains(text(),'task')]"))

# vs

s(with_text('task'))

DRY locator helpers

driver.find_element(By.XPATH, "//*[contains(text(),'task')]")

# vs

s(by_xpath("//*[contains(text(),'task')]"))

# vs

s(with_text('task'))

driver.find_element(By.XPATH, "//*[contains(text(),'task')]")

# vs

s(by_xpath("//*[contains(text(),'task')]"))

# vs

s(with_text('task'))

OOP

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

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

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

driver.find_element(By.XPATH, "//*[contains(text(),'task')]")

# vs

s(by_xpath("//*[contains(text(),'task')]")).

# vs

s(with_text('task'))

Modular

OOPfrom 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’)

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’)

s(by_css(‘#new-todo’))

# vs/or

s('#new-todo')

Convenient defaults

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’)

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

driver.find_element_by_css_selector('#new-todo')\

.send_keys('task', Keys.ENTER)

# vs

s('#new-todo').send_keys('task').press_enter()

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

s(‘#new-todo’).clear().send_keys(‘task’)

# vs

s(‘#new-todo’).set(‘task’)

“Short-cut” methods

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)

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()

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()

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()

?

Dynamic Elements

Dynamic Elementsaka Lazy Proxy Elements

new_todo = s('#new-todo')

def s(css_selector_or_locator): return SElement(css_selector_or_locator)

Element Factory

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())

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())

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())

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())

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())

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”

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”

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), ...)

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), ...)

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), ...)

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), ...)

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()

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()

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

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 simpleand secure

but less powerful

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())

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())

Speed?

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

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

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 neededwait always

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:)

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” :)

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())

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?

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

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 :)

Proof Demo

Total Laziness

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

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

visible_tasks = ss(‘#todo-list>li').filter(visible) visit('http://todomvc4tasj.herokuapp.com') # ...

def ss(css_selector_or_locator): return SElementsCollection(css_selector_or_locator)

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 ...

...

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 ...

...

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

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

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

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) ...

class FilteredSElementsCollection(SElementsCollection): def __init__(self, selements_collection, condition): self.coll = selements_collection self.condition = condition # ... def finder(self): filtered_elements = ... return filtered_elements

class FilteredSElementsCollection(SElementsCollection): def __init__(self, selements_collection, condition): self.coll = selements_collection self.condition = condition # ... def finder(self): filtered_elements = ... return filtered_elements

class FilteredSElementsCollection(SElementsCollection): def __init__(self, selements_collection, condition): self.coll = selements_collection self.condition = condition # ... def finder(self): filtered_elements = ... return filtered_elements

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__

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__

filtered_elements = [selement for selement in self.coll if self.condition(selement)] return filtered_elements

FilteredSElementsCollection#finder

Meta-programming?

class SElement(...): def __init__(self, ..., context=...): # …

def finder(self): return self.context.find_element(*self.locator)

# ...class SElementsCollection(...): def __init__(self, ..., context=..., selement_class=…): # ... # ...

def __init__(self, ..., context=RootSElement()):

class RootSElement(object): def __getattr__(self, item): return getattr(selene.tools.get_driver(), item)

The most innocent example :)

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

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

Widgets

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 :)

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 :)

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)

#...

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)

#...

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)

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)

class SElementsCollection(...): def __init__(self, ..., ...): ... ... ... def finder(self): ... return [ SElement(locator(index)).cash_with(element) for index, element in enumerate(webelements)]

...

recalling basic implementation…

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…

and sometimes even much more… including meta-programming…

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() ...

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")) =>

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__

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__

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

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

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

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 ;)

__init__ vs init ? o_O

class SElement(...): def __init__(self): ... if hasattr(self, 'init'): self.init()

__init__ vs init ? o_O

Proxying vs dynamic extensionSElement(...) 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))

Install & Docs

$ pip install selene

https://github.com/yashaka/selene

visit(‘/questions_and_answers') s('#question').set('<YOUR QUESTION>’).press_enter() ss('.answer').should_not_be(empty)

github.com/yashaka

github.com/yashaka/selene

yashaka@gmail.com

@yashaka

Thank You

Recommended