news 2026/5/16 20:31:07

【Appium 系列】第10节-手势操作实战 — 滑动、拖拽、缩放与轻拂

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Appium 系列】第10节-手势操作实战 — 滑动、拖拽、缩放与轻拂

对应代码:base/base_page.pyutils/gesture_helper.py

说明:本节所有手势操作均来自配套代码的BasePageGestureHelper,代码与实际源码 1:1 对应。


移动端测试跟 Web 测试最大的区别,就是手势

Web 上你能干的事基本就仨:点、输入、滚动。移动端不一样——滑动、拖拽、捏合、轻拂、长按,每个操作对应一个真实的用户场景。朋友圈列表要上滑刷新,地图要双指缩放,消息列表要左滑删除,这些手势测不到,移动端测试就不完整。

这一节把配套代码里所有手势操作串一遍,从BasePage的基础滑动到GestureHelper的多指触控,全部过完。


1. 滑动操作(swipe_up/down/left/right)

BasePage里最常用的就是四个方向的滑动,代码分布在base_page.py第 429-543 行。

def swipe_up(self, duration: int = 1000, distance: Optional[int] = None): size = self.driver.get_window_size() width = size['width'] height = size['height'] start_x = width // 2 start_y = int(height * 0.8) # 从屏幕 80% 位置开始 end_y = int(height * 0.2) if distance is None else start_y - distance end_x = start_x self.driver.swipe(start_x, start_y, end_x, end_y, duration)

四个方向的坐标计算逻辑

方向起点终点场景
swipe_up屏幕正中、Y=80%处Y=20%处列表上滑刷新
swipe_down屏幕正中、Y=20%处Y=80%处下拉加载历史
swipe_leftX=80%处、屏幕正中X=20%处翻页/删除
swipe_rightX=20%处、屏幕正中X=80%处侧滑菜单

核心思路:以屏幕百分比算坐标,不写死像素值。不同手机分辨率不一样,直接写start_y = 800换台设备就废了。

distance参数可以控制滑动距离。不传时默认滑半个屏幕,传了就从起点偏移指定像素。

自定义滑动(第 525-542 行):

def swipe(self, start_x: int, start_y: int, end_x: int, end_y: int, duration: int = 1000): self.driver.swipe(start_x, start_y, end_x, end_y, duration)

四个方向的滑动都是调driver.swipe(),只是算好了起止坐标。如果标准方向不够用,直接调swipe()传自定义坐标就行。


2. 滚动到元素(scroll_to_element)

列表页测到某个特定元素时,不能指望它一打开就在屏幕上。BasePage第 794-822 行做了这件事:

def scroll_to_element(self, locator_type: str, locator_value: str, direction: str = "down", max_scrolls: int = 10): for i in range(max_scrolls): if self.is_element_displayed(locator_type, locator_value, timeout=2): logger.info(f"找到目标元素,滚动次数: {i+1}") return True if direction <span class="wx-em-red"> "down": self.swipe_up() else: self.swipe_down() time.sleep(0.5) logger.warning(f"滚动{max_scrolls}次后仍未找到元素") return False

逻辑就是:每次滑动前先看看目标在不在,不在就滑一屏,最多滑 10 次direction="down"表示页面往下(新内容在上方,所以要上滑页面),direction="up"反之。

使用场景:电商 App 的商品列表页,翻到底部找到"加载更多"按钮;设置页找到某个深层选项。

GestureHelper里的scroll_to_text(第 535-636 行)是升级版——通过文本内容找元素,Android 上优先用UiScrollable原生滚动 API,兜底用 W3C Actions 滚动:

# UiScrollable 方式(Android 专属,速度快且精准) scrollable_selector = ( f'new UiScrollable(' f'new UiSelector().scrollable(true).instance(0))' f'.scrollIntoView(' f'new UiSelector().textContains("{text}").instance(0))' ) element = self.driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, scrollable_selector)

scroll_to_text返回找到的元素,scroll_to_element返回布尔值。前者适合找文本内容的场景,后者适合用定位器找任意元素。


3. 拖拽操作(drag_and_drop)

拖拽在GestureHelper中,是配套代码里最复杂的单指手势之一。

