新手区 [求教] 在 Appium Inspector 中无法查看到控件 ID

debugtalk · May 26, 2016 · Last by debugtalk replied at May 30, 2016 · 3412 hits

在 Appium Inspector 中选中一个控件时,只能查看到 name、label、value、xpath 这些字段。

但是在 Appium Ruby Console 中,查看同一个控件,却可以看到控件的 ID 字段。

请问这是什么原因呢?如果我要在 Appium Inspector 中查看到控件的 ID 属性,要怎么操作呢?

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 15 条回复 时间 点赞

ios 把所有可见元素的 name,value,label 都认为是 accessiblity id

debugtalk #2 · May 27, 2016 Author

#1 楼 @lihuazhang 谢谢啦,刚采用 id 命令去试了下 name、label 和 value,的确也能定位到控件。不过我还不懂的是,为什么在 Console 中可以看到 id,但是在 Inspector 中看不到

#2 楼 @debugtalk 我理解为实现方式不一样

debugtalk #4 · May 27, 2016 Author

#3 楼 @lihuazhang 以你的经验来看,定位元素的最佳实践是采用什么方式呢?当前我是打算采用 name,但是 app 存在多国语言,这样就需要针对每一种语言进行处理;官方文档推荐采用 ID,但是 APP 里面大多控件都没有 ID 这个属性(从 Console 来看);而采用 xpath 的话,考虑到需求变动频繁,很有可能 UI 变动较大,所以也是个问题。

#4 楼 @debugtalk ID. iOS 基本元素都有 id。而且 id 是指 元素的 name,value,label

debugtalk #6 · May 27, 2016 Author

#5 楼 @lihuazhang 我还是不大明白。如果把 name,value,label 作为控件的 ID 的话,那和直接采用 find_element(:name, 'XXX') 这样的方式有什么差异呢?
而且,如果把 name,value,label 这些作为 ID,那么 APP 在不同语言下的值很有可能不一样,而且在版本迭代中,也很有可能改变,例如按钮名称变化;这也没有享受到 ID 相对较为固定的好处啊。

#6 楼 @debugtalk 一般来说会指定一个 ID 的,通常是 label 这种。不会一直变。find_element(:name, 'XXX') 本来就没什么差异。

没用过 ruby console 。。。据我所知本身 iOS 控件应该没有 id 这样的属性的,一般用的是 AccessibilityLabel 或者 AccessibilityIdentify。你能把在 ruby console 获取 id 时的 appium server log 发上来看看实际上请求的是什么命令吗?

debugtalk #9 · May 27, 2016 Author

#8 楼 @chenhengjie123
我也是在看官方文档时看到的,说通过 ID 定位元素是更好的方式。

debugtalk #10 · May 27, 2016 Author

#8 楼 @chenhengjie123

按照你的要求,我在 Ruby Console 里面通过 id 查询某元素。

➜ arc
[1] pry(main)> page
UIAButton
   name, label: My Account
   id: My Account => My Account
nil
[2] pry(main)> id('My Account').name
"My Account"

Appium Server Log 里面对应的日志:

[iOSLog] [IOS_SYSLOG_ROW] May 27 14:12:33 Leos-Mac-mini CoreSimulatorBridge[52416]: Switching to keyboard: zh-Hans

[iOSLog] [IOS_SYSLOG_ROW] May 27 14:12:33 Leos-Mac-mini CoreSimulatorBridge[52416]: KEYMAP: Failed to determine iOS keyboard layout for language zh-Hans.

[iOSLog] [IOS_SYSLOG_ROW] May 27 14:12:33 Leos-Mac-mini CoreSimulatorBridge[52416]: Switching to keyboard: zh-Hans

[iOSLog] [IOS_SYSLOG_ROW] May 27 14:12:33 Leos-Mac-mini CoreSimulatorBridge[52416]: KEYMAP: Failed to determine iOS keyboard layout for language zh-Hans.

