├── BatchRunAppUI.jar ├── UIAutoTest.iml ├── autoTest.sh ├── handleResult.sh ├── output └── report │ ├── css │ ├── bootstrap-theme.css │ ├── bootstrap-theme.css.map │ ├── bootstrap-theme.min.css │ ├── bootstrap.css │ ├── bootstrap.css.map │ └── bootstrap.min.css │ ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 │ ├── js │ ├── angular.js │ ├── bootstrap.js │ ├── bootstrap.min.js │ ├── exporting.js │ ├── highcharts-more.js │ ├── highcharts.js │ ├── highstock.js │ ├── jquery.min.js │ └── npm.js │ └── report.html ├── pom.xml ├── prepare.sh ├── run.sh ├── src ├── config.properties ├── log4j.properties └── test │ ├── java │ └── com │ │ └── xxxx │ │ └── uiautotest │ │ ├── base │ │ ├── AutoTestBase.java │ │ ├── ExecuteListener.java │ │ ├── PageObjectBase.java │ │ └── operateFactory │ │ │ ├── AndroidOperate.java │ │ │ ├── AppOperate.java │ │ │ ├── IosOperate.java │ │ │ ├── WebDriverOperate.java │ │ │ └── WebOperate.java │ │ ├── business │ │ └── page_object │ │ │ ├── app │ │ │ └── Elements_Example.java │ │ │ └── web │ │ │ └── page_baidu.java │ │ ├── testsuits │ │ ├── testcases │ │ │ ├── app │ │ │ │ └── Example_Tests.java │ │ │ └── web │ │ │ │ └── testExamlpe.java │ │ └── testdata │ │ │ └── Example_data.java │ │ └── util │ │ ├── Assertion.java │ │ ├── HandleSafetyTips.java │ │ ├── Log.java │ │ ├── ParseProperties.java │ │ ├── StartAppiumServer.java │ │ ├── StaticConfig.java │ │ ├── Tools.java │ │ └── testng │ │ ├── ConfigReader.java │ │ ├── PowerEmailableReporter.java │ │ ├── RetryListener.java │ │ ├── TestResultListener.java │ │ └── TestngRetry.java │ └── test.iml ├── testSuits ├── testSuitExample_android.xml ├── testSuitExample_ios.xml └── testSuitExample_web.xml └── tools ├── GenerateTestNgReport.jar ├── analysisxml.jar ├── driverServer ├── chromedriver ├── chromedriver.exe └── operadriver └── testngReport ├── build.xml └── testng-results.xsl /BatchRunAppUI.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kinget007/UIAutoTest/03d1d920818c2ba3364ca189cd1929afc982edfe/BatchRunAppUI.jar -------------------------------------------------------------------------------- /UIAutoTest.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /autoTest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #dateFileName=`date +"%Y%m%d%H%M%S"` 3 | #dateFileName=`date +"%Y%m%d"` 4 | #timeFileName=`date +"%H%S"` 5 | date=`cat date.txt |cut -d : -f1` 6 | time=`cat date.txt |cut -d : -f2` 7 | currentPath=`pwd` 8 | 9 | java -classpath "target/test-classes/" -Djava.ext.dirs=lib org.testng.TestNG -suitethreadpoolsize 1 TestSuits/APP_*_{devices}_*.xml -d output/${date}/${time}/{udid}/testngReports 10 | 11 | cd tools/testngReport 12 | ant transform -Din=${currentPath}/output/${date}/${time}/{udid}/testngReports/testng-results.xml -Dout=${currentPath}/output/${date}/${time}/{udid}/testngReports/index_xslt.html -Dexpression=${currentPath}/output/${date}/${time}/{udid}/testngReports/ 13 | #cd ${currentPath}/output/{date}/{time}/{udid}/testngReports 14 | #if [ -d "lastbuild" ]; then 15 | # rm -rf lastbuild 16 | #fi 17 | #mkdir lastbuild 18 | #cp -rf ./${timeFileName}/* ./lastbuild 19 | 20 | #cd ${currentPath}/tools 21 | #echo "junitreports_FilePath = " ../output/testngReports/${platform}/${dateFileName}/junitreports 22 | #java -jar GenerateTestNgReport.jar ../output/testngReports/${platform}/${dateFileName} ${syscflag} old 23 | #java -jar GenerateTestNgReport.jar ${currentPath}/output/testngReports/${dateFileName} {isSynchronizedData} 24 | #cd ../../../ 25 | #cp -r output ./../../userContent/$BUILD_NUMBER -------------------------------------------------------------------------------- /handleResult.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | isSynchronizedData=$1 3 | isCreateReport=$2 4 | date=`cat date.txt |cut -d : -f1` 5 | time=`cat date.txt |cut -d : -f2` 6 | currentPath=`pwd` 7 | 8 | cd ${currentPath}/tools 9 | java -jar GenerateTestNgReport.jar ${currentPath}/output/${date}/${time} ${isSynchronizedData} ${isCreateReport} 10 | 11 | cd $currentPath/output/report 12 | mv -f reportAll.html ${currentPath}/output/${date}/${time}/ 13 | 14 | cd ${currentPath}/output/ 15 | if [ -d "lastbuild" ]; then 16 | rm -rf lastbuild 17 | fi 18 | mkdir lastbuild 19 | cp -rf ${currentPath}/output/${date}/${time}/* ${currentPath}/output/lastbuild/ 20 | 21 | cd $currentPath 22 | rm -rf $currentPath/autoTest_* 23 | cp -rf output ./../../userContent/$BUILD_NUMBER 24 | ps -A|grep node|grep -v grep|awk 'NR=1 {print $1}'|xargs kill -9 25 | 26 | devices=`adb devices|awk 'NR!=1 {print $1}'|xargs` 27 | for device in $devices 28 | do 29 | echo "start reboot device >>" $device 30 | adb -s $device reboot 31 | done -------------------------------------------------------------------------------- /output/report/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.5 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} -------------------------------------------------------------------------------- /output/report/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kinget007/UIAutoTest/03d1d920818c2ba3364ca189cd1929afc982edfe/output/report/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /output/report/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kinget007/UIAutoTest/03d1d920818c2ba3364ca189cd1929afc982edfe/output/report/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /output/report/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kinget007/UIAutoTest/03d1d920818c2ba3364ca189cd1929afc982edfe/output/report/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /output/report/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kinget007/UIAutoTest/03d1d920818c2ba3364ca189cd1929afc982edfe/output/report/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /output/report/js/exporting.js: -------------------------------------------------------------------------------- 1 | /* 2 | Highstock JS v2.1.9 (2015-10-07) 3 | Exporting module 4 | 5 | (c) 2010-2014 Torstein Honsi 6 | 7 | License: www.highcharts.com/license 8 | */ 9 | (function(g){var z=g.Chart,s=g.addEvent,A=g.removeEvent,B=HighchartsAdapter.fireEvent,j=g.createElement,p=g.discardElement,u=g.css,l=g.merge,m=g.each,q=g.extend,E=g.splat,F=Math.max,k=document,C=window,G=g.isTouchDevice,H=g.Renderer.prototype.symbols,r=g.getOptions(),x;q(r.lang,{printChart:"Print chart",downloadPNG:"Download PNG image",downloadJPEG:"Download JPEG image",downloadPDF:"Download PDF document",downloadSVG:"Download SVG vector image",contextButtonTitle:"Chart context menu"});r.navigation= 10 | {menuStyle:{border:"1px solid #A0A0A0",background:"#FFFFFF",padding:"5px 0"},menuItemStyle:{padding:"0 10px",background:"none",color:"#303030",fontSize:G?"14px":"11px"},menuItemHoverStyle:{background:"#4572A5",color:"#FFFFFF"},buttonOptions:{symbolFill:"#E0E0E0",symbolSize:14,symbolStroke:"#666",symbolStrokeWidth:3,symbolX:12.5,symbolY:10.5,align:"right",buttonSpacing:3,height:22,theme:{fill:"white",stroke:"none"},verticalAlign:"top",width:24}};r.exporting={type:"image/png",url:"http://export.highcharts.com/", 11 | buttons:{contextButton:{menuClassName:"highcharts-contextmenu",symbol:"menu",_titleKey:"contextButtonTitle",menuItems:[{textKey:"printChart",onclick:function(){this.print()}},{separator:!0},{textKey:"downloadPNG",onclick:function(){this.exportChart()}},{textKey:"downloadJPEG",onclick:function(){this.exportChart({type:"image/jpeg"})}},{textKey:"downloadPDF",onclick:function(){this.exportChart({type:"application/pdf"})}},{textKey:"downloadSVG",onclick:function(){this.exportChart({type:"image/svg+xml"})}}]}}}; 12 | g.post=function(b,a,e){var c,b=j("form",l({method:"post",action:b,enctype:"multipart/form-data"},e),{display:"none"},k.body);for(c in a)j("input",{type:"hidden",name:c,value:a[c]},null,b);b.submit();p(b)};q(z.prototype,{sanitizeSVG:function(b){return b.replace(/zIndex="[^"]+"/g,"").replace(/isShadow="[^"]+"/g,"").replace(/symbolName="[^"]+"/g,"").replace(/jQuery[0-9]+="[^"]+"/g,"").replace(/url\([^#]+#/g,"url(#").replace(/.*?$/,"").replace(/(fill|stroke)="rgba\(([ 0-9]+,[ 0-9]+,[ 0-9]+),([ 0-9\.]+)\)"/g,'$1="rgb($2)" $1-opacity="$3"').replace(/ /g,"\u00a0").replace(/­/g,"\u00ad").replace(//g,"<$1title>").replace(/height=([^" ]+)/g,'height="$1"').replace(/width=([^" ]+)/g,'width="$1"').replace(/hc-svg-href="([^"]+)">/g,'xlink:href="$1"/>').replace(/ id=([^" >]+)/g,' id="$1"').replace(/class=([^" >]+)/g,'class="$1"').replace(/ transform /g, 14 | " ").replace(/:(path|rect)/g,"$1").replace(/style="([^"]+)"/g,function(a){return a.toLowerCase()})},getChartHTML:function(){return this.container.innerHTML},getSVG:function(b){var a=this,e,c,f,y,h,d=l(a.options,b),I=d.exporting.allowHTML;if(!k.createElementNS)k.createElementNS=function(a,b){return k.createElement(b)};c=j("div",null,{position:"absolute",top:"-9999em",width:a.chartWidth+"px",height:a.chartHeight+"px"},k.body);f=a.renderTo.style.width;h=a.renderTo.style.height;f=d.exporting.sourceWidth|| 15 | d.chart.width||/px$/.test(f)&&parseInt(f,10)||600;h=d.exporting.sourceHeight||d.chart.height||/px$/.test(h)&&parseInt(h,10)||400;q(d.chart,{animation:!1,renderTo:c,forExport:!0,renderer:"SVGRenderer",width:f,height:h});d.exporting.enabled=!1;delete d.data;d.series=[];m(a.series,function(a){y=l(a.options,{animation:!1,enableMouseTracking:!1,showCheckbox:!1,visible:a.visible});y.isInternal||d.series.push(y)});b&&m(["xAxis","yAxis"],function(a){m(E(b[a]),function(b,c){d[a][c]=l(d[a][c],b)})});e=new g.Chart(d, 16 | a.callback);m(["xAxis","yAxis"],function(b){m(a[b],function(a,c){var d=e[b][c],f=a.getExtremes(),h=f.userMin,f=f.userMax;d&&(h!==void 0||f!==void 0)&&d.setExtremes(h,f,!0,!1)})});f=e.getChartHTML();d=null;e.destroy();p(c);if(I&&(c=f.match(/<\/svg>(.*?$)/)))c=''+c[1]+"",f=f.replace("",c+"");f=this.sanitizeSVG(f);return f=f.replace(/(url\(#highcharts-[0-9]+)"/g,"$1").replace(/"/g, 17 | "'")},getSVGForExport:function(b,a){var e=this.options.exporting;return this.getSVG(l({chart:{borderRadius:0}},e.chartOptions,a,{exporting:{sourceWidth:b&&b.sourceWidth||e.sourceWidth,sourceHeight:b&&b.sourceHeight||e.sourceHeight}}))},exportChart:function(b,a){var e=this.getSVGForExport(b,a),b=l(this.options.exporting,b);g.post(b.url,{filename:b.filename||"chart",type:b.type,width:b.width||0,scale:b.scale||2,svg:e},b.formAttributes)},print:function(){var b=this,a=b.container,e=[],c=a.parentNode, 18 | f=k.body,g=f.childNodes;if(!b.isPrinting)b.isPrinting=!0,B(b,"beforePrint"),m(g,function(a,b){if(a.nodeType===1)e[b]=a.style.display,a.style.display="none"}),f.appendChild(a),C.focus(),C.print(),setTimeout(function(){c.appendChild(a);m(g,function(a,b){if(a.nodeType===1)a.style.display=e[b]});b.isPrinting=!1;B(b,"afterPrint")},1E3)},contextMenu:function(b,a,e,c,f,g,h){var d=this,l=d.options.navigation,D=l.menuItemStyle,n=d.chartWidth,o=d.chartHeight,k="cache-"+b,i=d[k],t=F(f,g),v,w,p,r=function(a){d.pointer.inClass(a.target, 19 | b)||w()};if(!i)d[k]=i=j("div",{className:b},{position:"absolute",zIndex:1E3,padding:t+"px"},d.container),v=j("div",null,q({MozBoxShadow:"3px 3px 10px #888",WebkitBoxShadow:"3px 3px 10px #888",boxShadow:"3px 3px 10px #888"},l.menuStyle),i),w=function(){u(i,{display:"none"});h&&h.setState(0);d.openMenu=!1},s(i,"mouseleave",function(){p=setTimeout(w,500)}),s(i,"mouseenter",function(){clearTimeout(p)}),s(document,"mouseup",r),s(d,"destroy",function(){A(document,"mouseup",r)}),m(a,function(a){if(a){var b= 20 | a.separator?j("hr",null,null,v):j("div",{onmouseover:function(){u(this,l.menuItemHoverStyle)},onmouseout:function(){u(this,D)},onclick:function(b){b.stopPropagation();w();a.onclick&&a.onclick.apply(d,arguments)},innerHTML:a.text||d.options.lang[a.textKey]},q({cursor:"pointer"},D),v);d.exportDivElements.push(b)}}),d.exportDivElements.push(v,i),d.exportMenuWidth=i.offsetWidth,d.exportMenuHeight=i.offsetHeight;a={display:"block"};e+d.exportMenuWidth>n?a.right=n-e-f-t+"px":a.left=e-t+"px";c+g+d.exportMenuHeight> 21 | o&&h.alignOptions.verticalAlign!=="top"?a.bottom=o-c-t+"px":a.top=c+g-t+"px";u(i,a);d.openMenu=!0},addButton:function(b){var a=this,e=a.renderer,c=l(a.options.navigation.buttonOptions,b),f=c.onclick,k=c.menuItems,h,d,m={stroke:c.symbolStroke,fill:c.symbolFill},j=c.symbolSize||12;if(!a.btnCount)a.btnCount=0;if(!a.exportDivElements)a.exportDivElements=[],a.exportSVGElements=[];if(c.enabled!==!1){var n=c.theme,o=n.states,p=o&&o.hover,o=o&&o.select,i;delete n.states;f?i=function(b){b.stopPropagation(); 22 | f.call(a,b)}:k&&(i=function(){a.contextMenu(d.menuClassName,k,d.translateX,d.translateY,d.width,d.height,d);d.setState(2)});c.text&&c.symbol?n.paddingLeft=g.pick(n.paddingLeft,25):c.text||q(n,{width:c.width,height:c.height,padding:0});d=e.button(c.text,0,0,i,n,p,o).attr({title:a.options.lang[c._titleKey],"stroke-linecap":"round"});d.menuClassName=b.menuClassName||"highcharts-menu-"+a.btnCount++;c.symbol&&(h=e.symbol(c.symbol,c.symbolX-j/2,c.symbolY-j/2,j,j).attr(q(m,{"stroke-width":c.symbolStrokeWidth|| 23 | 1,zIndex:1})).add(d));d.add().align(q(c,{width:d.width,x:g.pick(c.x,x)}),!0,"spacingBox");x+=(d.width+c.buttonSpacing)*(c.align==="right"?-1:1);a.exportSVGElements.push(d,h)}},destroyExport:function(b){var b=b.target,a,e;for(a=0;aj.len*j.tickInterval/(j.max-j.min)&&(l=0),i=x>l&&x<180-l?"left":x>180+l&&x<360-l?"right":"center"):i="center",d.attr({align:i})),a.x+=f.x,a.y+=m):a=a.call(this,b,c,d,e,f,h,k,g);return a});r(z,"getMarkPath",function(a,b,c,d,e,f,h){var k= 20 | this.axis;k.isRadial?(a=k.getPosition(this.pos,k.center[2]/2+d),b=["M",b,c,"L",a.x,a.y]):b=a.call(this,b,c,d,e,f,h);return b});p.arearange=t(p.area,{lineWidth:1,marker:null,threshold:null,tooltip:{pointFormat:'\u25cf {series.name}: {point.low} - {point.high}
'},trackByArea:!0,dataLabels:{align:null,verticalAlign:null,xLow:0,xHigh:0,yLow:0,yHigh:0},states:{hover:{halo:!1}}});i.arearange=u(i.area,{type:"arearange",pointArrayMap:["low","high"], 21 | dataLabelCollections:["dataLabel","dataLabelUpper"],toYData:function(a){return[a.low,a.high]},pointValKey:"low",deferTranslatePolar:!0,highToXY:function(a){var b=this.chart,c=this.xAxis.postTranslate(a.rectPlotX,this.yAxis.len-a.plotHigh);a.plotHighX=c.x-b.plotLeft;a.plotHigh=c.y-b.plotTop},getSegments:function(){var a=this;s(a.points,function(b){if(!a.options.connectNulls&&(b.low===null||b.high===null))b.y=null;else if(b.low===null&&b.high!==null)b.y=b.high});w.prototype.getSegments.call(this)}, 22 | translate:function(){var a=this,b=a.yAxis;i.area.prototype.translate.apply(a);s(a.points,function(a){var d=a.low,e=a.high,f=a.plotY;e===null&&d===null?a.y=null:d===null?(a.plotLow=a.plotY=null,a.plotHigh=b.translate(e,0,1,0,1)):e===null?(a.plotLow=f,a.plotHigh=null):(a.plotLow=f,a.plotHigh=b.translate(e,0,1,0,1))});this.chart.polar&&s(this.points,function(b){a.highToXY(b)})},getSegmentPath:function(a){var b,c=[],d=a.length,e=w.prototype.getSegmentPath,f,h;h=this.options;var k=h.step;for(b=HighchartsAdapter.grep(a, 23 | function(a){return a.plotLow!==null});d--;)f=a[d],f.plotHigh!==null&&c.push({plotX:f.plotHighX||f.plotX,plotY:f.plotHigh});a=e.call(this,b);if(k)k===!0&&(k="left"),h.step={left:"right",center:"center",right:"left"}[k];c=e.call(this,c);h.step=k;h=[].concat(a,c);this.chart.polar||(c[0]="L");this.areaPath=this.areaPath.concat(a,c);return h},drawDataLabels:function(){var a=this.data,b=a.length,c,d=[],e=w.prototype,f=this.options.dataLabels,h=f.align,k=f.inside,g,j,m=this.chart.inverted;if(f.enabled|| 24 | this._hasPointLabels){for(c=b;c--;)if(g=a[c])if(j=k?g.plotHighg.plotLow,g.y=g.high,g._plotY=g.plotY,g.plotY=g.plotHigh,d[c]=g.dataLabel,g.dataLabel=g.dataLabelUpper,g.below=j,m){if(!h)f.align=j?"right":"left";f.x=f.xHigh}else f.y=f.yHigh;e.drawDataLabels&&e.drawDataLabels.apply(this,arguments);for(c=b;c--;)if(g=a[c])if(j=k?g.plotHighg.plotLow,g.dataLabelUpper=g.dataLabel,g.dataLabel=d[c],g.y=g.low,g.plotY=g._plotY,g.below=!j,m){if(!h)f.align=j?"left":"right"; 25 | f.x=f.xLow}else f.y=f.yLow;e.drawDataLabels&&e.drawDataLabels.apply(this,arguments)}f.align=h},alignDataLabel:function(){i.column.prototype.alignDataLabel.apply(this,arguments)},setStackedPoints:v,getSymbol:v,drawPoints:v});p.areasplinerange=t(p.arearange);i.areasplinerange=u(i.arearange,{type:"areasplinerange",getPointSpline:i.spline.prototype.getPointSpline});(function(){var a=i.column.prototype;p.columnrange=t(p.column,p.arearange,{lineWidth:1,pointRange:null});i.columnrange=u(i.arearange,{type:"columnrange", 26 | translate:function(){var b=this,c=b.yAxis,d;a.translate.apply(b);s(b.points,function(a){var f=a.shapeArgs,h=b.options.minPointLength,k;a.tooltipPos=null;a.plotHigh=d=c.translate(a.high,0,1,0,1);a.plotLow=a.plotY;k=d;a=a.plotY-d;Math.abs(a)\u25cf {series.name}
Maximum: {point.high}
Upper quartile: {point.q3}
Median: {point.median}
Lower quartile: {point.q1}
Minimum: {point.low}
'}, 32 | whiskerLength:"50%",whiskerWidth:2});i.boxplot=u(i.column,{type:"boxplot",pointArrayMap:["low","q1","median","q3","high"],toYData:function(a){return[a.low,a.q1,a.median,a.q3,a.high]},pointValKey:"high",pointAttrToOptions:{fill:"fillColor",stroke:"color","stroke-width":"lineWidth"},drawDataLabels:v,translate:function(){var a=this.yAxis,b=this.pointArrayMap;i.column.prototype.translate.apply(this);s(this.points,function(c){s(b,function(b){c[b]!==null&&(c[b+"Plot"]=a.translate(c[b],0,1,0,1))})})},drawPoints:function(){var a= 33 | this,b=a.options,c=a.chart.renderer,d,e,f,h,k,g,j,m,l,i,x,n,K,p,t,r,v,u,w,y,B,A,z=a.doQuartiles!==!1,F,D=a.options.whiskerLength;s(a.points,function(q){l=q.graphic;B=q.shapeArgs;x={};p={};r={};A=q.color||a.color;if(q.plotY!==G)if(d=q.pointAttr[q.selected?"selected":""],v=B.width,u=C(B.x),w=u+v,y=E(v/2),e=C(z?q.q1Plot:q.lowPlot),f=C(z?q.q3Plot:q.lowPlot),h=C(q.highPlot),k=C(q.lowPlot),x.stroke=q.stemColor||b.stemColor||A,x["stroke-width"]=o(q.stemWidth,b.stemWidth,b.lineWidth),x.dashstyle=q.stemDashStyle|| 34 | b.stemDashStyle,p.stroke=q.whiskerColor||b.whiskerColor||A,p["stroke-width"]=o(q.whiskerWidth,b.whiskerWidth,b.lineWidth),r.stroke=q.medianColor||b.medianColor||A,r["stroke-width"]=o(q.medianWidth,b.medianWidth,b.lineWidth),j=x["stroke-width"]%2/2,m=u+y+j,i=["M",m,f,"L",m,h,"M",m,e,"L",m,k],z&&(j=d["stroke-width"]%2/2,m=C(m)+j,e=C(e)+j,f=C(f)+j,u+=j,w+=j,n=["M",u,f,"L",u,e,"L",w,e,"L",w,f,"L",u,f,"z"]),D&&(j=p["stroke-width"]%2/2,h+=j,k+=j,F=/%$/.test(D)?y*parseFloat(D)/100:D/2,K=["M",m-F,h,"L",m+ 35 | F,h,"M",m-F,k,"L",m+F,k]),j=r["stroke-width"]%2/2,g=E(q.medianPlot)+j,t=["M",u,g,"L",w,g],l)q.stem.animate({d:i}),D&&q.whiskers.animate({d:K}),z&&q.box.animate({d:n}),q.medianShape.animate({d:t});else{q.graphic=l=c.g().add(a.group);q.stem=c.path(i).attr(x).add(l);if(D)q.whiskers=c.path(K).attr(p).add(l);if(z)q.box=c.path(n).attr(d).add(l);q.medianShape=c.path(t).attr(r).add(l)}})},setStackedPoints:v});p.errorbar=t(p.boxplot,{color:"#000000",grouping:!1,linkedTo:":previous",tooltip:{pointFormat:'\u25cf {series.name}: {point.low} - {point.high}
'}, 36 | whiskerWidth:null});i.errorbar=u(i.boxplot,{type:"errorbar",pointArrayMap:["low","high"],toYData:function(a){return[a.low,a.high]},pointValKey:"high",doQuartiles:!1,drawDataLabels:i.arearange?i.arearange.prototype.drawDataLabels:v,getColumnMetrics:function(){return this.linkedParent&&this.linkedParent.columnMetrics||i.column.prototype.getColumnMetrics.call(this)}});p.waterfall=t(p.column,{lineWidth:1,lineColor:"#333",dashStyle:"dot",borderColor:"#333",dataLabels:{inside:!0},states:{hover:{lineWidthPlus:0}}}); 37 | i.waterfall=u(i.column,{type:"waterfall",upColorProp:"fill",pointValKey:"y",translate:function(){var a=this.options,b=this.yAxis,c,d,e,f,h,k,g,j,m,l=a.threshold,X=a.stacking;i.column.prototype.translate.apply(this);g=j=l;d=this.points;for(c=0,a=d.length;c0?b.translate(g,0,1)-f.y:b.translate(g,0,1)-b.translate(g-k,0,1);g+=k}f.height<0&&(f.y+=f.height,f.height*=-1);e.plotY=f.y=E(f.y)-this.borderWidth%2/2;f.height=O(E(f.height),0.001);e.yBottom=f.y+f.height;f=e.plotY+(e.negative?f.height:0);this.chart.inverted?e.tooltipPos[0]=b.len- 39 | f:e.tooltipPos[1]=f}},processData:function(a){var b=this.yData,c=this.options.data,d,e=b.length,f,h,k,g,j,m;h=f=k=g=this.options.threshold||0;for(m=0;m0?(f.pointAttr=e,f.color=d):f.pointAttr=a.pointAttr})},getGraphPath:function(){var a=this.data,b=a.length,c=E(this.options.lineWidth+this.borderWidth)%2/2,d=[],e,f,h;for(h=1;h0?(h-a)/i:0.5,m&&h>=0&&(h=Math.sqrt(h)),h=y.ceil(c+h*(d-c))/2),g.push(h);this.radii=g},animate:function(a){var b=this.options.animation;if(!a)s(this.points,function(a){var d=a.graphic,a=a.shapeArgs;d&&a&&(d.attr("r",1),d.animate({r:a.r},b))}),this.animate=null},translate:function(){var a,b=this.data,c,d,e=this.radii;i.scatter.prototype.translate.call(this); 45 | for(a=b.length;a--;)c=b[a],d=e?e[a]:0,typeof d==="number"&&d>=this.minPxSize/2?(c.shapeType="circle",c.shapeArgs={x:c.plotX,y:c.plotY,r:d},c.dlBox={x:c.plotX-d,y:c.plotY-d,width:2*d,height:2*d}):c.shapeArgs=c.plotY=c.dlBox=G},drawLegendSymbol:function(a,b){var c=B(a.itemStyle.fontSize)/2;b.legendSymbol=this.chart.renderer.circle(c,a.baseline-c,c).attr({zIndex:3}).add(b.legendGroup);b.legendSymbol.isMarker=!0},drawPoints:i.column.prototype.drawPoints,alignDataLabel:i.column.prototype.alignDataLabel, 46 | buildKDTree:v,applyZones:v});N.prototype.beforePadding=function(){var a=this,b=this.len,c=this.chart,d=0,e=b,f=this.isXAxis,h=f?"xData":"yData",k=this.min,g={},j=y.min(c.plotWidth,c.plotHeight),m=Number.MAX_VALUE,l=-Number.MAX_VALUE,i=this.max-k,x=b/i,n=[];s(this.series,function(b){var h=b.options;if(b.bubblePadding&&(b.visible||!c.options.chart.ignoreHiddenSeries))if(a.allowZoomOutside=!0,n.push(b),f)s(["minSize","maxSize"],function(a){var b=h[a],f=/%$/.test(b),b=B(b);g[a]=f?j*b/100:b}),b.minPxSize= 47 | g.minSize,b.maxPxSize=g.maxSize,b=b.zData,b.length&&(m=o(h.zMin,y.min(m,y.max(Q(b),h.displayNegative===!1?h.zThreshold:-Number.MAX_VALUE))),l=o(h.zMax,y.max(l,R(b))))});s(n,function(a){var b=a[h],c=b.length,g;f&&a.getRadii(m,l,a.minPxSize,a.maxPxSize);if(i>0)for(;c--;)typeof b[c]==="number"&&(g=a.radii[c],d=Math.min((b[c]-k)*x-g,d),e=Math.max((b[c]-k)*x+g,e))});n.length&&i>0&&!this.isLog&&(e-=b,x*=(b+d-e)/b,s([["min","userMin",d],["max","userMax",e]],function(b){o(a.options[b[0]],a[b[1]])===G&&(a[b[0]]+= 48 | b[2]/x)}))};(function(){function a(a,b,c){a.call(this,b,c);if(this.chart.polar)this.closeSegment=function(a){var b=this.xAxis.center;a.push("L",b[0],b[1])},this.closedStacks=!0}function b(a,b){var c=this.chart,g=this.options.animation,d=this.group,e=this.markerGroup,l=this.xAxis.center,i=c.plotLeft,n=c.plotTop;if(c.polar){if(c.renderer.isSVG)g===!0&&(g={}),b?(c={translateX:l[0]+i,translateY:l[1]+n,scaleX:0.001,scaleY:0.001},d.attr(c),e&&e.attr(c)):(c={translateX:i,translateY:n,scaleX:1,scaleY:1}, 49 | d.animate(c,g),e&&e.animate(c,g),this.animate=null)}else a.call(this,b)}var c=w.prototype,d=T.prototype,e;c.searchPointByAngle=function(a){var b=this.chart,c=this.xAxis.pane.center;return this.searchKDTree({clientX:180+Math.atan2(a.chartX-c[0]-b.plotLeft,a.chartY-c[1]-b.plotTop)*(-180/Math.PI)})};r(c,"buildKDTree",function(a){if(this.chart.polar)this.kdByAngle?this.searchPoint=this.searchPointByAngle:this.kdDimensions=2;a.apply(this)});c.toXY=function(a){var b,c=this.chart,g=a.plotX;b=a.plotY;a.rectPlotX= 50 | g;a.rectPlotY=b;b=this.xAxis.postTranslate(a.plotX,this.yAxis.len-b);a.plotX=a.polarPlotX=b.x-c.plotLeft;a.plotY=a.polarPlotY=b.y-c.plotTop;this.kdByAngle?(c=(g/Math.PI*180+this.xAxis.pane.options.startAngle)%360,c<0&&(c+=360),a.clientX=c):a.clientX=a.plotX};i.area&&r(i.area.prototype,"init",a);i.areaspline&&r(i.areaspline.prototype,"init",a);i.spline&&r(i.spline.prototype,"getPointSpline",function(a,b,c,g){var d,e,l,i,n,p,o;if(this.chart.polar){d=c.plotX;e=c.plotY;a=b[g-1];l=b[g+1];this.connectEnds&& 51 | (a||(a=b[b.length-2]),l||(l=b[1]));if(a&&l)i=a.plotX,n=a.plotY,b=l.plotX,p=l.plotY,i=(1.5*d+i)/2.5,n=(1.5*e+n)/2.5,l=(1.5*d+b)/2.5,o=(1.5*e+p)/2.5,b=Math.sqrt(Math.pow(i-d,2)+Math.pow(n-e,2)),p=Math.sqrt(Math.pow(l-d,2)+Math.pow(o-e,2)),i=Math.atan2(n-e,i-d),n=Math.atan2(o-e,l-d),o=Math.PI/2+(i+n)/2,Math.abs(i-o)>Math.PI/2&&(o-=Math.PI),i=d+Math.cos(o)*b,n=e+Math.sin(o)*b,l=d+Math.cos(Math.PI+o)*p,o=e+Math.sin(Math.PI+o)*p,c.rightContX=l,c.rightContY=o;g?(c=["C",a.rightContX||a.plotX,a.rightContY|| 52 | a.plotY,i||d,n||e,d,e],a.rightContX=a.rightContY=null):c=["M",d,e]}else c=a.call(this,b,c,g);return c});r(c,"translate",function(a){var b=this.chart;a.call(this);if(b.polar&&(this.kdByAngle=b.tooltip&&b.tooltip.shared,!this.preventPostTranslate)){a=this.points;for(b=a.length;b--;)this.toXY(a[b])}});r(c,"getSegmentPath",function(a,b){var c=this.points;if(this.chart.polar&&this.options.connectEnds!==!1&&b[b.length-1]===c[c.length-1]&&c[0].y!==null)this.connectEnds=!0,b=[].concat(b,[c[0]]);return a.call(this, 53 | b)});r(c,"animate",b);if(i.column)e=i.column.prototype,r(e,"animate",b),r(e,"translate",function(a){var b=this.xAxis,c=this.yAxis.len,d=b.center,e=b.startAngleRad,i=this.chart.renderer,l,n;this.preventPostTranslate=!0;a.call(this);if(b.isRadial){b=this.points;for(n=b.length;n--;)l=b[n],a=l.barX+e,l.shapeType="path",l.shapeArgs={d:i.symbols.arc(d[0],d[1],c-l.plotY,null,{start:a,end:a+l.pointWidth,innerR:c-o(l.yBottom,c)})},this.toXY(l),l.tooltipPos=[l.plotX,l.plotY],l.ttBelow=l.plotY>d[1]}}),r(e,"alignDataLabel", 54 | function(a,b,d,e,j,i){if(this.chart.polar){a=b.rectPlotX/Math.PI*180;if(e.align===null)e.align=a>20&&a<160?"left":a>200&&a<340?"right":"center";if(e.verticalAlign===null)e.verticalAlign=a<45||a>315?"bottom":a>135&&a<225?"top":"middle";c.alignDataLabel.call(this,b,d,e,j,i)}else a.call(this,b,d,e,j,i)});r(d,"getCoordinates",function(a,b){var c=this.chart,d={xAxis:[],yAxis:[]};c.polar?s(c.axes,function(a){var e=a.isXAxis,f=a.center,i=b.chartX-f[0]-c.plotLeft,f=b.chartY-f[1]-c.plotTop;d[e?"xAxis":"yAxis"].push({axis:a, 55 | value:a.translate(e?Math.PI-Math.atan2(i,f):Math.sqrt(Math.pow(i,2)+Math.pow(f,2)),!0)})}):d=a.call(this,b);return d})})()})(Highcharts); 56 | -------------------------------------------------------------------------------- /output/report/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /output/report/report.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Report 6 | 7 | 8 | 9 | 10 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 |
106 |
107 |
108 |
109 |

