Web Automation with Selenium & Python Part 4: [Registration Automation]

In our last episode, we automated the login test. Today we are gonna learn how can we the registration testing. Almost every web application has a registration feature. The most important testing part in registration is validation. Of course, we also need to test if it successfully works or not. In most cases, we have a username and email, sometimes both, the password and confirm password fields. Though some registration requires the phone number, country, etc. But in our target application we have these:

  • Username
  • Email
  • Password
  • Confirm Password
  • Phone Number

Let’s see their validation rules.

Username: Alphanumeric and required

Email: Generic email format and required

Password: Minimum 8 character and must be consist of one uppercase, one lowercase, and one number. It’s also required

Phone: Generic string field and not required.

login system automation

Test Cases

  • Submit the registration form without giving any input and check the backend validation message is showing properly or not.
  • Try to register with an invalid email and see if the HTML validation message is showing or not.
  • Try with an invalid password and check the backend validation message is showing properly or not.
  • Typing different password and confirm password value and check the backend validation message.
  • Registration is working perfectly after giving every input properly and also need to check the success message.

The Automation Testing Application Structure

.
├── application
│   ├── app.py
│   ├── __init__.py
│   └── utils
│       ├── constants.py
│       ├── helpers.py
│       ├── __init__.py
├── homepage
│   ├── homepage.py
│   ├── __init__.py
├── login
│   ├── __init__.py
│   ├── login.py
├── README.md
├── registration
│   ├── __init__.py
│   └── registration.py
├── requirements.txt
└── settings.py
└── .env

[ Step:01 ] Environment Variables

Add these credentials in our .env file.

REGISTRATION_USER_USERNAME=testuser100
REGISTRATION_USER_EMAIL=testuser100@yopmail.com
REGISTRATION_USER_PASSWORD=Pass.1234
REGISTRATION_USER_PHONE=93749273

INVALID_EMAIL=invalidemail.com
INVALID_PASSWORD=1
DIFFERENT_CONFIRM_PASSWORD=Pass.12345

And these variables in settings.py file.

REGISTRATION_USER_USERNAME = os.getenv("REGISTRATION_USER_USERNAME")
REGISTRATION_USER_EMAIL = os.getenv("REGISTRATION_USER_EMAIL")
REGISTRATION_USER_PASSWORD = os.getenv("REGISTRATION_USER_PASSWORD")
REGISTRATION_USER_PHONE = os.getenv("REGISTRATION_USER_PHONE")

INVALID_EMAIL = os.getenv("INVALID_EMAIL")
INVALID_PASSWORD = os.getenv("INVALID_PASSWORD")
DIFFERENT_CONFIRM_PASSWORD = os.getenv("DIFFERENT_CONFIRM_PASSWORD")

Adding these variables to settings our settings.py file will look like this