[HTTP] --> POST /wd/hub/session/9884af6e-bb65-44d5-9489-e4e73e135f64/element {"using":"id","value":"My Account"}

[MJSONWP] Calling AppiumDriver.findElement() with args: ["id","My Account","9884af6e-bb65-44d5-9489-e4e73e135f64"]
[debug] [iOS] Executing iOS command 'findElement'
[debug] [BaseDriver] Waiting up to 0 ms for condition
[debug] [UIAuto] Sending command to instruments: au.getElementByAccessibilityId('My Account')

[debug] [Instruments] [INST] 2016-05-27 06:12:35 +0000 Debug: Got new command 17 from instruments: au.getElementByAccessibilityId('My Account')

[debug] [Instruments] [INST] 2016-05-27 06:12:35 +0000 Debug: evaluating au.getElementByAccessibilityId('My Account')

[debug] [Instruments] [INST] 2016-05-27 06:12:35 +0000 Debug: evaluation finished

[debug] [Instruments] [INST] 2016-05-27 06:12:35 +0000 Debug: Lookup returned [object UIAButton] with the name "My Account" (id: 2).

[debug] [Instruments] [INST] 2016-05-27 06:12:35 +0000 Debug: responding with:

[debug] [Instruments] [INST] 2016-05-27 06:12:35 +0000 Debug: Running system command #18: /Applications/Appium.app/Contents/Resources/node/bin/node /Applications/Appium.app/Contents/Resources/node_modules/appium/node_modules/appium-ios-driver/node_modules/appium-uiauto/build/lib/bin/command-proxy-client.js /var/folders/wy/fh5d0cn53b5714pkw7282n540000gn/T/instruments_sock 2,{"status":0,"v...

[debug] [UIAuto] Socket data received (38 bytes)

[debug] [UIAuto] Got result from instruments: {"status":0,"value":{"ELEMENT":"2"}}

[MJSONWP] Responding to client with driver.findElement() result: {"ELEMENT":"2"}
[HTTP] <-- POST /wd/hub/session/9884af6e-bb65-44d5-9489-e4e73e135f64/element 200 1226 ms - 87 

[HTTP] --> GET /wd/hub/session/9884af6e-bb65-44d5-9489-e4e73e135f64/element/2/attribute/name {}
[MJSONWP] Calling AppiumDriver.getAttribute() with args: ["name","2","9884af6e-bb65-44d5-9489-e4e73e135f64"]
[debug] [iOS] Executing iOS command 'getAttribute'

[debug] [UIAuto] Sending command to instruments: au.getElement('2').name()

[debug] [Instruments] [INST] 2016-05-27 06:12:36 +0000 Debug: Got new command 18 from instruments: au.getElement('2').name()

[debug] [Instruments] [INST] 2016-05-27 06:12:36 +0000 Debug: evaluating au.getElement('2').name()

[debug] [Instruments] [INST] 2016-05-27 06:12:36 +0000 Debug: evaluation finished

[debug] [Instruments] [INST] 2016-05-27 06:12:36 +0000 Debug: responding with:
[debug] [Instruments] [INST] 2016-05-27 06:12:36 +0000 Debug: Running system command #19: /Applications/Appium.app/Contents/Resources/node/bin/node /Applications/Appium.app/Contents/Resources/node_modules/appium/node_modules/appium-ios-driver/node_modules/appium-uiauto/build/lib/bin/command-proxy-client.js /var/folders/wy/fh5d0cn53b5714pkw7282n540000gn/T/instruments_sock 2,{"status":0,"v...

[debug] [UIAuto] Socket data received (35 bytes)

[debug] [UIAuto] Got result from instruments: {"status":0,"value":"My Account"}

[MJSONWP] Responding to client with driver.getAttribute() result: "My Account"
[HTTP] <-- GET /wd/hub/session/9884af6e-bb65-44d5-9489-e4e73e135f64/element/2/attribute/name 200 1064 ms - 84 

