Web Automation with Selenium & Python Part 5: [Category Creation]

So far we have done the basic home page automation testing. And authentication automation testing. Now we need to drive deep. Its time to test some core features of the quiz application. Let me introduce you to a feature called category in our quiz app. If we try to test this as old-time we had to do many forms submit and it hurts our finger. But we have selenium now. It will take care of our fingers. We can create a category and can create a subcategory under it. The category has these fields.

  • Title [required]
  • Parent Category
  • Coin
  • Question Limit [required]
  • Quiz Limit [required]
  • Time Limit [required]
  • Serial [required]
  • Status [required]
  • Description
  • Thumbnail Image
  • White Image

So basically what we need to do is penetrate these fields with both wrong and right values. And today I’m going to give you an idea of how to do that. After that, you will be able to think more about testing.

The Automation Testing Application Structure

.
├── application
│   ├── app.py
│   ├── __init__.py
│   ├── selenium_base.py
│   └── utils
│       ├── constants.py
│       ├── helpers.py
│       ├── __init__.py
│       └── urls.py
├── category
│   ├── constants.py
│   ├── create.py
│   ├── __init__.py
│   └── test.py
├── homepage
│   ├── homepage.py
│   ├── __init__.py
├── login
│   ├── __init__.py
│   ├── login.py
├── README.md
├── registration
│   ├── __init__.py
│   └── registration.py
├── requirements.txt
└── settings.py

Hold on. what is selenium_base.py doing here? Why we have a constants.py file in the category package? Guess what? We are going to make things a bit modular. What if we have another automation testing application. Where we have the same feature called category with the same functionality? Well, we just need to copy and paste the category package. That has been said. Now let’s penetrate the form.

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

Now let’s add the constants in category/constants.py file. The URLs and XPaths of fields and validation messages.

# urls
CREATE_CATEGORY_URL = 'https://quiz.itech-theme.com/question-category-create'
CATEGORY_LIST_URL = 'https://quiz.itech-theme.com/question-category-list'

# constants
# ---------------------------- Create Form XPath -------------------------------
TITLE_X_PATH = '/html/body/div[1]/div[2]/div[3]/div/div/div/div/div/form/' 
               'div[1]/div[1]/div/input'

PARENT_CATEGORY_DROPDOWN_X_PATH = '/html/body/div[1]/div[2]/div[' 
                                  '3]/div/div/div/div/div/form/div[1]/div[' 
                                  '2]/div/div/select '
COIN_X_PATH = '/html/body/div[1]/div[2]/div[3]/div/' 
              'div/div/div/div/form/div[1]/div[3]/div/input'
QUESTION_LIMIT_X_PATH = '/html/body/div[1]/div[2]/div[3]/div/' 
                        'div/div/div/div/form/div[1]/div[4]/div/input'
QUIZ_LIMIT_X_PATH = '/html/body/div[1]/div[2]/div[3]/div/div/' 
                    'div/div/div/form/div[1]/div[5]/div/input'
TIME_LIMIT_X_PATH = '/html/body/div[1]/div[2]/div[3]/div/' 
                    'div/div/div/div/form/div[1]/div[6]/div/input'
SERIAL_X_PATH = '/html/body/div[1]/div[2]/div[3]/div/div/' 
                'div/div/div/form/div[1]/div[7]/div/input'
ACTIVATION_STATUS_DROPDOWN_X_PATH = '/html/body/div[1]/div[2]/div[3]/div/' 
                                   'div/div/div/div/form/div[1]/div[8]/' 
                                   'div/div/select'
DESCRIPTION_X_PATH = '/html/body/div[1]/div[2]/div[3]/div/div/' 
                    'div/div/div/form/div[1]/div[9]/div/textarea'
THUMBNAIL_IMAGE_X_PATH = '//*[@id="input-file-now"]'
WHITE_IMAGE_X_PATH = '//*[@id="input-file-now"]'
SUBMIT_BUTTON_X_PATH = '/html/body/div[1]/div[2]/div[3]/div/' 
                      'div/div/div/div/form/div[2]/div/button'

# ------------------------------------------------------------------------------

PARENT_CATEGORY_OPTION_X_PATH = '/html/body/div[1]/div[2]/div[3]/div/div/' 
                                'div/div/div/form/div[1]/div[2]/div/div/' 
                                'select/option[53]'
