selenium2自動化測試實戰--基於Python語言


自動化測試基礎

一、 軟件測試分類

1.1 根據項目流程階段划分軟件測試

1.1.1 單元測試

  單元測試(或模塊測試)是對程序中的單個子程序或具有獨立功能的代碼段進行測試的過程。

1.1.2 集成測試

  集成測試是在單元測試的基礎上,先通過單元模塊組裝成系統或子系統,再進行測試。重點是檢查模塊之間的接口是否正確。

1.1.3 系統測試

  系統測試是針對整個產品系統進行的測試,驗證系統是否滿足需求規格的定義,以及軟件系統的正確性和性能等是否滿足其需求規格的要求。

1.1.4 驗收測試

  驗收測試是部署軟件之前的最后一個測試階段。驗收測試的目的是確保軟件准備就緒,向軟件購買者展示該軟件系統能夠滿足用戶的需求。

1.2 白盒測試、黑盒測試、灰盒測試

  白盒測試與黑盒測試,主要是根據軟件測試工作中對軟件代碼的可見程度進行的划分。這也是軟件測試領域中最基本的概念之一。

1.2.1 黑盒測試

  黑盒測試,指的是把被測的軟件看做一個黑盒子,我們不去關心盒子里面的結構是什么樣子的,只關心軟件的輸入數據和輸出結果。

  它只檢查程序呈現給用戶的功能是否按照需求規格說明書的規定正常使用、程序是否能接受輸入數據並產生正確的輸出信息。黑盒測試着眼於程序外部結構,不考慮內部邏輯結構,主要針對軟件界面和軟件功能進行測試。

1.2.2 白盒測試

  白盒測試,指的是把盒子打開,去研究里面的源代碼和程序執行結果。

  它是按照程序內部的結構測試程序,通過測試來檢驗產品內部動作是否按照設計規格說明書的規定正常進行,檢驗程序中的每條邏輯路徑是否都能按預定要求正確工作。

1.2.3 灰盒測試

  灰盒測試介於黑盒測試和白盒測試之間。

  可以這樣理解,灰盒測試既關注輸出對於輸入的正確性,同時也關注內部表現。但這種關注不像白盒測試那樣詳細、完整,它只是通過一些表征性的現象、事件、標志來判斷內部的運行狀態。有時候輸出是正確的,但內部其實已經錯誤了,這種情況非常多。如果每次都通過白盒測試來操作,效率會很低,因此需要采取灰盒測試的方法。

 

1.3 功能測試與性能測試

  從軟件的不同測試面可以划分為功能測試與性能測試

1.3.1 功能測試

  功能測試主要檢查世紀功能是否符合用戶的需求,因此測試的大部分工作也是圍繞軟件的功能進行。設計軟件的目的就是滿足用戶對其功能的需求,如果偏離了這個目的,則任何測試工作都是沒有意義的。

功能測試又可以細分為很多種:邏輯功能測試,界面測試、易用性測試、安裝測試、兼容性測試等。

1.3.2 性能測試

  性能測試是通過自動化的測試工具模擬多種正常、峰值以及異常負載條件來對系統的各項性能指標進行的測試。

  軟件的性能包括很多方面,主要有時間性能和空間性能兩種。

  • 時間性能:主要是指軟件的一個具體的響應時間。例如一個登陸所需要的時間,一個商品交易所需要的時間等。當然,拋開具體的測試環境,來分析一次事務的響應時間是沒有任何意義的,它需要在搭建好的一個具體且獨立的測試環境下進行。
  • 空間性能:主要指軟件運行時所消耗的系統資源,例如硬件資源,CPU、內存、網絡寬帶消耗等。

 

1.4 手工測試與自動化測試

從對軟件測試工作的自動化程度可以划分為手工測試與自動化測試。

1.4.1 手工測試

手工測試就是由測試人員一個一個地去執行測試用例,通過鍵盤鼠標等輸入一些參數,並查看返回結果是否符合預期結果。

手工測試並非專業術語,手工測試通常是指我們在系統測試階段所進行的功能測試,為了更明顯地與自動化測試進行區分,這里使用了手工測試這種說法。

1.4.2 自動化測試

  自動化測試是把以人為驅動的測試行為轉化為機器執行的一種過程。通常,在設計測試用例並通過評審之后,由測試人員根據測試用例中描述的規則流程一步步執行測試,把得到的世紀結果與期望結果進行比較。在此過程中,為了節省人力、時間和硬件資源,提高測試效率,便引入了自動化測試的概念。

自動化測試又可分為:功能自動化測試與性能自動化測試。

  • 功能自動化測試:是把以人為驅動的測試行為轉化為機器執行的一種過程。通過測試工具(或框架)錄制/編寫測試腳本,對軟件的功能進行測試,並驗證測試結果是否正確,從而代替部分的手工測試工作,達到節約人力成本和時間成本的目的。
  • 性能自動化測試:通過性能功能來模擬成千上萬的虛擬用戶向系統發送請求,從而驗證系統的處理能力。

 

1.5 冒煙測試、回歸測試、隨機測試、探索性測試和安全測試

這幾種測試出現在軟件測試的周期中,既不算具體明確的測試階段,也不是具體的測試方法。

1.5.1 冒煙測試

  是指在對一個新版本進行大規模的系統測試之前,先驗證一下軟件的基本功能是否實現,是否具備可測性。

  引入到軟件測試中,就是指測試小組在正是測試一個新版本之前,先投入較少的人力和時間驗證一個軟件的主要功能,如果主要功能都沒有運行通過,則打回開發組重新開發。這樣做的好處是可以節省時間和人力投入到不可測的項目中。

1.5.2 回歸測試

  回歸測試是指修改了舊代碼后,重新進行測試以確認修改后沒有引入新的錯誤或導致其他代碼產生錯誤。
  回歸測試一般是在進行第二輪軟件測試時開始的,驗證第一輪軟件測試中發現的問題是否得到修復。當然,回歸也是一個循環的過程,如果回歸的問題通不過,則需要開發人員修改后再次進行回歸,直到所有問題回歸通過為止。

1.5.3 隨機測試

  是指測試中的所有輸入數據都是隨機生成的,其目的是模擬用戶的真實操作,並發現一些邊緣性的錯誤。

  隨機測試可以發現一些隱蔽的錯誤,但是也有很多缺點,例如測試不系統,無法統計代碼覆蓋率和需求覆蓋率、發現的問題難以重現等。一般是放在測試的最后執行。隨機測試更專業的升級版叫做探索性測試。

1.5.4 探索性測試

  探索性測試可以說是一種測試思維技術,它沒有很多實際的測試方法、技術和工具,但卻是所有測試人員多應該掌握的一種測試思維方式。探索性測試強調測試人員的主觀能動性,拋棄繁雜的測試計划和測試用例設計過程,強調在碰到問題時及時改變測試策略。

1.5.5 安全測試

  安全測試在IT軟件產品的生命周期中,特別是產品開發基本完成至發布階段,對產品進行檢驗以驗證產品符合安全需求定義和產品質量標准的過程。

  安全測試現在越來越受到企業的關注和重視,因為由於安全性問題造成的后果是不可估量的,尤其是互聯網產品,最容易遭受各種安全攻擊。

 

二、分層的自動化測試

  我們應該有更多的低級別的單元測試,而不僅僅是通過用戶界面運行的高層的端到端的測試。

 

   傳統的自動化測試我們可以理解為基於產品UI層的自動化測試,它是將黑盒功能測試轉化為由程序或工具執行的一種自動化測試。

   但是在目前的大多數研發組織當中,都存在開發與測試團隊割裂(部門牆)、質量職責錯配(測試主要對質量負責)的問題,在這種狀態下,測試團隊的一個“正常”反應就是試圖在測試團隊能夠掌握的黑盒測試環節進行盡可能全面的覆蓋,甚至是盡可能全面的  UI 自動化測試。

  這可能會導致兩個惡果:一是測試團隊規模的急劇膨脹;二是所謂的全面UI自動化測試運動。因為UI是非常易變得,所以UI自動化測試維護成本相對高昂。

  分層自動化測試倡導的是從黑盒(UI)單層到黑白盒多層的自動化測試體系,從全面黑盒自動化測試到對系統的不同層次進行自動化測試。

 

2.1 單元自動化測試

  單元自動化測試是指對軟件中的最小可測試單元進行檢查和驗證。對於單元測試中單元的含義,一般來說,要根據實際情況去判斷其具體含義,如C語言中單元是指一個函數,Java中單元是指一個類,圖形化的軟件中單元是指一個窗口或一個菜單等。總的來說,單元就是人為規定的最小的被測功能模塊。規范的進行單元測試需要借助單元測試框架,如Java語言的Junit、TextNG,C#語言的NUnit,以及Python語言的unittest、pytest等,目前幾乎所有的主流語言都有其相應的單元測試框架。
  Code Review中文翻譯為代碼評審或diamante審查,是指在軟件開發過程中,通過對源代碼進行系統性檢查的過程。通常的目的是查找系統缺陷、保證軟件總體質量以及提高開發者自身水平。與Code Review 相關的插件和工具有很多,例如Java語言中基於Eclipse的ReviewClipse和Jupiter、主要針對Python語言的Review Board等。

 

2.2 接口自動化測試

  Web應用的接口自動化測試大體分為兩類:模塊接口測試Web接口測試

2.2.1 模塊接口測試

  主要測試模塊之間的調用與返回。當然,我們也可以將其看做是單元測試的基礎。它主要強調對一個類方法或函數的調用,並對返回結果的驗證,所用到的測試工具與單元自動化測試相同。

 2.2.2 Web接口測試

  Web接口測試又可以分為兩類:服務器接口測試和外部接口測試。

  • 服務器接口測試:指測試瀏覽器與服務器的接口。我們知道Web開發一般分前端和后端,前端開發人員用HTML/CSS/JavaScript等技術,后端開發人員用PHP/Java/C#/Python/Ruby等各種語言。用戶的操作是在前端頁面上,需要后端提供服務器接口,前端通過調用這些接口來獲取需要的數據,通過HTTP協議實現前后端的數據傳遞。
  • 外部接口測試:指調用的接口由第三方系統提供。典型的例子就是第三方登錄,例如新上線的產品為了免於新用戶注冊賬號的麻煩會提供第三方登錄,納悶用戶在登錄的時候調用的就是第三方登錄的接口,用戶登錄信息的驗證由第三方完成,並返回給當前系統是否驗證通過。

當然,接口測試也有相應的類庫或工具,例如測試HTTP的有HttpUnit、Postman等

 