Report

110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 | 122 |
123 | 133 | 134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 | 142 | 143 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | UIAutoTest 8 | UIAutoTest 9 | 1.0-SNAPSHOT 10 | 11 | 12 | org.seleniumhq.selenium 13 | selenium-java 14 | 2.47.1 15 | 16 | 17 | io.appium 18 | java-client 19 | 3.2.0 20 | 21 | 22 | org.seleniumhq.selenium 23 | selenium-server 24 | 2.47.1 25 | 26 | 27 | org.uncommons 28 | reportng 29 | 1.1.4 30 | 31 | 32 | log4j 33 | log4j 34 | 1.2.12 35 | test 36 | 37 | 38 | org.testng 39 | testng 40 | 6.9.4 41 | test 42 | 43 | 44 | com.thoughtworks.qdox 45 | qdox 46 | 1.12.1 47 | compile 48 | 49 | 50 | net.sf.saxon 51 | saxon 52 | 8.7 53 | compile 54 | 55 | 56 | org.freemarker 57 | freemarker 58 | 2.3.18 59 | test 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /prepare.sh: -------------------------------------------------------------------------------- 1 | ps -A|grep node|grep -v grep|awk 'NR=1 {print $1}'|xargs kill -9 2 | . /Users/apple/.bash_profile 3 | . /etc/profile 4 | . /etc/bashrc 5 | mvn clean;mvn test-compile -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | echo `date +"%Y%m%d:%H%M"` > date.txt 2 | sh prepare.sh 3 | java -jar BatchRunAppUI.jar 4 | sh handleResult.sh y y 5 | -------------------------------------------------------------------------------- /src/config.properties: -------------------------------------------------------------------------------- 1 | retrycount=0 2 | miDevices=BYEYTOB6NRV8DQ5H=720x1280 3 | sourcecodedir=src/test 4 | sourcecodeencoding=UTF-8 -------------------------------------------------------------------------------- /src/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.logger.dev_log=INFO, stdout,dev 2 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 3 | log4j.appender.stdout.Threshold=INFO 4 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 5 | log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %c : %m%n 6 | 7 | log4j.appender.dev=org.apache.log4j.DailyRollingFileAppender 8 | log4j.appender.dev.Append=true 9 | log4j.appender.dev.DatePattern='.'yyyy-MM-dd 10 | log4j.appender.dev.File=output/${date}/${time}/${deviceID}/logs/run.log 11 | #log4j.appender.dev.File=output/logs/${date}/${deviceID}/run.log 12 | #log4j.appender.dev.File=output/logs/${log.info.file} 13 | #log4j.appender.dev.File=output/logs/dev.log 14 | log4j.appender.dev.Threshold=INFO 15 | log4j.appender.dev.layout=org.apache.log4j.PatternLayout 16 | log4j.appender.dev.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %c : %m%n 17 | log4j.additivity.dev=false 18 | 19 | log4j.logger.appium_log=INFO, appium 20 | log4j.appender.appium=org.apache.log4j.DailyRollingFileAppender 21 | log4j.appender.appium.Append=true 22 | log4j.appender.appium.DatePattern='.'yyyy-MM-dd 23 | log4j.appender.appium.File=output/${date}/${time}/${deviceID}/logs/server.log 24 | #log4j.appender.appium.File=output/logs/${date}/${deviceID}/server.log 25 | #log4j.appender.appium.File=output/logs/${log.appium.file} 26 | #log4j.appender.appium.File=output/logs/appium.log 27 | log4j.appender.appium.Threshold=INFO 28 | log4j.appender.appium.layout=org.apache.log4j.PatternLayout 29 | log4j.appender.appium.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %c : %m%n 30 | log4j.additivity.appium=false 31 | 32 | ## 应用于控制台 33 | #log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender 34 | #log4j.appender.CONSOLE.Threshold=INFO 35 | #log4j.appender.CONSOLE.Target=System.out 36 | #log4j.appender.CONSOLE.Encoding=GBK 37 | #log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout 38 | #log4j.appender.CONSOLE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n 39 | # 40 | ## 每天新建日志 41 | #log4j.appender.A1=org.apache.log4j.DailyRollingFileAppender 42 | #log4j.appender.A1.File=C:/log4j/log 43 | #log4j.appender.A1.Encoding=GBK 44 | #log4j.appender.A1.Threshold=DEBUG 45 | #log4j.appender.A1.DatePattern='.'yyyy-MM-dd 46 | #log4j.appender.A1.layout=org.apache.log4j.PatternLayout 47 | #log4j.appender.A1.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L : %m%n 48 | # 49 | ##应用于文件 50 | #log4j.appender.FILE=org.apache.log4j.FileAppender 51 | #log4j.appender.FILE.File=C:/log4j/file.log 52 | #log4j.appender.FILE.Append=false 53 | #log4j.appender.FILE.Encoding=GBK 54 | #log4j.appender.FILE.layout=org.apache.log4j.PatternLayout 55 | #log4j.appender.FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n 56 | # 57 | ## 应用于文件回滚 58 | #log4j.appender.ROLLING_FILE=org.apache.log4j.RollingFileAppender 59 | #log4j.appender.ROLLING_FILE.Threshold=ERROR 60 | #log4j.appender.ROLLING_FILE.File=rolling.log 61 | #log4j.appender.ROLLING_FILE.Append=true 62 | #log4j.appender.CONSOLE_FILE.Encoding=GBK 63 | #log4j.appender.ROLLING_FILE.MaxFileSize=10KB 64 | #log4j.appender.ROLLING_FILE.MaxBackupIndex=1 65 | #log4j.appender.ROLLING_FILE.layout=org.apache.log4j.PatternLayout 66 | #log4j.appender.ROLLING_FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n 67 | # 68 | ##自定义Appender 69 | #log4j.appender.im = net.cybercorlin.util.logger.appender.IMAppender 70 | #log4j.appender.im.host = mail.cybercorlin.net 71 | #log4j.appender.im.username = username 72 | #log4j.appender.im.password = password 73 | #log4j.appender.im.recipient = yyflyons@163.com 74 | #log4j.appender.im.layout=org.apache.log4j.PatternLayout 75 | #log4j.appender.im.layout.ConversionPattern =[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n 76 | # 77 | ##应用于socket 78 | #log4j.appender.SOCKET=org.apache.log4j.RollingFileAppender 79 | #log4j.appender.SOCKET.RemoteHost=localhost 80 | #log4j.appender.SOCKET.Port=5001 81 | #log4j.appender.SOCKET.LocationInfo=true 82 | ## Set up for Log Facter 5 83 | #log4j.appender.SOCKET.layout=org.apache.log4j.PatternLayout 84 | #log4j.appender.SOCET.layout.ConversionPattern=[start]%d{DATE}[DATE]%n%p[PRIORITY]%n%x[NDC]%n%t[THREAD]%n%c[CATEGORY]%n%m[MESSAGE]%n%n 85 | ## Log Factor 5 Appender 86 | #log4j.appender.LF5_APPENDER=org.apache.log4j.lf5.LF5Appender 87 | #log4j.appender.LF5_APPENDER.MaxNumberOfRecords=2000 88 | # 89 | ## 发送日志给邮件 90 | #log4j.appender.MAIL=org.apache.log4j.net.SMTPAppender 91 | #log4j.appender.MAIL.Threshold=FATAL 92 | #log4j.appender.MAIL.BufferSize=10 93 | #log4j.appender.MAIL.From=yyflyons@163.com 94 | #log4j.appender.MAIL.SMTPHost=www.wusetu.com 95 | #log4j.appender.MAIL.Subject=Log4J Message 96 | #log4j.appender.MAIL.To=yyflyons@126.com 97 | #log4j.appender.MAIL.layout=org.apache.log4j.PatternLayout 98 | #log4j.appender.MAIL.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n 99 | -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/base/AutoTestBase.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.base; 2 | 3 | import com.xxxx.uiautotest.base.operateFactory.AndroidOperate; 4 | import com.xxxx.uiautotest.base.operateFactory.AppOperate; 5 | import com.xxxx.uiautotest.base.operateFactory.IosOperate; 6 | import com.xxxx.uiautotest.base.operateFactory.WebOperate; 7 | import com.xxxx.uiautotest.util.*; 8 | import io.appium.java_client.AppiumDriver; 9 | import io.appium.java_client.android.AndroidDriver; 10 | import io.appium.java_client.ios.IOSDriver; 11 | import io.appium.java_client.remote.MobileCapabilityType; 12 | import org.apache.commons.io.FileUtils; 13 | import org.openqa.selenium.OutputType; 14 | import org.openqa.selenium.TakesScreenshot; 15 | import org.openqa.selenium.WebDriver; 16 | import org.openqa.selenium.chrome.ChromeDriver; 17 | import org.openqa.selenium.firefox.FirefoxDriver; 18 | import org.openqa.selenium.ie.InternetExplorerDriver; 19 | import org.openqa.selenium.opera.OperaDriver; 20 | import org.openqa.selenium.remote.CapabilityType; 21 | import org.openqa.selenium.remote.DesiredCapabilities; 22 | import org.openqa.selenium.remote.RemoteWebDriver; 23 | import org.openqa.selenium.safari.SafariDriver; 24 | import org.openqa.selenium.support.events.EventFiringWebDriver; 25 | import org.testng.annotations.AfterMethod; 26 | import org.testng.annotations.AfterSuite; 27 | import org.testng.annotations.BeforeSuite; 28 | import org.testng.annotations.Parameters; 29 | 30 | import java.io.File; 31 | import java.io.IOException; 32 | import java.net.MalformedURLException; 33 | import java.net.URL; 34 | import java.util.Map; 35 | import java.util.concurrent.TimeUnit; 36 | 37 | /** 38 | * Created by yuyilong on 15/6/2. 39 | */ 40 | public class AutoTestBase { 41 | protected static WebDriver driver; 42 | protected static AppOperate operateBase; 43 | protected static WebOperate webOperate; 44 | protected String port; 45 | protected static String date; 46 | protected static String time; 47 | protected static int timeout; 48 | protected static String appPackage; 49 | public static String platformName; 50 | public static String udid; 51 | public static String browser_name; 52 | 53 | public WebDriver getDriver() { 54 | return driver; 55 | } 56 | 57 | /** 58 | * Description: Prepare before Suite. 59 | * 测试套件运行前准备:启动driver server、driver client,配置相关参数。 60 | * 61 | * @param filePath app的安装包路径 62 | * @param appName app的安装包名 63 | * @param platformName app平台 android/ios 64 | * @param platformVersion app运行平台的版本 65 | * @param deviceName app设备名称,ios参数,例如:iPhone 6 66 | * @param appPackage app包名,android参数,例如:com.xxxx.xxxx 67 | * @param appActivity android参数,例如:com.xxxx.xxxx 68 | * @param port app的服务端口 69 | * @param udid app的设备识别符 70 | * @param timeout web/app等待超时的时间,单位:秒 71 | * @param browser_name web的浏览器类别,例如:chrome 72 | * @param remote_url web的远程运行的url,例如:http://172.1.2.3:8888/wd/hub 73 | */ 74 | @BeforeSuite(alwaysRun = true) 75 | @Parameters({"filePath", "appName", "platformName", "platformVersion", "deviceName", "appPackage", "appActivity", "port", "udid", "timeout", "browser_name", "remote_url"}) 76 | public void beforeSuite(String filePath, String appName, String platformName, String platformVersion, String deviceName, String appPackage, String appActivity, String port, String udid, int timeout, String browser_name, String remote_url) { 77 | this.port = port; 78 | this.timeout = timeout; 79 | AutoTestBase.platformName = platformName; 80 | AutoTestBase.udid = udid; 81 | AutoTestBase.browser_name = browser_name; 82 | AutoTestBase.appPackage = appPackage; 83 | 84 | try { 85 | File dateFile = new File("date.txt"); 86 | if (dateFile.exists()) { 87 | date = Tools.readAll("date.txt").split(":")[0].trim(); 88 | time = Tools.readAll("date.txt").split(":")[1].trim(); 89 | Log.logInfo("当前日期:" + date); 90 | Log.logInfo("当前时间:" + time); 91 | } else { 92 | Log.logInfo("date.txt 文件不存在!"); 93 | System.exit(0); 94 | } 95 | } catch (IOException e) { 96 | e.printStackTrace(); 97 | } 98 | 99 | if (platformName.toLowerCase().contains("android") || platformName.toLowerCase().contains("ios")) { 100 | File appDir = new File(filePath); 101 | File app = new File(appDir, appName); 102 | 103 | StartAppiumServer startAppiumServer = new StartAppiumServer(port, udid); 104 | 105 | System.setProperty("date", date); 106 | System.setProperty("time", time); 107 | System.setProperty("deviceID", udid); 108 | 109 | Log.logInfo("MobileCapabilityType.filePath = " + filePath); 110 | Log.logInfo("MobileCapabilityType.appName = " + appName); 111 | Log.logInfo("MobileCapabilityType.platformName = " + platformName); 112 | Log.logInfo("MobileCapabilityType.platformVersion = " + platformVersion); 113 | Log.logInfo("MobileCapabilityType.deviceName = " + deviceName); 114 | Log.logInfo("MobileCapabilityType.appPackage = " + appPackage); 115 | Log.logInfo("MobileCapabilityType.appActivity = " + appActivity); 116 | Log.logInfo("MobileCapabilityType.port = " + port); 117 | Log.logInfo("MobileCapabilityType.udid = " + udid); 118 | 119 | startAppiumServer.start(); 120 | 121 | try { 122 | Thread.sleep(30000); 123 | } catch (InterruptedException e) { 124 | e.printStackTrace(); 125 | } 126 | 127 | DesiredCapabilities capabilities = new DesiredCapabilities(); 128 | capabilities.setCapability(MobileCapabilityType.APPIUM_VERSION, "1.0"); 129 | capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, platformName); 130 | capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, platformVersion); 131 | capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); 132 | capabilities.setCapability(MobileCapabilityType.UDID, udid); 133 | capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, deviceName); 134 | capabilities.setCapability(MobileCapabilityType.APP_PACKAGE, appPackage); 135 | capabilities.setCapability("unicodeKeyboard", "True"); 136 | capabilities.setCapability("resetKeyboard", "True"); 137 | capabilities.setCapability(MobileCapabilityType.APP_ACTIVITY, appActivity); 138 | 139 | if (platformName.toLowerCase().contains("android")) { 140 | String[] miUdid = StaticConfig.getMiUdid(); 141 | capabilities.setCapability("noSign", true); 142 | 143 | try { 144 | if (Tools.isMatch(udid, miUdid)) { 145 | int tapx; 146 | int tapy; 147 | Map miResolution = StaticConfig.getMiResolution(); 148 | 149 | tapx = Integer.parseInt(miResolution.get(udid).split("x")[0]); 150 | tapy = Integer.parseInt(miResolution.get(udid).split("x")[1]); 151 | HandleSafetyTips handleSafetyTipsOne = new HandleSafetyTips(udid, tapx * 7 / 10, tapy * 95 / 100); 152 | HandleSafetyTips handleSafetyTipsTwo = new HandleSafetyTips(udid, tapx * 7 / 10, tapy * 7 / 10); 153 | handleSafetyTipsOne.start(); 154 | driver = new AndroidDriver(new URL("http://127.0.0.1:" + port + "/wd/hub"), capabilities); 155 | handleSafetyTipsTwo.start(); 156 | } else { 157 | driver = new AndroidDriver(new URL("http://127.0.0.1:" + port + "/wd/hub"), capabilities); 158 | } 159 | } catch (MalformedURLException e) { 160 | e.printStackTrace(); 161 | } 162 | operateBase = new AndroidOperate((AndroidDriver) driver); 163 | } else { 164 | capabilities.setCapability("autoAcceptAlerts", true); 165 | capabilities.setCapability("language", "zh-Hans"); 166 | try { 167 | // driver = new EventFiringWebDriver(new IOSDriver(new URL("http://127.0.0.1:"+port+"/wd/hub"), capabilities)).register(executeListener); 168 | driver = new IOSDriver(new URL("http://127.0.0.1:" + port + "/wd/hub"), capabilities); 169 | } catch (MalformedURLException e) { 170 | e.printStackTrace(); 171 | } 172 | operateBase = new IosOperate((IOSDriver) driver); 173 | } 174 | } else { 175 | System.setProperty("log.info.file", "web_" + browser_name + ".log"); 176 | // System.setProperty("log.appium.file", "appium_" + browser_name + ".log"); 177 | Log.logInfo("CapabilityType.BROWSER_NAME = " + browser_name); 178 | Log.logInfo("Remote_url = " + remote_url); 179 | 180 | DesiredCapabilities capabilities = new DesiredCapabilities(); 181 | capabilities.setCapability(CapabilityType.BROWSER_NAME, browser_name); 182 | 183 | String driverServer; 184 | String driverServerPath = "tools/driverServer/"; 185 | String processName = null; 186 | ExecuteListener executeListener = new ExecuteListener(); 187 | 188 | if (browser_name.toLowerCase().contains("chrome")) { 189 | driverServer = Tools.isWindows() ? "chromedriver.exe" : "chromedriver"; 190 | processName = Tools.isWindows() ? "chrome.exe" : "\"Google Chrome\""; 191 | System.setProperty("webdriver.chrome.driver", driverServerPath + driverServer); 192 | } else if (browser_name.toLowerCase().contains("ie")) { 193 | driverServer = "IEDriverServer.exe"; 194 | processName = "iexplore.exe"; 195 | System.setProperty("webdriver.ie.driver", driverServerPath + driverServer); 196 | } else if (browser_name.toLowerCase().contains("safari")) { 197 | driverServer = Tools.isWindows() ? "safari.exe" : "safari"; 198 | processName = Tools.isWindows() ? "safari.exe" : "Safari"; 199 | System.setProperty("webdriver.safari.driver", driverServerPath + driverServer); 200 | } else if (browser_name.toLowerCase().contains("opera")) { 201 | driverServer = Tools.isWindows() ? "operadriver.exe" : "operadriver"; 202 | processName = Tools.isWindows() ? "opera.exe" : "Opera"; 203 | System.setProperty("webdriver.opera.driver", driverServerPath + driverServer); 204 | } else if (browser_name.toLowerCase().contains("firefox")) { 205 | processName = Tools.isWindows() ? "firefox.exe" : "Firefox"; 206 | } 207 | 208 | Tools.killProcess(processName); 209 | 210 | try { 211 | if (null != remote_url && !"".equals(remote_url)) { 212 | if (browser_name.toLowerCase().contains("chrome")) { 213 | driver = new EventFiringWebDriver(new RemoteWebDriver(new URL(remote_url), DesiredCapabilities.chrome())).register(executeListener); 214 | } else if (browser_name.toLowerCase().contains("firefox")) { 215 | driver = new EventFiringWebDriver(new RemoteWebDriver(new URL(remote_url), DesiredCapabilities.firefox())).register(executeListener); 216 | } else if (browser_name.toLowerCase().contains("ie")) { 217 | driver = new EventFiringWebDriver(new RemoteWebDriver(new URL(remote_url), DesiredCapabilities.internetExplorer())).register(executeListener); 218 | } else if (browser_name.toLowerCase().contains("safari")) { 219 | driver = new EventFiringWebDriver(new RemoteWebDriver(new URL(remote_url), DesiredCapabilities.safari())).register(executeListener); 220 | } else if (browser_name.toLowerCase().contains("opera")) { 221 | driver = new EventFiringWebDriver(new RemoteWebDriver(new URL(remote_url), DesiredCapabilities.opera())).register(executeListener); 222 | } 223 | } else { 224 | if (browser_name.toLowerCase().contains("chrome")) { 225 | driver = new EventFiringWebDriver(new ChromeDriver()).register(executeListener); 226 | } else if (browser_name.toLowerCase().contains("firefox")) { 227 | driver = new EventFiringWebDriver(new FirefoxDriver()).register(executeListener); 228 | } else if (browser_name.toLowerCase().contains("ie")) { 229 | driver = new EventFiringWebDriver(new InternetExplorerDriver()).register(executeListener); 230 | } else if (browser_name.toLowerCase().contains("safari")) { 231 | driver = new EventFiringWebDriver(new SafariDriver()).register(executeListener); 232 | } else if (browser_name.toLowerCase().contains("opera")) { 233 | driver = new EventFiringWebDriver(new OperaDriver()).register(executeListener); 234 | } 235 | } 236 | } catch (MalformedURLException e) { 237 | e.printStackTrace(); 238 | } 239 | webOperate = new WebOperate(driver); 240 | driver.manage().timeouts().pageLoadTimeout(timeout, TimeUnit.SECONDS); 241 | driver.manage().timeouts().setScriptTimeout(timeout, TimeUnit.SECONDS); 242 | } 243 | driver.manage().timeouts().implicitlyWait(timeout, TimeUnit.SECONDS); 244 | } 245 | 246 | /** 247 | * Description: Back to home page after method. 248 | * app测试方法执行完成后,回到首页。 249 | */ 250 | @AfterMethod(alwaysRun = true) 251 | public void afterMethod() { 252 | if (platformName.toLowerCase().contains("android") || platformName.toLowerCase().contains("ios")) { 253 | operateBase.backToHomePage(); 254 | } 255 | } 256 | 257 | /** 258 | * Description: Driver quit after suit. 259 | * 测试套件执行后关闭driver。 260 | */ 261 | @AfterSuite(alwaysRun = true) 262 | public void afterSuit() { 263 | if (platformName.toLowerCase().contains("android") || platformName.toLowerCase().contains("ios")) { 264 | ((AppiumDriver) driver).removeApp(this.appPackage); 265 | } 266 | driver.quit(); 267 | } 268 | 269 | /** 270 | * Description: Screen Shot. 271 | * 实现屏幕截屏功能。 272 | * 273 | * @param fileName 截屏文件名 274 | * @throws IOException IO异常 275 | */ 276 | public static String ScreenShot(String fileName) throws IOException { 277 | String filePath_screenShots; 278 | String filePath_testngReports = "output" + File.separator + date + File.separator + time + File.separator + udid + File.separator + "testngReports" + File.separator; 279 | if (platformName.toLowerCase().contains("android") || platformName.toLowerCase().contains("ios")) { 280 | filePath_screenShots = "output" + File.separator + date + File.separator + time + File.separator + udid + File.separator + "screenShots" + File.separator; 281 | } else { 282 | filePath_screenShots = "output" + File.separator + date + File.separator + time + File.separator + browser_name + File.separator + "screenShots" + File.separator; 283 | } 284 | FileUtils.copyFile(((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE), new File(filePath_screenShots + fileName + ".png")); 285 | return filePath_testngReports; 286 | } 287 | } -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/base/ExecuteListener.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.base; 2 | 3 | import com.xxxx.uiautotest.util.Log; 4 | import org.openqa.selenium.By; 5 | import org.openqa.selenium.NoSuchElementException; 6 | import org.openqa.selenium.WebDriver; 7 | import org.openqa.selenium.WebElement; 8 | import org.openqa.selenium.support.events.WebDriverEventListener; 9 | 10 | /** 11 | * Created by yuyilong on 15/9/24. 12 | */ 13 | public class ExecuteListener implements WebDriverEventListener { 14 | private By lastFindBy; 15 | private String originalValue; 16 | 17 | /** 18 | * Description: overriding method. 19 | * 内容描述:监听导航事件,打印日志。 20 | * 21 | * @param url url 22 | * @param driver driver对象 23 | */ 24 | public void beforeNavigateTo(String url, WebDriver driver) { 25 | Log.logInfo("WebDriver navigating to:'" + url + "'"); 26 | } 27 | 28 | /** 29 | * Description: overriding method. 30 | * 内容描述:监听改变值之前的事件,保存初始值。 31 | * 32 | * @param element 页面元素对象 33 | * @param driver driver对象 34 | */ 35 | public void beforeChangeValueOf(WebElement element, WebDriver driver) { 36 | originalValue = element.getAttribute("value"); 37 | } 38 | 39 | /** 40 | * Description: overriding method. 41 | * 内容描述:监听值改变值之后的事件,打印日志。 42 | * 43 | * @param element 页面元素对象 44 | * @param driver driver对象 45 | */ 46 | public void afterChangeValueOf(WebElement element, WebDriver driver) { 47 | Log.logInfo("WebDriver changing value in element found " + lastFindBy + " from '" + originalValue + "' to '" + element.getAttribute("value") + "'"); 48 | } 49 | 50 | /** 51 | * Description: overriding method. 52 | * 内容描述:实现接口方法,无实现逻辑。 53 | */ 54 | public void beforeFindBy(By by, WebElement element, WebDriver driver) { 55 | lastFindBy = by; 56 | // driver.manage().timeouts().implicitlyWait(60, TimeUnit.SECONDS); 57 | } 58 | 59 | /** 60 | * Description: overriding method. 61 | * 内容描述:监听产生异常的事件,打印日志。 62 | * 63 | * @param error Throwable对象 64 | * @param driver driver对象 65 | */ 66 | public void onException(Throwable error, WebDriver driver) { 67 | if (error.getClass().equals(NoSuchElementException.class)) { 68 | Log.logError("WebDriver error: Element not found " + lastFindBy); 69 | } else { 70 | Log.logError("WebDriver error: unknown exception"); 71 | Log.logError(error.getStackTrace()); 72 | } 73 | } 74 | 75 | /** 76 | * Description: overriding method. 77 | * 内容描述:实现接口方法,无实现逻辑。 78 | */ 79 | public void beforeNavigateBack(WebDriver driver) { 80 | } 81 | 82 | /** 83 | * Description: overriding method. 84 | * 内容描述:实现接口方法,无实现逻辑。 85 | */ 86 | public void beforeNavigateForward(WebDriver driver) { 87 | } 88 | 89 | /** 90 | * Description: overriding method. 91 | * 内容描述:实现接口方法,无实现逻辑。 92 | */ 93 | public void beforeClickOn(WebElement element, WebDriver driver) { 94 | } 95 | 96 | /** 97 | * Description: overriding method. 98 | * 内容描述:实现接口方法,无实现逻辑。 99 | */ 100 | public void beforeScript(String script, WebDriver driver) { 101 | } 102 | 103 | /** 104 | * Description: overriding method. 105 | * 内容描述:监听点击事件,打印日志。 106 | * 107 | * @param element 页面元素对象 108 | * @param driver driver对象 109 | */ 110 | public void afterClickOn(WebElement element, WebDriver driver) { 111 | String locator = element.toString().split("-> ")[1]; 112 | Log.logInfo("WebDriver clicking on:'" + locator.substring(0, locator.length() - 1) + "'"); 113 | } 114 | 115 | /** 116 | * Description: overriding method. 117 | * 内容描述:实现接口方法,无实现逻辑。 118 | */ 119 | public void afterFindBy(By by, WebElement element, WebDriver driver) { 120 | } 121 | 122 | /** 123 | * Description: overriding method. 124 | * 内容描述:实现接口方法,无实现逻辑。 125 | */ 126 | public void afterNavigateBack(WebDriver driver) { 127 | } 128 | 129 | /** 130 | * Description: overriding method. 131 | * 内容描述:实现接口方法,无实现逻辑。 132 | */ 133 | public void afterNavigateForward(WebDriver driver) { 134 | } 135 | 136 | /** 137 | * Description: overriding method. 138 | * 内容描述:实现接口方法,无实现逻辑。 139 | */ 140 | public void afterNavigateTo(String url, WebDriver driver) { 141 | } 142 | 143 | /** 144 | * Description: overriding method. 145 | * 内容描述:实现接口方法,无实现逻辑。 146 | */ 147 | public void afterScript(String script, WebDriver driver) { 148 | } 149 | } -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/base/PageObjectBase.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.base; 2 | 3 | import io.appium.java_client.pagefactory.AppiumFieldDecorator; 4 | import org.openqa.selenium.WebDriver; 5 | import org.openqa.selenium.support.PageFactory; 6 | 7 | import java.util.concurrent.TimeUnit; 8 | 9 | /** 10 | * Created by yuyilong on 15/9/21. 11 | */ 12 | public class PageObjectBase { 13 | public PageObjectBase(WebDriver driver){ 14 | if(driver.getClass().getSimpleName().toLowerCase().contains("android") || driver.getClass().getSimpleName().toLowerCase().contains("ios")){ 15 | PageFactory.initElements(new AppiumFieldDecorator(driver, 6, TimeUnit.SECONDS), this); 16 | }else{ 17 | PageFactory.initElements(driver,this); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/base/operateFactory/AndroidOperate.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.base.operateFactory; 2 | 3 | import com.xxxx.uiautotest.util.Log; 4 | import io.appium.java_client.android.AndroidDriver; 5 | import io.appium.java_client.android.AndroidKeyCode; 6 | import org.openqa.selenium.By; 7 | import org.openqa.selenium.WebElement; 8 | 9 | /** 10 | * Created by yuyilong on 15/9/22. 11 | */ 12 | public class AndroidOperate extends AppOperate { 13 | 14 | private static AndroidDriver driver; 15 | 16 | public AndroidOperate(AndroidDriver driver) { 17 | super(driver); 18 | this.driver = driver; 19 | } 20 | 21 | /** 22 | * override original method 23 | * 按下指定的键,在原生方法执行时添加日志。 24 | * 25 | * @param keyEvent the element to be found. 26 | * @param LogText input log text. 27 | */ 28 | public void sendKeyEvent(int keyEvent, String LogText) { 29 | Log.logStep("[" + LogText + "] "); 30 | Log.logInfo("[" + LogText + "] "); 31 | driver.pressKeyCode(keyEvent); 32 | } 33 | 34 | /** 35 | * 向左滑屏 36 | */ 37 | public boolean swipeRightToLeft() { 38 | boolean isToHomePage = false; 39 | try { 40 | sendKeyEvent(AndroidKeyCode.KEYCODE_DPAD_RIGHT, "滑动引导页"); 41 | isToHomePage = true; 42 | } catch (Exception e) { 43 | Log.logInfo(e.getStackTrace()); 44 | } 45 | return isToHomePage; 46 | } 47 | 48 | public void acceptAlert() { 49 | Log.logInfo("accept alerts"); 50 | if (waitForText(20, "记住我的选择", "禁止", "允许")) { 51 | Log.logInfo("点击[允许]"); 52 | driver.findElement(By.name("允许")).click(); 53 | } 54 | } 55 | 56 | @Override 57 | public void scrollToUp(String TargetText) { 58 | scrollTo(TargetText); 59 | } 60 | 61 | @Override 62 | public void scrollToDown(String TargetText) { 63 | scrollTo(TargetText); 64 | } 65 | 66 | 67 | @Override 68 | public Boolean IdentifyIsDisplay(String[] TargetText, WebElement element) { 69 | Log.logInfo("等待输入验证码"); 70 | return waitForText(10, TargetText); 71 | } 72 | 73 | /** 74 | * 返回至首页。 75 | */ 76 | @Override 77 | public void backToHomePage(String[] contents) { 78 | int times = 0; 79 | while (!waitForText(5, contents)) { 80 | if (times >= 30) { 81 | Log.logInfo("尝试多次未能返回到首页,终止操作!"); 82 | break; 83 | } 84 | Log.logInfo("点击返回按钮"); 85 | driver.pressKeyCode(AndroidKeyCode.BACK); 86 | sendKeyEvent(AndroidKeyCode.BACK, "点击返回按钮"); 87 | times++; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/base/operateFactory/AppOperate.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.base.operateFactory; 2 | 3 | import com.xxxx.uiautotest.util.Log; 4 | import io.appium.java_client.AppiumDriver; 5 | import io.appium.java_client.NetworkConnectionSetting; 6 | import io.appium.java_client.TouchAction; 7 | import io.appium.java_client.android.AndroidDriver; 8 | import org.openqa.selenium.By; 9 | import org.openqa.selenium.JavascriptExecutor; 10 | import org.openqa.selenium.WebDriver; 11 | import org.openqa.selenium.WebElement; 12 | import org.openqa.selenium.remote.RemoteWebElement; 13 | import org.openqa.selenium.support.ui.ExpectedConditions; 14 | import org.openqa.selenium.support.ui.WebDriverWait; 15 | 16 | import java.util.*; 17 | import java.util.concurrent.TimeUnit; 18 | 19 | /** 20 | * Created by yuyilong on 15/9/22. 21 | */ 22 | public abstract class AppOperate { 23 | private static WebDriver driver; 24 | 25 | public AppOperate(WebDriver driver) { 26 | this.driver = driver; 27 | } 28 | 29 | public abstract boolean swipeRightToLeft(); 30 | 31 | public abstract void acceptAlert(); 32 | 33 | public abstract Boolean IdentifyIsDisplay(String[] TargetText, WebElement element); 34 | 35 | public abstract void backToHomePage(String[] contents); 36 | 37 | public abstract void scrollToUp(String TargetText); 38 | 39 | public abstract void scrollToDown(String TargetText); 40 | 41 | /** 42 | * 模拟轻触操作。 43 | * 44 | * @param width x坐标. 45 | * @param height y坐标. 46 | */ 47 | public void tapByCoordinate(double width, double height) { 48 | Map tap = new HashMap(); 49 | JavascriptExecutor js = (JavascriptExecutor) driver; 50 | tap.put("tapCount", new Double(2)); 51 | tap.put("touchCount", new Double(2)); 52 | tap.put("duration", new Double(0.5)); 53 | tap.put("x", width); 54 | tap.put("y", height); 55 | Log.logInfo("轻触坐标: x = " + width + ", y = " + height); 56 | js.executeScript("mobile: tap", tap); 57 | } 58 | 59 | /** 60 | * 模拟轻触操作。 61 | * 62 | * @param width x坐标. 63 | * @param height y坐标. 64 | * @param tapCount 轻触手指数. 65 | * @param touchCount 轻触次数. 66 | * @param duration 持续时间 67 | */ 68 | public void tapByCoordinate(double width, double height, double tapCount, double touchCount, double duration) { 69 | Map tap = new HashMap(); 70 | JavascriptExecutor js = (JavascriptExecutor) driver; 71 | tap.put("tapCount", tapCount); 72 | tap.put("touchCount", touchCount); 73 | tap.put("duration", duration); 74 | tap.put("x", width); 75 | tap.put("y", height); 76 | Log.logInfo("轻触坐标: x = " + width + ", y = " + height); 77 | js.executeScript("mobile: tap", tap); 78 | } 79 | 80 | /** 81 | * 如果目标元素不响应,可尝试重复点击。 82 | * 83 | * @param element 尝试点击的元素. 84 | * @param find 等待消失的文本,可以设置多个. 85 | * @param maxRetryTimes 重试次数 86 | * @return boolean 87 | */ 88 | public boolean clickToRetry(WebElement element, String[] find, int maxRetryTimes) { 89 | int retryTimes = 1; 90 | while (retryTimes <= maxRetryTimes) { 91 | Log.logInfo("第" + retryTimes + "次尝试点击......"); 92 | try { 93 | click(element, "点击按钮"); 94 | } catch (Exception e) { 95 | Log.logError("org.openqa.selenium.remote.SessionNotFoundException!"); 96 | } 97 | 98 | if (!waitForText(2, find)) { 99 | break; 100 | } else { 101 | retryTimes++; 102 | } 103 | } 104 | 105 | if (retryTimes > maxRetryTimes) { 106 | return false; 107 | } else { 108 | return true; 109 | } 110 | } 111 | 112 | /** 113 | * 如果目标元素不响应,可尝试重复点击。 114 | * 115 | * @param element 尝试点击的元素. 116 | * @param maxRetryTimes 重试次数 117 | * @return boolean 118 | */ 119 | public boolean clickToRetry(WebElement element, int maxRetryTimes) { 120 | int retryTimes = 1; 121 | boolean unClickable = true; 122 | 123 | while (unClickable) { 124 | Log.logInfo("第" + retryTimes + "次点击......"); 125 | try { 126 | click(element, "点击目标按钮"); 127 | unClickable = false; 128 | } catch (Exception e) { 129 | e.printStackTrace(); 130 | Log.logError(e.getStackTrace()); 131 | } 132 | 133 | retryTimes++; 134 | 135 | if (retryTimes > maxRetryTimes) 136 | break; 137 | } 138 | 139 | return !unClickable; 140 | } 141 | 142 | /** 143 | * wait for the text present in timeout setting 144 | * 在指定时间内等待,直到文本出现在页面上。 145 | * 146 | * @param timeoutInSeconds 设置等待时间,单位:秒. 147 | * @param TargetText 等待出现的文本,可以设置多个. 148 | * @return boolean 149 | */ 150 | public boolean waitForText(int timeoutInSeconds, String... TargetText) { 151 | Log.logStep("[Wait For Text : " + Arrays.toString(TargetText) + "] "); 152 | Log.logInfo("[Wait For Text : " + Arrays.toString(TargetText) + "] "); 153 | Boolean flag = false; 154 | String pageSource = null; 155 | long currentTime = System.currentTimeMillis(); 156 | while (true) { 157 | try { 158 | Thread.sleep(3 * 1000); 159 | if (driver != null) 160 | pageSource = driver.getPageSource(); 161 | 162 | } catch (InterruptedException e) { 163 | e.printStackTrace(); 164 | } 165 | 166 | if (null != pageSource) { 167 | for (int i = 0; i < TargetText.length; i++) { 168 | flag = pageSource.contains(TargetText[i]); 169 | } 170 | } 171 | 172 | if (System.currentTimeMillis() - currentTime >= timeoutInSeconds * 1000 || flag) { 173 | break; 174 | } 175 | } 176 | 177 | return flag; 178 | } 179 | 180 | /** 181 | * wait for the text present in timeout setting 182 | * 在指定时间内等待,直到文本消失在页面上。 183 | * 184 | * @param timeoutInSeconds 设置等待时间,单位:秒. 185 | * @param TargetText 等待消失的文本,可以设置多个. 186 | * @return boolean 187 | */ 188 | public boolean waitForTextDisappear(int timeoutInSeconds, String... TargetText) { 189 | Log.logStep("[Wait For Text Disappear : " + Arrays.toString(TargetText) + "] "); 190 | Log.logInfo("[Wait For Text Disappear : " + Arrays.toString(TargetText) + "] "); 191 | Boolean flag = true; 192 | long currentTime = System.currentTimeMillis(); 193 | while (true) { 194 | try { 195 | Thread.sleep(2 * 1000); 196 | } catch (InterruptedException e) { 197 | e.printStackTrace(); 198 | } 199 | String pageSource = driver.getPageSource(); 200 | 201 | for (int i = 0; i < TargetText.length; i++) { 202 | flag = (pageSource.contains(TargetText[i])) && flag; 203 | } 204 | if (System.currentTimeMillis() - currentTime >= timeoutInSeconds * 1000 || !flag) { 205 | break; 206 | } 207 | flag = true; 208 | } 209 | return !flag; 210 | } 211 | 212 | /** 213 | * override original method 214 | * 模拟点击,在原生方法执行时添加日志。 215 | * 216 | * @param element the element to be found. 217 | * @param LogText input log text. 218 | */ 219 | public void click(final WebElement element, String LogText) { 220 | Log.logStep("[" + LogText + "] "); 221 | Log.logInfo("[点击 " + element.toString().substring(element.toString().indexOf("->"))); 222 | element.click(); 223 | } 224 | 225 | /** 226 | * override original method 227 | * 模拟输入,在原生方法执行时添加日志。 228 | * 229 | * @param element the element to be found. 230 | * @param LogText input log text. 231 | * @param charSequences sendKeys content, support for multiple strings. 232 | */ 233 | public void sendKeys(final WebElement element, String LogText, CharSequence... charSequences) { 234 | Log.logStep("[" + LogText + "] "); 235 | Log.logInfo("[输入字符 " + element.toString().substring(element.toString().indexOf("->"))); 236 | element.sendKeys(charSequences); 237 | } 238 | 239 | /** 240 | * override original method 241 | * 模拟清除,在原生方法执行时添加日志。 242 | * 243 | * @param element the element to be found. 244 | * @param LogText input log text. 245 | */ 246 | public void clear(final WebElement element, String LogText) { 247 | Log.logStep("[" + LogText + "] "); 248 | Log.logInfo("[清空数据 " + element.toString().substring(element.toString().indexOf("->"))); 249 | element.clear(); 250 | } 251 | 252 | /** 253 | * override original method 254 | * 获取文本,在原生方法执行时添加日志。 255 | * 256 | * @param element the element to be found. 257 | * @param LogText input log text. 258 | * @return String 259 | */ 260 | public String getText(final WebElement element, String LogText) { 261 | Log.logStep("[" + LogText + "] "); 262 | Log.logInfo("[获取文本 " + element.toString().substring(element.toString().indexOf("->"))); 263 | return element.getText(); 264 | } 265 | 266 | /** 267 | * override original method 268 | * 获取属性,在原生方法执行时添加日志。 269 | * 270 | * @param element the element to be found. 271 | * @param LogText input log text. 272 | * @return String 273 | */ 274 | public String getAttribute(final WebElement element, String attribute, String LogText) { 275 | Log.logStep("[" + LogText + "] "); 276 | Log.logInfo("[获取文本 " + element.toString().substring(element.toString().indexOf("->"))); 277 | return element.getAttribute(attribute); 278 | } 279 | 280 | /** 281 | * override original method 282 | * 滚动到目标文本的位置,在原生方法执行时添加日志。 283 | * 284 | * @param targetText the target text to be scrollToExact. 285 | * @param LogText input log text. 286 | */ 287 | public void scroll_TargetText(String targetText, String LogText) { 288 | Log.logStep("[滑动屏幕定位目标文字:" + targetText + LogText + "] "); 289 | Log.logInfo("[滑动屏幕定位目标文字:" + targetText + "]"); 290 | ((AppiumDriver) driver).scrollToExact(targetText); 291 | } 292 | 293 | /** 294 | * override original method 295 | * 模拟手指在目标元素上持续轻触一段时间,在原生方法执行时添加日志。 296 | * 297 | * @param fingers the number of fingers. 298 | * @param element the element to be found. 299 | * @param duration duration. 300 | * @param LogText input log text. 301 | */ 302 | public void tab(int fingers, WebElement element, int duration, String LogText) { 303 | Log.logStep("[" + LogText + "] "); 304 | Log.logInfo("[轻触 " + element.toString().substring(element.toString().indexOf("->"))); 305 | ((AppiumDriver) driver).tap(fingers, element, duration); 306 | } 307 | 308 | /** 309 | * override original method 310 | * 锁屏指定时间后解锁,在原生方法执行时添加日志。 311 | * 312 | * @param LogText input log text. 313 | * @param timeoutInSeconds time for waiting, unit: second. 314 | */ 315 | public void lockScreen(String LogText, int timeoutInSeconds) { 316 | Log.logStep("[" + LogText + "] "); 317 | Log.logInfo("[" + LogText + "] "); 318 | ((AppiumDriver) driver).lockScreen(timeoutInSeconds); 319 | } 320 | 321 | /** 322 | * 长按源元素移至目标元素后释放。 323 | * 324 | * @param elementA the source element. 325 | * @param elementB the target element. 326 | * @param LogText input log text. 327 | */ 328 | public void swipe_elementA_TO_elementB(WebElement elementA, WebElement elementB, String LogText) { 329 | Log.logStep("[" + LogText + "] "); 330 | Log.logInfo("[" + LogText + "] "); 331 | new TouchAction(((AppiumDriver) driver)).longPress(elementA) 332 | .moveTo(elementB).release().perform(); 333 | } 334 | 335 | /** 336 | * 设置手机的网络连接状态,可以开关蓝牙、wifi、数据流量。 337 | * 338 | * @param LogText input log text. 339 | * @param airplaneMode airplaneMode switch. 340 | * @param wifi wifi switch. 341 | * @param data data switch. 342 | */ 343 | public void setNetworkConnection(String LogText, Boolean airplaneMode, Boolean wifi, Boolean data) { 344 | Log.logStep("[" + LogText + "] "); 345 | Log.logInfo("[" + LogText + "] "); 346 | ((AndroidDriver) driver).setNetworkConnection(new NetworkConnectionSetting(airplaneMode, wifi, data)); 347 | } 348 | 349 | /** 350 | * Js模拟向下滚动至目标元素。 351 | * 352 | * @param element the element to be found. 353 | */ 354 | public void scrollDown(WebElement element) { 355 | // java 356 | JavascriptExecutor js = (JavascriptExecutor) driver; 357 | HashMap scrollObject = new HashMap(); 358 | scrollObject.put("direction", "down"); 359 | scrollObject.put("element", ((RemoteWebElement) element).getId()); 360 | js.executeScript("mobile: scroll", scrollObject); 361 | } 362 | 363 | /** 364 | * Js模拟向上滚动至目标元素。 365 | * 366 | * @param element the element to be found. 367 | */ 368 | public void scrollUP(WebElement element) { 369 | // java 370 | JavascriptExecutor js = (JavascriptExecutor) driver; 371 | HashMap scrollObject = new HashMap(); 372 | scrollObject.put("direction", "up"); 373 | scrollObject.put("element", ((RemoteWebElement) element).getId()); 374 | js.executeScript("mobile: scroll", scrollObject); 375 | } 376 | 377 | /** 378 | * Js模拟滚动至目标文本的位置。 379 | * 380 | * @param TargetText the target text to be found. 381 | */ 382 | public void scrollTo(String TargetText) { 383 | Log.logStep("[滑动页面以发现'" + TargetText + "'] "); 384 | Log.logInfo("[滑动页面以发现'" + TargetText + "'] "); 385 | ((AppiumDriver) driver).scrollTo(TargetText); 386 | } 387 | 388 | /** 389 | * 模拟手势密码。 390 | * 391 | * @param webElements 手势密码元素对象. 392 | */ 393 | public void GesturePWD(List webElements) { 394 | int x = webElements.get(0).getSize().width / 2; 395 | int y = webElements.get(0).getSize().height / 2; 396 | TouchAction touchAction = new TouchAction((AppiumDriver) driver); 397 | touchAction.press(webElements.get(0).getLocation().x + x, webElements.get(0).getLocation().y + y); 398 | for (int i = 1; i < webElements.size(); i++) { 399 | touchAction.waitAction(500); 400 | touchAction.moveTo(webElements.get(i).getLocation().x - webElements.get(i - 1).getLocation().x, webElements.get(i).getLocation().y - webElements.get(i - 1).getLocation().y); 401 | } 402 | touchAction.release(); 403 | touchAction.perform(); 404 | } 405 | 406 | /** 407 | * 模拟手势密码。 408 | * 409 | * @param GestureContainer 手势密码盘元素对象. 410 | * @param password 手势密码数组 411 | */ 412 | public void GesturePWD(List GestureContainer, int[] password) { 413 | List webElements = new ArrayList(); 414 | for (int i = 0; i < password.length; i++) { 415 | webElements.add(GestureContainer.get(password[i] - 1)); 416 | } 417 | int x = webElements.get(0).getSize().width / 2; 418 | int y = webElements.get(0).getSize().height / 2; 419 | TouchAction touchAction = new TouchAction((AppiumDriver) driver); 420 | touchAction.press(webElements.get(0).getLocation().x + x, webElements.get(0).getLocation().y + y); 421 | for (int i = 1; i < webElements.size(); i++) { 422 | touchAction.waitAction(500); 423 | touchAction.moveTo(webElements.get(i).getLocation().x - webElements.get(i - 1).getLocation().x, webElements.get(i).getLocation().y - webElements.get(i - 1).getLocation().y); 424 | } 425 | touchAction.release(); 426 | touchAction.perform(); 427 | } 428 | 429 | /** 430 | * This Method for swipe up 431 | * 432 | * @param during wait for page loading 433 | * @author quqing 434 | */ 435 | public void swipeToDown(int during) { 436 | int width = ((AppiumDriver) driver).manage().window().getSize().width; 437 | int height = ((AppiumDriver) driver).manage().window().getSize().height; 438 | ((AppiumDriver) driver).swipe(width / 2, height / 6, during, width / 2, height * 3 / 4); 439 | } 440 | 441 | /** 442 | * This Method for swipe down 443 | * 444 | * @param during wait for page loading 445 | * @author quqing 446 | */ 447 | public void swipeToDown(int during, int startXDenominator, int startYDenominator, int endXDenominator, int endYDenominator) { 448 | int width = ((AppiumDriver) driver).manage().window().getSize().width; 449 | int height = ((AppiumDriver) driver).manage().window().getSize().height; 450 | ((AppiumDriver) driver).swipe(width / startXDenominator, height / startYDenominator, width / endXDenominator, height / endYDenominator, during); 451 | } 452 | 453 | /** 454 | * This Method for swipe up 455 | * 456 | * @param during wait for page loading 457 | * @author quqing 458 | */ 459 | public void swipeToUp(int during) { 460 | int width = ((AppiumDriver) driver).manage().window().getSize().width; 461 | int height = ((AppiumDriver) driver).manage().window().getSize().height; 462 | ((AppiumDriver) driver).swipe(width / 2, height * 3 / 4, width / 2, height / 6, during); 463 | } 464 | 465 | public void swipeToUp(int startX, int startY, int endX, int endY, int during) { 466 | int width = ((AppiumDriver) driver).manage().window().getSize().width; 467 | int height = ((AppiumDriver) driver).manage().window().getSize().height; 468 | ((AppiumDriver) driver).swipe(startX, startY, endX, endY, during); 469 | } 470 | 471 | /** 472 | * Description: set element locate timeout. 473 | * 内容描述:设置对象查找超时时间. 474 | * 475 | * @param time The amount of time to wait. 476 | * @author quqing 477 | */ 478 | public void setElementLocateTimeout(long time) { 479 | driver.manage().timeouts().implicitlyWait(time, TimeUnit.SECONDS); 480 | } 481 | 482 | /** 483 | * wait for the element clickable in timeout setting 484 | * 在指定时间内等待,直到对象能够被点击。 485 | * 486 | * @param by the element locator By 487 | * @param timeOutInSeconds The timeout in seconds when an expectation is called 488 | * @param sleepInMillis The duration in milliseconds to sleep between polls. 489 | * @return boolean 490 | * @author quqing 491 | */ 492 | public boolean waitForElementClickable(By by, long timeOutInSeconds, long sleepInMillis) { 493 | try { 494 | setElementLocateTimeout(timeOutInSeconds); 495 | WebDriverWait wait = new WebDriverWait(driver, timeOutInSeconds - 5, sleepInMillis); 496 | return wait.until(ExpectedConditions.elementToBeClickable(by)) != null; 497 | } finally { 498 | setElementLocateTimeout(timeOutInSeconds); 499 | } 500 | } 501 | 502 | /** 503 | * judge if the element is present in specified seconds 504 | * 在指定的时间内判断指定的对象是否存在,兼容AndroidDriver&IOSDriver。 505 | * 506 | * @param by the element locator By 507 | * @param seconds timeout in seconds 508 | * @return boolean 509 | */ 510 | public boolean elementExists(int seconds, By... by) { 511 | boolean exists = false; 512 | setElementLocateTimeout(1); 513 | long start = System.currentTimeMillis(); 514 | while (!exists && ((System.currentTimeMillis() - start) < seconds * 1000)) { 515 | try { 516 | if (driver instanceof AndroidDriver) { 517 | exists = driver.findElements(by[0]).size() > 0; 518 | } else { 519 | exists = driver.findElements(by[1]).size() > 0; 520 | } 521 | } catch (NoSuchElementException ne) { 522 | exists = false; 523 | } 524 | } 525 | setElementLocateTimeout(30); 526 | return exists; 527 | } 528 | 529 | /** 530 | * judge if the element is present in specified seconds 531 | * 在指定的时间内判断指定的对象是否存在。 532 | * 533 | * @param by the element locator By 534 | * @param duration the time during which something continues. 535 | * @return boolean 536 | */ 537 | public boolean elementExists(int duration, By by) { 538 | boolean exists = false; 539 | setElementLocateTimeout(1); 540 | long start = System.currentTimeMillis(); 541 | while (!exists && ((System.currentTimeMillis() - start) <= duration * 1000)) { 542 | try { 543 | exists = (driver.findElements(by).size() > 0) ? true : false; 544 | } catch (NoSuchElementException ne) { 545 | exists = false; 546 | } 547 | } 548 | setElementLocateTimeout(30); 549 | return exists; 550 | } 551 | 552 | /** 553 | * judge if the element is present in specified seconds 554 | * 在指定的时间内判断指定的对象是否存在。 555 | * 556 | * @param webElement the element 557 | * @param duration the time during which something continues. 558 | * @return boolean 559 | */ 560 | public boolean elementExists(int duration, WebElement webElement) { 561 | boolean exists = false; 562 | setElementLocateTimeout(1); 563 | long start = System.currentTimeMillis(); 564 | while (!exists && ((System.currentTimeMillis() - start) <= duration * 1000)) { 565 | try { 566 | exists = (null != webElement) ? true : false; 567 | } catch (NoSuchElementException ne) { 568 | exists = false; 569 | } 570 | } 571 | setElementLocateTimeout(30); 572 | return exists; 573 | } 574 | 575 | /** 576 | * 在指定时间内等待,直到对象能够被点击. 577 | * 578 | * @param androidBy 579 | * @param iosBy 580 | * @param element 581 | * @param timeOutInSeconds 582 | * @param sleepInMillis 583 | * @param desc 584 | * @return boolean 585 | */ 586 | public boolean clickClickableElement(By androidBy, By iosBy, WebElement element, long timeOutInSeconds, long sleepInMillis, String... desc) { 587 | boolean clickable = false; 588 | 589 | try { 590 | if (driver instanceof AndroidDriver) { 591 | clickable = waitForElementClickable(androidBy, timeOutInSeconds, sleepInMillis); 592 | } else { 593 | clickable = waitForElementClickable(iosBy, timeOutInSeconds, sleepInMillis); 594 | } 595 | 596 | if (clickable) { 597 | if (null == desc) { 598 | element.click(); 599 | } else { 600 | click(element, desc[0]); 601 | } 602 | } 603 | } catch (Exception e) { 604 | Log.logError(e.getStackTrace()); 605 | e.printStackTrace(); 606 | } finally { 607 | return clickable; 608 | } 609 | } 610 | } -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/base/operateFactory/IosOperate.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.base.operateFactory; 2 | 3 | import com.xxxx.uiautotest.util.Log; 4 | import io.appium.java_client.ios.IOSDriver; 5 | import org.openqa.selenium.By; 6 | import org.openqa.selenium.WebElement; 7 | 8 | /** 9 | * Created by yuyilong on 15/9/22. 10 | */ 11 | public class IosOperate extends AppOperate { 12 | 13 | private static IOSDriver driver; 14 | 15 | public IosOperate(IOSDriver driver) { 16 | super(driver); 17 | this.driver = driver; 18 | } 19 | 20 | /** 21 | * 模拟向左滑屏。 22 | */ 23 | @Override 24 | public boolean swipeRightToLeft() { 25 | int width = driver.manage().window().getSize().width; 26 | int height = driver.manage().window().getSize().height; 27 | try { 28 | driver.swipe(width * 9 / 10, height / 2, width * 1 / 10, height / 2, 1000); 29 | } catch (Exception e) { 30 | return false; 31 | } 32 | return true; 33 | } 34 | 35 | /** 36 | * Js模拟滚动至目标文本的位置。 37 | * 38 | * @param TargetText the target text to be found. 39 | */ 40 | public void scrollTo(String TargetText) { 41 | Log.logStep("[滑动页面以发现'" + TargetText + "'] "); 42 | Log.logInfo("[滑动页面以发现'" + TargetText + "'] "); 43 | driver.scrollTo(TargetText); 44 | } 45 | 46 | @Override 47 | public void acceptAlert() { 48 | Log.logInfo("accept alerts"); 49 | } 50 | 51 | /** 52 | * Wait for element display 53 | * 模拟等待目标显示,在原生方法执行时添加日志。 54 | * 55 | * @param TargetText input log text. 56 | * @param element the element wait for display. 57 | * @return boolean 58 | */ 59 | @Override 60 | public Boolean IdentifyIsDisplay(String[] TargetText, WebElement element) { 61 | Log.logInfo("等待输入验证码"); 62 | return element.isDisplayed(); 63 | } 64 | 65 | /** 66 | * 模拟向上滑屏。 67 | */ 68 | @Override 69 | public void scrollToUp(String TargetText) { 70 | int width = driver.manage().window().getSize().width; 71 | int height = driver.manage().window().getSize().height; 72 | Log.logStep("向上滑动屏幕以发现[" + TargetText + "]"); 73 | driver.swipe(width / 2, height * 3 / 4, width / 2, height / 6, 3000); 74 | } 75 | 76 | /** 77 | * 向下方滑动屏幕。 78 | */ 79 | public void scrollToDown(String TargetText) { 80 | int width = driver.manage().window().getSize().width; 81 | int height = driver.manage().window().getSize().height; 82 | Log.logStep("向下滑动屏幕以发现" + TargetText); 83 | while (true) { 84 | if (waitForText(5, new String[]{TargetText})) { 85 | break; 86 | } 87 | driver.swipe(width / 2, height * 9 / 10, width / 2, height * 8 / 10, 1000); 88 | } 89 | } 90 | 91 | /** 92 | * 返回至首页。 93 | * @param contents [0]:主页的标示 [1]:返回按钮的标示 94 | */ 95 | @Override 96 | public void backToHomePage(String[] contents) { 97 | int retryTimes; 98 | boolean notFind; 99 | 100 | while (!waitForText(6, contents[0])) { 101 | Log.logInfo("点击返回按钮"); 102 | if (waitForText(20, contents[1])) { 103 | notFind = true; 104 | retryTimes = 1; 105 | while (notFind) { 106 | Log.logInfo("第" + retryTimes + "次查找[返回]按钮......"); 107 | if (retryTimes > 3) 108 | break; 109 | notFind = elementExists(3, By.name(contents[1])) ? false : true; 110 | retryTimes++; 111 | } 112 | if (!notFind) { 113 | driver.findElement(By.name(contents[1])).click(); 114 | } else { 115 | Log.logInfo("点击[返回]按钮失败"); 116 | } 117 | } else { 118 | break; 119 | } 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/base/operateFactory/WebDriverOperate.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.base.operateFactory; 2 | 3 | import org.openqa.selenium.*; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by yuyilong on 15/9/24. 9 | */ 10 | public class WebDriverOperate { 11 | 12 | private By tabBy = null; 13 | private WebElement table = null; 14 | private List tabRows = null; 15 | private List tables = null; 16 | 17 | /** 18 | * construct with parameters initialize. 19 | * 20 | * @param driver 21 | * the WebDriver instance. 22 | * @param tabFinder 23 | * the By locator of the table. 24 | * @param bodyOrHead 25 | * choice of table body and head to operate. 26 | */ 27 | public WebDriverOperate(WebDriver driver, By tabFinder, String bodyOrHead) { 28 | this.tabBy = tabFinder; 29 | this.tables = driver.findElements(tabBy); 30 | if (null == tables || tables.size() == 0) { 31 | throw new NoSuchElementException("the table " + tabFinder.toString() + "was not found!"); 32 | } 33 | this.table = tables.get(0); 34 | this.tabRows = table.findElements(By.tagName(bodyOrHead)).get(0).findElements(By.tagName("tr")); 35 | if (null == tabRows || tabRows.size() == 0) { 36 | throw new InvalidElementStateException("the table " + tabFinder.toString() + "is empty!"); 37 | } 38 | } 39 | 40 | /** 41 | * construct with parameters initialize. 42 | * 43 | * @param driver 44 | * the WebDriver instance. 45 | * @param tabFinder 46 | * the By locator of the table. 47 | */ 48 | public WebDriverOperate(WebDriver driver, By tabFinder) { 49 | this.tabBy = tabFinder; 50 | this.tables = driver.findElements(tabBy); 51 | if (null == tables || tables.size() == 0) { 52 | throw new NoSuchElementException("the table " + tabFinder.toString() + "was not found!"); 53 | } 54 | this.table = tables.get(0); 55 | this.tabRows = table.findElements(By.xpath("*/tr")); 56 | if (null == tabRows || tabRows.size() == 0) { 57 | throw new InvalidElementStateException("the table " + tabFinder.toString() + "is empty!"); 58 | } 59 | } 60 | 61 | /** 62 | * to get the whole web table element. 63 | * 64 | * @return the table element. 65 | */ 66 | public WebElement tableElement() { 67 | return this.table; 68 | } 69 | 70 | /** 71 | * to get the web table head element. 72 | * 73 | * @return the first table head element. 74 | */ 75 | public WebElement tableHeader() { 76 | return this.table.findElements(By.tagName("thead")).get(0); 77 | } 78 | 79 | /** 80 | * to get the web table body element. 81 | * 82 | * @return the first table body element. 83 | */ 84 | public WebElement tableBody() { 85 | return this.table.findElements(By.tagName("tbody")).get(0); 86 | } 87 | 88 | /** 89 | * get row count of a webtable. 90 | * 91 | * @return the row count of the table. 92 | */ 93 | public int rowCount() { 94 | return tabRows.size(); 95 | } 96 | 97 | /** 98 | * get column count of a specified webtable row. 99 | * 100 | * @param rowNum 101 | * row index of your table to count. 102 | * @return the column count of the row in table. 103 | */ 104 | public int colCount(int rowNum) { 105 | return tabRows.get(rowNum - 1).findElements(By.xpath("td")).size(); 106 | } 107 | 108 | /** 109 | * get the element in the table cell by row and col index. 110 | * 111 | * @param row 112 | * row index of the table. 113 | * @param col 114 | * column index of the table. 115 | * @param type 116 | * the element type, such as "img"/"a"/"input"... 117 | * @param index 118 | * element index in the specified cell, begins with 1. 119 | * @return the table cell WebElement. 120 | */ 121 | public WebElement childItem(int row, int col, String type, int index) { 122 | List cells = tabRows.get(row - 1).findElements(By.xpath("td")); 123 | return (type.contains("cell")) ? cells.get(col - 1) : childsGetter(cells.get(col - 1), type).get(index - 1); 124 | } 125 | 126 | /** 127 | * get the cell text of the table on specified row and column. 128 | * 129 | * @param row 130 | * row index of the table. 131 | * @param col 132 | * column index of the table. 133 | * @return the cell text. 134 | */ 135 | public String cellText(int row, int col) { 136 | return childItem(row, col, "cell", 0).getText(); 137 | } 138 | 139 | /** 140 | * button/edit/checkbox are using the same html tag "input", others may be 141 | * the same.
this method will get the WebElements List accord the user 142 | * element classes. 143 | * 144 | * @param father 145 | * the father element to get childs. 146 | * @param elementClass 147 | * link/button/edit/checkbox/image/list and so on. 148 | * @return the WebElements List. 149 | */ 150 | private List childsGetter(WebElement father, String elementClass) { 151 | return father.findElements(By.tagName(elementTagGetter(elementClass))); 152 | } 153 | 154 | /** 155 | * get the tag of element by webelement type. 156 | * 157 | * @param elementType 158 | * link/button/edit/checkbox/image/list and so on. 159 | */ 160 | private String elementTagGetter(String elementType) { 161 | if (elementType.toLowerCase().trim().contains("link")) { 162 | return "a"; 163 | } else if (elementType.toLowerCase().trim().contains("button")) { 164 | return "input"; 165 | } else if (elementType.toLowerCase().trim().contains("edit")) { 166 | return "input"; 167 | } else if (elementType.toLowerCase().trim().contains("checkbox")) { 168 | return "input"; 169 | } else if (elementType.toLowerCase().trim().contains("image")) { 170 | return "img"; 171 | } else if (elementType.toLowerCase().trim().contains("list")) { 172 | return "select"; 173 | } else if (elementType.toLowerCase().trim().contains("text")) { 174 | return "textarea"; 175 | } else { 176 | return elementType.toLowerCase(); 177 | } 178 | } 179 | } -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/business/page_object/app/Elements_Example.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.business.page_object.app; 2 | 3 | import com.xxxx.uiautotest.base.PageObjectBase; 4 | import io.appium.java_client.pagefactory.AndroidFindBy; 5 | import io.appium.java_client.pagefactory.iOSFindBy; 6 | import org.openqa.selenium.WebDriver; 7 | import org.openqa.selenium.WebElement; 8 | 9 | /** 10 | * Created by yuyilong on 15/6/4. 11 | */ 12 | public class Elements_Example extends PageObjectBase { 13 | 14 | public Elements_Example(WebDriver driver) { 15 | super(driver); 16 | } 17 | 18 | /** 19 | * 首页-Tab(xxxx) 20 | */ 21 | @AndroidFindBy(xpath = "xxxx") 22 | @iOSFindBy(xpath = "xxxx") 23 | public WebElement TabTest; 24 | } -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/business/page_object/web/page_baidu.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.business.page_object.web; 2 | 3 | import com.xxxx.uiautotest.base.PageObjectBase; 4 | import org.openqa.selenium.WebDriver; 5 | import org.openqa.selenium.WebElement; 6 | import org.openqa.selenium.support.FindBy; 7 | 8 | /** 9 | * Created by yuyilong on 15/9/21. 10 | */ 11 | public class page_baidu extends PageObjectBase { 12 | 13 | public page_baidu(WebDriver driver) { 14 | super(driver); 15 | } 16 | 17 | @FindBy(xpath = ".//*[@id='kw']") 18 | public WebElement searchInput; 19 | 20 | @FindBy(xpath = ".//*[@id='su']") 21 | public WebElement searchButton; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/testsuits/testcases/app/Example_Tests.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.testsuits.testcases.app; 2 | 3 | import com.xxxx.uiautotest.base.AutoTestBase; 4 | import com.xxxx.uiautotest.business.page_object.app.Elements_Example; 5 | import com.xxxx.uiautotest.util.Log; 6 | import org.testng.Assert; 7 | import org.testng.annotations.BeforeClass; 8 | import org.testng.annotations.Parameters; 9 | import org.testng.annotations.Test; 10 | 11 | /** 12 | * Created by yuyilong on 15/12/24. 13 | */ 14 | public class Example_Tests extends AutoTestBase { 15 | private Elements_Example elements_example; 16 | 17 | @BeforeClass(alwaysRun = true) 18 | public void beforeClass() { 19 | elements_example = new Elements_Example(driver); 20 | } 21 | 22 | @Parameters({"test"}) 23 | @Test(priority = 1, groups = {"p0"}, alwaysRun = true) 24 | public void example(String test) throws Throwable { 25 | Log.logFlow("登录-点击TabTest"); 26 | operateBase.click(elements_example.TabTest, "点击首页上面的[TabTest]按钮"); 27 | Assert.assertEquals("actual","expected","message"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/testsuits/testcases/web/testExamlpe.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.testsuits.testcases.web; 2 | 3 | import com.xxxx.uiautotest.base.AutoTestBase; 4 | import com.xxxx.uiautotest.business.page_object.web.page_baidu; 5 | import org.testng.annotations.BeforeClass; 6 | import org.testng.annotations.Test; 7 | 8 | /** 9 | * Created by quqing on 15/12/10. 10 | */ 11 | public class testExamlpe extends AutoTestBase { 12 | public page_baidu elm; 13 | 14 | @BeforeClass(alwaysRun = true) 15 | public void BeforeClass() { 16 | elm = new page_baidu(driver); 17 | } 18 | 19 | @Test(groups = {"p0"}) 20 | public void openbaidu() throws InterruptedException { 21 | driver.navigate().to("http://www.baidu.com"); 22 | elm.searchInput.sendKeys("hello"); 23 | elm.searchButton.click(); 24 | } 25 | 26 | @Test(groups = {"p0"}) 27 | public void HealthCheck() throws InterruptedException { 28 | webOperate.testLinksHealth("http://172.18.16.205:8080/api_manage/", 6000); 29 | // webOperate.testLinksHealth("http://172.18.16.205:8080/api_manage/","HTTP Status [456][06][0-9]",6000); 30 | // webOperate.testLinksHealth("http://172.18.16.205:8080/api_manage/","HTTP Status [456][06][0-9]|(?i)exception"); 31 | } 32 | } -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/testsuits/testdata/Example_data.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.testsuits.testdata; 2 | 3 | import org.testng.annotations.DataProvider; 4 | 5 | /** 6 | * Created by yuyilong on 15/12/24. 7 | */ 8 | public class Example_data { 9 | @DataProvider(name = "Example_data") 10 | public static Object[][] loginData_error() { 11 | return new Object[][] { 12 | { 13 | new String("test1"), 14 | new String("test2") 15 | } 16 | }; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/util/Assertion.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.util; 2 | 3 | import org.testng.Assert; 4 | 5 | public class Assertion { 6 | 7 | public static boolean flag = true; 8 | 9 | /** 10 | * 比较两个对象是否一致。 11 | * 12 | * @param actual the actual object. 13 | * @param expected the expected object. 14 | */ 15 | public static void verifyEquals(Object actual, Object expected) { 16 | try { 17 | Assert.assertEquals(actual, expected); 18 | } catch (Error e) { 19 | flag = false; 20 | } 21 | } 22 | 23 | /** 24 | * 比较两个对象是否一致。 25 | * 26 | * @param actual the actual object. 27 | * @param expected the expected object. 28 | * @param message the message for description. 29 | */ 30 | public static void verifyEquals(Object actual, Object expected, String message) { 31 | try { 32 | Assert.assertEquals(actual, expected, message); 33 | } catch (Error e) { 34 | flag = false; 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/util/HandleSafetyTips.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.util; 2 | 3 | /** 4 | * Created by yuyilong on 15/11/8. 5 | */ 6 | public class HandleSafetyTips extends Thread { 7 | private boolean needTap = false; 8 | private int tapx = 0; 9 | private int tapy = 0; 10 | private String udid; 11 | 12 | public HandleSafetyTips(String udid, int x, int y) { 13 | tapx = x; 14 | tapy = y; 15 | this.udid = udid; 16 | needTap = true; 17 | } 18 | 19 | public void run() { 20 | try { 21 | Log.logInfo(Thread.currentThread().getName() + " 开始点击小米安全提示"); 22 | 23 | Thread.sleep(20000); 24 | if (needTap) { 25 | for (int i = 0; i < 2; i++) { 26 | Log.logInfo("点击屏幕, x = " + tapx + ", y = " + tapy); 27 | Log.logInfo("adb -s " + udid + " shell input tap " + tapx + " " + tapy); 28 | Runtime.getRuntime().exec("adb -s " + udid + " shell input tap " + tapx + " " + tapy); 29 | if (i == 0) 30 | Thread.sleep(3000); 31 | } 32 | Log.logInfo("Thread of handleSafetyTips is interrupt"); 33 | Log.logInfo("关闭小米安全提示"); 34 | } 35 | } catch (Exception e) { 36 | e.printStackTrace(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/util/Log.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.util; 2 | 3 | import org.apache.log4j.*; 4 | import org.testng.Reporter; 5 | 6 | import java.io.File; 7 | 8 | public class Log { 9 | private static Logger logger; 10 | 11 | private static String filePath = "src/log4j.properties"; 12 | 13 | static { 14 | logger = Logger.getLogger("dev_log"); 15 | PropertyConfigurator.configure(new File(filePath).getAbsolutePath()); 16 | } 17 | 18 | /** 19 | * 记录Info级别日志。 20 | * 21 | * @param message the message object. 22 | */ 23 | public static void logInfo(Object message) { 24 | logger.info("[INFO] " + message); 25 | Reporter.log(new Tools().getSimpleDateFormat() + " : " + "[INFO] " + message); 26 | } 27 | 28 | /** 29 | * 记录测试步骤信息。 30 | * 31 | * @param message the message object. 32 | */ 33 | public static void logStep(Object message) { 34 | logger.info("[STEP] " + message); 35 | Reporter.log(new Tools().getSimpleDateFormat() + " : " + "[STEP] " + message); 36 | } 37 | 38 | /** 39 | * 记录测试流日志。 40 | * 41 | * @param message the message object. 42 | */ 43 | public static void logFlow(Object message) { 44 | logger.info("[FLOW] " + message); 45 | Reporter.log(new Tools().getSimpleDateFormat() + " : " + "[FLOW] " + message); 46 | } 47 | 48 | /** 49 | * 记录Error级别日志。 50 | * 51 | * @param message the message object. 52 | */ 53 | public static void logError(Object message) { 54 | logger.error("[ERROR] " + message); 55 | Reporter.log(new Tools().getSimpleDateFormat() + " : " + "[ERROR] " + message); 56 | } 57 | 58 | /** 59 | * 记录Warn级别日志。 60 | * 61 | * @param message the message object. 62 | */ 63 | public static void logWarn(Object message) { 64 | logger.warn("[WARN] " + message); 65 | Reporter.log(new Tools().getSimpleDateFormat() + " : " + "[WARN] " + message); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/util/ParseProperties.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.util; 2 | 3 | import java.io.File; 4 | import java.util.Properties; 5 | import java.io.FileInputStream; 6 | import java.io.FileNotFoundException; 7 | 8 | /** 9 | * 说明: 10 | * 使用Properties加载配置文件,解析指定的key值返回 11 | * Created by quqing on 15/11/7. 12 | */ 13 | public class ParseProperties { 14 | private File file; 15 | private String fileName = "config.properties"; 16 | 17 | /** 18 | * construct with parameter intialize 19 | * 20 | * @param filePath whole path and name of config file 21 | * @throws RuntimeException 22 | */ 23 | public ParseProperties(String filePath) { 24 | if (filePath == null) { 25 | throw new RuntimeException("the parameter can not be null!"); 26 | } 27 | this.fileName = filePath; 28 | this.file = new File(System.getProperty("user.dir") + File.separator + fileName); 29 | } 30 | 31 | /** 32 | * get specified key in config files 33 | * 34 | * @param key the key name to get value 35 | * @throws RuntimeException 36 | */ 37 | public String get(String key) { 38 | String keyValue = null; 39 | Properties properties = new Properties(); 40 | try { 41 | if (!file.exists()) { 42 | throw new FileNotFoundException("the config file [" + fileName + "] not exist!"); 43 | } 44 | properties.load(new FileInputStream(file)); 45 | } catch (Exception e) { 46 | properties.clear(); 47 | throw new RuntimeException("load properties failed:" + e.getMessage()); 48 | } 49 | if (properties.containsKey(key)) { 50 | keyValue = (String) properties.get(key); 51 | } 52 | return keyValue; 53 | } 54 | } -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/util/StartAppiumServer.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.util; 2 | 3 | /** 4 | * Created by yuyilong on 15/9/24. 5 | */ 6 | public class StartAppiumServer extends Thread { 7 | long bPort; 8 | long chromeport; 9 | private String port; 10 | private String udid; 11 | private String appiumPath; 12 | 13 | /** 14 | * @param port appium service port 15 | */ 16 | public StartAppiumServer(String port, String udid) { 17 | this.udid = udid; 18 | this.port = port; 19 | this.bPort = Long.parseLong(port) + 2; 20 | this.chromeport = Long.parseLong(port) + 4792; 21 | this.appiumPath = "/usr/local/lib/node_modules/appium/bin/"; 22 | } 23 | 24 | /** 25 | * Description: override method run to start appium service. 26 | * 启动Appium服务。 27 | */ 28 | public void run() { 29 | // int count = 1; 30 | // String out; 31 | // String result; 32 | 33 | //如果appium -p xxx -bp xxx 端口被占用则杀掉进程 34 | // out = Tools.cmdInvoke("ps -A|grep " + port + "|grep -v grep|awk 'NR=1 {print $1}'"); 35 | 36 | // while (null != out && !out.equals("")) { 37 | // if (count > 6) 38 | // break; 39 | // Log.logInfo("The " + count + " times try to kill the appium"); 40 | // Tools.cmdInvoke("ps -A|grep " + port + "|grep -v grep|awk 'NR=1 {print $1}'|xargs kill -9"); 41 | // out = Tools.cmdInvoke("ps -A|grep " + port + "|grep -v grep|awk 'NR=1 {print $1}'"); 42 | // count++; 43 | // } 44 | 45 | // result = (count > 6) ? "error >> killed appium's process failure!" : "kill appium's processes success"; 46 | // Log.logInfo(result); 47 | //--command-timeout 8400 48 | 49 | String command = "node " + appiumPath + "appium.js -a 127.0.0.1 -p " + port + " -bp " + bPort + " --session-override --chromedriver-port " + chromeport + " -U " + udid; 50 | //启动appium服务 51 | // Log.logInfo("start appium >> appium -p " + port + " -bp " + bPort + " --session-override" + " -U " + udid); 52 | // Tools.cmdInvoke("appium -p " + port + " -bp " + bPort + " --session-override" + " -U " + udid); 53 | Log.logInfo("run " + udid + " Appium Server in port " + port + "......"); 54 | Log.logInfo("start appium >> " + command); 55 | Tools.cmdInvoke(command); 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/util/StaticConfig.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.util; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Created by yuyilong on 15/8/26. 8 | */ 9 | public class StaticConfig { 10 | private static final ParseProperties CONFIG = new ParseProperties( 11 | "src/config.properties"); 12 | 13 | public static String IdentifyID_Image = "8888"; 14 | public static String IdentifyID_Message = "888888"; 15 | public static int retrycount = Integer.parseInt(CONFIG.get("retrycount")); 16 | public static String miDevices = CONFIG.get("miDevices"); 17 | 18 | public static String[] getMiDevices() { 19 | String miPhones = StaticConfig.miDevices; 20 | if (null != miPhones) { 21 | if (miPhones.indexOf(",") != -1) { 22 | return miPhones.split(","); 23 | } else { 24 | return new String[]{miPhones}; 25 | } 26 | } else { 27 | return null; 28 | } 29 | } 30 | 31 | public static String[] getMiUdid() { 32 | String[] miUdid; 33 | String[] miDevices = StaticConfig.getMiDevices(); 34 | if (null != miDevices) { 35 | miUdid = new String[miDevices.length]; 36 | for (int i = 0; i < miDevices.length; i++) { 37 | miUdid[i] = miDevices[i].split("=")[0]; 38 | } 39 | return miUdid; 40 | } else { 41 | return null; 42 | } 43 | } 44 | 45 | public static Map getMiResolution() { 46 | Map miResolution = new LinkedHashMap(); 47 | String[] miDevices = StaticConfig.getMiDevices(); 48 | if (null != miDevices) { 49 | for (int i = 0; i < miDevices.length; i++) { 50 | miResolution.put(miDevices[i].split("=")[0], miDevices[i].split("=")[1]); 51 | } 52 | return miResolution; 53 | } else { 54 | return null; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/util/Tools.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.util; 2 | 3 | import org.apache.log4j.Logger; 4 | import org.apache.log4j.PropertyConfigurator; 5 | 6 | import java.io.*; 7 | import java.text.SimpleDateFormat; 8 | import java.util.Calendar; 9 | import java.util.Date; 10 | import java.util.GregorianCalendar; 11 | import java.util.Random; 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | 15 | /** 16 | * Created by yuyilong on 15/8/27. 17 | */ 18 | public class Tools { 19 | private static String[] OperatorCode = {"134", "135", "136", "137", "138", "139", "150", "151", "152", "157", "158", "159", "130", "131", "132", "155", "156", "185", "186", "133", "153", "180", "189"}; 20 | private static String areaCodes[] = {"320102", "320103", "320104", "320105", "320106", "320107", "320111", "320113", "320114", "320115", "320116", "320124", "320125", "320202", "320203", "320204", "320205", "320206", "320211", "320281", "320282", 21 | "320302", "320303", "320304", "320305", "320311", "320321", "320322", "320323", "320324", "320381", "320382", "320402", "320404", "320405", "320411", "320412", "320481", "320482", "320502", "320503", "320504", 22 | "320505", "320506", "320507", "320581", "320582", "320583", "320584", "320585", "320602", "320611", "320612", "320621", "320623", "320681", "320682", "320684", "320703", "320705", "320706", "320721", "320722", 23 | "320723", "320724", "320802", "320803", "320804", "320811", "320826", "320829", "320830", "320831", "320902", "320903", "320921", "320922", "320923", "320924", "320925", "320981", "320982", "321002", 24 | "321003", "321011", "321023", "321081", "321084", "321088", "321088", "321102", "321111", "321112", "321181", "321182", "321183", "321202", "321203", "321281", "321282", "321283", "321284", "321302", 25 | "321311", "321322", "321323", "321324"}; 26 | 27 | /** 28 | * 在目标字符串数组中查找匹配的字符串 29 | * 30 | * @param find 被查找的字符串 31 | * @param target 目标字符串数组 32 | * @return 是否查找到字符串 33 | * @author quqing 34 | */ 35 | public static boolean isMatch(String find, String[] target) { 36 | for (String str : target) { 37 | if (find.equalsIgnoreCase(str)) { 38 | return true; 39 | } 40 | } 41 | return false; 42 | } 43 | 44 | public static String formatTime() { 45 | SimpleDateFormat time = new SimpleDateFormat("yyyyMMddHH"); 46 | return time.format(new Date()).toString(); 47 | } 48 | 49 | /** 50 | * 查找自定义条件的字符串,判断条件用正则表达式描述 51 | * 52 | * @param find 被查找的字符串 53 | * @param regx 正则表达式(例如:匹配包含换行符(回车)的任意字符串的正则表达式:[\\s\\S]*? ) 54 | * @return 匹配的字符串 55 | * @author quqing 56 | */ 57 | public static String findSubString(String find, String regx) { 58 | String str = null; 59 | try { 60 | Pattern pattern = Pattern.compile(regx); 61 | Matcher matcher = pattern.matcher(find); 62 | while (matcher.find()) { 63 | str = matcher.group(); 64 | return str; 65 | } 66 | } catch (Exception e) { 67 | e.printStackTrace(); 68 | return str; 69 | } 70 | return str; 71 | } 72 | 73 | /** 74 | * 查找预定义条件的字符串,判断条件 http status 400~469 500~569 600~669 75 | * 76 | * @param find 被查找的字符串 77 | * @return 匹配的字符串 78 | * @author quqing 79 | */ 80 | public static String findSubString(String find) { 81 | return findSubString(find, "HTTP Status [456][06][0-9]"); 82 | } 83 | 84 | /** 85 | * 杀掉指定名称的进程 86 | * 87 | * @param processName 进程名 88 | * @author quqing 89 | */ 90 | public static void killProcess(String processName) { 91 | try { 92 | String cmd; 93 | 94 | if (isWindows()) { 95 | cmd = "Taskkill /IM" + processName; 96 | } else { 97 | cmd = "killall \"" + processName + "\""; 98 | } 99 | cmdInvoke(cmd); 100 | } catch (Exception e) { 101 | Log.logInfo(e.getMessage()); 102 | } 103 | } 104 | 105 | /** 106 | * 整块读取文件内容 107 | * 108 | * @param fname 文件名 109 | * @return 110 | * @throws FileNotFoundException 111 | * @author quqing 112 | */ 113 | public static String readAll(String fname) throws FileNotFoundException, 114 | IOException { 115 | String content = ""; 116 | File file = new File(fname); 117 | BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file))); 118 | String line = null; 119 | while ((line = reader.readLine()) != null) { 120 | content += line + "\n"; 121 | } 122 | reader.close(); 123 | return content; 124 | } 125 | 126 | /** 127 | * 判断是否Windows操作系统 128 | * 129 | * @return boolean 130 | * @author quqing 131 | */ 132 | public static boolean isWindows() { 133 | String os = System.getProperty("os.name"); 134 | return (os.toLowerCase().startsWith("win")) ? true : false; 135 | } 136 | 137 | /** 138 | * 调用并执行控制台命令 139 | * 140 | * @param cmd 控制台命令 141 | * @return String 142 | * @author quqing 143 | */ 144 | public static String cmdInvoke(String cmd) { 145 | String cmdOut = ""; 146 | BufferedReader br = null; 147 | String filePath = "src/log4j.properties"; 148 | 149 | try { 150 | Logger logger = Logger.getLogger("appium_log"); 151 | PropertyConfigurator.configure(new File(filePath).getAbsolutePath()); 152 | // String[] command = { "/bin/sh", "-c", cmd }; 153 | String[] command = {"sh", "-c", cmd}; 154 | Process p = Runtime.getRuntime().exec(command); 155 | br = new BufferedReader(new InputStreamReader(p.getInputStream())); 156 | String line = null; 157 | while ((line = br.readLine()) != null) { 158 | cmdOut = line; 159 | logger.info(line); 160 | } 161 | } catch (Exception e) { 162 | e.printStackTrace(); 163 | } finally { 164 | if (br != null) { 165 | try { 166 | br.close(); 167 | } catch (Exception e) { 168 | e.printStackTrace(); 169 | } 170 | } 171 | } 172 | return cmdOut; 173 | } 174 | 175 | /** 176 | * 生成手机号码,手机号码的规则是:运营商编码(3位)+地区编码(4位)+用户编码(4位) 177 | * 178 | * @param count 要获取的手机号码个数 179 | * @return String 180 | */ 181 | public static String getPhoneNum(int count) { 182 | String PhoneNum = ""; 183 | int num = (int) (Math.random() * OperatorCode.length); 184 | PhoneNum = PhoneNum + OperatorCode[num]; 185 | 186 | Random random = new Random(); 187 | for (int i = 0; i < count; i++) { 188 | PhoneNum += random.nextInt(10); 189 | } 190 | return PhoneNum; 191 | } 192 | 193 | /** 194 | * 生成身份证号 195 | * 196 | * @return String 197 | */ 198 | public static String getIdentifyCode() { 199 | //市/区/县的名称与之对应的编号,目前仅支持江苏省 200 | Random random = new Random(); 201 | String areaCode = areaCodes[random.nextInt(areaCodes.length)]; 202 | int year = 1920 + random.nextInt(100); 203 | int month = random.nextInt(11); 204 | if (month == 0) 205 | month = 12; 206 | int day = 0; 207 | while (true) { 208 | day = random.nextInt(31); 209 | if (!((day == 0 || (month == 4 || month == 6 || month == 9 || month == 11) 210 | && day > 30) || (month == 2 && (((year) % 4 > 0 && day > 28) || day > 29)))) { 211 | break; 212 | } 213 | } 214 | String birthday = String.valueOf(year * 10000 + month * 100 215 | + day); 216 | String randomCode = String.valueOf(1000 + random.nextInt(999)) 217 | .substring(1); 218 | String verify = getVerify(areaCode + birthday + randomCode); 219 | String ret = areaCode + birthday + randomCode + verify; 220 | return ret; 221 | } 222 | 223 | /** 224 | * 计算身份证校验码 225 | * 226 | * @param cardId 身份证前17位 227 | * @return String 228 | */ 229 | public static String getVerify(String cardId) { 230 | String[] ValCodeArr = {"1", "0", "X", "9", "8", "7", "6", "5", "4", 231 | "3", "2"}; 232 | String[] Wi = {"7", "9", "10", "5", "8", "4", "2", "1", "6", "3", "7", 233 | "9", "10", "5", "8", "4", "2"}; 234 | int TotalmulAiWi = 0; 235 | for (int i = 0; i < 17; i++) { 236 | TotalmulAiWi = TotalmulAiWi 237 | + Integer.parseInt(String.valueOf(cardId.charAt(i))) 238 | * Integer.parseInt(Wi[i]); 239 | } 240 | int modValue = TotalmulAiWi % 11; 241 | String strVerifyCode = ValCodeArr[modValue]; 242 | return strVerifyCode; 243 | } 244 | 245 | /** 246 | * 输入1个字符串和1个长度,如果字符串长度小于输入长度,字符串前面补0 247 | * 248 | * @param str 字符串 249 | * @param len 长度 250 | * @return String 251 | */ 252 | private static String valueOfString(String str, int len) { 253 | String string = ""; 254 | for (int i = 0; i < len - str.length(); i++) { 255 | string = string + "0"; 256 | } 257 | return (str.length() == len) ? (str) : (string + str); 258 | } 259 | 260 | /** 261 | * 返回当前时间,格式为:2014-12-18 15:11:50 262 | * 263 | * @return String 264 | */ 265 | public static String getSimpleDateFormat() { 266 | SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 267 | return df.format(new Date()); 268 | } 269 | 270 | /** 271 | * 返回当前时间戳 272 | * 273 | * @return String 274 | */ 275 | public static String getTime() { 276 | return String.valueOf(new Date().getTime()); 277 | } 278 | 279 | /** 280 | * 生成一个长度为17的时间字符串,精确到毫秒 281 | * 282 | * @return String 283 | */ 284 | public static String getTimeString() { 285 | Calendar calendar = new GregorianCalendar(); 286 | String year = String.valueOf(calendar.get(Calendar.YEAR)); 287 | String month = valueOfString(String.valueOf(calendar.get(Calendar.MONTH) + 1), 2); 288 | String day = valueOfString(String.valueOf(calendar.get(Calendar.DAY_OF_MONTH)), 2); 289 | String hour = valueOfString(String.valueOf(calendar.get(Calendar.HOUR_OF_DAY)), 2); 290 | String minute = valueOfString(String.valueOf(calendar.get(Calendar.MINUTE)), 2); 291 | String second = valueOfString(String.valueOf(calendar.get(Calendar.SECOND)), 2); 292 | String millisecond = valueOfString(String.valueOf(calendar.get(Calendar.MILLISECOND)), 3); 293 | return year + month + day + hour + minute + second + millisecond; 294 | } 295 | 296 | /** 297 | * 得到昨天的日期 298 | * 299 | * @return String 300 | */ 301 | public static String getYestoryDate() { 302 | Calendar calendar = Calendar.getInstance(); 303 | calendar.add(Calendar.DATE, -1); 304 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); 305 | String yestoday = sdf.format(calendar.getTime()); 306 | return yestoday; 307 | } 308 | } -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/util/testng/ConfigReader.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.util.testng; 2 | 3 | import org.testng.log4testng.Logger; 4 | 5 | import java.io.FileInputStream; 6 | import java.io.FileNotFoundException; 7 | import java.io.IOException; 8 | import java.util.Enumeration; 9 | import java.util.Properties; 10 | 11 | public class ConfigReader { 12 | private static Logger logger = Logger.getLogger(ConfigReader.class); 13 | private static ConfigReader cr; 14 | private int retryCount = 0; 15 | private String sourceCodeDir = "src/test"; 16 | private String sourceCodeEncoding = "UTF-8"; 17 | 18 | private static final String RETRYCOUNT = "retrycount"; 19 | private static final String SOURCEDIR = "sourcecodedir"; 20 | private static final String SOURCEENCODING = "sourcecodeencoding"; 21 | private static final String CONFIGFILE = "src/config.properties"; 22 | 23 | private ConfigReader() { 24 | readConfig(CONFIGFILE); 25 | } 26 | 27 | public static ConfigReader getInstance() { 28 | if(cr == null) { 29 | cr = new ConfigReader(); 30 | } 31 | return cr; 32 | } 33 | 34 | private void readConfig(String fileName) { 35 | Properties properties = getConfig(fileName); 36 | if (properties != null) { 37 | String sRetryCount = null; 38 | 39 | Enumeration en = properties.propertyNames(); 40 | while (en.hasMoreElements()) { 41 | String key = (String) en.nextElement(); 42 | if(key.toLowerCase().equals(RETRYCOUNT)) { 43 | sRetryCount = properties.getProperty(key); 44 | } 45 | if(key.toLowerCase().equals(SOURCEDIR)) { 46 | sourceCodeDir = properties.getProperty(key); 47 | } 48 | if(key.toLowerCase().equals(SOURCEENCODING)) { 49 | sourceCodeEncoding = properties.getProperty(key); 50 | } 51 | } 52 | if (sRetryCount != null) { 53 | sRetryCount = sRetryCount.trim(); 54 | try { 55 | retryCount = Integer.parseInt(sRetryCount); 56 | } catch (final NumberFormatException e) { 57 | throw new NumberFormatException("Parse " + RETRYCOUNT + " [" + sRetryCount + "] from String to Int Exception"); 58 | } 59 | } 60 | } 61 | } 62 | 63 | public int getRetryCount() { 64 | return retryCount; 65 | } 66 | 67 | public String getSourceCodeDir() { 68 | return this.sourceCodeDir; 69 | } 70 | 71 | public String getSrouceCodeEncoding() { 72 | return this.sourceCodeEncoding; 73 | } 74 | 75 | private Properties getConfig(String propertyFileName) { 76 | Properties properties = new Properties(); 77 | try { 78 | properties.load(new FileInputStream(propertyFileName)); 79 | } catch (FileNotFoundException e) { 80 | properties = null; 81 | e.printStackTrace(); 82 | logger.warn("FileNotFoundException:" + propertyFileName); 83 | } catch (IOException e) { 84 | properties = null; 85 | logger.warn("IOException:" + propertyFileName); 86 | } 87 | return properties; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/util/testng/PowerEmailableReporter.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.util.testng; 2 | 3 | import com.thoughtworks.qdox.JavaDocBuilder; 4 | import com.thoughtworks.qdox.model.DocletTag; 5 | import com.thoughtworks.qdox.model.JavaClass; 6 | import com.thoughtworks.qdox.model.JavaMethod; 7 | import org.testng.*; 8 | import org.testng.collections.Lists; 9 | import org.testng.internal.Utils; 10 | import org.testng.log4testng.Logger; 11 | import org.testng.xml.XmlSuite; 12 | 13 | import java.io.*; 14 | import java.text.DecimalFormat; 15 | import java.text.NumberFormat; 16 | import java.util.*; 17 | 18 | 19 | public class PowerEmailableReporter implements IReporter { 20 | private static final Logger L = Logger.getLogger(PowerEmailableReporter.class); 21 | 22 | private PrintWriter m_out; 23 | 24 | private int m_row; 25 | 26 | private Integer m_testIndex; 27 | 28 | private Set testIds = new HashSet(); 29 | private List allRunTestIds = new ArrayList(); 30 | private JavaDocBuilder builder = new JavaDocBuilder(); 31 | 32 | public void generateReport(List xml, List suites, String outdir) { 33 | try { 34 | m_out = createWriter("output/testngReports/"); 35 | } catch (IOException e) { 36 | L.error("output file", e); 37 | return; 38 | } 39 | ConfigReader cr = ConfigReader.getInstance(); 40 | builder.setEncoding(cr.getSrouceCodeEncoding()); 41 | builder.addSourceTree(new File(cr.getSourceCodeDir())); 42 | startHtml(m_out); 43 | generateSuiteSummaryReport(suites); 44 | testIds.clear(); 45 | generateMethodSummaryReport(suites); 46 | testIds.clear(); 47 | generateMethodDetailReport(suites); 48 | testIds.clear(); 49 | endHtml(m_out); 50 | m_out.flush(); 51 | m_out.close(); 52 | } 53 | 54 | protected PrintWriter createWriter(String outdir) throws IOException { 55 | new File(outdir).mkdirs(); 56 | return new PrintWriter(new BufferedWriter(new FileWriter(new File(outdir, "power-emailable-report.html")))); 57 | } 58 | 59 | protected void generateMethodSummaryReport(List suites) { 60 | startResultSummaryTable("methodOverview"); 61 | int testIndex = 1; 62 | for (ISuite suite : suites) { 63 | if (suites.size() > 1) { 64 | titleRow(suite.getName(), 5); 65 | } 66 | Map r = suite.getResults(); 67 | for (ISuiteResult r2 : r.values()) { 68 | ITestContext testContext = r2.getTestContext(); 69 | String testName = testContext.getName(); 70 | m_testIndex = testIndex; 71 | 72 | resultSummary(suite, testContext.getSkippedConfigurations(), testName, "skipped", " (configuration methods)"); 73 | resultSummary(suite, testContext.getSkippedTests(), testName, "skipped", ""); 74 | resultSummary(suite, testContext.getFailedConfigurations(), testName, "failed", " (configuration methods)"); 75 | resultSummary(suite, testContext.getFailedTests(), testName, "failed", ""); 76 | resultSummary(suite, testContext.getPassedTests(), testName, "passed", ""); 77 | 78 | testIndex++; 79 | } 80 | } 81 | m_out.println(""); 82 | } 83 | 84 | protected void generateMethodDetailReport(List suites) { 85 | for (ISuite suite : suites) { 86 | Map r = suite.getResults(); 87 | for (ISuiteResult r2 : r.values()) { 88 | ITestContext testContext = r2.getTestContext(); 89 | if (r.values().size() > 0) { 90 | m_out.println("

" + testContext.getName() + "

"); 91 | } 92 | resultDetail(testContext.getFailedConfigurations()); 93 | resultDetail(testContext.getFailedTests()); 94 | resultDetail(testContext.getSkippedConfigurations()); 95 | resultDetail(testContext.getSkippedTests()); 96 | resultDetail(testContext.getPassedTests()); 97 | } 98 | } 99 | } 100 | 101 | 102 | private void resultSummary(ISuite suite, IResultMap tests, String testname, String style, String details) { 103 | if (tests.getAllResults().size() > 0) { 104 | StringBuffer buff = new StringBuffer(); 105 | String lastClassName = ""; 106 | int mq = 0; 107 | int cq = 0; 108 | Map methods = new HashMap(); 109 | Set setMethods = new HashSet(); 110 | for (ITestNGMethod method : getMethodSet(tests, suite)) { 111 | m_row += 1; 112 | 113 | ITestClass testClass = method.getTestClass(); 114 | String className = testClass.getName(); 115 | if (mq == 0) { 116 | String id = (m_testIndex == null ? null : "t" + Integer.toString(m_testIndex)); 117 | titleRow(testname + " — " + style + details, 5, id); 118 | m_testIndex = null; 119 | } 120 | if (!className.equalsIgnoreCase(lastClassName)) { 121 | if (mq > 0) { 122 | cq += 1; 123 | m_out.print("" + " 1) { 125 | m_out.print(" rowspan=\"" + mq + "\""); 126 | } 127 | m_out.println(">" + lastClassName + "" + buff); 128 | } 129 | mq = 0; 130 | buff.setLength(0); 131 | lastClassName = className; 132 | } 133 | Set resultSet = tests.getResults(method); 134 | long end = Long.MIN_VALUE; 135 | long start = Long.MAX_VALUE; 136 | for (ITestResult testResult : tests.getResults(method)) { 137 | if (testResult.getEndMillis() > end) { 138 | end = testResult.getEndMillis(); 139 | } 140 | if (testResult.getStartMillis() < start) { 141 | start = testResult.getStartMillis(); 142 | } 143 | } 144 | mq += 1; 145 | if (mq > 1) { 146 | buff.append(""); 147 | } 148 | String description = method.getDescription(); 149 | String testInstanceName = resultSet.toArray(new ITestResult[] {})[0].getTestName(); 150 | // Calculate each test run times, the result shown in the html report. 151 | ITestResult[] results = resultSet.toArray(new ITestResult[] {}); 152 | String methodName = method.getMethodName(); 153 | if (setMethods.contains(methodName)) { 154 | methods.put(methodName, methods.get(methodName) + 1); 155 | } else { 156 | setMethods.add(methodName); 157 | methods.put(methodName, 0); 158 | } 159 | String parameterString = ""; 160 | int count = 0; 161 | 162 | ITestResult result = null; 163 | if (results.length > methods.get(methodName)) { 164 | result = results[methods.get(methodName)]; 165 | int testId = getId(result); 166 | 167 | for (Integer id : allRunTestIds) { 168 | if (id.intValue() == testId) 169 | count++; 170 | } 171 | Object[] parameters = result.getParameters(); 172 | 173 | boolean hasParameters = parameters != null && parameters.length > 0; 174 | if (hasParameters) { 175 | for (Object p : parameters) { 176 | parameterString = parameterString + Utils.escapeHtml(p.toString()) + " "; 177 | } 178 | } 179 | } 180 | 181 | 182 | int methodId = method.getTestClass().getName().hashCode(); 183 | methodId = methodId + method.getMethodName().hashCode(); 184 | if(result != null) 185 | methodId = methodId + (result.getParameters() != null ? Arrays.hashCode(result.getParameters()) : 0); 186 | 187 | 188 | buff.append("" + qualifiedName(method) + " " 189 | + (description != null && description.length() > 0 ? "(\"" + description + "\")" : "") + "" 190 | + (null == testInstanceName ? "" : "
(" + testInstanceName + ")") + "" + this.getAuthors(className, method) 191 | + "" + resultSet.size() + "" + "" + (count == 0 ? "" : count) + "" + "" 192 | + parameterString + "" + "" + start + "" + "" + (end - start) + "" + ""); 193 | } 194 | if (mq > 0) { 195 | cq += 1; 196 | m_out.print("" + " 1) { 198 | m_out.print(" rowspan=\"" + mq + "\""); 199 | } 200 | m_out.println(">" + lastClassName + "" + buff); 201 | } 202 | } 203 | } 204 | 205 | private void startResultSummaryTable(String style) { 206 | tableStart(style, "summary"); 207 | m_out.println("ClassMethodAuthors# of
ScenariosRunning Counts" 208 | + "ParametersStartTime
(ms)"); 209 | m_row = 0; 210 | } 211 | 212 | private String qualifiedName(ITestNGMethod method) { 213 | StringBuilder addon = new StringBuilder(); 214 | String[] groups = method.getGroups(); 215 | int length = groups.length; 216 | if (length > 0 && !"basic".equalsIgnoreCase(groups[0])) { 217 | addon.append("("); 218 | for (int i = 0; i < length; i++) { 219 | if (i > 0) { 220 | addon.append(", "); 221 | } 222 | addon.append(groups[i]); 223 | } 224 | addon.append(")"); 225 | } 226 | 227 | return "" + method.getMethodName() + " " + addon; 228 | } 229 | 230 | private void resultDetail(IResultMap tests) { 231 | for (ITestResult result : tests.getAllResults()) { 232 | ITestNGMethod method = result.getMethod(); 233 | 234 | int methodId = getId(result); 235 | 236 | String cname = method.getTestClass().getName(); 237 | m_out.println("

" + cname + ":" + method.getMethodName() + "

"); 238 | Set resultSet = tests.getResults(method); 239 | generateForResult(result, method, resultSet.size()); 240 | m_out.println("

back to summary

"); 241 | 242 | } 243 | } 244 | 245 | private void generateForResult(ITestResult ans, ITestNGMethod method, int resultSetSize) { 246 | Object[] parameters = ans.getParameters(); 247 | boolean hasParameters = parameters != null && parameters.length > 0; 248 | if (hasParameters) { 249 | tableStart("result", null); 250 | m_out.print(""); 251 | for (int x = 1; x <= parameters.length; x++) { 252 | m_out.print("Parameter #" + x + ""); 253 | } 254 | m_out.println(""); 255 | m_out.print(""); 256 | for (Object p : parameters) { 257 | m_out.println("" + Utils.escapeHtml(p.toString()) + ""); 258 | } 259 | m_out.println(""); 260 | } 261 | List msgs = Reporter.getOutput(ans); 262 | boolean hasReporterOutput = msgs.size() > 0; 263 | Throwable exception = ans.getThrowable(); 264 | boolean hasThrowable = exception != null; 265 | if (hasReporterOutput || hasThrowable) { 266 | if (hasParameters) { 267 | m_out.print(" 1) { 269 | m_out.print(" colspan=\"" + parameters.length + "\""); 270 | } 271 | m_out.println(">"); 272 | } else { 273 | m_out.println("
"); 274 | } 275 | if (hasReporterOutput) { 276 | if (hasThrowable) { 277 | m_out.println("

Test Messages

"); 278 | } 279 | for (String line : msgs) { 280 | m_out.println(line + "
"); 281 | } 282 | } 283 | if (hasThrowable) { 284 | boolean wantsMinimalOutput = ans.getStatus() == ITestResult.SUCCESS; 285 | if (hasReporterOutput) { 286 | m_out.println("

" + (wantsMinimalOutput ? "Expected Exception" : "Failure") + "

"); 287 | } 288 | generateExceptionReport(exception, method); 289 | } 290 | if (hasParameters) { 291 | m_out.println(""); 292 | } else { 293 | m_out.println("
"); 294 | } 295 | } 296 | if (hasParameters) { 297 | m_out.println(""); 298 | } 299 | } 300 | 301 | protected void generateExceptionReport(Throwable exception, ITestNGMethod method) { 302 | m_out.print("
"); 303 | m_out.print(Utils.stackTrace(exception, true)[0]); 304 | m_out.println("
"); 305 | } 306 | 307 | private Collection getMethodSet(IResultMap tests, ISuite suite) { 308 | List r = Lists.newArrayList(); 309 | List invokedMethods = suite.getAllInvokedMethods(); 310 | 311 | for (IInvokedMethod im : invokedMethods) { 312 | if (tests.getAllMethods().contains(im.getTestMethod())) { 313 | int testId = getId(im.getTestResult()); 314 | if (!testIds.contains(testId)) { 315 | testIds.add(testId); 316 | r.add(im); 317 | } 318 | } 319 | } 320 | Arrays.sort(r.toArray(new IInvokedMethod[r.size()]), new TestSorter()); 321 | List result = Lists.newArrayList(); 322 | 323 | for (IInvokedMethod m : r) { 324 | result.add(m.getTestMethod()); 325 | } 326 | 327 | for (ITestResult allResult : tests.getAllResults()) { 328 | int testId = getId(allResult); 329 | if (!testIds.contains(testId)) { 330 | result.add(allResult.getMethod()); 331 | } 332 | } 333 | 334 | return result; 335 | } 336 | 337 | 338 | 339 | public void generateSuiteSummaryReport(List suites) { 340 | tableStart("testOverview", null); 341 | m_out.print(""); 342 | tableColumnStart("Test"); 343 | tableColumnStart("Methods
Passed"); 344 | tableColumnStart("Scenarios
Passed"); 345 | tableColumnStart("# skipped"); 346 | tableColumnStart("# failed"); 347 | tableColumnStart("Total
Time"); 348 | tableColumnStart("Included
Groups"); 349 | tableColumnStart("Excluded
Groups"); 350 | m_out.println(""); 351 | NumberFormat formatter = new DecimalFormat("#,##0.0"); 352 | int qty_tests = 0; 353 | int qty_pass_m = 0; 354 | int qty_pass_s = 0; 355 | int qty_skip = 0; 356 | int qty_fail = 0; 357 | long time_start = Long.MAX_VALUE; 358 | long time_end = Long.MIN_VALUE; 359 | m_testIndex = 1; 360 | for (ISuite suite : suites) { 361 | if (suites.size() > 1) { 362 | titleRow(suite.getName(), 8); 363 | } 364 | Map tests = suite.getResults(); 365 | for (ISuiteResult r : tests.values()) { 366 | qty_tests += 1; 367 | ITestContext overview = r.getTestContext(); 368 | startSummaryRow(overview.getName()); 369 | 370 | getAllTestIds(overview, suite); 371 | int q = getMethodSet(overview.getPassedTests(), suite).size(); 372 | qty_pass_m += q; 373 | summaryCell(q, Integer.MAX_VALUE); 374 | q = overview.getPassedTests().size(); 375 | qty_pass_s += q; 376 | summaryCell(q, Integer.MAX_VALUE); 377 | 378 | q = getMethodSet(overview.getSkippedTests(), suite).size(); 379 | qty_skip += q; 380 | summaryCell(q, 0); 381 | 382 | q = getMethodSet(overview.getFailedTests(), suite).size(); 383 | qty_fail += q; 384 | summaryCell(q, 0); 385 | 386 | time_start = Math.min(overview.getStartDate().getTime(), time_start); 387 | time_end = Math.max(overview.getEndDate().getTime(), time_end); 388 | summaryCell(formatter.format((overview.getEndDate().getTime() - overview.getStartDate().getTime()) / 1000.) + " seconds", 389 | true); 390 | summaryCell(overview.getIncludedGroups()); 391 | summaryCell(overview.getExcludedGroups()); 392 | m_out.println(""); 393 | m_testIndex++; 394 | } 395 | } 396 | if (qty_tests > 1) { 397 | m_out.println("Total"); 398 | summaryCell(qty_pass_m, Integer.MAX_VALUE); 399 | summaryCell(qty_pass_s, Integer.MAX_VALUE); 400 | summaryCell(qty_skip, 0); 401 | summaryCell(qty_fail, 0); 402 | summaryCell(formatter.format((time_end - time_start) / 1000.) + " seconds", true); 403 | m_out.println(" "); 404 | } 405 | m_out.println(""); 406 | } 407 | 408 | private void summaryCell(String[] val) { 409 | StringBuffer b = new StringBuffer(); 410 | for (String v : val) { 411 | b.append(v + " "); 412 | } 413 | summaryCell(b.toString(), true); 414 | } 415 | 416 | private void summaryCell(String v, boolean isgood) { 417 | m_out.print("" + v + ""); 418 | } 419 | 420 | private void startSummaryRow(String label) { 421 | m_row += 1; 422 | m_out.print("" + label + "" + ""); 424 | } 425 | 426 | private void summaryCell(int v, int maxexpected) { 427 | summaryCell(String.valueOf(v), v <= maxexpected); 428 | } 429 | 430 | private void tableStart(String cssclass, String id) { 431 | m_out.println(""); 434 | m_row = 0; 435 | } 436 | 437 | private void tableColumnStart(String label) { 438 | m_out.print(""); 439 | } 440 | 441 | private void titleRow(String label, int cq) { 442 | titleRow(label, cq, null); 443 | } 444 | 445 | private void titleRow(String label, int cq, String id) { 446 | m_out.print(""); 451 | m_row = 0; 452 | } 453 | 454 | protected void startHtml(PrintWriter out) { 455 | out.println(""); 456 | out.println(""); 457 | out.println(""); 458 | out.println("TestNG Report"); 459 | out.println(""); 477 | out.println(""); 478 | out.println(""); 479 | } 480 | 481 | protected void endHtml(PrintWriter out) { 482 | out.println(""); 483 | } 484 | 485 | private class TestSorter implements Comparator { 486 | 487 | public int compare(IInvokedMethod o1, IInvokedMethod o2) { 488 | return (int) (o1.getDate() - o2.getDate()); 489 | } 490 | } 491 | 492 | private String getAuthors(String className, ITestNGMethod method) { 493 | JavaClass cls = builder.getClassByName(className); 494 | DocletTag[] authors = cls.getTagsByName("author"); 495 | String allAuthors = ""; 496 | if (authors.length == 0) { 497 | allAuthors = "unknown"; 498 | } else { 499 | for (DocletTag author : authors) { 500 | allAuthors += author.getValue() + " "; 501 | } 502 | } 503 | JavaMethod[] mtds = cls.getMethods(); 504 | for (JavaMethod mtd : mtds) { 505 | if (mtd.getName().equals(method.getMethodName())) { 506 | authors = mtd.getTagsByName("author"); 507 | if (authors.length != 0) { 508 | allAuthors = ""; 509 | for (DocletTag author : authors) { 510 | allAuthors += author.getValue() + " "; 511 | } 512 | } 513 | break; 514 | } 515 | } 516 | return allAuthors.trim(); 517 | } 518 | 519 | private String getClassComment(String className) { 520 | JavaClass cls = builder.getClassByName(className); 521 | return cls.getComment(); 522 | } 523 | 524 | private int getId(ITestResult result) { 525 | int id = result.getTestClass().getName().hashCode(); 526 | id = id + result.getMethod().getMethodName().hashCode(); 527 | id = id + (result.getParameters() != null ? Arrays.hashCode(result.getParameters()) : 0); 528 | return id; 529 | } 530 | 531 | private void getAllTestIds(ITestContext context, ISuite suite) { 532 | IResultMap passTests = context.getPassedTests(); 533 | IResultMap failTests = context.getFailedTests(); 534 | List invokedMethods = suite.getAllInvokedMethods(); 535 | for (IInvokedMethod im : invokedMethods) { 536 | if (passTests.getAllMethods().contains(im.getTestMethod()) || failTests.getAllMethods().contains(im.getTestMethod())) { 537 | int testId = getId(im.getTestResult()); 538 | allRunTestIds.add(testId); 539 | } 540 | } 541 | } 542 | } 543 | -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/util/testng/RetryListener.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.util.testng; 2 | 3 | import org.testng.IAnnotationTransformer; 4 | import org.testng.IRetryAnalyzer; 5 | import org.testng.annotations.ITestAnnotation; 6 | 7 | import java.lang.reflect.Constructor; 8 | import java.lang.reflect.Method; 9 | 10 | public class RetryListener implements IAnnotationTransformer { 11 | 12 | public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) { 13 | IRetryAnalyzer retry = annotation.getRetryAnalyzer(); 14 | if (retry == null) { 15 | annotation.setRetryAnalyzer(TestngRetry.class); 16 | } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/util/testng/TestResultListener.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.util.testng; 2 | 3 | import com.xxxx.uiautotest.util.Log; 4 | import com.xxxx.uiautotest.base.AutoTestBase; 5 | import org.testng.ITestContext; 6 | import org.testng.ITestResult; 7 | import org.testng.Reporter; 8 | import org.testng.TestListenerAdapter; 9 | 10 | import java.text.SimpleDateFormat; 11 | import java.util.*; 12 | 13 | public class TestResultListener extends TestListenerAdapter { 14 | @Override 15 | public void onTestFailure(ITestResult tr) { 16 | super.onTestFailure(tr); 17 | Log.logInfo(tr.getName() + " Failure"); 18 | saveScreenShot(tr); 19 | } 20 | 21 | @Override 22 | public void onTestSkipped(ITestResult tr) { 23 | super.onTestSkipped(tr); 24 | Log.logInfo(tr.getName() + " Skipped"); 25 | saveScreenShot(tr); 26 | } 27 | 28 | @Override 29 | public void onTestSuccess(ITestResult tr) { 30 | super.onTestSuccess(tr); 31 | Log.logInfo(tr.getName() + " Success"); 32 | } 33 | 34 | @Override 35 | public void onTestStart(ITestResult tr) { 36 | super.onTestStart(tr); 37 | Log.logInfo(tr.getName() + " Start"); 38 | } 39 | 40 | @Override 41 | public void onFinish(ITestContext testContext) { 42 | super.onFinish(testContext); 43 | ArrayList testsToBeRemoved = new ArrayList(); 44 | Set passedTestIds = new HashSet(); 45 | for (ITestResult passedTest : testContext.getPassedTests().getAllResults()) { 46 | Log.logInfo("PassedTests : " + passedTest.getName()); 47 | passedTestIds.add(getId(passedTest)); 48 | } 49 | 50 | Set skipTestIds = new HashSet(); 51 | for (ITestResult skipTest : testContext.getSkippedTests().getAllResults()) { 52 | Log.logInfo("skipTest : " + skipTest.getName()); 53 | 54 | int skipTestId = getId(skipTest); 55 | 56 | if (skipTestIds.contains(skipTestId) || passedTestIds.contains(skipTestId)) { 57 | testsToBeRemoved.add(skipTest); 58 | } else { 59 | skipTestIds.add(skipTestId); 60 | } 61 | } 62 | 63 | Set failedTestIds = new HashSet(); 64 | for (ITestResult failedTest : testContext.getFailedTests().getAllResults()) { 65 | Log.logInfo("FailedTest : " + failedTest.getName()); 66 | int failedTestId = getId(failedTest); 67 | 68 | if (failedTestIds.contains(failedTestId) || passedTestIds.contains(failedTestId) || 69 | skipTestIds.contains(failedTestId)) { 70 | testsToBeRemoved.add(failedTest); 71 | } else { 72 | failedTestIds.add(failedTestId); 73 | } 74 | } 75 | 76 | for (Iterator iterator = testContext.getFailedTests().getAllResults().iterator(); iterator.hasNext(); ) { 77 | ITestResult testResult = iterator.next(); 78 | if (testsToBeRemoved.contains(testResult)) { 79 | Log.logInfo("Remove repeat Fail Test : " + testResult.getName()); 80 | iterator.remove(); 81 | } 82 | } 83 | 84 | } 85 | 86 | private int getId(ITestResult result) { 87 | int id = result.getTestClass().getName().hashCode(); 88 | id = id + result.getMethod().getMethodName().hashCode(); 89 | id = id + (result.getParameters() != null ? Arrays.hashCode(result.getParameters()) : 0); 90 | return id; 91 | } 92 | 93 | private void saveScreenShot(ITestResult tr) { 94 | String filePath_testngReports = null; 95 | SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss"); 96 | String mDateTime = formatter.format(new Date()); 97 | String fileName = tr.getName() + "-" + mDateTime; 98 | 99 | try { 100 | filePath_testngReports = AutoTestBase.ScreenShot(fileName); 101 | } catch (Exception e) { 102 | Log.logInfo(tr.getName() + " takeScreenshot Failure:"); 103 | } 104 | 105 | if (null != filePath_testngReports && !"".equals(filePath_testngReports)) { 106 | Reporter.setCurrentTestResult(tr); 107 | Reporter.log(""); 108 | 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /src/test/java/com/xxxx/uiautotest/util/testng/TestngRetry.java: -------------------------------------------------------------------------------- 1 | package com.xxxx.uiautotest.util.testng; 2 | 3 | import com.xxxx.uiautotest.util.Log; 4 | import org.apache.log4j.Logger; 5 | import org.testng.IRetryAnalyzer; 6 | import org.testng.ITestResult; 7 | import org.testng.Reporter; 8 | 9 | public class TestngRetry implements IRetryAnalyzer { 10 | private static Logger logger = Logger.getLogger(TestngRetry.class); 11 | private int retryCount = 1; 12 | private static int maxRetryCount; 13 | 14 | static { 15 | ConfigReader config = ConfigReader.getInstance(); 16 | maxRetryCount = config.getRetryCount(); 17 | Log.logInfo("retrycount=" + maxRetryCount); 18 | Log.logInfo("sourceCodeDir=" + config.getSourceCodeDir()); 19 | Log.logInfo("sourceCodeEncoding=" + config.getSrouceCodeEncoding()); 20 | } 21 | 22 | public boolean retry(ITestResult result) { 23 | if (retryCount <= maxRetryCount) { 24 | String message = "Retry for [" + result.getName() + "] on class [" + result.getTestClass().getName() + "] Retry " 25 | + retryCount + " times"; 26 | logger.info(message); 27 | Reporter.setCurrentTestResult(result); 28 | Reporter.log("RunCount=" + (retryCount + 1)); 29 | retryCount++; 30 | return true; 31 | } 32 | return false; 33 | } 34 | 35 | public static int getMaxRetryCount() { 36 | return maxRetryCount; 37 | } 38 | 39 | public int getRetryCount() { 40 | return retryCount; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/test/test.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /testSuits/testSuitExample_android.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | //adb devices 获取 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /testSuits/testSuitExample_ios.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /testSuits/testSuitExample_web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /tools/GenerateTestNgReport.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kinget007/UIAutoTest/03d1d920818c2ba3364ca189cd1929afc982edfe/tools/GenerateTestNgReport.jar -------------------------------------------------------------------------------- /tools/analysisxml.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kinget007/UIAutoTest/03d1d920818c2ba3364ca189cd1929afc982edfe/tools/analysisxml.jar -------------------------------------------------------------------------------- /tools/driverServer/chromedriver: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kinget007/UIAutoTest/03d1d920818c2ba3364ca189cd1929afc982edfe/tools/driverServer/chromedriver -------------------------------------------------------------------------------- /tools/driverServer/chromedriver.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kinget007/UIAutoTest/03d1d920818c2ba3364ca189cd1929afc982edfe/tools/driverServer/chromedriver.exe -------------------------------------------------------------------------------- /tools/driverServer/operadriver: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kinget007/UIAutoTest/03d1d920818c2ba3364ca189cd1929afc982edfe/tools/driverServer/operadriver -------------------------------------------------------------------------------- /tools/testngReport/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | --------------------------------------------------------------------------------
" + label + "" + label + "