STATUS_ACTIVE_OPTION_X_PATH = '/html/body/div[1]/div[2]/div[3]/div/div/' 
                              'div/div/div/form/div[1]/div[8]/div/' 
                              'div/select/option[1]'
SAMPLE_IMAGE_FILE_PATH = '/home/trex/Downloads/python_image.jpg'

SUCCESS_VALIDATION_BOX_XPATH = '//*[@id="notification_box"]'

CATEGORY_CREATION_SUCCESS_VALIDATION_MESSAGE = "Sub Category " 
                                               "Created Successfully"
CATEGORY_UPDATE_SUCCESS_VALIDATION_MESSAGE = "Sub Category Updated Successfully"

TARGET_CATEGORY_X_PATH = '//*[@id="category-table"]/tbody/tr[21]/td[4]/a'
TARGET_SUBCATEGORY_X_PATH = '//*[@id="category-table"]/tbody/tr[1]/td[9]/ul' 
                            '/a[1]'

EDIT_FORM_HEADER_X_PATH = '/html/body/div[1]/div[2]/div[2]/div/div/div/div/h2'
EDIT_FORM_HEADER_TEXT = 'Edit Sub Category'

ERROR_VALIDATION_MESSAGES = [
    "This name already taken",
    "Title field can not be empty",
    "Max limit field can not be empty",
    "Quiz limit field can not be empty",
    "Time limit field can not be empty",
    "Serial field can not be empty",

]

# dictionaries
CATEGORY_FORM_X_PATHS = dict(
    title=TITLE_X_PATH,
    parent_category=PARENT_CATEGORY_DROPDOWN_X_PATH,
    coin=COIN_X_PATH,
    question_limit=QUESTION_LIMIT_X_PATH,
    quiz_limit=QUIZ_LIMIT_X_PATH,
    time_limit=TIME_LIMIT_X_PATH,
    serial=SERIAL_X_PATH,
    status=ACTIVATION_STATUS_DROPDOWN_X_PATH,
    description=DESCRIPTION_X_PATH,
    thumbnail_image=THUMBNAIL_IMAGE_X_PATH,
    white_image=WHITE_IMAGE_X_PATH,
    submit=SUBMIT_BUTTON_X_PATH
)

CATEGORY_FORM_DATA = dict(
    title='Python3.1.5',
    parent_category=PARENT_CATEGORY_OPTION_X_PATH,
    coin=100,
    question_limit=10,
    quiz_limit=10,
    time_limit=1,
    serial=243,
    status=STATUS_ACTIVE_OPTION_X_PATH,
    description='Programming category',
    thumbnail_image=SAMPLE_IMAGE_FILE_PATH,
    white_image=SAMPLE_IMAGE_FILE_PATH
)

CATEGORY_FORM_EDIT_DATA = dict(
    title='Python2.4.3',
    parent_category=PARENT_CATEGORY_OPTION_X_PATH,
    coin=200,
    question_limit=20,
    quiz_limit=20,
    time_limit=2,
    serial=405,
    status=STATUS_ACTIVE_OPTION_X_PATH,
    description='Programming category',
    thumbnail_image=SAMPLE_IMAGE_FILE_PATH,
    white_image=SAMPLE_IMAGE_FILE_PATH
)

ERROR_VALIDATION_MESSAGE_X_PATHS = dict(
    title='/html/body/div[1]/div[2]/div[3]/div/div/'
          'div/div/div/form/div[1]/div[1]/div/span/strong',
    quistion_limit='/html/body/div[1]/div[2]/div[3]/div/div/'
                   'div/div/div/form/div[1]/div[4]/div/span/strong',
    quiz_limit='/html/body/div[1]/div[2]/div[3]/div/div/'
               'div/div/div/form/div[1]/div[5]/div/span/strong',
    time_limit='/html/body/div[1]/div[2]/div[3]/div/div/'
               'div/div/div/form/div[1]/div[6]/div/span/strong',
    serial='/html/body/div[1]/div[2]/div[3]/div/div/'
           'div/div/div/form/div[1]/div[7]/div/span/strong'
)

DUPLICATE_NAME_VALIDATION_MESSAGE_X_PATH = '/html/body/div[1]/div[2]/div[3]/' 
                                           'div/div/div/div/div/form/div[1]/' 
                                           'div[1]/div/span/strong'

