In our last episode, we automated the homepage. Now its time to do a login automation test. Well, when a QA test that manually, what he/she does is typing email/username and password. And submit the login/signup button of the login page. And while they are testing, they try with both wrong and correct credentials. But this is what they had to do more often. Well if you need to check a feature that requires authentication, you need to log in. But we are not gonna do that again by our hand from today. I will demonstrate the login automation now. But first, let’s see our updated application structure.
The Automation Testing Application Structure
. ├── application │ ├── app.py │ ├── __init__.py │ └── utils │ ├── constants.py │ ├── helpers.py │ ├── __init__.py │ └── urls.py ├── homepage │ ├── homepage.py │ ├── __init__.py ├── login │ ├── __init__.py │ ├── login.py ├── README.md ├── requirements.txt └── settings.py └── .env
Last time we worked on the homepage package. Today we have a new package called login. And we have a file called login.py under it.
Let’s Start The Login Test Automation
In most cases, there will be a user/email field, a password field, and a submit button. And in this automation testing, we have to work on those fields. So let’s meet our requirements.
- Login with correct credentials.
- Failed login with wrong credentials.
- After a successful login, we will visit the login page again. And check if it’s redirecting us to the dashboard or not.
- Log out.
[ Step:00 ] Use Pylint and Kite For better Performance
You can use Pylint in 2 ways.
- Install it in your system
- Integrate with your IDE
If you are a Linux user then its really easy.
sudo apt-get install pylint
and then use it for any python file like this:
pylint example.py
And it will show you where you can improve in your python code.
Now about the kite. Kite is the AI assistant giving developers superpowers. It uses machine learning to show you auto compilation and has a co-pilot mode to help you know the code better with documentation. If you are a Linux user then run this command below.
bash -c "$(wget -q -O - https://linux.kite.com/dls/linux/current)"
[ Step:01 ] Environment Variables
I will be working with the admin login part. And I will leave the user login to you guys. Here are the credentials in our .env file.
ADMIN_EMAIL=admin@email.com ADMIN_PASSWORD=123456 USER_EMAIL=user@email.com USER_PASSWORD=123456 WRONG_EMAIL = 'xyz@xyz.com' WRONG_PASSWORD = 'xyz'
To access .env we need to install python-dotenv package.
pip install python-dotenv
And now we will access them from settings.py file.
"""Application Settings""" import os from dotenv import load_dotenv load_dotenv() ADMIN_EMAIL = os.getenv("ADMIN_EMAIL") ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD") USER_EMAIL = os.getenv("USER_EMAIL") USER_PASSWORD = os.getenv("USER_PASSWORD") WRONG_EMAIL = os.getenv("WRONG_EMAIL") WRONG_PASSWORD = os.getenv("WRONG_PASSWORD")
[ Step:02 ] Add Constants And URLs In Our Application
In our application/utils/urls.py file lets add these URLs.
ADMIN_DASHBOARD = "https://querkiz.itech-theme.com/admin" USER_DASHBOARD = "https://querkiz.itech-theme.com/home"
And in our application/utils/constants.py let’s add those constants of required XPath.
LOGIN_EMAIL_FIELD_X_PATH = '/html/body/div/div/div/div/div/div/div/form/div[' '1]/input ' LOGIN_PASSWORD_FIELD_X_PATH = '/html/body/div/div/div/div/div/div/div/form' '/div[2]/input ' LOGIN_SUBMIT_BUTTON_X_PATH = '/html/body/div/div/div/div/div/div/div/form' '/button ' DASHBOARD_UPPER_RIGHT_MENU_X_PATH = '/html/body/div[1]/div[2]/div[' '1]/div/div/div[2]/div/button ' LOGOUT_LINK_X_PATH = '/html/body/div[1]/div[2]/div[1]/div/div/div[' '2]/div/div/a[3] ' LOGIN_PAGE_X_PATHS = dict(email_field=LOGIN_EMAIL_FIELD_X_PATH, password_field=LOGIN_PASSWORD_FIELD_X_PATH, submit_button=LOGIN_SUBMIT_BUTTON_X_PATH)
[ Step:03 ] Add Helper Methods
In our last episode do you guys remember that we used some code to print colored messages right? Did you notice that I broke the DRY principle there? If you do so then I thank you. Let’s refactor our code to follow the DRY principle. We will create a method called console_print() which will do the job.
"""Helper methods""" from termcolor import colored def console_print(message_type: str, message: str): """ Print console message with color both for success and fail :param message_type: success or failed :param message: message to print """ status, color = ('[success] ', 'green') if message_type == 'success' else ('[failed] ', 'red') state = colored(status, color, attrs=['bold']) message = colored(message, 'white') console_message = state + message print(console_message)
This method will take two parameters called message_type and message. message color will depend on the value of message_type. If it is “success” then the color will be green and if not then it will be red.
[ Step: 04 ] Developing Login Package
Write those below codes in our login/login.py file
""" LoginTest module will test: 1. Successful login 2. Failed login with wrong credential 3. Secondary Login attempt in a new tab after a successful login. If users redirect to the dashboard url or any given url, then login attempt will be successful """ from time import sleep from selenium import webdriver from selenium.common.exceptions import ElementNotInteractableException, NoSuchElementException, JavascriptException, TimeoutException from application.utils.helpers import console_print class LoginTest: """ LoginTest class test successful and failed login attempt as well as try to login after a successful login in a new tab. Log out testing can also be done. """ def __init__(self, url: str, login_page_x_paths: dict, dashboard_url: str): """ :param url: Web application's login url :param login_page_x_paths: form and button XPaths :param dashboard_url: dashboard url after login redirection """ self.url = url self.login_page_x_paths = login_page_x_paths self.dashboard_url = dashboard_url self._browser = webdriver.Chrome() def visit_login_page(self): """ Visit the web application's given login url :return: bool """ try: self._browser.get(self.url) console_print('success', '[Login page OK!]') sleep(2) except TimeoutException as error: console_print('failed', '[Login page not OK!]') console_print('failed', str(error)) self._browser.quit() raise def login_attempt(self, email: str, password: str): """ Attempt a login with given correct credentials :param email: given user email :param password: given user password """ self.login(email, password) redirected_to_dashboard: bool = self.current_url_is_dashboard_url() console_print('success', '[Login is working!]') if redirected_to_dashboard else console_print('failed', '[Wrong credential, Login failed!]') def secondary_login_attempt_in_new_tab(self): """ After make a successful login attempt we will try to open a new tab and visit the login page again, generally it will redirect us to the after login redirect url which is dashboard in most cases. """ self.open_and_switch_to_new_tab() self.visit_login_page() self.current_url_is_dashboard_url() console_print('success', '[Secondary login attempt redirected to ' 'dashboard!]') def open_and_switch_to_new_tab(self): """ Execute javascript to open new tab and then switch to the new tab """ try: self._browser.execute_script("window.open('');") sleep(1) self._browser.switch_to.window(self._browser.window_handles[1]) console_print('success', '[New tab opened!]') except JavascriptException as error: console_print('failed', '[New tab open failed!]') console_print('failed', str(error)) self._browser.quit() raise def current_url_is_dashboard_url(self) -> bool: """ Check the current url and if it is the dashboard url then return true otherwise return false :return: bool """ current_url = self._browser.current_url if current_url == self.dashboard_url: console_print('success', '[Current url is dashboard url!]') return True console_print('failed', '[Current url is not dashboard url!]') return False def login(self, email: str, password: str): """ Invoke methods to set email and password and submit the login form :param email: user email :param password: user password """ self.set_email(email) self.set_password(password) self.submit() sleep(2) def set_email(self, email: str): """ Set email to the email field which will be selected by its XPath :param email: user email """ try: email_field = self._browser.find_element_by_xpath( self.login_page_x_paths['email_field']) email_field.send_keys(email) except ( ElementNotInteractableException, NoSuchElementException) as error: console_print('failed', '[Email input failed!]') console_print('failed', str(error)) self._browser.quit() raise def set_password(self, password: str): """ Set password to the password field which will be selected by its XPath :param password: user password """ try: password_field = self._browser.find_element_by_xpath( self.login_page_x_paths['password_field']) password_field.send_keys(password) except ( ElementNotInteractableException, NoSuchElementException) as error: console_print('failed', '[Password input failed!]') console_print('failed', str(error)) self._browser.quit() raise def submit(self): """ Get the submit button by its given XPath and click the button to submit the login form """ try: submit_button = self._browser.find_element_by_xpath( self.login_page_x_paths['submit_button']) submit_button.click() except ( ElementNotInteractableException, NoSuchElementException) as error: console_print('failed', '[Credential submit failed!]') console_print('failed', str(error)) self._browser.quit() print() raise def logout(self, menu: str, logout_link: str): """ Logout from dashboard :param menu: Upper right menu in dashboard :param logout_link: logout link """ try: self._browser.find_element_by_xpath(menu).click() self._browser.find_element_by_xpath(logout_link).click() console_print('success', '[Logout successful!]') if self._browser.current_url == self.url else console_print('failed', '[Logout failed!]') except ( ElementNotInteractableException, NoSuchElementException) as error: console_print('failed', '[Logout failed!]') console_print('failed', str(error)) self._browser.quit() print() raise
[ Step: 05 ] Code Breakdown & Explanation
Hold your horse. I’m not gonna let you just copy and paste the code. I need you to understand them.
Initialize the attributes of LoginTest Class
In our __init__ method we are initializing url, login_page_x_paths, dashboard_url. Also, we have initialized our web driver as _browser. Let’s break more,
self._browser = webdriver.Chrome()
This is how we will initiate our web driver. And we used the Chrome web driver.
Visit login page
Our visit_login_page() method is responsible for visiting the given URL. We will hit the login page URL.
self._browser.get(self.url)
After that, we will print a colored message. And wait for 2 seconds.
console_print('success', '[Login page OK!]') sleep(2)
But there could be an exception like a timeout. Last time we used the whole Exception class, which is really bad. We will use only what necessary here.
except TimeoutException as error: console_print('failed', '[Login page not OK!]') console_print('failed', str(error)) self._browser.quit() raise
We handled the TimeoutException exception here and printed an error message and then raised the error.
Login Attempt
The login_attempt() method will call the login() method and will check if the redirected URL is the dashboard URL or not. Because after login we will be redirected there normally.
self.login(email, password) redirected_to_dashboard: bool = self.current_url_is_dashboard_url()
And we will print the colored message accordingly.
console_print('success', '[Login is working!]') if redirected_to_dashboard else console_print('failed', '[Wrong credential, Login failed!]')
Now let’s see what the login() method is doing. It’s calling the other methods to set email, password, and submit the form.
self.set_email(email) self.set_password(password) self.submit()
We will wait for 2 seconds after that to reload the page properly.
sleep(2)
Set Email, Password And Submit Form
The set_email() method will get the email field and type the given email.
email_field = self._browser.find_element_by_xpath( self.login_page_x_paths['email_field']) email_field.send_keys(email)
Now, here can happen 2 things. One the XPath of the field is wrong or that is not an input field. So 2 kinds of exception can happen. ElementNotInteractableException and NoSuchElementException. Let’s handle them and, print colored messages, quit the browser, and raise the exception.
except ( ElementNotInteractableException, NoSuchElementException) as error: console_print('failed', '[Email input failed!]') console_print('failed', str(error)) self._browser.quit() raise
The set_password() will do the similar. It will get the password field and type the given password and then submit.
password_field = self._browser.find_element_by_xpath( self.login_page_x_paths['password_field']) password_field.send_keys(password)
And the same kind of exceptions can happen so let’s handle them with,
except ( ElementNotInteractableException, NoSuchElementException) as error: console_print('failed', '[Password input failed!]') console_print('failed', str(error)) self._browser.quit() raise
The submit() method will click on the submit button. So first we will get the submit button by the given XPath and then click the button.
submit_button = self._browser.find_element_by_xpath( self.login_page_x_paths['submit_button']) submit_button.click()
And to handle the possible exceptions, print colored messages, quit the browser and raise the exception we will do,
except ( ElementNotInteractableException, NoSuchElementException) as error: console_print('failed', '[Credential submit failed!]') console_print('failed', str(error)) self._browser.quit() print() raise
New Tab And Secondary Login Attempt In The Same Browser
The open_and_switch_to_new_tab() will create the new tab and shift to the new tab.
self._browser.execute_script("window.open('');") sleep(1) self._browser.switch_to.window(self._browser.window_handles[1])
And the only exception that we must handle is JavascriptException. Because we are executing javascript in the browser.
except JavascriptException as error: console_print('failed', '[New tab open failed!]') console_print('failed', str(error)) self._browser.quit() raise
in our secondary_login_attempt_in_new_tab() method we will call open_and_switch_to_new_tab() method to create new tab and shift focus there then we will call visit_login_page() to go to the login URL. But if everything is fine then we will be redirected to the dashboard URL. To check that we will call the current_url_is_dashboard_url() method which will actually return boolean.
self.open_and_switch_to_new_tab() self.visit_login_page() redirected_to_dashboard: bool = self.current_url_is_dashboard_url() console_print('success', '[Secondary login attempt redirected to ' 'dashboard!]') if redirected_to_dashboard else console_print('failed', '[New tab redirection failed!]')
Congratulation if you understood all the codes. Its time to execute the code finally.
[ Step:06 ] It’s Time, Run The Automation
In our core package application, we will run the login automation testing. Write the below codes in the application/app.py file.
"""Main application test execution area""" from homepage.homepage import HomepageTest from application.utils.urls import HOMEPAGE_URL, LOGIN_URL, ADMIN_DASHBOARD from login.login import LoginTest from application.utils.constants import HOMEPAGE_NAV_BAR, LOGIN_PAGE_X_PATHS, DASHBOARD_UPPER_RIGHT_MENU_X_PATH, LOGOUT_LINK_X_PATH from settings import ADMIN_EMAIL, ADMIN_PASSWORD, WRONG_EMAIL, WRONG_PASSWORD if __name__ == '__main__': # Homepage Testing homepage = HomepageTest(HOMEPAGE_URL, HOMEPAGE_NAV_BAR) homepage.visit_homepage() homepage.nav_bar_content_testing() homepage.click_nav_elements_on_fullscreen() homepage.click_nav_elements_on_mobile_screen() homepage.close_browser() # Successful Login Testing login_test = LoginTest(LOGIN_URL, LOGIN_PAGE_X_PATHS, ADMIN_DASHBOARD) login_test.visit_login_page() login_test.login_attempt(ADMIN_EMAIL, ADMIN_PASSWORD) login_test.secondary_login_attempt_in_new_tab() login_test.logout(DASHBOARD_UPPER_RIGHT_MENU_X_PATH, LOGOUT_LINK_X_PATH) # Failed Login Testing login_test.visit_login_page() login_test.login_attempt(WRONG_EMAIL, WRONG_PASSWORD)
Explanation
We also have our old homepage testing execution code here. Below the homepage testing code, We create an object of LoginTest Class with LOGIN_URL, LOGIN_PAGE_X_PATHS, ADMIN_DASHBOARD. To test user login you have to give USER_DASHBOARD here.
login_test = LoginTest(LOGIN_URL, LOGIN_PAGE_X_PATHS, ADMIN_DASHBOARD)
Then we will call visit_login_page() to visit the homepage and try to attempt a login request by login_attempt() method where we are giving two parameters called ADMIN_EMAIL and ADMIN_PASSWORD, which we imported from settings.py module.
login_test.visit_login_page() login_test.login_attempt(ADMIN_EMAIL, ADMIN_PASSWORD)
Then we will try to open a new tab to log in again, which is not theoretically possible but we need to check if it’s redirecting us t the dashboard or not.
login_test.secondary_login_attempt_in_new_tab()
Then we will do the logout.
login_test.logout(DASHBOARD_UPPER_RIGHT_MENU_X_PATH, LOGOUT_LINK_X_PATH)
After that, we will try to login with wrong credentials. And definitely we will fail!!!
# Failed Login Testing login_test.visit_login_page() login_test.login_attempt(WRONG_EMAIL, WRONG_PASSWORD)
Here is a video demo of all the procedures.
Conclusion
Congratulation, We have automated the login. Keep watching the series for more content. In our next episode will do some awesome functional testing. Share this article with your friends to help them learn the web automation. Thanks for being with me for the whole time.