2.3 UI自動化測試

  UI層是用戶使用該產品的入口,所有功能都通過這一層提供並展示給用戶,所以測試工作大多集中在這一層進行。為了減輕這一層的測試人力和時間成本,早期的自動化測試工具主要針對該層設計。目前主流的測試工具有UFT、Watie、Robot Framework、Selenium等。

  除UI層所展示的功能外,前端代碼同樣需要進行測試。在前端開發中最主要的莫過於JavaScript腳本語言,而QUnit就是針對JavaScript的一個強大的單元測試框架。

   測試金字塔映射了不同測試階段所投入的自動化測試的比例,UI層被放到了塔尖,這也說明UI層應該投入較少的自動化測試。如果系統只關注UI層的自動化測試並不是一種明智的做法,因為其很難從本質上保證產品的質量。如果妄圖實現全面的UI層的自動化測試,那么需要投入大量的人力和時間,然而, 最終獲得的收益可能遠低於所投入的成本,因為對於一個系統來講,越接近用戶其越容易變化,為了適應這種變化就必須要投入更多的成本。

  既然UI層的自動化測試這么勞民傷財,那么我們是不是只做單元測試與接口測試就可以了呢?答案是否定的,因為不管什么樣的產品,最終呈現給用戶的都是UI層的功能,所以產品才需要招聘大量的測試人員進行UI層的功能測試。也正是因為測試人員在UI層投入了大量的時間與精力,所以我們才有必要通過自動化的方式幫助測試人員解放部分重復的工作。所以,應該更多的提倡“半自動化”的開展測試工作,把可以自動化測試的工作交給工具或腳本完成,這樣測試人員就可以把更多的精力放在更重要的測試工作上,例如探索性測試等。

  至於在金字塔中每一層測試的投入比例則要根據實際的產品特征來划分。在《Google測試之道》一書中提到,Google對產品測試類型划分為:小測試、中測試和大測試,采用70%(小),20%(中)、10%(大)的比例,大體對應測試金字塔中的Unit、Service和 UI 層。

  在進行自動化測試中最擔心的是變化,因為變化會直接導致測試用例的運行失敗,所以需要對自動化腳本進行不斷調整。如何控制失敗、降低維護成本是對自動化測試工具及人員能力的挑戰。反過來講,一份永遠都運行通過的自動化測試用例已經失去了它存在的價值。

 

三、什么樣的項目適合自動化測試

  1. 任務測試明確,不會頻繁變動。
  2. 每日構建后的測試驗證。
  3. 比較頻繁的回歸測試。
  4. 軟件系統界面穩定,變動少。
  5. 需要在多平台上運行的相同測試案例、組合遍歷型的測試,大量的重復任務。
  6. 軟件維護周期長。
  7. 項目進度壓力不太大。
  8. 被測軟件系統開發較為規范,能夠保證系統的可測試性。
  9. 具備大量的自動化測試平台。
  10. 測試人員具備較強的編程能力。

在我們普遍的自動化測試經驗中,一般滿足以下三個條件就可以對項目開展自動化測試。

1. 軟件需求變動不頻繁

  自動化測試腳本變化的大小與頻率決定了自動化測試的維護成本。如果軟件需求變動過於頻繁,那么測試人員就需要根據變動的需求來不斷地更新自動化測試用例,從而適應新的功能。而腳本的維護本身就是一個開發代碼的過程,需要擴展、修改、調試,有時還需要對架構做出調整。如果所花費的維護成本高於利用其節省的測試成本,那么自動化測試就失去了它的價值與意義。

  一種折中的做法是先對系統中相對穩定的模塊與功能進行自動化測試,而變動較大的部分用用工進行測試。

2. 項目周期較長

  由於自動化測試需求的確定,自動化測試框架的設計、腳本的開發與調試均需要時間來完成,而這個過程本身就是一個軟件的開發過程,如果項目的周期較短,沒有足夠的時間去支持這樣一個過程的話,那么就不需要進行自動化測試了。

3. 自動化測試腳本可重復使用

  自動化測試腳本的重復使用要從三個方面來考量:一是所測試的項目之間是否存在很大的差異性(如C/S系統架構與B/S系統架構的差異);二是所選擇的測試技術和工具是否適應這種差異;三是測試人員是否有能力設計開發出適應這種差異的自動化測試框架。

 

四、自動化測試及工具簡述

   自動化測試的概念有廣義與俠義之分:廣義上來講,所有借助工具來輔助進行軟件測試的方法都可以稱為自動化測試;狹義上來講,主要指基於UI層的功能自動化測試。

目前市面上的自動化測試工具非常多,下面幾款是比較常見的自動化測試工具。

1.UTF

  UTF有QTP和ST合並而來,有HP公司開發。它是一個企業級的自動測試工具,提供了強大易用的錄制回放功能,同時兼容對象識別模式與圖像識別模式兩種識別方式,支持B/S與C/S兩種架構的軟件測試,是目前主流的自動化測試工具。

2. Robot Framework

  Robot Framework 是一款基於Python語言編寫的自動化測試框架,具備良好的可擴展性,支持關鍵字驅動,可以同時測試多種類型的客戶端或者接口,可以進行分布式測試。

3. Watir

  Watir是一個基於Web模式的自動化功能測試工具。Watir是一個Ruby語言庫,使用Ruby語言進行腳本開發。

4. Selenium

  Selenium也是一個用於Web應用程序測試的工具,支持多平台、多瀏覽器、多語言去實現自動化測試。目前在Web自動化領域應用越來越廣泛。

 

當然,除上面所列的自動化測試工具外,根據不同的應用還有很多商業的或開源的以及公司自己開發的自動化測試工具。

 

五、Selenium工具介紹

5.1 什么是Selenium?

  Selenium主要用於Web應用程序的自動化測試,但並不局限於此,它還支持所有基於Web的管理任務自動化。

Selenium的特點如下:

  • 開源、免費
  • 多瀏覽器支持:Firefox、Chrome、IE、Opera、Edge
  • 多平台支持:Linux Windows MAC
  • 多語言支持:Java Python Ruby C# JavaScript C++
  • 對Web頁面有良好的支持
  • 簡單(API簡單),靈活(用開發語言驅動)
  • 支持分布式測試用例執行

 

5.2 Selenium IDE

  Selenium IDE是嵌入到Firefox瀏覽器中的一個插件,實現簡單的瀏覽器操作的錄制與回放功能。官方定位:

快速地創建bug重現腳本,在測試人員測試過程中,發現bug之后可以通過IDE將重現的步驟錄制下來,以幫助開發人員更容易地重現BUG

 IDE錄制的腳本可以轉換成多種語言,從而幫助我們快速地開發腳本。關於這個功能在后面的章節中我們會着重介紹。

 

5.3 Selenium Grid

  Selenium Grid是一種自動化的測試輔助工具,Gird通過利用現有的計算機基礎設施,能加快Web-App的功能測試。利用Grid可以很方便地實現在多台機器上和異構環境中運行測試用例

 

5.4 Selenium RC

  Selenium RC是Selenium家族的核心部分。Selenium RC支持多種不同語言編寫的自動化測試腳本,通過Selenium RC的服務器作為代理服務器去訪問應用,從而達到測試的目的。

  Selenium RC分為Client Libraries和Selenium Server。Client Libraries庫主要用於編寫測試腳本,用來控制Selenium Server的庫。Selenium Server負責控制瀏覽器行為。總的來說,Selenium Server主要包括三部分,Launcher/Http Proxy和Core。其中,Selenium Core是被Selenium Server 嵌入到瀏覽器頁面中的。其實Selenium Core就是一堆JavaScript函數的集合,即通過這些JavaScript函數,我們才可以實現用程序對瀏覽器進行操作。Launcher用於啟動瀏覽器,把Selenium Core加載到瀏覽器頁面當中,並把瀏覽器的代理設置為Selenium Server的Http Proxy。

 

5.5 Selenium 2.0

  搞清了Selenium 1.0的家族關系,再來看看Selenium 2.0。Selenium 2.0就是把 WevDriver 加入到了這個家族中,簡單用公式表示為:

Selenium 2.0 = Selenium1.0 + WebDriver

  需要強調的是,在Selenium 2.0中主推的是WebDriver,可以將其看做Selenium RC 的替代品。因為Selenium為了保持向下的兼容性,所以在Selenium 2.0中並沒有徹底地拋棄Selenium RC。如果是初次使用Selenium開發一個新的自動化測試項目,那么可以直接使用WebDriver。

Selenium RC與WebDriver的區別

  Selenium RC是在瀏覽器中運行JavaScript應用,使用瀏覽器內置的JavaScript翻譯器來翻譯和執行selenese命令(selenese是Selenium命令集合)。

  WebDriver是通過原生瀏覽器支持或者瀏覽器擴展來直接控制瀏覽器。WebDriver針對各個瀏覽器而開發,取代了嵌入到被測Web應用中的JavaScript,與瀏覽器緊密集成,因此支持創建更高級的測試,避免了JavaScript安全模型導致的限制。除了來自瀏覽器廠商的支持之外,WebDriver還利用操作系統級的調用,模擬用戶輸入

Selenium 與 WebDriver 合並原因?

  部分原因是WebDriver 解決了Selenium存在的缺點(例如能夠繞過JavaScript沙箱,我們有出色的API)。部分原因是 Selenium 解決了WebDriver存在的問題(例如支持廣泛的瀏覽器),部分原因是因為Selenium的主要貢獻者和WebDriver的主要貢獻者都覺得合並項目是為用戶提供最優秀框架的最佳途徑。

WebDriver API

火狐瀏覽器驅動:https://github.com/mozilla/geckodriver/releases

谷歌瀏覽器驅動:http://npm.taobao.org/mirrors/chromedriver

Selenium 及 驅動 安裝

pip install Selenium
下載瀏覽器驅動
http://www.seleniumhq.org/download/
把下載的瀏覽器驅動即相應的exe文件放在PATH下即可。

from selenium import webdriver
import time

driver = webdriver.Firefox()
driver.get("http://www.baidu.com")

driver.find_element_by_id("kw").send_keys("Selenium2")
driver.find_element_by_id("su").click()
time.sleep(10)
driver.quit()

一、什么是Selenium 和WebDriver?

  Selenium是一個瀏覽器自動化操作框架。Selenium主要由三種工具組成。第一個工具SeleniumIDE,是Firefox的擴展插件,支持用戶錄制和回訪測試。錄制/回訪模式存在局限性,對許多用戶來說並不適合,因此第二個工具——Selenium WebDriver提供了各種語言環境的API來支持更多控制權和編寫符合標准軟件開發實踐的應用程序。最后一個工具——SeleniumGrid幫助工程師使用Selenium API控制分布在一系列機器上的瀏覽器實例,支持並發運行更多測試。在項目內部,它們分別被稱為“IDE”、“WebDriver”和“Grid”。

  這里主要介紹它的第二個工具:WebDriver。

  官網上是這么介紹它的:WebDriver is a clean, fast framework for automated testing of webapps. 但是我覺得它並不局限與進行自動化測試,完全可以用作其它用途。

  WebDriver針對各個瀏覽器而開發,取代了嵌入到被測Web應用中的JavaScript。與瀏覽器的緊密集成支持創建更高級的測試,避免了JavaScript安全模型導致的限制。除了來自瀏覽器廠商的支持,WebDriver還利用操作系統級的調用模擬用戶輸入。WebDriver支持Firefox(FirefoxDriver)、IE (InternetExplorerDriver)、Opera (OperaDriver)和Chrome (ChromeDriver)。 它還支持Android (AndroidDriver)和iPhone (IPhoneDriver)的移動應用測試。它還包括一個基於HtmlUnit的無界面實現,稱為HtmlUnitDriver。WebDriver API可以通過Python、Ruby、Java和C#訪問,支持開發人員使用他們偏愛的編程語言來創建測試。