支持三种调用方式:

# 方式1:元素拖到元素 gesture.drag_and_drop(source_btn, target_area) # 方式2:元素拖到坐标 gesture.drag_and_drop(source_btn, end_x=500, end_y=800) # 方式3:坐标到坐标 gesture.drag_and_drop(start_x=200, start_y=300, end_x=500, end_y=800)

内部实现用了 W3C Actions API。关键逻辑:

finger = self._create_pointer_input("drag_finger") actions = ActionChains(self.driver) actions.w3c_actions.add_pointer_input(finger) # 第一步:移动到起始位置并按住 finger.pointer_move(x=start_x, y=start_y) finger.pointer_down(button=MouseButton.LEFT) finger.pause(duration / 1000.0) # 第二步:拆分成多步,平滑移动到目标 steps = max(1, duration // 100) for i in range(1, steps + 1): progress = i / steps current_x = int(start_x + (end_x - start_x) * progress) current_y = int(start_y + (end_y - start_y) * progress) finger.pointer_move(x=current_x, y=current_y) # 第三步:松开 finger.pointer_up(button=MouseButton.LEFT) actions.perform()

两个设计细节值得说:

  • 平滑拖拽:不是从 A 瞬移到 B,而是按进度拆成多步,每 100ms 一步,看起来跟真人的拖拽轨迹一样。App 端如果检测到拖拽动作太生硬(坐标突变),可能会拒掉这次操作。
  • 坐标推导:传了元素没传坐标,自动取元素中心点当坐标;传了坐标就不管元素了。rect属性包含xywidthheight,中心点就是(x + w/2, y + h/2)

还有一个简化版drag_and_drop_fast,用ActionChains链式调用,不做平滑步进,适合不需要精细控制的简单拖拽场景。


4. 双指缩放(pinch_in/pinch_out)

双指缩放是移动端独有的操作。地图要放大缩小、图片要缩放查看,单指做不到——必须模拟两根手指同时触控。

GestureHelper实现了两个方法:

  • pinch_in:两指向内捏合,缩小画面
  • pinch_out:两指向外扩张,放大画面

pinch_in为例:

# 创建两根手指 finger1 = self._create_pointer_input("pinch_finger_1") finger2 = self._create_pointer_input("pinch_finger_2") # 手指1:从中心左上方 → 中心 f1_start_x = center_x - offset f1_start_y = center_y - offset f1_end_x = center_x f1_end_y = center_y # 手指2:从中心右下方 → 中心 f2_start_x = center_x + offset f2_start_y = center_y + offset f2_end_x = center_x f2_end_y = center_y # 两根手指同时向中心移动(拆分成多步) steps = max(1, duration // 100) for i in range(1, steps + 1): progress = i / steps fx1 = int(f1_start_x + (f1_end_x - f1_start_x) * progress) fy1 = int(f1_start_y + (f1_end_y - f1_start_y) * progress) finger1.pointer_move(x=fx1, y=fy1) fx2 = int(f2_start_x + (f2_end_x - f2_start_x) * progress) fy2 = int(f2_start_y + (f2_end_y - f2_start_y) * progress) finger2.pointer_move(x=fx2, y=fy2) # 分别执行两根手指的动作 action1 = ActionChains(self.driver) action1.w3c_actions.add_pointer_input(finger1) action2 = ActionChains(self.driver) action2.w3c_actions.add_pointer_input(finger2) action1.perform() action2.perform()

pinch_in 手势原理:两根手指分别放在中心点的左上和右下两个角,同时向中心移动。offset控制两指之间的初始距离,distance越大,捏合幅度越大。

pinch_out 手势原理:反过来,两根手指从中心同时向两个对角方向移动。起点都在屏幕中心,终点分别是左上和右下方向。

注意这里有个重要的实现细节:W3C Actions API 不支持真正的"同时执行"多指动作,只能按顺序perform——先执行action.perform()(手指1),再执行action2.perform()(手指2)。虽然两根手指不是严格同步的,但在 500ms 内先后执行,效果上基本等效于同时捏合。


5. 轻拂操作(flick)

轻拂(flick)跟普通滑动(swipe)的区别在于时间更短、速度更快。普通滑动持续 1000ms,轻拂默认只有 100ms。用户场景是快速翻页、列表项左滑删除这类操作。

