|
#!/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)
|