"""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")

REGISTRATION_USER_USERNAME = os.getenv("REGISTRATION_USER_USERNAME")
REGISTRATION_USER_EMAIL = os.getenv("REGISTRATION_USER_EMAIL")
REGISTRATION_USER_PASSWORD = os.getenv("REGISTRATION_USER_PASSWORD")
REGISTRATION_USER_PHONE = os.getenv("REGISTRATION_USER_PHONE")

INVALID_EMAIL = os.getenv("INVALID_EMAIL")
INVALID_PASSWORD = os.getenv("INVALID_PASSWORD")
DIFFERENT_CONFIRM_PASSWORD = os.getenv("DIFFERENT_CONFIRM_PASSWORD")

So you get the idea of settings.py file right?


[ Step:02 ] Add Constants And URLs In Our Application

In our application/utils/urls.py file lets add these URLs.

REGISTRATION_URL = "https://querkiz.itech-theme.com/signup"

And in our application/utils/constants.py let’s add those constants of required XPath. I have said details about XPath in previous episodes.

REGISTRATION_USERNAME_FIELD_X_PATH = '/html/body/div/div/div/div/div/div/' 
                                     'div/form/div/div[1]/div/input[1]'
REGISTRATION_EMAIL_FIELD_X_PATH = '/html/body/div/div/div/div/div/div/div/' 
                                  'form/div/div[2]/div/input'
REGISTRATION_PASSWORD_FIELD_X_PATH = '/html/body/div/div/div/div/div/div/div/' 
                                     'form/div/div[3]/div/input'
REGISTRATION_CONFIRM_PASSWORD_FIELD_X_PATH = '/html/body/div/div/div/div/' 
                                             'div/div/div/form/div/div[4]/' 
                                             'div/input'
REGISTRATION_PHONE_FIELD_X_PATH = '/html/body/div/div/div/div/div/div/div/' 
                                  'form/div/div[5]/div/input'
REGISTRATION_SUBMIT_BUTTON_X_PATH = '/html/body/div/div/div/div/div/div/div/' 
                                    'form/div/div[6]/div/button'
REGISTRATION_VALIDATION_MESSAGE_BOX_X_PATHS = '//*[@id="notification_box"]/ul'
REGISTRATION_SUCCESS_MESSAGE_BOX_X_PATHS = '//*[@id="notification_box"]'

REGISTRATION_PAGE_X_PATHS = dict(
    username_field=REGISTRATION_USERNAME_FIELD_X_PATH,
    email_field=REGISTRATION_EMAIL_FIELD_X_PATH,
    password_field=REGISTRATION_PASSWORD_FIELD_X_PATH,
    confirm_password_field=REGISTRATION_CONFIRM_PASSWORD_FIELD_X_PATH,
    phone_field=REGISTRATION_PHONE_FIELD_X_PATH,
    submit_button=REGISTRATION_SUBMIT_BUTTON_X_PATH,
    error_message_box=REGISTRATION_VALIDATION_MESSAGE_BOX_X_PATHS,
    success_message_box=REGISTRATION_SUCCESS_MESSAGE_BOX_X_PATHS
)

REGISTRATION_ERROR_MESSAGES = [
    "Name field can not be empty",
    "Email field can not be empty",
    "Password field can not be empty",
    "Password confirmed field can not be empty",
    "The password confirmation does not match.",
    "Password length must be above 8 characters.",
    "Password must be consist of one Uppercase, one Lowercase and one Number!"
]

REGISTRATION_SUCCESS_MESSAGE = "We have just sent a verification link on " 
                               "Email ."
INVALID_EMAIL_MESSAGE = "Please enter an email address."

[ Step: 03 ] Developing The Registration Package

Nice! we are ready to go now. Write those below codes in our registration/registration.py file.

"""
    Registration module tasks:
    1. Register with correct information
    2. Register with wrong information
    3. Check validation messages
    4. Check success message
    5. Test with invalid email
