Selenide Alternative in Python
Introducing Selene
Preface…
Alternative in PythonSelenide
Introducing Selene
Selenide
Selenide =
Selenide = ?
Selenide = …
web automation tool
…
Selenide = …
web automation tool
selenium wrapper
Selenide = …
web automation tool
selenium wrapper
Selenide = Effective
web test automation tool
Selenide = Effective
web test automation tool
being also
selenium wrapper
Selenide = Effective
web test automation tool
=
?
Selenide = Effective
web test automation tool
=
tool to automate
web UI tests logic
Selenide = Effective
web test automation tool
=
tool to automate
web UI tests logic
not browser
Selenide = Effective
web test automation tool
=
tool to automate
web UI tests logic
not browser
(it should be already
automated;)
Selenide = Effective
web test automation tool
=
tool to automate
web UI tests logic
not browser
concise API
…
…
…
Selenide = Effective
web test automation tool
=
tool to automate
web UI tests logic
not browser
concise API
waiting search
…
…
Selenide = Effective
web test automation tool
=
tool to automate
web UI tests logic
not browser
concise API
waiting search
waiting asserts
…
Selenide = Effective
web test automation tool
=
tool to automate
web UI tests logic
not browser
concise API
waiting search
waiting asserts
dynamic elements
Selenide = Effective
web test automation tool
=
tool to automate
web UI tests logic
not browser
concise API
waiting search
waiting asserts
dynamic elements
? = Effective
web test automation tool
=
tool to automate
web UI tests logic
not browser
concise API
waiting search
waiting asserts
dynamic elements
Selene = Effective
web test automation tool
=
tool to automate
web UI tests logic
not browser
concise API
waiting search
waiting asserts
dynamic elements
“UI Tests Logic” Automation with Selene
class TestTodoMVC(BaseTest):

def test_filter_active_tasks(self):



# visit page


# add "a"
# add "b"
# add "c"

# tasks should be "a", "b", "c"



# toggle "b"



# filter active

# tasks should be "a", "c"
UI Tests Logic


# visit page


# add "a"
# add "b"
# add "c"

# tasks should be "a", "b", "c"



# toggle "b"



# filter active

# tasks should be "a", "c"
UI Tests Logic


visit("https://todomvc4tasj.herokuapp.com/")


# add "a"
# add "b"
# add "c"

# tasks should be "a", "b", "c"



# toggle "b"



# filter active

# tasks should be "a", "c"
UI Tests Logic


visit("https://todomvc4tasj.herokuapp.com/")


s("#new-todo").set("a").press_enter()
# add "b"
# add "c"

# tasks should be "a", "b", "c"



# toggle "b"



# filter active

# tasks should be "a", "c"
UI Tests Logic


visit("https://todomvc4tasj.herokuapp.com/")


s("#new-todo").set("a").press_enter()
# add "b"
# add "c"

# tasks should be "a", "b", "c"



# toggle "b"



# filter active

# tasks should be "a", "c"
UI Tests Logic
Concise API


visit("https://todomvc4tasj.herokuapp.com/")


s("#new-todo").set("a").press_enter()
# add "b"
# add "c"

# tasks should be "a", "b", "c"



# toggle "b"



# filter active

# tasks should be "a", "c"
UI Tests Logic
Concise API
search element “short-cut”


visit("https://todomvc4tasj.herokuapp.com/")


s("#new-todo").set("a").press_enter()
# add "b"
# add "c"

# tasks should be "a", "b", "c"



# toggle "b"



# filter active

# tasks should be "a", "c"
UI Tests Logic
Concise API
default conversion to “by css” locator


visit("https://todomvc4tasj.herokuapp.com/")


s("#new-todo").set("a").press_enter()
# add "b"
# add "c"

# tasks should be "a", "b", "c"



# toggle "b"



# filter active

# tasks should be "a", "c"
UI Tests Logic
Concise API
with implicit clear()


visit("https://todomvc4tasj.herokuapp.com/")


s("#new-todo").set("a").press_enter()
# add "b"
# add "c"

# tasks should be "a", "b", "c"



# toggle "b"



# filter active

# tasks should be "a", "c"
UI Tests Logic
Concise API
chainable methods


visit("https://todomvc4tasj.herokuapp.com/")


s("#new-todo").set("a").press_enter()
# add "b"
# add "c"

