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 @@
+
+
+
-