appium滑动操作详解
不管是在appium还是selenium的滑动操作都是如出一辙的,都是基于web driver中提供的TouchAction完成的。这里说一些关于web滑动的题外话,web自动化测试最好用的方法还是使用js,可以通过执行js脚本的方式达到向下滑动加载更多页面的目的。
TouchAction目前版本已经不再被支持了,取而代之的是ActionChains,ActionChains是一种自动化底层互动的方式,如鼠标移动、鼠标按键动作、按键和上下文菜单的交互。这对做更复杂的动作很有用,如悬停和拖放。生成用户动作。当你在ActionChains对象上调用动作的方法时、动作会被存储在ActionChains对象的队列中。当你调用perform()时,事件会按照它们的顺序被触发、被排队的顺序触发。
ActionChains可以连锁模式使用:
menu = driver.find_element(By.CSS_SELECTOR, ".nav") hidden_submenu = driver.find_element(By.CSS_SELECTOR, ".nav #submenu1") ActionChains(driver).move_to_element(menu).click(hidden_submenu).former()
或者可以将动作逐一排队,然后执行:
menu = driver.find_element(By.CSS_SELECTOR, ".nav") hidden_submenu = driver.find_element(By.CSS_SELECTOR, ".nav #submenu1") actions = ActionChains(driver) actions.move_to_element(menu) actions.click(hidden_submenu) actions.perform()
无论哪种方式,动作都是按照它们被调用的顺序执行的,一个接一个的被执行。ActionChains中包含了多种模拟操作,包括但不限于按键操作、拖拽操作、单击、双击、移动等操作。使用的时候要 from selenium.webdriver import ActionChains。
appium贴心的帮我们封装了ActionChains中的一些方法,根据坐标进行滑动的方法有self.driver.swipe(start_x: int, start_y: int, end_x: int, end_y: int, duration: int = 0)
,其实本质就是封装了ActionChains的move to 操作,
actions = ActionChains(self) actions.w3c_actions = ActionBuilder(self, mouse=PointerInput(interaction.POINTER_TOUCH, "touch")) actions.w3c_actions.pointer_action.move_to_location(start_x, start_y) actions.w3c_actions.pointer_action.pointer_down() actions.w3c_actions.pointer_action.pause(duration / 1000) actions.w3c_actions.pointer_action.move_to_location(end_x, end_y) actions.w3c_actions.pointer_action.release() actions.perform() return self # type: ignore
如果你觉得这种传值方式你不满意,不想要每次都用location函数计算element坐标再去滑动的话,知道原理后也可以自己封装一个方法使用,将获取元素location的方法写在里面,改造成这样:
def swipe_one_button_to_another_button(self, start_element, end_element): action = TouchAction(self.driver) start_location = start_element.location start_x = start_location['x'] start_y = start_location['y'] end_location = end_element.location end_x = end_location['x'] end_y = end_location['y'] action.press(x=start_x, y=start_y).move_to(x=end_x, y=end_y).release().perform()
这样就可以实现传入element了,如果你想传locator那也可以将find element的逻辑写在函数中。
self.driver.drag_and_drop(start_element, end_element), 这个方法更像是上面改造了的传值方法,但是观察源码会发现有一点区别:
def drag_and_drop(self, origin_el: WebElement, destination_el: WebElement) -> 'WebDriver': """Drag the origin element to the destination element Args: origin_el: the element to drag destination_el: the element to drag to Returns: Union['WebDriver', 'ActionHelpers']: Self instance """ actions = ActionChains(self) # 'mouse' pointer action actions.w3c_actions.pointer_action.click_and_hold(origin_el) actions.w3c_actions.pointer_action.move_to(destination_el) actions.w3c_actions.pointer_action.release() actions.perform() return self # type: ignore
这里有一步click and hold的操作,所以self.driver.drag_and_drop是点击第一个按钮然后拖拽到下一个按钮,页面往往不会停在最后的按钮上,而是惯性再向上一段距离。
在日常使用中可以根据自己的从需求完成不同方法的封装,一般情况下我们可能会遇到
-
从element拖到另一个element,这时候可以使用self.driver.drag_and_drop
-
从一个坐标到另一个坐标,这时候可以使用self.driver.swipe
-
从当前位置到另一个element,可以自己封装一个方法
def swipe_from_current_to_button(self, target_element): action = TouchAction(self.driver) action.move_to(target_element).release().perform()
-
任意向上或者向下滑动一段距离
def swipe_random_up_distance(self): action = TouchAction(self.driver) screen_size = self.driver.get_window_size() screen_width = screen_size['width'] screen_height = screen_size['height'] start_x = screen_width // 2 start_y = screen_height // 2 end_x = start_x end_y = start_y - 200 # 或者 +200 action.press(x=start_x, y=start_y).move_to(x=end_x, y=end_y).release().perform()
-
滑动屏幕直到有某个element,这个方法很常用,因为随着页面的render会出现我们想要的按钮,而不会一次render所有按钮
def swipe_screen_until_to_element(self, locator): while True: try: self.driver.find_element(*locator) break except NoSuchElementException: self.swipe_random_down_distance()
当然Android中有个神奇的方法也可以完成一样的事情,这是源于Android底层给我们抛出的方法
self.driver.find_element_by_android_uiautomator( 'new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().text("目标按钮文本").instance(0));' ).click()
这里会自动向上或者向下滚动查找目标按钮,new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView这个方法是根据UIautomator的方法完成的。