[ Step: 02 ] Refactoring

SeleniumBase Class

We will demonstrate the creation feature today. but to create we need to login first right? And we also need to stay in the same browser. Well, Let’s write a base class for selenium.

Let’s import the selenium modules

"""Base class for selenium operation"""
from selenium import webdriver
from selenium.common.exceptions import WebDriverException

Now let’s write the class and its __init__ method.

class SeleniumBase:
    """Base class for selenium driver"""
    def __init__(self, browser=None):
        """
        :param browser: for cain testing
        """
        if browser is None:
            self.browser = webdriver.Firefox()
        else:
            self.browser = browser

Now let’s write the most repeated methods, visit URL and closing browser.

def visit_url(self, url: str):
    """
    Visit a specific url
    :param url:
    """
    try:
        self.browser.get(url)
    except WebDriverException:
        self.close_browser()

        raise

def close_browser(self):
    """
    Quit this browser instance
    """
    self.browser.quit()

After that, your SeleniumBase class will look like this.

"""Base class for selenium operation"""
from selenium import webdriver
from selenium.common.exceptions import WebDriverException


class SeleniumBase:
    """Base class for selenium driver"""
    def __init__(self, browser=None):
        """
        :param browser: for cain testing
        """
        if browser is None:
            self.browser = webdriver.Firefox()
        else:
            self.browser = browser

    def visit_url(self, url: str):
        """
        Visit a specific url
        :param url:
        """
        try:
            self.browser.get(url)
        except WebDriverException:
            self.close_browser()

            raise

    def close_browser(self):
        """
        Quit this browser instance
        """
        self.browser.quit()

Login Class Refactor

Now let’s refactor our login class. As we build a base class for selenium operation. We need to inherit the SeleniumBase class.

class LoginTest(SeleniumBase):
    """
        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
        """

        super().__init__()
        self.url = url
        self.login_page_x_paths = login_page_x_paths
        self.dashboard_url = dashboard_url

[ Step: 03 ] Developing The Category Package

CategoryCreateTest Class

We have created a file called “create.py”. Where we will develop the creation testing of a category/subcategory. Let’s import all the things we need first.

"""Category Create"""
from time import sleep
from selenium.common.exceptions import (
    ElementNotInteractableException,
    NoSuchElementException,
    JavascriptException)
from application.selenium_base import SeleniumBase
import unittest
from application.utils.helpers import console_print
from category.constants import (CATEGORY_FORM_X_PATHS,
                                SUCCESS_VALIDATION_BOX_XPATH,
                                ERROR_VALIDATION_MESSAGE_X_PATHS,
                                ERROR_VALIDATION_MESSAGES,
                                DUPLICATE_NAME_VALIDATION_MESSAGE_X_PATH)

Now let’s create the class by extending SeleniumBase class and TestCase class from “unittest”. Guess what? We are implementing unit testing which we will cover in another episode.

class CategoryCreateTest(SeleniumBase, unittest.TestCase):
    def __init__(self, browser=None):
        """
        :param browser: chained previous browser
        """
        super().__init__(browser)
        if browser is not None:
            self.browser = browser

Set Title Method

Now to submit a form we need to set all the field right? No problem, let’s go step by step. Set the title first with this method below. It will take a parameter called title which is a string. We will find the title field by XPath with the help of the find_element_by_xpath() method. And we will set the title with the send_keys() method.

def set_title(self, title: str):
    """
    Set title
    :param title: category title
    """
    try:
        title_field = self.browser.find_element_by_xpath(
            CATEGORY_FORM_X_PATHS['title'])
        title_field.send_keys(title)
        console_print('success', '[Title has been set]')
        self.assertTrue(True)

    except (
            ElementNotInteractableException,
            NoSuchElementException) as error:
        console_print('failed', str(error))
        self.close_browser()

        raise

Set the parent category method

In order to select a parent category, we need to select a drop-down option. We will find it by it’s XPath. And we will trigger a click event with click() method and will click on an option.

def set_parent_category(self, option_xpath: str):
    """
    Set parent category
    :param option_xpath: option xpath
    """
    try:
        parent_category_field = self.browser.find_element_by_xpath(
            CATEGORY_FORM_X_PATHS['parent_category'])
        parent_category_field.click()
        option = self.browser.find_element_by_xpath(option_xpath)
        option.click()
        console_print('success', '[Parent category has been set]')
        self.assertTrue(True)

    except (
            ElementNotInteractableException,
            NoSuchElementException) as error:
        console_print('failed', str(error))
        self.close_browser()

        raise

