自动化测试实战:Android端验证码自动获取与填充方案

自动化测试实战:Android端验证码自动获取与填充方案

解决正式环境测试账号受限的优雅方案

背景与痛点

在移动应用自动化测试过程中,我们经常会遇到一个棘手的问题:正式环境的接口由于安全考虑,不愿意向测试团队开放测试账号。这意味着我们需要使用真实账号进行测试,而其中最麻烦的环节就是验证码的获取与填写。

传统的解决方案要么依赖mock数据(在正式环境不可行),要么手动查看手机短信并输入验证码(效率低下且无法自动化)。针对这一痛点,我开发了一个基于ADB的Android短信验证码自动获取工具,完美解决了正式环境下的验证码自动化测试问题。

技术方案概述

本方案的核心思路是通过ADB命令访问Android系统的短信内容提供器(Content Provider),获取短信内容后通过正则表达式提取验证码,最后自动填充到应用输入框中。

主要技术组件:

  • ADB命令操作
  • Android Content Provider访问
  • 正则表达式匹配
  • 自动化测试集成

核心代码实现

短信工具类 (SmsUtils)

python 复制代码
import subprocess
import time
import re
from utils.logger_until import Logger

class SmsUtils:
    def __init__(self):
        self.logger = Logger(name=__name__).logger

    def get_sms_data_from_device(self):
        """从安卓设备读取所有短信"""
        command = "adb shell content query --uri content://sms/inbox --projection _id,address,date,body"
        result = subprocess.run(command, shell=True, capture_output=True, text=True, encoding="utf-8")
        
        if result.returncode != 0:
            raise Exception("设备获取短信失败,请检查ADB连接")
        
        # 解析短信数据
        parsed_sms = []
        for line in result.stdout.splitlines():
            sms_dict = {}
            for item in line.split(","):
                key_value = item.split("=")
                if len(key_value) == 2:
                    sms_dict[key_value[0].strip()] = key_value[1].strip()
            parsed_sms.append(sms_dict)
        
        return parsed_sms

    @staticmethod
    def filter_sms_by_keyword(sms_data, keyword):
        """根据关键字筛选短信"""
        return [sms for sms in sms_data if keyword in sms.get("body", "")]

    def get_latest_sms_by_timestamp(self, sms_data):
        """获取最新时间戳的短信"""
        sms_data_with_date = [sms for sms in sms_data if 'date' in sms]
        sorted_sms = sorted(sms_data_with_date, key=lambda x: int(x['date']), reverse=True)
        
        if not sorted_sms:
            return None
            
        # 验证短信时效性(10分钟内)
        latest_sms = sorted_sms[0]
        sms_timestamp = int(latest_sms['date'])
        current_timestamp = int(time.time() * 1000)
        time_diff = current_timestamp - sms_timestamp
        
        if time_diff <= 600000:  # 10分钟
            return latest_sms
        else:
            self.logger.error(f"验证码已过期,时间差:{time_diff//60000}分钟")
            return False

    def extract_verification_code(self, sms_body):
        """从短信内容提取验证码"""
        try:
            if isinstance(sms_body, dict):
                number_body = str(sms_body.get("body", ""))
                self.logger.info(f"原始短信内容: {number_body}")
                match = re.search(r"\d{6}", number_body)
                if match:
                    return match.group(0)
            return None
        except Exception as e:
            self.logger.error(f"验证码提取错误: {e}")
            raise

    @staticmethod
    def get_latest_verification_code(keyword="魔意科技"):
        """获取最新验证码(整合方法)"""
        sms = SmsUtils()
        sms_data = sms.get_sms_data_from_device()
        filtered_sms = sms.filter_sms_by_keyword(sms_data, keyword)
        latest_sms = sms.get_latest_sms_by_timestamp(filtered_sms)
        
        if latest_sms:
            return sms.extract_verification_code(latest_sms)
        else:
            return False

登录页面集成 (LoginPage)

python 复制代码
import time
from functools import wraps
from utils.sms_util import SmsUtils

