From 899aa44fc1209b1c5a032d34a39dd3e8405eb247 Mon Sep 17 00:00:00 2001 From: Yanis Wang Date: Mon, 6 Mar 2017 15:18:50 +0800 Subject: [PATCH] 1. Fix: fix continue record issue when json file is missing 2. Remove: remove runtime 3. Update: support new version of macaca 4. Add: support new feature for mobile record: sleep, text, back, alert, expect, end 5. Add: support ios real device 6. Add: support download app file from url 7. Add: support continue record for mobile --- CHANGE.md | 10 + README.md | 11 +- README_zh-cn.md | 10 +- README_zh-tw.md | 10 +- chrome-extension/img/alert.png | Bin 0 -> 1446 bytes chrome-extension/img/back.png | Bin 0 -> 1128 bytes chrome-extension/img/end.png | Bin 2883 -> 7508 bytes chrome-extension/img/text.png | Bin 0 -> 934 bytes chrome-extension/js/foreground.js | 23 +- chrome-extension/js/mobile.js | 328 ++++++++++++++++++++++++--- chrome-extension/manifest.json | 5 +- chrome-extension/mobile.html | 119 +++++++++- i18n/en.js | 22 +- i18n/zh-cn.js | 22 +- i18n/zh-tw.js | 22 +- lib/init.js | 6 +- lib/start.js | 353 +++++++++++++++++++++++------- package.json | 4 +- project/package.json | 2 +- template/jwebdriver-mobile.js | 23 +- template/jwebdriver.js | 5 +- tool/uirecorder.crx | Bin 368525 -> 379710 bytes 22 files changed, 788 insertions(+), 187 deletions(-) create mode 100644 chrome-extension/img/alert.png create mode 100644 chrome-extension/img/back.png create mode 100644 chrome-extension/img/text.png diff --git a/CHANGE.md b/CHANGE.md index cca9398..8f60086 100644 --- a/CHANGE.md +++ b/CHANGE.md @@ -1,6 +1,16 @@ UI Recorder change log ==================== +## ver 2.4.0 (2017-3-6) + +1. Fix: fix continue record issue when json file is missing +2. Remove: remove runtime +3. Update: support new version of macaca +4. Add: support new feature for mobile record: sleep, text, back, alert, expect, end +5. Add: support ios real device +6. Add: support download app file from url +7. Add: support continue record for mobile + ## ver 2.3.32 (2017-2-17) 1. Fix: fix continue record issue diff --git a/README.md b/README.md index b2d346a..f4924ce 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,7 @@ Features 10. Support screenshots after each step 11. Support HTML report & JUnit report 12. Support systems: windows, mac, linux -13. Support mutli runtime test, such as: devtest, pretest -14. Test file base on NodeJs: [jWebDriver](http://jwebdriver.com/) +13. Test file base on NodeJs: [jWebDriver](http://jwebdriver.com/) Screenshots ================ @@ -178,14 +177,6 @@ How to dock Jenkins? > [HTML](https://wiki.jenkins-ci.org/display/JENKINS/HTML+Publisher+Plugin): `reports/index.html` -How to switch runtime? ----------------- - -1. `export runtime=dev` ( Linux|Mac ) or `set runtime=dev` ( Window ) -2. `uirecorder init` (saved to `config-dev.json`, `hosts-dev`) -3. `uirecorder start` (read from `config-dev.json`, `hosts-dev`) -4. `source run.sh` or `run.bat` (read from `config-dev.json`, `hosts-dev`) - How to filter unstable path ---------------- diff --git a/README_zh-cn.md b/README_zh-cn.md index c3255ea..17b7454 100644 --- a/README_zh-cn.md +++ b/README_zh-cn.md @@ -18,7 +18,7 @@ UI Recorder 非常简单易用. 2. 语言切换: [English](https://github.com/alibaba/uirecorder/blob/master/README.md), [简体中文](https://github.com/alibaba/uirecorder/blob/master/README_zh-cn.md), [繁體中文](https://github.com/alibaba/uirecorder/blob/master/README_zh-tw.md) 3. 变更日志: [CHANGE](https://github.com/alibaba/uirecorder/blob/master/CHANGE.md) 4. 视频教程:[PC中文教程](http://v.youku.com/v_show/id_XMTY4NTk5NjI4MA==.html) -5. QQ交流群:416221937(加入验证:UIRecorder录制) +5. 钉钉交流群:11779932(加入验证:UIRecorder录制),下载钉钉:[https://www.dingtalk.com/](https://www.dingtalk.com/) 功能 ================ @@ -187,14 +187,6 @@ QA nvm install v6.9.5 npm install --registry=https://registry.npm.taobao.org -如何切换runtime运行时环境? ----------------- - -1. `export runtime=dev` ( Linux|Mac ) 或者 `set runtime=dev` ( Window ) -2. `uirecorder init` (保存到`config-dev.json`, `hosts-dev`) -3. `uirecorder start` (从`config-dev.json`, `hosts-dev`读取) -4. `source run.sh` 或者 `run.bat` (从`config-dev.json`, `hosts-dev`读取) - 如何过滤不稳定的PATH路径? ---------------- diff --git a/README_zh-tw.md b/README_zh-tw.md index 6e8efc0..8d4cc71 100644 --- a/README_zh-tw.md +++ b/README_zh-tw.md @@ -18,7 +18,7 @@ UI Recorder 非常簡單易用. 2. 語言切換: [English](https://github.com/alibaba/uirecorder/blob/master/README.md), [簡體中文](https://github.com/alibaba/uirecorder/blob/master/README_zh-cn.md), [繁體中文](https://github.com/alibaba/uirecorder/blob/master/README_zh-tw.md) 3. 變更日誌: [CHANGE](https://github.com/alibaba/uirecorder/blob/master/CHANGE.md) 4. 視頻教程:[PC中文教程](http://v.youku.com/v_show/id_XMTY4NTk5NjI4MA==.html) -5. QQ交流群:416221937(加入驗證:UIRecorder錄制) +5. 釘釘交流群:11779932(加入驗證:UIRecorder錄制),下載釘釘:[https://www.dingtalk.com/](https://www.dingtalk.com/) 功能 ================ @@ -187,14 +187,6 @@ QA nvm install v6.9.5 npm install --registry=https://registry.npm.taobao.org -如何切換runtime運行時環境? ----------------- - -1. `export runtime=dev` ( Linux|Mac ) 或者 `set runtime=dev` ( Window ) -2. `uirecorder init` (保存到`config-dev.json`, `hosts-dev`) -3. `uirecorder start` (從`config-dev.json`, `hosts-dev`讀取) -4. `source run.sh` 或者 `run.bat` (從`config-dev.json`, `hosts-dev`讀取) - 如何過濾不穩定的PATH路徑? ---------------- diff --git a/chrome-extension/img/alert.png b/chrome-extension/img/alert.png new file mode 100644 index 0000000000000000000000000000000000000000..6c76cfc5a15770f31b589943a22ce52f720461b0 GIT binary patch literal 1446 zcmV;X1zGxuP)j}9bgH}Ersf{>jZGPAkI#BwMKxqITo^8I{%c1l&#H}iSk zkpO}x0Q~u#9RW^{QDR;aB_Q$Bz`*j)K;jw5iBFn2vCsc?j2yE4Y^g*k=f?u~CMDi2 ztPDvazbBJ|#3YdT6iB!j*C!|E6P9)3Y`ILCR~~u{8W046*PwTlAs?`VR6&U)P~wiJ zo$r_BjNBRvpjTh0R48T4jhuD?@1L6^%IK*3tUr20s`u{g8)E|j1UBH#z%K>{3X|t5 zm8nHVc{8o+S6l)r=TVulp1`duap3}qojd0~7jNGt`r@MdY&5|g@Fj!vEj7^8J&fU; zX-4FBJwTnqz@V~Ibm|$0bJ6gsLX;?bE9!$uDPf?F6V`28k%>ocsp%ky0-{0~Bi{M= z{7<7n8t^PL=9>?^-F%o0CVL$ohJdrX3)9;mjY2_0j;e%w=5sFr&%Aw0a3}(so2~*J zB~PcHko<{~Y?6@u2p}kv<=_N%b_8h-lB3z0wwG+Zno_eOd=(G`a4iFrI4U5(ZCfmb zT`Up`HQx2J+f;yg0&FXQ_1|>hD$q#@a8%9Z$RK1oj&*>DQ!cv-cJEExpMX#x00DGR zgD)CNo|&KD`wdi(f`GS{fO8)Ls6Z&xCeQ&~2XGE+t)x<9_|hf!UGDN_;*?4~+cMs% zXIm$GI#&${L|TpLU?Ms&v5w#JX2X%9QKFtY)!tb2^+D6G1tMT#(sEu01jkL=r&ZMe z$FEJq76%5N8fujm7;D-#>gQur^jwM$d^$wP4cy-X}2<&D`m_2zK^k0SAnre z#966Ue_hvgYd}$ECKCyvgi)`ztK_H#f#jiCeYL%f;O_(Yj}oP85K`SGumV!cjOfMRVK#-DY^c`Q@94jt4N zJn-Rk6e2HKmQu0p2s96c_dcsQFu;UTt3j!mHs>7=r~d*qXL?J#eX$P!001R)MObuX zVRU6WV{&C-bY%cCFflMKFfuJMF;p-yIx#XjGdC?THaajchxAZ50000bbVXQnWMOn= zI&E)cX=Zr#!H99jnIyEpYFg7|cFqQ!|>;M1&07*qoM6N<$f`5mL AXaE2J literal 0 HcmV?d00001 diff --git a/chrome-extension/img/back.png b/chrome-extension/img/back.png new file mode 100644 index 0000000000000000000000000000000000000000..15d234d948588398ec678a93c9922383600452e5 GIT binary patch literal 1128 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 e>mzt~;rc&AYj}=HvVXY%0000 delta 2877 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 IM6N<$f@T=ZW&i*H literal 0 HcmV?d00001 diff --git a/chrome-extension/js/foreground.js b/chrome-extension/js/foreground.js index 8b99881..48b994d 100644 --- a/chrome-extension/js/foreground.js +++ b/chrome-extension/js/foreground.js @@ -1734,6 +1734,7 @@ }); // 对话框 var divDomDialog = document.createElement("div"); + var isShowDialog = false; var okCallback = null; var cancelCallback = null; divDomDialog.id = 'uirecorder-dialog'; @@ -1766,6 +1767,25 @@ break; } }); + divDomDialog.addEventListener('keyup', function(event){ + var keyCode = event.keyCode; + switch(keyCode){ + case 13: + okCallback(); + break; + } + }); + document.addEventListener('keyup', function(event){ + var keyCode = event.keyCode; + switch(keyCode){ + case 27: + if(isShowDialog){ + hideDialog(); + cancelCallback && cancelCallback(); + } + break; + } + }, true); // 显示对话框 function showDialog(title, content, events){ domDialogTitle.innerHTML = title; @@ -1773,6 +1793,7 @@ okCallback = events.onOk; cancelCallback = events.onCancel; divDomDialog.style.display = 'block'; + isShowDialog = true; var onInit = events.onInit; if(onInit){ onInit(); @@ -1788,6 +1809,7 @@ domDialogTitle.innerHTML = ''; domDialogContent.innerHTML = ''; divDomDialog.style.display = 'none'; + isShowDialog = false; } function showExpectDailog(expectTarget, callback){ var arrHtmls = [ @@ -2137,7 +2159,6 @@ domSleepTime.focus(); alert('dialog_sleep_time_tip'); } - }, onCancel: function(){ setGlobalWorkMode('record'); diff --git a/chrome-extension/js/mobile.js b/chrome-extension/js/mobile.js index 7722d4a..f9fb55d 100644 --- a/chrome-extension/js/mobile.js +++ b/chrome-extension/js/mobile.js @@ -1,4 +1,17 @@ (function(){ + // i18n + var i18n = {}; + var __ = function(str){ + var args = arguments; + str = i18n[str] || str; + var count = 0; + str = str.replace(/%s/g, function(){ + count ++; + return args[count] || ''; + }); + return str; + }; + // 全局事件 var mapGlobalEvents = {}; var eventPort = chrome.extension.connect(); @@ -25,6 +38,251 @@ GlobalEvents._emit(msg.type, msg.data); }); + var mobilePlatform = 'Android'; + + // load config + chrome.runtime.sendMessage({ + type: 'getConfig' + }, function(config){ + if(config.testVars){ + testVars = config.testVars; + } + mobilePlatform = config.mobilePlatform; + i18n = config.i18n; + initRecorder(); + }); + + var isSelectorMode = false; + var isShowDialog = false; + var baseUrl = chrome.extension.getURL("/"); + var divToolsPannel = document.getElementById('toolPannel'); + var divDomDialog = document.getElementById('uirecorder-dialog'); + var divDomDialogMask = document.getElementById('dialogMask'); + var domDialogTitle = document.getElementById('uirecorder-dialog-title'); + var domDialogContent = document.getElementById('uirecorder-dialog-content'); + var divDialogBottom = document.getElementById('dialogBottom'); + + function initRecorder(){ + // init tool pannel + var arrToolsHtml = [ + ''+__('button_sleep_text')+'', + ''+__('button_text_text')+'', + (mobilePlatform === 'Android'?''+__('button_back_text')+'':''), + ''+__('button_alert_text')+'', + ''+__('button_expect_text')+'', + ''+__('button_end_text')+'' + ]; + divToolsPannel.innerHTML = arrToolsHtml.join(''); + divDialogBottom.innerHTML = ''+__('dialog_ok')+''+__('dialog_cancel')+''; + } + + divToolsPannel.addEventListener('click', function(event){ + var target = event.target; + if(target.tagName === 'IMG'){ + target = target.parentNode; + } + isSelectorMode = false; + var name = target.name; + switch(name){ + case 'sleep': + showSleepDailog(); + break; + case 'text': + showTextDailog(); + break; + case 'back': + saveCommand('back'); + break; + case 'alert': + showAlertDailog(); + break; + case 'expect': + isSelectorMode = true; + break; + case 'end': + chrome.runtime.sendMessage({ + type: 'end' + }); + break; + } + }); + + // 对话框 + var okCallback = null; + var cancelCallback = null; + divDomDialog.addEventListener('click', function(event){ + event.stopPropagation(); + event.preventDefault(); + var target = event.target; + if(target.tagName === 'IMG'){ + target = target.parentNode; + } + var name = target.name; + switch(name){ + case 'uirecorder-ok': + okCallback(); + break; + case 'uirecorder-cancel': + hideDialog(); + cancelCallback && cancelCallback(); + break; + } + }); + divDomDialog.addEventListener('keyup', function(event){ + var keyCode = event.keyCode; + switch(keyCode){ + case 13: + okCallback(); + break; + } + }); + document.addEventListener('keyup', function(event){ + var keyCode = event.keyCode; + switch(keyCode){ + case 27: + if(isShowDialog){ + hideDialog(); + cancelCallback && cancelCallback(); + } + isSelectorMode = false; + break; + } + }); + // 显示对话框 + function showDialog(title, content, events){ + domDialogTitle.innerHTML = title; + domDialogContent.innerHTML = content; + okCallback = events.onOk; + cancelCallback = events.onCancel; + divDomDialog.style.display = 'block'; + divDomDialogMask.style.display = 'block'; + isShowDialog = true; + var onInit = events.onInit; + if(onInit){ + onInit(); + } + setDialogCenter(); + } + function setDialogCenter(){ + divDomDialog.style.marginTop = '-'+(divDomDialog.offsetHeight/2)+'px'; + divDomDialog.style.marginLeft = '-'+(divDomDialog.offsetWidth/2)+'px'; + } + // 隐藏对话框 + function hideDialog(){ + domDialogTitle.innerHTML = ''; + domDialogContent.innerHTML = ''; + divDomDialog.style.display = 'none'; + divDomDialogMask.style.display = 'none'; + isShowDialog = false; + isSelectorMode = false; + } + + // 延迟对话框 + function showSleepDailog(){ + var strHtml = '
  • ms
