1129UI自動化測試經驗分享-顯式等待(三)指令碼設定元素等待【乾貨】
分享到這第三篇了,要說點重點的、大家很願意知道的:在腳本里到底要如何設定元素等待呢?
一)Web指令碼設定元素等待
我和我已離職的同事都喜歡 將元素等待和定位元素結合在一起。
同事A的指令碼(已做修改):
def find_element123(self, key, value): from selenium.webdriver.support.wait import WebDriverWait if key == 'id': WebDriverWait(self.driver, 10).until(lambda the_driver: the_driver.find_element_by_id(value).is_displayed()) return self.driver.find_element_by_id(value) if key == 'xpath': WebDriverWait(self.driver, 5).until( lambda the_driver: the_driver.find_element_by_xpath(value).is_displayed()) return self.driver.find_element_by_xpath(value)
同事B的指令碼(已做修改):
def element_wait456(self, by, locator, wait_time=5): from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as ec if by not in [By.ID, By.XPATH, By.LINK_TEXT, By.CSS_SELECTOR, By.CLASS_NAME, By.TAG_NAME, By.PARTIAL_LINK_TEXT]: raise NameError("enter the correct targeting elements:'id','name','class','link_text','xpath','css'") WebDriverWait(self.driver, wait_time, 0.5).until(ec.presence_of_element_located((by, locator))) def find_element456(self, by, locator, wait_time=5): self.element_wait456(by, locator, wait_time) return self.driver.find_element(by, locator)
我目前的設定:
def my_find_Element(self, driver, by, location, thetime=10): """ :param driver: 瀏覽器驅動 :param by: 元素定位的方式 :param location: 元素定位的屬性值 :param thetime: 超時時間,預設10秒 """ # 下面兩個都可以;第二種比起第一種會 再多一個搜尋定位元素,但實際不影響效率,時間基本相同 return WebDriverWait(driver, thetime).until(ec.visibility_of_element_located((by, location)), '智慧等待10秒,定位失敗') # WebDriverWait(driver, thetime).until(EC.visibility_of_element_located((by, location)), '失敗') # return driver.find_element(by, location)
- 為什麼用visibility_of_element_located()?
expected_conditions模組的visibility_of_element_located() 瞭解一下
在第二篇分享中 講到visibility_of_element_located()和顯式等待結合後,返回值是WebElement;
我的想法是既然已經返回了元素,何必再次去driver.find_element_by_xx(‘XXXX’),所以直接把這個WebElement拿來用。
- By類
原始碼
class By(object):
"""
Set of supported locator strategies.
"""
ID = "id"
XPATH = "xpath"
LINK_TEXT = "link text"
PARTIAL_LINK_TEXT = "partial link text"
NAME = "name"
TAG_NAME = "tag name"
CLASS_NAME = "class name"
CSS_SELECTOR = "css selector"
在Web自動化測試中,對單一元素(不是elements)定位的方式有:find_element_by_id()、find_element_by_xpath()、find_element_by_link_text()、find_element_by_partial_link_text()、find_element_by_name()、find_element_by_tag_name()、find_element_by_class_name()、find_element_by_css_selector()。【8種方法在 selenium包 webdriver模組的WebDriver類下 可以查詢到原始碼】
By類的定位方式 就是 這8種常用的方式。
下圖是QQ郵箱的部分用例,全部通過【加強制等待0.5秒 是為了能夠看到】
class TestBangZhu(PageBangZhu):
"""上方區域-幫助中心"""
def test_01(self):
self.my_find_element(self.driver, By.CSS_SELECTOR, self.bangzhu_css).click()
time.sleep(0.5)
class TestFanKui(PageFanKui):
"""上方區域-反饋建議"""
def test_01(self):
self.my_find_element(self.driver, By.CLASS_NAME, self.fankui_class).click()
time.sleep(0.5)
class TestHuanFu(PageHuanFu):
"""上方區域-換膚"""
def test_01(self):
self.my_find_element(self.driver, By.XPATH, self.huanfu_xpath).click()
time.sleep(0.5)
class TestSheZhi(PageSheZhi):
"""上方區域-設定"""
def test_01(self):
self.my_find_element(self.driver, By.ID, self.shezhi_id).click()
time.sleep(0.5)
class TestShouYe(PageShouYe):
"""上方區域-郵箱首頁"""
def test_01(self):
self.my_find_element(self.driver, By.ID, 'frame_html_setting').click()
time.sleep(0.5)
self.my_find_element(self.driver, By.PARTIAL_LINK_TEXT, self.shouye_link).click()
class TestTuiChu(PageTuiChu):
"""上方區域-退出"""
def test_01(self):
"""teardown設定了 賬號退出登入"""
print(self.my_find_element(self.driver, By.LINK_TEXT, self.tuichu_linktext).tag_name)
二)App指令碼設定元素等待
web的定位方式就8種,但是app的定位元素的方式多了些。
appium包 webdriver模組的WebDriver類下的 對單一元素(不是elements)定位方式有:【和selenium重複的那8種不寫了】find_element_by_image()、find_element_by_ios_uiautomation()、find_element_by_ios_predicate()、find_element_by_ios_class_chain();find_element_by_android_uiautomator()、find_element_by_accessibility_id();
我的安卓app自動化指令碼有時候用得是find_element_by_android_uiautomator()、find_element_by_accessibility_id()這兩種。
我從元素屬性名的角度【text \resource-id\class\content-desc】去想,怎麼能 更簡潔把顯式等待和定位方式結合在一起,想了好久沒想好。直到看了WebDriver類下面定位元素方式的原始碼,才豁然開朗。
def find_element_by_android_uiautomator(self, uia_string):
"""Finds element by uiautomator in Android.
:Args:
- uia_string - The element name in the Android UIAutomator library
:Usage:
driver.find_element_by_android_uiautomator('.elements()[1].cells()[2]')
"""
return self.find_element(by=By.ANDROID_UIAUTOMATOR, value=uia_string)
def find_element_by_accessibility_id(self, id):
"""Finds an element by accessibility id.
:Args:
- id - a string corresponding to a recursive element search using the
Id/Name that the native Accessibility options utilize
:Usage:
driver.find_element_by_accessibility_id()
"""
return self.find_element(by=By.ACCESSIBILITY_ID, value=id)
知道這兩個定位方式的 實際執行方式,再和By類結合起來,簡直不能再容易了。
def my_find_element_android_uiautomator(self, driver, new_str, thetime=10):
return WebDriverWait(driver, thetime).until(ec.visibility_of_element_located((By.ANDROID_UIAUTOMATOR, new_str)), '失敗')
def my_find_element_accessibility_id(self, driver, location, thetime=10):
return WebDriverWait(driver, thetime).until(ec.visibility_of_element_located((By.ACCESSIBILITY_ID, location)), '失敗')
下圖是谷歌市場版 微信的部分用例,全部通過
def test_05(self):
"""發現-朋友圈"""
self.my_find_Element(self.driver, By.XPATH, self.faxian_xpath).click()
self.my_find_Element(self.driver, By.ID, self.pengyouquan_id2).click() # 1-2s 第二種1-2s
def test_06(self):
"""通訊錄-新的朋友"""
self.my_find_Element(self.driver, By.XPATH, self.tongxunlu_xpath).click()
self.my_find_Element(self.driver, By.ID, self.xindepengyou_id).click() # 1-2s 第二種1-2s
def test_09(self):
"""通訊錄-新的朋友"""
self.my_find_element_android_uiautomator(self.driver, 'text("通訊錄")').click()
self.my_find_element_android_uiautomator(self.driver, 'text("新的朋友")').click()
def test_09e(self):
"""通訊錄-新的朋友"""
self.my_find_element_android_uiautomator(self.driver, 'textContains("%s")' % self.tongxunlu_text2).click()
self.my_find_element_android_uiautomator(self.driver, 'resourceId("%s")' % self.xindepengyou_id).click()
def test_09f(self):
"""通訊錄-微信團隊"""
self.my_find_element_android_uiautomator(self.driver, 'textContains("%s")' % self.tongxunlu_text2).click()
self.my_find_element_android_uiautomator(self.driver, 'description("%s")' % self.weixintuandui_desc).click()
def test_09k(self):
"""通訊錄-新的朋友 此className第一個是新的朋友"""
self.my_find_element_android_uiautomator(self.driver, 'textContains("%s")' % self.tongxunlu_text2).click()
self.my_find_element_android_uiautomator(self.driver, 'className("%s")' % self.gengduo_class).click() # 找的是 更多功能的class
def test_10b(self):
"""通訊錄-微信團隊"""
self.my_find_element_android_uiautomator(self.driver, 'textContains("%s")' % self.tongxunlu_text2).click()
self.my_find_element_accessibility_id(self.driver, self.weixintuandui_desc).click()
從元素屬性名的角度,到底怎麼解決的?find_element_by_android_uiautomator() 瞭解一下
另外提醒下:
appium1.5以下的版本是可以通過name定位的,新版本從1.5以後都不支援name定位了。
find_element_by_accessibility_id() 取content-desc屬性。
find_element_by_id() 取resource-id的值。
三)elements
講了element,但是有時候遇到 某一屬性相同的elements,該怎麼才能準確定位到其中的某一位元素呢?
def my_find_elements(self, driver, by, location, num, thetime=10):
return WebDriverWait(driver, thetime).until(ec.visibility_of_any_elements_located((by, location)), '失敗')[num]
expected_conditions模組 這兒有三種關於檢查elements存在的類,【此外until()判斷條件 還有 匿名函式 + driver.find_elements()的用法 總共4種方式 】。帶大家看下 我選擇visibility_of_any_elements_located()的原因:
1.原始碼
class presence_of_all_elements_located(object):
""" An expectation for checking that there is at least one element present
on a web page.
locator is used to find the element
returns the list of WebElements once they are located
"""
def __init__(self, locator):
self.locator = locator
def __call__(self, driver):
return _find_elements(driver, self.locator)
class visibility_of_any_elements_located(object):
""" An expectation for checking that there is at least one element visible
on a web page.
locator is used to find the element
returns the list of WebElements once they are located
"""
def __init__(self, locator):
self.locator = locator
def __call__(self, driver):
return [element for element in _find_elements(driver, self.locator) if _element_if_visible(element)]
class visibility_of_all_elements_located(object):
""" An expectation for checking that all elements are present on the DOM of a
page and visible. Visibility means that the elements are not only displayed
but also has a height and width that is greater than 0.
locator - used to find the elements
returns the list of WebElements once they are located and visible
"""
def __init__(self, locator):
self.locator = locator
def __call__(self, driver):
try:
elements = _find_elements(driver, self.locator)
for element in elements:
if _element_if_visible(element, visibility=False):
return False
return elements
except StaleElementReferenceException:
return False
這三個類 區別在於at least one element present、at least one element visible、all elements are present on the DOM of a page and visible。
做UI自動化測試,肯定是想元素可見(如果查詢的元素算上隱藏的,會更弄不清index),所以presence_of_all_elements_located() 和 find_elements() 排除。
find_elements() 排除的原因:1.presence_of_all_elements_located()的原始碼中,return _find_elements(driver, self.locator),下面是_find_elements()的原始碼; 2. 真實的測試結果中,find_elements() 和presence_of_all_elements_located()結果也一樣的,從側面證明第1條原因。
def _find_elements(driver, by):
try:
return driver.find_elements(*by)
except WebDriverException as e:
raise e
2.全部元素都可見 對於web自動化測試有一點點難度(實際用到操作隱藏元素的機會也不多)
def test5702b(self):
"""expected_conditions模組裡的elements類"""
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as ec
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.get("https://www.12306.cn/index/")
print('開始')
print('visibility_of_any_elements_located')
# 標籤為a的元素
print(WebDriverWait(self.driver, 10).until(ec.visibility_of_any_elements_located((By.CSS_SELECTOR, 'a')), '顯式等待10秒'))
# 標籤為input type的屬性值是text的元素
print(WebDriverWait(self.driver, 10).until(ec.visibility_of_any_elements_located((By.CSS_SELECTOR, 'input[type="text"]')), '顯式等待10秒'))
# 標籤為input type的屬性值是hidden的元素 (此類元素 實際是隱藏的)
try:
print(WebDriverWait(self.driver, 10).until(ec.visibility_of_any_elements_located((By.CSS_SELECTOR, 'input[type="hidden"]')), '顯式等待10秒'))
except Exception as e111:
print(e111)
# 完全不存在的元素
try:
print(WebDriverWait(self.driver, 10).until(ec.visibility_of_any_elements_located((By.CSS_SELECTOR, 'a[class="123456789"]')), '顯式等待10秒'))
except Exception as e1:
print(e1)
print('visibility_of_all_elements_located')
try:
print(WebDriverWait(self.driver, 10).until(ec.visibility_of_all_elements_located((By.CSS_SELECTOR, 'input[type="text"]')), '顯式等待10秒'))
except Exception as e111111:
print(e111111)
try:
print(WebDriverWait(self.driver, 10).until(ec.visibility_of_all_elements_located((By.CSS_SELECTOR, 'a')), '顯式等待10秒'))
except Exception as e222:
print(e222)
try:
print(WebDriverWait(self.driver, 10).until(ec.visibility_of_all_elements_located((By.CSS_SELECTOR, 'input[type="hidden"]')), '顯式等待10秒'))
except Exception as e333:
print(e333)
try:
print(WebDriverWait(self.driver, 10).until(ec.visibility_of_all_elements_located((By.CSS_SELECTOR, 'a[class="123456789"]')), '顯式等待10秒'))
except Exception as e2:
print(e2)
print('presence_of_all_elements_located')
print(WebDriverWait(self.driver, 10).until(ec.presence_of_all_elements_located((By.CSS_SELECTOR, 'a')), '顯式等待10秒'))
print(WebDriverWait(self.driver, 10).until(ec.presence_of_all_elements_located((By.CSS_SELECTOR, 'input[type="text"]')), '顯式等待10秒'))
print(WebDriverWait(self.driver, 10).until(ec.presence_of_all_elements_located((By.CSS_SELECTOR, 'input[type="hidden"]')), '顯式等待10秒'))
try:
print(WebDriverWait(self.driver, 10).until(ec.presence_of_all_elements_located((By.CSS_SELECTOR, 'a[class="123456789"]')), '顯式等待10秒'))
except Exception as e3:
print(e3)
print('find_elements')
print(WebDriverWait(self.driver, 10).until(lambda the_driver: the_driver.find_elements(By.CSS_SELECTOR, 'a')))
print(WebDriverWait(self.driver, 10).until(lambda the_driver: the_driver.find_elements(By.CSS_SELECTOR, 'input[type="text"]'), '顯式等待10秒'))
print(WebDriverWait(self.driver, 10).until(lambda the_driver: the_driver.find_elements(By.CSS_SELECTOR, 'input[type="hidden"]'), '顯式等待10秒'))
try:
print(WebDriverWait(self.driver, 10).until(lambda the_driver: the_driver.find_elements(By.CSS_SELECTOR, 'a[class="123456789"]'), '顯式等待10秒'))
except Exception as e4:
print(e4)
time.sleep(1)
print('結束')
self.driver.quit()
上圖用例的測試結果如下:
雖然給出瞭解決定位elements的方案,但我還是推薦 使用其他方式單獨唯一定位元素 。儘量少用elements,真的會把握不準確index;
下圖是谷歌市場版 微信的關於elements定位的用例,全部通過
def test_08(self):
"""通訊錄-標籤"""
self.my_find_elements(self.driver, By.ID, self.tongxunlu_id, 1).click() # 0是微信 1是通訊錄
self.my_find_elements(self.driver, By.ID, self.qunliao_id2, 1).click() # 1是標籤
def test_07c(self):
"""通訊錄-群聊"""
self.my_find_elements(self.driver, By.ID, self.tongxunlu_id, 1).click() # 0是微信 1是通訊錄
self.my_find_elements(self.driver, By.ID, self.qunliao_id2, 0).click() # 0是群聊
def test_02(self):
"""發現-掃一掃"""
self.my_find_elements(self.driver, By.ID, self.faxian_id, 2).click()
self.my_find_elements(self.driver, By.ID, self.pengyouquan_id, 3).click() # 2是朋友圈和掃一掃之間的框 3是掃一掃
def test_01(self):
"""發現-朋友圈"""
self.my_find_elements(self.driver, By.ID, self.faxian_id, 2).click() # 0是微信 1是通訊錄
self.my_find_elements(self.driver, By.ID, self.pengyouquan_id, 1).click() # 0是前面的框 1是朋友圈
12月份,距離過年更近了,要更努力啊!!!
交流技術 歡迎+QQ 153132336 zy
歡迎關注 微信公眾號:紫雲小站