环境

APPIUM Version: 1.6.3
JAVA Client: 5.0.0
JAVA: 1.8
UiAutomator2 Drvier: 0.2.6

Code

//capabilities
capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.ANDROID_UIAUTOMATOR2);
//click the button to verify the toast message
driver.findElementByXPath("//*[contains(@text,'Button')]").click();
WebDriverWait wait = new WebDriverWait(driver, 10);
wait.until(ExpectedConditions.presenceOfElementLocated(By.xpath("//*[contains(@text, 'port')]")))

Toast 内容

Toast.makeText(getBaseContext(), "Change port successfully", Toast.LENGTH_SHORT).show();

Result

使用 uiautomator2 启动应用是成功的,点击也是成功的,但是尝试验证 Toast 消息时,没有成功过。错误信息如下:
org.openqa.selenium.TimeoutException: Expected condition failed: waiting for presence of element located by: By.xpath: //*contains(@text, 'port')

Other

手机中有安装两个应用:

  1. io.appium.uiautomator2.server
  2. io.appium.uiautomator2.server.test
//同样的代码,定位符改成一个Button的定位符,是能找到的
wait.until(ExpectedConditions.presenceOfElementLocated(By.xpath("//*[@text='Button']")));


源码打开后,这里的红色标记怎么解决啊,没有任何报错啊,编译也没有问题

appium 提供的单元测试源码

@Test
public void toastVerificationTest() throws JSONException {
    getUiDevice().waitForIdle();
    scrollTo("Views"); // Due to 'Views' option not visible on small screen
    waitForElement(By.accessibilityId("Views"), 10 * SECOND);
    click(findElement(By.accessibilityId("Views")));
    scrollTo("Popup Menu");
    waitForElement(By.accessibilityId("Popup Menu"), 10 * SECOND);
    click(findElement(By.accessibilityId("Popup Menu")));
    waitForElement(By.accessibilityId("Make a Popup!"), 10 * SECOND);
    click(findElement(By.accessibilityId("Make a Popup!")));
    waitForElement(By.xpath(".//*[@text='Search']"), 10 * SECOND);
    click(findElement(By.xpath(".//*[@text='Search']")));
    waitForMilliSeconds(500);
    element = findElement(By.xpath("//*[@text='Clicked popup menu item Search']"));
    String toastMSG = getText(element);
    assertEquals("Clicked popup menu item Search", toastMSG);
    Logger.info("[AppiumUiAutomator2Server]", " findElement By.xpath: " + element);
    assertTrue(By.xpath("//*[@text='Clicked popup menu item Search']") + "not found", isElementPresent(element));

    click(findElement(By.accessibilityId("Make a Popup!")));
    waitForElement(By.xpath(".//*[@text='Add']"), 10 * SECOND);
    click(findElement(By.xpath(".//*[@text='Add']")));
    waitForMilliSeconds(500);
    element = findElement(By.xpath("//*[contains(@text,'Clicked popup menu item Add')]"));
    Logger.info("[AppiumUiAutomator2Server]", " findElement By.xpath: " + element);
    assertTrue(By.xpath("//*[@text='Clicked popup menu item Add']") + "not found", isElementPresent(element));
    toastMSG = getText(element);
    assertEquals("Clicked popup menu item Add", toastMSG);

    click(findElement(By.accessibilityId("Make a Popup!")));
    waitForElement(By.xpath(".//*[@text='Edit']"), 10 * SECOND);
    click(findElement(By.xpath(".//*[@text='Edit']")));
    waitForMilliSeconds(500);
    element = findElement(By.xpath("//*[@text='Clicked popup menu item Edit']"));
    Logger.info("[AppiumUiAutomator2Server]", " findElement By.xpath: " + element);
    assertTrue(By.xpath("//*[@text='Clicked popup menu item Edit']") + "not found", isElementPresent(element));
    toastMSG = getText(element);
    assertEquals("Clicked popup menu item Edit", toastMSG);


    click(findElement(By.xpath(".//*[@text='Share']")));
    waitForMilliSeconds(1000);
    element = findElement(By.xpath("//*[@text='Clicked popup menu item Share']"));
    Logger.info("[AppiumUiAutomator2Server]", " findElement By.xpath: " + element);
    assertTrue(By.xpath("//*[@text='Clicked popup menu item Share']") + "not found", isElementPresent(element));
    toastMSG = getText(element);
    assertEquals("Clicked popup menu item Share", toastMSG);

}