from selenium import webdriver

b = webdriver.Firefox()
b.get("http://www.baidu.com")
b.find_element_by_id("kw").send_keys("火影")
b.find_element_by_id("su").click()
b.close()

在不同的編程語言中會有語法的差異,我們跑去這些差異,在不同的語言中實現百度搜索的自動化實例都完成了下面幾個操作。

  1. 首先導入Selenium(WebDriver)相關模塊。
  2. 調用Selenium的瀏覽器驅動,獲取瀏覽器句柄(driver)並啟動瀏覽器。
  3. 通過句柄訪問百度URL。
  4. 通過句柄操作頁面元素(百度輸入框和按鈕)。
  5. 通過句柄關閉瀏覽器。

  所以,webDriver支持多種編程語言,也可以看作是多種語言都支持WebDriver,唯一的不同在於不同語言實現的類和方法名的命名差異性。當然,這樣做的好處不言而喻:每個人都可以根據自己熟悉的語言來使用 WebDriver 編寫自動化測試腳本。

二、WebDriver API

2.1 元素定位

2.1.1 id 定位

find_element_by_id("id")

2.1.2 name 定位

find_element_by_name("name")

2.1.3 class 定位

find_element_by_class_name("class")

2.1.4 tag 定位

find_element_by_tag_name("div")
# 就是標簽,比如<div>/<span>等

2.1.5 link 定位

  link 定位與前面介紹的幾種定位方法有所不同,它專門用來定位文本鏈接。百度輸入框上面的幾個文本鏈接的代碼如下:

<a class="mnav" name="tj_trnews" href="http://www.baidu.com">新聞</a>
<a class="mnav" name="tj_trhao123" href="http://www.hao123.com">hao123</a>
<a class="mnav" name="tj_trmap" href="http://map.baidu.com">地圖</a>
<a class="mnav" name="tj_trvideo" href="http://v.baidu.com">視頻</a>
<a class="mnav" name="tj_trtieba" href="http://tieba.baidu.com">貼吧</a>

  其實可以使用name屬性來定位。這里演示link定位的使用

 

find_element_by_link_text("新聞")
find_element_by_link_text("hao123")
find_element_by_link_text("地圖")
find_element_by_link_text("視頻")
find_element_by_link_text("貼吧")

  find_element_by_link_text("文本") 方法通過元素標簽對之間的文本信息來定位元素。

2.1.6 partial link 定位

  partial link 定位是對 link 定位的一種補充,有些文本鏈接會比較長,這個時候我們可以取文本鏈接的一部分定位,只要這一部分信息可以唯一地標識這個鏈接。

<a class="mnav" name="tj_lang" href="#">一個很長很長的文本鏈接</a>

  可以如下定位:

find_element_by_partial_link_text("一個很長的")
find_element_by_partial_link_text("文本鏈接")

  find_element_by_partial_link_text()方法通過元素標簽對之間的部分文本信息來定位元素。

 

  前面介紹的幾種定位方法相對來說比較簡單,理想狀態下,在一個頁面當中每一個元素都有一個唯一id和name屬性值,我們可以通過它們的屬性值來找到它們。但在實際項目中並非想象得這般美好,有時候一個元素並沒有id、name屬性,或者頁面上多個元素的id和name屬性值相同,又或者每一次刷新頁面,id值都會隨機變化。怎么辦?可以用Xpath與CSS定位。

2.1.7 XPath 定位

  可參考:http://www.w3school.com.cn/xpath/index.asp

  XPath是一種在XML文檔中定位元素的語言。因為HTML可以看做XML的一種實現,所以selenium用戶可以使用這種強大的語言在web應用中定位元素。

  絕對路徑定位

  XPath 有多種定位策略,最簡單直觀的就是寫出元素的絕對路徑。

  參考baidu.html前端工具所展示的代碼,我們可以通過下面的方式找到百度輸入框和搜索按鈕。

find_element_by_xpath("/html/body/div/div[2]/div/div/div/from/span/input")
find_element_by_xpath("/html/body/div/div[2]/div/div/div/from/span[2]/input")

  find_element_by_xpath()方法使用XPath語言來定位元素。XPath主要用標簽名的層級關系來定位元素的絕對路徑,最外層為html語言。在body文本內,一級一級往下查找,如果一個層級下有多個相同的標簽名,那么就按上下順序確定是第幾個,例如,div[2]表示當前層級下的第二個div標簽。

  利用元素屬性定位

  除了使用絕對路徑外,XPath 也可以使用元素的屬性值來定位。同樣以百度輸入框和搜索按鈕為例:

find_element_by_xpath("//*[@id='kw']")   #注意外層 " 符號和內層  ' 符號
find_element_by_xpath("//*[@id='su']")

  //表示當前頁面某個目錄下,input 表示定位元素的標簽名,[@id="kw"]表示這個元素的 id 屬性值等於 kw。下面通過name和class屬性值來定位。

  瀏覽器開發者工具F12,復制,XPath

  層級與屬性結合

  如果一個元素本身沒有可以唯一標識這個元素的屬性值,name我們可以找其上一級元素,如果它的上一級元素有可以唯一標識屬性的值。也可以拿來使用。

find_element_by_xpath('//[@id="cnblogs_post_body"]/p[1]/a[1]')

  使用邏輯運算符

  如果一個屬性不能唯一地區分一個元素,我們還可以使用邏輯運算符鏈接多個屬性來查找元素。

<input type="text" id="kw" class="su" name="ie">
<input type="text" id="kw" class="aa" name="ie">
<input type="text" id="bb" class="su" name="ie">

  如上面的三行元素,假設我們現在要定位第一行元素,如果使用 id 將會與第二行元素重名,如果使用 class 將會與第三行元素重名,如果同時使用 id 和 class 就會唯一地標識這個元素,這個時候就可以通過邏輯運算符 “and” 來連接兩個條件。

  當然,我們也可以用“and”連接更多的屬性來唯一地標識一個元素。

find_element_by_xpath('//input[@id="kw" and @class="su"]/span/input')

2.1.8 CSS 定位

  CSS 是一種語言,它用來描述HTML和XML文檔的表現。CSS使用選擇器來為頁面元素綁定屬性。這些選擇器可以被Selenium用作另外的定位策略。

  CSS可以較為靈活地選擇空間的任意屬性,一般情況下定位速度要比XPath快,但對於初學者來說學習起來稍微有點難度。

(1) 通過 class 屬性定位

find_element_by_css_selector(".s_ipt")
find_element_by_css_selector(".bgs_btn")

  find_element_by_css_selector()方法用於CSS語言定位元素,點號(.)表示通過class屬性來定位元素。

(2) 通過 id 屬性定位

find_element_by_css_selector("#kw")
find_element_by_css_selector("#su")

(3) 井號(#)表示通過 id 屬性來定位元素

  通過標簽名定位:

find_element_by_css_selector("input")

  在 CSS 語言中,用標簽名定位元素不需要任何符號標識,直接使用標簽名即可。但我們前面已經了解到,標簽名重復的概率非常大,所以通過這種方式很難找到想要的元素。

(3.1)通過父子關系定位

find_element_by_css_selector("span>input")

  上面的寫法表示有父親元素,它的標簽名為apan,查找它的所有標簽名叫input的子元素。

(3.2)通過屬性定位

find_element_by_css_selector("[autocomplete=off]")
find_element_by_css_selector("[name='kw']")
find_element_by_css_selector("[type='submit']")

  在 CSS 當中也可以使用元素的任意屬性,只要這些屬性可以唯一標識這個元素,對於屬性值來說,可加引號,也可以不加,但注意和整個字符串的引號進行區分。

(3.3)組合定位

  我們當然可以把上面的定位策略組合起來使用,這就大大加強了定位元素的唯一性。

find_element_by_css_selector("span.bgs_ipt_wr>input.s_inpt")
find_element_by_css_selector("span.bgs_btn_wr>input#su")

  有一個父元素,它的標簽名叫 span;它有一個class屬性值叫 bgs_ipt_wr;它有一個子元素,標簽名叫 input,並且這個子元素的 class 屬性值叫 s_ipt。

  瀏覽器開發者工具F12,復制,selector

2.1.9 用 By 定位元素

  針對前面介紹的 8 種定位方法,WebDriver 還提供了另外一套寫法,即統一調用 find_element()方法,通過 By 來聲明定位的方法,並且傳入對應定位方法的定位參數,具體如下:

find_element(By.ID,"kw")
find_element(By.NAME,"wd")
find_element(By.CLASS_NAME,"s_ipt")
find_element(By.TAG_NAME,"input")
find_element(By.LINK_TEXT,"新聞")
find_element(By.PARTIAL_LINK_TEXT,"")
find_element(By.XPATH,"//*[@class='bgs_btn']")
find_element(By.CSS_SELECTOR,"span.bgs_btn_wr>input#su")

  find_element()方法只用於定位元素。它需要兩個參數,第一個參數是定位的類型,由By提供;第二個參數是定位的具體方法,在使用By之前需要將By類導入。

from selenium.webdriver.common.by import By

  通過查看 WebDriver 的底層實現代碼發現它們其實是一回事兒,例如,find_element_by_id()方法的實現。

    def find_elements_by_id(self, id_):
        """
        Finds multiple elements by id.

        :Args:
         - id\_ - The id of the elements to be found.

        :Returns:
         - list of WebElement - a list with elements if any was found.  An
           empty list if not

        :Usage:
            elements = driver.find_elements_by_id('foo')
        """
        return self.find_elements(by=By.ID, value=id_)

2.2、控制瀏覽器

2.2.1 控制瀏覽器窗口大小

from selenium import webdriver

driver = webdriver.Firefox()
driver.get("http://m.mail.10086.cn")

#參數數字為像素點
print("設置瀏覽器寬480、高800顯示")
driver.set_window_size(480,800)
driver.quit()

  在PC端執行自動化測試腳本大多的情況下是希望瀏覽器在全屏模式下執行,那么可以使用maximize_window()方法使打開的瀏覽器全屏顯示,其用法與set-window_size() 相同,但它不需要參數。

quit和close的區別:https://blog.csdn.net/yangfengjueqi/article/details/84338167

2.2.2 控制瀏覽器后退、前進

  在使用瀏覽器瀏覽網頁時,瀏覽器提供了后退和前進按鈕,可以方便地在瀏覽過的網頁之間切換,WebDriver 也提供了對應的 back() forward() 方法來模擬后退和前進按鈕,下面通過例子來演示這兩個方法的使用。

from selenium import webdriver

driver = webdriver.Firefox()

#訪問百度首頁
first_url="http://www.baidu.com"
print("now access %s" %(first_url))
driver.get(first_url)

#訪問新聞頁面
second_url="http://news.baidu.com"
print("now access %s" %(second_url))
driver.get(second_url)

#返回(后退)到百度首頁
print("back to %s" %(first_url))
driver.back()

#前進到新聞頁
print("forward to %s" %(second_url))
driver.forward()
driver.quit()

  為了看清腳本的執行過程,下面每操作一步都通過print() 來打印當前的 URL 地址,執行結果如下:

now access http://www.baidu.com
now access http://news.baidu.com
back to http://www.baidu.com
forward to http://news.baidu.com

2.2.3 模擬瀏覽器刷新

driver.refresh()     #刷新當前頁面

2.3 簡單元素操作

  • clear():                               清除文本
  • send_keys(*value):          模擬按鍵輸入
  • click():                                單擊元素

  clear() 方法用於清除文本輸入框中的內容。例如,登錄框內一般默認會有“賬號”、“密碼”等提示信息,用於引導用戶輸入正確的數據;但如果直接向輸入框中輸入數據,則可能會與輸入框中的提示信息拼接,從而造成輸入信息錯誤。這個時候可以先使用clear()方法來清除輸入框中的默認提示信息。

  send_keys() 方法模擬鍵盤向輸入框里輸入內容。如上面的例子中,通過這個方法向用戶名和密碼框中輸入登錄信息。當然,它的作用不僅於此,我們還可以用它發送鍵盤按鍵,甚至用它來模擬文件上傳。

  click() 方法可以用來單擊一個元素,前提是它是可以被單擊的對象,它與 send_keys() 方法是Web頁面操作中最常用到的兩個方法。其實 click() 方法不僅可用於單擊一個按鈕,它還能單擊任何可以單擊的文字/圖片鏈接、復選框、單選框、下拉框等。

from selenium import webdriver

b = webdriver.Chrome()
b.get("https://mail.qq.com/")
b.find_element_by_xpath('//*[@id="u"]').clear()
b.find_element_by_xpath('//*[@id="u"]').send_keys("578389018@qq.com")
b.find_element_by_xpath('//*[@id="p"]').send_keys("*******")
b.find_element_by_xpath('//*[@id="login_button"]').click()

b.quit()

  會報錯,表示找不到元素。

selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"xpath","selector":"//*[@id="u"]"}

