MonkenRunner通过HierarchyViewer定位控件的方法和建议(Appium/UIAutomator/Robotium姊妹篇)

1. 背景

在使用MonkeyRunner的时候我们经常会用到Chimchat下面的HierarchyViewer模块来获取目标控件的一些信息来辅助我们测试,但在MonkeyRunner的官网上是没有看到相应的API的描述的,上面只有以下三个类的API引用信息(http://developer.android.com/tools/help/MonkeyDevice.html)

  • MonkeyDevice
  • MonkeyImage
  • MonkeyRunner

所以在这里尝试整理下HierarchyViewer提供的API的用法并根据实践作出相应的建议,首先请看该类提供的所有可用的公共方法,内容并不多:

从图中可以看出HierarchyViewer类中提供的方法主要是用来定位控件相关的,包括根据ID取得控件,根据控件取得控件在屏幕的位置等。但还有一些其他方法,我们会顺带一并描述,毕竟内容并不多。

本文我们依然跟上几篇文章一样以SDK自带的NotePad为实验目标,看怎么定位到NotesList下面的Menu Options中的Add note这个Menu Entry。

以下是通过HierarchyViewer这个工具获得的目标设备界面的截图:

2.findViewById(String id)

2.1 示例

targetDevice = MonkeyRunner.waitForConnection()
‘‘‘
     public ViewNode findViewById(String id)
     * @param id id for the view.
     * @return view with the specified ID, or {@code null} if no view found.
‘‘‘
viewer = targetDevice.getHierarchyViewer()
button = viewer.findViewById(‘id/title‘)
text = viewer.getText(button)
print text.encode(‘utf-8‘)

2.2 分析和建议

此API的目的就是通过控件的ID来获得代表用户控件的一个ViewNode对象。因为这个是第一个示例,所以这里有几点需要说明

  • 一旦MonkeyRunner连接上设备,会立刻获得一个MonkeyDevice的对象代表了目标测试设备,我们就是通过这个设备对象来控制设备的
  • 注意这里需要填写的id的格式和UIAutomatorViewer获得ResourceId是不一样的,请看下图UIAutomatorViewer截图中ResourceId前面多出了"android:"字串:
  • 这个方法返回的一个ViewNode的对象,代表目标控件,拥有大量控件相关的属性,由于篇幅问题这里不详述,往后应该会另外撰文描述它的使用。在本文里知道它代表了目标控件就行了
  • 最后打印的时候需要转换成UTF-8编码的原因跟Jython默认的编码格式有关系,具体描述和Workaround请查看:http://www.haogongju.net/art/1636997

3. findViewById(String id, ViewNode rootNode)

3.1示例

‘‘‘
     public ViewNode findViewById(String id, ViewNode rootNode)
     * Find a view by ID, starting from the given root node
     * @param id ID of the view you‘re looking for
     * @param rootNode the ViewNode at which to begin the traversal
     * @return view with the specified ID, or {@code null} if no view found.

‘‘‘
iconMenuView = viewer.findViewById(‘id/icon_menu‘)
button = viewer.findViewById(‘id/title‘,iconMenuView)
print "Button Text:",text.encode(‘utf-8‘)

3.2分析

这个方法是上面方法的一个重载,除了需要指定ID之外,还需要指定一个rootNode,该rootNode指的就是已知控件的父控件,父到什么层级就没有限制了。为什么需要这个方法了,我们可以想象下这种情况:同一界面上存在两个控件拥有相同的ID,但是他们某一个层级父控件开始发生分叉。那么我们就可以把rootNode指定为该父控件(不包括)到目标控件(不包含)路径中的其中一个父控件来精确定位我们需要的目标控件了。

如我们的示例就是明确指出我们需要的是在父控件“id/icon_menu"(请看背景的hierarchyviewer截图)下面的那个”id/title"控件。

4 getAbsolutePositionOfView(ViewNode node)

4.1示例

‘‘‘
    public static Point getAbsoluteCenterOfView(ViewNode node)
     * Gets the absolute x/y center of the specified view node.
     *
     * @param node view node to find position of.
     * @return absolute x/y center of the specified view node.
     */
‘‘‘
point = viewer.getAbsoluteCenterOfView(button)
print "Button Absolute Center Position:",point

4.2 分析和建议

这个API的目的是想定位一个已知ViewNode控件的左上角在屏幕上的绝对坐标。对于我们正常的APP里面的控件,本人实践过是没有问题的。但是有一种情况要特别注意:这个对Menu Options下面的控件是无效的!

以上示例最后一段代码的输出是(3,18),其实这里不用想都知道这个不可能是相对屏幕左上角坐标(0,0)的绝对坐标值了,就偏移这一点点像素,你真的当我的实验机器HTC Incredible S是可以植入脑袋的神器啊。

那么这个数据是如何获得的呢?其实按照我的理解(真的只是我自己的理解,不对的话就指正吧,但请描述详细点以供我参考),这个函数的定义应该是“获得从最上层的DecorView(具体DectorView的描述请查看我以前CSDN转载的一篇文章《Android DecorView浅析》)左上角坐标到目标控件的的偏移坐标”,只是这个最上层的DecorView的坐标一般都是从(0,0)开始而已。如下图我认为最上面的那个FrameLayout就代表了DecorView,或者说整个窗体

那么在假设我的观点是对的情况下,这个就很好解析了,请看Menu Option的最上层FrameLayout的绝对坐标是(0,683)

而Add note的绝对坐标是(3,701)

两者一相减就是和我们的输出结果绝对吻合的(3,18)了。

5. getAbsoluteCenterOfView(ViewNode node)

5.1 示例

‘‘‘
    public static Point getAbsoluteCenterOfView(ViewNode node)
     * Gets the absolute x/y center of the specified view node.
     *
     * @param node view node to find position of.
     * @return absolute x/y center of the specified view node.
     */
‘‘‘
point = viewer.getAbsoluteCenterOfView(button)
print "Button Absolute Center Position:",point

5.2 分析和建议

这个方法的目的是获得目标ViewNode控件的中间点的绝对坐标值,但是对Menu Options下面的控件同样不适用,具体请查看第3章节。

以下两个方法都不是用来定位控件的,一并记录下来以供参考。

6. getFocusedWindowName()

6.1 示例

‘‘‘
    public String getFocusedWindowName()
     * Gets the window that currently receives the focus.
     *
     * @return name of the window that currently receives the focus.
‘‘‘
window = viewer.getFocusedWindowName()
print "Window Name:",window.encode(‘utf-8‘)

6.2 解析

其实就是获得当前打开的窗口的packageName/activityName,输出与HierarchyViewer工具检测到的信息一致,所以猜想其用到同样的方法。 输出: HierarchyViewer监控信息:

7. visible(ViewNode node)

7.1 示例

‘‘‘
    public boolean visible(ViewNode node)
      * Gets the visibility of a given element.
     * @param selector selector for the view.
     * @return True if the element is visible.
‘‘‘
isVisible = viewer.visible(button)
print "is visible:",isVisible

就是查看下控件是否可见,没什么好解析的了。

8. 测试代码

from com.android.monkeyrunner import MonkeyRunner,MonkeyDevice
from com.android.monkeyrunner.easy import EasyMonkeyDevice,By
from com.android.chimpchat.hierarchyviewer import HierarchyViewer
from com.android.hierarchyviewerlib.models import ViewNode, Window
from java.awt import Point

#from com.android.hierarchyviewerlib.device import 

#Connect to the target targetDevice
targetDevice = MonkeyRunner.waitForConnection()

easy_device = EasyMonkeyDevice(targetDevice)  #touch a button by id would need this
targetDevice.startActivity(component="com.example.android.notepad/com.example.android.notepad.NotesList")

#time.sleep(2000)
#invoke the menu options
MonkeyRunner.sleep(6)
targetDevice.press(‘KEYCODE_MENU‘, MonkeyDevice.DOWN_AND_UP);

‘‘‘
     public ViewNode findViewById(String id)
     * @param id id for the view.
     * @return view with the specified ID, or {@code null} if no view found.
‘‘‘
viewer = targetDevice.getHierarchyViewer()
button = viewer.findViewById(‘id/title‘)
text = viewer.getText(button)
print text.encode(‘utf-8‘)

‘‘‘
     public ViewNode findViewById(String id, ViewNode rootNode)
     * Find a view by ID, starting from the given root node
     * @param id ID of the view you‘re looking for
     * @param rootNode the ViewNode at which to begin the traversal
     * @return view with the specified ID, or {@code null} if no view found.

‘‘‘
iconMenuView = viewer.findViewById(‘id/icon_menu‘)
button = viewer.findViewById(‘id/title‘,iconMenuView)
print "Button Text:",text.encode(‘utf-8‘)

‘‘‘
    public String getFocusedWindowName()
     * Gets the window that currently receives the focus.
     *
     * @return name of the window that currently receives the focus.
‘‘‘
window = viewer.getFocusedWindowName()
print "Window Name:",window.encode(‘utf-8‘)

‘‘‘
    public static Point getAbsoluteCenterOfView(ViewNode node)
     * Gets the absolute x/y center of the specified view node.
     *
     * @param node view node to find position of.
     * @return absolute x/y center of the specified view node.
     */
‘‘‘
point = viewer.getAbsoluteCenterOfView(button)
print "Button Absolute Center Position:",point

‘‘‘
    public static Point getAbsolutePositionOfView(ViewNode node)
     * Gets the absolute x/y position of the view node.
     *
     * @param node view node to find position of.
     * @return point specifying the x/y position of the node.
‘‘‘
point = viewer.getAbsolutePositionOfView(button)
print "Button Absolute Position:", point

‘‘‘
    public boolean visible(ViewNode node)
      * Gets the visibility of a given element.
     * @param selector selector for the view.
     * @return True if the element is visible.
‘‘‘
isVisible = viewer.visible(button)
print "is visible:",isVisible

9.附上HierarchyViewer类的源码方便参照

/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.chimpchat.hierarchyviewer;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.Log;
import com.android.hierarchyviewerlib.device.DeviceBridge;
import com.android.hierarchyviewerlib.device.ViewServerDevice;
import com.android.hierarchyviewerlib.models.ViewNode;
import com.android.hierarchyviewerlib.models.Window;
import org.eclipse.swt.graphics.Point;
/**
 * Class for querying the view hierarchy of the device.
 */
public class HierarchyViewer {
    public static final String TAG = "hierarchyviewer";
    private IDevice mDevice;
    /**
     * Constructs the hierarchy viewer for the specified device.
     *
     * @param device The Android device to connect to.
     */
    public HierarchyViewer(IDevice device) {
        this.mDevice = device;
        setupViewServer();
    }
    private void setupViewServer() {
        DeviceBridge.setupDeviceForward(mDevice);
        if (!DeviceBridge.isViewServerRunning(mDevice)) {
            if (!DeviceBridge.startViewServer(mDevice)) {
                // TODO: Get rid of this delay.
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                }
                if (!DeviceBridge.startViewServer(mDevice)) {
                    Log.e(TAG, "Unable to debug device " + mDevice);
                    throw new RuntimeException("Could not connect to the view server");
                }
                return;
            }
        }
        DeviceBridge.loadViewServerInfo(mDevice);
    }
    /**
     * Find a view by id.
     *
     * @param id id for the view.
     * @return view with the specified ID, or {@code null} if no view found.
     */
    public ViewNode findViewById(String id) {
        ViewNode rootNode = DeviceBridge.loadWindowData(
                new Window(new ViewServerDevice(mDevice), "", 0xffffffff));
        if (rootNode == null) {
            throw new RuntimeException("Could not dump view");
        }
        return findViewById(id, rootNode);
    }
    /**
     * Find a view by ID, starting from the given root node
     * @param id ID of the view you‘re looking for
     * @param rootNode the ViewNode at which to begin the traversal
     * @return view with the specified ID, or {@code null} if no view found.
     */
    public ViewNode findViewById(String id, ViewNode rootNode) {
        if (rootNode.id.equals(id)) {
            return rootNode;
        }
        for (ViewNode child : rootNode.children) {
            ViewNode found = findViewById(id,child);
            if (found != null) {
                return found;
            }
        }
        return null;
    }
    /**
     * Gets the window that currently receives the focus.
     *
     * @return name of the window that currently receives the focus.
     */
    public String getFocusedWindowName() {
        int id = DeviceBridge.getFocusedWindow(mDevice);
        Window[] windows = DeviceBridge.loadWindows(new ViewServerDevice(mDevice), mDevice);
        for (Window w : windows) {
            if (w.getHashCode() == id)
                return w.getTitle();
        }
        return null;
    }
    /**
     * Gets the absolute x/y position of the view node.
     *
     * @param node view node to find position of.
     * @return point specifying the x/y position of the node.
     */
    public static Point getAbsolutePositionOfView(ViewNode node) {
        int x = node.left;
        int y = node.top;
        ViewNode p = node.parent;
        while (p != null) {
            x += p.left - p.scrollX;
            y += p.top - p.scrollY;
            p = p.parent;
        }
        return new Point(x, y);
    }
    /**
     * Gets the absolute x/y center of the specified view node.
     *
     * @param node view node to find position of.
     * @return absolute x/y center of the specified view node.
     */
    public static Point getAbsoluteCenterOfView(ViewNode node) {
        Point point = getAbsolutePositionOfView(node);
        return new Point(
                point.x + (node.width / 2), point.y + (node.height / 2));
    }
    /**
     * Gets the visibility of a given element.
     *
     * @param selector selector for the view.
     * @return True if the element is visible.
     */
    public boolean visible(ViewNode node) {
        boolean ret = (node != null)
                && node.namedProperties.containsKey("getVisibility()")
                && "VISIBLE".equalsIgnoreCase(
                        node.namedProperties.get("getVisibility()").value);
        return ret;
    }
    /**
     * Gets the text of a given element.
     *
     * @param selector selector for the view.
     * @return the text of the given element.
     */
    public String getText(ViewNode node) {
        if (node == null) {
            throw new RuntimeException("Node not found");
        }
        ViewNode.Property textProperty = node.namedProperties.get("text:mText");
        if (textProperty == null) {
            // give it another chance, ICS ViewServer returns mText
            textProperty = node.namedProperties.get("mText");
            if (textProperty == null) {
                throw new RuntimeException("No text property on node");
            }
        }
        return textProperty.value;
    }
}

