|
前言之前看到一个帖子(appium 1.3.4.1 版 sendkey 错误),里面提到了在appium 1.3.4.1中用sendKeys输入文本会变成追加文本,而appium 1.2.4则没有这个问题。同时如果sendKeys前有先clear也不会出现这个问题。周末有时间在源码环境下探究了一下具体原因,结果不仅发现了具体原因,还发现了一个小bug。
调试环境appium版本:1.3.6(REV faf0873919a70c930b32df48e7653e8fe830a142)
所用IDE: WebStorm(node.js部分),IDEA(Android上使用的bootstrap部分)
源码修改: 为了能对bootstrap进行远程调试,我在appium/libs/devices/android/uiautomator.js的启动参数中添加了-e debug true这个参数:
被测app: 自行制作的app,里面只有一个界面,界面中有一个editText控件,控件的默认值为“中文”(非hint text)。控件具有content-description,因此脚本中使用id来查找控件。
注意:添加了这个参数后bootstrap启动时会一直等待直到有debugger联系到它。所以非debug用途请勿做此修改。
至于如何设置远程调试bootstrap,调试方法和调试Android UIAutomator一样。由于不是本文的重点,因此不再仔细介绍,有兴趣的同学请自行搜索。
SendKeys工作过程log分析SendKeys从 appium server->bootstraps->最终输入 的完整log如下:
- info: --> POST /wd/hub/session/be7c34f0-e12d-4368-9f80-c116e0cc3990/element/1/value {"sessionId":"be7c34f0-e12d-4368-9f80-c116e0cc3990","id":"1","value":["f","i","r","s","t"]}
- info: [debug] Pushing command to appium work queue: ["element:setText",{"elementId":"1","text":"first","replace":false}]
- info: [debug] [BOOTSTRAP] [debug] Got data from client: {"cmd":"action","action":"element:setText","params":{"elementId":"1","text":"first","replace":false}}
- info: [debug] [BOOTSTRAP] [debug] Got command of type ACTION
- info: [debug] [BOOTSTRAP] [debug] Got command action: setText
- info: [debug] [BOOTSTRAP] [debug] Attempting to clear using UiObject.clearText().
- info: [debug] [BOOTSTRAP] [debug] Sending plain text to element: 中文first
- info: [debug] [BOOTSTRAP] [debug] Returning result: {"value":true,"status":0}
- info: [debug] Responding to client with success: {"status":0,"value":true,"sessionId":"be7c34f0-e12d-4368-9f80-c116e0cc3990"}
- info: <-- POST /wd/hub/session/be7c34f0-e12d-4368-9f80-c116e0cc3990/element/1/value 200 9119.841 ms - 76 {"status":0,"value":true,"sessionId":"be7c34f0-e12d-4368-9f80-c116e0cc3990"}
复制代码 简单说一下各个log是什么意思:
- info: --> POST /wd/hub/session/be7c34f0-e12d-4368-9f80-c116e0cc3990/element/1/value {"sessionId":"be7c34f0-e12d-4368-9f80-c116e0cc3990","id":"1","value":["f","i","r","s","t"]}
- 表示server接收到一个post类型的http请求,post的url是/wd/hub/session/be7c34f0-e12d-4368-9f80-c116e0cc3990/element/1/value,post请求的内容(即http协议中的的body部分)为{"sessionId":"be7c34f0-e12d-4368-9f80-c116e0cc3990","id":"1","value":["f","i","r","s","t"]}
- 此部分的通讯遵循webdriver协议,无论哪个client发出来都是采用这个格式
- info: [debug] Pushing command to appium work queue: ["element:setText",{"elementId":"1","text":"first","replace":false}]
- 表示server把["element:setText",{"elementId":"1","text":"first","replace":false}]这个命令放到待处理命令序列中。
- info: [debug] [BOOTSTRAP] [debug] Got command of type ACTION
- info: [debug] [BOOTSTRAP] [debug] Got command action: setText
- 表示bootstrap收到类型为action,名称为setText的命令
- info: [debug] [BOOTSTRAP] [debug] Attempting to clear using UiObject.clearText().
- 表示执行UiObject.clearText()来清除元素的现有文字
- info: [debug] [BOOTSTRAP] [debug] Sending plain text to element: 中文first
- 表示在元素中输入中文first
- info: [debug] [BOOTSTRAP] [debug] Returning result: {"value":true,"status":0}
- 表示bootstrap返回执行结果{"value":true,"status":0}
- info: [debug] Responding to client with success: {"status":0,"value":true,"sessionId":"be7c34f0-e12d-4368-9f80-c116e0cc3990"}
- 表示appium server准备返回{"status":0,"value":true,"sessionId":"be7c34f0-e12d-4368-9f80-c116e0cc3990"}给发起请求的client
- info: <-- POST /wd/hub/session/be7c34f0-e12d-4368-9f80-c116e0cc3990/element/1/value 200 9119.841 ms - 76 {"status":0,"value":true,"sessionId":"be7c34f0-e12d-4368-9f80-c116e0cc3990"}
- 表示appium server返回url为/wd/hub/session/be7c34f0-e12d-4368-9f80-c116e0cc3990/element/1/value,http状态码为200,内容为{"status":0,"value":true,"sessionId":"be7c34f0-e12d-4368-9f80-c116e0cc3990"}的数据包给client
复制代码 提出疑问通过分析,里面有几个让人在意的地方:
- 从client给server的请求中只有sessionId,id和value三个参数,而server加入到工作序列的参数除了命令名称外还增加了replace参数(那个帖子中还有unicodeKeyboard参数),增加的replace和unicodeKeyboard参数到底是以什么为根据来确定的
- bootstrap中获得的setText的value是first,为何实际在bootstrap输入的是中文first?
- 实际测试中,当appium server返回操作成功的信息时(bootstrap也显示输入了值),实际上此时应用中editText控件的内容为空,即清除内容后没有按照log所说输入中文first
带着这几个问题,我们开始分析及调试源码。
解决疑问所有命令都是先通过routing分析来源来调用对应的方法。因此先查routing:
- appium/lib/server/routing.js
- ...
- rest.post('/wd/hub/session/:sessionId?/element/:elementId?/value', controller.setValue);
- ...
- rest.post('/wd/hub/session/:sessionId?/appium/element/:elementId?/value', controller.setValueImmediate);
- rest.post('/wd/hub/session/:sessionId?/appium/element/:elementId?/replace_value', controller.replaceValue);
复制代码 找到三个和serValue相关的命令。通过url可以看出只有setValue方法是遵循webdriver api的,其它两个方法都是appium自己另外添加的。这里的controller方法的源码分别如下:
- appium/lib/server/controller.js
- ...
- exports.setValue = function (req, res) {
- var elementId = req.params.elementId
- // spec says value attribute is an array of strings;
- // let's turn it into one string
- , value = req.body.value.join('');
- req.device.setValue(elementId, value, getResponseHandler(req, res));
- };
- exports.replaceValue = function (req, res) {
- var elementId = req.params.elementId
- // spec says value attribute is an array of strings;
- // let's turn it into one string
- , value = req.body.value.join('');
- req.device.replaceValue(elementId, value, getResponseHandler(req, res));
- };
- ...
- exports.setValueImmediate = function (req, res) {
- var element = req.params.elementId
- , value = req.body.value;
- if (checkMissingParams(req, res, {element: element, value: value})) {
- req.device.setValueImmediate(element, value, getResponseHandler(req, res));
- }
- };
- ...
复制代码
|
|