元素找不到有好幾種可能:

2.3.1 Frame/Iframe原因定位不到元素

  這個是最常見的原因,首先要理解下frame的實質,frame中實際上是嵌入了另一個頁面,而webdriver每次只能在一個頁面識別,因此需要先定位到相應的frame,對那個頁面里的元素進行定位。

解決方案:
  如果 iframe有name或id的話,直接使用switch_to.frame("name值")或switch_to.frame("id值")。如下:
from selenium import webdriver

b = webdriver.Chrome()
b.get("https://mail.qq.com/")
b.switch_to.frame('login_frame')  #需先跳轉到iframe框架
b.find_element_by_xpath('//*[@id="u"]').clear()
b.find_element_by_xpath('//*[@id="u"]').send_keys("578389018@qq.com")
b.find_element_by_xpath('//*[@id="p"]').send_keys("********")
b.find_element_by_xpath('//*[@id="login_button"]').click()

b.quit()
如果iframe沒有name或id的話,則可以通過下面的方式定位:
#先定位到iframe
#再將定位對象傳給switch_to.frame()方法
from selenium import webdriver

b = webdriver.Chrome()
b.get("https://mail.qq.com/")
elementi= b.find_element_by_xpath('//*[@id="login_frame"]')
b.switch_to.frame(elementi)  #需先跳轉到iframe框架
b.find_element_by_xpath('//*[@id="u"]').clear()
b.find_element_by_xpath('//*[@id="u"]').send_keys("578389018@qq.com")
b.find_element_by_xpath('//*[@id="p"]').send_keys("********")
b.find_element_by_xpath('//*[@id="login_button"]').click()

b.quit()
  如果完成操作后,可以通過 switch_to.parent_content()方法跳出當前一級iframe,或者還可以通過 switch_to.default_content()方法跳回最外層的頁面。

2.3.2 Xpath描述錯誤原因

由於Xpath層級太復雜,容易犯錯。

可以使用Firefox的firePath,復制xpath路徑。該方式容易因為層級改變而需要重新編寫過xpath路徑,不建議使用,初學者可以先復制路徑,然后嘗試去修改它。

2.3.3 頁面還沒有加載出來,就對頁面上的元素進行的操作

     這種情況一般說來,可以設置等待,等待頁面顯示之后再操作,這與人手工操作的原理一樣:
  1. 設置等待時間;缺點是需要設置較長的等待時間,案例多了測試就很慢;
  2. 設置等待頁面的某個元素出現,比如一個文本、一個輸入框都可以,一旦指定的元素出現,就可以做操作。
  3. 在調試的過程中可以把頁面的html代碼打印出來,以便分析。
解決方案:
導入時間模塊。
import time
time.sleep(3)

2.3.4 動態id定位不到元素

解決方案:
如果發現是動態id,直接用xpath定位或其他方式定位。

2.3.5 二次定位,如彈出框登錄

     如百度登錄彈出框登錄百度賬號,需先定位到百度彈出框,然后再定位到用戶名密碼登錄。

2.3.6 不可見元素定位

     如上百度登錄代碼,通過名稱為tj_login查找的登錄元素,有些是不可見的,所以加一個循環判斷,找到可見元素(is_displayed())點擊登錄即可。

參考:https://blog.csdn.net/apollolkj/article/details/77096547

2.3.7 WebElement 接口常用方法

submit()

submit() 方法用於提交表單。例如,在搜索框輸入關鍵字之后的“回車”操作,就可以通過submit() 方法模擬。

from selenium import webdriver

driver = webdriver.Firefox()

driver.get("http://www.youdao.com")

driver.find_element_by_id("translateContent").send_keys("hello")
#提交輸入框的內容
driver.find_element_by_id("translateContent").submit()

  上面的例子,我們通過定位有道搜索框並通過submit()提交搜索框的內容,同樣達到單擊“搜索”按鈕的效果。有時候 submit() 可以與 click() 方法互換來使用, submit() 同樣可以提交一個按鈕,但 submit() 的應用范圍遠不及 click() 廣泛。

  • size:                                            返回元素的尺寸
  • text:                                            獲取元素的文本
  • get_attribute(name):                獲取屬性值
  • is_dispalyed():                          設置該元素是否用戶可見,注意,是不可見,不是不存在

 

from selenium import webdriver

driver = webdriver.Firefox()
driver.get("http://www.baidu.com")

# 獲取輸入框的尺寸
size = driver.find_element_by_id("kw").size
print(size)

# 返回百度頁面底部備案信息
text = driver.find_element_by_id("cp").text
print(text)

# 返回元素的屬性值,可以是id、name、type或其他任意屬性
attribute = driver.find_element_by_id("kw").get_attribute("type")
print(attribute)

# 返回元素的結果是否可見,返回結果為True或False
result = driver.find_element_by_id("kw").is_displayed()       # 注意,是不可見,不是不存在。不存在會直接報錯
print(result)

driver.quit()

>>>
{'height': 22.0, 'width': 500.0}
©2018 Baidu 使用百度前必讀 意見反饋 京ICP證030173號  京公網安備11000002000001號 
text
True

***Repl Closed***

 2.4 鼠標事件

  ActionChains 類提供了鼠標操作的常用方法:

  • perform():                              執行所有ActionChains中存儲的行為
  • context_click():                     右擊
  • double_click():                      雙擊
  • drag_and_drop():                 拖動
  • move_to_element():            鼠標懸停

2.4.1 鼠標右擊操作

from selenium import webdriver
# 引入 ActionChains 類
from selenium.webdriver.common.action_chains import ActionChains

driver = webdriver.Firefox()
driver.get("http://www.baidu.com")

# 定位到要右擊的元素
right_click = driver.find_element_by_id("kw")
# 對定位到的元素執行鼠標右鍵操作
ActionChains(driver).context_click(right_click).perform()

 

  • from selenium.webdriver.common.action_chains import ActionChains 導入提供鼠標操作的 ActionChains 類
  • ActionChains(driver) 調用 ActionChains() 類,將瀏覽器驅動 driver 作為參數傳入
  • context_click(right_click)    context_click() 方法用於模擬鼠標右鍵操作,在調用時需要指定元素定位。
  • perform()    執行所有 ActionChains 中儲存的行為,可以理解成是對整個操作的提交動作。 

 

2.4.2 鼠標懸停

from selenium import webdriver
# 引入 ActionChains 類
from selenium.webdriver.common.action_chains import ActionChains

driver = webdriver.Firefox()
driver.get("http://www.baidu.com")

# 定位到要懸停的元素
above = driver.find_element_by_id("su")
# 對定位到的元素執行懸停操作
ActionChains(driver).move_to_element(above).perform()

2.4.3 鼠標雙擊操作

from selenium import webdriver
# 引入 ActionChains 類
from selenium.webdriver.common.action_chains import ActionChains

driver = webdriver.Firefox()
driver.get("http://www.baidu.com")

# 定位到要雙擊的元素
double_click = driver.find_element_by_id("su")
# 對定位到的元素執行雙擊操作
ActionChains(driver).double_click(double_click).perform()

2.4.4 鼠標拖動操作

  drag_and_drop(source,target)在源元素上按住鼠標左鍵,然后移動到目標元素上釋放。

  • source:鼠標拖動的源元素
  • target:鼠標釋放的目標元素
from selenium import webdriver
# 引入 ActionChains 類
from selenium.webdriver.common.action_chains import ActionChains

driver = webdriver.Firefox()
driver.get("http://www.baidu.com")

# 定位元素的原位置
element = driver.find_element_by_id("su")
target = driver.find_element_by_id("su")

# 執行元素的拖動操作
ActionChains(driver).drag_and_drop(element,target).perform()

2.5 鍵盤事件

  keys() 類提供了鍵盤上幾乎所有按鍵的方法。前面了解到,send_keys()方法可以用來模擬鍵盤輸入,除此之外,我們還可以用它來輸入鍵盤上的按鍵,甚至是組合鍵,如 Ctrl + A ,Ctrl + C等。

#! /usr/bin/env python
# -*- coding: utf-8 -*-
# __author__ = "Q1mi"
# Date: 2018/7/2

from selenium import  webdriver
# 引入Keys模塊
from selenium.webdriver.common.keys import Keys

driver = webdriver.Firefox()
driver.get("http://www.baidu.com")

# 輸入框輸入內容
driver.find_element_by_id("kw").send_keys("seleniumm")

# 刪除多輸入的一個m
driver.find_element_by_id("kw").send_keys(Keys.BACK_SPACE)

# 輸入空格鍵 + “教程”
driver.find_element_by_id("kw").send_keys(Keys.SPACE)
driver.find_element_by_id("kw").send_keys("教程")

# ctrl + a 全選輸入框內容
driver.find_element_by_id("kw").send_keys(Keys.CONTROL,"a")

# ctrl + x 剪切輸入框內容
driver.find_element_by_id("kw").send_keys(Keys.CONTROL,"x")

