Android-side automated testing tool source code sharing

Hello, everyone, I haven’t seen you for a long time, and I forgot to update it. In the last article, I made a simple understanding and use of the automated testing framework on the Android side. Today’s article, let’s talk about it in detail. realization ideas.

To be honest, for this version 1.0.0, there are still many flaws, but I can’t keep up with the pace of continued development. I have enough time to plan for the follow-up, so I still have to work hard. The value of this thing , In automated testing on the Android side, the self-feeling is still very high.

In the effect of the previous article, we can find that all trigger controls are delivered through the web page, and the display of the web page is enabled through the mobile phone. That is to say, the mobile phone must be used as a A server for sending and receiving instructions.

Using the mobile phone as the server is the first step to realize automated testing. After all, on the page of automated testing, not only need to obtain all the apps installed on the mobile phone, but also need to send test instructions to the server (mobile phone), so how to achieve What about such a function?

Those who follow me may know that I shared an article on how to access files in the SD card on the computer before. In fact, in this article, I introduced an open source framework, AndServer, which is a Web server for the Android platform. Server and Web Framework, through this framework, we can easily use the mobile phone as a server. This framework can indeed solve many problems. Interested friends can take a look.

Related document address:

https://yanzhenjie.com/AndServer/

The framework of AndServer only provides us with a platform as a service, but some functions such as sending instructions and obtaining the App list need to be completed by ourselves. In fact, it is the writing of the interface, using the mobile phone as the "backend " ", with the web page as the front end.

We disassemble the specific implementation, which can be basically divided into three parts: the display of the front-end page, the writing of the "back-end" interface, and the writing of automation logic.

Front page display

The front-end page, that is, the webpage we saw above, is written using html, css and javascript. You can view the assets directory in the source code . Below this directory is all the source code of the above-mentioned webpage effects. The index.html file is Our main web page source files, the web language, are relatively simple, html and css are very similar to our Android xml, and the data dynamic interaction javascript is similar to our java and kotlin, and it is relatively simple to learn , For this one, I won’t explain too much. Let’s see how you guys understand it. I hope that everyone, if you have time, it’s better to be more familiar with it.

Writing the " backend" interface

After the front-end page is written, data rendering and logical processing are required. According to the documentation of AndServer, we can simply write related interfaces.

According to the front-end UI, all the Apps installed on the mobile phone need to be obtained on the left side, so a related interface for obtaining the list of mobile Apps is required.

The interface is provided in the form of Get:

/**
     * AUTHOR:AbnerMing
     * INTRODUCE:获取手机应用信息
     */
    @GetMapping(path = "/appList")
    List<AppInfo> getAppList() {
        return AppUtils.getAppUtils().getAppInfo(1);
}

The specific logic of obtaining the mobile phone list:

// 获取已安装的应用信息队列
    public ArrayList<AppInfo> getAppInfo(int type) {
        ArrayList<AppInfo> appList = new ArrayList<>();
        SparseIntArray siArray = new SparseIntArray();
        // 获得应用包管理器
        PackageManager pm = mContext.getPackageManager();
        // 获取系统中已经安装的应用列表
        @SuppressLint("WrongConstant")
        List<ApplicationInfo> installList = pm.getInstalledApplications(
                PackageManager.PERMISSION_GRANTED);
        for (int i = 0; i < installList.size(); i++) {
            ApplicationInfo item = installList.get(i);

            // 去掉重复的应用信息
            if (siArray.indexOfKey(item.uid) >= 0) {
                continue;
            }
            // 往siArray中添加一个应用编号,以便后续的去重校验
            siArray.put(item.uid, 1);
            try {
                // 获取该应用的权限列表
                String[] permissions = pm.getPackageInfo(item.packageName,
                        PackageManager.GET_PERMISSIONS).requestedPermissions;
                if (permissions == null) {
                    continue;
                }

                boolean isQueryNetwork = false;
                for (String permission : permissions) {
                    // 过滤那些具备上网权限的应用
                    if (permission.equals("android.permission.INTERNET")) {
                        isQueryNetwork = true;
                        break;
                    }
                }
                // 类型为0表示所有应用,为1表示只要联网应用
                if (type == 0 || (type == 1 && isQueryNetwork)) {
                    AppInfo app = new AppInfo();
                    app.uid = item.uid; // 获取应用的编号
                    app.label = item.loadLabel(pm).toString(); // 获取应用的名称
                    app.package_name = item.packageName; // 获取应用的包名
                    //Drawable drawable = item.loadIcon(pm); // 获取应用的图标

                    if (!app.label.contains(".") && !app.label.contains(" ")) {
                        appList.add(app);
                    }
                }


            } catch (Exception e) {
                e.printStackTrace();
                continue;
            }
        }
        return appList;  // 返回去重后的应用包队列
    }

The middle part of the front-end UI is the area for displaying and writing application scripts, and the storage and acquisition of scripts also depend on the mobile phone. There are many storage methods, including memory, files or databases. Here I simply It is stored through SharedPreferences.

When storing, you need to pay attention, because the scripts stored in different applications are also different, and they need to be distinguished according to the identification of the application. Here, the number uid of the application is used. In the above obtained application list, you can get it through ApplicationInfo . The execution effect on the front end is to click on different applications on the left to switch between different uids, edit the script through the middle, and then call the save interface.

