一,前言
在app自动化测试的过程中经常会遇到需要对toast进行定位,最常见的就是定位toast或者获取toast的文案进行断言,如下图,通过定位"登录成功"的toast就可以断言今日头条登录用例是否通过。但toast区别于控件元素,无法获取焦点,不能通过uiautomatorviewer.bat、appium、weditor等工具定位,因此我们就需要通过别的方法来定位。
二,环境
- windows 10
- Android 10
- appium 1.18.0 (desktop)
- selenium 3.141.0
- jdk 1.8
三,toast定位准备与定位方法
1,准备
注意:网上大量的博客都说定位toast需要使用uiautomator2,且需要安装appium-uiautomator2-driver。但我在以上环境定位toast时是不需要uiautomator2,也无需安装appium-uiautomator2-driver,且能定位成功!!!大家可以尝试,如果报错的话就老实按照下面步骤进行吧。
1.1,在Capablity里新增参数使用uiautomator2:
desired_caps['automationName'] = 'uiautomator2',
1.2,再安装appium-uiautomator2-driver,命令如下:
cnpm install appium-uiautomator2-driver
安装成功后在C:\Users\xxx\node_modules会出现如下文件:
_appium-uiautomator2-driver@1.12.0@appium-uiautomator2-driver _appium-uiautomator2-server@1.10.0@appium-uiautomator2-server
2,定位方法
toast需使用xpath的方式进行定位
2.1,根据toast的文本内容定位toast
driver.find_element_by_xpath('//*[@text="xxxxxx"]')
这种方式一般用于判断或断言是否出现文本为"xxxxxx"的toast,因此我们可以封装如下:
# -*- coding:utf-8 -*- # @author: 给你一页白纸 from selenium.webdriver.support import expected_conditions as ec from selenium.webdriver.support.ui import WebDriverWait from appium.webdriver.common.mobileby import MobileBy as By def is_toast_exist(driver, text, timeout=20, poll_frequency=0.1): ''' 判断toast是否存在,是则返回True,否则返回False :param driver: driver实例对象 :param text: toast文本 :param timeout: 定位超时时间 :param poll_frequency: 查询频率 :return: True or False ''' try: toast_loc = (By.XPATH, ".//*[contains(@text, %s)]" % text) WebDriverWait(driver, timeout, poll_frequency).until( ec.presence_of_element_located(toast_loc) ) return True except: return False
2.2,根据toast的属性className定位toast
toast的className值为:android.widget.Toast
driver.find_element_by_xpath('//*[@class="android.widget.Toast"]')
这种方式一般用于获取toast的文本内容,封装如下:
# -*- coding:utf-8 -*- # @author: 给你一页白纸 def get_toast_text(driver, timeout=20, poll_frequency=0.1): ''' 定位toast元素,获取text属性 :param driver: driver实例对象 :param timeout: 元素定位超时时间 :param poll_frequency: 查询频率 :return: toast文本内容 ''' toast_loc = (By.XPATH, '//*[@class="android.widget.Toast"]') try: toast = WebDriverWait(driver, timeout, poll_frequency).until( ec.presence_of_element_located(toast_loc) ) toast_text = toast.get_attribute('text') return toast_text except Exception as e: return e
注意:
- 等待方式只能用presence_of_element_located(),即只能等待其存在,而不能等待其可见。
- 如果初始化构造driver时已经使用了隐式等待implicitly_wait(),则timeout参数可以不写。
四,示例代码
定位今日头条app账号密码登录成功后的 "登录成功"toast
注意:我这里是将desired_caps里的Uiautomator2参数注释掉了,且未安装appium-uiautomator2-driver,也同样能定位到,大家可在与我相同的环境下进行尝试。
# -*- coding:utf-8 -*- # @author: 给你一页白纸 from appium import webdriver from selenium.webdriver.support import expected_conditions as ec from selenium.webdriver.support.ui import WebDriverWait from appium.webdriver.common.mobileby import MobileBy as By def android_driver(): desired_caps = { "platformName": "Android", "platformVersion": "10", "deviceName": "PCT_AL10", "appPackage": "com.ss.android.article.news", "appActivity": ".activity.MainActivity", # "automationName": "UiAutomator2", "unicodeKeyboard": True, "resetKeyboard": True, "noReset": True, } # 启动app driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps) return driver def is_toast_exist(driver, text, timeout=20, poll_frequency=0.1): ''' 判断toast是否存在,是则返回True,否则返回False ''' try: toast_loc = (By.XPATH, ".//*[contains(@text, %s)]" % text) WebDriverWait(driver, timeout, poll_frequency).until( ec.presence_of_element_located(toast_loc) ) return True except: return False def get_toast_text(driver, timeout=20, poll_frequency=0.1): ''' 定位toast元素,获取text属性 ''' toast_loc = (By.XPATH, '//*[@class="android.widget.Toast"]') try: toast = WebDriverWait(driver, timeout, poll_frequency).until( ec.presence_of_element_located(toast_loc) ) toast_text = toast.get_attribute('text') return toast_text except Exception as e: return e def login_opera(driver): '''登录今日头条操作''' try: # driver.find_element_by_id("com.ss.android.article.news:id/cji").click() # 点击【同意】 driver.find_element_by_id("com.ss.android.article.news:id/cji").click() # 点击【我知道了】 driver.find_element_by_id("android:id/button1").click() # 点击权限管理-确定按钮 driver.find_element_by_xpath("//android.widget.TabWidget/android.widget.RelativeLayout[@index=3]").click() # 点击未登录 driver.find_element_by_id("com.ss.android.article.news:id/a10").click() # 未登录页点击登录按钮 driver.find_element_by_id("com.ss.android.article.news:id/bgh").click() # 登录页点击“。。。” driver.find_element_by_xpath("//android.widget.LinearLayout[@index=4]").click() # 选择密码登录 driver.find_element_by_id("com.ss.android.article.news:id/bu").send_keys("xxxxxxxx") # 输入账号 driver.find_element_by_id("com.ss.android.article.news:id/c5").send_keys("xxxxxxxx") # 输入密码 driver.find_element_by_id("com.ss.android.article.news:id/a2o").click() # 点击登录 except Exception as e: print("登录错误,原因为:{}".format(e)) # 报错时截图 driver.get_screenshot_as_file(r'E:\blog\blog_script\images\test_login_error_01.png') else: toast_text = get_toast_text(driver) print(toast_text) toast_el = is_toast_exist(driver, "登录成功") print(toast_el) if __name__ == '__main__': driver = android_driver() login_opera(driver)
运行结果如下,说明定位该toast成功:
C:\Users\xiaoqq\AppData\Local\Programs\Python\Python37\python.exe E:/blog/blog_script/login_jrtt.py 登录成功 True Process finished with exit code 0