Introduction

As we are developing for healthcare workers, it is crucial for projects to not only meet the needs of their target audience but also to deliver a seamless, reliable, and user-friendly experience. To achieve this, our team employed a comprehensive testing approach, utilizing multiple testing methods such as Unit Testing, End-to-End Testing, and User Testing. This multi-faceted strategy ensured the quality and functionality of our project while addressing any bugs or errors during development.

End-to-End Testing

End-to-End Testing is a vital aspect of our testing strategy, ensuring that all project components work together seamlessly. To accomplish this, we utilized Selenium in Python, an open-source software library that enables automated testing of web applications. Selenium's robust set of tools and APIs allowed us to simulate user actions in a web browser and interact with web elements like buttons, links, and text boxes. This comprehensive testing method verified that our project functioned flawlessly from start to finish, providing an exceptional user experience.

Page-Oriented Testing with Selenium and Python

In addition to the standard capabilities of Selenium, our team adopted a page-oriented approach to testing. This design pattern involves treating each web page as an object, encapsulating its elements and behaviors within separate classes. By following this approach, we were able to create maintainable and reusable test scripts, streamlining our testing process.

The benefits of using a page-oriented design in our End-to-End Testing process include:

  1. Modularity: By isolating the functionality of each page, our test scripts become more modular and easier to understand. This approach simplifies the process of updating or modifying tests as the project evolves.
  2. Reusability: The page-oriented design enables us to reuse common elements and functions across multiple test scripts, reducing redundancy and decreasing the time needed for test development.
  3. Maintainability: With the separation of concerns provided by the page-oriented design, our test scripts are easier to maintain. Changes to the project's web pages are less likely to impact multiple tests, as updates can be made in the corresponding page object, rather than scattered throughout multiple test scripts.
  4. Readability: By representing each web page as an object with clearly defined methods, our test scripts become more readable and understandable. This approach promotes a clear understanding of the project's structure and functionality among team members, facilitating effective communication and collaboration.

Overall, the incorporation of page-oriented testing with Selenium and Python in our End-to-End Testing process greatly enhanced our ability to create maintainable, reusable, and readable test scripts. This design pattern not only streamlined our testing process but also ensured that our project functioned seamlessly, providing a top-notch user experience.

Example of Selenium End-to-End testing

We will show an example of how one of our tests look like to give a quick breakdown of it.

TestLogIn class: This class inherits from unittest.TestCase and contains methods to set up, test, and tear down the login process. This is the file that is run to test our application.

          
            class TestLogIn(unittest.TestCase):
                def setUp(self):
                    self.driver = webdriver.Chrome()
                    self.driver.get("https://adela734.softr.app")
                def test_log_in(self):
                    WelcomePage = page.WelcomePage(self.driver)
                    WelcomePage.click_sign_in()
                    password = os.getenv("PASSWORD")
                    email = os.getenv("EMAIL")

                    LoginPage = page.LoginPage(self.driver)
                    LoginPage.email_element = email
                    LoginPage.password_element = password

                    LoginPage.click_sign_in()
                    main_page = page.MainPage(self.driver)
                    assert main_page.open_stats_page(), "Stats page does not open after signing in."

                def tearDown(self):
                    self.driver.quit()
      
    
      
        class ClickableElement(object):
            def __init__(self, locator):
                self.locator = locator

            def __set__(self, obj, value):
                driver = obj.driver
                WebDriverWait(driver, 10).until(EC.element_to_be_clickable(self.locator))
                element = driver.find_element(*self.locator)
                element.click()

            def __get__(self, obj, owner):
                driver = obj.driver
                WebDriverWait(driver, 10).until(EC.element_to_be_clickable(self.locator))
                element = driver.find_element(*self.locator)
                return element
      
    

The `ClickableElement` class is a descriptor class for managing clickable elements in a Selenium WebDriver test. It defines the __init__, __set__, and __get__ methods, which are used to create and manipulate instances of this class.

      
        class WelcomePageLocators(object):
            """A class for welcome page locators. All welcome page locators should come here"""

            # /html/body/div[1]/nav/div/div[1]/ul/li/a
            SIGN_IN = (By.XPATH, "/html/body/div[1]/nav/div/div[1]/ul/li/a")
            # /html/body/div[1]/header/div/div/div[1]/div/a[2]
            SIGN_UP = (By.XPATH, "/html/body/div[1]/header/div/div/div[1]/div/a[2]")

The WelcomePageLocators class contains locators for the welcome page elements. This class makes it easy to manage and update locators in a centralized location.

  
    class WelcomePage(BasePage):
        """Welcome page action methods come here."""

        sign_up_element = ClickableElement(WelcomePageLocators.SIGN_UP)
        sign_in_element = ClickableElement(WelcomePageLocators.SIGN_IN)

        def click_sign_up(self):
            element = self.sign_up_element
            element.click()

        def click_sign_in(self):
            element = self.sign_in_element
            element.click()
  