[iOSLog] [IOS_SYSLOG_ROW] May 27 14:12:37 Leos-Mac-mini CoreSimulatorBridge[52416]: Switching to keyboard: zh-Hans

[iOSLog] [IOS_SYSLOG_ROW] May 27 14:12:37 Leos-Mac-mini CoreSimulatorBridge[52416]: KEYMAP: Failed to determine iOS keyboard layout for language zh-Hans.
[iOSLog] [IOS_SYSLOG_ROW] May 27 14:12:37 Leos-Mac-mini CoreSimulatorBridge[52416]: Switching to keyboard: zh-Hans

[iOSLog] [IOS_SYSLOG_ROW] May 27 14:12:37 Leos-Mac-mini CoreSimulatorBridge[52416]: KEYMAP: Failed to determine iOS keyboard layout for language zh-Hans.

[HTTP] --> GET /wd/hub/status {}

[MJSONWP] Calling AppiumDriver.getStatus() with args: []

[MJSONWP] Responding to client with driver.getStatus() result: {"build":{"version":"1.5.2","revision":null}}
[HTTP] <-- GET /wd/hub/status 200 17 ms - 83 

[HTTP] --> GET /wd/hub/status {}
[MJSONWP] Calling AppiumDriver.getStatus() with args: []
[MJSONWP] Responding to client with driver.getStatus() result: {"build":{"version":"1.5.2","revision":null}}
[HTTP] <-- GET /wd/hub/status 200 16 ms - 83 

#10 楼 @debugtalk 我想看的是 page 命令对应的 log ,但你给的貌似是 id('My Account').name 的(log 里面一开始就是 find element)。能补充一下吗?

debugtalk #12 · May 27, 2016 Author

#11 楼 @chenhengjie123
page 命令对应的 log 如下:

[HTTP] --> GET /wd/hub/session/4d6f5865-be02-4462-a33f-59304d4f2b20/context {}

[MJSONWP] Calling AppiumDriver.getCurrentContext() with args: ["4d6f5865-be02-4462-a33f-59304d4f2b20"]

[debug] [iOS] Executing iOS command 'getCurrentContext'

[MJSONWP] Responding to client with driver.getCurrentContext() result: "NATIVE_APP"
[HTTP] <-- GET /wd/hub/session/4d6f5865-be02-4462-a33f-59304d4f2b20/context 200 8 ms - 84 
[HTTP] --> GET /wd/hub/session/4d6f5865-be02-4462-a33f-59304d4f2b20/context {}
[MJSONWP] Calling AppiumDriver.getCurrentContext() with args: ["4d6f5865-be02-4462-a33f-59304d4f2b20"]
[debug] [iOS] Executing iOS command 'getCurrentContext'

[MJSONWP] Responding to client with driver.getCurrentContext() result: "NATIVE_APP"
[HTTP] <-- GET /wd/hub/session/4d6f5865-be02-4462-a33f-59304d4f2b20/context 200 3 ms - 84 

[HTTP] --> POST /wd/hub/session/4d6f5865-be02-4462-a33f-59304d4f2b20/execute {"script":"UIATarget.localTarget().frontMostApp().windows()[0].getTree()","args":[]}
[MJSONWP] Calling AppiumDriver.execute() with args: ["UIATarget.localTarget().frontMostApp().windows()[0].getTree()",[],"4d6f5865-be02-4462-a33f-59304d4f2b20"]
[debug] [iOS] Executing iOS command 'execute'
[debug] [UIAuto] Sending command to instruments: UIATarget.localTarget().frontMostApp().windows()[0].getTree()

[debug] [Instruments] [INST] 2016-05-27 12:30:39 +0000 Debug: Got new command 3 from instruments: UIATarget.localTarget().frontMostApp().windows()[0].getTree()

[debug] [Instruments] [INST] 2016-05-27 12:30:39 +0000 Debug: evaluating UIATarget.localTarget().frontMostApp().windows()[0].getTree()

[debug] [Instruments] [INST] 2016-05-27 12:30:39 +0000 Debug: evaluation finished

