做爲網易開源的ATX APP自動化測試框架,對比現有的macaca自動化框架/Appium自動化框架,最大的特別就是在於可遠程進行自動化測試java
先給你們看一張我本身梳理的框架架構圖python
框架巧妙點:android
1. 使用golang做爲server端運行在Android手機上,免root運行git
2. AutomatorHttpService使用NanoHTTPD框架,也本身運行一個server,專門監聽及處理過來的http jsonRpc請求github
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方式golang
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.ydkf.me/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加了必定處理,重寫了一遍,只要確保入參數保持一致json
@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版app