# ctrl + v 粘貼內容到輸入框
driver.find_element_by_id("kw").send_keys(Keys.CONTROL,"v")

# 通過回車鍵來代替單擊操作
driver.find_element_by_id("su").send_keys(Keys.ENTER)

  以下為常用的鍵盤操作:

send_keys(Keys.BACK_SPACE)       刪除鍵(BackSpace)
send_keys(Keys.SPACE)            空格鍵(Space)
send_keys(Keys.TAB)              制表鍵(Tab)
send_keys(Keys.ESCAPE)           回退鍵(Esc)
send_keys(Keys.ENTER)            回車鍵(Enter)
send_keys(Keys.CONTROL,"a")      全選(Ctrl+A)
send_keys(Keys.CONTROL,"c")      復制(Ctrl+C)
send_keys(Keys.CONTROL,"x")      剪切(Ctrl+X)
send_keys(Keys.CONTROL,"v")      粘貼(Ctrl+V)
send_keys(Keys.F1)               鍵盤F1
......                           .
send_keys(Keys.F12)              鍵盤F12

2.6 獲取驗證信息

  在編寫功能測試用例時,會假定一個預期結果,在執行用例的過程中把得到的實際結果與預期結果進行比較,從而判斷用戶的通過或失敗。自動化測試用例是由機器去執行的,通常機器並不像人一樣有思維和判斷能力,那么是不是模擬各種操作頁面的動作沒有報錯就說明用例執行成功呢?並非如此,假如我們模擬百度搜索的用例,當新的迭代版本上線后,每一頁的搜索結果少一條,但用例的執行不會報錯,因此這個 bug 永遠不會被自動化測試發現。

  那么是不是在運行自動化測試用例時需要由測試人員盯着用例的執行來辨別執行結果呢?如果是這樣的話,自動化測試就失去了“自動化”的意義。在自動化用例執行完成之后,我們可以從頁面上獲取一些信息來“證明“用例執行是成功還是失敗。

  通常用得最多的幾種驗證信息分別是title、URL和text。text方法在前面已經講過,它用於獲取標簽對之間的文本信息。

from selenium import webdriver
import time

driver = webdriver.Chrome()
driver.get("https://mail.qq.com/")
time.sleep(2)
frame_add = driver.find_element_by_xpath('//*[@id="login_frame"]')
driver.switch_to_frame(frame_add)  #需先跳轉到iframe框架
print("Before login==========")

# 打印當前頁面title
now_title = driver.title print(now_title)


# 打印當前頁面URL
now_url = driver.current_url print(now_url)

# 執行郵箱登錄
driver.find_element_by_id("u").clear()
driver.find_element_by_id("u").send_keys("578389018@qq.com")
driver.find_element_by_id("p").clear()
driver.find_element_by_id("p").send_keys("*******")
driver.find_element_by_id("login_button").click()
time.sleep(5)

print("After login==========")
# 再次打印當前頁面title
now_title = driver.title
print(now_title)


# 再次打印當前頁面URL
now_url = driver.current_url
print(now_url)

# 獲取登錄的用戶名
user = driver.find_element_by_id("useraddr").text print(user)

driver.quit()

>>>
C:\Users\Administrator\AppData\Local\Programs\Python\Python37\python.exe C:/Users/Administrator/Desktop/py/test.py
Before login==========
登錄QQ郵箱
https://mail.qq.com/
After login==========
QQ郵箱
https://mail.qq.com/cgi-bin/frame_html?sid=IDp2I6d3Vi8WORtA&r=b6242c8e186a9f7c4b80aab437e760f8
dongye95@foxmail.com

Process finished with exit code 0
  • title:用於獲得當前頁面的標題。
  • current_url:用戶獲得當前頁面的URL

  通過打印結果,我們發現登錄前后的title和URL明顯不同。我們可以把登錄之后的這些信息存放起來,作為登錄是否成功的驗證信息。當然,這里URL每次登錄都會有所變化,是不能拿來做驗證信息的。title可以拿來做驗證信息,但它並不能明確地標識是哪個用戶登錄成功了,因此通過text獲取用戶文本(dongye95@foxmail.com)是很好的驗證信息。

 

 2.7 設置元素等待

  如今大多數Web應用程序使用AJAX技術。當瀏覽器在加載頁面時,頁面上的元素可能並不是同時被加載完成的,這給元素的定位增加了困難。如果因為在加載某個元素時延遲而造成ElementNotVisibleException的情況出現,那么就會降低自動化腳本的穩定性,我們可以通過設置元素等待改善這種問題造成的不穩定。

  WebDriver提供了兩種類型的等待:顯式待和隱式等待

2.7.1 顯式等待

    顯式等待使 WebDriver 等待某個條件成立時繼續執行,否則在達到最大時長時拋棄超時等待。(TimeoutException)

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver = webdriver.Firefox()
driver.get("http://www.baidu.com")

element = WebDriverWait(driver, 5, 0.5).until(
    EC.presence_of_element_located((By.ID,"kw"))
    )

element.send_keys('selenium')
driver.quit()

  WebDriverWait 類是由 WebDriver 提供的的等待方法。在設置時間內,默認每隔一段時間檢測一次當前頁面元素是否存在,如果超過設置時間檢測不到則拋出異常。具體格式如下

WebDriverWait(driver,timeout,poll_frequency=0.5,ignored_exceptions=None)
  • driver                                瀏覽器驅動
  • timeout                             最長超時時間,默認以秒為單位
  • poll_frequency                 檢測的間隔(步長)時間,默認為0.5s    
  • ignored_exceptions         超時后的異常信息,默認情況下拋NoSuchElementException 異常

  WebDriverWait() 一般由 until() 或者 until_not() 方法配合使用,下面是 until() 和 until_not() 方法的說明。

  • until(method, message=' ')

  調用該方法提供的驅動程序作為一個參數,直到返回值為True

  • until_not(method, message=' ')

  調用該方法提供的驅動程序作為一個參數,直到返回值為False

  在本例中,通過 as 關鍵字將 expected_conditions 重命名為 EC,並調用 presence_of_element_located() 方法判斷元素是否存在。

  expected_conditions 類所提供的預期條件判斷的方法如下表所示。

  除expected_conditions 所提供的豐富的預期條件判斷方法外,還可以使用前面學過的is_displayed() 方法來判斷元素是否可見。

from selenium import webdriver
from time import sleep, ctime

driver = webdriver.Firefox()
driver.get("http://www.baidu.com")

print(ctime())
for i in range(10):
    try:
        el = driver.find_element_by_id("kw22")
        if el.is_displayed():
            break
    except Exception as e:
        pass
        sleep(1)
else:
    print("time out")
driver.close()
print(ctime())

>>>
Tue Jul  3 09:41:42 2018
time out
Tue Jul  3 09:41:54 2018

***Repl Closed***

  相對來說,這種方式更容易理解,通過for循環10次,每次循環判斷元素的is_displayed()狀態是否為True;如果為True,則break跳出循環;否則sleep(1) 后繼續循環判斷,直到10次循環結束后,打印“time out”信息。

2.7.2 隱式等待

  隱式等待是通過一定的時長等待頁面上某元素加載完成。如果超出還沒有被加載,則拋出 NoSuchElementException 異常。WebDriver提供方法來實現隱式等待,默認設置為0.它的用法相對來說要簡單得多。

from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from time import ctime

driver  = webdriver.Firefox()

# 設置隱式等待10秒
driver.implicitly_wait(10)
driver.get("http://www.baidu.com")

try:
    print(ctime())
    driver.find_element_by_id("kw22").send_keys('selenium')
except NoSuchElementException as e:
    print("查找元素異常 %s" %(e))
else:
    pass
finally:
    print(ctime())
    driver.quit()

>>>
Wed Jul  4 12:51:30 2018
查找元素異常 Message: Unable to locate element: [id="kw22"]

Wed Jul  4 12:51:40 2018

Process finished with exit code 0

  implicitly_wait() 默認參數的單位為秒,本例中設置等待時長為10秒。首先這10秒並非一個固定的等待時間,它並不影響腳本的執行速度。其次,它並不針對頁面上的某一元素進行等待。當腳本執行到某個元素定位時,如果元素可以定位,則繼續執行;如果元素定位不到,則它將以輪詢的方式不斷地判斷元素是否被定位到。假設在第6秒定位到了元素則繼續執行,若直到超出設置時間(10秒)還沒有定位到元素,則拋出異常。

  在上面的例子中,顯然百度輸入框的定位 id=kw22 是有誤的,通過打印的兩個時間可以看出,當執行度一百度輸入框的操作時,超過了10秒的等待。

2.7.3 sleep 休眠方法

  有時候我們希望腳本在執行到某一位置時做固定時間的休眠,尤其是在腳本調試過程中。這時可以使用sleep()方法,需要說明的是,sleep()方法由Python的time模塊提供。

from selenium import webdriver
from time import sleep

driver = webdriver.Firefox()
driver.get("http://www.baidu.com")

sleep(2)
driver.find_element_by_id("kw").send_keys("webdriver")
driver.find_element_by_id("su").click()
sleep(3)

driver.quit()

  當執行到sleep()方法時會固定休眠一定的時長,然后再繼續執行。sleep()方法默認參數以秒為單位,如果設置時長小於1秒,則可以用小數表示,如sleep(0.5)表示休眠0.5秒。

 

2.8 定位一組元素

  在2.4中我們已經學了8中定位方法,這8種定位方法時針對單個元素定位的,WebDriver還提供了與之對應的8種用於定位一組元素的方法。

find_elements_by_id()
find_elements_by_name()
find_elements_by_class_name()
find_elements_by_tag_name()
find_elements_by_link_text()
find_elements_by_partial_link_text()
find_elements_by_xpath
find_elements_by_css_selector()

  定位一組元素的方法與定位單個元素的方法類似,唯一的區別是在單詞element后面多了一個s表示復數。定位一組元素一般用於以下場景:

  • 批量操作元素,例如勾選頁面上所有的復選框
  • 先獲取一組元素,再從這組對象中過濾出需要操作的元素。例如定位出頁面上所有的checkbox然后選擇其中的一個進行操作。
from selenium import webdriver
import os,time

driver = webdriver.Firefox()
file_path = "file:///" + os.path.abspath("checkbox.html")   # file:/// 換成 file: 也可以,但是這個是必須的前綴
driver.get(file_path)

# 選擇頁面上所有的tag name 為input的元素
inputs = driver.find_elements_by_tag_name('input')

# 然后從中過濾出type為checkbox的元素,單擊勾選
for i in inputs:
    if i.get_attribute("type") == "checkbox":
        i.click()
        time.sleep(1)

driver.quit()

  前面提到,通過tag name的定位方式很難定位到單個元素,因為元素標簽名重名的概率很高,因而在定位一組元素時,這種方式就派上用場了。在上面的例子中先通過find_elements_by_tag_name()找到一組標簽名為input的元素。然后通過for循環進行遍歷,在遍歷過程中,通過get_attribute()方法獲取元素的type屬性是否為“checkbox”,如果為“checkbox”,就認為這個元素是一個復選框,對其進行勾選操作。

  需要注意的是,在上面的例子中,通過瀏覽器打開的是一個本地的html文件,所以需要用到Python的os模塊,path.abspath()方法用於獲取當前路徑下的文件。

  除此之外,我們還可以使用XPath或CSS來直接判斷屬性值,從而進行單擊操作。

