Skip to content

基于手机短信验证码的自动化测试


目录

  • 课程目标
  • 应用场景
  • 短信验证码简介
  • 短信验证码自动化测试方案
  • 通过Appium获取短信内容(Python)
  • 通过Appium获取短信内容(Java)

课程目标

  1. 掌握 自动化过程中短信验证码的获取方式

  2. L1.用例录制与编写

  3. L2.高级定位与 PO 设计模式
  4. L3.webview 与微信小程序测试
  5. L4.Appium 源码分析与定制
  6. L5.分布式测试与多设备管理【当前阶段】

思考

自动化登录时短信验证码如何获取?


短信验证码简介

  • 短信验证码是通过发送验证码到手机的一种有效的验证码系统。
  • 可以比较准确和安全地保证系统的安全性,验证用户的正确性。
  • 利用短信验证码来注册,大大降低了非法注册,烂注册的数据。

短信验证码自动化测试方案

  1. 研发给暗门或者万能验证码。
  2. 通过appium获取通知栏中短信内容(appium版本2.16.0以上)。
  3. 其他方案(较麻烦):
    • 通过短信app获取短信内容。
    • 通过短信数据库SQLite获取短信内容,需要root权限和SQLite客户端。
    • 通过广播服务监听短信内容,需要开发app,注册监听器。
    • 通过service服务监听界面所有信息流,需要开发app,注册accessbilityservice。

登录企业微信代码(Python)

  • 如果没处理验证码,无法登录。
def test_login(self):
    # 勾选已同意
    self.driver.find_element(AppiumBy.XPATH, "//*[@text='已阅读并同意 软件许可及服务协议 和 隐私政策']/../android.widget.ImageView").click()
    self.driver.find_element(AppiumBy.XPATH, "//*[@text='已阅读并同意 软件许可及服务协议 和 隐私政策']/../android.widget.ImageView").click()
    # 点击手机号登录
    self.driver.find_element(AppiumBy.XPATH, "//*[@text='手机号登录']").click()
    # 输入手机号
    self.driver.find_element(AppiumBy.XPATH, "//*[@text='手机号']").send_keys("13926528314")
    # 点击下一步
    self.driver.find_element(AppiumBy.XPATH, "//*[@text='下一步']").click()
    # 点击下一步
    sleep(5)
    self.driver.find_element(AppiumBy.XPATH, "//*[@text='下一步']").click()
    # 验证是否登录成功
    text = self.driver.find_element(AppiumBy.XPATH, "//*[@text='选择工作身份']").get_attribute("text")
    assert "选择工作身份" == text

通过Appium获取短信内容(Python)

# SMScode.py
class SMScode:

    @classmethod
    def get_by_ADB(cls):
        cmd = 'adb shell am broadcast -a io.appium.settings.notifications'
        # 执行adb命令返回结果
        lines = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True).stdout.readlines()
        # 对短信内容进行切片,获取最后一个验证码
        code = str(lines[1], 'utf-8').split("技】")[1].split("(企")[0].strip()
        return code
# test_code.py
class TestSMSCode:
    def setup_class(self):
        self.desire_cap = {
            "platformName": "Android",
            "appium:deviceName": "31030c8d",
            "appium:automationName": "Appium",
            "appium:appPackage": "com.tencent.wework",
            "appium:appActivity": ".launch.LaunchSplashActivity",
            "noReset": "true",
        }
        self.driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", self.desire_cap)
        self.driver.implicitly_wait(10)

    def teardown_class(self):
        self.driver.quit()

    def test_login_by_ADB(self):
        # 勾选已同意
        self.driver.find_element(AppiumBy.XPATH, "//*[@text='已阅读并同意 软件许可及服务协议 和 隐私政策']/../android.widget.ImageView").click()
        self.driver.find_element(AppiumBy.XPATH, "//*[@text='已阅读并同意 软件许可及服务协议 和 隐私政策']/../android.widget.ImageView").click()
        # 点击手机号登录
        self.driver.find_element(AppiumBy.XPATH, "//*[@text='手机号登录']").click()
        # 输入手机号
        self.driver.find_element(AppiumBy.XPATH, "//*[@text='手机号']").send_keys("13926528314")
        # 点击下一步
        self.driver.find_element(AppiumBy.XPATH, "//*[@text='下一步']").click()
        sleep(10)
        # 获取验证码
        code = SMScode.get_by_ADB()
        # 上划关闭通通知栏
        size = self.driver.get_window_size()
        width = size.get("width")
        height = size.get("height")
        self.driver.swipe((width / 2), int((height * 0.3)), (width / 2), (height * 0.1), 2000)
        sleep(5)
        # 输入验证码
        self.driver.find_element(AppiumBy.XPATH, "//*[@text='验证码']").send_keys(code)
        # 点击下一步
        sleep(5)
        self.driver.find_element(AppiumBy.XPATH, "//*[@text='下一步']").click()
        # 验证是否登录成功
        text = self.driver.find_element(AppiumBy.XPATH, "//*[@text='选择工作身份']").get_attribute("text")
        assert "选择工作身份" == text

