[adb] 读取屏幕内容与点击,用于测试
adb 可以实现点击屏幕与抓取屏幕的 xml 结构(包括元素坐标),因此可以利用这个特性写一些 APP 测试脚本
本程序实现了adb 命令的发送,xml 数据的解析,串口的发送。以达到预期效果
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <fcntl.h>
#include <termios.h>
int fd = 0;
void serial_send(uint8_t *data, int len) {
fd = open("/dev/cu.wchusbserial140", O_RDWR | O_NOCTTY);
if (fd < 0) {
printf("open serial port failed");
return;
}else{
printf("open serial port success");
}
struct termios tty;
tcgetattr(fd, &tty);
cfsetospeed(&tty, B115200);
cfsetispeed(&tty, B115200);
tty.c_cflag |= (CLOCAL | CREAD);
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8;
tty.c_cflag &= ~PARENB;
tty.c_cflag &= ~CSTOPB;
tcsetattr(fd, TCSANOW, &tty);
printf("set serial port success");
write(fd, data, len);
printf("send success\n");
close(fd);
}
typedef enum {
SUCCESS = 0,
FAIL = -1,
} status_t;
// 执行命令
int run_cmd(const char *cmd) {
return system(cmd);
}
// 解析 bounds
status_t parse_bounds(const char *bounds, int *x, int *y) {
int x1, y1, x2, y2;
if (sscanf(bounds, "[%d,%d][%d,%d]", &x1, &y1, &x2, &y2) == 4) {
*x = (x1 + x2) / 2;
*y = (y1 + y2) / 2;
return SUCCESS;
}
return FAIL;
}
// 判断是否匹配(支持“包含匹配”)
status_t is_match(xmlNode *cur, const char *txt) {
xmlChar *text = xmlGetProp(cur, (const xmlChar *) "text");
xmlChar *desc = xmlGetProp(cur, (const xmlChar *) "content-desc");
status_t ret = FAIL;
if ((text && strstr((char *) text, txt)) ||
(desc && strstr((char *) desc, txt))) {
ret = SUCCESS;
}
if (text) xmlFree(text);
if (desc) xmlFree(desc);
return ret;
}
// 解析坐标
status_t get_xy(xmlNode *cur, int *x, int *y) {
xmlChar *bounds = xmlGetProp(cur, (const xmlChar *) "bounds");
if (!bounds) return FAIL;
status_t ret = parse_bounds((char *) bounds, x, y);
xmlFree(bounds);
return ret;
}
// 递归查找(找到第一个就返回)
status_t find_txt(xmlNode *node, const char *txt, int *x, int *y) {
for (xmlNode *cur = node; cur; cur = cur->next) {
if (cur->type == XML_ELEMENT_NODE) {
if (is_match(cur, txt) == SUCCESS) {
if (get_xy(cur, x, y) == SUCCESS) {
return SUCCESS;
}
}
}
if (find_txt(cur->children, txt, x, y) == SUCCESS) {
return SUCCESS;
}
}
return FAIL;
}
// dump + pull
status_t dump_ui() {
if (run_cmd("adb shell uiautomator dump /sdcard/ui.xml") != 0) return FAIL;
if (run_cmd("adb pull /sdcard/ui.xml ./ui.xml") != 0) return FAIL;
return SUCCESS;
}
void tap(int x, int y) {
char cmd[128];
sprintf(cmd, "adb shell input tap %d %d", x, y);
run_cmd(cmd);
}
void tap_back() {
tap(102, 217);
}
typedef enum {
NULL_MENU = 0,
HOME,
DEVICE,
MY,
SETTING,
LANGUAGE,
} menu_t;
static menu_t current_menu = NULL_MENU;
uint8_t back = 0;
void business_task(xmlNode *root) {
int x = 0, y = 0;
switch (current_menu) {
case NULL_MENU:
if (back == 0) {
if (find_txt(root, "家庭", &x, &y) == SUCCESS) {
tap(x, y);
current_menu = HOME;
printf("tap home\n");
break;
}
}
break;
case HOME:
if (back == 0) {
if (find_txt(root, "设备", &x, &y) == SUCCESS) {
tap(x, y);
current_menu = DEVICE;
printf("tap device\n");
break;
}
} else {
back = 0;
}
break;
case DEVICE:
if (back == 0) {
if (find_txt(root, "我的", &x, &y) == SUCCESS) {
tap(x, y);
current_menu = MY;
printf("tap my\n");
break;
}
} else {
if (find_txt(root, "家庭", &x, &y) == SUCCESS) {
tap(x, y);
current_menu = HOME;
printf("tap home\n");
break;
}
}
break;
case MY:
if (back == 0) {
if (find_txt(root, "设置", &x, &y) == SUCCESS) {
tap(x, y);
current_menu = SETTING;
printf("tap setting\n");
break;
}
} else {
if (find_txt(root, "设备", &x, &y) == SUCCESS) {
tap(x, y);
current_menu = DEVICE;
printf("tap device\n");
break;
}
}
break;
case SETTING:
if (back == 0) {
if (find_txt(root, "语言", &x, &y) == SUCCESS) {
tap(x, y);
current_menu = LANGUAGE;
printf("tap language\n");
break;
}
} else {
tap_back();
current_menu = MY;
printf("tap back\n");
}
break;
case LANGUAGE:
if (back == 0) {
tap_back();
back = 1;
current_menu = SETTING;
printf("tap back\n");
}
break;
default:
break;
}
serial_send((uint8_t *) "lock", 4);
}
int main() {
while (1) {
// 1. 拉取当前界面的 XML 文件
if (dump_ui() == SUCCESS) {
printf("dump success %ld\n", time(NULL));
} else {
printf("dump failed %ld\n", time(NULL));
sleep(1);
continue;
}
// 2. 解析 XML 文件
xmlDoc *doc = xmlReadFile("ui.xml", NULL, 0);
if (doc == NULL) {
printf("xml read failed %ld\n", time(NULL));
sleep(1);
continue;
} else {
printf("xml read success %ld\n", time(NULL));
}
xmlNode *root = xmlDocGetRootElement(doc);
business_task(root);
xmlFreeDoc(doc);
}
}
windows读取串口
#include <windows.h>
#include <stdio.h>
int main() {
HANDLE hSerial;
// 打开串口
hSerial = CreateFile("COM1",
GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL);
if (hSerial == INVALID_HANDLE_VALUE) {
printf("无法打开串口\n");
return -1;
}
// 配置串口参数(必须)
DCB dcb = {0};
dcb.DCBlength = sizeof(dcb);
GetCommState(hSerial, &dcb);
dcb.BaudRate = CBR_115200;
dcb.ByteSize = 8;
dcb.StopBits = ONESTOPBIT;
dcb.Parity = NOPARITY;
SetCommState(hSerial, &dcb);
// 要发送的数据
unsigned char data[3] = {0xFF, 0xFF, 0xFF};
DWORD bytesWritten;
WriteFile(hSerial, data, 3, &bytesWritten, NULL);
printf("发送了 %ld 字节\n", bytesWritten);
CloseHandle(hSerial);
return 0;
}
License:
CC BY 4.0