from selenium import webdriver
import os,time

driver = webdriver.Firefox()
file_path = "file:///" + os.path.abspath("checkbox.html")
driver.get(file_path)

# 通過XPath找到type=checkbox的元素
# checkboxes = driver.find_elements_by_xpath("//input[@type='checkbox']")

# 通過CSS找到type=checkbox的元素
checkboxes = driver.find_elements_by_css_selector("//input[type=checkbox]")
for checkbox in checkboxes:
    if i.get_attribute("type") == "checkbox":
        checkbox.click()
        time.sleep(1)

# 打印當前頁面上的type為checkbox的個數
print(len(checkboxes))

# 把頁面上最后1個checkbox的鈎給去掉
driver.find_element_by_css_selector("input[type=checkbox]").pop().click()

driver.quit()

  通過XPath或CSS來查找一組元素時,省去了判斷步驟。因為定位方法已經做了判斷,只需循環對這一組元素進行勾選即可。

 

2.9 多表單切換

  在Web應用中經常會遇到frame/iframe表單嵌套頁面的應用,WebDriver只能在一個頁面上對元素識別與定位,對於frame/iframe表單內嵌頁面上的元素無法直接定位。這時就需要通過switch_to.frame() 方法將當前定位的主體切換為frame/iframe表單的內嵌頁面中。

from selenium import webdriver

b = webdriver.Chrome()
b.get("https://mail.qq.com/")

# 切換到  iframe(id="login_frame")
b.switch_to.frame('login_frame')
  
b.find_element_by_xpath('//*[@id="u"]').clear()
b.find_element_by_xpath('//*[@id="u"]').send_keys("578389018@qq.com")
b.find_element_by_xpath('//*[@id="p"]').send_keys("********")
b.find_element_by_xpath('//*[@id="login_button"]').click()

b.quit()

  switch_to.frame() 默認可以直接取表單的 id 或 name 屬性。如果 iframe 沒有可用的 id 和 name 屬性,則可以通過下面的方式進行定位。

from selenium import webdriver

b = webdriver.Chrome()
b.get("https://mail.qq.com/")

# 先通過xpath定位到iframe
elementi= b.find_element_by_xpath('//*[@id="login_frame"]')
#再將定位對象傳給switch_to.frame()方法
b.switch_to.frame(elementi)

b.find_element_by_xpath('//*[@id="u"]').clear()
b.find_element_by_xpath('//*[@id="u"]').send_keys("578389018@qq.com")
b.find_element_by_xpath('//*[@id="p"]').send_keys("********")
b.find_element_by_xpath('//*[@id="login_button"]').click()
b.switch_to.parent_frame()
b.quit()

 switch_to包的方法詳解

  在switch_to的基礎上,有這么幾個方法,鑒於基本上都是之前曾經講過的,這次把等價的方法也列出來,供大家參考

1. driver.switch_to.active_element() ---------------替換-------------- driver.switch_to_active_element()

  定位到當前聚焦的元素上      聚焦這部分參考:https://blog.csdn.net/huilan_same/article/details/52338073

2. driver.switch_to.alert() ---------------替換--------------  driver.switch_to_alert()

  切換到alert彈窗

3. driver.switch_to.default_content() ---------------替換-------------- driver.switch_to_default_content()

  切換到最上層頁面

4. driver.switch_to.frame(frame_reference) ---------------替換--------------driver.switch_to_frame(frame_reference)

  通過id、name、element(定位的某個元素)、索引來切換到某個frame

5. driver.switch_to.parent_frame()

  這是switch_to中獨有的方法,可以切換到上一層的frame,對於層層嵌套的frame很有用

6. driver.switch_to.window(window_name)           等同於     driver.switch_to_window(window_name)

  切換到制定的window_name頁面

  注: 官方把selenium.webdriver包中的switch方法全部封裝成了一個包,這樣能夠比較明了和方便,也符合軟件編程中的高內聚低耦合的思想。

 

2.10 多窗口切換

   在頁面操作過程中有時候點擊某個鏈接會彈出新的窗口,這時就需要主機切換到新打開的窗口上進行操作。WebDriver提供了switch_to.window() 方法,可以實現在不同的窗口之間切換。

  以百度首頁和百度注冊頁為例。

from selenium import webdriver
import time

driver = webdriver.Chrome()
driver.implicitly_wait(10)
driver.get("http://www.baidu.com")

# 獲得百度搜索窗口句柄
sreach_windows = driver.current_window_handle

driver.find_element_by_link_text("登錄").click()
driver.find_element_by_link_text("立即注冊").click()

# 獲得當前所有打開的窗口的句柄
all_handles = driver.window_handles

# 進入注冊窗口
for handle in all_handles:
    if handle != sreach_windows:
        driver.switch_to.window(handle)
        print("now register window!")
        driver.find_element_by_name("userName").send_keys("username")
        driver.find_element_by_id("TANGRAM__PSP_3__password").send_keys("password")
        time.sleep(2)

# 回到搜索窗口
for handle in all_handles:
    if handle == sreach_windows:
        driver.switch_to.window(handle)
        print("now sreach window!")

        # 會跳出來一個讓你下載APP的彈窗,把這個彈窗給點掉
        try:
            driver.find_element_by_id("TANGRAM__PSP_4__closeBtn")
        except:
            break
        else:
            driver.find_element_by_id("TANGRAM__PSP_4__closeBtn").click()

        # 彈窗消失后,再去查找
        driver.find_element_by_id("kw").send_keys("selenium")
        driver.find_element_by_id("su").click()
        time.sleep(2)

driver.quit()

 

  腳本的執行過程:首先打開百度首頁,通過current_window_handle 獲得當前窗口的句柄,並賦值給變量sreach_handle。接着打開登錄彈窗,在登錄彈窗上單擊“立即注冊”,從而打開新的注冊窗口。通過window_handles獲得當前打開的所有窗口的句柄,並賦值給變量all_handles。

  第一個循環遍歷all_handles,如果handle不等於sreach_handle,那么一定是注冊窗口,因為腳本執行過程中只打開了兩個窗口。所以,通過switch_to.window()切換到注冊頁進行注冊操作。第二個循環類似,不過這一次判斷如果handle等於sreach_handle,那么切換到百度搜索頁,然后進行搜索操作。

  在本例中所涉及的新方法如下:

  current_window_handle:獲得當前窗口句柄。

  window_handles:返回所有窗口的句柄到當前會話。

  switch_to.window():用於切換到相應的窗口,與上一節的switch_to.frame()類似,前者用於不同窗口的切換,后者用於不同表單之間的切換。

 

 2.11 警告框處理

  在WebDriver 中處理JavaScript所生成的alert、confirm以及prompt十分簡單,具體做法是使用switch_to.alert() 方法定位到 alert/confirm/prompt,然后使用text/accept/dismiss/send_keys等方法進行操作。

  • text:返回 alert/confirm/prompt中的文字信息。
  • accept():接受現有警告框
  • dismiss():解散現有警告框
  • send_keys(keysToSend):發送文本至警告框。keysToSend:將文本發送至警告框。

  百度搜索設置彈出的窗口是不能通過前端工具對其進行定位的,這個時候就可以通過switch_to.alert()方法接受這個彈窗

from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
import time

driver = webdriver.Chrome()
driver.implicitly_wait(10)
driver.get("http://www.baidu.com")

# 鼠標懸停至“設置”鏈接
link = driver.find_element_by_link_text("設置")
ActionChains(driver).move_to_element(link).perform()

# 打開搜索設置
driver.find_element_by_link_text("搜索設置").click()
time.sleep(0.3)   #  沒有這個延遲會出現問題,表現在點不到保存設置上,具體還不清楚咋回事情

# 保存設置
driver.find_element_by_link_text('保存設置').click()


# 接受警告框
driver.switch_to.alert.accept()

driver.quit()

2.12 上傳文件

  上傳文件是比較常見的Web功能之一,但WebDriver並沒有提供專門用於上傳的方法,如何實現上傳操作關鍵在於上傳文件的思路。

  一般Web頁面的上傳功能的操作需要單擊“上傳”按鈕后打開本地的Window窗口,從窗口中選擇本地文件進行上傳。而webDriver是無法操作Windows控件,所以,對於初學者來說,一般思路會卡在如何識別Window空間這個問題上。

  對於Web頁面的上傳功能實現一般由以下兩種方式。

  • 普通上傳:普通的附件上傳是將本地文件的路徑作為一個值放在input標簽中,通過form表單將這個值提交給服務器。
  • 插件上傳:一般是指基於Flash、JavaScript或Ajax等技術所實現的上傳功能。

2.12.1 send_keys 實現上傳

  對於通過input標簽實現的上傳功能,可以將其看做是一個輸入框,即通過send_keys()指定本地文件路徑的方式實現文件上傳。

from selenium import webdriver
import os,time

driver = webdriver.Chrome()
file_path = "file:///" + os.path.abspath("test.html")
driver.get(file_path)

# 定位上傳按鈕,添加本地文件
driver.find_element_by_name("file").send_keys("C:\\Users\\Administrator\\Desktop\\py\\geckodriver.log")

driver.quit()

2.12.2 Autolt 實現上傳

  autoit 目前最新版本是v3,它是一個使用類似BASIC腳本語言的免費軟件,它被設計用來進行Windows GUI(圖形用戶界面)的自動化測試。它利用模擬鍵盤按鍵,鼠標移動和窗口/控件的組合來實現自動化任務。

  具體參考《自動化測試實戰  基於PYTHON語言》一書4.12.2章節。並不推薦,因為通過Python調用的exe程序並不在Python的可控范圍內。換句話說,exe執行多長時間,執行是否出錯,Python程序都無法得知。

  pyautoit了解一下

  PyAutoGUI 也了解一下

 

 

2.13 下載文件

  https://blog.csdn.net/huilan_same/article/details/52789954

  WebDriver 允許我們設置默認的文件下載路徑,也就是說,文件會自動下載並且存放到設置的目錄中。下面以Firefox瀏覽器為例,執行文件的下載。

from selenium import webdriver
import os

fp = webdriver.FirefoxProfile()

fp.set_preference("browser.download.folderList",2)
fp.set_preference("browser.download.manager.showWhenStarting",False)
fp.set_preference("browser.download.manager.dir",os.getcwd())
fp.set_preference("browser.helperApps.neverAsk.saveToDisk","application/octet-stream")  # 下載文件的類型

driver = webdriver.Firefox(firefox_profile=fp)
driver.get("http://pypi.Python.org/pypi/selenium")
driver.find_element_by_partial_link_text("selenium-2").click()

  為了讓Firefox瀏覽器能實現文件下載,我們需要通過FirefoxProfile()對其做一些設置。

browser.download.folderList

設置成0代表下載到瀏覽器默認下載路徑,設置成2則可以保存到制定目錄

browser.download.manager.showWhenStarting

是否顯示開始:True為顯示,Flase為不顯示

browser.download.dir

用於指定所下載文件的目錄。os.getcwd()函數不需要傳遞參數,用於返回當前的目錄。

browser.helperApps.neverAsk.saveToDisk
指定要下載頁面的Content_type值,“application/octet-stream”為文件的類型

HTTP Content_type常用對照表:http://tool.oschina.net/commons

這些參數的設置可以通過在Firefox瀏覽器地址欄輸入:about:config進行設置。

  將所有設置信息在調用WebDriver的Firefox()方法時作為參數傳遞給瀏覽器。Firefox瀏覽器在下載時就根據這些設置信息將文件下載在當前腳本的目錄下。

  上面例子中的設置只針對Firefox瀏覽器,不同瀏覽器設置方法不應。通用的借助 AutoIt來操作Windows空間進行下載。

2.14 操作Cookie

  有時候我們需要驗證瀏覽器中cookie是否正確,因為基於真實cookie的測試是無法通過白盒和集成測試的。WebDriver提供了操作Cookie的相關方法,可以獲取、添加和刪除cookie信息。

  WebDriver 操作 cookie的方法:

  • get_cookies():                                   獲得所有cookie信息
  • get_cookie(name):                            返回字典的key為“name”的cookie信息
  • add_cookie(cookie_dict):                  添加cookie。“cookie_dict”指字典對象,必須有name和value值
  • delete_cookie(name,optionsString):  刪除cookie信息。“name”是要刪除的cookie的名稱,“optionString”是該cookie的選項,目前支持的選項包括“路徑”,“域”。
  • delete_all_cookies():                         刪除所有cookie信息

下面通過get-cookies()來獲取當前瀏覽器的cookie信息

from selenium import webdriver

driver = webdriver.Firefox()
driver.get("http://www.youdao.com")

# 獲取cookie信息
cookie = driver.get_cookies()

# 將獲得cookie的信息打印
print(cookie)

driver.quit()

>>>
[{'name': 'YOUDAO_MOBILE_ACCESS_TYPE', 'value': '1', 'path': '/', 'domain': '.youdao.com', 'expiry': 1562137504, 'secure': False, 'httpOnly': False}, {'name': 'DICT_UGC', 'value': 'be3af0da19b5c5e6aa4e17bd8d90b28a|', 'path': '/', 'domain': '.youdao.com', 'expiry': None, 'secure': False, 'httpOnly': False}, {'name': 'OUTFOX_SEARCH_USER_ID', 'value': '-1243283229@60.12.215.75', 'path': '/', 'domain': '.youdao.com', 'expiry': 2476681504, 'secure': False, 'httpOnly': False}, {'name': 'JSESSIONID', 'value': 'abcXbMI_bHumq7K2x9Erw', 'path': '/', 'domain': '.youdao.com', 'expiry': None, 'secure': False, 'httpOnly': False}, {'name': '___rl__test__cookies', 'value': '1530601505713', 'path': '/', 'domain': 'www.youdao.com', 'expiry': None, 'secure': False, 'httpOnly': False}, {'name': 'OUTFOX_SEARCH_USER_ID_NCOO', 'value': '1523083508.1908145', 'path': '/', 'domain': '.youdao.com', 'expiry': 1593673505, 'secure': False, 'httpOnly': False}]

  從執行結果可以看出,cookie數據是以字典的形式進行存放的。知道了cookie的存放形式,接下來我們就可以按照這種形式向瀏覽器中寫入cookie信息。

from selenium import webdriver

driver = webdriver.Firefox()
driver.get("http://www.youdao.com")

# 向cookie的name和value中添加會話信息
driver.add_cookie({"name":"key-aaaa","value":"value-bbbb"})

# 遍歷cookies中的name和value信息並打印,當然還有上面添加的信息。
for cookie in driver.get_cookies():
    print("%s -> %s" %(cookie["name"],cookie["value"]))


driver.quit()

>>>
YOUDAO_MOBILE_ACCESS_TYPE -> 1
DICT_UGC -> be3af0da19b5c5e6aa4e17bd8d90b28a|
OUTFOX_SEARCH_USER_ID -> 871140950@60.12.215.75
JSESSIONID -> abcq80IlD53H4bj3V_Erw
___rl__test__cookies -> 1530601866047
OUTFOX_SEARCH_USER_ID_NCOO -> 2091021681.247173
key-aaaa -> value-bbbb

  從執行結果可以看到,最后一條cookie信息是在腳本執行過程中通過 add_cookie() 方法添加的。通過遍歷得到所有的cookie信息,從而找到key為“name”和“value”的特定cookie的value。

  那么在什么情況下會用到cookie的操作呢?例如開發人員開發一個功能,當用戶登錄后,會將用戶的用戶名寫入瀏覽器cookie,指定的key為“username”,那么我們就可以通過 get_cookies()找到username,打印value。如果找不到username或對應的value為空,那么說明cookie沒有成功地保存到瀏覽器中。

  delete_cookie()和delete_all_cookies() 的使用也很簡單,前者通過name刪除一個特定的cookie信息,后者直接刪除瀏覽器中的所有cookies()信息。

 

2.15 調用JavaScript

  雖然WebDriver提供了操作瀏覽器的前進和后退方法,但對於瀏覽器滾動條並沒有提供相應的操作方法。在這種情況下,就可以借助JavaScript來控制瀏覽器的滾動條。WebDriver提供了 execute_script() 方法來執行JavaScript代碼。

  一般我們想到的必須使用滾動條的場景是:注冊時的法律條文的閱讀。判斷用戶是否閱讀完的標准是,滾動條是否拉到頁面底部。當然,有時候為了使操作更接近用戶行為也會使用滾動條,例如用戶要操作的元素在頁面的第二屏,一般用戶不會對看不到的元素進行操作,那么就需要先將滾動條拖到頁面的第二屏再進行操作。

  用於調整瀏覽器滾動條位置的JavaScript代碼如下:

......
<!-- window.scrollTo(左邊距,上邊距);-->
window.scrollTo(0,450)
......

  window.scrollTo() 方法用於設置瀏覽器窗口滾動條的水平和垂直位置。方法的第一個參數表示水平的左間距,第二個參數表示垂直的上邊距。其代碼如下:

from selenium import webdriver
from time import sleep

# 訪問百度
driver = webdriver.Firefox()
driver.get("http://www.baidu.com")

# 設置瀏覽器窗口大小
driver.set_window_size(600,600)

# 搜索
driver.find_element_by_id("kw").send_keys("selenium")
driver.find_element_by_id("su").click()
sleep(2)

# 通過javascript設置瀏覽器窗口的滾動條位置
js="window.scrollTo(100,450);" driver.execute_script(js)
sleep(3)

driver.quit()

  通過瀏覽器打開百度進行搜索,並且提前通過 set_window_size() 方法將瀏覽器窗口設置為固定寬高顯示,目的是讓窗口出現水平和垂直滾動條。然后通過 execute_script() 方法執行 JavaScript 代碼來移動滾動條的位置。

 

1、滾動條回到頂部:

js="var q=document.getElementById('id').scrollTop=0"
driver.execute_script(js)

或者

js="var q=document.documentElement.scrollTop=0"
driver.execute_script(js)

2、滾動條拉到底部:

js="var q=document.getElementById('id').scrollTop=10000"
driver.execute_script(js)

或者

js="var q=document.documentElement.scrollTop=10000"
driver.execute_script(js)

3、滾動條拉到指定位置(具體元素):--最常用

target = driver.find_element_by_id("id_keypair")
driver.execute_script("arguments[0].scrollIntoView();", target)

4、通過模擬鍵盤DOWN(↓)來拖動:

driver.find_element_by_id("id").send_keys(Keys.DOWN)

5、scrollTo函數

--scrollHeight 獲取對象的滾動高度。 

--scrollLeft 設置或獲取位於對象左邊界和窗口中目前可見內容的最左端之間的距離。 

--scrollTop 設置或獲取位於對象最頂端和窗口中可見內容的最頂端之間的距離。 

--scrollWidth 獲取對象的滾動寬度。

 

#滾動到底部

js = "window.scrollTo(0,document.body.scrollHeight)"
driver.execute_script(js)

#滾動到頂部

js = "window.scrollTo(0,0)"
driver.execute_script(js)

二、參考代碼

 
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @File : jss.py
# @Software: PyCharm
 
from selenium import webdriver
import time
from selenium.webdriver.common.keys import Keys
url = 'http://www.cnblogs.com/sanzangTst/'
browser = webdriver.Firefox()
browser.get(url)
time.sleep(3)
 
# 拉到底部
js="var q=document.documentElement.scrollTop=10000"
browser.execute_script(js)
 
# 回到頂部 
js="var q=document.documentElement.scrollTop=0"
browser.execute_script(js)
 
# 拖到指定位置
target = browser.find_element_by_id("homepage1_HomePageDays_DaysList_ctl05_DayList_TitleUrl_0")
browser.execute_script("arguments[0].scrollIntoView();", target)
 
#滾動到底部
js = "window.scrollTo(0,document.body.scrollHeight)"
browser.execute_script(js)
 
#滾動到頂部
js = "window.scrollTo(0,0)"
browser.execute_script(js)

 

 當然,JavaScript的作用不僅僅體現在瀏覽器滾動條的操作上,還可以用它向頁面中textarea文本框輸入內容。

......
<textarea id="id" style="width: 98%" cols="50" rows="5" class="textarea">
</textarea>
......

  雖然我們可以通過id的方式將其進行定位,但卻不能通過send_keys()向文本框中輸入文本信息。這種情況下,就需要借助JavaScript代碼完成輸入。

......
text = "input text"
js = "var sum=document.getElementById("id"); sum.value = '" + text + "';"
driver.execute_script(js)
......

  首先定義了要輸入的內容text,然后將text與JavaScript代碼通過 “+” 進行拼接。這樣做的目的是為了使輸入內容變得可自定義。最后,通過execute_script()執行JavaScript代碼。

2.16 處理HTML5的視頻播放

  目前 HTML5 技術已漸漸成為主流,主流的瀏覽器都已支持 HTML5。越來越多的應用使用了 HTML5 的元素,如canvas、video等,另外網頁存儲功能更增加了用戶的網絡體驗,使得越來越多的開發者在使用這樣的標准,所以我們也需要學習如何使用自動化技術來測試它們。

  WebDriver 支持在指定的瀏覽器上測試 HTML5,另外,我們還可以使用 JavaScript 來測試這些功能,這樣就可以在任何瀏覽器上測試HTML5了。

  大多數瀏覽器使用控件(如Flash)來播放視頻,但是,不同的瀏覽器需要使用不同的插件。HTML5定義了一個新的元素<video>,指定了一個標准的方式來嵌入電影片段。

from selenium import webdriver
from time import sleep

driver = webdriver.Firefox()
driver.get("http://videojs.com/")

video = driver.find_element_by_xpath("body/Setion[1]/div/video")

# 返回播放文件地址
url = driver.execute_script("return arguments[0].currentSrc;",video)
print(url)

