lemoncheesecake-selenium¶
lemoncheesecake-selenium provides logging facilities to the Python Selenium library for tests written with the lemoncheesecake test framework.
Here is a usage example based on the getting started example of the Selenium Python Binding (unofficial) documentation:
# suites/python_org_search.py
import lemoncheesecake.api as lcc
from lemoncheesecake.matching import *
from lemoncheesecake_selenium import Selector, save_screenshot, is_in_page
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
@lcc.test()
def python_org_search():
driver = webdriver.Firefox()
driver.implicitly_wait(10)
driver.get("http://www.python.org")
check_that("title", driver.title, contains_string("Python"))
selector = Selector(driver)
search_field = selector.by_name("q")
search_field.clear()
search_field.set_text("pycon")
search_field.set_text(Keys.RETURN)
selector.by_xpath("//h3[text()='Results']").check_element(is_in_page())
save_screenshot(driver)
driver.close()
We run the test:
$ lcc run
============================== python_org_search ==============================
OK 1 # python_org_search.python_org_search
Statistics :
* Duration: 10s
* Tests: 1
* Successes: 1 (100%)
* Failures: 0
HTML report : file:///tmp/python_org_search/report/report.html
And here is the resulting HTML report:
Installation¶
Install through pip:
$ pip install lemoncheesecake-selenium
lemoncheesecake-selenium is compatible with Python 3.7-3.10 and Selenium 4.x.
You will also need to install a WebDriver to control your web browser.
Introduction¶
The main feature of the library is to provide a layer above Selenium’s WebElement
that will log the interactions performed on elements (such as clicking, entering text, etc…) and allow various
checking operations (such as verifying the existence of the element, doing matching operations on the DOM node’s text,
etc…), this is the job of the Selection
class.
Selection
instances are obtained through the
Selector
class
which acts like a Selection
factory. A Selector
instance mirrors the Selenium’s
By class such as:
Selector.by_id
will build aSelection
using theBy.ID
locator strategySelector.by_xpath
will build aSelection
using theBy.XPATH
locator strategy… and so on for each locator strategy of
By
Obtaining a Selection¶
As told previously, the Selection
is the main class you’ll have to use in lemoncheesecake-selenium to interact with the underlying
WebElements
. It can be obtained through the Selector
class:
selector = Selector(driver)
selection = selector.by_id("login")
Interacting with elements¶
With the Selection
class you can
click
,
clear
or
set_text
(the equivalent of Selenium’s send_keys
) the element:
selection.set_text("hello")
You can also directly interact with a <select>
element, using the same select_*
and deselect_*
methods as the
Selenium’s
Select
class with methods such as
select_by_value
,
select_by_index
, etc…:
selection.select_by_index(2)
If anything wrong happens (the WebElement
cannot be found, the requested interaction is not possible on that
element, etc..), the underlying Selenium’s exception will be propagated.
You can choose to automatically take a screenshot of the web page when this is happening by setting the class attribute
Selection.screenshot_on_exceptions
to True
(meaning that this behavior will be applied to all Selection
instances):
Selection.screenshot_on_exceptions = True
Checking elements¶
The Selection
class allows you to do checks on the underlying element using
the same check/require/assert logic of lemoncheesecake
with the methods:
where expected
is a Matcher
instance whose
matches
method will
take a WebElement
as argument. lemoncheesecake-selenium provides the following built-in matcher functions:
Examples:
selection.check_element(is_in_page())
selection.check_element(has_text(match_pattern(r"(\d)€")))
selection.check_element(has_attribute("class"))
selection.check_element(has_attribute("class", equal_to("enabled")))
selection.check_element(has_property("text_length"))
selection.check_element(has_property("text_length", equal_to(8)))
selection.check_element(is_displayed())
selection.check_element(is_enabled())
selection.check_element(not_(is_enabled()))
selection.check_element(is_selected())
As these methods look for a WebElement
before calling the matcher on it,
you can check for the non-existence of an element through the following Selection
methods:
Example:
selection.require_no_element()
It is possible to automatically take a screenshot on failed checks (either it’s done by a check_*
, require_*
or
assert_*
method) by setting the class attribute
Selection.screenshot_on_failed_checks
to True
(also meaning that this behavior will be applied to all Selection
instances):
Selection.screenshot_on_failed_checks = True
Explicit waits / Expected condition¶
lemoncheesecake-selenium provides support for
the explicit waits / expected condition
mechanism of Selenium with the following Selection
methods:
Examples:
selection = selector.by_xpath("//button[text()='ok']").\
must_be_waited_until(EC.element_to_be_clickable)
selection = selector.by_id("banner").\
must_be_waited_until_not(EC.visibility_of_element_located, timeout=10)
These two methods assume that the expected condition callable passed in argument takes a locator
as first argument.
They both return self
(the Selection
instance) meaning that they can be chained like in the previous example.
Making screenshots¶
Beyond the Selection.screenshot_on_*
attributes described above, you can also make explicit screenshots with
the save_screenshot
function:
from lemoncheesecake_selenium import ..., save_screenshot
[...]
save_screenshot(driver)
You can also use the save_screenshot_on_exception
context manager to make a screenshot when a WebDriverException
exception occurs:
from lemoncheesecake_selenium import ..., save_screenshot_on_exception
[...]
with save_screenshot_on_exception(driver):
[... your code here ...]
Page Object Model (POM)¶
While lemoncheesecake-selenium does not enforce any design pattern, it plays pretty well with the Page Object Model (POM) design pattern.
Here is how the initial example could be rewritten using this pattern:
# suites/python_org_search_pom.py
import lemoncheesecake.api as lcc
from lemoncheesecake.matching import *
from lemoncheesecake_selenium import Selector, save_screenshot, is_in_page
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
class SearchPage(Selector):
@property
def search_field(self):
return self.by_name("q")
def search(self, value):
field = self.search_field
field.clear()
field.set_text(value)
field.set_text(Keys.RETURN)
return ResultsPage(self.driver)
class ResultsPage(Selector):
@property
def results_header(self):
return self.by_xpath("//h3[text()='Results']")
@lcc.test()
def python_org_search():
driver = webdriver.Firefox()
driver.implicitly_wait(10)
driver.get("http://www.python.org")
check_that("title", driver.title, contains_string("Python"))
search_page = SearchPage(driver)
results_page = search_page.search("pycon")
results_page.results_header.check_element(is_in_page())
save_screenshot(driver)
driver.close()
Changelog¶
The Changelog will tell you about features, improvements and fixes of each version.