登录企业微信代码(Java)

  • 如果没处理验证码,无法登录。
// LoginByCodeTest.java
class LoginByCodeTest {

    public static AndroidDriver driver;

    @BeforeAll
    public static void beforeAll() throws MalformedURLException {
        DesiredCapabilities desiredCapabilities = new DesiredCapabilities();
        // 平台名称 安卓系统就是Android 苹果手机就是iOS
        desiredCapabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android");
        // 启动的app的包名
        desiredCapabilities.setCapability(AndroidMobileCapabilityType.APP_PACKAGE, "com.tencent.wework");
        // 启动的app的页面
        desiredCapabilities.setCapability(AndroidMobileCapabilityType.APP_ACTIVITY, ".launch.LaunchSplashActivity");
        // 设备名称
        desiredCapabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "31030c8d");
        // 设备的UDID;adb devices -l 获取,多设备的时候要指定,若不指定默认选择列表的第一个设备
        desiredCapabilities.setCapability(MobileCapabilityType.UDID, "31030c8d");
        // app不重置
        desiredCapabilities.setCapability(MobileCapabilityType.NO_RESET, true);
        //在假设客户端退出并结束会话之前,Appium 将等待来自客户端的新命令多长时间(以秒为单位) http请求等待响应最长5分钟
        desiredCapabilities.setCapability(MobileCapabilityType.NEW_COMMAND_TIMEOUT, 300000);
        // 默认权限通过
        desiredCapabilities.setCapability(SupportsAutoGrantPermissionsOption.AUTO_GRANT_PERMISSIONS_OPTION, true);
        // 实例化driver
        driver = new AndroidDriver(new URL("http://127.0.0.1:4723/wd/hub"), desiredCapabilities);
        // 隐式等待
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
    }

    @AfterAll
    public static void afterAll() {
        driver.quit();
    }

    @Test
    public void loginByADB() throws InterruptedException, IOException {
        // 勾选已同意
        driver.findElement(AppiumBy.id("com.tencent.wework:id/mm")).click();
        driver.findElement(AppiumBy.id("com.tencent.wework:id/mm")).click();
        // 点击手机号登录
        driver.findElement(AppiumBy.xpath("//*[@text='手机号登录']")).click();
        // 输入手机号
        driver.findElement(AppiumBy.xpath("//*[@text='手机号']")).sendKeys("13926528314");
        // 点击下一步
        driver.findElement(AppiumBy.xpath("//*[@text='下一步']")).click();
        sleep(10000);
        // 点击下一步
        sleep(5000);
        driver.findElement(AppiumBy.xpath("//*[@text='下一步']")).click();
        // 验证是否登录成功
        String text = driver.findElement(AppiumBy.xpath("//*[@text='选择工作身份']")).getText().toString();
        assertEquals("选择工作身份", text);
    }
}

通过Appium获取短信内容(Java)

// SMSCode.java
public class SMSCode {

    public static String getByADB() throws IOException {
        // 使用Runtime.getRuntime().exec执行adb命令
        Process process = null;
        process = Runtime.getRuntime().exec("adb shell am broadcast -a io.appium.settings.notifications");

        // 使用getInputStream获取输出流
        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8));
        // while循环打印每一行
        String line = null;
        String code = "";
        while (true) {
            line = reader.readLine();
            // 无内容时跳出循环
            if (line == null) break;
            System.out.println(line);
            if (line.contains("技】")) {
                code = line.split("技】")[1].split("(企")[0].strip();
                System.out.println(code);
            }
        }
        return code;
    }
}
// LoginByCodeTest.java
class LoginByCodeTest {

    public static AndroidDriver driver;