GestureHelper中:

def flick(self, start_x: int = None, start_y: int = None, end_x: int = None, end_y: int = None, direction: str = None, distance: int = 200, duration: int = 100): # 支持两种模式:指定方向、指定坐标

方向模式自动计算坐标:

方向起点终点
up屏幕正中间偏下向上偏移 distance
down屏幕正中间偏上向下偏移 distance
left屏幕中偏右向左偏移 distance
right屏幕中偏左向右偏移 distance

swipe_up/down/left/right的区别就是duration=100duration=1000快得多,而且滑动距离默认只有 200px 而不是半个屏幕。

元素级别轻拂flick_element专门做列表项滑动删除:

def flick_element(self, element, direction: str = "left", distance: int = 150, duration: int = 100): rect = element.rect element_center_x = rect['x'] + rect['width'] // 2 element_center_y = rect['y'] + rect['height'] // 2 if direction </span> "left": start_x = element_center_x + rect['width'] // 4 start_y = element_center_y end_x = start_x - distance end_y = element_center_y # ... 其余方向类似

使用方式:

gesture.flick_element(list_item, direction="left") # 在列表项上左滑,露出删除按钮

起点不再是屏幕百分比,而是元素内部的某个位置(中心偏移 1/4 宽度),保证轻拂动作发生在该元素上而不是随便一个位置。


6. 长按操作(long_press)

长按在BasePage第 546-593 行,有两个方法。

长按元素(第 546-576 行):

def long_press(self, locator_type: str, locator_value: str, duration: int = 2000, timeout: int = 10): element = self.find_element(locator_type, locator_value, timeout) # 优先使用 W3C Actions API try: from selenium.webdriver.common.action_chains import ActionChains actions = ActionChains(self.driver) actions.click_and_hold(element).pause(duration / 1000).release().perform() except Exception: # 回退到 TouchAction from appium.webdriver.common.touch_action import TouchAction action = TouchAction(self.driver) action.long_press(element, duration=duration).release().perform()

长按坐标(第 578-593 行):

def long_press_by_coordinates(self, x: int, y: int, duration: int = 2000): from appium.webdriver.common.touch_action import TouchAction action = TouchAction(self.driver) action.long_press(x=x, y=y, duration=duration).release().perform()

使用场景

  • 长按桌面图标弹出快捷菜单
  • 长按聊天消息复制/删除/转发
  • 长按输入框粘贴最近复制的内容

注意long_press包含降级逻辑——优先用 W3C Actions,不行再切 TouchAction。long_press_by_coordinates目前只用了 TouchAction,因为click_and_hold在坐标模式下不太好使。


7. 踩坑:TouchAction 废弃 vs W3C Actions API

这是这节最重要的部分。

Appium 1.x 时代,所有手势操作都靠TouchAction

from appium.webdriver.common.touch_action import TouchAction action = TouchAction(driver) action.press(x=100, y=200).wait(500).move_to(x=300, y=400).release().perform()

这套 API 用了好几年,简单直观。但 Appium 2.x 明确把它标为废弃(deprecated),推荐全面迁移到W3C WebDriver Actions API

为什么废弃TouchAction是 Appium 自己造的轮子,不在 W3C 标准规范里。W3C Actions API 是所有浏览器自动化工具通用的标准,Selenium 4 已经完全切换到这套 API。Appium 2.x 跟进标准,废弃了非标准的 TouchAction。

