I'm new to Selenium and python and am creating a framework using pytest from scratch.
The URL
https://opensource-demo.orangehrmlive.com/web/index.php/auth/login
The username and password is documented on the test site login page (and below):
Username: Admin
Password: admin123
I am unable to click on Admin or ESS option for the User Role field.
HTML for the dropdown
Most of the data is from an Excel workbook.
My code is below
test_addUser.py
import time
import pytest
import self
from PageObject.LoginPage import LoginPage
from PageObject.AddUserPage import addUserPage
from TestData.LoginPageData import DataLoginPage
from Utlities.BaseClass import BaseClass
class TestValidateAddUser(BaseClass):
def test_addUser(self, getData):
lgnPage = LoginPage(self.driver)
lgnPage.addUsername().send_keys(getData["Username"])
lgnPage.entrePassword().send_keys(getData["Password"])
lgnPage.submitBtn().click()
self.verifyClickElement(addUserPage.adminbtn)
adminUser = addUserPage(self.driver)
adminUser.clickAddbtn().click()
self.verifyPresenceOfAllEle(adminUser.UserRole()).click()
self.visibilityByElement(adminUser.dropDownOpt()).click()
self.verifyClickElement(adminUser.clickDrpOptions()).click()
time.sleep(5)
@pytest.fixture(params=DataLoginPage.getTestData("addUser"))
def getData(self, request):
return request.param''']
AddUserPage.py
from selenium.webdriver.common.by import By
class addUserPage:
def __init__(self, driver):
self.driver = driver
adminbtn = (By.XPATH, "//span[text()='Admin']")
addbtn = (By.XPATH, "//button[@class='oxd-button oxd-button--medium oxd-button--secondary']")
addUserRole = (By.XPATH, "//div[@class='oxd-grid-item oxd-grid-item--gutters'])[1]")
dropDownOptions = (By.XPATH, "//div[@role='listbox']")
selectOptions = (By.XPATH, "//div[@role='listbox']//span[text()='ESS']")
empName = (By.XPATH, "//label[contains(., 'Employee Name')]//following::div[1]")
StatusSel = (By.XPATH, "//label[contains(., 'Status')]//following::div[1]//div[@class='oxd-select- text-input']")
addUserName = (By.XPATH, "//label[contains(., 'Username')]//following::div[1]")
addPassword = (By.XPATH, "//label[contains(., 'Password')]//following::div[1]")
addCPassword = (By.XPATH, "//label[contains(., 'Confirm Password')]//following::div[1]")
btnSave = (By.XPATH, "//div[@class = 'oxd-form-actions']/button[2]")
def clickAdmin(self):
return self.driver.find_element(*addUserPage.adminbtn)
def clickAddbtn(self):
return self.driver.find_element(*addUserPage.addbtn)
def UserRole(self):
return self.driver.find_element(*addUserPage.addUserRole)
def dropDownOpt(self):
return self.driver.find_element(*addUserPage.dropDownOptions).__getattribute__('outerHTML')
def clickDrpOptions(self):
return self.driver.find_element(*addUserPage.selectOptions)`
BaseClass.py
import pytest
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.select import Select
from selenium.webdriver.support.wait import WebDriverWait
@pytest.mark.usefixtures("setupBrowser")
class BaseClass:
def verifyTextPresence(self, text):
element = WebDriverWait(self.driver, 15).until(
expected_conditions.presence_of_element_located((By.XPATH, text)))
def verifyPresenceOfAllEle(self, text):
element = WebDriverWait(self.driver, 10).until(
expected_conditions.presence_of_all_elements_located((By.XPATH, text)))
def verifyClickElement(self, locator):
element = WebDriverWait(self.driver, 10).until(
expected_conditions.element_to_be_clickable(locator))
element.click()
def selectOptionByText(self, locator, text):
sel = Select(locator)
sel.select_by_visible_text(text)
def visibilityByElement(self, text):
drpDwnlist = WebDriverWait(self.driver, 10).until(
expected_conditions.visibility_of_all_elements_located((By.XPATH, text)))
drpDwnlist.click()
I was able click on the dropdown but unable to select the desired values using several XPath attempts. I created a reusable function in BaseClass.py but I am getting errors. I want to be able to select dropdown based on the data present in Excel, e.g. use the "ESS" value from Excel.
Your page objects aren't bad. Better than most I've seen here on SO but I have some suggestions and some fixes:
Don't pass back elements on the page, e.g. don't pass back the Submit button on the login page just for it to be clicked. Create a method Login(self, username, password)
in LoginPage.py
that fills in the username and password fields with the supplied info and then clicks the submit button. A page object is supposed to provide a "pretty" interface to the page while hiding the details of the page itself. Allowing the elements to be accessed outside of the page object breaks that model.
In your BaseClass, don't restrict the user to only using XPaths. Pass in a By
locator instead of a string text
. That way you open up your BaseClass methods to any locator type, e.g. By.ID or By.CSS_SELECTOR which should be preferred over XPaths. You do it in some methods but not consistently.
There's not much point to some of your BaseClass methods, e.g. they are one-liner wrappers around provided Selenium methods, e.g. verifyTextPresence()
is just a WebDriverWait
. If you aren't doing anything more complex, just put the WebDriverWait
in the page object itself.
Use more accurate and more descriptive method names. For example, verifyClickElement()
doesn't verify anything... it actually just clicks the element. In that case, just call it click()
. visibilityByElement()
actually clicks an element too so it's repetitive with the above. That method won't work BTW... you are using EC..visibility_of_all_elements_located()
which returns a collection of elements, not just one and then you do drpDwnlist.click()
which is going to throw an exception because you can't click a collection. You could do drpDwnlist[0].click()
but if you're going to do that then chance your EC
to the singular version.
I have a framework that I use from time to time so I just created the page objects and test to match your scenario in my format and got it working... including setting the User Role and all other fields on the Add User page.
NOTE: You will need to install pytest and if you want to run your tests in parallel, pytest-xdist.
I created enums for the Add User dropdowns just because I prefer them to trying to handle strings because you can avoid typos, capitalization issues, etc. You obviously don't have to use them but I highly suggest them in cases like these.
There's a number of files below, each one is labelled.
\common\enums\LeftNavOptions.py
from enum import Enum
# Left navigation menu options, available on most/many pages?
class LeftNavOption(Enum):
ADMIN = "Admin"
PIM = "PIM"
\common\enums\Statuses.py
from enum import Enum
# Status options from the Add User page
class Statuses(Enum):
DISABLED = "Disabled"
ENABLED = "Enabled"
\common\enums\UserRoles.py
from enum import Enum
# User Role options from the Add User page
class UserRoles(Enum):
ADMIN = "Admin"
ESS = "ESS"
\page_objects\AddUserPage.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from common.enums.Statuses import Statuses
from common.enums.UserRoles import UserRoles
from .BasePage import BasePage
class AddUserPage(BasePage):
_confirm_password_locator = (By.XPATH, "//div[./div/label[text()='Confirm Password']]//input")
_employee_name_locator = (By.XPATH, "//div[./div/label[text()='Employee Name']]//input")
_password_locator = (By.XPATH, "//div[./div/label[text()='Password']]//input")
_save_button_locator = (By.XPATH, "//button[text()=' Save ']")
_status_dropdown_locator = (By.XPATH, "//div[./div/label[text()='Status']]//i")
_user_role_dropdown_locator = (By.XPATH, "//div[./div/label[text()='User Role']]//i")
_username_locator = (By.XPATH, "//div[./div/label[text()='Username']]//input")
def save(self):
self.click(self._save_button_locator)
def set_confirm_password(self, password: str):
self.send_keys(self._confirm_password_locator, password)
def set_employee_name(self, employee_name: str):
self.send_keys(self._employee_name_locator, employee_name)
def set_password(self, password: str):
self.send_keys(self._password_locator, password)
def set_status(self, status: Statuses):
self.click(self._status_dropdown_locator)
self.click((By.XPATH, f"//div[./div/label[text()='Status']]//*[text()='{status.value}']"))
def set_username(self, username: str):
self.send_keys(self._username_locator, username)
def set_user_role(self, user_role: UserRoles):
self.click(self._user_role_dropdown_locator)
self.click((By.XPATH, f"//div[./div/label[text()='User Role']]//*[text()='{user_role.value}']"))
\page_objects\BasePage.py
from datetime import datetime, timedelta
from selenium import webdriver
from selenium.common.exceptions import (ElementClickInterceptedException,
StaleElementReferenceException,
TimeoutException)
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
class BasePage:
def __init__(self, driver: webdriver):
self.driver = driver
def click(self, locator: By, time_out_in_seconds: int = 10):
expire = datetime.now() + timedelta(seconds=time_out_in_seconds)
while datetime.now() < expire:
try:
WebDriverWait(self.driver, time_out_in_seconds).until(EC.element_to_be_clickable(locator)).click()
return
except (ElementClickInterceptedException, StaleElementReferenceException):
# do nothing, loop again
pass
raise Exception(
f"Not able to click element <{locator}> within {time_out_in_seconds}s.")
def element_exists(self, locator: By):
return len(self.find_elements(locator)) > 0
def find_element(self, locator: By):
return self.driver.find_element(*locator)
def find_element(self, expected_condition: EC, time_out_in_seconds: int = 10):
return WebDriverWait(self.driver, time_out_in_seconds).until(expected_condition)
def find_elements(self, locator: By):
return self.driver.find_elements(*locator)
def find_elements(self, expected_condition: EC, time_out_in_seconds: int = 10):
return WebDriverWait(self.driver, time_out_in_seconds).until(expected_condition)
def get_text(self, locator: By):
time_out_in_seconds = 10
expire = datetime.now() + timedelta(seconds=time_out_in_seconds)
while datetime.now() < expire:
try:
return WebDriverWait(self.driver, time_out_in_seconds).until(EC.visibility_of_element_located(locator)).text
except (StaleElementReferenceException):
# do nothing, loop again
pass
raise Exception(f"Not able to get .Text from element <{locator}> within {time_out_in_seconds}s.")
def get_value(self, locator: By):
time_out_in_seconds = 10
expire = datetime.now() + timedelta(seconds=time_out_in_seconds)
while datetime.now() < expire:
try:
return WebDriverWait(self.driver, time_out_in_seconds).until(EC.visibility_of_element_located(locator)).get_attribute("value")
except (StaleElementReferenceException):
# do nothing, loop again
pass
raise Exception(f"Not able to get 'value' from element <{locator}> within {time_out_in_seconds}s.")
def is_displayed(self, locator: By, time_out_in_seconds: int = 10):
try:
self.find_element(EC.visibility_of_element_located(locator), time_out_in_seconds)
return True
except (TimeoutException):
return False
def send_keys(self, locator: By, text: str, time_out_in_seconds: int = 10):
expire = datetime.now() + timedelta(seconds=time_out_in_seconds)
while datetime.now() < expire:
try:
WebDriverWait(self.driver, time_out_in_seconds).until(EC.element_to_be_clickable(locator)).send_keys(text)
return
except (StaleElementReferenceException):
# do nothing, loop again
pass
raise Exception(
f"Not able to .send_keys() to element <{locator}> within {time_out_in_seconds}s.")
\page_objects\DashboardPage.py
from selenium.webdriver.common.by import By
from .BasePage import BasePage
class DashboardPage(BasePage):
_login_button_locator = (By.XPATH, "//button[text()=' Login ']")
_password_locator = (By.CSS_SELECTOR, "input[name='password']")
_username_locator = (By.CSS_SELECTOR, "input[name='username']")
def left_nav_link(self, username: str, password: str):
self.send_keys(self._username_locator, username)
self.send_keys(self._password_locator, password)
self.click(self._login_button_locator)
\page_objects\LeftNavPanel.py
from selenium.webdriver.common.by import By
from .BasePage import BasePage
from common.enums.LeftNavOptions import LeftNavOption
class LeftNavPanel(BasePage):
def click_navigation_option(self, left_nav_option: LeftNavOption):
self.click((By.XPATH, f"//ul//span[text()='{left_nav_option.value}']"))
\page_objects\LoginPage.py
from selenium.webdriver.common.by import By
from .BasePage import BasePage
class LoginPage(BasePage):
_login_button_locator = (By.XPATH, "//button[text()=' Login ']")
_password_locator = (By.CSS_SELECTOR, "input[name='password']")
_username_locator = (By.CSS_SELECTOR, "input[name='username']")
def login(self, username: str, password: str):
self.send_keys(self._username_locator, username)
self.send_keys(self._password_locator, password)
self.click(self._login_button_locator)
\page_objects\UserManagementPage.py
from selenium.webdriver.common.by import By
from .BasePage import BasePage
class UserManagementPage(BasePage):
_add_button_locator = (By.XPATH, "//button[text()=' Add ']")
def add_user(self):
self.click(self._add_button_locator)
\tests\test_addUser.py
from common.enums.LeftNavOptions import LeftNavOption
from common.enums.Statuses import Statuses
from common.enums.UserRoles import UserRoles
from page_objects.AddUserPage import AddUserPage
from page_objects.LeftNavPanel import LeftNavPanel
from page_objects.LoginPage import LoginPage
from page_objects.UserManagementPage import UserManagementPage
def test_addUser(setup):
username = "Admin"
password = "admin123"
employee_name = "Charlotte Smith"
user_password = "password"
user_username = "c.smith"
status = Statuses.ENABLED
user_role = UserRoles.ADMIN
LoginPage(setup).login(username, password)
LeftNavPanel(setup).click_navigation_option(LeftNavOption.ADMIN)
UserManagementPage(setup).add_user()
add_user_page = AddUserPage(setup)
add_user_page.set_user_role(user_role)
add_user_page.set_employee_name(employee_name)
add_user_page.set_status(status)
add_user_page.set_username(user_username)
add_user_page.set_password(user_password)
add_user_page.set_confirm_password(user_password)
# add_user_page.save()
conftest.py
import pytest
from selenium import webdriver
@pytest.fixture
def setup():
url = "https://opensource-demo.orangehrmlive.com/web/index.php/auth/login"
driver = webdriver.Chrome()
driver.get(url)
driver.maximize_window()
yield driver
driver.close()