# 重试装饰器
def retry(times=3, delay=2):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            last_exception = None
            for attempt in range(times):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    last_exception = e
                    time.sleep(delay)
                    print(f"第{attempt+1}次重试失败,剩余{times - attempt - 1}次重试")
            if last_exception:
                raise last_exception
        return wrapper
    return decorator

class LoginPage(BasePage):
    def __init__(self, device_manager):
        super().__init__(device_manager)
        self.logger = Logger(name=__name__).logger
    
    @retry(times=3, delay=15)
    def auto_fill_verification_code(self, keyword="魔意科技"):
        """自动填充验证码(带重试机制)"""
        verification_code = SmsUtils.get_latest_verification_code(keyword)
        if verification_code:
            self.logger.info(f"验证码提取成功: {verification_code}")
            self.input_text("phone_code_input", verification_code)
            self.logger.info(f"验证码已自动填充")
        else:
            self.logger.error("未找到有效验证码")
            raise ValueError("验证码提取失败")
    
    def _perform_login_flow(self, account: str, locator_prefix: str):
        """执行登录流程"""
        # 确保在正确的登录方式页面
        if not self.assert_element_visible(f"{locator_prefix}_input"):
            self.click_element(f"switch_to_{locator_prefix}")
        
        # 输入账号并获取验证码
        self.input_text(f"{locator_prefix}_input", account)
        self.click_element("phone_code_get_captcha")
        
        # 等待并自动填充验证码
        time.sleep(10)  # 等待短信发送
        self.auto_fill_verification_code()
        
        # 后续登录操作...

关键技术点解析

1. ADB访问短信内容提供器

通过adb shell content query命令直接访问Android系统的短信数据库,这是本方案的核心:

bash 复制代码
adb shell content query --uri content://sms/inbox --projection _id,address,date,body

2. 短信时效性验证

考虑到验证码的有效期,我们添加了时间戳检查逻辑,确保只使用10分钟内的最新验证码。

3. 重试机制

通过装饰器实现了优雅的重试机制,应对短信延迟到达的情况,提高测试稳定性。

4. 关键字过滤

支持通过关键字过滤短信,确保获取到的是目标应用发送的验证码,避免其他短信干扰。

应用效果与价值

  1. 完全自动化:解决了正式环境验证码测试的最后一公里问题
  2. 提高效率:无需人工干预查看和输入验证码
  3. 增强稳定性:重试机制和时效验证确保测试可靠性
  4. 易于集成:模块化设计,可以轻松集成到现有测试框架中

注意事项与优化方向

  1. 权限要求:需要授予应用读取短信的权限(Android 6.0+需要动态申请)
  2. 兼容性:不同Android版本短信数据库结构可能略有差异
  3. 安全考虑:正式环境中使用需注意隐私保护问题
  4. 扩展性:可考虑支持多种验证码格式(4位、8位等)

未来可考虑扩展为支持多种验证方式(邮箱验证码、语音验证码等)的统一验证码获取平台。

结语

这个Android验证码自动获取方案已经在我们项目中稳定运行,大大提升了正式环境自动化测试的效率和可靠性。希望这个方案对大家有所启发,也欢迎交流更好的实现思路。

相关推荐
霍格沃兹_测试6 小时前
从零开始学MCP(7) | 实战:用 MCP 构建论文分析智能体
测试
叫我阿柒啊13 小时前
Java全栈开发面试实战:从基础到复杂场景的深度解析
java·数据库·spring boot·面试·vue·测试·全栈开发
冲啊咸鱼1 天前
Python自动化测试框架系统实现
测试
Apifox1 天前
Apifox 8 月更新|新增测试用例、支持自定义请求示例代码、提升导入/导出 OpenAPI/Swagger 数据的兼容性
前端·后端·测试
三翼鸟数字化技术团队2 天前
AI应用--接口测试篇
测试
郝同学的测开笔记2 天前
打通回家之路:OpenVPN,你的企业网络万能钥匙(一)
运维·后端·测试
前端工作日常2 天前
我的 Weex 测试 入门之旅
前端·测试
前端工作日常2 天前
我的 端到端(E2E)测试 入门之旅
前端·测试
前端工作日常3 天前
我的 代码覆盖率 入门之旅
前端·测试