Set coin method

Same as setting the title. It will also take a parameter called coin and its an integer. We will use the send_keys() method to set the coin value.

def set_coin(self, coin: int):
    """
    Set coin
    :param coin: category coin
    """
    try:
        coin_field = self.browser.find_element_by_xpath(
            CATEGORY_FORM_X_PATHS['coin'])
        coin_field.send_keys(coin)
        console_print('success', '[Coin has been set]')
        self.assertTrue(True)

    except (
            ElementNotInteractableException,
            NoSuchElementException) as error:
        console_print('failed', str(error))
        self.close_browser()

        raise

Set other fields

So, just like the other methods, we will set all the fields except the file fields.

def set_question_limit(self, question_limit: str):
    """
    Set question_limit
    :param question_limit: category question_limit
    """
    try:
        question_limit_field = self.browser.find_element_by_xpath(
            CATEGORY_FORM_X_PATHS['question_limit'])
        question_limit_field.send_keys(question_limit)
        console_print('success', '[Question limit has been set]')
        self.assertTrue(True)

    except (
            ElementNotInteractableException,
            NoSuchElementException) as error:
        console_print('failed', str(error))
        self.close_browser()

        raise

def set_quiz_limit(self, quiz_limit: str):
    """
    Set quiz_limit
    :param quiz_limit: category quiz_limit
    """
    try:
        quiz_limit_field = self.browser.find_element_by_xpath(
            CATEGORY_FORM_X_PATHS['quiz_limit'])
        quiz_limit_field.send_keys(quiz_limit)
        console_print('success', '[Quiz limit has been set]')
        self.assertTrue(True)

    except (
            ElementNotInteractableException,
            NoSuchElementException) as error:
        console_print('failed', str(error))
        self.close_browser()

        raise

def set_time_limit(self, time_limit: str):
    """
    Set time_limit
    :param time_limit: category time_limit
    """
    try:
        time_limit_field = self.browser.find_element_by_xpath(
            CATEGORY_FORM_X_PATHS['time_limit'])
        time_limit_field.send_keys(time_limit)
        console_print('success', '[Time limit has been set]')
        self.assertTrue(True)

    except (
            ElementNotInteractableException,
            NoSuchElementException) as error:
        console_print('failed', str(error))
        self.close_browser()

        raise

def set_serial(self, serial: str):
    """
    Set serial
    :param serial: category serial
    """
    try:
        serial_field = self.browser.find_element_by_xpath(
            CATEGORY_FORM_X_PATHS['serial'])
        serial_field.send_keys(serial)
        console_print('success', '[Serial has been set]')
        self.assertTrue(True)

    except (
            ElementNotInteractableException,
            NoSuchElementException) as error:
        console_print('failed', str(error))
        self.close_browser()

        raise

def set_status(self, option_xpath: str):
    """
    Set status
    :param option_xpath: option xpath
    """
    try:
        status_field = self.browser.find_element_by_xpath(
            CATEGORY_FORM_X_PATHS['status'])
        status_field.click()
        option = self.browser.find_element_by_xpath(option_xpath)
        option.click()
        console_print('success', '[Status has been set]')
        self.assertTrue(True)

    except (
            ElementNotInteractableException,
            NoSuchElementException) as error:
        console_print('failed', str(error))
        self.close_browser()

        raise

def set_description(self, description: str):
    """
    Set description
    :param description: category description
    """
    try:
        description_field = self.browser.find_element_by_xpath(
            CATEGORY_FORM_X_PATHS['description'])
        description_field.send_keys(description)
        console_print('success', '[Serial has been set]')
        self.assertTrue(True)

    except (
            ElementNotInteractableException,
            NoSuchElementException) as error:
        console_print('failed', str(error))
        console_print('failed', str(error))
        self.close_browser()

        raise

Select Image File

All right, now to set an image file to a field that you need to do is, set a constant for a file path from your local machine. And submit it to the file fields.

