{"id":22072,"date":"2024-06-18T00:26:57","date_gmt":"2024-06-18T04:26:57","guid":{"rendered":"https:\/\/qxf2.com\/blog\/?p=22072"},"modified":"2024-06-18T00:26:57","modified_gmt":"2024-06-18T04:26:57","slug":"enhancements-to-mobile-testing-in-qxf2-page-object-model-framework","status":"publish","type":"post","link":"https:\/\/qxf2.com\/blog\/enhancements-to-mobile-testing-in-qxf2-page-object-model-framework\/","title":{"rendered":"Enhancements to Mobile testing in Qxf2 Page Object Model framework"},"content":{"rendered":"<p><a href=\"https:\/\/github.com\/qxf2\/qxf2-page-object-model\" rel=\"noopener\" target=\"_blank\">Qxf2&#8217;s Page Object Model framework<\/a> has been a dependable automation framework for mobile testing. Team <a href=\"https:\/\/qxf2.com\/?utm_source=enchance-mobile-testing&#038;utm_medium=click&#038;utm_campaign=From%20blog\" rel=\"noopener\" target=\"_blank\">Qxf2<\/a>  has added support for all the major gestures to our framework. In this blog, we&#8217;ll explore these mobile capabilities that were added to our framework.<\/p>\n<h4> 1. Swipe until an element is found <\/h4>\n<p>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.<br \/>\nWe have added the following code to the Mobile Base Page in our <a href=\"https:\/\/github.com\/qxf2\/qxf2-page-object-model\" rel=\"noopener\" target=\"_blank\">Page Object Model framework<\/a> to enable users to swipe until a desired element is found<\/p>\n<pre lang=\"python\">\r\n    def swipe_to_element(self,scroll_group_locator, search_element_locator, max_swipes=20, direction=\"up\"):\r\n        result_flag = False\r\n        try:\r\n            #Get the scroll view group\r\n            scroll_group = self.get_element(scroll_group_locator)\r\n\r\n            #Get the swipe coordinates\r\n            coordinates = self.swipe_coordinates(scroll_group)\r\n            start_x, start_y, end_x, end_y, center_x, center_y = (coordinates[key] for key in [\"start_x\", \"start_y\",\r\n                                                                \"end_x\", \"end_y\", \"center_x\", \"center_y\"])\r\n            #Get the search element locator\r\n            path = self.split_locator(search_element_locator)\r\n\r\n            #Perform swipes in a loop until the searchelement is found\r\n            for _ in range(max_swipes):\r\n                    #Return when search element is located\r\n                try:\r\n                    element = self.driver.find_element(*path)\r\n                    if element.is_displayed():\r\n                        self.write('Element found', 'debug')\r\n                        result_flag = True\r\n                        return result_flag\r\n                except Exception:\r\n                    self.write('Element not found, swiping again', 'debug')\r\n                    pass\r\n\r\n                # Perform swipe based on direction\r\n                self.perform_swipe(direction, start_x, start_y,\r\n                    end_x, end_y, center_x, center_y, duration=200)\r\n\r\n            self.conditional_write(\r\n                result_flag,\r\n                positive=f'Located the element: {search_element_locator}',\r\n                negative=f'Could not locate the element {search_element_locator} after swiping.'\r\n            )\r\n            return result_flag\r\n\r\n        except Exception as e:\r\n            self.write(str(e), 'debug')\r\n            self.exceptions.append(f'Error while swiping to element - {search_element_locator}')\r\n\r\n<\/pre>\n<p>Now, let&#8217;s look at how the actual swipe is performed based on the direction provided<\/p>\n<pre lang=\"python\">\r\n\r\n    def perform_swipe(self, direction, start_x, start_y, end_x, end_y, center_x, center_y, duration):\r\n        \"Perform swipe based on the direction\"\r\n        try:\r\n            if direction == \"up\":\r\n                self.driver.swipe(start_x=center_x, start_y=start_y, end_x=center_x, end_y=end_y, duration=duration)\r\n            elif direction == \"down\":\r\n                self.driver.swipe(start_x=center_x, start_y=end_y, end_x=center_x, end_y=start_y, duration=duration)\r\n            elif direction == \"left\":\r\n                self.driver.swipe(start_x=start_x, start_y=center_y, end_x=end_x, end_y=center_y, duration=duration)\r\n            elif direction == \"right\":\r\n                self.driver.swipe(start_x=end_x, start_y=center_y, end_x=start_x, end_y=center_y, duration=duration)\r\n\r\n        except Exception as e:\r\n            self.write(str(e), 'debug')\r\n            self.exceptions.append(\"Error while performing swipe\")\r\n<\/pre>\n<figure id=\"attachment_22239\" aria-describedby=\"caption-attachment-22239\" style=\"width: 215px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/06\/swipe_to_element-ezgif.com-loop-count.gif\" data-rel=\"lightbox-image-0\" data-rl_title=\"\" data-rl_caption=\"\" title=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/06\/swipe_to_element-ezgif.com-loop-count.gif\" alt=\"Swipe to element\" width=\"215\" height=\"469\" class=\"size-full wp-image-22239\" \/><\/a><figcaption id=\"caption-attachment-22239\" class=\"wp-caption-text\">Swipe until the element is found<\/figcaption><\/figure>\n<h4>2. Long Press<\/h4>\n<p>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.<br \/>\nWe have added the following method to enable users to long press on an element for a desired duration<\/p>\n<pre lang=\"python\">\r\n\r\n    def long_press(self, element, duration=5):\r\n        \"\"\"\r\n        Perform a long press gesture on the specified element.\r\n        \"\"\"\r\n        result_flag = False\r\n        try:\r\n            # Convert element locator to WebDriver element\r\n            web_element = self.get_element(element)\r\n            # Perform long press gesture\r\n            action = ActionChains(self.driver)\r\n            action.click_and_hold(web_element).pause(duration).release().perform()\r\n            result_flag = True\r\n\r\n        except Exception as e:\r\n            # Log error if any exception occurs\r\n            self.write(str(e), 'debug')\r\n            self.exceptions.append(\"Error while performing long press gesture.\")      \r\n        return result_flag\r\n<\/pre>\n<figure id=\"attachment_22241\" aria-describedby=\"caption-attachment-22241\" style=\"width: 215px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/06\/long_press-ezgif.com-loop-count.gif\" data-rel=\"lightbox-image-1\" data-rl_title=\"\" data-rl_caption=\"\" title=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/06\/long_press-ezgif.com-loop-count.gif\" alt=\"long press\" width=\"215\" height=\"469\" class=\"size-full wp-image-22241\" \/><\/a><figcaption id=\"caption-attachment-22241\" class=\"wp-caption-text\">Long press<\/figcaption><\/figure>\n<h4>3. Zoom Guestures<\/h4>\n<p>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.<br \/>\nThe following code shows the implementation of <strong>Zoom Gestures<\/strong> in our framework.<\/p>\n<pre lang=\"python\">\r\ndef zoom(self, element_locator, zoom_direction=\"in\"):\r\n    \"\"\"\r\n    Perform zoom gesture.\r\n    \"\"\"\r\n    try:\r\n        # Get the element to be zoomed\r\n        zoom_element = self.get_element(element_locator)\r\n\r\n        # Get the center of the element\r\n        coordinates = self.get_element_center(zoom_element)\r\n        center_x = coordinates['center_x']\r\n        center_y = coordinates['center_y']\r\n        zoom_element_size = zoom_element.size\r\n\r\n        # Perform zoom\r\n        self.perform_zoom(zoom_direction, center_x, center_y)\r\n\r\n        size_after_zoom = zoom_element.size\r\n        width_change = size_after_zoom['width'] - zoom_element_size['width']\r\n        height_change = size_after_zoom['height'] - zoom_element_size['height']\r\n\r\n        if zoom_direction == \"in\":\r\n            # Check if the size has increased\r\n            if width_change > 0 and height_change > 0:\r\n                return True\r\n            else:\r\n                return False\r\n        elif zoom_direction == \"out\":\r\n            # Check if the size has decreased\r\n            if width_change < 0 and height_change < 0:\r\n                return True\r\n            else:\r\n                return False\r\n        else:\r\n            # Invalid zoom direction\r\n            self.write(\"Invalid zoom direction\", 'debug')\r\n            return False\r\n\r\n    except Exception as e:\r\n        self.write(str(e), 'debug')\r\n        self.exceptions.append(\"An exception occurred when zooming\")\r\n        return False\r\n<\/pre>\n<p>Now, let's see how the zoom is actually performed based on whether the user wants to Zoom 'in' or 'Out' <\/p>\n<pre lang=\"python\">\r\n    def perform_zoom(self, zoom_direction, center_x, center_y):\r\n        \"\"\"\r\n        Execute zoom based on the zoom direction.\r\n        \"\"\"\r\n        try:\r\n            start_offset = 400\r\n            end_offset = 100\r\n            actions = ActionChains(self.driver)\r\n            finger1 = actions.w3c_actions.add_pointer_input('touch', 'finger1')\r\n            finger2 = actions.w3c_actions.add_pointer_input('touch', 'finger2')\r\n\r\n            if zoom_direction == \"in\":\r\n                start_offset = 100\r\n                end_offset = 400\r\n            finger1.create_pointer_move(x=center_x - start_offset, y=center_y)\r\n            finger1.create_pointer_down(button=MouseButton.LEFT)\r\n            finger1.create_pause(0.5)\r\n            finger1.create_pointer_move(x=center_x - end_offset, y=center_y, duration=500)\r\n            finger1.create_pointer_up(button=MouseButton.LEFT)\r\n\r\n            finger2.create_pointer_move(x=center_x + start_offset, y=center_y)\r\n            finger2.create_pointer_down(button=MouseButton.LEFT)\r\n            finger2.create_pause(0.5)\r\n            finger2.create_pointer_move(x=center_x + end_offset, y=center_y, duration=500)\r\n            finger2.create_pointer_up(button=MouseButton.LEFT)\r\n\r\n            actions.perform()\r\n\r\n        except Exception as e:\r\n            self.write(str(e), 'debug')\r\n            self.exceptions.append(\"An exception occured when performing the zooming\")\r\n<\/pre>\n<p>The following GIF shows the Zoom in gesture in action<br \/>\n<figure id=\"attachment_22243\" aria-describedby=\"caption-attachment-22243\" style=\"width: 195px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/06\/swag_lab_zoom_in-ezgif.com-loop-count.gif\" data-rel=\"lightbox-image-2\" data-rl_title=\"\" data-rl_caption=\"\" title=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/06\/swag_lab_zoom_in-ezgif.com-loop-count.gif\" alt=\"Zoom in guesture\" width=\"195\" height=\"433\" class=\"size-full wp-image-22243\" \/><\/a><figcaption id=\"caption-attachment-22243\" class=\"wp-caption-text\">Zoom in gesture<\/figcaption><\/figure><\/p>\n<p>Similarly, the following GIF shows the Zoom Out gesture in action<br \/>\n<figure id=\"attachment_22245\" aria-describedby=\"caption-attachment-22245\" style=\"width: 195px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/06\/swag_lab_zoom_out-ezgif.com-loop-count.gif\" data-rel=\"lightbox-image-3\" data-rl_title=\"\" data-rl_caption=\"\" title=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/06\/swag_lab_zoom_out-ezgif.com-loop-count.gif\" alt=\"Zoom out gesture\" width=\"195\" height=\"433\" class=\"size-full wp-image-22245\" \/><\/a><figcaption id=\"caption-attachment-22245\" class=\"wp-caption-text\">Zoom out gesture<\/figcaption><\/figure><\/p>\n<h4>4. Scroll Backward\/Forward<\/h4>\n<p>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.<br \/>\nThe following code shows the implementation of 'Backward scroll' in our framework<\/p>\n<pre lang=\"python\">\r\ndef scroll_backward(self, distance):\r\n    \"Scroll backward\"\r\n\r\n    result_flag = False\r\n    try:\r\n        self.driver.find_element(\r\n            AppiumBy.ANDROID_UIAUTOMATOR,\r\n            value=f'new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollBackward({distance})'\r\n        )\r\n        result_flag = True\r\n    except Exception as e:\r\n        self.write(str(e), 'debug')\r\n        self.exceptions.append(\"An exception occurred when scrolling backward\")\r\n    return result_flag\r\n<\/pre>\n<figure id=\"attachment_22248\" aria-describedby=\"caption-attachment-22248\" style=\"width: 215px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/06\/scroll_backward-ezgif.com-loop-count.gif\" data-rel=\"lightbox-image-4\" data-rl_title=\"\" data-rl_caption=\"\" title=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/06\/scroll_backward-ezgif.com-loop-count.gif\" alt=\"scroll backwards\" width=\"215\" height=\"469\" class=\"size-full wp-image-22248\" \/><\/a><figcaption id=\"caption-attachment-22248\" class=\"wp-caption-text\">Scroll Backward<\/figcaption><\/figure>\n<p>Similarly, 'Forward scroll' is implemented as follows<\/p>\n<pre lang=\"python\">\r\ndef scroll_forward(self, distance):\r\n    \"Scroll forward\"\r\n\r\n    result_flag = False\r\n    try:\r\n        self.driver.find_element(\r\n            AppiumBy.ANDROID_UIAUTOMATOR,\r\n            value=f'new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollForward({distance})'\r\n        )\r\n        result_flag = True\r\n    except Exception as e:\r\n        self.write(str(e), 'debug')\r\n        self.exceptions.append(\"An exception occurred when scrolling forward\")\r\n    return result_flag\r\n<\/pre>\n<figure id=\"attachment_22247\" aria-describedby=\"caption-attachment-22247\" style=\"width: 216px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/06\/scroll_forward-ezgif.com-loop-count.gif\" data-rel=\"lightbox-image-5\" data-rl_title=\"\" data-rl_caption=\"\" title=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/06\/scroll_forward-ezgif.com-loop-count.gif\" alt=\"scroll forward\" width=\"216\" height=\"469\" class=\"size-full wp-image-22247\" \/><\/a><figcaption id=\"caption-attachment-22247\" class=\"wp-caption-text\">Scroll Forward<\/figcaption><\/figure>\n<h4>5. Scroll to Bottom\/Top<\/h4>\n<p>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.<\/p>\n<pre lang=\"python\">\r\ndef scroll_to_bottom(self, scroll_amount):\r\n    \"\"\"\r\n    Scroll to the bottom of the page.\r\n    \"\"\"\r\n    result_flag = False\r\n    try:\r\n        self.driver.find_elements(AppiumBy.ANDROID_UIAUTOMATOR,\r\n        value=f'new UiScrollable(new UiSelector().scrollable(true).instance(0)).flingToEnd({scroll_amount})')\r\n        result_flag = True\r\n    except Exception as e:\r\n        self.write(str(e), 'debug')\r\n        self.exceptions.append(\"An exception occurred when scrolling to the bottom of the page\")\r\n    return result_flag\r\n<\/pre>\n<figure id=\"attachment_22237\" aria-describedby=\"caption-attachment-22237\" style=\"width: 215px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/06\/scroll_to_bottom-ezgif.com-loop-count.gif\" data-rel=\"lightbox-image-6\" data-rl_title=\"\" data-rl_caption=\"\" title=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/06\/scroll_to_bottom-ezgif.com-loop-count.gif\" alt=\"scroll to bottom\" width=\"215\" height=\"469\" class=\"size-full wp-image-22237\" \/><\/a><figcaption id=\"caption-attachment-22237\" class=\"wp-caption-text\">Scroll to the bottom of the page<\/figcaption><\/figure>\n<p>Conversely, scrolling to the top is equally important. Whether it\u2019s 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.<\/p>\n<pre lang=\"python\">\r\n    def scroll_to_top(self, scroll_amount):\r\n        \"\"\"\r\n        Scroll to the top of the page.\r\n        \"\"\"\r\n        result_flag = False\r\n        try:\r\n            self.driver.find_element(by=AppiumBy.ANDROID_UIAUTOMATOR,\r\n            value=f'new UiScrollable(new UiSelector().scrollable(true).instance(0)).flingToBeginning(scroll_amount)')\r\n            result_flag = True\r\n        except Exception as e:\r\n            self.write(str(e),'debug')\r\n            self.exceptions.append(\"An exception occured when scrolling to top of page\")\r\n        return result_flag\r\n<\/pre>\n<figure id=\"attachment_22234\" aria-describedby=\"caption-attachment-22234\" style=\"width: 215px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/06\/scroll_to_top-ezgif.com-loop-count.gif\" data-rel=\"lightbox-image-7\" data-rl_title=\"\" data-rl_caption=\"\" title=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/06\/scroll_to_top-ezgif.com-loop-count.gif\" alt=\"scroll to top feature\" width=\"215\" height=\"469\" class=\"size-full wp-image-22234\" \/><\/a><figcaption id=\"caption-attachment-22234\" class=\"wp-caption-text\">Scroll to top<\/figcaption><\/figure>\n<h4>6. Support for Testing in Landscape Mode<\/h4>\n<p>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.<br \/>\nYou can switch the orientation of the device to landscape mode by using the CLI option <code>--orientation=\"LANDSCAPE\"<\/code><br \/>\nExample usage:<\/p>\n<pre lang=\"bash\">\r\npython -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\r\n<\/pre>\n<figure id=\"attachment_22231\" aria-describedby=\"caption-attachment-22231\" style=\"width: 476px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/06\/landscape_mode-ezgif.com-loop-count-1.gif\" data-rel=\"lightbox-image-8\" data-rl_title=\"\" data-rl_caption=\"\" title=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/qxf2.com\/blog\/wp-content\/uploads\/2024\/06\/landscape_mode-ezgif.com-loop-count-1.gif\" alt=\"Landscape support\" width=\"476\" height=\"469\" class=\"size-full wp-image-22231\" \/><\/a><figcaption id=\"caption-attachment-22231\" class=\"wp-caption-text\">Testing in Landscape mode<\/figcaption><\/figure>\n<p>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.<\/p>\n<hr>\n<h4>Hire technical testers from Qxf2<\/h4>\n<p>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.<\/p>\n<hr>\n","protected":false},"excerpt":{"rendered":"<p>Qxf2&#8217;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&#8217;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 [&hellip;]<\/p>\n","protected":false},"author":29,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[50,47,71],"tags":[],"class_list":["post-22072","post","type-post","status-publish","format-standard","hentry","category-appium","category-mobile","category-mobile-automation"],"_links":{"self":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts\/22072","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/users\/29"}],"replies":[{"embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/comments?post=22072"}],"version-history":[{"count":54,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts\/22072\/revisions"}],"predecessor-version":[{"id":22276,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts\/22072\/revisions\/22276"}],"wp:attachment":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/media?parent=22072"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/categories?post=22072"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/tags?post=22072"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}