目录
- 使用Python操作你的手机(Appium入门)
-
- [1. 引言](#1. 引言)
- [2. 环境准备](#2. 环境准备)
-
- [2.1 安装Node.js和NPM](#2.1 安装Node.js和NPM)
- [2.2 安装Appium](#2.2 安装Appium)
- [2.3 安装Appium Python客户端](#2.3 安装Appium Python客户端)
- [2.4 安装和配置Android SDK](#2.4 安装和配置Android SDK)
- [2.5 安装Java Development Kit (JDK)](#2.5 安装Java Development Kit (JDK))
- [3. Appium基础概念](#3. Appium基础概念)
-
- [3.1 Appium架构](#3.1 Appium架构)
- [3.2 Desired Capabilities](#3.2 Desired Capabilities)
- [3.3 元素定位策略](#3.3 元素定位策略)
- [4. 编写第一个Appium脚本](#4. 编写第一个Appium脚本)
-
- [4.1 导入必要的库](#4.1 导入必要的库)
- [4.2 配置Desired Capabilities](#4.2 配置Desired Capabilities)
- [4.3 实现计算器操作](#4.3 实现计算器操作)
- [4.4 完整的测试类](#4.4 完整的测试类)
- [5. 进阶操作](#5. 进阶操作)
-
- [5.1 处理不同的元素定位情况](#5.1 处理不同的元素定位情况)
- [5.2 手势操作](#5.2 手势操作)
- [5.3 处理弹窗和权限请求](#5.3 处理弹窗和权限请求)
- [5.4 等待策略](#5.4 等待策略)
- [6. 完整代码示例](#6. 完整代码示例)
- [7. 常见问题与解决方案](#7. 常见问题与解决方案)
-
- [7.1 连接问题](#7.1 连接问题)
- [7.2 元素找不到问题](#7.2 元素找不到问题)
- [7.3 权限问题](#7.3 权限问题)
- [7.4 性能问题](#7.4 性能问题)
- [8. 最佳实践](#8. 最佳实践)
-
- [8.1 代码组织](#8.1 代码组织)
- [8.2 错误处理](#8.2 错误处理)
- [8.3 维护性](#8.3 维护性)
- [9. 总结](#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官网下载并安装最新版本。
安装完成后,可以通过以下命令验证安装是否成功:
bash
node --version
npm --version
2.2 安装Appium
通过NPM全局安装Appium:
bash
npm install -g appium
安装完成后,可以通过以下命令启动Appium服务器:
bash
appium
2.3 安装Appium Python客户端
使用pip安装Appium的Python客户端库:
bash
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服务器
客户端脚本 Appium服务器 Android设备 iOS设备 UI Automator XCUITest
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 导入必要的库
python
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
python
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 实现计算器操作
python
def calculator_operations(driver):
"""执行计算器操作"""
try:
# 等待计算器加载完成
time.sleep(2)
# 定位数字按钮和操作符
# 注意:不同设备上的计算器应用可能具有不同的元素ID
# 这里使用的是Android原生计算器的元素ID
# 点击数字 7
btn_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()
# 点击数字 8
btn_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.text
print(f"计算结果: 7 + 8 = {calculated_result}")
# 验证结果是否正确
expected_result = '15'
if calculated_result == expected_result:
print("测试通过!计算结果正确。")
else:
print(f"测试失败!期望结果: {expected_result}, 实际结果: {calculated_result}")
return calculated_result
except Exception as e:
print(f"操作过程中出现错误: {str(e)}")
return None
4.4 完整的测试类
python
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
# 清除后显示区域应该为空或显示0
self.assertTrue(not current_display or current_display == '0',
"清除操作未正常工作")
except Exception as e:
self.fail(f"清除操作测试失败: {str(e)}")
5. 进阶操作
5.1 处理不同的元素定位情况
在实际应用中,我们可能会遇到各种复杂的定位情况。以下是一些常用的定位方法:
python
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支持多种手势操作,如滑动、长按、拖拽等:
python
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 / 2
start_y = screen_height * 0.8
end_x = screen_width / 2
end_y = screen_height * 0.2
driver.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 处理弹窗和权限请求
python
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} 按钮")
break
except Exception:
continue
except Exception as e:
print(f"处理弹窗时出现错误: {str(e)}")
5.4 等待策略
合理的等待策略对于自动化测试的稳定性至关重要:
python
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
def 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 False
custom_element = wait.until(custom_condition)
return {
'clickable': element,
'visible': visible_element,
'present': present_element,
'custom': custom_element
}
6. 完整代码示例
下面是一个完整的Appium脚本示例,展示了如何使用Python操作手机:
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_url
self.driver = None
self.wait = None
def 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 True
except Exception as e:
logger.error(f"设置Appium驱动失败: {str(e)}")
return False
def 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 True
except Exception as e:
logger.error(f"点击元素失败: {value}, 错误: {str(e)}")
return False
def 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 True
except Exception as e:
logger.error(f"截图失败: {str(e)}")
return False
def get_page_source(self):
"""获取页面源代码"""
try:
return self.driver.page_source
except Exception as e:
logger.error(f"获取页面源代码失败: {str(e)}")
return None
class 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.text
logger.info(f"计算结果: {result}")
return result
except Exception as e:
logger.error(f"获取结果失败: {str(e)}")
return None
def clear_calculator(self):
"""清除计算器"""
self.safe_click(AppiumBy.ID, 'com.android.calculator2:id/clr')
logger.info("计算器已清除")
class TestCalculatorOperations(unittest.TestCase):
"""计算器操作测试类"""
@classmethod
def setUpClass(cls):
"""测试类设置"""
cls.calculator = CalculatorAutomation()
success = cls.calculator.setup_driver()
if not success:
raise Exception("无法初始化Appium驱动")
@classmethod
def 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 - 8
self.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都是一个值得掌握的技能。
随着移动应用的不断发展,掌握移动自动化技术将会变得越来越重要。希望本文能够为你提供一个良好的起点,帮助你在移动自动化的道路上走得更远。
注意:在实际使用中,请根据你的具体设备和应用调整代码中的元素定位信息和配置参数。不同的设备和应用版本可能会有差异,需要灵活调整脚本。