Mobile Development 8 min read

Defining Android UI Pages and Filtering Action Events for Model‑Based Testing

This article explains how to represent Android UI view trees as lists, generate unique page signatures, and filter actionable events such as scrolls and touches to improve model‑based testing coverage and traceability, providing Python code examples for each step.

360 Quality & Efficiency
360 Quality & Efficiency
360 Quality & Efficiency
Defining Android UI Pages and Filtering Action Events for Model‑Based Testing

In Android UI model‑based testing, defining UI pages and selecting relevant action events are essential for achieving higher coverage and traceability compared to random monkey testing. The UI hierarchy, called a view tree, consists of nodes (sub‑views) that contain attributes like resource_id , clickable , scrollable , and bounds . Converting this tree into a flat list while preserving parent‑child relationships enables systematic processing.

def get_view_list(view_tree): view_tree['parent'] = -1 view_list = [] view_tree_to_list(0, view_tree, view_list) self.last_acc_event['view_list'] = view_list return view_list def view_tree_to_list(index, view_tree, view_list): tree_id = len(view_list) view_tree['temp_id'] = tree_id bounds = [[-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'] = index view_tree['bounds'] = bounds view_list.append(view_tree) children_ids = [] for item in range(len(view_tree['children'])): child_tree = view_tree['children'][item] child_tree['parent'] = tree_id view_tree_to_list(item, child_tree, view_list) children_ids.append(child_tree['temp_id']) view_tree['children'] = children_ids

To distinguish pages that may refresh indefinitely (e.g., feed pages), each page is represented by a textual signature composed of selected sub‑view attributes ( class , clickable , checked , scrollable , long‑clickable , text ) and then hashed with MD5 to obtain a unique identifier.

def get_state_str(view_list): state_str_raw = get_state_str_raw(view_list) return md5(state_str_raw) def get_state_str_raw(view_list): view_signatures = set() for view in view_list: view_signature = get_view_signature(view) if view_signature: view_signatures.add(view_signature) return "%s{%s}" % (self.foreground_activity, ",".join(sorted(view_signatures))) def get_view_signature(view_dict): view_text = view_dict['text'] if view_text is None or len(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'] ) return signature

Action‑event filtering proceeds by first discarding system navigation bar views (identified by known resource_id values) and then selecting sub‑views whose scrollable or clickable flags are true. Additional criteria such as enabled or focusable can be added as needed.

def get_possible_input(view_list): possible_events = [] enabled_view_ids = [] touch_exclude_view_ids = set() for view_dict in view_list: if view_dict['enabled'] and view_dict['resource_id'] not in ['android:id/navigationBarBackground', 'android:id/statusBarBackground']: enabled_view_ids.append(view_dict['temp_id']) for view_id in enabled_view_ids: if view_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")) elif view_list[view_id]['clickable']: possible_events.append(TouchEvent(view=views_list[view_id])) touch_exclude_view_ids.add(view_id) return possible_events

Further filtering removes events whose bounding coordinates lie outside the device screen or whose height/width is less than five pixels, ensuring only meaningful interactions are kept.

def filter_possible_input(possible_events, origin_dim=[1080, 1920]): filter_events = [] for event in possible_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]) if x_min >= x_max or y_min >= y_max: continue event.view["bounds"] = [[x_min, y_min], [x_max, y_max]] if (y_max - y_min) < 5: pass else: filter_events.append(event) return filter_events

By defining pages through hashed signatures and filtering actionable events, different UI states can be grouped, redundant interactions eliminated, and a directed graph of UI states can be constructed. This graph enables the application of traversal algorithms such as depth‑first search, heuristic search, or reinforcement learning to achieve thorough automated testing of Android applications.

mobile developmentpythonAndroidView TreeUI testingModel-Based TestingEvent Filtering
360 Quality & Efficiency
Written by

360 Quality & Efficiency

360 Quality & Efficiency focuses on seamlessly integrating quality and efficiency in R&D, sharing 360’s internal best practices with industry peers to foster collaboration among Chinese enterprises and drive greater efficiency value.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.