"""
from time import sleep
from selenium import webdriver
from selenium.common.exceptions import ElementNotInteractableException, 
    NoSuchElementException, TimeoutException
from application.utils.helpers import console_print


class RegistrationTest:
    """RegistrationTest class"""

    def __init__(self, url: str, registration_x_paths: dict):
        self.url = url
        self.registration_x_paths = registration_x_paths
        self._browser = webdriver.Firefox()

    def visit_registration_page(self):
        """
        Will visit the registration page
        """
        try:
            self._browser.get(self.url)
            console_print('success', '[Registration page OK!]')
            sleep(2)
        except TimeoutException as error:
            console_print('failed', '[Registration page not OK!]')
            console_print('failed', str(error))
            self._browser.quit()

            raise

    def register(self, username: str, email: str, password: str,
                 confirm_password: str, phone: str):
        """
        Invoke methods to set username, email, password, confirm password and
        submit the registration form
        :param phone:
        :param confirm_password:
        :param username:
        :param email: user email
        :param password: user password
        """
        self.set_username(username)
        sleep(1)
        self.set_email(email)
        sleep(1)
        self.set_password(password)
        sleep(1)
        self.set_confirm_password(confirm_password)
        sleep(1)
        self.set_phone(phone)
        self.submit()
        sleep(2)

    def check_registration_success_message(self, username: str, email: str,
                                           password: str,
                                           confirm_password: str,
                                           phone: str,
                                           login_url: str) -> bool:
        try:
            self._browser.refresh()
            self.register(username, email, password, confirm_password, phone)
            sleep(2)
            current_url = self._browser.current_url
            if current_url is not login_url:
                console_print('failed', '[Current url is not login url, '
                                        'registration failed]')
                return False
            console_print('success', '[Current url is login url, '
                                     'registration successful]')
            return True
        except (
                ElementNotInteractableException,
                NoSuchElementException) as error:
            console_print('failed', str(error))
            self._browser.quit()

            return False

    def registration_with_invalid_email(self, username: str, email: str,
                                        password: str, confirm_password: str,
                                        phone: str, invalid_email_message):
        """
        This method will try to register with a given invalid email where
        other information will be valid. And we will check the HTML validation
        message is shown or not. We will return True if it does otherwise, we
        will return False.
        :param username:
        :param email:
        :param password:
        :param confirm_password:
        :param phone:
        :param invalid_email_message:
        :return:
        """
        try:
            self._browser.refresh()
            self.register(username, email, password, confirm_password, phone)
            sleep(1)
            email_validation_message = self._browser.find_element_by_xpath(
                self.registration_x_paths['email_field']) 
                .get_attribute('validationMessage')
            sleep(1)
            print(email_validation_message)
            print(invalid_email_message)
            if email_validation_message != invalid_email_message:
                console_print('failed', '[Invalid email validation message '
                                        'didnt match!]')
                return False
            console_print('success', '[Invalid email validation message '
                                     'matched!]')

            return True
        except (
                ElementNotInteractableException,
                NoSuchElementException) as error:
            console_print('failed', str(error))
            self._browser.quit()

            return False

    def check_registration_validation_message(self, username: str, email: str,
                                              password: str,
                                              confirm_password: str,
                                              phone: str,
                                              error_messages: list) -> bool:
        """
        Attempt a failed registration and then will check the validation
        messages is in our predefined expected list
        :param username: will be empty
        :param email: will be empty or invalid email
        :param password: invalid password will be given
        :param confirm_password: password and confirm password will not be same
        :param phone: might be empty for this particular test
        :param error_messages: predefined expected validation messages
        :return: True / False according to the test
        """
        try:
            self._browser.refresh()
            sleep(1)
            self.register(username, email, password, confirm_password, phone)
            sleep(2)
            validation_message_ul = self._browser.find_element_by_xpath(
                self.registration_x_paths['error_message_box'])
            validation_messages_li = validation_message_ul. 
                find_elements_by_tag_name('li')
            validation_messages = []
            for li in validation_messages_li:
                validation_messages.append(li.text)
            print(validation_messages)
            for message in validation_messages:
                if message not in error_messages:
                    console_print('failed', '[Validation message didnt match!]')
                    print(message)
                    return False
            console_print('success', '[All validation message matched!]')

            return True
        except (
                ElementNotInteractableException,
                NoSuchElementException) as error:
            console_print('failed', str(error))
            self._browser.quit()

            return False

    def set_username(self, username: str):
        """
        Set username to the username field which will be selected by its XPath
        :param username: user email
        """
        try:
            username_field = self._browser.find_element_by_xpath(
                self.registration_x_paths['username_field'])
            username_field.send_keys(username)
        except (
                ElementNotInteractableException,
                NoSuchElementException) as error:
            console_print('failed', '[Username input failed!]')
            console_print('failed', str(error))
            self._browser.quit()

            raise

    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.registration_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.registration_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 set_confirm_password(self, confirm_password):
        """
        Set same or wrong password to the confirm password field which will be
        selected by its XPath
        :param confirm_password: user password
        """
        try:
            confirm_password_field = self._browser.find_element_by_xpath(
                self.registration_x_paths['confirm_password_field'])
            confirm_password_field.send_keys(confirm_password)
        except (
                ElementNotInteractableException,
                NoSuchElementException) as error:
            console_print('failed', '[Confirm Password input failed!]')
            console_print('failed', str(error))
            self._browser.quit()

            raise

    def set_phone(self, phone: str):
        """
        Set phone to the phone field which will be selected by its XPath
        :param phone: user phone number
        """
        try:
            phone_field = self._browser.find_element_by_xpath(
                self.registration_x_paths['phone_field'])
            phone_field.send_keys(phone)
        except (
                ElementNotInteractableException,
                NoSuchElementException) as error:
            console_print('failed', '[Phone 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 registration form
        """
        try:
            submit_button = self._browser.find_element_by_xpath(
                self.registration_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

[ Step: 04 ] Code Breakdown & Explanation

Ow my God! Lots of code. But let’s breakdown. Slow and steady.

Initialize the attributes of RegistrationTest Class

In our __init__  method we are initializing URL, registration_x_paths. Also, we have initialized our web driver as _browser. Let’s break more,

self.url = url
self.registration_x_paths = registration_x_paths
self._browser = webdriver.Firefox()

This is how we will initiate our web driver. And we used the Firefox web driver in this episode. You need to install the driver in your system.

Visit registration page

Our visit_registration_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', '[Registration 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', '[Registration 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.

Register method

The register() method will call the set_username(), set_email(), set_password(), set_confirm_password() and set_phone() method.

self.set_username(username)
sleep(1)
self.set_email(email)
sleep(1)
self.set_password(password)
sleep(1)
self.set_confirm_password(confirm_password)
sleep(1)
self.set_phone(phone)
self.submit()
sleep(2)

So, let’s break these methods.

The set_username() method will find the username field first by its XPath and will type the given username in it. ElementNotInteractableException, NoSuchElementException exceptions will be handled here as we are finding and interacting only.

def set_username(self, username: str):
    """
    Set username to the username field which will be selected by its XPath
    :param username: user email
    """
    try:
        username_field = self._browser.find_element_by_xpath(
            self.registration_x_paths['username_field'])
        username_field.send_keys(username)
    except (
            ElementNotInteractableException,
            NoSuchElementException) as error:
        console_print('failed', '[Username input failed!]')
        console_print('failed', str(error))
        self._browser.quit()

        raise

The same goes for other input field typing methods. The submit method will just find and click the submit button.

def submit(self):
    """
    Get the submit button by its given XPath and click the button to submit
    the registration form
    """
    try:
        submit_button = self._browser.find_element_by_xpath(
            self.registration_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

The registration_with_invalid_email() method

This method will try to register with a given invalid email where other information will be valid. And we will check the HTML validation message is shown or not. We will return True if it does otherwise, we will return False. Required parameters are

:param username:
:param email:
:param password:
:param confirm_password:
:param phone:
:param invalid_email_message:

We will invoke the register() method first. And will wait for one second.

self.register(username, email, password, confirm_password, phone)
sleep(1)

Now how can we get the HTML validation message that it will automatically show? When there is a required attribute? Here comes the  get_attribute() method of the web driver.

email_validation_message = self._browser.find_element_by_xpath(
    self.registration_x_paths['email_field'])
    .get_attribute('validationMessage')

Check if the validation message we got from the web page and the given invalid_email_message parameter is the same or not. If it’s the same then we will return true.

if email_validation_message is not invalid_email_message:
    console_print('failed', '[Invalid email validation message '
                            'didnt match!]')
    return False

If not, then call the developer and tell him to fix it. But before that, you better return false and get the hell out of your method.

console_print('failed', '[Invalid email validation message '
                        'matched!]')
print(email_validation_message)

return True

The check_registration_validation_message() method

It’s time to disturb the fellow developer if he forgot to add backend validation. Attempt a failed registration and then will check the validation messages are in our predefined expected list or not. But first, refresh the browser to clear all the input fields and try to register.

self._browser.refresh()
sleep(1)
self.register(username, email, password, confirm_password, phone)
sleep(2)

Fetch all the validation messages from the web page

validation_message_ul = self._browser.find_element_by_xpath(
    self.registration_x_paths['error_message_box'])
validation_messages_li = validation_message_ul. 
    find_elements_by_tag_name('li')
validation_messages = []
for li in validation_messages_li:
    validation_messages.append(li.text)

Now check and ask your fellow developer to fix the validation if an unexpected thing happens.

for message in validation_messages:
    if message not in error_messages:
        console_print('failed', '[Validation message didnt match!]')
        print(message)
        return False
console_print('success', '[All validation message matched!]')

return True

The check_registration_success_message() method

We have tested on worst cases till now. Let’s see if it actually works. Call the register() method and fetch the success message from the web page.

self.register(username, email, password, confirm_password, phone)
sleep(2)
message = self._browser.find_element_by_xpath(
    self.registration_x_paths['success_message_box']).text

And now check if the current URL is LOGIN_URL or not.

current_url = self._browser.current_url
if current_url is not login_url:
    console_print('failed', '[Current url is not login url, '
                            'registration failed]')
    return False
console_print('success', '[Current url is login url, '
                         'registration successful]')
return True

All set, now we are ready to write the execution code.


[ Step:05 ] 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. We will run the homepage, login, and registration automation test synchronously.

"""Main application test execution area"""

from homepage.homepage import HomepageTest
from application.utils.urls import (
    HOMEPAGE_URL, LOGIN_URL, ADMIN_DASHBOARD, REGISTRATION_URL)
from login.login import LoginTest
from registration.registration import RegistrationTest
from application.utils.constants import (
    HOMEPAGE_NAV_BAR, LOGIN_PAGE_X_PATHS, REGISTRATION_PAGE_X_PATHS,
    REGISTRATION_ERROR_MESSAGES, REGISTRATION_SUCCESS_MESSAGE,
    DASHBOARD_UPPER_RIGHT_MENU_X_PATH, LOGOUT_LINK_X_PATH,
    INVALID_EMAIL_MESSAGE
)
from settings import (
    ADMIN_EMAIL, ADMIN_PASSWORD, WRONG_EMAIL, WRONG_PASSWORD,
    REGISTRATION_USER_USERNAME,
    REGISTRATION_USER_EMAIL,
    REGISTRATION_USER_PASSWORD,
    REGISTRATION_USER_PHONE,
    INVALID_EMAIL,
    INVALID_PASSWORD,
    DIFFERENT_CONFIRM_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)

    # Registration Testing
    registration_test = RegistrationTest(REGISTRATION_URL,
                                         REGISTRATION_PAGE_X_PATHS)
    registration_test.visit_registration_page()

    # Failed registration testing without any values in form
    registration_test.check_registration_validation_message(
        '', '', '', '', '', REGISTRATION_ERROR_MESSAGES
    )

    # Failed registration with invalid email
    registration_test.registration_with_invalid_email(
        REGISTRATION_USER_USERNAME,
        INVALID_EMAIL,
        REGISTRATION_USER_PASSWORD,
        REGISTRATION_USER_PASSWORD,
        REGISTRATION_USER_PHONE,
        INVALID_EMAIL_MESSAGE
    )

    # Failed registration with invalid password
    registration_test.check_registration_validation_message(
        REGISTRATION_USER_USERNAME,
        REGISTRATION_USER_EMAIL,
        INVALID_PASSWORD,
        INVALID_PASSWORD,
        REGISTRATION_USER_PHONE,
        REGISTRATION_ERROR_MESSAGES
    )

    # Failed registration with different password and confirm password
    registration_test.check_registration_validation_message(
        REGISTRATION_USER_USERNAME,
        REGISTRATION_USER_EMAIL,
        REGISTRATION_USER_PASSWORD,
        DIFFERENT_CONFIRM_PASSWORD,
        REGISTRATION_USER_PHONE,
        REGISTRATION_ERROR_MESSAGES
    )

    # Successful registration test
    registration_test.check_registration_success_message(
        REGISTRATION_USER_USERNAME,
        REGISTRATION_USER_EMAIL,
        REGISTRATION_USER_PASSWORD,
        REGISTRATION_USER_PASSWORD,
        REGISTRATION_USER_PHONE,
        REGISTRATION_SUCCESS_MESSAGE
    )

Now run the test and enjoy the automation! Don’t forget to change the username and email. If everything works fine then you will find something similar like the below image.

And here is the video demo of the registration automation testing.

Now do one thing as practice. Try to register with an existing username or email and check it’s validation message. Keep practicing more. We will enter the core feature of the quiz application in the next episode. Till then, stay safe. Stay home and Don’t forget to share the article and your love for python. God bless you.

Github Repository: https://github.com/zim0101/quiz_app_automation_test_with_python_selenium

Web Automation with Selenium & Python Part 1

Web Automation with Selenium & Python Part 2

Web Automation with Selenium & Python Part 3