前言
Toast是什么呢?在这个手机飞速发展的时代,app的种类也越来越多,那们在日常生活使用中,经常会发现,当你在某个app的输入框输入非法字符或者非法执行某个流程时,经常看到系统会给你弹出一个黑色的提示框,告诉你你的操作不合法,比如某个app的登录流程,当你输入错误的用户名时,系统会弹出一个框提示你:用户名不正确,请重新输入;并且这个提示框往往会很快消失,并不需要用户自己执行关闭操作,其实这个弹框就是Toast
定位Toast的条件
定位toast及操作toast之前,你需要确保你的自动化测试环境应该满足以下条件
1.你的app自动化测试环境本身没有问题,能够成功执行自动化测试代码
2.appium server的版本应该在1.6以上(最好直接安装最新的版本,避免不必要的麻烦)
你可以参考我这篇关于appium环境搭建的文章,在写这篇文章时,我下载的就是比较老的版本,导致我后来定位toast时,一直定位不到,找了好半天原因,我一直以为我安装的是最新版的,其实并不是,罪魁祸首就是下载appim desktop时的地址不是官方的地址,当然之前的文章我也已经做了更新,避免你们遇到和我一样的问题
3.Toast是无法通过定位工具成功定位到的,所以你需要了解selenium中显示等待方法presence_of_element_located的作用(注意这里之只能使用这个方法,别的方法都无法定位到)
4.初始化driver时的参数需要指定automationName为UiAutomator2
Toast定位表达式
Toast虽然无法通过定位工具定位,但是几乎所有的toast都有一段文本,根据我们以往的经验,这段文本内容应该就是toast的text属性值,因此我们可以通过text属性结合xpath模糊定位的方式来定义toast的表达式,如果你对xpath有一定的了解,那么对这个表达式的意义应该不难理解: "//*[contains(@text, '{}')]".format("toast文本值")
需要说明一点,这里的@text代表的是toast的text属性(比如说它还有resource-Id属性),请区分web定位某一个元素时使用的定位表达式//*[contains(text(), '文本值')]
测试代码
定位表达式有了,再结合显示等待的presence_of_element_located方法,我们来编写一段测试代码测试一下是否能够成功定位到toast并获取他的文本值
""" ------------------------------------ @Time : 2019/8/12 20:28 @Auth : linux超 @File : test_toast.py @IDE : PyCharm @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error! @QQ : 28174043@qq.com @GROUP: 878565760 ------------------------------------ """ from appium import webdriver from appium.webdriver.common.mobileby import MobileBy from selenium.webdriver.support import expected_conditions as ec from selenium.webdriver.support.wait import WebDriverWait def wait_present_element(locator): return WebDriverWait(driver, 20, 0.01).until(ec.presence_of_element_located(locator)) def wait_clickable_element(locator): return WebDriverWait(driver, 20, 0.01).until(ec.element_to_be_clickable(locator)) desired_caps = { "automationName": "UiAutomator2", "platformName": "Android", "platformVersion": "5.1.1", "deviceName": "127.0.0.1:62001", "appPackage": "com.xxzb.fenwoo", "appActivity": "com.xxzb.fenwoo.activity.addition.WelcomeActivity" } # 初始化driver driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps) # 直接跳到指定页面 driver.start_activity("com.xxzb.fenwoo", ".activity.addition.FillPhoneActivity") # 输入用户名 wait_present_element((MobileBy.ID, "com.xxzb.fenwoo:id/et_phone")).send_keys("1369157984") # 点击下一步 wait_present_element((MobileBy.ID, "com.xxzb.fenwoo:id/btn_next_step")).click() # 定位toast toast = wait_present_element((MobileBy.XPATH, "//*[contains(@text, '{}')]".format('无效的手机号码'))) # 获取toast的文本值 # context = toast.text context = toast.get_attribute("text") print("toast的文本值为", context) # 关闭driver driver.quit()
输出
toast的文本值为无效的手机号码
Process finished with exit code 0
ok, 这段代码成功输出了toast的文本值,那么在自动化测试的时候,我们就可以通过文本值来做一些断言了
代码优化
你会发现上面的测试代码完全是流水式的编程方式,实际工作中几乎不会允许你出现这样的代码,虽然今天的主题是如何处理toast,无关代码写的好与不好,也无关代码的编程思想,但是我想说的是所有的代码书写规范和编程思想都是日积月累的养成的,并不是一朝一夕就能掌握的,而且我也希望看到我文章的人不仅仅只学到简单的用法,除此之外还能学到一些编程思想和小技巧,还是那句话希望在你成功的道路上有我的身影!
废话说了这么多,我们需要做的就是模块化,把一些代码中能够分离出来的部分,能够重复调用的部分进行分离,比如查找某一个元素,等待某个元素能点击,等待某个元素出现,向某个输入框中输入值等,还有就是把所有的操作尽量结合显示等待来操作,这样可以增加脚本的稳定性,并且对于toast的定位我们也需要封装成一个通用的方法,通过传递不同的文本值,达到定位不同toast的目的
封装代码
""" ------------------------------------ @Time : 2019/8/12 8:45 @Auth : linux超 @File : base.py @IDE : PyCharm @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error! @QQ : 28174043@qq.com @GROUP: 878565760 ------------------------------------ """ from appium.webdriver import WebElement from appium.webdriver.common.mobileby import MobileBy from appium.webdriver.webdriver import WebDriver from selenium.webdriver.support.wait import WebDriverWait from selenium.common.exceptions import TimeoutException, NoSuchElementException from selenium.webdriver.support import expected_conditions as ec class Base(object): def __init__(self, driver: WebDriver): self.driver = driver def find_element(self, locator: tuple, timeout=30) -> WebElement: wait = WebDriverWait(self.driver, timeout) try: element = wait.until(lambda driver: driver.find_element(*locator)) return element except TimeoutException as e: print('no found element {} by {}'.format(locator[1], locator[0])) raise e def input_value(self, locator: tuple, value): element = self.find_element(locator) if element: element.clear() return element.send_keys(value) else: raise NoSuchElementException("the element not found, so doesn't input value") def wait_element_presence(self, locator, timeout=30) ->WebElement: """等待元素出现在DOM中,但是不一定是可见的""" wait = WebDriverWait(self.driver, timeout, 0.01) try: element = wait.until(ec.presence_of_element_located(locator)) return element except TimeoutException as e: print("the element {} not presence".format(locator[1])) raise e def wait_element_clickable(self, locator: tuple, timeout=30): wait = WebDriverWait(self.driver, timeout) try: element = wait.until(ec.element_to_be_clickable(locator)) return element except TimeoutException as e: print("the element {} not found or element un-clickable".format(locator[1])) raise e def click(self, locator: tuple): element = self.wait_element_clickable(locator) if element: return element.click() else: raise TypeError('NoneType object is not callable') def get_toast(self, context): """text: toast的文本值 只支持appium server 版本在1.6以上,且"automationName"为"uiautomator2" """ locator = (MobileBy.XPATH, "//*[@text='{}']".format(context)) toast = self.wait_element_presence(locator) try: try: text = toast.text except AttributeError: text = toast.get_attribute("text") except AttributeError as e: print("get context of toast fail") raise e return text if __name__ == '__main__': pass
测试代码
""" ------------------------------------ @Time : 2019/8/12 21:10 @Auth : linux超 @File : toast.py @IDE : PyCharm @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error! @QQ : 28174043@qq.com @GROUP: 878565760 ------------------------------------ """ import unittest from appium import webdriver from appium.webdriver.common.mobileby import MobileBy from base import Base class TestToast(unittest.TestCase): phone_element = (MobileBy.ID, "com.xxzb.fenwoo:id/et_phone") next_setup = (MobileBy.ID, "com.xxzb.fenwoo:id/btn_next_step") def setUp(self): desired_caps = { "automationName": "UiAutomator2", "platformName": "Android", "platformVersion": "5.1.1", "deviceName": "Android Emulator", "appPackage": "com.xxzb.fenwoo", "appActivity": "com.xxzb.fenwoo.activity.addition.WelcomeActivity", "noReset": "true" } self.driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps) def test_toast(self): base = Base(self.driver) self.driver.start_activity("com.xxzb.fenwoo", ".activity.addition.FillPhoneActivity") base.input_value(self.phone_element, '136915798') base.click(self.next_setup) toast_text = base.get_toast("无效的手机号码") print(toast_text)
self.assertEqual("无效的手机号码", toast_text)
def tearDown(self):
self.driver.quit()
if __name__ == '__main__':
unittest.main()
封装代码中的toast方法,有一点需要说明,通过WebElement.text的方式并不一定能够获取到它的文本值,所以根据以往的经验通过get_attribute("属性")应该就能获取到了