Appium 自动测试,读书打卡
此博客链接:https://www.cnblogs.com/ping2yingshi/p/16169748.html 。
1.需求分析
1.1需求
本研究目的是实现在手机上自动完成读书打卡功能。
1.2分析
需求可以拆分成两个部分。第一部分,需要先实现自动化,本人采用Appium 实现自动化。第二部分,对需求进步一细化,首先在手机上找到读书软件,找到需要阅读的书籍。其次,每隔固定时间滑动屏幕模拟人在阅读书籍的动作,设置读书总时长,能够结束阅读。最后,找到最后阅读书籍的当前页数和书籍总页数并读出这两个数据。
2.准备工作
2.1说明
本项目采用Appium 实现自动化,客户端采用夜神模拟器实现。
2.2软件安装
2.2.1 JDK
JDK的下载和安装可以参考本人另一篇博客 安装配置jdk:https://www.cnblogs.com/ping2yingshi/p/14930839.html。
2.2.2 Android SDK
Android SDK 下载链接:https://www.androiddevtools.cn/。
1.配置环境变量参考:https://www.csdn.net/tags/MtjaggzsNTc4MjktYmxvZwO0O0OO0O0O.html。
2.在安装Android SDK 时,可能缺少platform-tools和build-tools,需要在 SDK Manager中再次安装。
2.2.3夜神模拟器
夜神模拟器下载链接:https://www.yeshen.com/。
提示,Android SDK 的adb版本可能和夜神模拟器的adb版本不一致,需要把Android SDK 的adb版本可复制到夜神模拟器的bin下,替换原来夜神模拟器的adb版本。参考链接:https://www.cnblogs.com/ping2yingshi/p/16155697.html(4.1.2.1部分)。
2.2.4 Appium 自动测试
1.关于Appium 自动测试说明,可以参考Appium 官方链接:https://appium.io/docs/en/about-appium/intro/。
2.Appium 下载链接:https://github.com/appium/appium-desktop/releases/download/v1.22.3/Appium.Server.GUI-windows-1.22.3.exe。
3.安装后双击出现以下界面,说明安装成功。
4.点击“Edit Configurations”进行配置信息。如果Android SDK和JDK的环境都配置成功,那么这里会自动显示。
2.2.5 Maven
1.Maven下载链接:https://maven.apache.org/download.cgi 。
2.安装和配置环境可参考Maven教程:https://www.runoob.com/maven/maven-setup.html。
3.测试Maven环境是否配置成功,输入mvn -version,显示版本信息,表示配置成功。
3.技术原型
1.appium启动App
2.appium点击屏幕
3.appium滑动屏幕
4.识别固定位置上的字符串
4.实验
4.1Appium启动模拟器中的设置
4.1.1实验目的
了解Appium是如何和安卓模拟器连接的。
4.1.2实验准备
1.打开Appium。
2.打开夜神模拟器。
3.进入模拟器中的设置,找到安卓版本号位置,多次点击。进入开发者模式。
4.1.3实验内容
4.1.3.1创建Maven项目
1.点击file下创建新的Maven项目。
2.选择创建一个简单的工程。
3.填写一些信息,点击完成按钮。
4.找到新创建的工程,打开pom.xml文件,添加4.1.3.2中的两个依赖。
4.1.3.2添加依赖
4.1.3.2.1添加Java-client依赖
Java-client下载连接:https://mvnrepository.com/ 。
1.打开Maven Repository,搜素Appium。找到第一个Java Client。
2.点击Java Client,复制依赖。
4.1.3.2.2添加slf4j-nop依赖
在pom.xml中添加以下依赖。
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-nop</artifactId> <version>1.7.2</version> <type>jar</type> </dependency>
添加所有依赖后的结果如下。
4.1.3.3 Appium自动打开模拟器中的settings程序
1.在eclipse中新建一个class,复制以下代码。
DesiredCapabilities capabilities = new DesiredCapabilities(); // 模拟器类型 // capabilities.setCapability("deviceName", "Android Emulator"); capabilities.setCapability("deviceName", "device"); // 自动化测试引擎 capabilities.setCapability("automationName", "Appium"); // 手机操作系统Android capabilities.setCapability("platformName", "Android"); // 手机操作系统版本号 capabilities.setCapability("platformVersion", "7.1.2"); // app包名 capabilities.setCapability("appPackage", "com.android.settings"); // app中启动的 Activity名称 capabilities.setCapability("appActivity", ".Settings"); AndroidDriver driver = new AndroidDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); // wait(500); driver.quit();
2.找到eclipse中Windown下的preferences,第一步在Maven下找到Installations并点击,第二步点击Add,第三步选择Maven的安装路径。
3.运行程序。注意查看夜神模拟器。
4.1.4实验结果
当运行完java程序后,夜神模拟器中的设置被自动打开,效果如下。
4.2Appium在模拟器中点击
4.2.1实验目的
使Appium能在模拟器中点击。
4.2.2准备工程
下载Appium Inspector。下载链接:https://github.com/appium/appium-inspector/releases 。
把Remote Path设置称为/wd/hub,platformName填写成Android,如下图所示。
4.2.3实验内容
1.通过Appium Inspector获取设置中WLAN选项的坐标位置(350,850)。
2.在程序中添加如下代码。执行程序。
Thread.sleep(1000); TouchAction touchAction = new TouchAction(driver); touchAction.tap(PointOption.point(350, 850)).release().perform();//点击
Thread.sleep(2000);
4.2.4实验结果
Appium打开了设置,并点击了设置中的WLAN。
4.3 Appium在模拟器中滑动
4.3.1实验目的
实现Appium滑动屏幕。
4.3.2实验内容
1.使用TouchAction的方法实现滑动屏幕效果。(提示新版本的java-client已经取消swipe的方法)
2.复制以下代码。(t提示注意移动的坐标,这里我一开始写的是左右移动,看模拟器也没有反应,找了好长时间才发现问题)
DesiredCapabilities capabilities = new DesiredCapabilities(); // 模拟器类型 // capabilities.setCapability("deviceName", "Android Emulator"); capabilities.setCapability("deviceName", "device"); // 自动化测试引擎 capabilities.setCapability("automationName", "Appium"); // 手机操作系统Android capabilities.setCapability("platformName", "Android"); // 手机操作系统版本号 capabilities.setCapability("platformVersion", "7.1.2"); // app包名 capabilities.setCapability("appPackage", "com.android.settings"); // app中启动的 Activity名称 capabilities.setCapability("appActivity", ".Settings"); AndroidDriver driver = new AndroidDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); Thread.sleep(1000); TouchAction touchAction = new TouchAction(driver); int width = driver.manage().window().getSize().width;// 获取当前屏幕的宽度 int height = driver.manage().window().getSize().height;// 获取当前屏幕的高 Thread.sleep(1000); // new一个TouchAction对象,调用其按压press()方法,输入坐标点,moveTo移动到下一个坐标点,之后调用release()和perform()方法执行 PointOption startOption = PointOption.point(width / 2, height * 4 / 5); PointOption endOption = PointOption.point(width / 2, height / 5); WaitOptions waitOption = WaitOptions.waitOptions(Duration.ofNanos((long) (1.5 * 1000)));// 设置动作持续时间:按压一秒 touchAction.press(startOption).waitAction(waitOption).moveTo(endOption).release().perform();// 按压一秒——移动——松开释放,向下滑动
touchAction.press(endOption).waitAction(waitOption).moveTo(startOption).release().perform();//向上滑动
Thread.sleep(1000);
4.3.3实验结果
Appium打开了设置,并在设置中滑动屏幕。
参考链接:https://www.cnblogs.com/kaola8023/archive/2018/02/27/8478018.html 。
4.4 Appium在模拟器中识别固定元素
4.4.1实验目的
实现Appium识别出模拟器中所需要的数据。
4.4.2实验构思
本项目需要识别的是当前读书的页数已经书的总页数,而这在读书软件中的位置是固定的,需要先找到这个固定位置,然后在识别固定位置上的数据。
4.4.3实验内容
4.4.3.1 Appium定位元素
4.4.3.1.1实验目的
实现Appium识别固定元素的位置。
4.4.3.1.2实验内容
1.复制以下代码。
DesiredCapabilities capabilities = new DesiredCapabilities(); // 模拟器类型 // capabilities.setCapability("deviceName", "Android Emulator"); capabilities.setCapability("deviceName", "device"); // 自动化测试引擎 capabilities.setCapability("automationName", "Appium"); // 手机操作系统Android capabilities.setCapability("platformName", "Android"); // 手机操作系统版本号 capabilities.setCapability("platformVersion", "7.1.2"); // app包名 capabilities.setCapability("appPackage", "com.android.settings"); // app中启动的 Activity名称 capabilities.setCapability("appActivity", ".Settings"); capabilities.setCapability("automationName", "uiautomator2"); AndroidDriver driver = new AndroidDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); Thread.sleep(1000); TouchAction touchAction = new TouchAction(driver); int width = driver.manage().window().getSize().width;// 获取当前屏幕的宽度 int height = driver.manage().window().getSize().height;// 获取当前屏幕的高 Thread.sleep(1000); String id = "com.android.settings:id/dashboard_tile"; driver.findElementById(id).click();//通过ID定位元素并点击
2.效果如下图所示。
在以上过程中,遇到了java-client版本中没有findElementById()问题,然后降低版本,但是又出现了driver报错,最终又降低了Appium版本才解决问题。过程参见博客:https://www.cnblogs.com/ping2yingshi/p/16155697.html(4.4部分)。
本项目的java-client是7.3.0,Appium是1.15.1。
4.4.3.1.3实验结果
实现Appium识别固定元素的位置。
4.4.3.2 Appium识别元素内容
4.4.3.2.1实验目的
实现Appium识别元素的内容。
4.4.3.2.2实验内容
1.使用以下命令查看微信读书的包名和活动名字。
adb shell dumpsys window windows | findstr mFocusedApp
2.使用getText()方法获取元素的内容。复制以下代码。
String book_id = "com.tencent.weread:id/st"; AndroidElement bookText = (AndroidElement) driver.findElementById(book_id); driver.manage().timeouts().implicitlyWait(300, TimeUnit.SECONDS); System.out.println("登录名字" + bookText.getText());
3.运行程序,获取到微信的登录名字。
在以上过程中,遇到了模拟器中ID不唯一问题,最后使用了微信读书做这部分的实验。过程参见博客:https://www.cnblogs.com/ping2yingshi/p/16155697.html(4.5部分)。
4.4.3.2.3实验结果
实现Appium识别元素的内容。
4.4.4实验结果
实现Appium识别出模拟器中所需要的数据。
5.实现
5.1思路
实现微信读书打卡,可以按照以下思路来实现。
先打开微信读书,点击书架[书架ID]按钮,找到制定的书籍[书籍ID]点击,设置固定时间间隔向左滑动屏幕,设置一段时间读取书籍右下角的数字。
5.2过程
1.登录微信读书时,每次登录都会进行扫描,然后才能登录,这里把noReset设置为true,再次登录微信读书时,就不需要再次进行登录。
// noReset设置 capabilities.setCapability("noReset", "true");
2.在经历获取不到页码内容后,计划换技术路线。使用图片识别技术来识别页码。
具体问题参见博客:https://www.cnblogs.com/ping2yingshi/p/16155697.html(5.实现)。
5.2.1获取图片并裁剪图片
1.编写以下代码,对微信读书屏幕进行截屏。
/**
* 截图
*/
public static String captureApp(AndroidDriver driver) {
System.out.println("开始截图");
// 截图名称
String screenshotName = "wechat_book.png";
String filePath = SCREENSHOT_PATH + File.separator + screenshotName;
// 截图目录
File screenshotFile = new File(SCREENSHOT_PATH);
// 若文件夹不存在就创建该文件夹
if (!screenshotFile.exists() && !screenshotFile.isDirectory()) {
screenshotFile.mkdirs();
}
try {
// 截图操作
File sourceFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
// 截图存储
FileUtils.copyFile(sourceFile, new File(filePath));
} catch (IOException e) {
e.printStackTrace();
System.out.println("截图操作异常!");
}
System.out.println("截图结束");
return filePath;
}
2.截图效果如下图所示。
3.编写以下代码对2中图片进行裁剪,获取右下角页码。
public static void getSubImg(String soursePicPath, String targetPath) { File sourcePic = new File(soursePicPath); try { BufferedImage pic1 = ImageIO.read(sourcePic); int width = pic1.getWidth(); int height = pic1.getHeight(); // 参数依次为,截取起点的x坐标,y坐标,截取宽度,截取高度 BufferedImage pic2 = pic1.getSubimage(width / 9 * 7, height / 16 * 15, width / 9 * 2, height / 16 * 1); // 将截取的子图另行存储 File desImage = new File(targetPath); ImageIO.write(pic2, "png", desImage); } catch (IOException e1) { e1.printStackTrace(); } }
4.裁剪后的图片如下图所示。
5.2.2识别图片中文字
5.2.2.1下载Tesseract-OCR
1.下载Tesseract-OCR ,下载地址:https://digi.bib.uni-mannheim.de/tesseract/。
2.编写以下代码。
public class OCRUtil { private final String LANG_OPTION = "-l"; // 英文字母小写l,并非数字1 private final String EOL = System.getProperty("line.separator"); private String tessPath = "D:\\JAVA\\Java\\ocr\\Tesseract-OCR";// ocr默认安装路径 private String transname = "chi_sim";// 默认中文语言包,识别中文 /** * 从图片中识别文字 * * @param imageFile * @return text recognized in image * @throws Exception */ public String recognizeText(File imageFile) throws Exception { File tempImage = new ImageIOHelper().createImage(imageFile); return ocrImages(tempImage, imageFile); } /** * 识别图片中的文字 * * @param tempImage * @param imageFile * @return * @throws IOException * @throws InterruptedException */ private String ocrImages(File tempImage, File imageFile) throws IOException, InterruptedException { File outputFile = new File(imageFile.getParentFile(), "output"); Runtime.getRuntime().exec("attrib " + "\"" + outputFile.getAbsolutePath() + "\"" + " +H"); // 设置文件隐藏 StringBuffer strB = new StringBuffer(); List<String> cmd = new ArrayList<String>(); cmd.add(tessPath + "//tesseract"); cmd.add(""); cmd.add(outputFile.getName()); cmd.add(LANG_OPTION); cmd.add(transname); ProcessBuilder pb = new ProcessBuilder(); Map<String, String> env = pb.environment(); env.put("TESSDATA_PREFIX", tessPath + "\\tessdata"); pb.directory(imageFile.getParentFile()); cmd.set(1, tempImage.getName()); pb.command(cmd); pb.redirectErrorStream(true); Process process = pb.start(); int w = process.waitFor(); tempImage.delete();// 删除临时正在工作文件 if (w == 0) { BufferedReader in = new BufferedReader( new InputStreamReader(new FileInputStream(outputFile.getAbsolutePath() + ".txt"), "UTF-8")); String str; while ((str = in.readLine()) != null) { strB.append(str).append(EOL); } in.close(); } else { String msg; switch (w) { case 1: msg = "Errors accessing files.There may be spaces in your image's filename."; break; case 29: msg = "Cannot recongnize the image or its selected region."; break; case 31: msg = "Unsupported image format."; break; default: msg = "Errors occurred."; } tempImage.delete(); throw new RuntimeException(msg); } new File(outputFile.getAbsolutePath() + ".txt").delete(); return strB.toString(); } public static String imgToText(String imgPath) throws Exception { System.out.println("img recognize begin"); File img = new File(imgPath); String result = new OCRUtil().recognizeText(img); System.out.println("识别内容:" + result); System.out.println("img recognize end"); return result; } }
public class ImageIOHelper { /** * 创建临时图片文件 * * @param imageFile * @return * @throws IOException */ public File createImage(File imageFile) throws IOException { Iterator<ImageReader> readers = ImageIO.getImageReaders(new FileImageInputStream(imageFile)); ImageReader reader = readers.next(); ImageInputStream iis = ImageIO.createImageInputStream(imageFile); reader.setInput(iis); IIOMetadata streamMetadata = reader.getStreamMetadata(); TIFFImageWriteParam tiffWriteParam = new TIFFImageWriteParam(Locale.CHINESE); tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_DISABLED); Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("tiff"); ImageWriter writer = writers.next(); BufferedImage bi = reader.read(0); IIOImage image = new IIOImage(bi, null, reader.getImageMetadata(0)); File tempFile = tempImageFile(imageFile); ImageOutputStream ios = ImageIO.createImageOutputStream(tempFile); writer.setOutput(ios); writer.write(streamMetadata, image, tiffWriteParam); ios.close(); iis.close(); writer.dispose(); reader.dispose(); return tempFile; } /** * 添加后缀 tempfile * * @param imageFile * @return * @throws IOException */ private File tempImageFile(File imageFile) throws IOException { String path = imageFile.getPath(); StringBuffer strB = new StringBuffer(path); strB.insert(path.lastIndexOf('.'), "_text_recognize_temp"); String s = strB.toString().replaceFirst("(?<=//.)(//w+)$", "tif"); Runtime.getRuntime().exec("attrib " + "\"" + s + "\"" + " +H"); // 设置文件隐藏 return new File(strB.toString()); } }
3.找一张带有页码的图片,识别出图片中的页码。
4.先打开微信读书,然后调用截图和裁剪图片的方法,获取到只有页码的图片。最后调用识别图片的方法,获取图片中的页码。
实验中出现的一些问题,请参考博客:https://www.cnblogs.com/ping2yingshi/p/16155697.html (5.2部分)。
5.3效果
使用Appium实现了自动打开微信读书并获取页码的效果。如下图所示。
6.PSP