Performing assurance on the planned and developed software project is critical to project development. I realized this when I explored Software Quality Assurance.

As I surfed through the available practices and tools, such as Robot, Selenium, Cypress, Appium, Playwright, TestNG, etc., to perform automated software testing, my fascination with Python led me to choose it as my language of choice for automation in Selenium.

Adhering to the classic method

Starting with the basics of testing and manual testing, I gradually transitioned into automated testing with Selenium in Python. The core of automation lies in understanding how to locate the items on the web, i.e., web elements and operate on them. Some of the most widely used locators are x-path, CSS selector, id, class name, etc., making automation even easier and quicker. However, not all DOM elements can be manipulated uniformly; elements like radio buttons, multi-select dropdowns, frames, and calendars possess distinct characteristics, requiring specific approaches when employed for automation testing.

Let’s visualize a sample code below:

def validPhoneLogin(self):
    username = driver.find_element(By.XPATH,"//input[@id='username']").send_keys("98********")
    password = driver.find_element(By.XPATH,"//input[@id='password']").send_keys("*******")
    login_button = driver.find_element(By.XPATH, "//button[normalize-space()='Login']")
    login_button.click()
    sleep(5)
    current_page = driver.current_url
    if current_page == 'https://www.yarsabazar.com/verify?redirectTo=/account':
        print("Valid login with phone test successful!")
    else:
        print("Valid login with phone test failed!")

Automated phone login validation with URL check.

The code snippet above runs to open the login page and makes an attempt to log in to the website. This sample represents only a small portion of what we need to test for the project. Sustaining these codes will be difficult, with time, when the size of test suites and test cases grow.

Tailoring to a revamped approach

To mitigate the issue above, I have embraced a design pattern, namely, Page Object Model (POM), which creates an object repository for the DOM elements of the web. Understanding POM is about creating page classes and methods for each web page in the project framework. For a singular change in web UI, we would need to update the entire code base with a traditional approach, but with POM, this is quite efficient as we just need to update a few lines in a class. Thus, this approach minimizes code duplication and enhances code maintainability.
Take an example: For a login page, we create a login.py file to store its classes and methods. Likewise, we create a separate file named ‘locators.py’ to store the web page's locators and ‘elements.py’ to define web elements and centralize the locators.

Let's look forward to implementing POM in the example below:

tests/test_login_page.py

    def test_login_form_with_phone(self):
        login_page = LoginPage(self.driver)
        assert login_page.is_title_matches()
        login_page.username_input = '98********'
        login_page.password_input = '********'
        login_page.click_submit_btn()
        assert login_page.successful_login()

Testing login form with phone number credentials.

In test_login_page.py, we write the test cases for the application's login page. Then, we instantiate an object of the LoginPage class and utilize its methods and variables in the test cases.

my_pack/login.py

class UsernameElement(BasePageElement):
locator = "//input[@id='username']"

class PasswordElement(BasePageElement):
locator = "//input[@id='password']"

class LoginPage(BasePage):
username_input = UsernameElement()
password_input = PasswordElement()

def is_title_matches(self):
    return 'Yarsa Bazar' in self.driver.title

def click_submit_btn(self):
    element = self.driver.find_element(*LoginPageLocators.SUBMIT_BTN)
    element.click()

def successful_login(self):
    return verify_page in self.driver.current_url

Encapsulating login page elements and functionality.

In login.py, we have created a LoginPage class with variables and methods to test the login page. These methods and variables are invoked within the functions of test cases.

my_pack/locators.py

class LoginPageLocators(object):
SUBMIT_BTN = (By.XPATH, "//button[normalize-space()='Login']")

This locators.py file stores the elements' location on a page, like the submit button in the above example.

Juggling multiple test data

Digging deep into the Selenium Python ecosystem, I settled on Data Driven Testing(DDT). Carrying out tests with varying data, manually updating the values in test script or rewriting functions with newer data every time for a particular functionality is messy and monotonous. Rather, using parameterized or data-importing techniques for the test data is more optimal. This technique covers most tests and implies positive and negative tests in a single go. In Selenium Python, we should install the ddt library, which has methods like @data to load arguments from the files and, similarly, @unpack to unload the file data into multiple arguments.

Talking of performing data-based testing, gaining real data is challenging and here come the scenarios:

“where to get the imaginary test data from?”
"Create it manually in a CSV or Excel file
.
or
“Ask the project lead?”

Writing test data for each argument is laborious and time-consuming. Instead, we can use the Faker library to generate random fake data that looks real! The Faker library in Python can produce simulated data in whatever amount we require. Using Faker is very easy; you just need to install it on your device and import it into your project test file. Let's generate some random names using Faker.

From faker import Faker:
faker = Faker()

for _ in range(5):
    fake = Faker()
    key = fake.name()
    print(key)

Besides using default Faker methods, we can customize Faker to produce data according to our needs. Overall, learning and implementing DDT has allowed me to execute tests with multiple data sets and enhanced the robustness of my test suite.

Adopting a framework

Merely depending on testing tools is no longer synonymous with modern testing practices. Our methods are elevated in the era of advancing testing frameworks, resulting in greater efficiency and effectiveness in our tasks. In addition to PyUnit, the standard Python testing framework in 2023, several other Selenium Python frameworks are on the market. Some of the best frameworks are Robot, Gauge, PyTest, Behave, etc. The major purpose of using a test framework is to justify your testing requirements and to be easy to use. I have choosen Pytest as a framework other than UnitTest in my project. Pytest supports the auto-discovery of test modules, allowing multiple fixtures and enhancing parameterization. We can integrate report-generating tools such as Allure with Pytest, and a basic idea of Python Selenium is sufficient to embark on your Pytest journey.

Thus, from foundational testing principles to more advanced testing techniques such as DDT and Testing frameworks, each phase has been vital in shaping my journey as an SQA engineer, as will they be in yours.

Thank you for reading this article. See you in the next one.