In summary, the TestLogIn class and its methods rely on the interdependence of the various classes we've discussed. The TestLogIn class leverages the WelcomePage and LoginPage classes, which in turn inherit from the BasePage class. These classes utilize elements, locators, and custom descriptors like ClickableElement to define the behavior of elements and actions on the web pages.

The TestLogIn class serves as a starting point for the test execution. The setUp method initializes the web driver and opens the target website. The test_log_in method uses the WelcomePage and LoginPage classes to interact with the web elements, perform actions like clicking buttons, and entering login credentials. Finally, the tearDown method closes the web driver when the test is complete.

This is just one example of a Selenium end-to-end test for our system. There are many other tests covering various user flows and functionalities. These tests help to ensure that the application behaves as expected and provides a consistent user experience. By utilizing Selenium and Python, we can create a comprehensive suite of automated tests to validate the functionality and usability of our web application.

User Testing

User testing was a crucial part of our development process as it helped us to identify any usability or functionality issues from the perspective of the hospital workers. In our case, we carried out user testing with clinicians, who are representative of our target audience. This allowed us to ensure that our application meets the needs of our users and provides an optimal user experience.

Throughout the development process, we conducted weekly meetings with clinicians to gather feedback and insights. We also used a testing schedule (click here) to guide our user testing efforts. The testing schedule outlines the specific functionalities and user flows that need to be validated by the users.

To collect user feedback, we utilized Google Forms as a survey tool. This allowed us to gather structured feedback on specific functionalities and identify any areas of improvement. Based on the feedback we received from our users, we iteratively updated and enhanced our application to address any usability or functionality issues.

Some of the key aspects of our user testing process included:

After several rounds of user testing and incorporating the feedback we received, we successfully completed the final round of user acceptance testing. All our main features were functioning as intended, ensuring a seamless and user-friendly experience for our target audience.

Here are the results from the user testing

clinician Persona
clinician Persona

Unit Testing

We had to ensure that each individual part of our system works correctly, therefore we also implemented unit testing on Airtable database where we test whether the data there is stored and processed correctly. To implement that we decided to use the Airtable automation feature where JS script can be inserted allowing to access and manipulate records in the database.

Due to the fact the testing is being implemented within Airtable, the test results are updated in a seperate airtable table 'Testing' where there are two fields: Test description and a checkbox, whether that test was passed.

prototype image

In order to test each type of record in the database, we made an automation which would create a record (simulating storing data from softr) and after checking whether it gets integrated into the system correctly, the automation deletes it.

  • Clinicians
    • At first, the test creates a record in the "Users" table which contains values of a clinician who just did sign-up on the system and got approved by the hospital head.
    • The following step is retrieving the records from the "Teams" and "Hospitals" records and checking whether the user's email is included among his hospital's and team's members. This way, we ensure that the data is linked correctly when a new record is created.
    • Lastly, we check whether the fields in the "Users" record are correctly filled to ensure the accuracy of data mapping when a new clinician user account is added.
  • Hospital Sign-Up:
    • We test whether when a new Hospital Sign-Up is created, a new user is created in the "Users" table who has an automatically assigned status of a "Hospital Head".
    • We make sure that a new record is created in the "Hospitals" table and data is being mapped correctly between the tables. For example, the user who creates the hospital, has the status of "Hospital Head" .
    • In addition, It is also checked whether any data is not inserted in fields which should be empty for this user role to assure that the system does not perform any additional actions on the data behind the scenes.
  • Team Manager:
    • The tests checks when a clinician in the "Users" table gets assigned to be a manager of a team, a new team is created in the "Teams" table.
    • Then it makes sure that the data from the team manager's record in "Users" table is linked correctly to the "Teams" table.
    • Finally, we test if the fields in the "Users" table are still correctly filled, this way ensuring that any other data is not edited in the meanwhile.
  • Department Head:
    • We check, that when a record in the "Users" table is assigned a "Department Head" status, a new record is created in the "Departments" table containing the value from the "Department Name" field in the "Users" table.
    • Then, the test makes sure that there exists record in the "Hospitals" table whose hospital name matches with the one whose member the user is. This ensures that the data is linked properly between tables.
    • Eventually, we test if the fields in the "Users" table are still correctly filled, this way ensuring that any other data is not edited in the meanwhile.
  • Form and Questions
  • This test can be split into two categories:

    1. Form creation testing
      • At first, a new form, a first user, a second user who created the form, questions are automatically created.
      • Then "Users", "Forms", "Questions" tables are retrieved for further testing.
      • After that the test checks whether the user and the manager are from the same hospital and question's id is included in the created making sure data was linked accurately between tables.
    2. Form responses testing
      • If the first-step test runs successfuly, the previous records get updated simulating the user having responded the form.
      • Then response forms and questions were created.
      • In order to implement further testing, we retrieve the corresponding tables records: "Users", "Forms", "Questions", "Question Responses", "Form Responses".
      • Finally, we check whether the data is linked correctly between the tables.