10. 参考阅读

以下是之前不同框架的控件定位的实践,一并列出来方便直接跳转参考:

作者 自主博客 微信服务号及扫描码 CSDN
天地会珠海分舵 http://techgogogo.com 服务号:TechGoGoGo扫描码: http://blog.csdn.net/zhubaitian
时间: 2024-10-25 10:59:06

MonkenRunner通过HierarchyViewer定位控件的方法和建议(Appium/UIAutomator/Robotium姊妹篇)的相关文章

UIAutomator定位Android控件的方法实践和建议(Appium姊妹篇)

在本人之前的一篇文章<<Appium基于安卓的各种FindElement的控件定位方法实践和建议>>第二章节谈到Appium可以通过使用UIAutomator的方法去定位Android界面上的控件,当时只是一笔带过举了个例子.如该文给自己的承诺,今天特撰写此文以描述UIAutomator各种控件定位的方法,以作为前文的姊妹篇互通有无. 1. 背景 为了和前文达成一致,这次的实践对象同样也是使用SDK自带的NotePad应用,同样是尝试去获得在NotesList那个Activity里

ADF控件ID变化引发JS无法定位控件的解决方法

原文地址:ADF控件ID变化引发JS无法定位控件的解决方法作者:Nicholas JSFF定义的控件ID到了客户端时往往会改变.例如在JSFF中的一个的ID为"ot1",但是当这个JSFF被嵌入TaskFlow中,并以Region的方式在页面展示时,在浏览器中,此控件的ID就会变成"r1:0:ot1". ADF动态修改控件ID的原因其实是为了提供重用性的同时防止控件ID的冲突.试想如果有两个JSFF中分别都有一个,其ID均为ot1,那么当这两个JSFF被引入同一个页