# tasks should be "a", "b", "c"



# toggle "b"



# filter active

# tasks should be "a", "c"
UI Tests Logic
Dynamic Elements
search actually starts here


visit("https://todomvc4tasj.herokuapp.com/")


s("#new-todo").set("a").press_enter()
# add "b"
# add "c"

# tasks should be "a", "b", "c"



# toggle "b"



# filter active

# tasks should be "a", "c"
UI Tests Logic
Waiting Search
with implicit waiting for visibility


visit("https://todomvc4tasj.herokuapp.com/")


s("#new-todo").set("a").press_enter()
s("#new-todo").set("b").press_enter()
s("#new-todo").set("c").press_enter()

# tasks should be "a", "b", "c"



# toggle "b"



# filter active

# tasks should be "a", "c"
UI Tests Logic


visit("https://todomvc4tasj.herokuapp.com/")


s("#new-todo").set("a").press_enter()
s("#new-todo").set("b").press_enter()
s("#new-todo").set("c").press_enter()

ss("#todo-list li").should_have(exact_texts("a", "b",
"c"))



# toggle "b"



# filter active

# tasks should be "a", "c"
UI Tests Logic
Waiting Asserts


visit("https://todomvc4tasj.herokuapp.com/")


s("#new-todo").set("a").press_enter()
s("#new-todo").set("b").press_enter()
s("#new-todo").set("c").press_enter()

ss("#todo-list li").should_have(exact_texts("a", "b",
"c"))



# toggle "b"



# filter active

# tasks should be "a", "c"
UI Tests Logic
Waiting Asserts
aka “explicit waits”


visit("https://todomvc4tasj.herokuapp.com/")


s("#new-todo").set("a").press_enter()
s("#new-todo").set("b").press_enter()
s("#new-todo").set("c").press_enter()

ss("#todo-list li").should_have(texts("a", "b", "c"))



# toggle "b"



# filter active

# tasks should be "a", "c"
UI Tests Logic
handy conditions
Waiting Asserts


visit("https://todomvc4tasj.herokuapp.com/")


s("#new-todo").set("a").press_enter()
s("#new-todo").set("b").press_enter()
s("#new-todo").set("c").press_enter()

ss("#todo-list li").should_have(texts("a", "b", "c"))



ss("#todo-list li").findBy(text("b")).find(".toggle").click()



# filter active

# tasks should be "a", "c"
UI Tests Logic
Concise API & Waiting Search


visit("https://todomvc4tasj.herokuapp.com/")


s("#new-todo").set("a").press_enter()
s("#new-todo").set("b").press_enter()
s("#new-todo").set("c").press_enter()

ss("#todo-list li").should_have(texts("a", "b", "c"))



ss("#todo-list li").findBy(text("b")).find(".toggle").click()



# filter active

# tasks should be "a", "c"
UI Tests Logic
laconic inner collection search by text
Concise API & Waiting Search


visit("https://todomvc4tasj.herokuapp.com/")


s("#new-todo").set("a").press_enter()
s("#new-todo").set("b").press_enter()
s("#new-todo").set("c").press_enter()

ss("#todo-list li").should_have(texts("a", "b", "c"))



ss("#todo-list li").findBy(text("b")).find(".toggle").click()



# filter active

# tasks should be "a", "c"
UI Tests Logic
laconic inner collection search by text
instead of bulky xpath locators
Concise API & Waiting Search


visit("https://todomvc4tasj.herokuapp.com/")


s("#new-todo").set("a").press_enter()
s("#new-todo").set("b").press_enter()
s("#new-todo").set("c").press_enter()

ss("#todo-list li").should_have(texts("a", "b", "c"))



ss("#todo-list li").findBy(text("b")).find(".toggle").click()



# filter active

# tasks should be "a", "c"
UI Tests Logic
inner element search
Concise API & Waiting Search


visit("https://todomvc4tasj.herokuapp.com/")


s("#new-todo").set("a").press_enter()
s("#new-todo").set("b").press_enter()
s("#new-todo").set("c").press_enter()

ss("#todo-list li").should_have(texts("a", "b", "c"))



ss("#todo-list li").findBy(text("b")).s(".toggle").click()



# filter active

# tasks should be "a", "c"
UI Tests Logic
handy alias
Concise API & Waiting Search