[debug] [Instruments] [INST] 2016-05-27 12:30:39 +0000 Debug: responding with:":{"x":0,"y":0},"size":{"width":375,"height":618}},"dom":null,"enabled":true,"valid":true,"visible":true,"children":[{"name":"no_login_icon","type":"UIAImage","label":null,"value":null,"rect":{"origin":{"x":157.5,"y":60},"size":{"width":60,"height":60}},"dom":null,"enabled":true,"valid":true,"visible":false,"children":[],"hint":null},{"name":"XXX","type":"UIAStaticText","label":"XXX","value":"XXX","rect":{"origin":{"x":20,"y":135},"size":{"width":335,"height":20.5}},"dom":null,"enabled":true,"valid":true,"visible":false,"children":[],"hint":null},{"name":"Label","type":"UIAStaticText","label":"Label","value":"Label","rect":{"origin":{"x":0,"y":196},"size":{"width":375,"height":20.5}},"dom":null,"enabled":true,"valid":true,"visible":false,"children":[],"hint":null},{"name":"Fans","type":"UIAStaticText","label":"Fans","value":"Fans","rect":{"origin":{"x":217.5,"y":220},"size":{"width":30.5,"height":20}},"dom":null,"enabled":true,"valid":true,"visible":false,"children":[],"hint":null},{"name":"Follow","type":"UIAStaticText","label":"Follow","value":"Follow","rect":{"origin":{"x":116,"y":220},"size":{"width":41.5,"height":20}},"dom":null,"enabled":true,"valid":true,"visible":false,"children":[],"hint":null},{"name":"100","type":"UIAStaticText","label":"100","value":"100","rect":{"origin":{"x":122.5,"y":198},"size":{"width":28.5,"height":22}},"dom":null,"enabled":true,"valid":true,"visible":false,"children":[],"hint":null},{"name":"200","type":"UIAStaticText","label":"200","value":"200","rect":{"origin":{"x":217.5,"y":198},"size":{"width":31,"height":22}},"dom":null,"enabled":true,"valid":true,"visible":false,"children":[],"hint":null},{"name":"me edite","type":"UIAButton","label":"me edite","value":null,"rect":{"origin":{"x":335,"y":25},"size":{"width":30,"height":30}},"dom":null,"enabled":true,"valid":true,"visible":false,"children":[{"name":"me_edite","type":"UIAImage","label":null,"value":null,"rect":{"origin":{"x":337.5,"y":27.5},"size":{"width":25,"height":25}},"dom":null,"enabled":true,"valid":true,"visible":false,"children":[],"hint":null}],"hint":null},{"name":"Login","type":"UIAButton","label":"Login","value":null,"rect":{"origin":{"x":44.5,"y":175},"size":{"width":123,"height":30}},"dom":null,"enabled":true,"valid":true,"visible":true,"children":[],"hint":null},{"name":"Register","type":"UIAButton","label":"Register","value":null,"rect":{"origin":{"x":207.5,"y":175},"size":{"width":123,"height":30}},"dom":null,"enabled":true,"valid":true,"visible":true,"children":[],"hint":null},{"name":"Hi!Guest","type":"UIAStaticText","label":"Hi!Guest","value":"Hi!Guest","rect":{"origin":{"x":0,"y":134.5},"size":{"width":375,"height":20.5}},"dom":null,"enabled":true,"valid":true,"visible":true,"children":[],"hint":null},{"name":"Label","type":"UIAStaticText","label":"Label","value":"Label","rect":{"origin":{"x":0,"y":165.5},"size":{"width":375,"height":20.5}},"dom":null,"enabled":true,"valid":true,"visible":false,"children":[],"hint":null},{"name":"User Feedback ","type":"UIATableCell","label":null,"value":"","rect":{"origin":{"x":0,"y":260},"size":{"width":375,"height":50}},"dom":null,"enabled":true,"valid":true,"visible":true,"children":[{"name":"User Feedback ","type":"UIAStaticText","label":"User Feedback ","value":"User Feedback ","rect":{"origin":{"x":50,"y":276},"size":{"width":107.5,"height":18}},"dom":null,"enabled":true,"valid":true,"visible":true,"children":[],"hint":null}],"hint":null},{"name":null,"type":"UIATableGroup","label":null,"value":null,"rect":{"origin":{"x":0,"y":310},"size":{"width":375,"height":20}},"dom":null,"enabled":true,"valid":true,"visible":false,"children":[],"hint":null},{"name":"System Settings","type":"UIATableCell","label":null,"value":"","rect":{"origin":{"x":0,"y":330},"size":{"width":375,"height":50}},"dom":null,"enabled":true,"valid":true,"visible":true,"children":[{"name":"System Settings","type":"UIAStaticText","label":"System Settings","value":"System Settings","rect":{"origin":{"x":50
,"y":346},"size":{"width":111.5,"height":18}},"dom":null,"enabled":true,"valid":true,"visible":true,"children":[],"hint":null}],"hint":null}],"hint":null},{"name":null,"type":"UIATabBar","label":null,"value":null,"rect":{"origin":{"x":0,"y":618},"size":{"width":375,"height":49}},"dom":null,"enabled":true,"valid":true,"visible":true,"children":[{"name":null,"type":"UIAImage","label":null,"value":null,"rect":{"origin":{"x":0,"y":617.5},"size":{"width":375,"height":0.5}},"dom":null,"enabled":true,"valid":true,"visible":false,"children":[],"hint":null},{"name":null,"type":"UIAImage","label":null,"value":null,"rect":{"origin":{"x":0,"y":618},"size":{"width":375,"height":49}},"dom":null,"enabled":true,"valid":true,"visible":true,"children":[],"hint":null},{"name":"Store","type":"UIAButton","label":"Store","value":null,"rect":{"origin":{"x":2,"y":619},"size":{"width":71,"height":48}},"dom":null,"enabled":true,"valid":true,"visible":true,"children":[],"hint":null},{"name":"Experience","type":"UIAButton","label":"Experience","value":null,"rect":{"origin":{"x":77,"y":619},"size":{"width":71,"height":48}},"dom":null,"enabled":true,"valid":true,"visible":true,"children":[],"hint":null},{"name":"Nearby","type":"UIAButton","label":"Nearby","value":null,"rect":{"origin":{"x":152,"y":619},"size":{"width":71,"height":48}},"dom":null,"enabled":true,"valid":true,"visible":true,"children":[],"hint":null},{"name":"Forum","type":"UIAButton","label":"Forum","value":null,"rect":{"origin":{"x":227,"y":619},"size":{"width":71,"height":48}},"dom":null,"enabled":true,"valid":true,"visible":true,"children":[],"hint":null},{"name":"My Account","type":"UIAButton","label":"My Account","value":1,"rect":{"origin":{"x":302,"y":619},"size":{"width":71,"height":48}},"dom":null,"enabled":true,"valid":true,"visible":true,"children":[],"hint":null}],"hint":null}],"hint":null}}
[debug] [Instruments] [INST] 2016-05-27 12:30:39 +0000 Debug: Running system command #4: /Applications/Appium.app/Contents/Resources/node/bin/node /Applications/Appium.app/Contents/Resources/node_modules/appium/node_modules/appium-ios-driver/node_modules/appium-uiauto/build/lib/bin/command-proxy-client.js /var/folders/wy/fh5d0cn53b5714pkw7282n540000gn/T/instruments_sock 2,{"status":0,"v...