【转】UIAutomator定位Android控件的方法实践和建议(Appium姊妹篇)

原文地址:http://blog.csdn.net/zhubaitian/article/details/39777951 在本人之前的一篇文章<<Appium基于安卓的各种FindElement的控件定位方法实践和建议>>第二章节谈到Appium可以通过使用UIAutomator的方法去定位Android界面上的控件,当时只是一笔带过举了个例子.如该文给自己的承诺,今天特撰写此文以描述UIAutomator各种控件定位的方法,以作为前文的姊妹篇互通有无. 1. 背景 为了和前文达

Robotium之Android控件定位实践和建议(Appium/UIAutomator姊妹篇)

本人之前曾经撰文描述Appium和UIAutomator框架是如何定位Android界面上的控件的. UIAutomator定位Android控件的方法实践和建议 Appium基于安卓的各种FindElement的控件定位方法实践和建议 今天我们换一个渊源更留长,当今更盛行的框架Robotium,实践下看它又是如何对控件进行定位的. 1. 背景 为保持这个系列的一致性,我们继续用SDK自带的NotePad实例应用作为我们的试验目标应用,但是这次不仅仅是像以前一样主要围绕Menu Option里面

Atitit.swt&#160;线程调用ui控件的方法

Atitit.swt 线程调用ui控件的方法 1 SwingUtilities.invokeLater1 2 display.asyncExec方法1 3  display.timerExec(500,timer);2 4 .但有时候并不一定要程序执行时就要定时检测,有时需要外部事情激发这就出现了第2种解决方案,写一个内置类,可以放在事件监听的方法中,然后激发:2 5 参考3 1   SwingUtilities.invokeLater SwingUtilities.invokeLater(ne

EasyUI常用控件禁用方法

EasyUI常用控件禁用方法: 1.validatebox可以用的用法:前两种适用于单个的validatebox; 第三种应用于整个form里面的输入框; <1>.$("#id").attr("readonly", true); -----  $("#id").removeAttr("readonly"); <2>.$("#id").attr("readonly"

驰骋工作流引擎-地图定位控件

Technorati Tags: 驰骋工作流引擎 .工作流 .CCBPM JFLOW CCFLOW.手机表单. 地图定位控件 关键词:驰骋工作流引擎 工作流 CCBPM JFLOW CCFLOW手机表单 地图定位控件 驰骋工作流引擎开源软件,为适应在手机端的应用,现开发了地图定位控件.该控件可嵌入到表单中,在系统人员进行待办信息的处理时获取当前所处位置信息. 地图定位控件位于驰骋工作流表单设计器的工具栏位置.如图所示: 通过手机微信端和钉钉客户端的访问,可展示的效果如下.

WdatePicker 日历控件使用方法+基本常用方法

WdatePicker 日历控件使用方法+基本常用方法,记录一下. 很好的文章. 网上转来的. 1. 跨无限级框架显示 无论你把日期控件放在哪里,你都不需要担心会被外层的iframe所遮挡进而影响客户体验,因为My97日期控件是可以跨无限级框架显示的 示例2-7 跨无限级框架演示 可无限跨越框架iframe,无论怎么嵌套框架都不必担心了,即使有滚动条也不怕 2. 民国年日历和其他特殊日历 当年份格式设置为yyy格式时,利用年份差量属性yearOffset(默认值1911民国元年),可实现民国年日

C# winform datagridview 无需点击两次即可编辑内嵌控件的方法和删除默认的空行的方法

? 1 2 3 4 //点击一下即可对DataGridView中内嵌控件进行编辑,不需要二次点击 dgv.EditMode =DataGridViewEditMode.EditOnEnter; //DataGridView无默认行 dgv.AllowUserToAddRows = false; C# winform datagridview 无需点击两次即可编辑内嵌控件的方法和删除默认的空行的方法