def set_thumbnail_image(self, thumbnail_image_path: str):
    """
    Upload thumbnail image for category
    :param thumbnail_image_path: given image path in local machine
    """
    try:
        thumbnail_image_field = self.browser.find_element_by_xpath(
            CATEGORY_FORM_X_PATHS['thumbnail_image'])
        thumbnail_image_field.send_keys(thumbnail_image_path)
        console_print('success', '[Thumbnail image has been set]')
        self.assertTrue(True)

    except (
            ElementNotInteractableException,
            NoSuchElementException) as error:
        console_print('failed', str(error))
        self.close_browser()

        raise

def set_white_image(self, white_image_path: str):
    """
    Upload white image for category
    :param white_image_path: given image path in local machine
    """
    try:
        white_image_field = self.browser.find_element_by_xpath(
            CATEGORY_FORM_X_PATHS['white_image'])
        white_image_field.send_keys(white_image_path)
        console_print('success', '[White image has been set]')
        self.assertTrue(True)

    except (
            ElementNotInteractableException,
            NoSuchElementException) as error:
        console_print('failed', str(error))
        self.close_browser()

        raise

Submit method

Now all methods have been written for filling the fields. We need to write a method to submit the form.

def submit(self):
    """
    Submit category creation form
    """
    try:
        submit_button = self.browser.find_element_by_xpath(
            CATEGORY_FORM_X_PATHS['submit'])
        submit_button.click()
        console_print('success', '[Submit button clicked]')
        self.assertTrue(True)
        sleep(1)

    except (
            ElementNotInteractableException,
            NoSuchElementException) as error:
        console_print('failed', str(error))
        self.close_browser()

        raise

Create Category Method

Now to create the category we need to invoke all the methods one after another in our category method, which will take a parameter called form_data. And its a dictionary.

def create(self, form_data: dict):
    """
    Set all input fields by invoking their methods
    and submit the create form
    :param form_data: given form data as dictionary from constant file
    """
    try:
        self.set_title(form_data['title'])
        self.set_parent_category(form_data['parent_category'])
        self.set_coin(form_data['coin'])
        self.set_question_limit(form_data['question_limit'])
        self.set_quiz_limit(form_data['quiz_limit'])
        self.set_time_limit(form_data['time_limit'])
        self.set_serial(form_data['serial'])
        self.set_status(form_data['status'])
        self.set_description(form_data['description'])
        self.set_thumbnail_image(form_data['thumbnail_image'])
        self.set_white_image(form_data['white_image'])
        self.submit()
        console_print('success', '[Form has been submitted]')
        self.assertTrue(True)

    except Exception as error:
        console_print('failed', str(error))

        raise

Success message checking method

Now how can we actually check if the category has been created? By the success message of course. This method will take a parameter called message and will compare the message with the web page success message.

def success_validation_message_is_ok(self, message):
    """
    Check the validation message of a successful category creation
    :param message: success validation message
    """
    try:
        message_box = self.browser.find_element_by_xpath(
            SUCCESS_VALIDATION_BOX_XPATH)
        inner_text = message_box.text
        clean_text = inner_text.replace('x', '')
        print(len(clean_text.strip()))
        print(len(message.strip()))
        if clean_text.strip() == message.strip():
            console_print('success', '[Success validation message matched]')
            self.assertTrue(True)
        else:
            console_print('failed', '[Success validation didnt'
                                    ' message match]')
    except (
            ElementNotInteractableException,
            NoSuchElementException,
            JavascriptException) as error:
        console_print('failed', str(error))

        raise

Method to test error validation message

We have all the validation messages as an array in our constants.py. We need to grab all the messages from the web page and will check if any of the messages are not in our predefined message list.

def error_validation_message_is_ok(self):
    """
    Check all error validation message by giving empty string to all fields
    """
    try:
        for message_x_path in ERROR_VALIDATION_MESSAGE_X_PATHS:
            message = self.browser.find_element_by_xpath(
                ERROR_VALIDATION_MESSAGE_X_PATHS[message_x_path]).text
            if message not in ERROR_VALIDATION_MESSAGES:
                console_print('failed', '[' + message_x_path
                              + 'validation message didnt match!]')
                print(message)
            else:
                console_print('success',
                              '[All validation message matched!]')
    except (
            ElementNotInteractableException,
            NoSuchElementException) as error:
        console_print('failed', str(error))

        raise

Method to test duplicate category creation

We can’t create a category by an existing name. We need to check that horrible thing. How can we do that? Let’s try to create a category with the existing name and let’s check the message under the title field.

