In this tutorial, we will learn how to use vigilant-kit
with pytest
in real-like project
with POM pattern and fixtures.
This is a simplified example, but it is enough to understand how it works and be able to apply and use it on your project!
Good luck!
Source code: vigilant-kit and pytest
Pytest docs: pytest
To install vigilant-kit
in your project, run this command:
pip install vigilant-kit
Install pytest
:
pip install pytest
How to install and run Selenium Server described here Install Selenium Server
What is POM pattern? Page Object Model (POM) is a design pattern, popularly used in test automation that creates Object Repository for web UI elements. The advantage of the model is that it reduces code duplication and improves test maintenance. Under this model, for each web page in the application, there should be a corresponding Page Class. This Page class will identify the WebElements of that web page and also contains Page methods which perform operations on those WebElements.
For our basic ecommerce project we want to have next POM objects:
- Login Page
- Category List page
- Product Page
- Cart Page
- Checkout Page
Our test target will be this project: https://www.saucedemo.com/ It is simple, yet perfect for our purposes' resource.
Directory - separation between tests type is a good way to manage your tests.
Functional in functional/
directory, unit tests inside unit/
directory, etc.
Inside functional/
dir we need to create two more dirs:
fixtures/
- for our fixtures.
pom/
- for our Page Objects Model objects.
Like this:
tests/functional
- fixtures/
- pom/
- test_name.py
Also, don't forget about our configuration file .vigilant.env
. You can place it in root of your project,
or as example - inside tests/functional/
directory.
Example of .vigilant.env
configuration:
SELENIUM_HOST=http://127.0.0.1:4444/wd/hub
SELENIUM_BROWSER=firefox
BASE_URL=https://www.saucedemo.com
WAIT_TIMEOUT=10
LOGGER_LEVEL=INFO
I like to start testing from deciding and describing my Page Objects. So when I will write actual tests I will already use correct locators from correct Object Repository. So for which pages we will create POM objects? Check below:
- Login Page
- Category List page
- Product Page
- Cart Page
- Checkout Page
Let's start with Login Page, but first we need to do something else - create BasePage object. BasePage class will be parent class for all other pages.
functional/pom/base_page.py
from vigilant.driver.vigilant_driver import VigilantDriver
class BasePage:
def __init__(self, driver):
self.driver: VigilantDriver = driver
Using BasePage
class let's create LoginPage
.
- Login page
functional/pom/login_page.py
from .base_page import BasePage
class LoginPage(BasePage):
USERNAME_INPUT = '//input[@id="user-name"]'
PASSWORD_INPUT = '//input[@id="password"]'
LOGIN_BUTTON = '//input[@id="login-button"]'
USERNAME_DATA = 'standard_user'
PASSWORD_DATA = 'secret_sauce'
def __init__(self, driver):
super().__init__(driver)
def fill_in_and_login(self):
self.driver.fill_field(self.USERNAME_INPUT, self.USERNAME_DATA)
self.driver.fill_field(self.PASSWORD_INPUT, self.PASSWORD_DATA)
self.driver.click(self.LOGIN_BUTTON)
- Category List page
functional/pom/category_list_page.py
from .base_page import BasePage
class CategoryListPage(BasePage):
PRODUCT_CARD = '//div[@class="inventory_item"]'
PRODUCT_NAME = '//div[@class="inventory_item_name"]'
ADD_TO_CART_BUTTON = '//button[text()="Add to cart"]'
PRODUCT_PRICE = '//div[@class="inventory_item_price"]'
def __init__(self, driver):
super().__init__(driver)
- Product Detail page
functional/pom/product_detail_page.py
from .base_page import BasePage
class ProductDetailPage(BasePage):
PRODUCT_PRICE = '//div[@class="inventory_details_price"]'
ADD_TO_CART_BUTTON = '//button[text()="Add to cart"]'
BACK_TO_CLP_BUTTON = '//button[@id="back-to-products"]'
def __init__(self, driver):
super().__init__(driver)
- Cart page
functional/pom/cart_page.py
from .base_page import BasePage
class CartPage(BasePage):
CART_ICON_LINK = '//a[@class="shopping_cart_link"]'
PRODUCT_PRICE = '//div[@class="inventory_item_price"]'
PRODUCT_NAME = '//div[@class="inventory_item_name"]'
REMOVE_PRODUCT = '//button[text()="Remove"]'
CONTINUE_SHOPPING_BUTTON = '//button[@id="continue-shopping"]'
CHECKOUT_BUTTON = '//button[@id="checkout"]'
def __init__(self, driver):
super().__init__(driver)
- Checkout page
functional/pom/cart_page.py
from .base_page import BasePage
class CheckoutPage(BasePage):
# First step checkout
FIRST_NAME = '//input[@id="first-name"]'
LAST_NAME = '//input[@id="last-name"]'
ZIP_CODE = '//input[@id="postal-code"]'
CONTINUE_BUTTON = '//input[@id="continue"]'
# Second step checkout
FINISH_BUTTON = '//button[@id="finish"]'
CANCEL_BUTTON = '//button[@id="cancel"]'
# Checkout complete
BACK_HOME_BUTTON = '//button[@id="back-to-products"]'
def __init__(self, driver):
super().__init__(driver)
Now let's create Login test.
functional/test_ecommerce.py
import pytest
from vigilant.driver.vigilant_driver import VigilantDriver
from pom.login_page import LoginPage
@pytest.fixture(scope="module")
def driver():
vd = VigilantDriver()
yield vd
vd.quit()
def test_login(driver):
login_page = LoginPage(driver)
driver.get_page('/')
login_page.fill_in_and_login()
Run it:
pytest tests/functional/test_ecommerce.py
In the example above, we use data from LoginPage
class, which allow us to use elements locators and
method for login in. So if we will have more tests in the future which require login functionality - we will use
LoginPage
object, and if something in the project will change - we will need to refactor it only in one place, not in
the all tests that were affected by changes.
Good! We are now logged in. Go next to the Category List Page and make sure that we can add projects from CLP.
functional/test_ecommerce.py
...
from pom.category_list_page import CategoryListPage
...
def test_add_product_from_clp(driver):
clp = CategoryListPage(driver)
driver.assertions.see(clp.PRODUCT_CARD + '[1]') # +[1] is Xpath trick to say that we looking for FIRST available element with this selector
driver.click(clp.ADD_TO_CART_BUTTON + '[1]')
driver.assertions.see_text('Remove')
Great. Now let's add one more tests to check if customer is able to complete order. It will be more complicated test
if compare to previous one, but also it will show how to use different object in one tests:
functional/test_ecommerce.py
...
from pom.cart_page import CartPage
from pom.checkout_page import CheckoutPage
...
def test_complete_order(driver):
cart = CartPage(driver)
checkout = CheckoutPage(driver)
# Go to cart page and assert correct url
driver.click(cart.CART_ICON_LINK)
driver.assertions.see_in_url('/cart.html')
# From cart page go to checkout and complete it
driver.click(cart.CHECKOUT_BUTTON)
driver.assertions.see_in_url('/checkout-step-one.html')
# Fill personal info and go to Second step
checkout.fill_personal_info()
driver.click(checkout.CONTINUE_BUTTON)
driver.assertions.see_in_url('checkout-step-two.html')
# Place order
driver.click(checkout.FINISH_BUTTON)
driver.assertions.see_in_url('/checkout-complete.html')
Full result:
import pytest
from vigilant.driver.vigilant_driver import VigilantDriver
from pom.login_page import LoginPage
from pom.category_list_page import CategoryListPage
from pom.cart_page import CartPage
from pom.checkout_page import CheckoutPage
@pytest.fixture(scope="module")
def driver():
vd = VigilantDriver()
yield vd
vd.quit()
def test_login(driver):
login_page = LoginPage(driver)
driver.get_page('/')
login_page.fill_in_and_login()
driver.assertions.see_in_url('/inventory.html')
def test_add_product_from_clp(driver):
clp = CategoryListPage(driver)
driver.assertions.see(
clp.PRODUCT_CARD + '[1]') # +[1] is Xpath trick to say that we looking for FIRST available element with this selector
driver.click(clp.ADD_TO_CART_BUTTON + '[1]')
driver.assertions.see_text('Remove')
def test_complete_order(driver):
cart = CartPage(driver)
checkout = CheckoutPage(driver)
# Go to cart page and assert correct url
driver.click(cart.CART_ICON_LINK)
driver.assertions.see_in_url('/cart.html')
# From cart page go to checkout and complete it
driver.click(cart.CHECKOUT_BUTTON)
driver.assertions.see_in_url('/checkout-step-one.html')
# Fill personal info and go to Second step
checkout.fill_personal_info()
driver.click(checkout.CONTINUE_BUTTON)
driver.assertions.see_in_url('checkout-step-two.html')
# Place order
driver.click(checkout.FINISH_BUTTON)
driver.assertions.see_in_url('/checkout-complete.html')
Let's run it:
pytest tests/functional/test_ecommerce.py
Result should be this:
==================================================== test session starts ============================================
platform linux -- Python 3.10.6, pytest-7.2.0, pluggy-1.0.0
rootdir: /home/ivan/projects/vigilant_lab
collected 3 items
tests/functional/test_ecommerce.py ... [100%]
===================================================== 3 passed in 3.62s =============================================
Great! Everything work as expected.
What's next?
As example, you can try to move fixture that setup our browser, in to the fixture/
directory:
@pytest.fixture(scope="module")
def driver():
vd = VigilantDriver()
yield vd
vd.quit()