I'm practicing web automation in Python with Selenium using the GitHub website, and in my code I've created a unittest with page objects to test logging in, checking for the presence of the username in the entire HTML document and in the app header section and also logging out. However, the problem I'm having is that once Python Selenium has automated logging in, it's clicking the "Create" button that leads to the user's READ.ME page on the user's account main page which I did not specify in my code. My code is divided into four modules; here is the code for my test case, called test.py
:
import unittest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import page
unittest.TestLoader.sortTestMethodsUsing = None
class GitHubTestCase(unittest.TestCase):
"""Tests the user account access features of the GitHub website"""
@classmethod
def setUpClass(cls):
chrome_options = Options()
chrome_options.add_experimental_option("detach", True)
cls.driver = webdriver.Chrome(options=chrome_options)
cls.driver.get("https://github.com")
def test_login(self):
"""Tests the login functionality of GitHub website"""
main_page = page.MainPage(self.driver)
main_page.sign_in_link()
login_credentials = page.LoginPage(self.driver)
login_credentials.username = "jimalu@live.com"
login_credentials.password = "onefaithmanymembers"
login_credentials.form_submission()
def test_username_presence(self):
"""Tests for the presence of the user's account name"""
account_page = page.AccountMainPage(self.driver)
self.assertTrue(account_page.universal_username_presence(),
"Username not found")
account_page.app_header_selector()
self.assertTrue(account_page.app_header_username_presence(),
"Username not found")
account_page.app_header_logout()
def test_user_sign_out(self):
"""Tests the logout functionality of the GitHub website"""
logout_page = page.LogoutPage(self.driver)
logout_page.single_account_sign_out()
@classmethod
def tearDownClass(cls):
cls.driver.close()
if __name__ == "__main__":
unittest.main()
And here is the code for the page.py
module:
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.keys import Keys
from locators import MainPageLocators, LoginPageLocators,
AccountPageLocators, LogoutPageLocators
from elements import BasePageElement
class BasePage():
"""Base class that will initialize the base page for all GitHub
webpages"""
def __init__(self, driver):
self.driver = driver
class UsernameTextElement(BasePageElement):
"""Username class that identifies locator element for entering
username or e-mail address"""
locator = "login_field"
class PasswordTextElement(BasePageElement):
"""Password class that identifies locator element for entering
password"""
locator = "password"
class MainPage(BasePage):
"""The class where home page actions are defined"""
def sign_in_link(self):
"""Selects the link to the login page and clicks it"""
element = self.driver.find_element(*MainPageLocators.SIGN_IN_BUTTON)
element.click()
class LoginPage(BasePage):
"""The class where login page actions are defined"""
username = UsernameTextElement()
password = PasswordTextElement()
def form_submission(self):
"""Submits the login credentials entered"""
element = self.driver.find_element(*LoginPageLocators.SUBMIT_BUTTON)
element.click()
class AccountMainPage(BasePage):
"""The class where all actions for the user's account page are
defined"""
def universal_username_presence(self):
"""Finds the user's username in the main account page
Returns:
bool: A True or False value for the presence of the username in
the webpage
"""
return "SubjectofthePotentate" in self.driver.page_source
def app_header_selector(self):
"""Clicks the button that opens the app header section"""
element = self.driver.find_element(*AccountPageLocators.APP_HEADER_BUTTON)
element.click()
def app_header_username_presence(self):
"""Finds the user's username in the app header
Returns:
bool: A True or False value for the presence of the username in the app header
"""
obj = self.driver
dialog_box = WebDriverWait(obj, 10).until(
lambda obj: obj.find_element(*AccountPageLocators.DIALOG_BOX))
profile_name = dialog_box.find_element(*AccountPageLocators.APP_HEADER)
return "SubjectofthePotentate" in profile_name.get_attribute("innerHTML")
def app_header_logout(self):
"""Clicks the sign out button in the app header section"""
link_box = self.driver.find_element(*AccountPageLocators.APP_HEADER_LINK_SECTION)
link_box.send_keys(Keys.PAGE_DOWN)
self.driver.find_element(*AccountPageLocators.APP_HEADER_SIGN_OUT).click()
class LogoutPage(BasePage):
"""The class where logout page actions are defined"""
def single_account_sign_out(self):
"""Clicks the sign out button for one account"""
obj = self.driver
element = WebDriverWait(obj, 10).until(
lambda obj: obj.find_element(*LogoutPageLocators.SINGLE_ACCOUNT_SIGN_OUT_BUTTON))
element.click()
def all_account_sign_out(self):
"""Clicks the sign out button for all accounts"""
obj = self.driver
element = WebDriverWait(obj, 10).until(
lambda obj: obj.find_element(*LogoutPageLocators.ALL_ACCOUNTS_SIGN_OUT_BUTTON))
element.click()
And here is the code for elements.py
:
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.by import By
class BasePageElement():
"""A base class for finding elements and performing actions with them"""
def __set__(self, obj, value):
""""
Sets the value of the text to be entered
Parameters:
obj (Any): Instance of WebDriver
value (str): The value to be entered in the textbox
"""
driver = obj.driver
text_box = WebDriverWait(driver, 10).until(
lambda driver: driver.find_element(By.ID, self.locator))
text_box.send_keys(value)
def __get__(self, obj, owner):
""""
Gets the value of the textbox
Parameters:
obj(Any): Instance of WebDriver
"""
driver = obj.driver
text_box = WebDriverWait(driver, 10).until(
lambda driver: driver.find_element(By.ID, self.locator))
text_box.get_attribute("autocomplete")
And finally here is the code for locators.py
:
from selenium.webdriver.common.by import By
class MainPageLocators():
"""This class defines locators on the main page"""
SIGN_IN_BUTTON = (By.LINK_TEXT, "Sign in")
class LoginPageLocators():
"""This class defines locators on the login page"""
SUBMIT_BUTTON = (By.CLASS_NAME, "js-sign-in-button")
class AccountPageLocators():
"""This class defines locators on the account page"""
APP_HEADER_BUTTON = (By.CLASS_NAME, "AppHeader-user")
DIALOG_BOX = (By.CLASS_NAME, "Dialog__StyledDialog-sc-uaxjsn-1")
APP_HEADER = (By.CLASS_NAME, "lh-condensed")
APP_HEADER_LINK_SECTION = (By.CLASS_NAME, "DialogOverflowWrapper")
APP_HEADER_SIGN_OUT = (By.LINK_TEXT, "Sign out")
class LogoutPageLocators():
"""This class defines locators on the logout page"""
SINGLE_ACCOUNT_SIGN_OUT_BUTTON = (By.XPATH, "//input[@type='submit']")
ALL_ACCOUNTS_SIGN_OUT_BUTTON = (By.XPATH, "//input[@value='Sign out from all accounts']")
I've tried commenting out this line in test.py
to see if this would solve the issue:
self.assertTrue(account_page.universal_username_presence(), "Username not found")
but that didn't work, so I tried commenting out this line next in the same test.py
module:
account_page.app_header_selector()
and that didn't work either, so I think that Selenium is clicking the "Create" link as soon as this line is executed:
account_page = page.AccountMainPage(self.driver)
So how can I stop it from clicking that element before it does anything else on the account page?
EDIT: I've tried executing this code with Python 12.5.2 and Python 11.9 using ChromeDriver version 109.0.5414.74 and I've tried it with Python 8 using ChromeDriver version 128.0.6612.119 as well as GeckoDriver 0.35.0 but there was no difference. However, I tried removing this code
if __name__ == "__main__":
unittest.main()
and I implemented the test case in test.py
like this:
test = GitHubTestCase()
test.setUpClass()
test.test_login()
test.test_username_presence()
test.test_sign_out()
and it worked without clicking the "Create" link in the account page, so I think the problem is with how unittest.main()
is executing the code. But how do I fix it?
I didn't find out why unittest.main()
was causing the automation to click on the "Create" link to the READ.ME page of the user's account on GitHub, but I found a workaround solution. I passed all the tests that I wanted to be automated as arguments to defaultTest
(one of the unittest.main()
parameters) in an iterable like this:
if __name__ == "__main__":
unittest.main(defaultTest=["<classname>.<testmethodname>", "<classname>.<testmethodname>", ...])
When I provided these arguments the tests were executed properly. Note that for the defaultTest
parameter you should not provide the setUp
or setUpClass
method, nor the tearDown
or tearDownClass
methods otherwise the tests will fail.