Python + Appium 实现 APP 自动化教程

一、环境搭建

1.1 安装必要软件

# 1. 安装 Python(建议 3.8+)
# 下载地址:https://www.python.org/downloads/

# 2. 安装 Appium
# 方法一:通过 npm 安装
npm install -g appium

# 方法二:安装 Appium Desktop
# 下载地址:https://github.com/appium/appium-desktop/releases

# 3. 安装 Appium Python 客户端
pip install Appium-Python-Client
pip install selenium

1.2 安装移动端开发工具

  • Android:安装 Android Studio 和 SDK

  • iOS:安装 Xcode(仅 macOS)

1.3 配置环境变量

# Android 环境变量示例(Windows)
ANDROID_HOME = C:\Users\用户名\AppData\Local\Android\Sdk
# 添加 Path
%ANDROID_HOME%\tools
%ANDROID_HOME%\platform-tools

二、基础示例

2.1 第一个自动化脚本

from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
import time

# 配置 Desired Capabilities
desired_caps = {
    'platformName': 'Android',  # 平台
    'platformVersion': '11.0',  # 系统版本
    'deviceName': 'Android Emulator',  # 设备名
    'appPackage': 'com.android.calculator2',  # 应用包名
    'appActivity': 'com.android.calculator2.Calculator',  # 启动 Activity
    'automationName': 'UiAutomator2',
    'noReset': True  # 不重置应用状态
}

# 连接 Appium 服务器
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)

try:
    # 点击数字 5
    driver.find_element(AppiumBy.ID, 'com.android.calculator2:id/digit_5').click()
    
    # 点击加号
    driver.find_element(AppiumBy.ID, 'com.android.calculator2:id/op_add').click()
    
    # 点击数字 3
    driver.find_element(AppiumBy.ID, 'com.android.calculator2:id/digit_3').click()
    
    # 点击等号
    driver.find_element(AppiumBy.ID, 'com.android.calculator2:id/eq').click()
    
    # 获取结果
    result = driver.find_element(AppiumBy.ID, 'com.android.calculator2:id/result').text
    print(f'计算结果: {result}')
    
    time.sleep(2)
    
finally:
    driver.quit()

三、核心概念

3.1 Desired Capabilities 配置

# 常用配置项
caps = {
    # 基础配置
    'platformName': 'Android',  # iOS
    'platformVersion': '11.0',
    'deviceName': '设备名称',
    
    # Android 专用
    'appPackage': 'com.example.app',
    'appActivity': '.MainActivity',
    'automationName': 'UiAutomator2',
    'udid': '设备序列号',
    
    # iOS 专用
    'bundleId': 'com.example.app',
    'automationName': 'XCUITest',
    'xcodeOrgId': '团队ID',
    'xcodeSigningId': 'iPhone Developer',
    
    # 通用配置
    'noReset': True,  # 不重置应用
    'fullReset': False,  # 完全重置
    'unicodeKeyboard': True,  # 支持 Unicode
    'resetKeyboard': True,  # 重置键盘
    'newCommandTimeout': 60,  # 命令超时时间
}

3.2 元素定位方法

# 8 种定位方式
from appium.webdriver.common.appiumby import AppiumBy

# 1. ID 定位
element = driver.find_element(AppiumBy.ID, '元素id')

# 2. 可访问性 ID(content-desc)
element = driver.find_element(AppiumBy.ACCESSIBILITY_ID, '描述')

# 3. Class Name
element = driver.find_element(AppiumBy.CLASS_NAME, '类名')

# 4. XPath
element = driver.find_element(AppiumBy.XPATH, '//元素[@属性="值"]')

# 5. Android UIAutomator
element = driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, 
    'new UiSelector().text("文本")')

# 6. iOS 谓词字符串
element = driver.find_element(AppiumBy.IOS_PREDICATE, 
    'label == "按钮"')

# 7. iOS Class Chain
element = driver.find_element(AppiumBy.IOS_CLASS_CHAIN, 
    '**/XCUIElementTypeButton[`label == "按钮"`]')

# 8. 文本定位
element = driver.find_element(AppiumBy.XPATH, '//*[@text="文本内容"]')

四、常用操作

4.1 基本操作

