目的
對appium-desktop腳本錄制功能進行二次開發,增加錄制ATK腳本功能。錄制樣式為
{ "preSteps": [ ----------前置條件為打開頁面PG或者啟動Driver(需要根據page參數判斷) ], "stepSets": [ [ ----------支持 點擊、輸入、滑動、坐標點擊、返回操作、校驗功能(新增校驗功能) ] ], "afterSteps": [ ] }
思路
新增ATK錄制腳本的JS-----准備腳本需要的參數----增加新功能---編寫腳本
實現步驟
1、增加AutoTestKit.js
新增js腳本用於實現錄制ATK腳本
1.1、實現方案
新增AutoTestKit.js腳本關聯到Framework,並設置為默認腳本
1.2、代碼修改
1.2.1、新增腳本
注意設置腳本最后面的readableName和export
AutoTestKit.readableName = 'Java - ATK'; export default AutoTestKit;
1.2.2、增加到frameworks
在lib/client-frameworks/index.js 常量中增加atk: AutoTestKit,注意要import AutoTestKit.
1.2.3、特殊處理AutoTestKit
AutoTestKit.js與其它語言錄制腳本格式不一致,不能采用默認的跳轉,需要重新設置。
首先需要在Inspector/RecordedActions.js中進行actionFramework參數傳遞。注意本次除了增加actionFramework參數外,還增加了sendElementName、page后面會用到
其次根據actionFramework路由到AutoTestKit.js
1.2.4、設置為默認
在reducers/Inspector.js中設置DEFAULT_FRAMEWORK='atk'
2、增加當前Activity值參數
腳本中需要根據activity值的不同設置前置條件
2.1、實現方案
在startRecording和RefreshSource操作中獲取當前頁面的activity, 並設置為action,如果是RegisterGuideActivity前置條件設置為啟動driver,否則設置為打開PG。
在ClearActions時把設置的activity重置為空
2.2、代碼修改
2.2.1、獲取activity
在Inspector/Inspector.js 修改refreshSource和startRecording的onClick 方法,在原來操作的基礎上增加設置當前頁面的activity到全部變量中。注意:refreshSource是先跟device交互再獲取activity.
atkStartRecording(){ this.props.startRecording(); this.getPage(); } atkRefreshSource(){ this.props.applyClientMethod({methodName: 'source'}); this.getPage(); } /** * 獲取當前頁面的activity * @returns {*} */ getPage() { let exec = require('child_process').execSync; let last = exec('adb shell dumpsys window windows | grep mCurrent'); let page=last.toString("utf8").trim(); this.props.setFieldValue("page",page.substring(page.lastIndexOf(".")+1,page.length-1)); }
2.2.2、傳遞到Framework
在Inspector/RecordedActions.js中進行page參數傳遞。
如1.2.3圖
2.2.3、設置前置條件
目前沒有辦法直接獲取當前頁面的PG,只能手動維護一個activity與PG的對應關系,后續如果出現新的activity,需要手動維護。RegisterGuideActivity 是啟動成功頁面。
2.2.4、clearActions
清楚actions時需要重置全局變量中page的值,否則不能獲取新頁面的值。
atkClearActions(){ this.props.clearRecording(); this.props.setFieldValue("page",""); }
3、增加assert校驗功能
自動化腳本需要assert校驗,現有錄制沒有該功能,需要新增。
3.1、實現方案
在Tap、Send Keys、Clear后面增加Assert功能,Assert只需要校驗定位的元素存在即可,不需要和device交互。
3.2、代碼修改
3.2.1、增加Assert按鍵
由於不需要和device交互,所以參數中不存在定位元素的屬性,需要手動把findDataSource參數傳遞下去。
actions/Inspector.js中增加atkAssertResult()方法
export function atkAssertResult (params) { return (dispatch) => { let args = []; args = args.concat(params.args || []); dispatch({type: RECORD_ACTION, action: params.methodName, params: args });----說明是錄屏操作 dispatch({type: METHOD_CALL_DONE});---方法調用結束標示 }; }
4、ATK腳本編寫
錄制為ATK腳本
4.1、實現方案
根據操作類型action對應不同的AW,action=findAndAssign 獲取定位元素的屬性(ID 、xpath);action=click、sendKeys、swipe、tap對應AWAppiumComponentInit;action=back對應AWKeyBoardAction;action=assert對應AWDriverActionAssertBatch
4.2、代碼實現
4.2.1、獲取元素的描述信息
獲取元素的text或者content-desc信息用於顯示腳本中remark
let note=""; if(selectedElement&&selectedElement.attributes.text!==""){ note=selectedElement.attributes.text; }else if(selectedElement&&selectedElement.attributes["content-desc"]!==""){ note=selectedElement.attributes["content-desc"]; }
因為一個腳本會有多個action,所以要把元素和action進行綁定。
修改reducers/Inspector.js,每個錄屏操作原來都有action、params參數,現在增加selectedElement
case RECORD_ACTION: return { ...state, recordedActions: [ ...state.recordedActions, {action: action.action, params: action.params,selectedElement:state.selectedElement} ] };
結果如圖:
4.2.2、switch-case
其中assert與back邏輯一致
switch(action){ case "findAndAssign": //取出type和value type=params[0]; value=params[1]; break; case "click": str+=` ["${note}", "${type}","${action}","${value}"]`; break; case "sendKeys": str+=` ["${note}", "${type}","${action}","${params[2]}", "${value}"]`; break; case "swipe": str+=` ["滑屏", "","${action}","${params[2]}","${params[3]}","${params[4]}","${params[5]}"]`; break; case "tap": str+=` ["坐標點擊", "","${action}","${params[2]}","${params[3]}"]`; break; case "back": let backAction="back",frontAction="back"; //獲取下一次操作 if(i!==actions.length-1){ backAction=actions[i+1].action; } //獲取上一步操作 if(i!==0){ frontAction=actions[i-1].action; } //如果返回操作是第一個操作的情況 if(i===0){ str=`{\n \"preSteps\": [\n${preStep} ],\n \"stepSets\": [\n [\n`; }else{ if("back"!==frontAction&&"assert"!==frontAction){ //上一步操作不是返回、校驗操作,去除最后一個逗號 str=str.substring(0,str.length-2); str+=`\n ]\n },\n`; } } str+=` {\n "className":"AWKeyBoardAction"\n }`; //如果back不是最后一個操作,則重新開始 if(i!==actions.length-1){ str+=`,\n`; if("back"!==backAction&&"assert"!==backAction){ //如果下一個操作不是返回,校驗操作,則添加 str+=` {\n "className": "AWAppiumComponentInit",\n "remark": "",\n "elementInfoList": [\n`; } } break;
5、增加元素名稱的輸入框--暫時不需要
由於ATK腳本中元素是以“app-page-element”的方式定位元素,所以需要用戶輸入page名稱和element名稱,並保存到數據庫中。
5.1、實現方案
在現有的Tap、Send Keys、Clear操作上增加元素輸入框操作,如圖
5.2、代碼修改
5.2.1、修改onClick方法
修改Inspector/SelectedElement.js 中tab、Tap、Send Keys、Clear的onClick方法,修改為通過在actions/Inspector.js中新增showSendElementNameModal()方法來控制彈出框, 該方法會把type設置為
SHOW_SEND_ELEMENT_NAME_MODAL, 在reducers/Inspector.js中中新增switch-case的一個分支,來設置sendElementNameModalVisible=true
export function showSendElementNameModal () { return (dispatch) => { dispatch({type: SHOW_SEND_ELEMENT_NAME_MODAL}); }; } case SHOW_SEND_ELEMENT_NAME_MODAL: return { ...state, sendElementNameModalVisible: true };
5.2.2、增加輸入框
新增輸入element的輸入框,當sendElementNameModalVisible=true時該輸入框顯示,點擊OK時,調用新增的handleSendElementName()方法,進行元素的獲取和這是輸入框隱藏
handleSendElementName () { const {sendElementName, applyClientMethod, hideSendElementNameModal, selectedElementId: elementId} = this.props; applyClientMethod({methodName: 'click', elementId}); hideSendElementNameModal();---同showSendElementNameModal()方法 }
6、保存元素名稱到數據庫-暫時不需要
表單提交元素名稱后,需要把元素名稱保存到數據庫中,
6.1、實現方案
使用react已經引入JQuery的網絡請求方案。通過POST方式把數據提交到數據庫中
6.2、代碼修改
Framework.js是各個語言錄制腳本(java.js、python.js、ruby.js等)的父類。所以在Framework.js中進行數據庫的統一添加
6.2.1、傳參修改
Framework.js 的getCodeString()方法 增加sendElementName參數。通過RecordedActions傳遞過來。代碼修改如下
如1.2.3圖
6.2.2、post請求
其中page、element、expectedvalue需要用戶填寫,其它值可以通過代碼獲取。
postJSON() { var obj = { "app":"guanjia", "element":'aaaa', "expectedimg":"bbbb","expectedvalue": "cccc","os": "Android","page": "eeee","type": "id","value": "rwa"};--測試數據 const req = new XMLHttpRequest() req.open('POST', 'http://10.247.39.5:7070/wisper/api/v1/atk/appium/elementinfo') req.setRequestHeader('Content-Type', 'application/json;charset=UTF-8') req.send(JSON.stringify(obj)) }