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 0000000..6c76cfc Binary files /dev/null and b/chrome-extension/img/alert.png differ diff --git a/chrome-extension/img/back.png b/chrome-extension/img/back.png new file mode 100644 index 0000000..15d234d Binary files /dev/null and b/chrome-extension/img/back.png differ diff --git a/chrome-extension/img/end.png b/chrome-extension/img/end.png index 01a0751..4c56142 100644 Binary files a/chrome-extension/img/end.png and b/chrome-extension/img/end.png differ diff --git a/chrome-extension/img/text.png b/chrome-extension/img/text.png new file mode 100644 index 0000000..f6485fe Binary files /dev/null and b/chrome-extension/img/text.png differ 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 = ''; + 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 = [ + '' + ]; + 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 f30ebb8..a4b1f73 100644 Binary files a/tool/uirecorder.crx and b/tool/uirecorder.crx differ