配套代码里两种方式都用着,但策略是优先 W3C Actions,TouchAction 只做回退

  • long_press先试ActionChains.click_and_hold(),失败再切TouchAction.long_press()
  • GestureHelper全部用 W3C Actions(PointerInput+ActionChains
  • swipe_up/down/left/rightdriver.swipe()——这是 Appium 封装的简便方法,底层在 Appium 2.x 已切到 W3C 实现

如果你从 Appium 1.x 项目迁移上来,注意这些差异

对比项TouchAction(废弃)W3C Actions API
导入包appium.webdriver.common.touch_actionselenium.webdriver.common.actions
多指支持MultiAction 包装多 PointerInput 实例
标准程度Appium 私有W3C 行业标准
跨版本兼容Appium 2.x 不可靠Appium 2.x 原生支持
代码复杂度简洁稍复杂,粒度更细

迁移示例

# 旧:TouchAction 方式(别用了) action = TouchAction(driver) action.press(el).wait(500).move_to(el2).release().perform() # 新:W3C Actions API from selenium.webdriver.common.action_chains import ActionChains actions = ActionChains(driver) actions.click_and_hold(el).pause(0.5).move_to_element(el2).release().perform()

多指触控的迁移更明显

# 旧:MultiAction 方式 finger1 = TouchAction(driver).press(x=100, y=200) finger2 = TouchAction(driver).press(x=300, y=400) multi = MultiAction(driver) multi.add(finger1, finger2) multi.perform() # 新:两个 PointerInput + 两个 ActionChains finger1 = PointerInput(interaction.POINTER_TOUCH, "finger1") finger2 = PointerInput(interaction.POINTER_TOUCH, "finger2") action1 = ActionChains(driver) action1.w3c_actions.add_pointer_input(finger1) action2 = ActionChains(driver) action2.w3c_actions.add_pointer_input(finger2) # ... 分别构建动作序列 action1.perform() action2.perform()

配套代码的GestureHelper全部用新写法,如果想参考多指触控的实现,直接看pinch_inpinch_out两个方法就行。


总结

六个手势操作,三个在BasePage,三个在GestureHelper

操作所在文件核心方法场景
滑动BasePageswipe_up/down/left/right,swipe列表滚动、页面翻页
滚动到元素BasePagescroll_to_element列表中找到特定元素
拖拽GestureHelperdrag_and_drop图标排序、拼图游戏
缩放GestureHelperpinch_in,pinch_out地图放大缩小、图片缩放
轻拂GestureHelperflick,flick_element快速翻页、左滑删除
长按BasePagelong_press,long_press_by_coordinates弹出菜单、快捷操作

关键规则

  1. W3C Actions API 是现在和未来,新代码全部用它
  2. 坐标用百分比算,不写死像素值——不同分辨率设备差异巨大
  3. 复杂手势拆多步实现平滑移动——App 端会拒掉生硬的瞬间位移
  4. 降级兜底——W3C Actions 不行就切 TouchAction,保证兼容性
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/16 20:25:31

【VCS】(6)Code Coverage:从覆盖率收集到报告生成的全流程实战

1. 代码覆盖率基础概念 第一次接触代码覆盖率这个概念时&#xff0c;我也是一头雾水。记得当时领导问我&#xff1a;"这个模块的验证覆盖率多少了&#xff1f;"我只能支支吾吾说还在跑仿真。后来才明白&#xff0c;代码覆盖率是衡量验证完整性的重要指标&#xff0c;…

作者头像 李华
网站建设 2026/5/16 20:22:12

【职场】职场里,那些永远“没问题“的人,最终都出了大问题

职场里&#xff0c;那些永远"没问题"的人&#xff0c;最终都出了大问题 ——写给那些把"我可以"挂在嘴边&#xff0c;却把崩溃藏在心里的人有一种人&#xff0c;你在职场里一定见过。 他们永远精神饱满&#xff0c;永远面带微笑&#xff0c;永远第一个举手…

作者头像 李华
网站建设 2026/5/16 20:13:53

彻底掌控你的Windows 11:Win11Debloat一键优化指南

彻底掌控你的Windows 11&#xff1a;Win11Debloat一键优化指南 【免费下载链接】Win11Debloat A simple, lightweight PowerShell script that allows you to remove pre-installed apps, disable telemetry, as well as perform various other changes to declutter and custo…

作者头像 李华
网站建设 2026/5/16 20:11:15

为什么你的Tea印相总显“假胶片”?5个高频失效场景+对应prompt结构重写模板(附CMYK通道对比图谱)

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;Tea印相的本质与胶片感失真根源 Tea印相并非一种物理冲洗工艺&#xff0c;而是基于数字图像处理的语义化模拟范式——它将胶片成像中不可控的化学扩散、颗粒随机分布与光学畸变&#xff0c;抽象为可编程…

作者头像 李华