diff --git a/requirements.txt b/requirements.txt index c16f7c5..fb0f80f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ selenium==4.35.0 webdriver-manager==4.0.2 +guara==0.0.23 # Only direct dependencies required for running unittest-based Selenium tests. # unittest is in the Python standard library; no need to list it here. \ No newline at end of file diff --git a/tests/test_login_transaction.py b/tests/test_login_transaction.py new file mode 100644 index 0000000..f261971 --- /dev/null +++ b/tests/test_login_transaction.py @@ -0,0 +1,23 @@ +import unittest +from guara import it +from guara.transaction import Application +from tests.base_test import BaseTest +from transactions.login_transaction import UserLogin, UserInMainPage + +class TestLoginTransaction(BaseTest): + + def test_user_can_login_successfully(self): + app = Application(self.driver) + + ( + app.given(UserInMainPage) + .when( + UserLogin, + with_username="test_user", + with_secret="secure_password" + ) + .then(it.Contains, "dashboard") + ) + +if __name__ == "__main__": + unittest.main() diff --git a/transactions/README.md b/transactions/README.md new file mode 100644 index 0000000..4229b53 --- /dev/null +++ b/transactions/README.md @@ -0,0 +1,61 @@ +# Selenium Page Transactions Boilerplate (with Guará) +This repository demonstrates an advanced automation architecture that evolves the traditional **Page Object Model (POM)** into **Page Transactions** using the [Guará](https://github.com/douglasdcm/guara) framework. + +By adding a semantic transaction layer over standard Page Objects, tests become highly readable, decoupled from UI details, and focused entirely on the user's business journey. +## 🚀 Key Benefits +- **Fluent BDD Syntax:** Writes like Gherkin (`given`, `when`, `then`) but runs natively in pure Python code. +- **Better Separation of Concerns:** Page Objects handle HTML element selectors, while Transactions handle business behaviors and workflows. +- **High Maintainability:** Steps are encapsulated. Changes in a user workflow require updating only a single Transaction class rather than multiple test files. + +## 💻 Code Examples +### 1. The Transaction Layer (`transactions/login_transaction.py`) +Transactions group atomic Page Object steps into comprehensive user behaviors. +```python +from guara.transaction import AbstractTransaction +from pages.login_page import LoginPage +from pages.main_page import MainPage + +class UserInMainPage(AbstractTransaction): + def do(self): + main_page = MainPage(self._driver) + main_page.click_sign_in_button() + +class UserLogin(AbstractTransaction): + def __init__(self, driver): + super().__init__(driver) + + def do(self, with_username, with_secret): + login_page = LoginPage(self._driver) + login_page.enter_email(with_username) + login_page.enter_password(with_secret) + login_page.login() + return self._driver.current_url +``` + +### 2. The Test Script (`tests/test_login_transaction.py`) +Tests are purely declarative, describing *what* happens rather than *how* it happens. +```python +import unittest +from guara import it +from guara.transaction import Application +from tests.base_test import BaseTest +from transactions.login_transaction import UserLogin, UserInMainPage + +class TestLoginTransaction(BaseTest): + + def test_user_can_login_successfully(self): + app = Application(self.driver) + + ( + app.given(UserInMainPage) + .when( + UserLogin, + with_username="test_user", + with_secret="secure_password" + ) + .then(it.Contains, "dashboard") + ) + +if __name__ == "__main__": + unittest.main() +``` diff --git a/transactions/__init__.py b/transactions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/transactions/login_transaction.py b/transactions/login_transaction.py new file mode 100644 index 0000000..c68b875 --- /dev/null +++ b/transactions/login_transaction.py @@ -0,0 +1,19 @@ +from guara.transaction import AbstractTransaction +from pages.login_page import LoginPage +from pages.main_page import MainPage + +class UserInMainPage(AbstractTransaction): + def do(self): + main_page = MainPage(self._driver) + main_page.click_sign_in_button() + +class UserLogin(AbstractTransaction): + def __init__(self, driver): + super().__init__(driver) + + def do(self, with_username, with_secret): + login_page = LoginPage(self._driver) + login_page.enter_email(with_username) + login_page.enter_password(with_secret) + login_page.login() + return self._driver.current_url