移动测试基础 [探究解答] getAttribute 方法为何不支持参数为 index 问题帖的探究

Archer · 2014年08月01日 · 最后由 lifreshman 回复于 2014年12月23日 · 2681 次阅读
本帖已被设为精华帖!

原来的问题帖在这里,在看本帖之前,请先仔细阅读一下原来的问题帖。
http://testerhome.com/topics/1199

虽然现在原帖子被置为了【已解决】,但是,我显然不甘心这样的解决方式。

问题很简单:为什么嗅探出来的 index 属性,通过 getAttribute 方法获取不到!?

我们先从 getAttribute 的源码开始看

这里我还是以 python client 为例

def get_attribute(self, name):
        """Gets the attribute value.

        :Args:
            - name - name of the attribute property to retieve.

        Example::

            # Check if the 'active' css class is applied to an element.
            is_active = "active" in target_element.get_attribute("class")

        """
        resp = self._execute(Command.GET_ELEMENT_ATTRIBUTE, {'name': name})
        attributeValue = ''
        if resp['value'] is None:
            attributeValue = None
        else:
            attributeValue = resp['value']
            if name != 'value' and attributeValue.lower() in ('true', 'false'):
                attributeValue = attributeValue.lower()

        return attributeValue

webelement 实例支持 get_attribute 方法,那么这个方法究竟是怎么实现获取元素属性值的呢?
根据代码中的 Command.GET_ELEMENT_ATTRIBUTE,我们来到这里

GET_ELEMENT_ATTRIBUTE = "getElementAttribute"

原来,这个方法调用了 webdriver 的 getElementAttribute 命令,我们不妨去绝对标准的 W3C 上看看这条命令的解释

W3C 关于 webdriver 中 getElementAttribute 的解释

10.3 Reading Attributes and Properties

Although the [HTML5] spec is very clear about the difference between the properties and attributes of a DOM element, users are frequently confused between the two. Because of this, the WebDriver API offers a single command ("getElementAttribute") which covers the case of returning both the value of a DOM element's property or attribute. If a user wishes to refer specifically to an attribute or a property, they should evaluate Javascript in order to be unambiguous. In this section, the "attribute" with name name shall refer to the result of calling the Javascript "getAttribute" function on the element, with the following exceptions:

If, in the current rendering mode, the content attribute name reflects a boolean IDL attribute, as per the HTML specification, the value must be the string 'true' if that IDL attribute's value is true or the null value if the IDL attribute's value is false.
If the element is an OPTION element and name is "value" and there is no "value" attribute, then the text content of the OPTION element must be returned, in accordance with [HTML401] spec, specifically the section on pre-selected options. The text content must be the result of calling the "getElementText" command on the OPTION element.
If the element is selectable, and name is "selected", or the element is an INPUT element of type "checkbox" or "radio" and name is "checked", return the string 'true' if the element is selected, and the null value otherwise.
If name is "style", the value returned must be serialized as defined in the [CSSOM-VIEW] spec. Notably, css property names must be cased the same as specified in in section 6.5.1 of the [CSSOM-VIEW] spec.
Consequently, it should be equivalent to obtaining the "cssText" property, with the additional constraint that the same value must be returned after a round trip through "executeScript". That is, the following pseudo-code must be true (where "driver" is a WebDriver instance, and "element" is a WebElement):

var style = element.getAttribute('style');
driver.executeScript('arguments[0].style = arguments[1]', element, style);
var recovered = element.getAttribute('style');
assertEquals(style, recovered);

Color property values must be standardized to rgba format, matching the regular expression: rgba(\d+, \d+, \d+, (1|0(.\d+)?)).
If the value is expected to be a URL (see the below table), return the property named name, i.e. a fully resolved URL: TODO: This doesn't feel like an exhaustive list
Tag name "name" value

A   href
IMG src

If name is in the below table, and the above stages have not yielded a defined, non-null value, the value of the aliased attribute in the table below should be returned:
Original property name Aliased property name

class   className
readonly    readOnly

NOTE
These aliases provide the commonly used names for element properties.

getElementAttribute 的核心实现参考

Command Name    getElementAttribute
Parameters  "sessionId" {string} The key that identifies which session this request is for.
"id" {string} The ID of the WebElement on which to operate.
"name" {string} The name of the property of attribute to return.
Return Value    {string|null} The value returned by the above algorithm, coerced to a nullable string, or null if no value is defined.
Errors  StaleElementException If the element is no longer attached to the DOM.

但是,看到这里,我们似乎还是没有从 W3C 那里扒到一点什么,但是起码我们还是有收获的。

这个时候我们在想,这个 index 属性到底是不是 element 的?

