You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Appium将RecyclerView所有项存入ArrayList并验证安卓排序筛选功能

解决Appium中RecyclerView懒加载下的Sort&Filter自动化验证问题

我之前在电商App的自动化测试项目里,完全碰到过和你一模一样的痛点——RecyclerView懒加载导致只能获取可见项,滚动后索引乱跳,根本没法直接拿到所有结果来验证排序和筛选。下面是我实际项目里用过的几个靠谱方案,附示例代码,你可以参考:

方案一:分段滚动+局部验证+趋势检查(无需全量数据)

如果不需要严格验证每一项,这个方案最省时间:

  • 验证筛选有效性:每次滚动后,检查当前可见的所有项是否都符合筛选条件(比如分类、价格区间)
  • 验证排序趋势:比如按价格升序,就检查当前批次的最后一项价格 ≤ 下一批次的第一项价格,保证整体趋势正确
  • 辅助验证:同时检查页面上的排序/筛选标签是否正确,以及结果总数是否和接口返回一致(如果能调接口的话)

示例代码片段(Python):

def verify_sort_trend_and_filter():
    # ... 初始化driver、搜索、排序筛选操作省略 ...
    
    last_price = -1.0
    while True:
        current_items = get_visible_items(driver)
        # 验证当前可见项符合筛选条件
        for item in current_items:
            assert item["category"] == "智能", f"商品{item['name']}不符合筛选"
            # 验证排序趋势
            if last_price != -1.0:
                assert item["price"] >= last_price, f"排序异常:{last_price} > {item['price']}"
            last_price = item["price"]
        
        # 尝试滚动,判断是否到底部
        screen_size = driver.get_window_size()
        start_y = screen_size['height'] * 3//4
        end_y = screen_size['height']//4
        driver.swipe(0, start_y, 0, end_y, 500)
        time.sleep(1)
        
        # 检查是否还有新项(比如判断当前可见项的第一个是否和上一次的最后一个重复)
        new_first_item = get_visible_items(driver)[0]
        if new_first_item["name"] == current_items[-1]["name"]:
            break

方案二:滚动收集+去重缓存(获取全量数据验证)

如果必须验证每一项,那就通过滚动分批获取可见项,用唯一标识去重后缓存,直到滚动到底部:

  • 核心是不依赖元素索引,而是用商品ID、唯一名称等标识判断是否已经收集过
  • 滚动时要处理加载状态,避免漏项

完整示例代码(Python):

from appium import webdriver
import time

def get_visible_items(driver):
    """获取当前可见商品的详细信息(用商品ID作为唯一标识)"""
    items = driver.find_elements_by_id("com.yourapp:id/layout_product_item")
    item_list = []
    for item in items:
        prod_id = item.get_attribute("data-prod-id")  # 假设元素有唯一ID属性
        price = float(item.find_element_by_id("com.yourapp:id/tv_price").text.replace("¥", ""))
        category = item.find_element_by_id("com.yourapp:id/tv_category").text
        item_list.append({
            "id": prod_id,
            "price": price,
            "category": category
        })
    return item_list

def scroll_up(driver):
    """模拟向上滚动,触发RecyclerView加载更多"""
    screen = driver.get_window_size()
    driver.swipe(screen['width']//2, screen['height']*3//4, 
                 screen['width']//2, screen['height']//4, 600)
    time.sleep(1.5)  # 等待加载完成

def verify_full_sort_filter():
    # 初始化driver
    desired_caps = {
        "platformName": "Android",
        "deviceName": "Pixel_5",
        "appPackage": "com.yourshop.app",
        "appActivity": ".search.SearchResultActivity",
        "automationName": "UiAutomator2"
    }
    driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
    
    # 1. 执行搜索、排序筛选
    driver.find_element_by_id("com.yourshop.app:id/et_search").send_keys("无线耳机")
    driver.find_element_by_id("com.yourshop.app:id/btn_search").click()
    time.sleep(2)
    # 选择价格升序
    driver.find_element_by_id("com.yourshop.app:id/tv_sort").click()
    driver.find_element_by_xpath("//android.widget.TextView[@text='价格从低到高']").click()
    # 筛选"降噪"分类
    driver.find_element_by_id("com.yourshop.app:id/tv_filter").click()
    driver.find_element_by_xpath("//android.widget.CheckBox[@text='降噪']").click()
    driver.find_element_by_id("com.yourshop.app:id/btn_confirm").click()
    time.sleep(2)
    
    # 2. 滚动收集所有项(去重)
    all_prods = {}  # 用ID做key,避免重复
    prev_item_count = 0
    while True:
        current_items = get_visible_items(driver)
        for item in current_items:
            if item["id"] not in all_prods:
                all_prods[item["id"]] = item
        
        # 如果没有新增项,说明已滚动到底部
        if len(all_prods) == prev_item_count:
            break
        prev_item_count = len(all_prods)
        scroll_up(driver)
    
    # 3. 验证筛选条件
    for prod in all_prods.values():
        assert prod["category"] == "降噪", f"商品ID {prod['id']}不符合筛选条件"
    
    # 4. 验证排序(价格升序)
    sorted_prices = sorted([p["price"] for p in all_prods.values()])
    actual_prices = [p["price"] for p in all_prods.values()]
    assert actual_prices == sorted_prices, "排序结果不符合预期"
    
    driver.quit()

if __name__ == "__main__":
    verify_full_sort_filter()

方案三:接口+UI结合验证(高效且稳定)

这是我最喜欢的方案,兼顾效率和准确性:

  • 先调用后端的搜索、排序筛选接口,拿到全量的预期数据
  • UI层只需要验证前几项、后几项和接口返回一致,同时验证页面上的排序/筛选标签正确
  • 完全避开RecyclerView懒加载的问题,测试速度快很多

示例代码片段:

import requests

def get_expected_data():
    """调用后端接口获取预期的排序筛选结果"""
    api_url = "https://api.yourshop.com/v1/products/search"
    params = {
        "keyword": "无线耳机",
        "sort_type": "price_asc",
        "filter_category": "降噪"
    }
    resp = requests.get(api_url, params=params)
    return resp.json()["products"]

# UI验证部分
expected_data = get_expected_data()
# 验证前3项
first_three_ui = get_visible_items(driver)[:3]
for i in range(3):
    assert first_three_ui[i]["id"] == expected_data[i]["id"]
    assert first_three_ui[i]["price"] == expected_data[i]["price"]

# 滚动到底部验证最后3项
scroll_up(driver)  # 多次滚动直到底部
last_three_ui = get_visible_items(driver)[-3:]
for i in range(3):
    assert last_three_ui[i]["id"] == expected_data[-3+i]["id"]
    assert last_three_ui[i]["price"] == expected_data[-3+i]["price"]

# 验证页面筛选标签
assert driver.find_element_by_id("com.yourshop.app:id/tv_selected_filter").text == "降噪"
assert driver.find_element_by_id("com.yourshop.app:id/tv_selected_sort").text == "价格从低到高"

一些注意事项

  • 滚动时一定要加足够的等待时间,避免元素未加载完成就获取,导致漏项或错误
  • 去重尽量用商品ID这种唯一标识,不要用名称,避免重名商品导致的错误
  • 如果遇到加载更多按钮,要先点击按钮再滚动,否则可能无法加载后续数据
  • 结合接口的方案要保证接口参数和UI操作的参数完全一致(比如排序类型、筛选条件)

内容的提问来源于stack exchange,提问作者shiva oleti

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:31:00