找回密码
 会员注册
查看: 22|回复: 0

UI遍历中页面定义和动作事件筛选方法

[复制链接]

2万

主题

0

回帖

6万

积分

超级版主

积分
64454
发表于 2024-10-11 21:43:48 | 显示全部楼层 |阅读模式
点击上方蓝字关注我们!背景在Android UI遍历测试中,除传统的基于monkey的随机性测试外,基于模型的测试在测试覆盖率和可回溯性上表现更好,是目前热门的研究方向。在基于模型的测试中,对UI页面的定义和动作事件的筛选是十分重要而基础的工作。本文将介绍UI页面定义和动作事件筛选的具体方法。技术实现本项目使用python。我们可以使用UI Automator来获取UI界面的层次树信息。01View TreeUI页面其实是一种树状结构的数据,称作view tree,其节点是每一个子view。每个子view一般包含'resource_id'、'scrollable'、'clickable'、'bounds'等信息,我们可以充分利用这些信息,来对页面进行定义以及动作事件的筛选。为了便于操作,先将view tree树结构转成list列表形式,并保存子view的index索引等信息。defget_view_list(view_tree):view_tree['parent']=-1view_list=[]view_tree_to_list(0,view_tree,view_list)self.last_acc_event['view_list']=view_listreturnview_listdefview_tree_to_list(index,view_tree,view_list):tree_id=len(view_list)view_tree['temp_id']=tree_idbounds=[[-1,-1],[-1,-1]]bounds[0][0]=view_tree['bounds'][0]bounds[0][1]=view_tree['bounds'][1]bounds[1][0]=view_tree['bounds'][2]bounds[1][1]=view_tree['bounds'][3]width=bounds[1][0]-bounds[0][0]height=bounds[1][1]-bounds[0][1]view_tree['size']="%d*%d"%(width,height)view_tree['index']=indexview_tree['bounds']=boundsview_list.append(view_tree)children_ids=[]foriteminrange(len(view_tree['children'])):child_tree=view_tree['children'][item]child_tree['parent']=tree_idview_tree_to_list(item,child_tree,view_list)children_ids.append(child_tree['temp_id'])view_tree['children']=children_ids02新页面的定义由于App里有feed页等可无限刷新的页面,这就需要对页面进行定义区分,将类似的页面归为一类,避免UI遍历过程陷入无限循环的状态。这里,我们对页面有效信息提取成文本,并哈希成字符串作为该页面的唯一标识符。具体地,我们提取了每一个子view的'class'、'clickable'、'checked'、'scrollable'、'long-clickable'、'text'这些信息,将UI页面所有的子view信息组成文本,并用md5哈希成字符串。defget_state_str(view_list):state_str_raw=get_state_str_raw(view_list)returnmd5(state_str_raw)defget_state_str_raw(view_list):view_signatures=set()forviewinview_list:view_signature=get_view_signature(view)ifview_signature:view_signatures.add(view_signature)return"%s{%s}"%(self.foreground_activity,",".join(sorted(view_signatures)))defget_view_signature(view_dict):view_text=view_dict['text']ifview_textisNoneorlen(view_text)>50:view_text="None"signature="[class]%s[text]%s[%s,%s,%s,%s]"%\(view_dict['class'],view_text,view_dict['clickable'],view_dict['checked'],view_dict['scrollable'],view_dict['long-clickable'])returnsignature03动作事件筛选我们遍历所有的子view,首先去掉'resource_id'为'android:id/navigationBarBackground'、'android:id/statusBarBackground'的导航栏的view,这在遍历测试中是不需要的。但有的app,它的导航栏view的'resource_id'不是这个,那就需要新加入过滤的内容,或者直接通过顶栏的坐标过滤掉导航栏。过滤完系统的view事件之后,我们继续筛选,这里我们选取了'scrollable'或者'clickable'为true的子view。当然我们还可以筛选'enabled'、'focusable'等为true的子view事件,可以根据项目实际需要自行定义选择。defget_possible_input(view_list):possible_events=[]enabled_view_ids=[]touch_exclude_view_ids=set()forview_dictinview_list:ifview_dict['enabled']and\view_dict['resource_id']notin\['android:id/navigationBarBackground','android:id/statusBarBackground']:enabled_view_ids.append(view_dict['temp_id'])forview_idinenabled_view_ids:ifview_list[view_id]['scrollable']:possible_events.append(ScrollEvent(view=views_list[view_id],direction="UP"))possible_events.append(ScrollEvent(view=views_list[view_id],direction="DOWN"))possible_events.append(ScrollEvent(view=views_list[view_id],direction="LEFT"))possible_events.append(ScrollEvent(view=views_list[view_id],direction="RIGHT"))elifview_list[view_id]['clickable']:possible_events.append(TouchEvent(view=views_list[view_id]))touch_exclude_view_ids.add(view_id)#elifviews_list[view_id]['enabled']and\#views_list[view_id]['focusable']:#possible_events.append(TouchEvent(view=views_list[view_id]))returnpossible_events我们如果想优先把'scrollable'的事件放在前面,可以分开筛选。有的时候向下的ScrollEvent事件是不必要的,我们也可以注释掉。forview_idinenabled_view_ids:ifviews_list[view_id]['scrollable']:possible_events.append(ScrollEvent(view=views_list[view_id],direction="UP"))#possible_events.append(ScrollEvent(view=views_list[view_id],direction="DOWN"))possible_events.append(ScrollEvent(view=views_list[view_id],direction="LEFT"))possible_events.append(ScrollEvent(view=views_list[view_id],direction="RIGHT"))forview_idinenabled_view_ids:ifviews_list[view_id]['clickable']:possible_events.append(TouchEvent(view=views_list[view_id]))touch_exclude_view_ids.add(view_id)另外,我们还可以过滤一些不必要的子view事件。比如,有的子view的坐标bounds超出了UI界面,这些界面外子view是不需要去测试遍历的。还有的子view的bounds会挤在一小块的像素内,这些也是不必要的。这里,我们设置如果bounds上下边界坐标差小于5时,就过滤掉。deffilter_possible_input(possible_events,origin_dim=[1080,1920]):filter_events=[]foreventinpossible_events:#过滤坐标为负的值bounds=event.view["bounds"]bounds=[bounds[0][0],bounds[0][1],bounds[1][0],bounds[1][1]]x_min=max(0,bounds[0])y_min=max(0,bounds[1])x_max=min(origin_dim[0],bounds[2])y_max=min(origin_dim[1],bounds[3])ifx_min>=x_maxory_min>=y_max:continue#更新bounds坐标点event.view["bounds"]=[[x_min,y_min],[x_max,y_max]]#过滤小于5个像素的eventif(y_max-y_min)
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-27 02:08 , Processed in 0.549921 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表