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") |
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 |
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
Similarly, the following GIF shows the Zoom Out gesture in action
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 |
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 |
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 |
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 |
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 |
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.
I am a QA Engineer. I completed my engineering in computer science and joined Qxf2 as an intern QA. During my internship, I got a thorough insight into software testing methodologies and practices. I later joined Qxf2 as a full-time employee after my internship period. I love automation testing and seek to learn more with new experiences and challenges. My hobbies are reading books, listening to music, playing chess and soccer.