一、Selenium基本知識
1. 什么是Selenium?
Selenium是瀏覽器自動化工具,主要用來Web的自動化測試,以及基於Web的任務管理自動化。它支持的語言有:python、Java、ruby、JavaScript等,並且幾乎能在主流的瀏覽器上運行。
Selenium2.0、Selenium3.0主要由三大部分組成:SeleniumIDE、Selenium WebDriver、Selenoium Grid。
- Selenium IDE:錄制和回放腳本,可以模擬用戶對頁面的真實操作,區別於其他工具:是通過攔截http請求。
- 一般只把錄制腳本當作一個輔助功能,因為一個UI節點的細微變化,都可能導致自動化測試工具無法識別,當測試項目項目大時,定位、更新十分困難。
- 其次,錄制的腳本有時候人工難以理解。
- Selenium Grid:實現在多台機器上、和異構環境中並行執行測試用例。並行執行不僅節省時間,而且可以同時在不同的瀏覽器、平台上運行自動化測試腳本。
- Selenium Web Driver:針對各個瀏覽器而開發,通過原生瀏覽器支持或者擴展(Chrome webDrive、FireFox WebDriver)直接控制瀏覽器
VS Selenium RC(Selenium1.0):在瀏覽器中運行javaScript,使用瀏覽器內置的JavaScript來翻譯和執行selense
Web Driver原理
webDriver是按照client/server模式設計的。client是我們的測試腳本,發送請求;server就是打開的瀏覽器,用來接收client的請求並作出響應。
具體的工作流程:
- webDriver打開瀏覽器並綁定到指定端口。啟動的瀏覽器作為遠程服務器remote server
- client通過CommandExecuter發送http請求給遠程服務器的偵聽端口(the wire protocal)
- 遠程服務器根據原生的瀏覽器組件來轉化為瀏覽器的本地(native)調用
以web Driver用到的協議:
- 打開瀏覽器時:HTTP協議
- client端發送http請求到遠程服務器的偵聽端口:the wire protocol
其中:
- 有線協議:指的是從點到點獲取數據的方式,是應用層的協議。
- HTTP協議:是用於從服務器傳輸超文本標記語言HTML到客戶端的通信協議。是一個應用層協議,由請求/響應構成,是一個標准的客戶/服務器模式。是一個無狀態的協議。(無狀態:對事務沒有記憶能力,不會保存這次傳輸的信息——節約內存)
2. Selenium的特點有:
- 支持錄制和回放(Selenium IDE)
- 通過WebDriver,直接控制瀏覽器,而不是通過攔截HTTP請求,實現真正模仿了用戶的操作;同時使用WebDriver能夠靈活的獲取頁面元素(WebDriver),並且提供執行JS的接口
- 能夠分布式運行在不同機器和異構環境中(不同瀏覽器)
3. Selenium的內部運行機制?如何能夠跨瀏覽器使用?——WebDriver原理(&RC原理)
1)RC原理
在Selenium1.0中,是通過Selenium RC服務器作為代理服務器去訪問應用從而達到測試的目的。
Selenium RC分為三個部分,Launcher、HttpProxy、Core。
- Launcher用於啟動瀏覽器,把Selenium Core加載到瀏覽器中,並且把瀏覽器的代理設置為Selenium Server的Http Proxy。
- Core是一堆JavaScript的集合,所以本質相當於運行這些JavaScript函數來實現對Html頁面的操作。——這也是為什么可以運行在幾乎所有主流的瀏覽器上。
然而直接運行JavaScript會有極大的安全漏洞,所以會受到“同源限制”,在這個基礎上,Selenium2.0引入了WebDriver。
2)Web Driver原理
webDriver是按照client/server模式設計的。client是我們的測試腳本,發送請求;server就是打開的瀏覽器,用來接收client的請求並作出響應。
具體的工作流程:
- webDriver打開瀏覽器並綁定到指定端口。啟動的瀏覽器作為遠程服務器remote server
- client通過CommandExecuter發送http請求給遠程服務器的偵聽端口(the wire protocal)
- 遠程服務器根據原生的瀏覽器組件來轉化為瀏覽器的本地(native)調用
所以web Driver用到的協議:
- 打開瀏覽器時:HTTP協議
- client端發送http請求到遠程服務器的偵聽端口:the wire protocol
其中:
- 有線協議:指的是從點到點獲取數據的方式,是應用層的協議。
- HTTP協議:是用於從服務器傳輸超文本標記語言HTML到客戶端的通信協議。是一個應用層協議,由請求/響應構成,是一個標准的客戶/服務器模式。是一個無狀態的協議。(無狀態:對事務沒有記憶能力,不會保存這次傳輸的信息——節約內存)
4. 如何提高selenium腳本的執行速度?
1)優化測試用例。
- 盡可能不用sleep、減少使用implicityWait,而使用WebDriverWait/FluentWait,這樣可以優化等待時間
- 減少不必要的操作步驟。
2)使用Selenium grid,通過testNG實現並發執行。
說到這里,在編寫測試用例的時候,一定要實現松耦合,然后再服務器允許的情況下,盡量設置多線程實現並發運行。
3)設置等待時間、中斷頁面加載。如果頁面加載內容太多,我們可以查看一下加載緩慢的原因,在不影響測試的情況下,可以設置超時時間,中斷頁面加載。
5. 提高自動化腳本穩定性——減少誤報
1. 誤報問題。我們一旦測試用例沒有通過,則無法完成每日自動構建,但是其實這些測試用例是正確,也不存在BUG。
2. 主要的原因:頁面還沒有加載完成,我們就開始進行元素定位。
3. 解決方法:重試機制。利用遞歸封裝了一個等待元素的方法。其中,設置最大等待時間為1s,輪詢時間為50ms,這個方法會不斷輪詢,直到方法執行成功或者超過設置的最大等待時間。在我們最好的一次實踐中,我們把一個測試用例的誤報率從10%降低到0,並且執行時間從原先的45秒降低到33秒。
6. 如何設計高質量自動化腳本
1. 使用四層結構實現業務邏輯、腳本、數據分離。
2. 使用PO設計模式,將一個頁面用到的元素和操作步驟封裝在一個頁面類中。如果一個元素定位發生了改變,我們只用修改這個頁面的元素屬性
3. 對於頁面類的方法,我們盡量從客戶的正向邏輯去分析,方法中是一個獨立場景,例如:登錄到退出,而且不要想着把所有的步驟都封裝在一個方法中。
4. 測試用例設計中,減少測試用例之間的耦合度。
7. 你覺得自動化測試最大的缺陷是什么?
1. 一旦項目發生變化,測試用例就需要改進,工作量大。
2. 驗證的范圍有限,操作更加復雜,比如說簡單的一個驗證驗證碼,如果是人工識別很快就可以輸入,但是自動化測試中會增添很多困難。那么這個時候速度也不如人工。
3. 不穩定
4. 可靠性不強
5. 成本與收益
二、元素定位
1. ElementNotVisible
1. selenium中hidden或者是display = none的元素是否可以定位到?——用Js修改display = block
1)區分:display= none VS hidden
共同點:都把網頁中的元素給隱藏起來了;在selenium中無法直接定位
區別:none:不為隱藏的對象保留其物理空間 看不見/摸不着
hidden:仍占有空間 看不見/摸得着
1. 處理 display:none
頁面主要通過"dislay:none"來控制整個下拉框不見。如果直接操作:
from selenium.webdriver.support.ui import WebDriverWait from selenium import webdriver from selenium.webdriver.support.select import Select import os driver = webdriver.Firefox(executable_path="/Users/lesley/Downloads/geckodriver") file_path = 'file:///' + os.path.abspath('test.html') driver.get(file_path) select = driver.find_elements('select') Select(select).select_by_value('volvo') WebDriverWait() driver.quit()
報錯:ElementNotVisible
我們可以通過JavaScript來修改display的值
js = 'document.querySelectortAll('select')[0]'.style.display='block';' select = driver.find_element_by_tag_name('select') Select(select).select_by_value('Opel')
document.querySelectAll('select'):選擇所有的select;[0]表示第幾個
style.display='block':修改display=block,表示可見
2. NoSuchElementException
首先,判斷一個元素是否顯示:is_displayed()
1. Frame/IFrame原因定位不到元素——switch_to_iframe
frame是指:頁面中嵌入另一個頁面,而webdriver每次只能在一個頁面識別,因此需要先定位到相應的frame,對那個頁面里的元素進行定位。
此時,有兩種方式:
1. iframe存在id 或者name。
首先用switch_to_frame('x-URS-iframe')定位到這個iframe,然后再定位這個iframe中的元素
driver=webdriver.Firefox() driver.get(r'http://www.126.com/') driver.switch_to_frame('x-URS-iframe') #需先跳轉到iframe框架 username=driver.find_element_by_name('email') username.clear()
2. iframe不存在name/id。
先定位到iframe,再swith_to_frame
#先定位到iframe elementIframe= driver.find_element_by_class_name('APP-editor-iframe') #再將定位對象傳給switch_to_frame()方法 driver.switch_to_frame(elementIframe)
如果完成操作后,可以通過switch_to_parent_content()方法跳出當前iframe,或者還可以通過switch_to_default_content()方法跳回最外層的頁面。
2. 頁面沒有加載出來,就對頁面中元素進行操作。
——設置等待時間直到元素出現(WebDriveWait(driver,10).until(lambda x:x.find_elemetn_by_id('someId').is_displayed)
獲取頁面加載狀態:
document.readyState
例如:當Selenium點擊一個按鈕打開一個彈窗,彈窗還沒有打開的時候,我們就要使用彈窗上一個按鈕。
——>解決:設置等待最大等待時間
1)sleep():設置固定休眠時間
2)implicity_wait():是webDriver提供的一個超時等待,隱的等待一個元素被發現,或者一個命令完成
3)WebDriverWait():同樣也是WebDriver提供的方法。在設置時間內,默認每隔一段時間檢測一次當前頁面元素是否存在,如果超出指定時間檢測不到則拋出異常。
WebDriverWait(driver, timeout, poll_frequency=0.5, ignored_exceptions=None) # driver:WebDriver的驅動程序 # timeout:最長超時時間,默認以秒為單位 # poll_frequency:休眠時間的間隔時間 # ignore_exception():超時后的異常信息,默認情況下拋出NoSuchElementException
通常與until()或者until_not()方法配合使用
until(method, message="") # 調用該方法提供的驅動程序作為一個參數,直到返回值不為FALSE until_not(method, message="") # 調用該方法提供的驅動程序作為一個參數,直到返回值為FALSE
舉例:
1 from selenium.webdriver.support.ui import WebDriverWait 2 3 from selenium import webdriver 4 import time 5 6 driver = webdriver.Firefox(executable_path="/Users/lesley/Downloads/geckodriver") 7 driver.get("https://www.baidu.com/") 8 9 # 添加WebDriverWait 10 element = WebDriverWait(driver, 10).until(lambda driver:driver.find_element_by_id("kw")) 11 is_disappeared = WebDriverWait(driver, 5).until_not(lambda x: x.find_element_by_id("someId").is_displayed()) 12 element.send_keys("sbw") 13 14 # 添加智能等待 15 driver.implicitly_wait(5) 16 driver.find_element_by_id("su").click() 17 18 # 添加固定時間等待 19 time.sleep(5) 20 21 driver.quit()
4)WaitFor:配合setTimeout,設置最大等待時間,然后輪詢查看是否在指定時間內找到該元素。
1 def waitfor(getter, timeout=3, interval=0.5, *args): 2 starttime = datetime.datetime.now() 3 while True: 4 if getter(args): 5 return 6 else: 7 runtime = datetime.datetime.now() - starttime 8 print runtime 9 if runtime.seconds >= timeout: 10 raise Exception 11 time.sleep(interval) 12 13 current_value = 1 14 def testgetval(args): 15 wanted_value = args[0] 16 global current_value 17 current_value += 1 18 print '%d, %d' % (wanted_value, current_value) 19 return current_value > wanted_value 20 21 if __name__ == '__main__': 22 waitfor(testgetval, 1, 0.3, 2) 23 print '=======================' 24 waitfor(testgetval, 1, 0.3, 8)
3. 動態ID無法定位元素——1)直接使用Xpath相對路徑;2)根據部分元素定位
如何判斷是動態ID?
簡單,一般看到元素屬性里有拼接一串數字的,就很有可能是動態的。想要分辨,刷新一下瀏覽器再看該元素,屬性值中的數字串改變了,即是動態屬性了。
<div id="btn-attention_2030295">...</div>
方式(一)根據相對路徑
http://blog.csdn.net/huilan_same/article/details/52541680
方式(二)根據部分元素屬性定位
driver.find_element_by_xpath("//div[contains(@id, 'btn-attention')]") driver.find_element_by_xpath("//div[starts-with(@id, 'btn-attention')]") driver.find_element_by_xpath("//div[ends-with(@id, 'btn-attention')]") # 這個需要結尾是‘btn-attention’
4. 二次定位,如彈出登陸框
——層級定位
# 點擊打開菜單欄 driver.find_element_by_xpath("//*[@id='sidebar-collapse']/i").click(); # 點擊菜單塊 driver.find_element_by_xpath("//*[@id='sidebar']/div[1]/ul/li[2]/a").click(); # 點擊“待辦中心” driver.find_element_by_linkText("待辦案件").click();
5. 有兩個屬性相同的元素,但是其中一個是不可見的。——找到符合這個屬性且style屬性中display=none的元素
driver.find_element_by_xpath("//span[contains(@id, 'sbw')] and not(contains[@style, 'display:none'])")
6. Xpath描述錯誤
1)通過屬性定位元素
find_element_by_xpath("//標簽名[@屬性='屬性值']")
例如:
# id屬性: driver.find_element_by_xpath("//input[@id='kw']") # class屬性: driver.find_element_by_xpath("//input[@class='s_ipt']") # name屬性: driver.find_element_by_xpath("//input[@name='wd']") # maxlength屬性: driver.find_element_by_xpath("//input[@maxlength='255']")
2)通過標簽名
driver.find_elment_by_xpath('//input')
3)父子定位元素
查找有父親元素的標簽名為span,它的所有標簽名叫input的子元素
driver.find_element_by_xpath("//span/input")
4)通過元素內容
例如:
<p id="jgwab"> <i class="c-icon-jgwablogo"></i> 京公網安備11000002000001號 </p>
則我們可以定位:
# 根據text()
driver.find_elment_by_xpath('//p[contains(text(), '京公網')]')
# 根據class
driver.find_elment_By_xpath('//p[contains(@class, '京公網')]')
5. 組合定位元素
//父元素標簽名/標簽名的屬性值:指的是span下的input標簽下class屬性為s_ipt的元素
driver.find_element_by_xpath("//span/input[@class='s_ipt']")
6. 多個屬性組合定位
指的是input標簽下id屬性為kw且name屬性為wd的元素
driver.find_element_by_xpath("//input[@class='s_ipt' and @name='wd']")
指的是p標簽下內容包含“京公網”且id屬性為jgwab的元素
find_element_by_xpath("//p[contains(text(),'京公網') and @id='jgwab']")
三、常見控件使用
1. link、button
element.click()
2. Textbox
element.send_keys('test')
3. Upload
element.send_keys('D\test.txt')
4. Mouse Event——ActionChains()
#雙擊 ActionChains(driver).double_click(element).perform() #右擊 ActionChains(driver).context_click(element).perform() #拖動 ActionChains(driver).drag_and_drop(element).perform() #懸停 ActionChains(driver).move_to_element(element).perform()
5. DropDown:
1)<Select>標簽的下拉菜單
from selenium.webdriver.support.ui import Select Select(driver.find_element_by_id('gender')).select_by_value('2') Select(driver.find_element_by_id('gender')).select_by_index(1) Select(driver.find_element_by_id('gender')).select_by_visible_text('Male')
2)非<select>標簽——層級定位
Dropdown1 = driver.find_element_by_id(‘id’) #先定位到dropdown Dropdown1.find_element_by_id(“li2_input_2”) #再定位到dropdown中的值
3)使用js實現:
6. Alert
driver.switch_to_alert().accept() # 接收彈窗 driver.switch_to_alert().dismiss() # 取消彈窗 # 獲取彈窗的文本消息 Message = driver.switch_to_alert().text
7. Window
driver.refresh() # 刷新 driver.back() # 后退 driver.forward() # 前進 driver.maximize_window() # 最大化 driver.set_window_size(100,200) # 設置窗口大小 driver.switch_to.window(searchwindow)
8. frame
driver.switch_to.frame(ReferenceFrame) driver.switch_to.parent_frame() # frame需要一級一級切 driver.switch_to.default_content() # 返回最外層
四、等待
1. 顯示等待——WebDriverWait()
:等到某個條件成立時繼續執行。每隔一段時間檢測,超出最大時間則拋出異常
is_disappeared = WebDriverWait(driver, 5).until_not(lambda x: x.find_element_by_id("someId").is_displayed())
2. 隱式等待——implicitly_wait()
隱式等待中的時間並非一個固定的等待時間,它並不影響腳本的執行速度。比如進行某元素的定位時,如果元素可以定位就繼續執行,如果目前定位不到就以輪詢的方式持續判斷該元素是否被定位到,如果超過規定的時間還沒定位到就拋出異常。
driver.implicitly_wait(20)
3. 強制等待——sleep()
from time import sleep sleep(5)
五、測試模型
3. selenium中如何保證操作元素的成功率?也就是說如何保證我點擊的元素一定是可以點擊的?
- 首先通過封裝find方法,實現wait_for_element_ispresent,這樣在對元素進行操作之前保證元素被找到,進而提高成功率。(WebDriverWait)
- 在對頁面進行click之前,先滾動到該元素(通過Js封裝),避免在頁面未加在完成前或是在下拉之后才能顯示。
4. Selenium有幾種定位方式?你最偏愛哪一種,為什么?
Selenium有八種定位方式:
- 與name有關的有三種:name、class_name、tag_name
- 與link相關的有兩種:link_text、partitial_link_text
- 與id有關:id
- 全能選手:xpath、css_selector
如果存在id,我一定使用Id,因為簡單方便,定位最快。其次是Xpath,因為很多情況下html標簽的屬性不夠規范,無法唯一定位。Xpath是通過相對位置定位
5. 如何去定位頁面上動態加載的元素?
首先觸發動態事件,然后再定位。如果是動態菜單,則需要層級定位。——JS實現(對動態事件封裝)
http://www.cnblogs.com/tobecrazy/p/4817946.html
6. 如何去定位屬性動態變化的元素?
屬性動態變化也就是指該元素沒有固定的屬性值,可以通過:
- JS實現,
- 通過相對位置來定位,比如xpath的軸,paren/following-sibling/percent-sibling
http://www.cnblogs.com/zhaozhan/archive/2009/09/10/1564332.html
8. 點擊鏈接以后,selenium是否會自動等待該頁面加載完畢?
不會的。所以有的時候,當selenium並未加載完一個頁面時再請求頁面資源,則會誤報不存在此元素。所以首先我們應該考慮判斷,selenium是否加載完此頁面。其次再通過函數查找該元素。
11. 如何在定位元素后高亮元素(以調試為目的)?
12. 什么是斷言?VS 驗證
1)斷言(assert):測試將會在檢查失敗時停止,並不運行后續的檢查
優點:可以直截了當的看到檢查是否通過
缺點:檢查失敗后,后續檢查不會執行,無法收集那些檢查結果狀態
2)驗證(vertify):將不會終止測試
缺點:你必須做更多的工作來檢查測試結果:查看日志——>耗時多,所以更偏向於斷言
# 斷言驗證:百度搜索的標題是否為:百度搜索 # import unittest try: self.assertEqual(u"百度搜素", driver.title) except AssertionError as e: print("Cannot find this title")
3)Waitfor:用於等待某些條件變為真。可用於AJAX應用程序的測試。
如果該條件為真,他們將立即成功執行。如果該條件不為真,則將失敗並暫停測試。直到超過當前所設定的超時時間。 一般跟setTimeout時間一起用。
常用的斷言:
1 assertLocation # 判斷當前是在正確的頁面 2 assertTitle #檢查當前頁面的title是否正確 3 assertValue # 檢查input的值, checkbox或radio,有值為”on”無為”off” 4 assertSelected # 檢查select的下拉菜單中選中是否正確 5 assertSelectedOptions # 檢查下拉菜單中的選項的是否正確 6 assertText # 檢查指定元素的文本 7 assertTextPresent # 檢查在當前給用戶顯示的頁面上是否有出現指定的文本 8 assertTextNotPresent # 檢查在當前給用戶顯示的頁面上是否沒有出現指定的文本 9 assertAttribute # 檢查當前指定元素的屬性的值 10 assertTable # 檢查table里的某個cell中的值 11 assertEditable # 檢查指定的input是否可以編輯 12 assertNotEditable # 檢查指定的input是否不可以編輯 13 assertAlert # 檢查是否有產生帶指定message的alert對話框 14 waitForElementPresent # 等待檢驗某元素的存在。為真時,則執行。
13. 如果有一個按鈕,點擊該按鈕后發出一個ajax call,然后得到返回結果后內容顯示到新彈出的一個layer中。在寫腳本的時候,點擊這個按鈕動作是否可以用clickAndWait命令?如果不行,怎么解決?
不能夠。Ajax是一種支持動態改變用戶界面元素的技術。在Ajax驅動的應用程序中,數據可以從應用服務器檢索,然后顯示在頁面上,而不需要加載整個頁面,只有一小部分頁面或者元素本身被重新加載。
所以不能夠使用ClickAndWait,因為Ajax call不會刷新整個頁面,clickAndWait命令會因為等待頁面重新加載而出現time out。
也就是說最大的麻煩是判斷Ajax調用是否結束。可以用click + pause完成
使用JQuery進行輔助測試:http://www.cnblogs.com/nbkhic/archive/2011/10/23/2221729.html
其中,去哪兒網的題目如下:
一、 UI自動化測試
Qunar機票搜索場景
1) 訪問Qunar機票首頁http://flight.qunar.com,選擇“單程”,輸入出發、到達城市,選擇today+7日后的日期,點“搜索”,跳轉到機票單程搜索列表頁。
2) 在列表頁停留1分鍾,至到頁面上出現“搜索結束”。
3) 如果出現航班列表,對於出現“每段航班均需繳納稅費”的行隨機點選“訂票”按鈕,在展開的列表中會出現“第一程”、 “第二程”;
對於沒有出現“每段航班均需繳納稅費”的行隨機點選“訂票”按鈕,在展開的列表底部中會出現“報價范圍”
4) 如果不出現航班列表,則頁面會出現“該航線當前無可售航班”
請使用maven創建java工程,引入Selenium框架,編寫WebUI代碼,實現上述人工操作和驗證。要求能隨機驗證100個城市對的3個月內的任意搜索條件。
參見答案:http://www.cnblogs.com/tobecrazy/p/4752684.html
很多人可能第一步就卡住了,怎么選擇7天以后的日期呢?
實際上很簡單,直接在輸入框里輸入就好了。因為selenium支持的語言很多,這里就用js寫一下。大家用selenium執行這段js就可以搞定了。
var date = new Date(); date.setDate(date.getDate() + 7); var a_week_later = date.getFullYear() + '-' (date.getMonth()+1) + '-' + date.getDate(); $('input[name=fromDate]').val(a_week_later);
參考答案: http://www.cnblogs.com/tobecrazy/p/5873288.html