Test Automation Symphony
E2E Web Testing with Python, Pytest and Selenium Webdriver
By Alejandro Serrano
Add Value
Manual tests
Ideal Test Automation Pyramid
Ice Cream Cone Anti-pattern
Unit tests
Integration tests
UI tests
UI tests
Integration tests
Unit tests
Manual tests
Mimic how people tend to use the system under test
Python
Pytest
Selenium
Design
Patterns
def test_login_successful(driver):
username_field = driver.find_element(By.ID, 'username')
username_field.clear()
username_field.send_keys('admin')
password_field = driver.find_element(By.ID, 'password')
password_field.clear()
password_field.send_keys('Test1234')
login_btn = driver.find_element(By.XPATH, '//input[@value="LOGIN"]')
login_btn.click()
def test_login_successful(driver):
username_field = driver.find_element(By.ID, 'username')
username_field.clear()
username_field.send_keys('admin')
# Unhelpful comment that most probably will be ignored
time.sleep(1)
password_field = driver.find_element(By.ID, 'password')
password_field.clear()
password_field.send_keys('Test1234')
login_btn = driver.find_element(By.XPATH, '//input[@value="LOGIN"]')
login_btn.click()
def test_login_successful(driver):
username_field = driver.find_element(By.ID, 'username')
username_field.clear()
username_field.send_keys('admin')
# Unhelpful comment that most probably will be ignored
time.sleep(3)
password_field = driver.find_element(By.ID, 'password')
password_field.clear()
password_field.send_keys('Test1234')
login_btn = driver.find_element(By.XPATH, '//input[@value="LOGIN"]')
login_btn.click()
def test_login_successful(driver):
username_field = driver.find_element(By.ID, 'username')
username_field.clear()
username_field.send_keys('admin')
# Unhelpful comment that most probably will be ignored.
time.sleep(3)
password_field = driver.find_element(By.ID, 'password')
password_field.clear()
password_field.send_keys('Test1234')
login_btn = driver.find_element(By.XPATH, '//input[@value="LOGIN"]')
login_btn.click()
# First click does not work sometimes.
login_btn.click()
<div class="login-box">
<form>
<input type="text" class="form_input" data-test="username" id="user-name" placeholder="Username" value="">
<input type="password" class="form_input" data-test="password" id="password" placeholder="Password" value="">
<input type="submit" class="btn_action" value="LOGIN">
</form>
</div>
<div class="login-box">
<form>
<input type="text" class="form_input" data-test="username" id="user-name" placeholder="Username" value="">
<input type="password" class="form_input" data-test="password" id="password" placeholder="Password" value="">
<input type="submit" class="btn_action" value="LOGIN">
</form>
</div>
<div class="login-box">
<form>
<input type="text" class="form_input" data-test="username" id="user-name" placeholder="Username" value="">
<input type="password" class="form_input" data-test="password" id="password" placeholder="Password" value="">
<input type="submit" class="btn_action" value="LOGIN">
</form>
</div>
<div class="login-box">
<form>
<input type="text" class="form_input" data-test="username" id="user-name" placeholder="Username" value="">
<input type="password" class="form_input" data-test="password" id="password" placeholder="Password" value="">
<input type="submit" class="btn_action" value="LOGIN">
</form>
</div>
demo
drivers
src
login
tests
login
demo
drivers
src
login
tests
login
__init__.py
login.py
__init__.py
test_login.py
__init__.py
conftest.py
demo
drivers
src
login
tests
login
__init__.py
login.py
__init__.py
test_login.py
chromedriver
__init__.py
conftest.py
C:\> cd demo
C:\demo> python -m venv e2e
C:\demo> C:\demo\e2e\Scripts\activate.bat
(e2e) C:\demo>
(e2e) C:\demo> pip install -U pytest
(e2e) C:\demo> pip install -U selenium
# demo/tests/conftest.py
@pytest.fixture
def driver():
pass
# demo/tests/conftest.py
import pytest
import selenium.webdriver
@pytest.fixture
def driver():
pass
# demo/tests/conftest.py
import pytest
import selenium.webdriver
@pytest.fixture
def driver():
driver = selenium.webdriver.Chrome()
driver.implicitly_wait(10)
driver.get('https://www.saucedemo.com/')
# demo/tests/conftest.py
import pytest
import selenium.webdriver
@pytest.fixture
def driver():
driver = selenium.webdriver.Chrome()
driver.implicitly_wait(10)
driver.get('https://www.saucedemo.com/')
yield driver
driver.quit()
# demo/src/login/login.py
class LoginPage():
pass
def test_login_successful(driver):
username_field = driver.find_element(By.ID, 'username')
username_field.clear()
username_field.send_keys('admin')
# Unhelpful comment that most probably will be ignored.
time.sleep(3)
password_field = driver.find_element(By.ID, 'password')
password_field.clear()
password_field.send_keys('Test1234')
login_btn = driver.find_element(By.XPATH, '//input[@value="LOGIN"]')
login_btn.click()
# First click does not work sometimes.
login_btn.click()
# demo/src/login/login.py
class LoginPage():
def __init__(self, driver):
self.driver = driver
# demo/src/login/login.py
class LoginPage():
def __init__(self, driver):
self.driver = driver
def login(self, username, password):
pass
def test_login_successful(driver):
username_field = driver.find_element(By.ID, 'username')
username_field.clear()
username_field.send_keys('admin')
# Unhelpful comment that most probably will be ignored.
time.sleep(3000)
password_field = driver.find_element(By.ID, 'password')
password_field.clear()
password_field.send_keys('Test1234')
login_btn = driver.find_element(By.XPATH, '//input[@value="LOGIN"]')
login_btn.click()
# First click does not work sometimes.
login_btn.click()
# demo/src/login/login.py
from selenium.webdriver.common.by import By
class LoginPage():
USERNAME_FLD = (By.ID, 'user-name')
PASSWORD_FLD = (By.ID, 'password')
LOGIN_BTN = (By.XPATH, '//input[@value="LOGIN"]')
def __init__(self, driver):
self.driver = driver
def login(self, username, password):
self._set_text(username, *self.USERNAME_FLD)
self._set_text(password, *self.PASSWORD_FLD)
self.driver.find_element(*self.LOGIN_BTN).click()
def _set_text(self, value, locator_type, locator):
input_field = self.driver.find_element(locator_type, locator)
input_field.clear()
input_field.send_keys(value)
<div class="login-box">
<form>
<input type="text" class="form_input" data-test="username" id="user-name" placeholder="Username" value="">
<input type="password" class="form_input" data-test="password" id="password" placeholder="Password" value="">
<input type="submit" class="btn_action" value="LOGIN">
</form>
</div>
# demo/tests/login/test_login.py
def test_login_successful():
pass
# demo/tests/login/test_login.py
import pytest
def test_login_successful():
pass
# demo/tests/login/test_login.py
import pytest
from src.login.login import LoginPage
def test_login_successful():
pass
# demo/tests/login/test_login.py
import pytest
from src.login.login import LoginPage
def test_login_successful(driver):
login_page = LoginPage(driver)
login_page.login(username="standard_user", password="secret_sauce")
def test_login_successful(driver):
username_field = driver.find_element(By.ID, 'username')
username_field.clear()
username_field.send_keys('admin')
# Unhelpful comment that most probably will be ignored.
time.sleep(3)
password_field = driver.find_element(By.ID, 'password')
password_field.clear()
password_field.send_keys('Test1234')
login_btn = driver.find_element(By.XPATH, '//input[@value="LOGIN"]')
login_btn.click()
# First click does not work sometimes.
login_btn.click()
# demo/tests/conftest.py
import pytest
import selenium.webdriver
@pytest.fixture
def driver():
driver = selenium.webdriver.Chrome()
driver.implicitly_wait(10)
driver.get('https://www.saucedemo.com/')
yield driver
driver.quit()
<h3 data-test="error">
<button class="error-button">
<svg aria-hidden="true" data-prefix="fas" data-icon="times-circle" class="svg-inline--fa fa-times-circle fa-w-16 fa-2x " role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm121.6 313.1c4.7 4.7 4.7 12.3 0 17L338 377.6c-4.7 4.7-12.3 4.7-17 0L256 312l-65.1 65.6c-4.7 4.7-12.3 4.7-17 0L134.4 338c-4.7-4.7-4.7-12.3 0-17l65.6-65-65.6-65.1c-4.7-4.7-4.7-12.3 0-17l39.6-39.6c4.7-4.7 12.3-4.7 17 0l65 65.7 65.1-65.6c4.7-4.7 12.3-4.7 17 0l39.6 39.6c4.7 4.7 4.7 12.3 0 17L312 256l65.6 65.1z">
</path>
</svg>
</button>Epic sadface: Username and password do not match any user in this service
</h3>
# demo/src/login/login.py
from selenium.webdriver.common.by import By
class LoginPage():
USERNAME_FLD = (By.ID, 'user-name')
PASSWORD_FLD = (By.ID, 'password')
LOGIN_BTN = (By.XPATH, '//input[@value="LOGIN"]')
def __init__(self, driver):
self.driver = driver
def login(self, username, password):
self._set_text(username, *self.USERNAME_FLD)
self._set_text(password, *self.PASSWORD_FLD)
self.driver.find_element(*self.LOGIN_BTN).click()
def _set_text(self, value, locator_type, locator):
input_field = self.driver.find_element(locator_type, locator)
input_field.clear()
input_field.send_keys(value)
# demo/src/login/login.py
from selenium.webdriver.common.by import By
class LoginPage():
USERNAME_FLD = (By.ID, 'user-name')
PASSWORD_FLD = (By.ID, 'password')
LOGIN_BTN = (By.XPATH, '//input[@value="LOGIN"]')
ERROR_MESSAGE = (By.XPATH, '//*[@data-test="error"]')
EXPECTED_ERROR_MESSAGE = 'Epic sadface: Username and password do not match any user in this service'
def __init__(self, driver):
self.driver = driver
def login(self, username, password):
self._set_text(username, *self.USERNAME_FLD)
self._set_text(password, *self.PASSWORD_FLD)
self.driver.find_element(*self.LOGIN_BTN).click()
def _set_text(self, value, locator_type, locator):
input_field = self.driver.find_element(locator_type, locator)
input_field.clear()
input_field.send_keys(value)
# demo/src/login/login.py
def login(self, username, password):
self._set_text(username, *self.USERNAME_FLD)
self._set_text(password, *self.PASSWORD_FLD)
self.driver.find_element(*self.LOGIN_BTN).click()
def _set_text(self, value, locator_type, locator):
input_field = self.driver.find_element(locator_type, locator)
input_field.clear()
input_field.send_keys(value)
def _get_text(self, locator_type, locator):
return self.driver.find_element(locator_type, locator).text
# demo/src/login/login.py
def login(self, username, password):
self._set_text(username, *self.USERNAME_FLD)
self._set_text(password, *self.PASSWORD_FLD)
self.driver.find_element(*self.LOGIN_BTN).click()
def _set_text(self, value, locator_type, locator):
input_field = self.driver.find_element(locator_type, locator)
input_field.clear()
input_field.send_keys(value)
def _get_text(self, locator_type, locator):
return self.driver.find_element(locator_type, locator).text
class LoginError(Exception):
pass
# demo/src/login/login.py
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
...
def _get_text(self, locator_type, locator):
return self.driver.find_element(locator_type, locator).text
def _check_login_successful(self):
try:
WebDriverWait(self.driver, WAIT_FOR_ELEMENT).until(
expected_conditions.invisibility_of_element_located(self.LOGIN_BTN))
except:
WebDriverWait(self.driver, WAIT_FOR_ELEMENT).until(
expected_conditions.visibility_of_element_located(self.ERROR_MESSAGE))
error_message = self._get_text(*self.ERROR_MESSAGE)
raise LoginError(error_message)
class LoginError(Exception):
pass
# demo/src/login/login.py
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
...
WAIT_FOR_ELEMENT = 2
...
def _check_login_successful(self):
try:
WebDriverWait(self.driver, WAIT_FOR_ELEMENT).until(
expected_conditions.invisibility_of_element_located(self.LOGIN_BTN))
except:
WebDriverWait(self.driver, WAIT_FOR_ELEMENT).until(
expected_conditions.visibility_of_element_located(self.ERROR_MESSAGE))
error_message = self._get_text(*self.ERROR_MESSAGE)
raise LoginError(error_message)
class LoginError(Exception):
pass
# demo/src/login/login.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
WAIT_FOR_ELEMENT = 2
class LoginPage():
USERNAME_FLD = (By.ID, 'user-name')
PASSWORD_FLD = (By.ID, 'password')
LOGIN_BTN = (By.XPATH, '//input[@value="LOGIN"]')
ERROR_MESSAGE = (By.XPATH, '//*[@data-test="error"]')
EXPECTED_ERROR_MESSAGE = 'Epic sadface: Username and password do not match any user in this service'
def __init__(self, driver):
self.driver = driver
def login(self, username, password):
self._set_text(username, *self.USERNAME_FLD)
self._set_text(password, *self.PASSWORD_FLD)
self.driver.find_element(*self.LOGIN_BTN).click()
self._check_login_successful()
# demo/tests/login/test_login.py
import pytest
from src.login.login import LoginPage
def test_login_successful(driver):
login_page = LoginPage(driver)
login_page.login(username="standard_user", password="secret_sauce")
# demo/tests/login/test_login.py
import pytest
from src.login.login import LoginPage
def test_login_successful(driver):
login_page = LoginPage(driver)
login_page.login(username="standard_user", password="secret_sauce")
def test_login_unsuccessful(driver):
login_page = LoginPage(driver)
with pytest.raises(LoginError):
login_page.login(username="locked_out_user", password="secret_sauce")
POM Repository
Tests
Webdriver
Base Framework
Monolithic Architecture
System Under Test
POM Repository
Webdriver
Test Env Creator
"Services" Architecture
System Under Test
Reporting Tool
Custom Framework Glue Code
Tests
Let computers do what they are good at…
Freeing up humans to create…
to dream…
to change the world!