Why advanced patterns handle real-world complexity in PyTest - Automation Benefits in Action
import pytest from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class LoginPage: def __init__(self, driver): self.driver = driver self.username_input = (By.ID, 'username') self.password_input = (By.ID, 'password') self.login_button = (By.ID, 'loginBtn') def login(self, username, password): self.driver.find_element(*self.username_input).clear() self.driver.find_element(*self.username_input).send_keys(username) self.driver.find_element(*self.password_input).clear() self.driver.find_element(*self.password_input).send_keys(password) self.driver.find_element(*self.login_button).click() class DashboardPage: def __init__(self, driver): self.driver = driver self.profile_link = (By.ID, 'profileLink') def wait_for_dashboard(self): WebDriverWait(self.driver, 10).until( EC.url_contains('/dashboard') ) def go_to_profile(self): self.driver.find_element(*self.profile_link).click() class ProfilePage: def __init__(self, driver): self.driver = driver self.display_name_input = (By.ID, 'displayName') self.save_button = (By.ID, 'saveProfile') self.success_message = (By.ID, 'successMsg') def update_display_name(self, new_name): self.driver.find_element(*self.display_name_input).clear() self.driver.find_element(*self.display_name_input).send_keys(new_name) self.driver.find_element(*self.save_button).click() def wait_for_success_message(self): WebDriverWait(self.driver, 10).until( EC.visibility_of_element_located(self.success_message) ) def get_success_message_text(self): return self.driver.find_element(*self.success_message).text class BasePage: def __init__(self, driver): self.driver = driver self.logout_button = (By.ID, 'logoutBtn') def logout(self): self.driver.find_element(*self.logout_button).click() @pytest.fixture def driver(): driver = webdriver.Chrome() driver.implicitly_wait(5) yield driver driver.quit() @pytest.mark.usefixtures('driver') def test_login_update_profile_logout(driver): driver.get('https://example.com/login') login_page = LoginPage(driver) login_page.login('testuser', 'Test@1234') dashboard_page = DashboardPage(driver) dashboard_page.wait_for_dashboard() assert '/dashboard' in driver.current_url, 'Dashboard URL not loaded after login' dashboard_page.go_to_profile() profile_page = ProfilePage(driver) profile_page.update_display_name('Test User Updated') profile_page.wait_for_success_message() success_text = profile_page.get_success_message_text() assert success_text == 'Profile updated successfully', 'Success message not shown or incorrect' base_page = BasePage(driver) base_page.logout() WebDriverWait(driver, 10).until( EC.url_contains('/login') ) assert '/login' in driver.current_url, 'Login page not shown after logout'
This test script uses the Page Object Model to organize code for each page: LoginPage, DashboardPage, ProfilePage, and BasePage for logout. This keeps locators and actions in one place, making maintenance easier.
We use explicit waits (WebDriverWait) to wait for pages to load or messages to appear, which handles real-world delays and dynamic content.
The pytest fixture driver sets up and tears down the browser cleanly.
Assertions check the URL after login and logout, and verify the success message text after profile update, ensuring the test validates expected outcomes clearly.
This approach handles real-world complexity by separating concerns, waiting properly, and making tests readable and maintainable.
Now add data-driven testing with 3 different user credentials and display names