Enhancements to Mobile testing in Qxf2 Page Object Model framework

Qxf2’s Page Object Model framework has been a dependable automation framework for mobile testing. Team Qxf2 has added support for all the major gestures to our framework. In this blog, we’ll explore these mobile capabilities that were added to our framework.

1. Swipe until an element is found

Swiping is a common gesture in mobile applications, used to navigate through screens or perform actions. Our framework now supports swipe gestures, allowing testers to simulate left, right, up, and down swipes. This enhancement ensures that we can test various swipe-based interactions, from carousel navigation to swipe-to-refresh features.
We have added the following code to the Mobile Base Page in our Page Object Model framework to enable users to swipe until a desired element is found

    def swipe_to_element(self,scroll_group_locator, search_element_locator, max_swipes=20, direction="up"):
        result_flag = False
        try:
            #Get the scroll view group
            scroll_group = self.get_element(scroll_group_locator)
 
            #Get the swipe coordinates
            coordinates = self.swipe_coordinates(scroll_group)
            start_x, start_y, end_x, end_y, center_x, center_y = (coordinates[key] for key in ["start_x", "start_y",
                                                                "end_x", "end_y", "center_x", "center_y"])
            #Get the search element locator
            path = self.split_locator(search_element_locator)
 
            #Perform swipes in a loop until the searchelement is found
            for _ in range(max_swipes):
                    #Return when search element is located
                try:
                    element = self.driver.find_element(*path)
                    if element.is_displayed():
                        self.write('Element found', 'debug')
                        result_flag = True
                        return result_flag
                except Exception:
                    self.write('Element not found, swiping again', 'debug')
                    pass
 
                # Perform swipe based on direction
                self.perform_swipe(direction, start_x, start_y,
                    end_x, end_y, center_x, center_y, duration=200)
 
            self.conditional_write(
                result_flag,
                positive=f'Located the element: {search_element_locator}',
                negative=f'Could not locate the element {search_element_locator} after swiping.'
            )
            return result_flag
 
        except Exception as e:
            self.write(str(e), 'debug')
            self.exceptions.append(f'Error while swiping to element - {search_element_locator}')

Now, let’s look at how the actual swipe is performed based on the direction provided

 
    def perform_swipe(self, direction, start_x, start_y, end_x, end_y, center_x, center_y, duration):
        "Perform swipe based on the direction"
        try:
            if direction == "up":
                self.driver.swipe(start_x=center_x, start_y=start_y, end_x=center_x, end_y=end_y, duration=duration)
            elif direction == "down":
                self.driver.swipe(start_x=center_x, start_y=end_y, end_x=center_x, end_y=start_y, duration=duration)
            elif direction == "left":
                self.driver.swipe(start_x=start_x, start_y=center_y, end_x=end_x, end_y=center_y, duration=duration)
            elif direction == "right":
                self.driver.swipe(start_x=end_x, start_y=center_y, end_x=start_x, end_y=center_y, duration=duration)
 
        except Exception as e:
            self.write(str(e), 'debug')
            self.exceptions.append("Error while performing swipe")
Swipe to element
Swipe until the element is found

2. Long Press

Long press is often used to trigger context menus or special actions within mobile apps. Our framework now supports long press gestures, allowing us to test these interactions thoroughly.
We have added the following method to enable users to long press on an element for a desired duration

 
    def long_press(self, element, duration=5):
        """
        Perform a long press gesture on the specified element.
        """
        result_flag = False
        try:
            # Convert element locator to WebDriver element
            web_element = self.get_element(element)
            # Perform long press gesture
            action = ActionChains(self.driver)
            action.click_and_hold(web_element).pause(duration).release().perform()
            result_flag = True
 
        except Exception as e:
            # Log error if any exception occurs
            self.write(str(e), 'debug')
            self.exceptions.append("Error while performing long press gesture.")      
        return result_flag
long press
Long press

3. Zoom Guestures

With the increasing use of multimedia and maps in mobile apps, zoom functionality is essential. Our framework now includes the ability to simulate pinch-to-zoom gesture. This allows us to test the zooming functionality on images, maps, and other zoomable content within an app.
The following code shows the implementation of Zoom Gestures in our framework.

def zoom(self, element_locator, zoom_direction="in"):
    """
    Perform zoom gesture.
    """
    try:
        # Get the element to be zoomed
        zoom_element = self.get_element(element_locator)
 
        # Get the center of the element
        coordinates = self.get_element_center(zoom_element)
        center_x = coordinates['center_x']
        center_y = coordinates['center_y']
        zoom_element_size = zoom_element.size
 
        # Perform zoom
        self.perform_zoom(zoom_direction, center_x, center_y)
 
        size_after_zoom = zoom_element.size
        width_change = size_after_zoom['width'] - zoom_element_size['width']
        height_change = size_after_zoom['height'] - zoom_element_size['height']
 
        if zoom_direction == "in":
            # Check if the size has increased
            if width_change > 0 and height_change > 0:
                return True
            else:
                return False
        elif zoom_direction == "out":
            # Check if the size has decreased
            if width_change < 0 and height_change < 0:
                return True
            else:
                return False
        else:
            # Invalid zoom direction
            self.write("Invalid zoom direction", 'debug')
            return False
 
    except Exception as e:
        self.write(str(e), 'debug')
        self.exceptions.append("An exception occurred when zooming")
        return False