from appium.webdriver.common.touch_action import TouchAction

# 点击操作
element.click()
driver.tap([(x, y)])  # 坐标点击

# 输入文本
element.send_keys('文本内容')
element.clear()  # 清空

# 获取元素属性
text = element.text
location = element.location
size = element.size
is_displayed = element.is_displayed()
is_enabled = element.is_enabled()

# 滑动操作
driver.swipe(start_x, start_y, end_x, end_y, duration=800)

# 复杂手势
action = TouchAction(driver)
action.tap(element).perform()  # 点击
action.long_press(element, duration=2000).release().perform()  # 长按
action.press(x=100, y=100).move_to(x=200, y=200).release().perform()  # 拖拽

4.2 等待机制

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# 显式等待
wait = WebDriverWait(driver, 10)
element = wait.until(EC.presence_of_element_located((AppiumBy.ID, '元素id')))

# 隐式等待
driver.implicitly_wait(10)  # 全局等待

# 常用条件
EC.visibility_of_element_located(locator)  # 元素可见
EC.element_to_be_clickable(locator)  # 元素可点击
EC.text_to_be_present_in_element(locator, '文本')  # 包含文本

五、高级功能

5.1 页面滑动

def swipe_up(driver, duration=1000):
    """向上滑动"""
    size = driver.get_window_size()
    start_x = size['width'] * 0.5
    start_y = size['height'] * 0.8
    end_y = size['height'] * 0.2
    driver.swipe(start_x, start_y, start_x, end_y, duration)

def swipe_down(driver, duration=1000):
    """向下滑动"""
    size = driver.get_window_size()
    start_x = size['width'] * 0.5
    start_y = size['height'] * 0.2
    end_y = size['height'] * 0.8
    driver.swipe(start_x, start_y, start_x, end_y, duration)

# 使用示例
swipe_up(driver)  # 上滑
swipe_down(driver)  # 下滑

5.2 获取页面源码

# 获取当前页面 XML
page_source = driver.page_source

# 保存到本地
with open('page.xml', 'w', encoding='utf-8') as f:
    f.write(page_source)

5.3 截图

# 截图
driver.save_screenshot('screenshot.png')

# 元素截图
element.screenshot('element.png')

六、实用工具类

6.1 基础框架

import yaml
import logging
from datetime import datetime
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class AppAutomation:
    def __init__(self, caps=None, config_file='config.yaml'):
        """初始化驱动"""
        self.load_config(config_file)
        if caps:
            self.caps.update(caps)
        
        self.driver = webdriver.Remote(
            f"http://{self.config['appium_server']['host']}:"
            f"{self.config['appium_server']['port']}/wd/hub",
            self.caps
        )
        self.wait = WebDriverWait(self.driver, 10)
        
    def load_config(self, config_file):
        """加载配置文件"""
        with open(config_file, 'r', encoding='utf-8') as f:
            self.config = yaml.safe_load(f)
        self.caps = self.config.get('desired_capabilities', {})
    
    def find_element(self, by, value, timeout=10):
        """查找元素(带等待)"""
        return WebDriverWait(self.driver, timeout).until(
            EC.presence_of_element_located((by, value))
        )
    
    def find_and_click(self, by, value, timeout=10):
        """查找并点击元素"""
        element = self.find_element(by, value, timeout)
        element.click()
        return element
    
    def find_and_input(self, by, value, text, timeout=10):
        """查找并输入文本"""
        element = self.find_element(by, value, timeout)
        element.clear()
        element.send_keys(text)
        return element
    
    def swipe_to_find(self, by, value, max_swipes=5):
        """滑动查找元素"""
        for _ in range(max_swipes):
            try:
                return self.driver.find_element(by, value)
            except:
                self.swipe_up()
        raise Exception(f"未找到元素: {value}")
    
    def take_screenshot(self, name=None):
        """截图"""
        if not name:
            name = datetime.now().strftime('%Y%m%d_%H%M%S')
        filename = f"screenshots/{name}.png"
        self.driver.save_screenshot(filename)
        logging.info(f"截图已保存: {filename}")
        return filename
    
    def quit(self):
        """退出"""
        if self.driver:
            self.driver.quit()

6.2 配置文件示例 (config.yaml)