'; + var domSleepTime; + showDialog(__('dialog_sleep_title'), strHtml, { + onInit: function(){ + domSleepTime = document.getElementById('uirecorder-sleeptime'); + domSleepTime.select(); + domSleepTime.focus(); + }, + onOk: function(){ + var time = domSleepTime.value; + if(/\d+/.test(time)){ + saveCommand('sleep', time); + hideDialog(); + } + else{ + domSleepTime.focus(); + alert(__('dialog_sleep_time_tip')); + } + } + }); + } + + // 文字对话框 + function showTextDailog(){ + var strHtml = '
'; + var domTextContent; + showDialog(__('dialog_text_title'), strHtml, { + onInit: function(){ + domTextContent = document.getElementById('textContent'); + domTextContent.select(); + domTextContent.focus(); + }, + onOk: function(){ + var text = domTextContent.value; + if(text){ + saveCommand('sendKeys', text); + hideDialog(); + } + else{ + domTextContent.focus(); + alert(__('dialog_text_content_tip')); + } + } + }); + } + + // 文字对话框 + function showAlertDailog(){ + var strHtml = '
'; + var domAlertOption; + showDialog(__('dialog_alert_title'), strHtml, { + onInit: function(){ + domAlertOption = document.getElementById('alertOption'); + }, + onOk: function(){ + var alertOption = domAlertOption.value; + saveCommand(alertOption+'Alert'); + hideDialog(); + } + }); + } + + // 断言对话框 + function showExpectDailog(path){ + var arrHtmls = [ + '
    ', + '
  • ms
  • ', + '
  • ', + '
  • ', + '
  • ', + '
  • ', + '