visit("https://todomvc4tasj.herokuapp.com/")


s("#new-todo").set("a").press_enter()
s("#new-todo").set("b").press_enter()
s("#new-todo").set("c").press_enter()

ss("#todo-list li").should_have(texts("a", "b", "c"))



ss("#todo-list li").findBy(text("b")).s(".toggle").click()



s(by_link_text("Active")).click()

# tasks should be "a", "c"
UI Tests Logic


visit("https://todomvc4tasj.herokuapp.com/")


s("#new-todo").set("a").press_enter()
s("#new-todo").set("b").press_enter()
s("#new-todo").set("c").press_enter()

ss("#todo-list li").should_have(texts("a", "b", "c"))



ss("#todo-list li").findBy(text("b")).s(".toggle").click()



s(by_link_text("Active")).click()

# tasks should be "a", "c"
UI Tests Logic
custom locators


visit("https://todomvc4tasj.herokuapp.com/")


s("#new-todo").set("a").press_enter()
s("#new-todo").set("b").press_enter()
s("#new-todo").set("c").press_enter()

ss("#todo-list li").should_have(texts("a", "b", "c"))



ss("#todo-list li").findBy(text(“b")).s(".toggle").click()



s(by_link_text("Active")).click()

tasks.filterBy(visible).should_have(texts("a", "c"))
UI Tests Logic
Concise API & Waiting Asserts


visit("https://todomvc4tasj.herokuapp.com/")


s("#new-todo").set("a").press_enter()
s("#new-todo").set("b").press_enter()
s("#new-todo").set("c").press_enter()

ss("#todo-list li").should_have(texts("a", "b", "c"))



ss("#todo-list li").findBy(text(“b")).s(".toggle").click()



s(by_link_text("Active")).click()

tasks.filterBy(visible).should_have(texts("a", "c"))
UI Tests Logic
Concise API & Waiting Asserts
filtering collection


visit("https://todomvc4tasj.herokuapp.com/")


s("#new-todo").set("a").press_enter()
s("#new-todo").set("b").press_enter()
s("#new-todo").set("c").press_enter()

ss("#todo-list li").should_have(texts("a", "b", "c"))



ss("#todo-list li").findBy(text(“b")).s(".toggle").click()



s(by_link_text("Active")).click()

tasks.filterBy(visible).should_have(texts("a", "c"))
UI Tests Logic
Page steps for even
more readable code?
#tasks.py
def visit():

tools.visit("https://todomvc4tasj.herokuapp.com/")

def add(*task_texts):

for text in task_texts:
s("#new-todo").set(text).press_enter()



def filter_active():

s(by_link_text(”Active”)).click()



def filter_completed():

s(by_link_text(”Completed”)).click()
#tasks.py
tasks = ss("#todo-list>li")



...
def toggle(task_text):

tasks.findBy(text(task_text)).find(".toggle").click()





def should_be(*task_texts):

tasks.filterBy(visible).should_have(texts(*task_texts))
tasks = ss("#todo-list>li")



...
tasks = ss("#todo-list>li")



...
Dynamic Elements
tasks = ss("#todo-list>li")



...
Dynamic Elements
possible because search does not start here
tasks = ss("#todo-list>li")



...
Dynamic Elements
possible because search does not start here
in fact, ss creates “lazy elements proxy” ;)
class TestTodoMVC(BaseTest):



def test_filter_tasks(self):



tasks.visit()



tasks.add("a", "b", "c")

tasks.should_be("a", "b", "c")



tasks.toggle("b")



tasks.filter_active ()

tasks.should_be("a", "c")



tasks.filter_completed ()

tasks.should_be("b")
Unfortunately still some
“automating browser” low level
code needed for setup
class TestTodoMVC(BaseTest):



def test_filter_tasks(self):



tasks.visit()



tasks.add("a", "b", "c")

tasks.should_be("a", "b", "c")



tasks.toggle("b")



tasks.filter_active ()

tasks.should_be("a", "c")



tasks.filter_completed ()

tasks.should_be("b")
@pytest.fixture(scope='class')

def setup(request):

set_driver(webdriver.Firefox())



def teardown():

get_driver().quit()



request.addfinalizer(teardown)





@pytest.mark.usefixtures("setup")

class BaseTest(object):

pass
Customisation
config.app_host = "http://mydomain.com"
...
visit("/subpage")
s("#new-todo").should_be(enabled)
Default Timeouts Behaviour
will wait until 4 seconds
s("#new-todo").should_be(enabled, timeout=10)
Custom
or
config.timeout = 10
...
s("#new-todo").should_be(enabled)
Widgets for even more
structural OOP code?
Selene as htmlelements alternative ;)
Standalone custom element aka Widget
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()

ss("#todo-list>li").assure(texts("b"))
Standalone custom element aka Widget
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()

ss("#todo-list>li").assure(texts("b"))
Standalone custom element aka Widget
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()

ss("#todo-list>li").assure(texts("b"))
Standalone custom element aka Widget
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()

ss("#todo-list>li").assure(texts("b"))
Standalone custom element aka Widget
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()

ss("#todo-list>li").assure(texts("b"))
Autocompletion as a bonus
Standalone custom element aka Widget
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()

ss("#todo-list>li").assure(texts("b"))
Standalone custom element aka Widget
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()

ss("#todo-list>li").assure(texts("b"))
Standalone custom element aka Widget
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()

ss("#todo-list>li").assure(texts("b"))
Inheriting all Selene element’s behaviour
Standalone custom element aka Widget
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()

ss("#todo-list>li").assure(texts("b"))
search inside self
Standalone custom element aka Widget
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()

ss("#todo-list>li").assure(texts("b"))
search inside self
def test_custom_selements():

given_active("a", "b")



page = TodoMVC()



page.tasks.find(text("b")).toggle()

page.clear_completed()

page.tasks.assure(texts("a"))
PageObjects of “plural” Widgets:
Usage
def test_nested_custom_selements():

given_active("a", "b")



page = TodoMVC()



page.tasks.find(text("b")).toggle()

page.clear_completed()

page.tasks.assure(texts("a"))
PageObjects of “plural” Widgets: Usage
Autocompletion?
Autocompletion?
NO :’(
PageObjects of “plural” Widgets: Implementation
class TodoMVC(object):

def __init__(self):

self.tasks = ss("#todo-list>li").of(self.Task)


def clear_completed(self):

s(“#clear-completed").click()



class Task(SElement):

def toggle(self):

self.s(".toggle").click()

return self
PageObjects of “plural” Widgets: Implementation
class TodoMVC(object):

def __init__(self):

self.tasks = ss("#todo-list>li").of(self.Task)


def clear_completed(self):

s(“#clear-completed").click()



class Task(SElement):

def toggle(self):

self.s(".toggle").click()

return self
Just keep balance…
class SelectList(SElement):

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

super(SelectList, self).__init__(locator, context)



def set(self, value):

self.assure(visible)

Select(self.found).select_by_visible_text(value)

return self
...
s('[name="first_name"]').set('Iakiv')

s('[name="last_name"]').set('Kramarenko')
SelectList('#salutation').set('mr')
Good case for reusable Widget
class SelectList(SElement):

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

super(SelectList, self).__init__(locator, context)



def set(self, value):

self.assure(visible)

Select(self.found).select_by_visible_text(value)

return self
...
s('[name="first_name"]').set('Iakiv')

s('[name="last_name"]').set('Kramarenko')
SelectList('#salutation').set('mr')
Good case for reusable Widget
Doubtful…
class TodoMVC(object):

def __init__(self):

self.tasks = ss("#todo-list>li").of(self.Task)



class Task(SElement):

def toggle(self):

self.s(".toggle").click()

return self
vs
tasks = ss("#todo-list>li")

def toggle(task_text):
tasks.findBy(text(task_text)).find(".toggle").click()
Doubtful…
vs
tasks.toggle("b")
main.tasks.find(text("b")).toggle()
Keep It Simple Stupid!
KISS Automation
Plain Pages over Pages of Nested Widgets
…
…
KISS Automation
Plain Pages over Pages of Nested Widgets
Page Modules over PageObjects
…
KISS Automation
Plain Pages over Pages of Nested Widgets
Page Modules over PageObjects
Automating UI tests logic over low level coding
Q&A
github.com/yashaka
github.com/yashaka/selene
yashaka@gmail.com
@yashaka
Thank You

Selenide alternative in Python - Introducing Selene [SeleniumCamp 2016]