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 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.
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:
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.
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 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
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.
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.
This test can be split into two categories: