­

如何獲取app中的toast

  • 2019 年 10 月 3 日
  • 筆記

前言

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(“屬性”)應該就能獲取到了

執行過程及測試結果