分享

APP自動化框架-ATX原理解析及JAVA版客戶端

 wenxuefeng360 2022-11-07 发布于四川

作為網易開源的ATX APP自動化測試框架,對比現有的macaca自動化框架/Appium自動化框架,最大的特別就是在於可遠程進行自動化測試

先給大家看一張我自己梳理的框架架構圖

框架巧妙點:

1. 使用golang作為server端運行在Android手機上,免root運行

2. AutomatorHttpService使用NanoHTTPD框架,也自己運行一個server,專門監聽及處理過來的http jsonRpc請求

public class AutomatorHttpServer extends NanoHTTPD {

    public AutomatorHttpServer(int port) {
        super(port);
    }

    private Map<String, JsonRpcServer> router = new HashMap<String, JsonRpcServer>();

    public void route(String uri, JsonRpcServer rpc) {
        router.put(uri, rpc);
    }

    @Override
    public Response serve(String uri, Method method,
                          Map<String, String> headers, Map<String, String> params,
                          Map<String, String> files) {
        Log.d(String.format("URI: %s, Method: %s, params, %s, files: %s", uri, method, params, files));

        if ("/stop".equals(uri)) {
            stop();
            return newFixedLengthResponse("Server stopped!!!");
        } else if ("/ping".equals(uri)) {
            return newFixedLengthResponse("pong");
        } else if ("/screenshot/0".equals(uri)) {
            float scale = 1.0f;
            if (params.containsKey("scale")) {
                try {
                    scale = Float.parseFloat(params.get("scale"));
                } catch (NumberFormatException e) {
                }
            }
            int quality = 100;
            if (params.containsKey("quality")) {
                try {
                    quality = Integer.parseInt(params.get("quality"));
                } catch (NumberFormatException e) {
                }
            }
            File f = new File(InstrumentationRegistry.getTargetContext().getFilesDir(), "screenshot.png");
            UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).takeScreenshot(f, scale, quality);

            try {
                return newChunkedResponse(Response.Status.OK, "image/png", new FileInputStream(f));
            } catch (FileNotFoundException e) {
                Log.e(e.getMessage());
                return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "Internal Server Error!!!");
            }
        } else if (router.containsKey(uri)) {
            JsonRpcServer jsonRpcServer = router.get(uri);
            ByteArrayInputStream is = null;
            if (params.get("NanoHttpd.QUERY_STRING") != null)
                is = new ByteArrayInputStream(params.get("NanoHttpd.QUERY_STRING").getBytes());
            else if (files.get("postData") != null)
                is = new ByteArrayInputStream(files.get("postData").getBytes());
            else
                return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "Invalid http post data!");
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            try {
                jsonRpcServer.handleRequest(is, os);
                return newFixedLengthResponse(Response.Status.OK, "application/json", new ByteArrayInputStream(os.toByteArray()), os.size());
            } catch (IOException e) {
                return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "Internal Server Error!!!");
            }
        } else
            return newFixedLengthResponse(Response.Status.NOT_FOUND, MIME_PLAINTEXT, "Not Found!!!");
    }

}

 

3. 使用jsonRpc反射反射形式對外提供 uiautomator方式

package com.github.uiautomator.stub;

import android.content.Context;
import android.content.Intent;
import android.os.RemoteException;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.LargeTest;
import android.support.test.filters.SdkSuppress;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.Configurator;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.Until;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.googlecode.jsonrpc4j.JsonRpcServer;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * Use JUnit test to start the uiautomator jsonrpc server.
 *
 * @author xiaocong@gmail.com
 */
@RunWith(AndroidJUnit4.class)
@SdkSuppress(minSdkVersion = 18)
public class Stub {
    private final String TAG = "UIAUTOMATOR";
    private static final int LAUNCH_TIMEOUT = 5000;


    int PORT = 9008;
    AutomatorHttpServer server = new AutomatorHttpServer(PORT);

    @Before
    public void setUp() throws Exception {
        launchService();
        //這是關鍵核心代碼,把AutomatorService使用jsonRpcServer進行反射處理
        server.route("/jsonrpc/0", new JsonRpcServer(new ObjectMapper(), new AutomatorServiceImpl(), AutomatorService.class));
        server.start();
    }

    private void launchPackage(String packageName) {
        Log.i(TAG, "Launch " + packageName);
        UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
        Context context = InstrumentationRegistry.getContext();
        final Intent intent = context.getPackageManager()
                .getLaunchIntentForPackage(packageName);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        context.startActivity(intent);

        device.wait(Until.hasObject(By.pkg(packageName).depth(0)), LAUNCH_TIMEOUT);
        device.pressHome();
    }

    private void launchService() throws RemoteException {
        UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
        Context context = InstrumentationRegistry.getContext();
        device.wakeUp();

        // Wait for launcher
        String launcherPackage = device.getLauncherPackageName();
        Boolean ready = device.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), LAUNCH_TIMEOUT);
        if (!ready) {
            Log.i(TAG, "Wait for launcher timeout");
            return;
        }

        Log.d("Launch service");
        context.startService(new Intent("com.github.uiautomator.ACTION_START"));

        // Reset Configurator Wait Timeout
        Configurator configurator = Configurator.getInstance();
        configurator.setWaitForSelectorTimeout(0L);

        // BUG(uiautomator): setWaitForIdleTimeout is useless
        // Refs: https://www./archives/22
    }

    @After
    public void tearDown() {
        server.stop();
        Context context = InstrumentationRegistry.getContext();
        context.startService(new Intent("com.github.uiautomator.ACTION_STOP"));
    }

    @Test
    @LargeTest
    public void testUIAutomatorStub() throws InterruptedException {
        while (server.isAlive()) {
            Thread.sleep(100);
        }
    }
}

 

4. AutomatorServiceImpl把原生UiAutomation加了一定處理,重寫了一遍,只要確保入參數保持一致

@Override
    public boolean click(int x, int y) {
        return device.click(x, y);
    }
    @Override
    public boolean drag(int startX, int startY, int endX, int endY, int steps) throws NotImplementedException {
        return device.drag(startX, startY, endX, endY, steps);
    }

 

從整體而言,代碼簡潔、可讀性、代碼解耦,在ATX上提現較為明顯

附上我這邊寫的java版ATX客戶端,原框架只提供了python版

https://github.com/tigerge000/atxuiautomatorclient

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多