' + ]; + var domExpectSleep, domExpectType, domExpectPath, domExpectCompare, domExpectTo; + showDialog(__('dialog_expect_title'), arrHtmls.join(''), { + onInit: function(){ + domExpectSleep = document.getElementById('expectSleep'); + domExpectType = document.getElementById('expectType'); + domExpectPath = document.getElementById('expectPath'); + domExpectCompare = document.getElementById('expectCompare'); + domExpectTo = document.getElementById('expectTo'); + + domExpectSleep.value = '300'; + domExpectPath.value = path; + domExpectTo.focus(); + }, + onOk: function(){ + var sleep = domExpectSleep.value; + var type = domExpectType.value; + var path = domExpectPath.value; + var compare = domExpectCompare.value; + var to = domExpectTo.value; + saveCommand('expect', { + sleep: sleep, + type: type, + path: path, + compare: compare, + to: to + }); + hideDialog(); + } + }); + } + // get event var appSource = null; var appTree = null; @@ -39,8 +297,12 @@ cmd: cmd, data: data }; - checkResult = false; - showLoading(); + checkResult = null; + setTimeout(function(){ + if(checkResult === null){ + showLoading(); + } + }, 500); hideLine(); chrome.runtime.sendMessage({ type: 'command', @@ -180,7 +442,6 @@ var bottomLine = document.getElementById('bottomLine'); var leftLine = document.getElementById('leftLine'); var rightLine = document.getElementById('rightLine'); - var keyinput = document.getElementById('keyinput'); function showLoading(){ loadingContainer.style.display = 'block'; } @@ -191,18 +452,22 @@ topLine.style.left = left+'px'; topLine.style.top = top+'px'; topLine.style.width = width+'px'; + topLine.style.background = isSelectorMode ? 'green' : 'red'; bottomLine.style.left = left+'px'; bottomLine.style.top = top+height+'px'; bottomLine.style.width = width+'px'; + bottomLine.style.background = isSelectorMode ? 'green' : 'red'; leftLine.style.top = top+'px'; leftLine.style.left = left+'px'; leftLine.style.height = height+'px'; + leftLine.style.background = isSelectorMode ? 'green' : 'red'; rightLine.style.top = top+'px'; rightLine.style.left = left+width+'px'; rightLine.style.height = height+'px'; + rightLine.style.background = isSelectorMode ? 'green' : 'red'; } function hideLine(){ topLine.style.left = '-9999px'; @@ -219,6 +484,8 @@ appHeight = screenshot.naturalHeight; imgWidth = screenshot.width; imgHeight = screenshot.height; + divToolsPannel.style.left = screenshot.offsetLeft + imgWidth + 20 + 'px'; + divToolsPannel.style.display = 'block'; scaleX = appWidth / imgWidth; scaleY = appHeight / imgHeight; if(appSource.tree){ @@ -226,7 +493,7 @@ scaleX /= rate; scaleY /= rate; } - checkResult && hideLoading(); + checkResult !== null && hideLoading(); }); GlobalEvents.on('checkResult', function(data){ checkResult = data.success || false; @@ -234,19 +501,19 @@ var downX = -9999, downY = -9999, downTime = 0; screenshot.addEventListener('click', function(event){ var upX = event.offsetX, upY = event.offsetY; + var clickDuration = new Date().getTime() - downTime; if(Math.abs(downX - upX) < 20 && Math.abs(downY - upY) < 20){ var cmdData = getNodeInfo(Math.floor(upX * scaleX), Math.floor(upY * scaleY)); - saveCommand('click', cmdData); - if(cmdData.path){ - keyinput.style.display = 'block'; - keyinput.focus(); + if(isSelectorMode){ + if(cmdData.path){ + showExpectDailog(cmdData.path); + } } else{ - keyinput.style.display = 'none'; + saveCommand(clickDuration>500?'press':'click', cmdData); } + downTime = 0; } - event.stopPropagation(); - event.preventDefault(); }); screenshot.addEventListener('mousedown', function(event){ downX = event.offsetX; @@ -257,18 +524,33 @@ }); screenshot.addEventListener('mouseup', function(event){ var upX = event.offsetX, upY = event.offsetY; - if(Math.abs(downX - upX) >= 20 || Math.abs(downY - upY) >= 20){ - saveCommand('swipe', { - startX: Math.floor(downX * scaleX), - startY: Math.floor(downY * scaleY), - endX: Math.floor(upX * scaleX), - endY: Math.floor(upY * scaleY), - duration: 20 + if(downX >=0 && downY >= 0 && + upX >= 0 && upY >= 0 && + (Math.abs(downX - upX) >= 20 || Math.abs(downY - upY) >= 20)){ + saveCommand('drag', { + fromX: Math.floor(downX * scaleX), + fromY: Math.floor(downY * scaleY), + toX: Math.floor(upX * scaleX), + toY: Math.floor(upY * scaleY) }); + downTime = 0; } event.stopPropagation(); event.preventDefault(); }); + document.addEventListener('mouseup', function(event){ + if(downTime !== 0){ + var upX = event.clientX < screenshot.offsetLeft ? 0 : screenshot.width -1; + var upY = event.clientY; + saveCommand('drag', { + fromX: Math.floor(downX * scaleX), + fromY: Math.floor(downY * scaleY), + toX: Math.floor(upX * scaleX), + toY: Math.floor(upY * scaleY) + }); + downTime = 0; + } + }); screenshot.addEventListener('mousemove', function(event){ var bestNodeInfo = { node: null, @@ -293,16 +575,6 @@ hideLine(); } }); - keyinput.addEventListener('keydown', function(event){ - if(event.keyCode === 13){ - var self = this; - saveCommand('val', { - keys: this.value - }); - self.value = ''; - self.style.display = 'none'; - } - }); // 计算字节长度,中文两个字节 function byteLen(text){ diff --git a/chrome-extension/manifest.json b/chrome-extension/manifest.json index 0fa7393..91b9c48 100644 --- a/chrome-extension/manifest.json +++ b/chrome-extension/manifest.json @@ -25,7 +25,10 @@ "img/cancel.png", "img/vars.png", "img/success.png", - "img/fail.png" + "img/fail.png", + "img/text.png", + "img/alert.png", + "img/back.png" ], "background": { "scripts": [ "js/background.js" ] diff --git a/chrome-extension/mobile.html b/chrome-extension/mobile.html index a46fc1f..c6d4b9a 100644 --- a/chrome-extension/mobile.html +++ b/chrome-extension/mobile.html @@ -12,6 +12,9 @@ height: 100%; overflow-y: hidden; } +li{ + list-style:none; +} #screenContainer{ position:absolute; width: 100%; @@ -19,15 +22,11 @@ text-align: center; } -#keyinput{ - position:absolute; - left: 5px; - top: 5px; -} #screenshot{ width: auto; height: 100%; cursor: pointer; + box-shadow: 5px 5px 10px #888888; } #loadingContainer{ position:absolute; @@ -52,6 +51,108 @@ height: 1px; z-index: 999; } +#toolPannel{ + display: none; + position:fixed; + padding:10px 20px; + box-sizing:border-box; + border:1px solid #ccc; + line-height:1; + box-shadow: 5px 5px 10px #888888; + top:10px; + left:10px; +} +#toolPannel span{ + display: block; + margin-bottom: 12px; +} +.uirecorder-button { + cursor: pointer; + margin: 5px; +} +.uirecorder-button a { + text-decoration: none; + color: #333333; + font-family: arial, sans-serif; + font-size: 12px; + color: #777; + text-shadow: 1px 1px 0px white; + background: -webkit-linear-gradient(top, #ffffff 0%, #dfdfdf 100%); + border-radius: 3px; + box-shadow: 0 1px 3px 0px rgba(0, 0, 0, 0.4); + padding: 5px 7px; +} +.uirecorder-button a:hover { + background: -webkit-linear-gradient(top, #ffffff 0%, #eee 100%); + box-shadow: 0 1px 3px 0px rgba(0, 0, 0, 0.4); +} +.uirecorder-button a:active { + background: -webkit-linear-gradient(top, #dfdfdf 0%, #f1f1f1 100%); + box-shadow: 0px 1px 1px 1px rgba(0, 0, 0, 0.2) inset, 0px 1px 1px 0 rgba(255, 255, 255, 1); +} +.uirecorder-button a img { + display: inline-block; + padding-right: 8px; + position: relative; + top: 2px; + vertical-align: baseline; + width: auto; + height: auto; +} + +#uirecorder-dialog { + display: none; + position: fixed; + z-index: 2147483647; + padding: 20px; + top: 50%; + left: 50%; + width: 480px; + margin-left: -240px; + margin-top: -160px; + box-sizing: border-box; + border: 1px solid #ccc; + background: rgba(241, 241, 241, 1); + box-shadow: 5px 5px 10px #888888; +} +#uirecorder-dialog h2 { + padding-bottom: 10px; + border-bottom: solid 1px #ccc; + margin-bottom: 10px; + color: #333; +} +#uirecorder-dialog ul { + list-style: none; + padding: 0; +} +#uirecorder-dialog li { + padding: 5px 0 5px 30px; + margin: 0; +} +#uirecorder-dialog li label { + display: inline-block; + width: 100px; + color: #666 +} +#uirecorder-dialog li input, #uirecorder-dialog li select, #uirecorder-dialog li textarea { + display: inline-block; + font-size: 16px; + border: 1px solid #ccc; + border-radius: 2px; + padding: 5px; +} +#uirecorder-dialog li input, #uirecorder-dialog li textarea { + width: 250px; +} +#dialogMask{ + display: none; + position:absolute; + width:100%; + height:100%; + z-index:99; + background: black; + opacity: 0.1; +} @@ -61,11 +162,17 @@
+
+
+