appium_server:
  host: 127.0.0.1
  port: 4723

desired_capabilities:
  platformName: Android
  platformVersion: "11.0"
  deviceName: Android Emulator
  automationName: UiAutomator2
  noReset: true
  newCommandTimeout: 60

apps:
  calculator:
    appPackage: com.android.calculator2
    appActivity: com.android.calculator2.Calculator
  settings:
    appPackage: com.android.settings
    appActivity: .Settings

七、测试框架集成

7.1 使用 Pytest

# test_calculator.py
import pytest
from app_automation import AppAutomation

class TestCalculator:
    @pytest.fixture(scope='class')
    def app(self):
        """初始化应用"""
        app = AppAutomation()
        yield app
        app.quit()
    
    def test_addition(self, app):
        """测试加法"""
        # 执行加法
        app.find_and_click(AppiumBy.ID, 'digit_5')
        app.find_and_click(AppiumBy.ID, 'op_add')
        app.find_and_click(AppiumBy.ID, 'digit_3')
        app.find_and_click(AppiumBy.ID, 'eq')
        
        # 验证结果
        result = app.find_element(AppiumBy.ID, 'result').text
        assert result == '8'
    
    def test_clear(self, app):
        """测试清除功能"""
        app.find_and_click(AppiumBy.ID, 'digit_9')
        app.find_and_click(AppiumBy.ID, 'clr')
        
        result = app.find_element(AppiumBy.ID, 'result').text
        assert result == '0'

# 运行测试
# pytest test_calculator.py -v

7.2 生成测试报告

# 安装 pytest-html
pip install pytest-html

# 运行测试并生成报告
pytest test_calculator.py --html=report.html --self-contained-html

八、实战案例

8.1 微信发消息自动化

class WeChatAutomation(AppAutomation):
    def __init__(self):
        caps = {
            'appPackage': 'com.tencent.mm',
            'appActivity': '.ui.LauncherUI',
            'noReset': True
        }
        super().__init__(caps)
    
    def send_message(self, contact_name, message):
        """发送微信消息"""
        # 搜索联系人
        self.find_and_click(AppiumBy.ID, '搜索按钮ID')
        self.find_and_input(AppiumBy.ID, '搜索框ID', contact_name)
        
        # 进入聊天
        self.find_and_click(AppiumBy.XPATH, f'//*[@text="{contact_name}"]')
        
        # 输入消息
        input_box = self.find_element(AppiumBy.ID, '输入框ID')
        input_box.send_keys(message)
        
        # 发送
        self.find_and_click(AppiumBy.ID, '发送按钮ID')
        
        # 验证发送成功
        last_msg = self.find_element(
            AppiumBy.XPATH, '(//消息元素)[last()]'
        ).text
        assert message in last_msg

九、调试技巧

9.1 使用 UI Automator Viewer

# Android SDK 工具
# 位置: {ANDROID_HOME}/tools/bin/uiautomatorviewer

9.2 使用 Appium Inspector

  1. 启动 Appium Server

  2. 打开 Appium Inspector

  3. 配置 Desired Capabilities

  4. 连接设备/模拟器

  5. 实时查看和操作元素

9.3 日志记录

import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('appium.log'),
        logging.StreamHandler()
    ]
)

十、常见问题

10.1 连接问题

# 检查 Appium 服务
# 终端执行:
appium --address 127.0.0.1 --port 4723

# 检查设备连接
adb devices

10.2 元素定位失败

  • 使用 driver.page_source查看当前页面结构

  • 增加等待时间

  • 使用相对定位代替绝对定位

  • 尝试不同的定位方式

10.3 性能优化

# 1. 减少不必要的截图
# 2. 使用合适的等待策略
# 3. 复用 driver 会话
# 4. 批量执行操作

学习资源

  1. 官方文档

  2. 实用工具

    • Appium Desktop:图形化工具

    • ADB:Android 调试桥

    • Charles/Fiddler:网络抓包

  3. 社区支持

    • GitHub Issues

    • Stack Overflow

    • 官方讨论区

这个教程涵盖了 Python + Appium 进行 APP 自动化的主要方面。建议从基础示例开始,逐步尝试更复杂的功能。实践中遇到问题,多查阅官方文档和社区讨论。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