# 播放視頻
print("start")
driver.execute_script("return arguments[0].play()",video)

# 播放15秒鍾
sleep(15)

# 暫停視頻
print("stop")
driver.execute_script("arguments[0].pause()",video)

driver.quit()

  JavaScript 函數有個內置的對象叫做 arguments。arguments對象包含了函數調用的參數數組,[0]表示取對象的第1個值。

  currentSrc 熟悉返回當前音頻 / 視頻的URL。如果未設置音頻 / 視頻,則返回空字符串。

  load() 、play() 、pause() 、等控制着視頻的加載、播放和暫停。

 

 2.17 窗口截圖

  自動化用例是由程序去執行的,因此有時候打印的錯誤信息並不十分明確。如果在腳本執行出錯的時候能對當前窗口截圖保存,那么通過圖片就可以非常直觀地看出出錯的原因。WebDriver提供了截圖函數 get_screenshot_as_file() 來截取當前窗口。

driver = webdriver.Firefox()
driver.get("http://www.baidu.com")

driver.find_element_by_id("kw").send_keys("selenium")
driver.find_element_by_id("su").click()
sleep(2)

# 截圖當前窗口,並制定截圖圖片的保存位置
driver.get_screenshot_as_file("D:\\pyse\\baidu_img.png")   # png格式
driver.quit()

 2.18 關閉窗口

  在前面的例子中我們一直使用 quit() 方法,其含義為退出相關的驅動程序和關閉所有窗口。除此之外,WebDriver還提供了close()方法,用來關閉當前窗口。在用例執行的過程中打開了多個窗口,我們想要關閉其中的某個窗口,這時就要用到 close() 方法進行關閉了。

 

2.19 驗證碼的處理

  對於Web應用來說,大部分的系統在用戶登錄時都要求用戶輸入驗證碼。驗證碼的類型很多,有字母數字的,有漢字的,甚至還有需要用戶輸入一道算術題的答案的。對於系統來說,使用驗證碼可以有效地防止采用機器猜測方法對口令的刺探,在一定程度上增加了安全性。

  但對測試人員來說,不管是進行性能測試還是自動化測試,都是一個比較棘手的問題。在WebDriver中並沒有提供相應的方法來處理驗證碼,這里有幾種處理驗證碼的常見方法。

  1. 去掉驗證碼

  這是最簡單的方法,對於看法人員來說,只是把驗證碼的相關代碼注釋掉即可。如果是在測試環境,這樣做可省去測試人員不少的麻煩;但如果自動化腳本是在正是環境測試,那么這種做法就給系統帶來了一定的風險。

  2. 設置萬能驗證碼

  去掉驗證碼的主要問題是安全,為了應對在線系統的安全威脅,可以在修改程序時不取消驗證碼,而是在程序中留一個“后門”,即設置一個“萬能驗證碼”。只要用戶輸入這個“萬能驗證碼”,程序就認為驗證通過,否則就判斷用戶輸入的驗證碼是否正確。

  設計萬能驗證碼的方式非常簡單,只需對用戶的輸入信息多加一個邏輯判斷。

  3. 驗證碼識別技術

  例如,可以通過Python-tesseract 來識別圖片驗證碼。Python-tesseract是光學字符識別 Tesseract OCR 引擎的Python封裝類,能夠讀取任何常規的圖片文件(JPG/GIF/PNG/TIFF等)。不過,目前市面上的驗證碼形式繁多,大多驗證碼識別技術,識別率都很難達到100%。

  4. 記錄cookie

  通過向瀏覽器中添加cookie可以繞過登錄的驗證碼,這是比較有意思的一種解決方案。例如我們在第一次登錄某網站時勾選“記住密碼”的選項,當下次再訪問該網站時自動就處於登陸狀態了。這樣自然就繞過了驗證碼問題。這個“記住密碼”功能其實就記錄在了瀏覽器的cookie中。前面已經學了通過WebDriver來操作瀏覽器的cookie,可以通過add_cookie() 方法將用戶名密碼寫入瀏覽器cookie,當再次訪問網站時,服務器將直接讀取瀏覽器的cookie進行登錄。

......
# 訪問 xx 網站
driver.get("http://www.xx.cn")

# 將用戶名密碼寫入瀏覽器 cookie
driver.add_cookie({"name":"Login_UserNumber","value":"username"})
driver.add_cookie({"name":"Login_Passwd","value":"password"})

# 再次訪問xx網站,將會自動登錄
driver.get("http://www.xx.cn/")
#......

driver.quit()

  這種方式最大的問題是如何從瀏覽器的 cookie 中找到用戶名和密碼對應的 key 值,並傳入對應的登錄信息。可以用 get_cookies()方法來獲取登錄的所有的cookie信息,從中找到用戶名和密碼的key。當然,更直接的方式是詢問開發人員。

 

 4.20 WebDriver 原理

  WebDriver 是按照 Server - Client 的經典設計模式設計的。

  Server 端就是 Remote Server,可以是任意的瀏覽器。當我們的腳本啟動瀏覽器后,該瀏覽器就是Remote Server,它的職業就是等待 Client 發送請求並作出響應。

  Client 端簡單說來就是我們的測試代碼。我們測試代碼中的一些行為,例如打開瀏覽器,轉跳到特定的 URL 等操作是以 http 請求的方式發送給被測試瀏覽器的,也就是 Remote Server。Remote Server。Remote Server 接受請求,執行相應操作,並在 Response 中返回執行狀態,返回值等信息。

  WebDriver 的工作流程:

  1. WebDriver 啟動目標瀏覽器,並綁定到指定端口。啟動的瀏覽器實例將作為 WebDriver 的 Remote Server。
  2. Client 端通過 CommandExcuter 發送 HTTPRequest 給Remote Server 的偵聽端口(通信協議:the webdriver wire protocol)
  3. Remote Server 需要依賴原生的瀏覽器組件(如IEDriverServer.exe、chromedriver.exe)來轉化瀏覽器的 native 調用。

  Python 提供了logging 模塊給運行中的應用提供了一個標准的信息輸出接口。它提供了basicConfig() 方法用於基本信息的定義。開啟 debug 模塊,就可以捕捉到客戶端向服務端發送的請求。

from selenium import webdriver
import logging

logging.basicConfig(level = logging.DEBUG)
driver = webdriver.Firefox()
driver.get("http://www.baidu.com")

driver.find_element_by_id("kw").send_keys("selenium")
driver.find_element_by_id("su").click()
driver.quit()

>>>
C:\Users\Administrator\AppData\Local\Programs\Python\Python37\python.exe C:/Users/Administrator/Desktop/py/test.py
DEBUG:selenium.webdriver.remote.remote_connection:POST http://127.0.0.1:3633/session {"capabilities": {"firstMatch": [{}], "alwaysMatch": {"browserName": "firefox", "acceptInsecureCerts": true}}, "desiredCapabilities": {"browserName": "firefox", "acceptInsecureCerts": true, "marionette": true}}
DEBUG:selenium.webdriver.remote.remote_connection:b'{"value": {"sessionId":"84ddc3fe-d7af-4d2e-a5bb-a7408100b3aa","capabilities":{"acceptInsecureCerts":true,"browserName":"firefox","browserVersion":"61.0","moz:accessibilityChecks":false,"moz:headless":false,"moz:processID":18244,"moz:profile":"C:\\\\Users\\\\Administrator\\\\AppData\\\\Local\\\\Temp\\\\rust_mozprofile.OvsTh3aeSztY","moz:useNonSpecCompliantPointerOrigin":false,"moz:webdriverClick":true,"pageLoadStrategy":"normal","platformName":"windows_nt","platformVersion":"10.0","rotatable":false,"timeouts":{"implicit":0,"pageLoad":300000,"script":30000}}}}'
DEBUG:selenium.webdriver.remote.remote_connection:Finished Request
DEBUG:selenium.webdriver.remote.remote_connection:POST http://127.0.0.1:3633/session/84ddc3fe-d7af-4d2e-a5bb-a7408100b3aa/url {"url": "http://www.baidu.com"}
DEBUG:selenium.webdriver.remote.remote_connection:b'{"value": null}'
DEBUG:selenium.webdriver.remote.remote_connection:Finished Request
DEBUG:selenium.webdriver.remote.remote_connection:POST http://127.0.0.1:3633/session/84ddc3fe-d7af-4d2e-a5bb-a7408100b3aa/element {"using": "css selector", "value": "[id=\"kw\"]"}
DEBUG:selenium.webdriver.remote.remote_connection:b'{"value":{"element-6066-11e4-a52e-4f735466cecf":"2f5d6f4b-81aa-425f-9a12-9b6b1b81c602"}}'
DEBUG:selenium.webdriver.remote.remote_connection:Finished Request
DEBUG:selenium.webdriver.remote.remote_connection:POST http://127.0.0.1:3633/session/84ddc3fe-d7af-4d2e-a5bb-a7408100b3aa/element/2f5d6f4b-81aa-425f-9a12-9b6b1b81c602/value {"text": "selenium", "value": ["s", "e", "l", "e", "n", "i", "u", "m"], "id": "2f5d6f4b-81aa-425f-9a12-9b6b1b81c602"}
DEBUG:selenium.webdriver.remote.remote_connection:b'{"value": null}'
DEBUG:selenium.webdriver.remote.remote_connection:Finished Request
DEBUG:selenium.webdriver.remote.remote_connection:POST http://127.0.0.1:3633/session/84ddc3fe-d7af-4d2e-a5bb-a7408100b3aa/element {"using": "css selector", "value": "[id=\"su\"]"}
DEBUG:selenium.webdriver.remote.remote_connection:b'{"value":{"element-6066-11e4-a52e-4f735466cecf":"d1ec924a-2bfa-4739-b3a0-e3f721319bee"}}'
DEBUG:selenium.webdriver.remote.remote_connection:Finished Request
DEBUG:selenium.webdriver.remote.remote_connection:POST http://127.0.0.1:3633/session/84ddc3fe-d7af-4d2e-a5bb-a7408100b3aa/element/d1ec924a-2bfa-4739-b3a0-e3f721319bee/click {"id": "d1ec924a-2bfa-4739-b3a0-e3f721319bee"}
DEBUG:selenium.webdriver.remote.remote_connection:b'{"value": null}'
DEBUG:selenium.webdriver.remote.remote_connection:Finished Request
DEBUG:selenium.webdriver.remote.remote_connection:DELETE http://127.0.0.1:3633/session/84ddc3fe-d7af-4d2e-a5bb-a7408100b3aa {}
DEBUG:selenium.webdriver.remote.remote_connection:b'{"value": null}'
DEBUG:selenium.webdriver.remote.remote_connection:Finished Request

Process finished with exit code 0

  basicConfig 所捕捉的log信息。不過basicConfig()開啟的debug模式只能捕捉到客戶端向服務器發送的 POST 請求,而無法獲取服務器所返回的應答信息。Selenium Server 可以獲取到更詳細的請求與應答信息。

 可參考別人的博客:Selenium-WebDriverApi接口詳解


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM