基于手机短信验证码的自动化测试
目录
- 课程目标
- 应用场景
- 短信验证码简介
- 短信验证码自动化测试方案
- 通过Appium获取短信内容(Python)
- 通过Appium获取短信内容(Java)
课程目标
-
掌握 自动化过程中短信验证码的获取方式
-
L1.用例录制与编写
- L2.高级定位与 PO 设计模式
- L3.webview 与微信小程序测试
- L4.Appium 源码分析与定制
- L5.分布式测试与多设备管理【当前阶段】
思考
自动化登录时短信验证码如何获取?

短信验证码简介
- 短信验证码是通过发送验证码到手机的一种有效的验证码系统。
- 可以比较准确和安全地保证系统的安全性,验证用户的正确性。
- 利用短信验证码来注册,大大降低了非法注册,烂注册的数据。
短信验证码自动化测试方案
- 研发给暗门或者万能验证码。
- 通过appium获取通知栏中短信内容(appium版本2.16.0以上)。
- 其他方案(较麻烦):
- 通过短信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>