[debug] [UIAuto] Socket data received (6165 bytes)

[debug] [UIAuto] Got result from instruments: {"status":0,"value":{"name":null,"type":"UIAWindow","label":null,"value":null,"rect":{"origin":{"x":0,"y":0},"size":{"width":375,"height":667}},"dom":null,"enabled":true,"valid":true,"visible":true,"children":[{"name":null,"type":"UIATableView","label":null,"value":"rows 1 to 2 of 2","rect":{"origin

[MJSONWP] Responding to client with driver.execute() result: {"name":null,"type":"UIAWindow","label":null,"value":null,"rect":{"origin":{"x":0,"y":0},"size":{"width":375,"height":667}},"dom":null,"enabled":tr...

[HTTP] <-- POST /wd/hub/session/4d6f5865-be02-4462-a33f-59304d4f2b20/execute 200 1100 ms - 6238 

[HTTP] --> POST /wd/hub/session/4d6f5865-be02-4462-a33f-59304d4f2b20/appium/app/strings {}

[MJSONWP] Calling AppiumDriver.getStrings() with args: [null,null,"4d6f5865-be02-4462-a33f-59304d4f2b20"]
[debug] [iOS] Executing iOS command 'getStrings'
[debug] [iOS] Gettings strings for language 'undefined' and string file 'null'
[debug] [iOS] No language specified. Using default strings
[debug] [iOS] Strings file not found. Looking in 'en.lproj' directory

[debug] [iOS] Parsed app 'Localizable.strings'
[MJSONWP] Responding to client with driver.getStrings() result: {"Pull down to refresh":"Pull down to refresh","Please input the new password.":"Please input your new password.","再看看":"Cancel","Total":"Total","我...
[HTTP] <-- POST /wd/hub/session/4d6f5865-be02-4462-a33f-59304d4f2b20/appium/app/strings 200 34 ms - 51687 


#12 楼 @debugtalk 从 log 来看,server 返回的这个控件的属性如下:

{
  "name": "My Account",
  "type": "UIAButton",
  "label": "My Account",
  "value": 1,
  "rect": {
    "origin": {
      "x": 302,
      "y": 619
    },
    "size": {
      "width": 71,
      "height": 48
    }
  },
  "dom": null,
  "enabled": true,
  "valid": true,
  "visible": true,
  "children": [],
  "hint": null
}

里面并没有返回名为 id 的属性。估计这个属性是 ruby client 或者 ruby console 自己添加的。然后看了下 ruby client 的源码,发现的确是它自己加的。相关代码:

...
          # there may be many ids with the same value.
          # output all exact matches.
          attributes = [name, label, value, hint].select { |attr| !attr.nil? }
          partial    = {}
          id_matches = @strings_xml.select do |key, val|
            next if val.nil? || val.empty?
            partial[key] = val if attributes.detect { |attr| attr.include?(val) }
            attributes.detect { |attr| val == attr }
          end

          # If there are no exact matches, display partial matches.
          id_matches = partial if id_matches.empty?

          unless id_matches.empty?
            match_str = ''
            max_len   = id_matches.keys.max_by(&:length).length

            # [0] = key, [1] = val
            id_matches.each do |key, val|
              arrow_space = ' ' * (max_len - key.length).to_i
              match_str += ' ' * 7 + "#{key} #{arrow_space}=> #{val}\n"
            end
            puts "   id: #{match_str.strip}\n"
          end
...

完整的源码:https://github.com/appium/ruby_lib/blob/master/lib/appium_lib/ios/helper.rb

当前我是打算采用 name,但是 app 存在多国语言,这样就需要针对每一种语言进行处理;官方文档推荐采用 ID,但是 APP 里面大多控件都没有 ID 这个属性(从 Console 来看);

关于这个补充回答一下,做 UI 自动化时一般需要手动对控件添加 AccessibilityLabel 来保证其唯一性的。这个属性仅用于做盲人辅助/UI 自动化,和界面显示、多语言之类的都无关。大部分情况下开发不会主动添加这个属性,个别控件会有这个属性值的原因是它们的 AccessibilityLabel 默认值就是控件的某个属性,但这种一般无法满足自动化的需要。

debugtalk #15 · May 30, 2016 Author

#14 楼 @chenhengjie123 太感谢啦,总算解决这些天的困惑了。

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up