    @BeforeAll
    public static void beforeAll() throws MalformedURLException {
        DesiredCapabilities desiredCapabilities = new DesiredCapabilities();
        // 平台名称 安卓系统就是Android 苹果手机就是iOS
        desiredCapabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android");
        // 启动的app的包名
        desiredCapabilities.setCapability(AndroidMobileCapabilityType.APP_PACKAGE, "com.tencent.wework");
        // 启动的app的页面
        desiredCapabilities.setCapability(AndroidMobileCapabilityType.APP_ACTIVITY, ".launch.LaunchSplashActivity");
        // 设备名称
        desiredCapabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "31030c8d");
        // 设备的UDID;adb devices -l 获取,多设备的时候要指定,若不指定默认选择列表的第一个设备
        desiredCapabilities.setCapability(MobileCapabilityType.UDID, "31030c8d");
        // app不重置
        desiredCapabilities.setCapability(MobileCapabilityType.NO_RESET, true);
        //在假设客户端退出并结束会话之前,Appium 将等待来自客户端的新命令多长时间(以秒为单位) http请求等待响应最长5分钟
        desiredCapabilities.setCapability(MobileCapabilityType.NEW_COMMAND_TIMEOUT, 300000);
        // 默认权限通过
        desiredCapabilities.setCapability(SupportsAutoGrantPermissionsOption.AUTO_GRANT_PERMISSIONS_OPTION, true);
        // 实例化driver
        driver = new AndroidDriver(new URL("http://127.0.0.1:4723/wd/hub"), desiredCapabilities);
        // 隐式等待
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
    }

    @AfterAll
    public static void afterAll() {
        driver.quit();
    }

    @Test
    public void loginByADB() throws InterruptedException, IOException {
        // 勾选已同意
        driver.findElement(AppiumBy.id("com.tencent.wework:id/mm")).click();
        driver.findElement(AppiumBy.id("com.tencent.wework:id/mm")).click();
        // 点击手机号登录
        driver.findElement(AppiumBy.xpath("//*[@text='手机号登录']")).click();
        // 输入手机号
        driver.findElement(AppiumBy.xpath("//*[@text='手机号']")).sendKeys("13926528314");
        // 点击下一步
        driver.findElement(AppiumBy.xpath("//*[@text='下一步']")).click();
        sleep(20000);
        // 获取验证码
        String code = SMSCode.getByADB();
        // 上划关闭通通知栏
        Dimension dimension = driver.manage().window().getSize();
        Point start = new Point((int)(dimension.width*0.5), (int)(dimension.height*0.3));
        Point end = new Point((int)(dimension.width*0.5), (int)(dimension.height*0.1));
        W3cActions.doSwipe(driver, start, end, 100);
        sleep(5000);
        // 输入验证码
        driver.findElement(AppiumBy.xpath("//*[@text='验证码']")).sendKeys(code);
        // 点击下一步
        sleep(5000);
        driver.findElement(AppiumBy.xpath("//*[@text='下一步']")).click();
        // 验证是否登录成功
        String text = driver.findElement(AppiumBy.xpath("//*[@text='选择工作身份']")).getText().toString();
        assertEquals("选择工作身份", text);
    }
}

总结


源码地址


附录:完整依赖配置(Python)

Appium-Python-Client==2.9.0
async-generator==1.10
attrs==23.1.0
certifi==2022.12.7
cffi==1.15.1
exceptiongroup==1.1.1
h11==0.14.0
idna==3.4
outcome==1.2.0
pycparser==2.21
PySocks==1.7.1
selenium==4.9.0
sniffio==1.3.0
sortedcontainers==2.4.0
trio==0.22.0
trio-websocket==0.10.2
urllib3==1.26.15
wsproto==1.2.0

附录:完整依赖配置(Java)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>javaProject</artifactId>
        <groupId>com.ceshiren</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>SMSCode</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <repositories>
        <repository>
            <id>jitpack.io</id>
            <url>https://jitpack.io</url>
        </repository>
    </repositories>

    <dependencies>
        <!--junit5-->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <!--对应添加的依赖的作用范围-->
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-suite</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
            <scope>test</scope>
        </dependency>

        <!--hamcrest断言-->
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest</artifactId>
            <version>2.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
            <version>2.2</version>
            <scope>test</scope>
        </dependency>

        <!-- appium -->
        <dependency>
            <groupId>io.appium</groupId>
            <artifactId>java-client</artifactId>
            <version>8.1.1</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

</project>