+
+
+
+
- diff --git a/i18n/en.js b/i18n/en.js index 598ca87..3cc3e70 100644 --- a/i18n/en.js +++ b/i18n/en.js @@ -7,15 +7,14 @@ "file_saved": "file saved", "file_created": "file created", "dir_created": "directory created", - "config_file": "Config file", - "hosts_file": "Hosts file", "json_parse_failed": "json parse failed!", "file_missed": "file search failed, please init first!", - "input_spec_name": "Test spec file name", + "input_spec_name": "Test spec file name:", "continue_to_record": "File existed, load and continue to record?", - "mobile_app_path": "App Path, extensions: apk, app", + "mobile_app_path": "App Path (extensions: apk, app, zip):", + "mobile_platform": "App platform:", "open_checker_browser": "Open checker browser?", - "browser_size": "Browser size, example: 1024 x 768", + "browser_size": "Browser size (example: 1024 x 768):", "dom_path_config": "Dom path config, extend: id, name, class", "attr_black_list": "Black list RegExp for attribute value", "hide_before_expect": "Hide before expect", @@ -64,12 +63,16 @@ "button_vars_text": "Use Var", "button_jump_text": "Jump to", "button_end_text": "End Record", + "button_text_text": "Input Text", + "button_alert_text": "Alert Cmd", + "button_back_text": "Back Button", "dialog_ok": "Ok", "dialog_cancel": "Cancel", "dialog_expect_title": "Add Expect:", "dialog_expect_sleep": "Delay Time: ", "dialog_expect_type": "Expect Type: ", "dialog_expect_dom": "Expect DOM: ", + "dialog_expect_path": "Expect Path: ", "dialog_expect_param": "Expect param: ", "dialog_expect_compare": "Expect compare: ", "dialog_expect_to": "Expect to: ", @@ -94,6 +97,13 @@ "dialog_jump_target": "Jump target: ", "dialog_sleep_title": "Add sleep: ", "dialog_sleep_time": "Sleep time: ", - "dialog_sleep_time_tip": "Please input sleep time" + "dialog_sleep_time_tip": "Please input sleep time", + "dialog_text_title": "Input Text: ", + "dialog_text_content": "Text Content: ", + "dialog_text_content_tip": "Please input text content", + "dialog_alert_title": "Alert Cmd: ", + "dialog_alert_option": "Alert option: ", + "dialog_alert_option_accpet": "Accpet", + "dialog_alert_option_dismiss": "Dismiss" } } diff --git a/i18n/zh-cn.js b/i18n/zh-cn.js index 9ce25e3..846b401 100644 --- a/i18n/zh-cn.js +++ b/i18n/zh-cn.js @@ -7,15 +7,14 @@ "file_saved": "文件保存成功", "file_created": "文件创建成功", "dir_created": "文件夹创建成功", - "config_file": "配置文件", - "hosts_file": "hosts文件", "json_parse_failed": "JSON解析失败!", "file_missed": "文件查找失败,请先初始化!", - "input_spec_name": "测试脚本文件名", + "input_spec_name": "测试脚本文件名:", "continue_to_record": "文件已存在,加载并继续录制吗?", - "mobile_app_path": "App路径, 扩展名: apk, app", + "mobile_app_path": "App路径 (扩展名: apk, app, zip):", + "mobile_platform": "App平台:", "open_checker_browser": "打开同步校验浏览器?", - "browser_size": "浏览器大小,格式:1024 x 768", + "browser_size": "浏览器大小 (格式:1024 x 768):", "dom_path_config": "Path扩展属性配置,除id,name,class之外", "attr_black_list": "属性值黑名单正则", "hide_before_expect": "断言前隐藏", @@ -64,12 +63,16 @@ "button_vars_text": "使用变量", "button_jump_text": "脚本跳转", "button_end_text": "结束录制", + "button_text_text": "输入文字", + "button_alert_text": "Alert命令", + "button_back_text": "后退按键", "dialog_ok": "确认", "dialog_cancel": "取消", "dialog_expect_title": "添加断言: ", "dialog_expect_sleep": "延迟时间: ", "dialog_expect_type": "断言类型: ", "dialog_expect_dom": "断言DOM: ", + "dialog_expect_path": "断言Path: ", "dialog_expect_param": "断言参数: ", "dialog_expect_compare": "比较方式: ", "dialog_expect_to": "断言结果: ", @@ -94,6 +97,13 @@ "dialog_jump_target": "跳转目标: ", "dialog_sleep_title": "添加延迟: ", "dialog_sleep_time": "延迟时间: ", - "dialog_sleep_time_tip": "请输入延迟时间,单位毫秒" + "dialog_sleep_time_tip": "请输入延迟时间,单位毫秒", + "dialog_text_title": "输入文字: ", + "dialog_text_content": "文字内容: ", + "dialog_text_content_tip": "请输入文字", + "dialog_alert_title": "Alert命令: ", + "dialog_alert_option": "Alert选项: ", + "dialog_alert_option_accpet": "接受", + "dialog_alert_option_dismiss": "拒绝" } } diff --git a/i18n/zh-tw.js b/i18n/zh-tw.js index b6539a8..c0fb6c1 100644 --- a/i18n/zh-tw.js +++ b/i18n/zh-tw.js @@ -7,15 +7,14 @@ "file_saved": "文件保存成功", "file_created": "文件創建成功", "dir_created": "文件夾創建成功", - "config_file": "配置文件", - "hosts_file": "hosts文件", "json_parse_failed": "JSON解析失敗!", "file_missed": "文件查找失敗,請先初始化!", - "input_spec_name": "測試腳本文件名", + "input_spec_name": "測試腳本文件名:", "continue_to_record": "文件已存在,加載並繼續錄制嗎?", - "mobile_app_path": "App路徑, 擴展名: apk, app", + "mobile_app_path": "App路徑 (擴展名: apk, app, zip):", + "mobile_platform": "App平台:", "open_checker_browser": "打開同步校驗瀏覽器?", - "browser_size": "瀏覽器大小,格式:1024 x 768", + "browser_size": "瀏覽器大小 (格式:1024 x 768):", "dom_path_config": "Path擴展屬性配置,除id,name,class之外", "attr_black_list": "屬性值黑名單正則", "hide_before_expect": "斷言前隱藏", @@ -64,12 +63,16 @@ "button_vars_text": "使用變量", "button_jump_text": "腳本跳轉", "button_end_text": "結束錄制", + "button_text_text": "輸入文字", + "button_alert_text": "Alert命令", + "button_back_text": "後退按鍵", "dialog_ok": "確認", "dialog_cancel": "取消", "dialog_expect_title": "添加斷言: ", "dialog_expect_sleep": "延遲時間: ", "dialog_expect_type": "斷言類型: ", "dialog_expect_dom": "斷言DOM: ", + "dialog_expect_path": "斷言Path: ", "dialog_expect_param": "斷言參數: ", "dialog_expect_compare": "比較方式: ", "dialog_expect_to": "斷言結果: ", @@ -94,6 +97,13 @@ "dialog_jump_target": "跳轉目標: ", "dialog_sleep_title": "添加延遲: ", "dialog_sleep_time": "延遲時間: ", - "dialog_sleep_time_tip": "請輸入延遲時間,單位毫秒" + "dialog_sleep_time_tip": "請輸入延遲時間,單位毫秒", + "dialog_text_title": "輸入文字: ", + "dialog_text_content": "文字內容: ", + "dialog_text_content_tip": "請輸入文字", + "dialog_alert_title": "Alert命令: ", + "dialog_alert_option": "Alert選項: ", + "dialog_alert_option_accpet": "接受", + "dialog_alert_option_dismiss": "拒絕" } } diff --git a/lib/init.js b/lib/init.js index a0e89e5..0f952cf 100644 --- a/lib/init.js +++ b/lib/init.js @@ -11,9 +11,7 @@ function initConfig(options){ if(locale){ i18n.setLocale(locale); } - var runtime = process.env['runtime'] || ''; - var configPath = runtime?'config-'+runtime+'.json':'config.json'; - console.log('? '.green+__('config_file').bold+' ' + configPath.cyan+''); + var configPath = 'config.json'; var configFile = path.resolve(configPath); var config = {}; if(fs.existsSync(configFile)){ @@ -158,7 +156,7 @@ function initConfig(options){ }); } else{ - var hostsPath = 'hosts'+(runtime?'-'+runtime:''); + var hostsPath = 'hosts'; initProject({ 'package.json':'', 'README.md':'', diff --git a/lib/start.js b/lib/start.js index 244f373..fca936d 100644 --- a/lib/start.js +++ b/lib/start.js @@ -41,9 +41,7 @@ function startRecorder(options){ if(locale){ i18n.setLocale(locale); } - var runtime = process.env['runtime'] || ''; - var configPath = 'config'+(runtime ? '-' + runtime : '') + '.json'; - console.log('? '.green+__('config_file').bold+' ' + configPath.cyan+''); + var configPath = 'config.json'; var configFile = path.resolve(rootPath + '/' + configPath); var configJson = {}; if(fs.existsSync(configFile)){ @@ -87,13 +85,10 @@ function startRecorder(options){ var hideBeforeExpect = recorderConfig.hideBeforeExpect; var testVars = configJson.vars; var isConfigEdited = false; - var hostsPath = 'hosts' + (runtime ? '-'+runtime : ''); + var hostsPath = 'hosts'; var hostsFile = path.resolve(rootPath + '/' + hostsPath); var hosts = ''; if(fs.existsSync(hostsFile)){ - if(!mobile){ - console.log('? '.green+__('hosts_file').bold+' ' + hostsPath.cyan+''); - } hosts = fs.readFileSync(hostsFile).toString(); } // read spec list @@ -128,6 +123,20 @@ function startRecorder(options){ return input !== '' && /\.js$/.test(input); } }, + { + 'type': 'confirm', + 'name': 'continueRecord', + 'message': __('continue_to_record'), + 'default': false, + 'when': function(answers){ + var fileName = answers.fileName; + if(fs.existsSync(fileName)){ + var content = fs.readFileSync(fileName).toString(); + return /\s*function _\(str\)/.test(content); + } + return false; + } + }, { 'type': 'input', 'name': 'mobileAppPath', @@ -137,7 +146,44 @@ function startRecorder(options){ return input.replace(/(^\s+|\s+$)/g, ''); }, 'validate': function(input){ - return /\.(apk|app)$/.test(input) && fs.existsSync(input); + return /\.(apk|app|zip)$/.test(input) && (/^https?:\/\//.test(input) || fs.existsSync(input)); + }, + 'when': function(answers){ + if(answers.continueRecord){ + var fileName = answers.fileName; + var content = fs.readFileSync(fileName).toString(); + var match = content.match(/appPath = '([^']+)';/); + if(match){ + answers.mobileAppPath = match[1]; + match = content.match(/platformName = '([^']+)';/); + answers.mobilePlatform = match[1]; + } + } + return !answers.mobileAppPath; + } + }, + { + 'type': 'list', + 'name': 'mobilePlatform', + 'choices': ['Android', 'iOS'], + 'message': __('mobile_platform'), + 'when': function(answers){ + if(!answers.mobilePlatform){ + var mobileAppPath = answers.mobileAppPath; + var mobilePlatform = null; + if(/(\bandroid\b|\.apk$)/i.test(mobileAppPath)){ + mobilePlatform = 'Android'; + } + if(/(\bios\b|\.app$)/i.test(mobileAppPath)){ + mobilePlatform = 'iOS'; + } + if(mobilePlatform){ + answers.mobilePlatform = mobilePlatform; + } + else{ + return true; + } + } } } ]; @@ -161,8 +207,8 @@ function startRecorder(options){ 'name': 'continueRecord', 'message': __('continue_to_record'), 'default': false, - 'when': function(anwsers){ - var fileName = anwsers.fileName; + 'when': function(answers){ + var fileName = answers.fileName; if(fs.existsSync(fileName)){ var content = fs.readFileSync(fileName).toString(); return /\s*function _\(str\)/.test(content); @@ -190,11 +236,11 @@ function startRecorder(options){ } ]; } - inquirer.prompt(questions).then(function(anwsers){ - var fileName = anwsers.fileName; - var continueRecord = anwsers.continueRecord; - var openChecker = anwsers.checker; - var browserSize = anwsers.browserSize || ''; + inquirer.prompt(questions).then(function(answers){ + var fileName = answers.fileName; + var continueRecord = answers.continueRecord; + var openChecker = answers.checker; + var browserSize = answers.browserSize || ''; var match = browserSize.match(/^(\d+)\s*[x, ]\s*(\d+)$/); if(match){ browserSize = [ parseInt(match[1], 10), parseInt(match[2], 10)]; @@ -202,7 +248,8 @@ function startRecorder(options){ else{ browserSize = null; } - var mobileAppPath = anwsers.mobileAppPath; + var mobileAppPath = answers.mobileAppPath; + var mobilePlatform = answers.mobilePlatform; var arrTestCodes = []; var recorderBrowser, checkerBrowser, recorderMobileApp; @@ -213,6 +260,8 @@ function startRecorder(options){ var arrRawCmds = []; var allCaseCount = 0; var failedCaseCount = 0; + var isModuleLoading = false; + function escapeStr(str){ return str.replace(/\\/g, '\\\\').replace(/\n/g, '\\n').replace(/\'/g, "\\'"); } @@ -246,7 +295,7 @@ function startRecorder(options){ console.log(' '+symbols.ok.green+__('exec_succeed').green); } else{ - console.log(' '+symbols.err.red+__('exec_failed').red, error); + console.log(' '+symbols.err.red+__('exec_failed').red, error && error.message || error); } } allCaseCount ++; @@ -270,6 +319,9 @@ function startRecorder(options){ } } var cmdQueue = async.priorityQueue(function(cmdInfo, next) { + if(isModuleLoading){ + return next(); + } var window = cmdInfo.window; var frame = cmdInfo.frame; var cmd = cmdInfo.cmd; @@ -384,26 +436,134 @@ function startRecorder(options){ switch(cmd){ case 'click': if(data.path){ - pushTestCode('click', data.text, data.path, 'return driver.wait(\''+escapeStr(data.path)+'\', 30000).click();'); - recorderMobileApp.wait(data.path, 10000).click().then(doNext).catch(catchError); + pushTestCode('tap', data.text, data.path, 'return driver.wait(\''+escapeStr(data.path)+'\', 30000).sendElementActions(\'tap\');'); + recorderMobileApp.wait(data.path, 10000).sendElementActions('tap').then(doNext).catch(catchError); } else{ - pushTestCode('click', '', data.x+', '+data.y, 'return driver.mouseMove('+data.x+', '+data.y+').click(0);'); - recorderMobileApp.mouseMove(data.x, data.y).click(0).then(doNext).catch(catchError); + pushTestCode('tap', '', data.x+', '+data.y, 'return driver.sendActions(\'tap\', {x: '+data.x+', y: '+data.y+'});'); + recorderMobileApp.sendActions('tap', {x: data.x, y: data.y}).then(doNext).catch(catchError); } break; - case 'swipe': - pushTestCode('swipe', '', data.startX+', '+data.startY+', '+data.endX+', '+data.endY+', '+data.duration, 'return driver.touchSwipe('+data.startX+', '+data.startY+', '+data.endX+', '+data.endY+', '+data.duration+');'); - recorderMobileApp.touchSwipe(data.startX, data.startY, data.endX, data.endY, data.duration).then(doNext).catch(catchError); + case 'dblClick': + if(data.path){ + pushTestCode('doubleTap', data.text, data.path, 'return driver.wait(\''+escapeStr(data.path)+'\', 30000).sendElementActions(\'doubleTap\');'); + recorderMobileApp.wait(data.path, 10000).sendElementActions('doubleTap').then(doNext).catch(catchError); + } + else{ + pushTestCode('doubleTap', '', data.x+', '+data.y, 'return driver.sendActions(\'doubleTap\', {x: '+data.x+', y: '+data.y+'});'); + recorderMobileApp.sendActions('doubleTap', {x: data.x, y: data.y}).then(doNext).catch(catchError); + } break; - case 'val': - pushTestCode('val', '', data.keys, 'return driver.val(\''+escapeStr(data.keys)+'\');'); - recorderMobileApp.val(data.keys).then(doNext).catch(catchError); + case 'press': + if(data.path){ + pushTestCode('press', data.text, data.path, 'return driver.wait(\''+escapeStr(data.path)+'\', 30000).sendElementActions(\'press\');'); + recorderMobileApp.wait(data.path, 10000).sendElementActions('press').then(doNext).catch(catchError); + } + else{ + pushTestCode('press', '', data.x+', '+data.y, 'return driver.sendActions(\'press\', {x: '+data.x+', y: '+data.y+'});'); + recorderMobileApp.sendActions('press', {x: data.x, y: data.y}).then(doNext).catch(catchError); + } break; - default: - callback(); + case 'drag': + pushTestCode('drag', '', data.fromX+', '+data.fromY+', '+data.toX+', '+data.toY, 'return driver.sendActions(\'drag\', {fromX: '+data.fromX+', fromY:'+data.fromY+', toX:'+data.toX+', toY:'+data.toY+'});'); + recorderMobileApp.sendActions('drag', {fromX: data.fromX, fromY: data.fromY, toX: data.toX, toY: data.toY}).then(doNext).catch(catchError); break; - } + case 'sendKeys': + pushTestCode('sendKeys', '', data, 'return driver.sendKeys(\''+escapeStr(data)+'\');'); + recorderMobileApp.sendKeys(data).then(doNext).catch(catchError); + break; + case 'sleep': + pushTestCode('sleep', '', data, 'return driver.sleep('+data+');'); + recorderMobileApp.sleep(data).then(doNext).catch(catchError); + break; + case 'acceptAlert': + pushTestCode('acceptAlert', '', '', 'return driver.acceptAlert();'); + recorderMobileApp.acceptAlert().then(doNext).catch(catchError); + break; + case 'dismissAlert': + pushTestCode('dismissAlert', '', '', 'return driver.dismissAlert();'); + recorderMobileApp.dismissAlert().then(doNext).catch(catchError); + break; + case 'back': + pushTestCode('back', '', '', 'return driver.back();'); + recorderMobileApp.back().then(doNext).catch(catchError); + break; + case 'expect': + co(function*(){ + var expectSleep = data.sleep; + var expectType = data.type; + var expectPath = data.path; + var expectCompare = data.compare; + var expectTo = data.to; + arrCodes = []; + arrCodes.push('return driver.sleep('+expectSleep+').wait(\''+escapeStr(expectPath)+'\', 30000)'); + switch(expectType){ + case 'text': + arrCodes.push(' .text()'); + break; + } + var codeExpectTo = expectTo.replace(/"/g, '\\"').replace(/\n/g, '\\n'); + arrCodes.push(' .should.not.be.a(\'error\')'); + switch(expectCompare){ + case 'equal': + arrCodes.push(' .should.equal(_('+(/^(true|false)$/.test(codeExpectTo)?codeExpectTo:'\''+escapeStr(codeExpectTo)+'\'')+'));'); + break; + case 'notEqual': + arrCodes.push(' .should.not.equal(_('+(/^(true|false)$/.test(codeExpectTo)?codeExpectTo:'\''+escapeStr(codeExpectTo)+'\'')+'));'); + break; + case 'contain': + arrCodes.push(' .should.contain(_(\''+escapeStr(codeExpectTo)+'\'));'); + break; + case 'above': + arrCodes.push(' .should.above(_(\''+escapeStr(codeExpectTo)+'\'));'); + break; + case 'below': + arrCodes.push(' .should.below(_(\''+escapeStr(codeExpectTo)+'\'));'); + break; + case 'match': + arrCodes.push(' .should.match('+escapeStr(codeExpectTo)+');'); + break; + case 'notMatch': + arrCodes.push(' .should.not.match('+escapeStr(codeExpectTo)+');'); + break; + } + pushTestCode('expect', '', expectType + ', ' + expectPath + ', ' + expectCompare + ', ' + expectTo, arrCodes); + var element, value; + element = yield recorderMobileApp.sleep(expectSleep).wait(expectPath, 10000); + switch(expectType){ + case 'text': + value = yield element.text(); + break; + } + value.should.not.be.a('error'); + switch(expectCompare){ + case 'equal': + expectTo = /^(true|false)$/.test(expectTo)?eval(expectTo):expectTo; + value.should.equal(getVarStr(expectTo)); + break; + case 'notEqual': + expectTo = /^(true|false)$/.test(expectTo)?eval(expectTo):expectTo; + value.should.not.equal(getVarStr(expectTo)); + break; + case 'contain': + value.should.contain(getVarStr(expectTo)); + break; + case 'above': + value.should.above(getVarStr(expectTo)); + break; + case 'below': + value.should.below(getVarStr(expectTo)); + break; + case 'match': + value.should.match(eval(expectTo)); + break; + case 'notMatch': + value.should.not.match(eval(expectTo)); + break; + } + }).then(doNext).catch(catchError); + break; + } } else{ var reDomRequire = /^(val|text|displayed|enabled|selected|attr|css)$/; @@ -837,18 +997,31 @@ function startRecorder(options){ sendWsMessage('moduleStart', { file: moduleName }); - var arrTasks = [runSpec(moduleName, recorderBrowser, testVars, function(title, errorMsg){ - title = title.replace(/^\w+:/, function(all){ - return all.cyan; - }); - console.log(' '+title); - console.log(' '+(errorMsg?symbols.err.red+__('exec_failed').red + '\t' + errorMsg:symbols.ok.green+__('exec_succeed').green)); - })]; - if(checkerBrowser){ - arrTasks.push(function*(){ - yield sleep(200); - yield runSpec(moduleName, checkerBrowser, testVars) - }); + isModuleLoading = true; + var arrTasks = []; + if(mobile){ + arrTasks.push(runSpec(moduleName, recorderMobileApp, testVars, function(title, errorMsg){ + title = title.replace(/^\w+:/, function(all){ + return all.cyan; + }); + console.log(' '+title); + console.log(' '+(errorMsg?symbols.err.red+__('exec_failed').red + '\t' + errorMsg:symbols.ok.green+__('exec_succeed').green)); + })); + } + else{ + arrTasks.push(runSpec(moduleName, recorderBrowser, testVars, function(title, errorMsg){ + title = title.replace(/^\w+:/, function(all){ + return all.cyan; + }); + console.log(' '+title); + console.log(' '+(errorMsg?symbols.err.red+__('exec_failed').red + '\t' + errorMsg:symbols.ok.green+__('exec_succeed').green)); + })); + if(checkerBrowser){ + arrTasks.push(function*(){ + yield sleep(200); + yield runSpec(moduleName, checkerBrowser, testVars) + }); + } } yield arrTasks; yield recorderBrowser.sleep(1000); @@ -862,6 +1035,7 @@ function startRecorder(options){ success: true }); console.log((' -------------- end load '+moduleName+' --------------').gray); + isModuleLoading = false; }).then(function(){ callback && callback(null); }).catch(function(error){ @@ -934,8 +1108,25 @@ function startRecorder(options){ } } }); - // checker browser - if(openChecker){ + + if(mobile){ + // init mobile + function updateMobileInfo(){ + cmdQueue.push({cmd:'!updateMobile'}, 1, function(){ + setTimeout(updateMobileInfo, 200); + }); + } + newMobileApp({ + mobileAppPath: mobileAppPath, + mobilePlatform: mobilePlatform + }, function(mobileApp){ + recorderMobileApp = mobileApp; + checkAllReady(); + updateMobileInfo(); + }); + } + else if(openChecker){ + // checker browser setTimeout(function(){ newChromeBrowser({hosts: hosts, isRecorder: false, debug: debug}, function*(browser){ if(browserSize){ @@ -962,21 +1153,9 @@ function startRecorder(options){ }); }, 1000); } - else if(mobile){ - function updateMobileInfo(){ - cmdQueue.push({cmd:'!updateMobile'}, 1, function(){ - setTimeout(updateMobileInfo, 200); - }); - } - newMobileApp({mobileAppPath: mobileAppPath}, function(mobileApp){ - recorderMobileApp = mobileApp; - checkAllReady(); - updateMobileInfo(); - }); - } } function checkAllReady(){ - if(recorderBrowser && ((openChecker && checkerBrowser) || !openChecker)){ + if(recorderBrowser && ((openChecker && checkerBrowser) || (mobile && recorderMobileApp) || !(openChecker || mobile))){ if(continueRecord){ var testFile = path.resolve(fileName); var absfileName = path.relative(rootPath, testFile).replace(/\\/g,'/'); @@ -1059,10 +1238,10 @@ function startRecorder(options){ cmdInfo.window === lastCmdInfo2.window && cmdInfo.frame === lastCmdInfo2.frame && lastCmdData.path === data.path && - Math.abs(lastCmdData.x - data.x) < 20 && - Math.abs(lastCmdData.y - data.y) < 20 + (lastCmdData.x == data.x || Math.abs(lastCmdData.x - data.x) < 20) && + (lastCmdData.y == data.y || Math.abs(lastCmdData.y - data.y) < 20) ){ - // 条件满足,合并为click + // 条件满足,合并为dblClick cmdInfo = { window: cmdInfo.window, frame: cmdInfo.frame, @@ -1116,6 +1295,7 @@ function startRecorder(options){ pathAttrs : pathAttrs, attrValueBlack: attrValueBlack, hideBeforeExpect: hideBeforeExpect, + mobilePlatform: mobilePlatform, testVars: testVars, specLists: specLists, i18n: __('chrome') @@ -1126,11 +1306,11 @@ function startRecorder(options){ var testFile = path.resolve(fileName); fileName = path.relative(rootPath, testFile).replace(/\\/g,'/'); arrTestCodes = arrTestCodes.map(function(line){ - return ' '+ line; + return line?' '+ line:''; }); if(continueRecord){ var testContent = fs.readFileSync(testFile).toString(); - testContent = testContent.replace(/ +function _\(str\){/, function(all){ + testContent = testContent.replace(/[ \t]+function _\(str\){/, function(all){ return arrTestCodes.join('\r\n') + '\r\n' + all; }); fs.writeFileSync(testFile, testContent); @@ -1147,10 +1327,13 @@ function startRecorder(options){ sizeCode = '.maximize()'; } if(mobileAppPath){ - var relAppPath = path.relative(rootPath, mobileAppPath); - if(/^\.\./.test(relAppPath) === false){ - mobileAppPath = relAppPath; + if(!/^https?:\/\//.test(mobileAppPath)){ + var relAppPath = path.relative(rootPath, mobileAppPath); + if(/^\.\./.test(relAppPath) === false){ + mobileAppPath = relAppPath; + } } + } templateContent = templateContent.replace(/\{\$(\w+)\}/g, function(all, name){ switch(name){ @@ -1160,6 +1343,8 @@ function startRecorder(options){ return sizeCode; case 'appPath': return mobileAppPath.replace(/\\/g, '\\\\'); + case 'platformName': + return mobilePlatform; } return all; }); @@ -1176,7 +1361,7 @@ function startRecorder(options){ if(raw){ var rawFile = testFile.replace(/\.js$/, '.json'); var rawFileName = fileName.replace(/\.js$/, '.json'); - if(continueRecord){ + if(continueRecord && fs.existsSync(rawFile)){ var oldRawContent = fs.readFileSync(rawFile).toString(); try{ var arrOldRaw = JSON.parse(oldRawContent); @@ -1364,14 +1549,14 @@ function getDeviceList(platformName){ } else{ // ios real device - strText = cp.execSync('instruments -s devices').toString(); - strText.replace(/([^\r\n]+)\s+\[(.+?)\]\r?\n/g, function(all, deviceName, udid){ - if(/^(iphone|ipad)/i.test(deviceName)){ - arrDeviceList.push({ - name: deviceName, - udid: udid - }); - } + strText = cp.execSync('idevice_id -l').toString(); + strText.replace(/(.+)\r?\n/g, function(all, udid){ + var deviceName = cp.execSync('idevice_id -d '+udid).toString(); + deviceName = deviceName.replace(/\r?\n/g, ''); + arrDeviceList.push({ + name: deviceName, + udid: udid + }); }); // ios simulator strText = cp.execSync('xcrun simctl list devices').toString(); @@ -1391,17 +1576,19 @@ function newMobileApp(options, callback){ 'port': 4444 }); var mobileAppPath = options.mobileAppPath; - var platformName = /\.apk$/.test(mobileAppPath)?'Android':'iOS'; - var deviceList = getDeviceList(platformName); + var mobilePlatform = options.mobilePlatform; + var deviceList = getDeviceList(mobilePlatform); + var capabilities = { + 'platformName': mobilePlatform, + 'app': /^(\/|[a-z]:\\|https?:\/\/)/i.test(mobileAppPath)?mobileAppPath:path.resolve(mobileAppPath) + }; if(deviceList.length === 0){ - console.log(__('mobile_open_first').red); - process.exit(1); + capabilities.deviceName = mobilePlatform === 'Android'?'Android Emulator':'iPhone 6'; + } + else{ + capabilities.udid = deviceList[0].udid; } - driver.session({ - 'platformName': platformName, - 'udid': deviceList[0].udid, - 'app': path.resolve(mobileAppPath) - }).then(function(){ + driver.session(capabilities).then(function(){ callback(this); }).catch(function(e){ console.log(__('mobile_open_failed').red, e); diff --git a/package.json b/package.json index 147107d..bf29151 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uirecorder", - "version": "2.3.32", + "version": "2.4.0", "description": "A tool for record ui test case", "main": "./index", "bin": { @@ -17,7 +17,7 @@ "fs-extra": "1.0.0", "i18n": "0.8.3", "inquirer": "3.0.1", - "jwebdriver": "2.0.8", + "jwebdriver": "2.1.1", "latest-version": "2.0.0", "os-locale": "1.4.0", "semver": "5.3.0", diff --git a/project/package.json b/project/package.json index a5a211b..23fa5dc 100644 --- a/project/package.json +++ b/project/package.json @@ -6,7 +6,7 @@ "dependencies": { "chai": "3.5.0", "faker": "3.1.0", - "jwebdriver": "2.0.8", + "jwebdriver": "2.1.1", "mocha": "3.1.2", "mocha-parallel-tests": "1.2.4", "mochawesome-uirecorder": "1.5.20", diff --git a/template/jwebdriver-mobile.js b/template/jwebdriver-mobile.js index f2a5e1d..c9605c2 100644 --- a/template/jwebdriver-mobile.js +++ b/template/jwebdriver-mobile.js @@ -9,7 +9,7 @@ chai.use(JWebDriver.chaiSupportChainPromise); var rootPath = getRootPath(); var appPath = '{$appPath}'; -var platformName = /\.apk$/.test(appPath)?'Android':'iOS'; +var platformName = '{$platformName}'; module.exports = function(){ @@ -36,8 +36,7 @@ if(module.parent && /mocha\.js/.test(module.parent.id)){ function runThisSpec(){ // read config - var runtime = process.env['runtime'] || ''; - var config = require(rootPath + '/config'+(runtime?'-'+runtime:'')+'.json'); + var config = require(rootPath + '/config.json'); var webdriverConfig = Object.assign({},config.webdriver); var host = webdriverConfig.host; var port = webdriverConfig.port || 4444; @@ -77,7 +76,7 @@ function runThisSpec(){ self.driver = driver.session({ 'platformName': platformName, 'udid': device.udid, - 'app': /^(\/|[a-z]:\\)/i.test(appPath) ? appPath : rootPath + '/' + appPath + 'app': /^(\/|[a-z]:\\|https?:\/\/)/i.test(appPath) ? appPath : rootPath + '/' + appPath }); self.testVars = testVars; return self.driver; @@ -158,14 +157,14 @@ function getDeviceList(platformName){ } else{ // ios real device - strText = cp.execSync('instruments -s devices').toString(); - strText.replace(/([^\r\n]+)\s+\[(.+?)\]\r?\n/g, function(all, deviceName, udid){ - if(/^(iphone|ipad)/i.test(deviceName)){ - arrDeviceList.push({ - name: deviceName, - udid: udid - }); - } + strText = cp.execSync('idevice_id -l').toString(); + strText.replace(/(.+)\r?\n/g, function(all, udid){ + var deviceName = cp.execSync('idevice_id -d '+udid).toString(); + deviceName = deviceName.replace(/\r?\n/g, ''); + arrDeviceList.push({ + name: deviceName, + udid: udid + }); }); // ios simulator strText = cp.execSync('xcrun simctl list devices').toString(); diff --git a/template/jwebdriver.js b/template/jwebdriver.js index f1e651c..77189b1 100644 --- a/template/jwebdriver.js +++ b/template/jwebdriver.js @@ -33,9 +33,8 @@ if(module.parent && /mocha\.js/.test(module.parent.id)){ function runThisSpec(){ // read config - var runtime = process.env['runtime'] || ''; var webdriver = process.env['webdriver'] || ''; - var config = require(rootPath + '/config'+(runtime?'-'+runtime:'')+'.json'); + var config = require(rootPath + '/config.json'); var webdriverConfig = Object.assign({},config.webdriver); var host = webdriverConfig.host; var port = webdriverConfig.port || 4444; @@ -52,7 +51,7 @@ function runThisSpec(){ delete webdriverConfig.browsers; // read hosts - var hostsPath = rootPath + '/hosts'+(runtime?'-'+runtime:''); + var hostsPath = rootPath + '/hosts'; var hosts = ''; if(fs.existsSync(hostsPath)){ hosts = fs.readFileSync(hostsPath).toString(); diff --git a/tool/uirecorder.crx b/tool/uirecorder.crx index f30ebb854a3969df526d74f7f47b4584af89c809..a4b1f73cf4f7ebe955f5aebfb8f076989adea0d7 100644 GIT binary patch delta 28318 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%0DF4IpvRy8AKjx!b3tYV-qF+;^?T}LQX}O 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