接到一个android自动化的任务,看了看手中的家伙:ranorex,appium,uiautomator
当然先捡商用的试试,简单呀,可以录制回放,不过不是抱特别大的期望,这个爷比较娇气,要是android工程中有第三方库可能就会instrument失败。这次运气不错,instrument成功了,录制了一下常用的操作,一切OK。想想还要准备一些啥:
先说手势:搜搜帮助:
public void Swipe( Location startLocation, GestureDirection direction, int distance, Duration swipeDuration, int steps ) |
好吧,照抄写一个函数出来,留着备用:
public void myUp2Down(string argument1) { int iparam1=Convert.ToInt32(argument1); for (int i=1;i<=iparam1;i++){
Report.Log(ReportLevel.Info, "Touch Gestures", "Swipe gesture with direction 'Up (270°)' starting from 'Center' with distance '100' with swipe duration'500ms' and step count '0' on item 'ComWumiiAndroidMimi.ListView'.", repo.xxx.ListViewInfo, new RecordItemIndex(1)); repo.xxx.ListView.Swipe(Location.Center, 270, 100, 500, 4); Delay.Milliseconds(500); } }
这个注释已经说的很清楚了,就是从下往上滑动,270度是向上,180度是左面,0度是右面,90度是下面。100是距离,500是持续时间,4是步骤
|
拷屏:
pc: Report.Screenshot();
android手机: Report.Screenshot(ReportLevel.Info, "User", "", repo.xx.Self, false); Report.Screenshot(ReportLevel.Info, "User", "", repo.xx.MyHomeActivity, false);
都是数据仓库中的对象,很简单吧 |
再往下做的时候,发现一个问题,ranorex不能跨应用,而这个被测程序要分享给什么微信,新浪微博一类的。得,换刀。
用啥呢,appium是最全面的,就他吧。开始的时候总是很愉快的:
拷屏:
public static void takeScreenShot(WebDriver driver,String s1) { File screenShotFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE); try { FileUtils.copyFile(screenShotFile, new File(s1)); } catch (IOException e) {e.printStackTrace();} } |
public static String getCurrentDateTime(){ SimpleDateFormat df = new SimpleDateFormat("HHmmss");//yyyyMMddHHmmss return df.format(new Date()); } |
调用方法: private WebDriver driver; takeScreenShot(driver,"/sdcard/"+getCurrentDateTime()+"main.png"); |
暴力等待:
public void mysleep(int i1){ try { Thread.sleep(i1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } 调用:等待5s:mysleep(5000); |
想想,要是可以用控件id引用,不是来的直接可靠得多。好像真机上hierarchyviewer.bat(D:\Android\android-sdk\tools)抓不到布局,用模拟器试试,提醒现在是monitor.bat时代了,直接使用管理员权限打开monitor.bat(右键该文件,选择管理员权限启动)。启动模拟器,等了半天,总算得到了布局文件,是不是一大堆东西,没事,只需要点击到几个主干节点上,就可以看到这几个节点所对应的元素图片,很容易找到吧,还可以用来调优哟,留心看看有个三个圆圈的按钮,会显示每个view的加载时间。
然后非常悲剧的,花了很长时间才试出来如何引用id:
//WebElement el = driver.findElement(By.id("btnImsi")); //error //WebElement el = driver.findElement(By.id("@+id/btnImsi")); //error //WebElement el = driver.findElement(By.id("id/btnImsi")); //error //WebElement el = driver.findElement(By.id("1")); //error //WebElement el = driver.findElement(By.id("汉字一")); //error WebElement el = driver.findElement(By.id("com.example.aimsi:id/btnImsi")); //ok el.click(); |
本来也算不错了,老是不停的切换真机和模拟器,hierarchyviewer这个破东西又慢的要死,中间又要启动appium.exe,而且要引用对象又要tagname,还得list里面去找时第几个,真是很烦人,等待的策略也没搜到让人爽一点的。而且那个sendkey在这台机器上每次都是抽风状态的,发个123,他能输出个321,肚脐眼都能被气歪。
得,再次换刀,uiautomator可是google的亲生儿子,一开头就弄了一个下马威:
拷屏:死活不行,终于明白了,就俩字:版本!
//for >=android4.2 public void TakeScreenShotsGE42(String s1) { File storePath = new File(s1); getUiDevice().takeScreenshot(storePath); } |
private void runShellCommand(String command) throws IOException, InterruptedException { Process p = null; BufferedReader resultReader = null; try { p = Runtime.getRuntime().exec(command); int status = p.waitFor(); if (status != 0) { throw new RuntimeException(String.format("Run shell command: %s, status: %s", command, status)); } } finally { if (resultReader != null) { resultReader.close(); } if (p != null) { p.destroy(); } } }
//for public void TakeScreenShotsL42(String s1) { // adb shell screencap -p /data/local/tmp/screen-capture.png // adb pull /data/local/tmp/screen-capture.png // adb shell rm /data/local/tmp/screen-capture.png try { runShellCommand("screencap -p "+s1); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } |
看到了吧,啥叫作孽,低版本的就得去调用底层的命令行,高版本的就俩句 调用方法就都差不多:
import java.util.Date; import java.text.SimpleDateFormat; public String getNowPng(){ SimpleDateFormat df = new SimpleDateFormat("MMddHHmmss"); //yyyy-MM-dd HH:mm:ss return "/sdcard/"+df.format(new Date())+".png"; }
TakeScreenShotsL42(getNowPng()); TakeScreenShotsGE42(getNowPng()); |
总算搞定了,看着有人说可以绕过那个破东西hierarchyviewer,可以在运行过程中保存实时布局,心下大爽,看,多简单呀,就这么两句:
public void dump(String s1) { UiDevice uiDevice = getUiDevice(); uiDevice.dumpWindowHierarchy(s1); } 调用也简单:dump(“d1.xml”); 会写入: /data/local/tmp目录,回头adb pull /data/local/tmp/d1.xml 拖下来就是了 |
理想黑丰满,现实超骨干,执行没报错,但是就是没生成,再次从真机(4.1.1)切换为模拟器(4.4.2),果然模拟器上执行正常,八成又是版本问题。你妹的
再次尝试id引用:
UiObject ac1=new UiObject(new UiSelector().resourceId("com.xx.android.xxx:id/menu")); |
哇,多爽呀,可以精确定位啦!大不了麻烦一点,先在模拟器中取得各个界面的布局文件,然后想操作那个对象都可以直接引用了,超级爽呀!
别整天做梦娶媳妇想得美了。这个也是高版本才行!真是让人抓狂!
好了,迄今为止,uiautomator基本上是够用了,整理思路,可以这样,每次开始前删除指定目录的文件,回头每次动作都保存图片进去。对象的引用方法也别想id了,又不支持中文,contentDescription更没指望,直接classname加index来吧,没啥搞不定的。对象等待也简单统一写法。好,一样样的来说吧:
先删除sd卡中的一个目录:
public void deletefile(String s1) { File file =new File(s1); File files[] = file.listFiles(); if (files != null) { for (int i = 0; i < files.length; i++) { // 遍历目录下所有的文件 files[i].delete(); } } }
调用方法:deletefile("/sdcard/xxx/"); |
对象引用方法,直接ddms(留心看有个小图标:Dump View for hierarchyviewer UI Automator),你不会连ddms怎么打开都不会吧?(window-openperspective,如果有ddms就直接打开,没有就选择other…,这下总该看到了吧):
UiObject ac02_1 = new UiObject(new UiSelector().className(android.widget.LinearLayout.class.getName()).index(0)); UiObject ac02_2 = ac02_1.getChild(new UiSelector().className(android.widget.FrameLayout.class.getName()).index(0)); UiObject ac02_3 = ac02_2.getChild(new UiSelector().className(android.widget.ImageView.class.getName()).index(0));
你可能奇怪,为啥引用这么多层,原因是这样的,当前页面中,有很多ImageView,而且index都是0,你要是直接那么引用,就会有重复的ImageView,无法定位到唯一的对象,即便你加上了上一层的FrameLayout,index=0,也未必就是唯一的。这种引用方法很是麻烦。但是唯一可靠,逻辑统一。
缺点吗,写出来的代码,我自己都看不出来是引用的啥对象。 |
就在已经差不多结束的时候,又冒出来一个小插曲,共享给qq客户端,没想到打开了一个老版本的,居然是webview,tnnd,uiautomator对付不了webview,无法引用到webview内部的对象,就算绝对定位后,点击了控件也没法输入用户名和密码。其实这样说是不公道的,UIAutomator是可以对付webview,不过,估计看官已经猜到了,又是你妹的高版本就可以!咆哮!
我总不能又绕回来使用appium,江湖传言,Appium在4.1以上使用uiautomator, 4.1以下使用selendroid,而selendroid擅长这个东东。我去。
得,最终敲定方案:
大部分简单功能都用ranorex,简单实用才是王道;
共享部分使用uiautomator,亲生的比appium还是更简单一些。
Webview怎么搞?装一个客户端就行了,没兴趣花时间去为了这么点功能啃骨头。
同学们看着玩吧。