Now, let’s see how the zoom is actually performed based on whether the user wants to Zoom ‘in’ or ‘Out’

    def perform_zoom(self, zoom_direction, center_x, center_y):
        """
        Execute zoom based on the zoom direction.
        """
        try:
            start_offset = 400
            end_offset = 100
            actions = ActionChains(self.driver)
            finger1 = actions.w3c_actions.add_pointer_input('touch', 'finger1')
            finger2 = actions.w3c_actions.add_pointer_input('touch', 'finger2')
 
            if zoom_direction == "in":
                start_offset = 100
                end_offset = 400
            finger1.create_pointer_move(x=center_x - start_offset, y=center_y)
            finger1.create_pointer_down(button=MouseButton.LEFT)
            finger1.create_pause(0.5)
            finger1.create_pointer_move(x=center_x - end_offset, y=center_y, duration=500)
            finger1.create_pointer_up(button=MouseButton.LEFT)
 
            finger2.create_pointer_move(x=center_x + start_offset, y=center_y)
            finger2.create_pointer_down(button=MouseButton.LEFT)
            finger2.create_pause(0.5)
            finger2.create_pointer_move(x=center_x + end_offset, y=center_y, duration=500)
            finger2.create_pointer_up(button=MouseButton.LEFT)
 
            actions.perform()
 
        except Exception as e:
            self.write(str(e), 'debug')
            self.exceptions.append("An exception occured when performing the zooming")

The following GIF shows the Zoom in gesture in action

Zoom in guesture
Zoom in gesture

Similarly, the following GIF shows the Zoom Out gesture in action

Zoom out gesture
Zoom out gesture

4. Scroll Backward/Forward

The ability to scroll forward and backward is crucial for navigating through content in most applications. This gesture is now supported in our framework, allowing testers to automate the process of scrolling through lists, feeds, or forms, ensuring that the app handles navigations gracefully.
The following code shows the implementation of ‘Backward scroll’ in our framework

def scroll_backward(self, distance):
    "Scroll backward"
 
    result_flag = False
    try:
        self.driver.find_element(
            AppiumBy.ANDROID_UIAUTOMATOR,
            value=f'new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollBackward({distance})'
        )
        result_flag = True
    except Exception as e:
        self.write(str(e), 'debug')
        self.exceptions.append("An exception occurred when scrolling backward")
    return result_flag
scroll backwards
Scroll Backward

Similarly, ‘Forward scroll’ is implemented as follows

def scroll_forward(self, distance):
    "Scroll forward"
 
    result_flag = False
    try:
        self.driver.find_element(
            AppiumBy.ANDROID_UIAUTOMATOR,
            value=f'new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollForward({distance})'
        )
        result_flag = True
    except Exception as e:
        self.write(str(e), 'debug')
        self.exceptions.append("An exception occurred when scrolling forward")
    return result_flag
scroll forward
Scroll Forward

5. Scroll to Bottom/Top

For a lot of applications, scrolling to the bottom of the page is a critical interaction. Our framework can now automate the ‘scroll-to-bottom gesture’, which is particularly useful for testing infinite scrolling, loading more content, or simply navigating to the end of a page or list.

def scroll_to_bottom(self, scroll_amount):
    """
    Scroll to the bottom of the page.
    """
    result_flag = False
    try:
        self.driver.find_elements(AppiumBy.ANDROID_UIAUTOMATOR,
        value=f'new UiScrollable(new UiSelector().scrollable(true).instance(0)).flingToEnd({scroll_amount})')
        result_flag = True
    except Exception as e:
        self.write(str(e), 'debug')
        self.exceptions.append("An exception occurred when scrolling to the bottom of the page")
    return result_flag
scroll to bottom
Scroll to the bottom of the page

Conversely, scrolling to the top is equally important. Whether it’s returning to the top of a feed, or a page, our new ‘scroll-to-top’ gesture ensures that this interaction is smooth and performs as expected in various scenarios.

    def scroll_to_top(self, scroll_amount):
        """
        Scroll to the top of the page.
        """
        result_flag = False
        try:
            self.driver.find_element(by=AppiumBy.ANDROID_UIAUTOMATOR,
            value=f'new UiScrollable(new UiSelector().scrollable(true).instance(0)).flingToBeginning(scroll_amount)')
            result_flag = True
        except Exception as e:
            self.write(str(e),'debug')
            self.exceptions.append("An exception occured when scrolling to top of page")
        return result_flag
scroll to top feature
Scroll to top

6. Support for Testing in Landscape Mode

Understanding that mobile applications must perform optimally in both portrait and landscape orientations, we’ve added support for testing in landscape mode. This enhancement allows testers to verify UI adaptability and responsiveness in landscape orientation and ensure that all elements are accessible and functional regardless of screen orientation.
You can switch the orientation of the device to landscape mode by using the CLI option --orientation="LANDSCAPE"
Example usage:

python -m pytest tests/test_weathershopper_app.py  --mobile_os_name Android --mobile_os_version 11 --device_name emulator-5554 --app_name weather_shopper.apk  --app_path /d/android_studio/app --app_package com.qxf2.weathershopper --orientation=LANDSCAPE
Landscape support
Testing in Landscape mode

In conclusion, Qxf2’s Page Object Model framework now supports a range of gestures, including swiping, long press, zoom, and scrolling. These updates have improved the framework’s capability to handle diverse mobile interactions effectively.


Hire technical testers from Qxf2

Qxf2 is the home for technical testers. We have a long history of open sourcing our work and supporting the testing community. More than 150 companies use our test automation framework. So, if you are looking for skilled testers with a strong technical mindset, reach out to us.


Leave a Reply

Your email address will not be published. Required fields are marked *