单元测试执行方法:

am instrument -w -e class io.appium.uiautomator2.unittest.test.HandlersTest#toastVerificationTest io.appium.uiautomator2.e2etest.test/android.support.test.runner.AndroidJUnitRunner

单元测试日志:

04-20 12:08:19.134  6055  6068 I appium  : [AppiumUiAutomator2Server] Starting S
erver
04-20 12:08:19.379  6055  6068 I appium  : [AppiumUiAutomator2Server] waiting fo
r app to launch
04-20 12:08:30.028  6055  6068 I appium  : [AppiumUiAutomator2Server] findElemen
t By.xpath: {"sessionId":":sessionId","status":0,"value":{"ELEMENT":"f2e952ea-d5
0c-4b4b-a81e-c1244240c30d"}}
04-20 12:08:32.122  6055  6068 I appium  : [AppiumUiAutomator2Server] findElemen
t By.xpath: {"sessionId":":sessionId","status":0,"value":{"ELEMENT":"4e141e0b-a5
3a-4e46-82d4-cec7cd748640"}}
04-20 12:08:34.646  6055  6068 I appium  : [AppiumUiAutomator2Server] findElemen
t By.xpath: {"sessionId":":sessionId","status":0,"value":{"ELEMENT":"3a16e7f8-86
2d-453e-90be-d70eec2bb85b"}}
04-20 12:08:36.705  6055  6068 I appium  : [AppiumUiAutomator2Server] findElemen
t By.xpath: {"sessionId":":sessionId","status":0,"value":{"ELEMENT":"9fc430ea-19
d5-44cd-b121-031275152d9b"}}

从日志来看,单元测试执行时,是有找到 toast 元素的,但是我在 eclipse 里面写测试用例的时候就是报没有找到,真是头疼

如何 debug 验证?

  1. 在代码中需要的地方打断点
  2. 在手机设置应用中,打开开发者选项,选择调试应用,选择 io.appium.uiautomator2.server.test
  3. 这时候给 server 发请求即可,可通过浏览器直接发请求,建立会话,或执行 appium 代码进入调试

原理解析?

private class Listener extends Thread{

        private long previousTime = currentTimeMillis();

        public void run() {
            while (true) {
                AccessibilityEvent accessibilityEvent = null;
                toastMessages = init();

                //return true if the AccessibilityEvent type is NOTIFICATION type
                UiAutomation.AccessibilityEventFilter eventFilter = new UiAutomation.AccessibilityEventFilter() {
                    @Override
                    public boolean accept(AccessibilityEvent event) {
                        return event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED;
                    }
                };
                Runnable runnable = new Runnable() {
                    @Override
                    public void run() {
                        // Not performing any event.
                    }
                };

                try {
                    //wait for AccessibilityEvent filter
                    accessibilityEvent = UiAutomatorBridge.getInstance().getUiAutomation()
                            .executeAndWaitForEvent(runnable /*executable event*/, eventFilter /* event to filter*/, 500 /*time out in ms*/);
                } catch (Exception ignore) {}

                if (accessibilityEvent != null) {
                    toastMessages = accessibilityEvent.getText();
                    previousTime = currentTimeMillis();
                    Logger.info("toastMessages:" + toastMessages);
                    GetToastMessage.toastMessage = toastMessages.toString();// add by carl
                }
                if(stopLooping){
                    break;
                }
            }
        }

post 方法

引用 uiautomator2.0 server 的相关 jar,不要引 android.jar,json jar: json-20070829.jar

try {
            Runtime.getRuntime().exec("adb forward tcp:"+ 8201 +" tcp:6790");
            Thread.sleep(2000);

            String sessionId = "";
            //create session
            sessionId = TestHelper.post("/wd/hub/session", new JSONObject().put("desiredCapabilities", new JSONObject()).toString());
            System.out.println(sessionId);
            if (sessionId.contains("sessionId")) {
                sessionId = new JSONObject(sessionId).getString("sessionId");
                System.out.println("sessionId:" + sessionId);
            } else {
                sessionId = "c04ada9b-38cf-4778-9168-12faf6747159";
            }

//          System.out.println(TestHelper.get("/wd/hub/session/" + sessionId + "/getPostMsg"));
//          System.exit(1);

            String elementId = "";

            //find element: Make a Popup!
            JSONObject element = new JSONObject("{\"strategy\":\"xpath\",\"selector\":\"//*[contains(@text,'Make a Popup!')]\",\"context\":\"\",\"multiple\":false}");
            elementId = TestHelper.post("/wd/hub/session/" + sessionId + "/element", element.toString());
            System.out.println("elementId:" + elementId);
            elementId = new JSONObject(elementId).getJSONObject("value").getString("ELEMENT");

            //click one element
            JSONObject click = new JSONObject().put("elementId", elementId);
            TestHelper.post("/wd/hub/session/" + sessionId + "/element/" + elementId + "/click", click.toString());
            TestHelper.waitTimes(2000);

            elementId = TestHelper.post("/wd/hub/session/" + sessionId + "/element", new JSONObject("{\"strategy\":\"xpath\",\"selector\":\"//*[@text='Search']\",\"context\":\"\",\"multiple\":false}").toString());
            System.out.println("elementId:" + elementId);
            elementId = new JSONObject(elementId).getJSONObject("value").getString("ELEMENT");

            //click one element
            TestHelper.post("/wd/hub/session/" + sessionId + "/element/" + elementId + "/click", new JSONObject().put("elementId", elementId).toString());

            for (int i=0; i<2000; i++) {
                elementId = TestHelper.post("/wd/hub/session/" + sessionId + "/element", new JSONObject("{\"strategy\":\"xpath\",\"selector\":\"//*[@text='Clicked popup menu item Search']\",\"context\":\"\",\"multiple\":false}").toString());
                if (!elementId.contains("not")) {
                    System.out.println("i=" + i);
                    System.out.println("elementId:" + elementId);
                    elementId = new JSONObject(elementId).getJSONObject("value").getString("ELEMENT");
                    System.out.println(TestHelper.get("/wd/hub/session/" + sessionId + "/element/" + elementId+ "/attribute/text"));
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

后感

这绝对是个坑,不过让我学习了 uiautomator2.0 drvier server 的原代码,和 uiautomator1.0 不一样,有点意思。
本人做的改动主要包含:

  1. 修改 private final int TOAST_CLEAR_TIMEOUT = 5000;//原来是 3500,不过肯定不是重点,short 类型的 toast 是没有 5 秒那么久的
  2. 增加 toast listener 启停的日志
  3. 增加了一个新的 get 服务,当有 toast 消息时,便记录下来,即使 toast 消息已经消失,仍然可以 get 出来 toast 的内容
package io.appium.uiautomator2.handler;

import io.appium.uiautomator2.handler.request.SafeRequestHandler;
import io.appium.uiautomator2.http.AppiumResponse;
import io.appium.uiautomator2.http.IHttpRequest;
import io.appium.uiautomator2.server.WDStatus;
import io.appium.uiautomator2.utils.Logger;

public class GetToastMessage extends SafeRequestHandler {

    public static String toastMessage = "defalut value is null";//值由GetToastMessage.toastMessage = toastMessages.toString();// add by carl这句更新

    public GetToastMessage(String mappedUri) {
        super(mappedUri);
    }

    @Override
    public AppiumResponse safeHandle(IHttpRequest request) {
        Logger.info("GetToastMessage command");
        return new AppiumResponse(getSessionId(request), WDStatus.SUCCESS, toastMessage);
    }

}

  1. 重新编译,更新本地路径(appium\node_modules\appium-uiautomator2-driver\uiautomator2)的应用,因为打包好的默认在 c 盘 build the android project using below commands: gradle clean assembleServerDebug assembleServerDebugAndroidTest
  2. 最后仍然有些疑惑,不过主要问题已经解决了。


↙↙↙阅读原文可查看相关链接,并与作者交流