我们不妨从 webdriver 中随便找一个简单的例子去执行一下瞧瞧

# -*- coding:utf-8 -*-

from selenium import webdriver

if __name__=="__main__":
    wb=webdriver.Firefox()
    wb.get("http://www.baidu.com")
    newsele=wb.find_elements_by_tag_name("a")
    wb.execute_script()
    for newe in newsele:
        print newe.get_attribute("index")

这里我打印出来的全是 None,说明我们这里取到的每一个 a 标签都没有这个 index 属性,我们去看看浏览器的 DOM 分析:

结果证明事实确实是这样,我们确实没有看到 index 这个属性值!

那么 UIAutomator 中的这个 index 属性值是哪儿来的?

我们不妨大胆猜测,这个 index 本身就不是节点的"属性",这个 index 只是类似于 jquery 中的 index() 方法一样取到的这个元素相对于其他同类元素的索引值,uiautomatorviewer 只是把它打印出来了,并显示在了界面上。你可以通过这个 index 索引 (类似于数组下标) 去访问到这个元素,但是反过来的话,就稍微有些复杂:

我们想象一个最普通的数组,里面放的是我们取到的各个同类元素

['链接a1','链接a2','链接a3','链接a4']

现在,这个列表中的每一个元素都是一个对象 (obj),假如我要给他们动态添加 index 属性,我需要遍历他们,并利用他们所在的数组下标和他们自身做一个操作:那就是调用自身的某个 set 方法,动态增加一个 index 属性,并将索引值赋给这个 index 属性,只有这样我们才能通过对象.属性去访问到这个值。

但是问题就在于,这个运行时才添加到的属性显然是无法保存的,当你试图去访问"index"属性时,返回的只有 None 或者是一些异常提示信息。

所以,这个问题跟"Appium 封装的 getAttribute 方法不支持 index","没有实现" 应该是没有任何关系的。

要想做到的话怎么办?

要想做到访问其实也不是不可以,那就是对这个对象进行封装,通过封装后的对象访问这个 index 属性。

这个有点类似于 QTP 中的封装属性和公共属性

我们知道,QTP 中的对象识别中,也有一个 index,但是这个 index 是 QTP 自己封装的,公共属性才是一个标准的元素大家都共有的属性,webdriver 中的 getAttribute 方法,只能获取到这里的公用属性,封装的属性是获取不到的,所以,我想这就是问题的答案。

欢迎不同意见

共收到 7 条回复 时间 点赞

@qddegtya 你也太专业了。。。膜拜下,但是我还是好奇为何 driver.findElementByXPath("//android.widget.ImageView[@index=0]"); 这个语句可以定位到对应的元素呢?

#2 楼 @tom_ma xpath 本身就是遍历 xml 中相关信息的,这个时候的 index 已经是 Node 中的一个节点了,但是这还是不能说 index 就是这个 element 的属性,这个跟这个问题本身是完全不矛盾的。

很赞,分析的非常好, 讲解的很透彻

学习!!

请教一下关于 getAttribute 的问题,我在 appium 的 lib/devices/ios/ios-controller.js 里看到有这么一段代码,
rest.get('/wd/hub/session/:sessionId?/element/:elementId?/attribute/:name', controller.getAttribute);

iOSController.getAttribute = function (elementId, attributeName, cb) {
  if (this.isWebContext()) {
    var atomsElement = this.getAtomsElement(elementId);
    if (atomsElement === null) {
      cb(null, {
        status: status.codes.UnknownError.code
      , value: "Error converting element ID for using in WD atoms: " + elementId
      });
    } else {
      this.executeAtom('get_attribute_value', [atomsElement, attributeName], cb);
    }
  } else {
    if (_.contains(['label', 'name', 'value', 'values', 'hint'], attributeName)) {
      var command = ["au.getElement('", elementId, "').", attributeName, "()"].join('');
      this.proxy(command, cb);
    } else {
      cb(null, {
        status: status.codes.UnknownCommand.code
      , value: "UIAElements don't have the attribute '" + attributeName + "'"
      });
    }
  }
};

这里是说 python 在用 get_attribute 方法时会发送 get 里的请求,然后 appium 将请求映射为 iOSController.getAttribute 么,那这里是说如果是原生 app,就只能获取到'label', 'name', 'value', 'values', 'hint'这几个属性么?
期待你的回复!

#6 楼 @lifreshman 从代码上来讲:_.contains(['label', 'name', 'value', 'values', 'hint'], attributeName) underscore 中的 contains 方法表示 list 中是否包含第二个参数,所以,除了这几个之外的,都会抛 “UIAElements don't have the attribute......”

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册