Android TV磁贴类app自动化框架二次改造(基于UiAutomator)
2016-04-20 22:20
471 查看
简介
项目一直是手工测试为主,加上一直是TV类应用,很多自动化工具都没有针对TV类项目做很好的适配,所以只有自己动手了。主要针对项目的特殊性进行了部分改造,不一定适用于其他项目。(涉及隐私,就不提供json文件和软件名字啦)痛点
1.非标准控件的难处通过uiautomatorviewer获取到的不一样的磁贴,属性全部相同(除了坐标点),意味着没法通过id和class+index方式获取,text属性为空,也就没有办法通过byText的方式获取uiobject,高度定制的磁贴,让自动化很为难,如果通过坐标点,太坑爹,不能跨设备,还是坑。
2.TV类应用没有触摸操作
TV类安卓程序,主要面向的是遥控器,也就是接收的是keyevent,所以touch事件显得不这么全面,为了最接近用户,还是选择用key来做自动化。
架构模块
解析模块
从服务器解析json文件格式,包装成实体类CellInfo,返回一个包含磁铁信息的List。一个磁铁对应一个CellInfo,一个CellInfo需要提取的信息有x坐标
y坐标
所属的Tab分类页
每个磁铁的说明标签
所以对应的定义如下:
package launcherClick.model; public class CellInfo { private String label; private String tab; public String getTab() { return tab; } public void setTab(String tab) { this.tab = tab; } private int x; private int y; public String getLabel() { return label; } public void setLabel(String label) { this.label = label; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } }
解析用的是第三方开源库org.json,对应的解析代码如下:
package launcherClick; import java.util.ArrayList; import java.util.List; import org.json.JSONArray; import org.json.JSONObject; import Utils.IOUtils; import Utils.Println; import launcherClick.model.CellInfo; public class AMetroParse { public static List<CellInfo> startParse(String url) { String str = IOUtils.readFromNet(url); if(str==null){ return null; } JSONObject jsonObject = new JSONObject(str); List<CellInfo> list = new ArrayList<CellInfo>(); JSONArray _tabs = jsonObject.getJSONArray("tabs"); for(int i = 0;i<_tabs.length();i++){ String tab = _tabs.getJSONObject(i).getString("label"); JSONArray _cells = _tabs.getJSONObject(i).getJSONArray("cells"); for(int j=0;j<_cells.length();j++){ CellInfo cellInfo = new CellInfo(); cellInfo.setX(_cells.getJSONObject(j).getJSONObject("location").getInt("x")); cellInfo.setY(_cells.getJSONObject(j).getJSONObject("location").getInt("y")); cellInfo.setLabel(_cells.getJSONObject(j).getJSONObject("content").getString("label")); cellInfo.setTab(tab); list.add(cellInfo); } } return list; } }
坐标转换模块
这部分的工作是把上一步骤解析后的实体类进行提取和处理,主要处理的内容是根据x y坐标计算磁铁移动量,更具x的极大值和分类页做跨分类移动的偏移计算以及一些其他处理。通过x y计算偏移比较简单,只是简单计算距离:
for (CellInfo cellInfo : list) { if (cellInfo.getLabel().contains(label) && cellInfo.getTab().contains(tab)) { int x = cellInfo.getX(); int y = cellInfo.getY(); new Println("x:" + x + " " + "y:" + y + " " + "偏移量" + _offset); // x方向 for (int i = x + _offset; i > 1; i--) { new Println("→" + " count is " + (x + _offset)); keyRight(); } // y方向 for (int i = y; i > 1; i--) { new Println("↓"); keyDown(); } } }
恩,里面有个偏移量,跨分类用的,偏移量是通过每个分类下磁铁最大值得出来的,每一个分类最后一个磁铁的x值即为最大值,遍历当前分类下的所有磁铁的x,如果大于后面一个,则放在前面,下次再用这个值去比较下面的x值。忘记这是什么排序算法了…囧…当然,也可以用Collections的自带的算法。
private int togicOffset(String tab) { int lineLenth = 0; List<CellInfo> list = AMetroParse .startParse("我是隐藏的接口"); for (CellInfo cellInfo : list) { if (cellInfo.getTab().contains(tab)) { int x = cellInfo.getX(); lineLenth = lineLenth >= x ? lineLenth : x; } } return lineLenth; }
那么通过上面的排序之后,就可以得到每个分类的偏移量,这样对于后面的分类磁贴,就可以知道磁贴在整体的真正位置啦,于是就可以开始移动了。
for (CellInfo cellInfo : list) { if (cellInfo.getLabel().contains(label) && cellInfo.getTab().contains(tab)) { int x = cellInfo.getX(); int y = cellInfo.getY(); new Println("x:" + x + " " + "y:" + y + " " + "偏移量" + _offset); // x方向 for (int i = x + _offset; i > 1; i--) { new Println("→" + " count is " + (x + _offset)); keyRight(); } // y方向 for (int i = y; i > 1; i--) { new Println("↓"); keyDown(); } } }
图片比较模块
来源于monkeyrunner的思路,原理是计算两个bitmap的长宽,然后提取出里面每个像素的像素值,如果相同,相似度+1,最后再除以总像素值(比如1280x720),这样就可以把相似程度转为一个一个可以衡量的具体值了,那么判断这个界面是不是我需要点击的时候,只需要截屏当前图片和预期的图片对比,相似度达到100的时候就认为是正确的。当然也提供了局部比较的功能,比如获取不到数据的异常提示。那么代码如下,提供了多种重载的方法以及两个assert判断。package Utils; import junit.framework.Assert; import android.graphics.Bitmap; import android.graphics.BitmapFactory; public class ImageCompare { public static void AssertBitmapEqual(Bitmap bitmap0, Bitmap bitmap1){ /** * 百分百图片相同断言-.- */ Assert.assertEquals(100, ImageCompare(bitmap0,bitmap1)); } public static void AssertBitmapNotEqual(Bitmap bitmap0, Bitmap bitmap1){ /** * 百分之零图片相同断言 */ Assert.assertEquals(0, ImageCompare(bitmap0,bitmap1)); } public static int ImageCompare(String path0,String path1) { /** * 提供根据路径直接比较 */ Bitmap bitmap0 = BitmapFactory.decodeFile(path0); Bitmap bitmap1 = BitmapFactory.decodeFile(path1); return ImageCompare(bitmap0,bitmap1); } public static int ImageCompareChild(Bitmap bitmap0, Bitmap bitmap1,int x,int y,int width,int height) { /** * 裁剪子图并比较,主要是为了解决拉取动态数据不同,但是局部提示不变的比较场景。 */ Bitmap bitmap00 = bitmap0.createBitmap(bitmap0, x, y, width, height); Bitmap bitmap01 = bitmap1.createBitmap(bitmap1, x, y, width, height); return ImageCompare(bitmap00,bitmap01); } public static int ImageCompare(Bitmap bitmap0, Bitmap bitmap1) { /** * 比较的主函数 * 只能比较相同长宽的图片,不相等返回-1失败 * 相似度为1~100 * 原理是提取每一个像素点比较,整张图相似度取决于像素点相同个数,所以还是比较准确的 */ int picPct = 0; int picCount = 0; int picCountAll = 0; new Println("begin to compare"); if (bitmap0 == null || bitmap1 == null) { new Println("null bitmap"); return -1; } if (bitmap0.getWidth() != bitmap1.getWidth() || bitmap0.getHeight() != bitmap1.getHeight()) { return -1; } new Println("宽度为:" + bitmap1.getWidth() + "高度为:" + bitmap1.getHeight()); for (int j = 0; j < bitmap1.getWidth(); j++) { for (int i = 0; i < bitmap0.getHeight(); i++) { if (bitmap0.getPixel(j, i) == bitmap1.getPixel(j, i)) { picCount++; } picCountAll++; } } int result = (int) (((float) picCount) / picCountAll * 100); new Println(picCount + "/" + picCountAll); new Println("相似度为:" + result); return result; } }
异常&&其他模块
定义了一些异常类,主要功能用于提示,这个提示的作用后面会用到。增加了启动应用和关闭应用的方法,原理是用到了shell命令。
Runtime.getRuntime().exec("am start -n 我是包名隐藏者");
初始化方法,主要用于异常时能够一键重新开始,以及磁贴复原功能,还有其他一些小的处理就不多说啦,下面开始持续构建。
自动化的持续集成
还是那句话,不持续集成的自动化不是自动化,所以这里介绍的是基于jenkins的自动化集成,其实UiAutomator做集成还是很容易的,只要把jar包放在一个固定的目录,然后shell命令执行就完事了。然后定时任务自己选吧,构建失败的邮件提醒这里设置的是,上面我自定义的异常类,当控制台输出我的异常类,那么会认为不通过,然后触发邮件提醒。
那么至此,一个较为完整的流程就完成啦,那么在此框架上组员们(就我一个)就可以更进一部去完善二级页面的自动化用例了。
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories