使用Python操作你的手机(Appium入门)
目录
- 使用Python操作你的手机(Appium入门)
- 1. 引言
- 2. 环境准备
- 2.1 安装Node.js和NPM
- 2.2 安装Appium
- 2.3 安装Appium Python客户端
- 2.4 安装和配置Android SDK
- 2.5 安装Java Development Kit (JDK)
- 3. Appium基础概念
- 3.1 Appium架构
- 3.2 Desired Capabilities
- 3.3 元素定位策略
- 4. 编写第一个Appium脚本
- 4.1 导入必要的库
- 4.2 配置Desired Capabilities
- 4.3 实现计算器操作
- 4.4 完整的测试类
- 5. 进阶操作
- 5.1 处理不同的元素定位情况
- 5.2 手势操作
- 5.3 处理弹窗和权限请求
- 5.4 等待策略
- 6. 完整代码示例
- 7. 常见问题与解决方案
- 7.1 连接问题
- 7.2 元素找不到问题
- 7.3 权限问题
- 7.4 性能问题
- 8. 最佳实践
- 8.1 代码组织
- 8.2 错误处理
- 8.3 维护性
- 9. 总结
『宝藏代码胶囊开张啦!』—— 我的 CodeCapsule 来咯!✨
写代码不再头疼!我的新站点 CodeCapsule 主打一个 “白菜价”+“量身定制”!无论是卡脖子的毕设/课设/文献复现,需要灵光一现的算法改进,还是想给项目加个“外挂”,这里都有便宜又好用的代码方案等你发现!低成本,高适配,助你轻松通关!速来围观 👉 CodeCapsule官网
使用Python操作你的手机(Appium入门)
1. 引言
在当今移动互联网时代,手机应用已经成为人们日常生活中不可或缺的一部分。随着移动应用的快速发展,自动化测试和手机操作的需求也日益增长。Appium作为一个开源的移动应用自动化测试框架,能够帮助我们实现这一目标。
Appium支持多种编程语言,包括Python、Java、Ruby等,并可以同时测试Android和iOS平台的应用。它采用WebDriver协议,使得我们可以使用熟悉的Selenium WebDriver API来编写移动应用的自动化脚本。
本文将详细介绍如何使用Python和Appium来操作手机,从环境搭建到实际脚本编写,帮助读者快速掌握这一实用技能。
2. 环境准备
在开始使用Appium之前,我们需要完成一系列的环境配置工作。以下是详细的步骤:
2.1 安装Node.js和NPM
Appium服务器是基于Node.js开发的,因此首先需要安装Node.js。可以从Node.js官网下载并安装最新版本。
安装完成后,可以通过以下命令验证安装是否成功:
node --version
npm --version
2.2 安装Appium
通过NPM全局安装Appium:
npm install -g appium
安装完成后,可以通过以下命令启动Appium服务器:
appium
2.3 安装Appium Python客户端
使用pip安装Appium的Python客户端库:
pip install Appium-Python-Client
2.4 安装和配置Android SDK
对于Android设备,需要安装Android SDK并配置环境变量:
- 下载Android Studio或独立SDK工具
- 设置ANDROID_HOME环境变量
- 将platform-tools和tools目录添加到PATH环境变量中
2.5 安装Java Development Kit (JDK)
Appium需要Java环境支持,请安装JDK 8或更高版本。
3. Appium基础概念
3.1 Appium架构
Appium采用客户端-服务器架构:
- Appium服务器:接收来自客户端的命令,并将其转换为移动设备可以理解的原生命令
- Appium客户端:各种编程语言的客户端库,用于发送命令到Appium服务器
3.2 Desired Capabilities
Desired Capabilities是一组键值对,用于告诉Appium服务器我们想要启动怎样的会话。常见的Capabilities包括:
platformName:平台名称(Android或iOS)platformVersion:平台版本deviceName:设备名称appPackage:应用包名appActivity:应用活动名
3.3 元素定位策略
Appium支持多种元素定位策略:
- ID定位
- Class Name定位
- XPath定位
- Accessibility ID定位
- Android UI Automator定位
4. 编写第一个Appium脚本
下面我们将编写一个简单的Appium脚本,用于打开手机上的计算器应用并进行简单的计算操作。
4.1 导入必要的库
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
from appium.options.android import UiAutomator2Options
import time
import unittest
4.2 配置Desired Capabilities
def setup_driver():# 配置Appium选项options = UiAutomator2Options()# 设置设备基本信息options.platform_name = 'Android'options.platform_version = '12' # 根据你的设备版本修改options.device_name = 'Android Emulator'options.automation_name = 'UiAutomator2'# 设置应用信息options.app_package = 'com.android.calculator2'options.app_activity = 'com.android.calculator2.Calculator'# 其他设置options.no_reset = True # 不重置应用状态# 连接Appium服务器driver = webdriver.Remote('http://localhost:4723', options=options)return driver
4.3 实现计算器操作
def calculator_operations(driver):"""执行计算器操作"""try:# 等待计算器加载完成time.sleep(2)# 定位数字按钮和操作符# 注意:不同设备上的计算器应用可能具有不同的元素ID# 这里使用的是Android原生计算器的元素ID# 点击数字 7btn_7 = driver.find_element(AppiumBy.ID, 'com.android.calculator2:id/digit_7')btn_7.click()# 点击加号btn_plus = driver.find_element(AppiumBy.ID, 'com.android.calculator2:id/op_add')btn_plus.click()# 点击数字 8btn_8 = driver.find_element(AppiumBy.ID, 'com.android.calculator2:id/digit_8')btn_8.click()# 点击等号btn_equals = driver.find_element(AppiumBy.ID, 'com.android.calculator2:id/eq')btn_equals.click()# 获取结果result = driver.find_element(AppiumBy.ID, 'com.android.calculator2:id/result')calculated_result = result.textprint(f"计算结果: 7 + 8 = {calculated_result}")# 验证结果是否正确expected_result = '15'if calculated_result == expected_result:print("测试通过!计算结果正确。")else:print(f"测试失败!期望结果: {expected_result}, 实际结果: {calculated_result}")return calculated_resultexcept Exception as e:print(f"操作过程中出现错误: {str(e)}")return None
4.4 完整的测试类
class CalculatorTest(unittest.TestCase):"""计算器测试类"""def setUp(self):"""测试前置设置"""self.driver = setup_driver()self.driver.implicitly_wait(10) # 设置隐式等待时间def tearDown(self):"""测试后置清理"""if self.driver:self.driver.quit()def test_addition_operation(self):"""测试加法运算"""result = calculator_operations(self.driver)self.assertEqual(result, '15', "加法运算结果不正确")def test_clear_operation(self):"""测试清除操作"""try:# 先输入一些数字btn_5 = self.driver.find_element(AppiumBy.ID, 'com.android.calculator2:id/digit_5')btn_5.click()# 点击清除按钮btn_clear = self.driver.find_element(AppiumBy.ID, 'com.android.calculator2:id/clr')btn_clear.click()# 验证显示区域是否已清除result = self.driver.find_element(AppiumBy.ID, 'com.android.calculator2:id/formula')current_display = result.text# 清除后显示区域应该为空或显示0self.assertTrue(not current_display or current_display == '0', "清除操作未正常工作")except Exception as e:self.fail(f"清除操作测试失败: {str(e)}")
5. 进阶操作
5.1 处理不同的元素定位情况
在实际应用中,我们可能会遇到各种复杂的定位情况。以下是一些常用的定位方法:
def advanced_element_locating(driver):"""演示高级元素定位方法"""# 1. 使用XPath定位# 通过文本内容定位元素element_by_text = driver.find_element(AppiumBy.XPATH, "//*[@text='确定']")# 通过部分文本内容定位element_by_partial_text = driver.find_element(AppiumBy.XPATH, "//*[contains(@text, '确定')]")# 2. 使用Accessibility ID定位(通常对应content-desc属性)element_by_accessibility = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "按钮描述")# 3. 使用Class Name定位elements_by_class = driver.find_elements(AppiumBy.CLASS_NAME, "android.widget.Button")# 4. 使用Android UI Automator定位element_by_uiautomator = driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("确定")')return {'by_text': element_by_text,'by_partial_text': element_by_partial_text,'by_accessibility': element_by_accessibility,'by_class': elements_by_class,'by_uiautomator': element_by_uiautomator}
5.2 手势操作
Appium支持多种手势操作,如滑动、长按、拖拽等:
def gesture_operations(driver):"""演示手势操作"""# 获取屏幕尺寸window_size = driver.get_window_size()screen_width = window_size['width']screen_height = window_size['height']# 1. 滑动操作 - 从底部滑动到顶部start_x = screen_width / 2start_y = screen_height * 0.8end_x = screen_width / 2end_y = screen_height * 0.2driver.swipe(start_x, start_y, end_x, end_y, 1000)# 2. 滚动操作# 滚动到指定元素scroll_to_element = driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR,'new UiScrollable(new UiSelector().scrollable(true))''.scrollIntoView(new UiSelector().text("目标元素"))')# 3. 长按操作element_to_long_press = driver.find_element(AppiumBy.ID, 'some.element.id')driver.long_press(element_to_long_press)# 4. 拖拽操作source_element = driver.find_element(AppiumBy.ID, 'source.element')target_element = driver.find_element(AppiumBy.ID, 'target.element')driver.drag_and_drop(source_element, target_element)
5.3 处理弹窗和权限请求
def handle_popups_and_permissions(driver):"""处理弹窗和权限请求"""try:# 尝试查找并点击允许按钮allow_button = driver.find_element(AppiumBy.ID, 'com.android.packageinstaller:id/permission_allow_button')allow_button.click()print("已处理权限请求")except Exception:# 如果找不到特定的允许按钮,尝试其他方式try:# 使用文本定位允许按钮allow_by_text = driver.find_element(AppiumBy.XPATH, "//*[@text='允许' or @text='ALLOW']")allow_by_text.click()print("通过文本定位处理了权限请求")except Exception:print("未找到权限请求弹窗或处理失败")# 处理其他类型的弹窗try:# 查找确定、好的、知道了等按钮confirm_buttons = ["确定", "确认", "好的", "知道了", "OK", "Okay"]for button_text in confirm_buttons:try:confirm_btn = driver.find_element(AppiumBy.XPATH, f"//*[@text='{button_text}']")confirm_btn.click()print(f"点击了 {button_text} 按钮")breakexcept Exception:continueexcept Exception as e:print(f"处理弹窗时出现错误: {str(e)}")
5.4 等待策略
合理的等待策略对于自动化测试的稳定性至关重要:
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as ECdef wait_strategies(driver):"""演示不同的等待策略"""# 1. 显式等待 - 等待元素可点击wait = WebDriverWait(driver, 10)element = wait.until(EC.element_to_be_clickable((AppiumBy.ID, 'some.element.id')))element.click()# 2. 显式等待 - 等待元素可见visible_element = wait.until(EC.visibility_of_element_located((AppiumBy.ID, 'visible.element')))# 3. 显式等待 - 等待元素存在(不一定可见)present_element = wait.until(EC.presence_of_element_located((AppiumBy.ID, 'present.element')))# 4. 自定义等待条件def custom_condition(driver):"""自定义等待条件"""try:element = driver.find_element(AppiumBy.ID, 'custom.element')return element.is_displayed()except Exception:return Falsecustom_element = wait.until(custom_condition)return {'clickable': element,'visible': visible_element,'present': present_element,'custom': custom_element}
6. 完整代码示例
下面是一个完整的Appium脚本示例,展示了如何使用Python操作手机:
#!/usr/bin/env python3
"""
Appium手机操作示例
作者:你的名字
日期:2024年1月
描述:使用Python和Appium操作手机计算器应用
"""import time
import unittest
import logging
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
from appium.options.android import UiAutomator2Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException# 配置日志
logging.basicConfig(level=logging.INFO,format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)class MobileAutomationFramework:"""移动自动化框架类"""def __init__(self, server_url='http://localhost:4723'):self.server_url = server_urlself.driver = Noneself.wait = Nonedef setup_driver(self, capabilities_dict=None):"""设置Appium驱动"""if capabilities_dict is None:# 默认配置 - Android计算器capabilities_dict = {'platformName': 'Android','platformVersion': '12','deviceName': 'Android Emulator','automationName': 'UiAutomator2','appPackage': 'com.android.calculator2','appActivity': 'com.android.calculator2.Calculator','noReset': True,'newCommandTimeout': 300}try:options = UiAutomator2Options()for key, value in capabilities_dict.items():setattr(options, key, value)logger.info("正在连接Appium服务器...")self.driver = webdriver.Remote(self.server_url, options=options)# 设置显式等待self.wait = WebDriverWait(self.driver, 15)logger.info("Appium驱动设置成功")return Trueexcept Exception as e:logger.error(f"设置Appium驱动失败: {str(e)}")return Falsedef teardown(self):"""清理资源"""if self.driver:self.driver.quit()logger.info("Appium驱动已关闭")def find_element_with_wait(self, by, value, timeout=15):"""带等待的元素查找"""wait = WebDriverWait(self.driver, timeout)return wait.until(EC.presence_of_element_located((by, value)))def safe_click(self, by, value, timeout=15):"""安全的点击操作"""try:element = self.find_element_with_wait(by, value, timeout)element.click()logger.info(f"成功点击元素: {value}")return Trueexcept Exception as e:logger.error(f"点击元素失败: {value}, 错误: {str(e)}")return Falsedef take_screenshot(self, filename=None):"""截取屏幕截图"""if filename is None:filename = f"screenshot_{int(time.time())}.png"try:self.driver.save_screenshot(filename)logger.info(f"截图已保存: {filename}")return Trueexcept Exception as e:logger.error(f"截图失败: {str(e)}")return Falsedef get_page_source(self):"""获取页面源代码"""try:return self.driver.page_sourceexcept Exception as e:logger.error(f"获取页面源代码失败: {str(e)}")return Noneclass CalculatorAutomation(MobileAutomationFramework):"""计算器自动化类"""def __init__(self):super().__init__()self.number_mapping = {'0': 'digit_0', '1': 'digit_1', '2': 'digit_2','3': 'digit_3', '4': 'digit_4', '5': 'digit_5','6': 'digit_6', '7': 'digit_7', '8': 'digit_8','9': 'digit_9'}self.operator_mapping = {'+': 'op_add', '-': 'op_sub', '*': 'op_mul', '/': 'op_div'}def input_number(self, number):"""输入数字"""if not isinstance(number, (int, str)):raise ValueError("数字必须是整数或字符串")number_str = str(number)for digit in number_str:if digit in self.number_mapping:element_id = f"com.android.calculator2:id/{self.number_mapping[digit]}"self.safe_click(AppiumBy.ID, element_id)time.sleep(0.1) # 短暂延迟,确保输入稳定else:logger.warning(f"无法识别的数字: {digit}")def input_operator(self, operator):"""输入操作符"""if operator not in self.operator_mapping:raise ValueError(f"不支持的操作符: {operator}")element_id = f"com.android.calculator2:id/{self.operator_mapping[operator]}"self.safe_click(AppiumBy.ID, element_id)def calculate(self, num1, operator, num2):"""执行计算"""logger.info(f"执行计算: {num1} {operator} {num2}")# 输入第一个数字self.input_number(num1)# 输入操作符self.input_operator(operator)# 输入第二个数字self.input_number(num2)# 点击等号self.safe_click(AppiumBy.ID, 'com.android.calculator2:id/eq')# 获取结果return self.get_result()def get_result(self):"""获取计算结果"""try:result_element = self.find_element_with_wait(AppiumBy.ID, 'com.android.calculator2:id/result')result = result_element.textlogger.info(f"计算结果: {result}")return resultexcept Exception as e:logger.error(f"获取结果失败: {str(e)}")return Nonedef clear_calculator(self):"""清除计算器"""self.safe_click(AppiumBy.ID, 'com.android.calculator2:id/clr')logger.info("计算器已清除")class TestCalculatorOperations(unittest.TestCase):"""计算器操作测试类"""@classmethoddef setUpClass(cls):"""测试类设置"""cls.calculator = CalculatorAutomation()success = cls.calculator.setup_driver()if not success:raise Exception("无法初始化Appium驱动")@classmethoddef tearDownClass(cls):"""测试类清理"""cls.calculator.teardown()def setUp(self):"""单个测试设置"""# 确保每次测试前计算器是清除状态self.calculator.clear_calculator()time.sleep(1)def test_addition(self):"""测试加法"""result = self.calculator.calculate(15, '+', 7)self.assertEqual(result, '22', "加法测试失败")def test_subtraction(self):"""测试减法"""result = self.calculator.calculate(20, '-', 8)self.assertEqual(result, '12', "减法测试失败")def test_multiplication(self):"""测试乘法"""result = self.calculator.calculate(6, '*', 9)self.assertEqual(result, '54', "乘法测试失败")def test_division(self):"""测试除法"""result = self.calculator.calculate(56, '/', 7)self.assertEqual(result, '8', "除法测试失败")def test_complex_operation(self):"""测试复杂运算"""# 15 + 27 - 8self.calculator.input_number(15)self.calculator.input_operator('+')self.calculator.input_number(27)self.calculator.input_operator('-')self.calculator.input_number(8)self.calculator.safe_click(AppiumBy.ID, 'com.android.calculator2:id/eq')result = self.calculator.get_result()self.assertEqual(result, '34', "复杂运算测试失败")def main():"""主函数"""logger.info("开始Appium手机操作演示")# 创建计算器自动化实例calculator = CalculatorAutomation()try:# 设置驱动if not calculator.setup_driver():logger.error("无法启动Appium驱动,程序退出")return# 执行一系列测试计算test_calculations = [(8, '+', 4),(15, '-', 6),(7, '*', 9),(81, '/', 9)]for num1, op, num2 in test_calculations:result = calculator.calculate(num1, op, num2)expected = str(eval(f"{num1}{op}{num2}"))if result == expected:logger.info(f"✓ {num1} {op} {num2} = {result} (正确)")else:logger.error(f"✗ {num1} {op} {num2} = {result} (期望: {expected})")# 截取屏幕截图calculator.take_screenshot("calculator_final_state.png")logger.info("Appium手机操作演示完成")except Exception as e:logger.error(f"程序执行过程中出现错误: {str(e)}")finally:# 确保资源被正确清理calculator.teardown()if __name__ == "__main__":# 可以直接运行演示main()# 或者运行单元测试# unittest.main(verbosity=2)
7. 常见问题与解决方案
7.1 连接问题
问题:无法连接到Appium服务器
解决方案:
- 确保Appium服务器正在运行:
appium - 检查端口是否被占用,默认端口是4723
- 验证URL格式:
http://localhost:4723
7.2 元素找不到问题
问题:脚本无法找到指定元素
解决方案:
- 增加等待时间,使用显式等待
- 使用不同的定位策略
- 检查应用是否已正确启动
- 使用Appium Desktop的Inspector工具验证元素定位
7.3 权限问题
问题:应用权限请求导致脚本中断
解决方案:
- 在Desired Capabilities中设置
autoGrantPermissions: true - 在脚本中添加权限处理逻辑
- 手动预先授予应用所需权限
7.4 性能问题
问题:脚本运行缓慢
解决方案:
- 减少不必要的等待时间
- 使用更高效的元素定位策略
- 避免频繁的页面源代码获取
- 考虑使用更快的测试设备或模拟器
8. 最佳实践
8.1 代码组织
- 使用Page Object模式将页面元素和操作封装成类
- 将配置信息与测试逻辑分离
- 使用配置文件或环境变量管理设备信息和应用信息
8.2 错误处理
- 实现完善的异常处理机制
- 添加重试机制处理偶发性失败
- 使用日志记录详细的操作信息
8.3 维护性
- 使用有意义的变量名和函数名
- 添加清晰的注释和文档
- 定期更新Appium和相关依赖
9. 总结
通过本文的介绍,我们学习了如何使用Python和Appium来操作手机应用。从环境搭建到基础操作,再到高级技巧,我们覆盖了使用Appium进行移动自动化的关键知识点。
Appium作为一个强大的跨平台移动自动化工具,结合Python的简洁语法,为我们提供了强大的手机操作能力。无论是进行自动化测试还是实现复杂的手机操作流程,Appium都是一个值得掌握的技能。
随着移动应用的不断发展,掌握移动自动化技术将会变得越来越重要。希望本文能够为你提供一个良好的起点,帮助你在移动自动化的道路上走得更远。
注意:在实际使用中,请根据你的具体设备和应用调整代码中的元素定位信息和配置参数。不同的设备和应用版本可能会有差异,需要灵活调整脚本。