/**
     * AUTHOR:AbnerMing
     * INTRODUCE:存储脚本信息
     */
    @PostMapping(path = "/save")
    void saveAppScript(
            @RequestParam(name = "appScript") String appScript,
            @RequestParam(name = "appKey") String appKey
    ) {
        SharedPreUtils.put(App.getInstance(), "script_" + appKey, appScript);
}

Some veterans may have doubts, you use SharedPreferences to save, how to realize the script list of each application, this is also simple, save it through json, convert the specific array and object into a string and store it.

Get script information:

/**
     * AUTHOR:AbnerMing
     * INTRODUCE:获取脚本信息
     */
    @GetMapping(path = "/appScript")
    String getAppScript(@RequestParam(name = "appKey") String appKey) {
        String json = SharedPreUtils.getString(App.getInstance(), "script_" + appKey);
        if (!TextUtils.isEmpty(json)) {
            return json;
        } else {
            return JsonUtils.failedJson(400, "没有找到对应的脚本数据");
        }
    }

Receive startup messages and current progress messages

After the front-end clicks on the script to run, it needs to send the current application information and script information to the back-end, that is, the mobile phone. So how does the mobile phone receive this notification? Here, a timing operation is done on the mobile phone, that is, to continuously poll whether there is an instruction coming. If so, assign a variable to true, and then execute the program directly. After the program is executed or click to terminate the operation, assign this variable It is false, that is to say, polling is always on, but the execution of the program is controlled by variables.

Open the program interface, including the command to open, the script information to call, and the package name of the application.

 /**
     * AUTHOR:AbnerMing
     * INTRODUCE:开启程序
     */
    @PostMapping(path = "/startApp")
    void startScript(
            @RequestParam(name = "startApp") String startApp,
            @RequestParam(name = "appScript") String appScript,
            @RequestParam(name = "appPack") String appPack
    ) {
        App.Companion.setMAppToastMessage("");
        App.Companion.setMAppScriptMessage("");
        App.Companion.setMStartApp(startApp);
        App.Companion.setMAppPack(appPack);
        App.Companion.setMAppScript(appScript);

    }

Of course, there are some other interface information. You can mainly look at the PhoneAppController class in the source code, which also has detailed comments.

automation logic

With the display of the front-end UI and the provision of the interface, basically, the effect of the previous article can be achieved, but after the run command is triggered, how to execute the script normally? For example, I wrote a script for JD.com, search for Moutai, click to enter the details, and then execute the snap-up operation. After the program is delivered, how can it run normally on the mobile phone?

Here, I am using AccessibilityService, which is the accessibility service on the Android side. Through AccessibilityService, we can monitor the currently visible applications and capture information about the controls on the page, and then perform operations such as our click, input, and slide.

You can look at the relevant source code of the AutomaticService class, which is only the first version at present, and many codes are written very casually, so let's make do with it first.

The relevant logic of this service implementation is to start a timer directly after starting the service. This timer exists all the time. Its purpose is to receive whether there is a new command. closure.

 If it is not empty, it proves that there is new script information, execute it, and then assign the value to empty to ensure that a command is only executed once.

When executing the script, you can see that it is edited in Chinese or abbreviated. In fact, these are defined by ourselves, that is to say, we can define it at will, such as clicking login, the most important thing is the word login , Whether you click or click, according to the definition, go back to our program, intercept, and trigger the relevant logic. The picture below is defined by myself.

This is intercepted in the code, you can see the following code, it is very simple, get your execution script, judge whether it contains the defined keywords, and then intercept and execute the click operation.

if (s.contains("点击") || s.contains("ck")) {
            try {

                String click = "";
                if (s.contains("点击")) {
                    click = s.replace("点击", "");
                } else {
                    click = s.replace("ck", "");
                }
                //判断是否存在
                WUtils.findTextAndClick(this, click, new CallBackListener() {
                    @Override
                    public void success() {
                        try {
                            Thread.sleep(1000);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                        App.Companion.setMAppScriptMessage(indexPosition + "==" + s);
                        Log.e(TAG, indexPosition + "==" + s);
                        containsContent();

                    }

                    @Override
                    public void fail() {
                        App.Companion.setMAppScriptMessage("-c==【" + s + "】未找到当前程序");
                        Log.e(TAG, "-c==【" + s + "】未找到当前程序");
                        //执行发生,错误之后,继续执行下一个逻辑
                        containsContent();

                    }
                });

            } catch (Exception e) {
                e.printStackTrace();
                App.Companion.setMAppScriptMessage("-c==【" + s + "】语法错误啊老铁");
                Log.e(TAG, "-c==【" + s + "】语法错误啊老铁");
            }

        }

The above is the click operation, sliding, input, etc. are all done in this way, you can directly look at the AutomaticService class.

Regarding click, slide, input and other operations, I have packaged them all. You can look at WUtils or BaseAccessibilityService, and there are basically comments in it.

The implementation of this version of the function is more anxious, and it has not been properly sorted out. According to the established plan, the use of AccessibilityService can be completely automated smoothly. At present, only a rough idea has been realized, and follow-up needs to be followed up. Okay, everyone. , let’s stop here, the following is the source code, you can change it at will.

Github address:

https://github.com/AbnerMing888/AndroidAutomatic

Guess you like

Origin blog.csdn.net/ming_147/article/details/126194697