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

 

 

 

 

 

posted @ 2022-04-20 14:36  萍2樱释  阅读(172)  评论(0编辑  收藏  举报