def duplicate_category_creation(self, form_data):
    """
    Submit form with a duplicate category name
    :param form_data:
    """
    try:
        self.create(form_data)
        message = self.browser.find_element_by_xpath(
            DUPLICATE_NAME_VALIDATION_MESSAGE_X_PATH)
        if message not in ERROR_VALIDATION_MESSAGES:
            console_print('success', '[Duplicate name '
                                     'validation message didnt match!]')
        else:
            console_print('failed', '[Duplicate name '
                                    'validation message matched!]')
    except (
            ElementNotInteractableException,
            NoSuchElementException) as error:
        console_print('failed', str(error))

        raise

After adding all the methods, your create.py file will look like this.

"""Category Create"""
from time import sleep
from selenium.common.exceptions import (
    ElementNotInteractableException,
    NoSuchElementException,
    JavascriptException)
from application.selenium_base import SeleniumBase
import unittest
from application.utils.helpers import console_print
from category.constants import (CATEGORY_FORM_X_PATHS,
                                SUCCESS_VALIDATION_BOX_XPATH,
                                ERROR_VALIDATION_MESSAGE_X_PATHS,
                                ERROR_VALIDATION_MESSAGES,
                                DUPLICATE_NAME_VALIDATION_MESSAGE_X_PATH)


class CategoryCreateTest(SeleniumBase, unittest.TestCase):
    def __init__(self, browser=None):
        """
        :param browser: chained previous browser
        """
        super().__init__(browser)
        if browser is not None:
            self.browser = browser

    def set_title(self, title: str):
        """
        Set title
        :param title: category title
        """
        try:
            title_field = self.browser.find_element_by_xpath(
                CATEGORY_FORM_X_PATHS['title'])
            title_field.send_keys(title)
            console_print('success', '[Title has been set]')
            self.assertTrue(True)

        except (
                ElementNotInteractableException,
                NoSuchElementException) as error:
            console_print('failed', str(error))
            self.close_browser()

            raise

    def set_parent_category(self, option_xpath: str):
        """
        Set parent category
        :param option_xpath: option xpath
        """
        try:
            parent_category_field = self.browser.find_element_by_xpath(
                CATEGORY_FORM_X_PATHS['parent_category'])
            parent_category_field.click()
            option = self.browser.find_element_by_xpath(option_xpath)
            option.click()
            console_print('success', '[Parent category has been set]')
            self.assertTrue(True)

        except (
                ElementNotInteractableException,
                NoSuchElementException) as error:
            console_print('failed', str(error))
            self.close_browser()

            raise

    def set_coin(self, coin: str):
        """
        Set coin
        :param coin: category coin
        """
        try:
            coin_field = self.browser.find_element_by_xpath(
                CATEGORY_FORM_X_PATHS['coin'])
            coin_field.send_keys(coin)
            console_print('success', '[Coin has been set]')
            self.assertTrue(True)

        except (
                ElementNotInteractableException,
                NoSuchElementException) as error:
            console_print('failed', str(error))
            self.close_browser()

            raise

    def set_question_limit(self, question_limit: str):
        """
        Set question_limit
        :param question_limit: category question_limit
        """
        try:
            question_limit_field = self.browser.find_element_by_xpath(
                CATEGORY_FORM_X_PATHS['question_limit'])
            question_limit_field.send_keys(question_limit)
            console_print('success', '[Question limit has been set]')
            self.assertTrue(True)

        except (
                ElementNotInteractableException,
                NoSuchElementException) as error:
            console_print('failed', str(error))
            self.close_browser()

            raise

    def set_quiz_limit(self, quiz_limit: str):
        """
        Set quiz_limit
        :param quiz_limit: category quiz_limit
        """
        try:
            quiz_limit_field = self.browser.find_element_by_xpath(
                CATEGORY_FORM_X_PATHS['quiz_limit'])
            quiz_limit_field.send_keys(quiz_limit)
            console_print('success', '[Quiz limit has been set]')
            self.assertTrue(True)

        except (
                ElementNotInteractableException,
                NoSuchElementException) as error:
            console_print('failed', str(error))
            self.close_browser()

            raise

    def set_time_limit(self, time_limit: str):
        """
        Set time_limit
        :param time_limit: category time_limit
        """
        try:
            time_limit_field = self.browser.find_element_by_xpath(
                CATEGORY_FORM_X_PATHS['time_limit'])
            time_limit_field.send_keys(time_limit)
            console_print('success', '[Time limit has been set]')
            self.assertTrue(True)

        except (
                ElementNotInteractableException,
                NoSuchElementException) as error:
            console_print('failed', str(error))
            self.close_browser()

            raise

    def set_serial(self, serial: str):
        """
        Set serial
        :param serial: category serial
        """
        try:
            serial_field = self.browser.find_element_by_xpath(
                CATEGORY_FORM_X_PATHS['serial'])
            serial_field.send_keys(serial)
            console_print('success', '[Serial has been set]')
            self.assertTrue(True)

        except (
                ElementNotInteractableException,
                NoSuchElementException) as error:
            console_print('failed', str(error))
            self.close_browser()

            raise

    def set_status(self, option_xpath: str):
        """
        Set status
        :param option_xpath: option xpath
        """
        try:
            status_field = self.browser.find_element_by_xpath(
                CATEGORY_FORM_X_PATHS['status'])
            status_field.click()
            option = self.browser.find_element_by_xpath(option_xpath)
            option.click()
            console_print('success', '[Status has been set]')
            self.assertTrue(True)

        except (
                ElementNotInteractableException,
                NoSuchElementException) as error:
            console_print('failed', str(error))
            self.close_browser()

            raise

    def set_description(self, description: str):
        """
        Set description
        :param description: category description
        """
        try:
            description_field = self.browser.find_element_by_xpath(
                CATEGORY_FORM_X_PATHS['description'])
            description_field.send_keys(description)
            console_print('success', '[Serial has been set]')
            self.assertTrue(True)

        except (
                ElementNotInteractableException,
                NoSuchElementException) as error:
            console_print('failed', str(error))
            console_print('failed', str(error))
            self.close_browser()

            raise

    def set_thumbnail_image(self, thumbnail_image_path: str):
        """
        Upload thumbnail image for category
        :param thumbnail_image_path: given image path in local machine
        """
        try:
            thumbnail_image_field = self.browser.find_element_by_xpath(
                CATEGORY_FORM_X_PATHS['thumbnail_image'])
            thumbnail_image_field.send_keys(thumbnail_image_path)
            console_print('success', '[Thumbnail image has been set]')
            self.assertTrue(True)

        except (
                ElementNotInteractableException,
                NoSuchElementException) as error:
            console_print('failed', str(error))
            self.close_browser()

            raise

    def set_white_image(self, white_image_path: str):
        """
        Upload white image for category
        :param white_image_path: given image path in local machine
        """
        try:
            white_image_field = self.browser.find_element_by_xpath(
                CATEGORY_FORM_X_PATHS['white_image'])
            white_image_field.send_keys(white_image_path)
            console_print('success', '[White image has been set]')
            self.assertTrue(True)

        except (
                ElementNotInteractableException,
                NoSuchElementException) as error:
            console_print('failed', str(error))
            self.close_browser()

            raise

    def submit(self):
        """
        Submit category creation form
        """
        try:
            submit_button = self.browser.find_element_by_xpath(
                CATEGORY_FORM_X_PATHS['submit'])
            submit_button.click()
            console_print('success', '[Submit button clicked]')
            self.assertTrue(True)
            sleep(1)

        except (
                ElementNotInteractableException,
                NoSuchElementException) as error:
            console_print('failed', str(error))
            self.close_browser()

            raise

    def create(self, form_data: dict):
        """
        Set all input fields by invoking their methods
        and submit the create form
        :param form_data: given form data as dictionary from constant file
        """
        try:
            self.set_title(form_data['title'])
            self.set_parent_category(form_data['parent_category'])
            self.set_coin(form_data['coin'])
            self.set_question_limit(form_data['question_limit'])
            self.set_quiz_limit(form_data['quiz_limit'])
            self.set_time_limit(form_data['time_limit'])
            self.set_serial(form_data['serial'])
            self.set_status(form_data['status'])
            self.set_description(form_data['description'])
            self.set_thumbnail_image(form_data['thumbnail_image'])
            self.set_white_image(form_data['white_image'])
            self.submit()
            console_print('success', '[Form has been submitted]')
            self.assertTrue(True)

        except Exception as error:
            console_print('failed', str(error))

            raise

    def success_validation_message_is_ok(self, message):
        """
        Check the validation message of a successful category creation
        :param message: success validation message
        """
        try:
            message_box = self.browser.find_element_by_xpath(
                SUCCESS_VALIDATION_BOX_XPATH)
            inner_text = message_box.text
            clean_text = inner_text.replace('x', '')
            print(len(clean_text.strip()))
            print(len(message.strip()))
            if clean_text.strip() == message.strip():
                console_print('success', '[Success validation message matched]')
                self.assertTrue(True)
            else:
                console_print('failed', '[Success validation didnt'
                                        ' message match]')
        except (
                ElementNotInteractableException,
                NoSuchElementException,
                JavascriptException) as error:
            console_print('failed', str(error))

            raise

    def error_validation_message_is_ok(self):
        """
        Check all error validation message by giving empty string to all fields
        """
        try:
            for message_x_path in ERROR_VALIDATION_MESSAGE_X_PATHS:
                message = self.browser.find_element_by_xpath(
                    ERROR_VALIDATION_MESSAGE_X_PATHS[message_x_path]).text
                if message not in ERROR_VALIDATION_MESSAGES:
                    console_print('failed', '[' + message_x_path
                                  + 'validation message didnt match!]')
                    print(message)
                else:
                    console_print('success',
                                  '[All validation message matched!]')
        except (
                ElementNotInteractableException,
                NoSuchElementException) as error:
            console_print('failed', str(error))

            raise

    def duplicate_category_creation(self, form_data):
        """
        Submit form with a duplicate category name
        :param form_data:
        """
        try:
            self.create(form_data)
            message = self.browser.find_element_by_xpath(
                DUPLICATE_NAME_VALIDATION_MESSAGE_X_PATH)
            if message not in ERROR_VALIDATION_MESSAGES:
                console_print('success', '[Duplicate name '
                                         'validation message didnt match!]')
            else:
                console_print('failed', '[Duplicate name '
                                        'validation message matched!]')
        except (
                ElementNotInteractableException,
                NoSuchElementException) as error:
            console_print('failed', str(error))

            raise

Now all the methods have been written. Let’s write the test code to execute them.

[ Step: 04 ] Test Category Creation

In our test.py file import the necessary things.

"""Category automation testing unit"""

from application.utils.urls import (LOGIN_URL, ADMIN_DASHBOARD)
from category.edit import CategoryEditTest
from login.login import LoginTest
from category.create import CategoryCreateTest
from application.utils.constants import LOGIN_PAGE_X_PATHS
from settings import (
    ADMIN_EMAIL, ADMIN_PASSWORD
)
from category.constants import *

Now let’s try to login first

if __name__ == '__main__':
    # Successful Login
    login_test = LoginTest(LOGIN_URL, LOGIN_PAGE_X_PATHS, ADMIN_DASHBOARD)
    login_test.visit_url(LOGIN_URL)
    login_test.login_attempt(ADMIN_EMAIL, ADMIN_PASSWORD)

Now we will try to create a category with the right credentials using the browser property from Login class

# Category Successful create Test
category_create_test = CategoryCreateTest(login_test.browser)
category_create_test.visit_url(CREATE_CATEGORY_URL)
category_create_test.create(CATEGORY_FORM_DATA)
category_create_test.success_validation_message_is_ok(
    CATEGORY_CREATION_SUCCESS_VALIDATION_MESSAGE)

Now We will try to create a category with wrong inputs. Also, we will check the error message for that.

# Category create validation message Test
category_create_test.visit_url(CREATE_CATEGORY_URL)
category_create_test.submit()
category_create_test.error_validation_message_is_ok()

Well, all we have to do is the error validation message testing.

# Try to save with duplicate name
category_create_test.duplicate_category_creation(CATEGORY_FORM_DATA)

Congratulation! After running the test.py file we will have a result similar result like the demo below.

In our next episode, we will do the category update testing. Don’t forget to share with your friends who are recently learning web automation. Here are the GitHub link and previous episodes.

Github Repository: Web Automation Of Quiz Application

Previous Episodes:

Web Automation with Selenium & Python Part 1 [Setup & Installation]

Web Automation with Selenium & Python Part 2 [Homepage Automation]

Web Automation with Selenium & Python Part 3 [Login Automation]

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