不管你是用哪種自動化測試的驅動框架,當咱們構建一個複雜應用程序的自動化測試的時候。都但願構建一個測試流程穩定,維護成本較低的自動化測試。可是,現實每每沒有理想豐滿。而這一篇,我會爲你們講解咱們在使用Selenium進行Web測試的時候應該如何控制咱們的測試流程,從而儘量地提升自動化測試可維護性。那麼,先看一下這一篇的內容主要涉及到的話題:html
《Selenium For C#》的系列文章寫到這裏,我以爲是時候跟你們分享一下關於自動化測試維護成本的問題了。對於自動化測試帶來的好處我就很少說了,網上對它推崇之詞猶如滔滔江水連綿不絕。這裏我想提一下構建一個自動化測試的成本是什麼?若是你的公司或是團隊想要爲本身的產品構建完整的自動化測試。那麼,我接下來要描述的這些都將會是你須要付出的成本。固然,與此同時你也會獲得自動化測試帶來的好處。git
我見過一些公司構建的自動化測試,每一輪運行會花費10個小時左右的時間。會有大量的case失敗,但其中大部分都是Case自己的緣由而非Bug引發的。這就直接致使了QA須要花費大量的時間在調試失敗的Case上。此時,咱們再想一想當初構建自動化測試的初衷,是爲了節約人力成本,讓人力成本能夠花在更加有效的地方。但上述這樣的自動化構建真是實現了咱們最初的目的嗎?因此,你所構建的自動化測試平臺的質量直接關係到這次構建的成敗。關於這個話題也不是一兩句能說明白的,若是有時間,我會寫一個關於自動化測試平臺構建的系列,羅列一下自認爲較好的自動化測試的具體實踐。在此,只是先拋出我我的的一個觀點:「若是你的自動化測試維護成本遠遠高於手動測試的成本。那麼,我想這樣的自動化測試構建是須要慎重考量的,或者說是失敗的!」。github
本系列的文章雖然只是對Selenium技術作一些講解,可是等待同步策略是每個自動化框架的設計人員都應該瞭解的。並且在具體的實踐中,因爲等待同步策略的使用不當而致使Case失敗的例子也不在少數。編程
在實際測試運行時,測試可能並非以相同的速度響應。例如,一個進度條會等待幾秒纔會100%,頁面上的某個圖表須要加載一段時間纔會顯示出來。隨着Web技術的發展,更多的延遲加載、Ajax以及RealTime技術的普遍使用。對自動化測試Case提出了更高的要求。對於接觸過自動化測試,或是參與過自動化測試實踐的同窗應該會有這樣經驗:不少自動化測試的用例在調試的時候沒有問題,可是一旦在CI集成系統中運行就會出現元素找不到的異常(多數是由於元素尚未來得及加載)。碰見這種狀況,多數會把問題歸結於環境的影響。可是,也有多是測試用例自己不夠健壯。下面我來給你們介紹一下Selenium框架爲咱們提供的等待機制,但願對各位小夥伴書寫健壯的測試用例提供一些幫助。安全
Selenium WebDriver提供了隱式的等待策略,咱們能夠設置一個超時時間。若是WebDriver沒有在DOM中找到元素(不會立刻拋出異常),它將繼續等待直到超過了設定的隱式等待時間,纔會拋出找不到元素的異常,隱式的等待同步設置很簡單,具體的設置方式以下所示:架構
1 /// <summary> 2 /// demo1 : 設置等待同步策略 3 /// </summary> 4 [Fact(DisplayName = "Cnblogs.TestFlowControl.Demo1")] 5 public void TestFlowControl_Demo1() 6 { 7 IWebDriver driver = new FirefoxDriver(); 8 // 1. 隱式的等待 同步測試 9 driver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(10)); 10 11 driver.Close(); 12 }
WebDriver的Manage().Timeouts() 返回的是一個實現了ITimeouts接口的對象。這裏咱們能夠順便了解一下該接口定義的其餘方法:app
1 // Summary: 2 // Defines the interface through which the user can define timeouts. 3 public interface ITimeouts 4 { 5 6 ITimeouts ImplicitlyWait(TimeSpan timeToWait); 7 8 ITimeouts SetPageLoadTimeout(TimeSpan timeToWait); 9 10 ITimeouts SetScriptTimeout(TimeSpan timeToWait); 11 }
所以咱們能夠更細粒度的測試程序的加載時間(雖然這些是性能測試應當關注的問題),能夠看到ITimeouts的接口定義的方法也都是自引用的(關於自引用能夠參照《Lesson 05 - Selenium For C# 之 API 下》),因此咱們能夠用這樣的方式來調用:框架
1 /// <summary> 2 /// demo1 : 設置等待同步策略 3 /// </summary> 4 [Fact(DisplayName = "Cnblogs.TestFlowControl.Demo1")] 5 public void TestFlowControl_Demo1() 6 { 7 IWebDriver driver = new FirefoxDriver(); 8 // 1. 隱式的等待 同步測試 9 driver.Manage().Timeouts() 10 .ImplicitlyWait(TimeSpan.FromSeconds(10)) 11 .SetPageLoadTimeout(TimeSpan.FromSeconds(10)) 12 .SetScriptTimeout(TimeSpan.FromSeconds(10)); 13 14 driver.Close(); 15 }
設置隱式的等待能夠解決元素加載時間致使DOM元素定位失敗的問題。但隱式的等待是全局屬性,一旦設置了隱式的等待就會影響整個運行計劃的時間。因此,更推薦的方式是顯式的等待同步策略。儘可能的使用顯式的等待,Selenium Webdriver也爲咱們提供了許多更加精準的定位方式,先看一個Demo:異步
1 /// <summary> 2 /// demo2 :設置顯示等待同步策略 3 /// </summary> 4 [Fact(DisplayName = "Cnblogs.TestFlowControl.Demo2")] 5 public void TestFlowControl_Demo2() 6 { 7 IWebDriver driver = new FirefoxDriver(); 8 //省略操做代碼.... 9 WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10)); 10 wait.Until(ExpectedConditions.ElementExists(By.Id("Object ID"))); 11 }
上述代碼是一個顯示等待的例子,Selenium WebDriver 提供了WebDriverWait的控制等待相關配置(這裏咱們設置了等待時間),然後咱們使用了對象的Until方法等待,直到某個元素存在爲止。Unitl方法的定義以下,他接受一個Func<T,TResult>類型的參數,Func類型是C#3.5以後引入的針對委託類型的簡化定義。在此,你能夠把它理解爲須要傳遞一個操做方法。而Unitl方法會循環的執行用戶傳入的方法(這裏就是等待元素出現)。直到超時或者操做方法知足下列條件之一:函數
1 public TResult Until<TResult>(Func<T, TResult> condition);
一般狀況下,通常的編寫自動化測試Case的人員是不須要直接編寫Until的Func參數的執行操做,這部份內容一般是由框架開發人員完成。另外,Selenium WebDriver 已經爲咱們提供了常見的操做函數,也就是上面Code中用到的ExpectedConditions類,它內部已經集成了大量的方法供框架消費者使用。這些方法的功能已經在命名上有了很好的體現,這裏就不一一介紹,我簡單的列舉一下當前使用的Selenium WebDriver(2.49.0)的版本ExpectedConditions包含方法:
1 // Summary: 2 // Supplies a set of common conditions that can be waited for using OpenQA.Selenium.Support.UI.WebDriverWait. 3 public sealed class ExpectedConditions 4 { 5 public static Func< IWebDriver, IAlert > AlertIsPresent(); 6 public static Func< IWebDriver, bool > AlertState(bool state); 7 public static Func< IWebDriver, IWebElement > ElementExists(By locator); 8 public static Func< IWebDriver, IWebElement > ElementIsVisible(By locator); 9 public static Func< IWebDriver, bool > ElementSelectionStateToBe(By locator, bool selected); 10 public static Func< IWebDriver, bool > ElementSelectionStateToBe(IWebElement element, bool selected); 11 public static Func< IWebDriver, IWebElement > ElementToBeClickable(By locator); 12 public static Func< IWebDriver, IWebElement > ElementToBeClickable(IWebElement element); 13 public static Func< IWebDriver, bool > ElementToBeSelected(By locator); 14 public static Func<IWebDriver, bool> ElementToBeSelected(IWebElement element); 15 public static Func< IWebDriver, bool > ElementToBeSelected(IWebElement element, bool selected); 16 public static Func< IWebDriver, IWebDriver > FrameToBeAvailableAndSwitchToIt(By locator); 17 public static Func< IWebDriver, IWebDriver > FrameToBeAvailableAndSwitchToIt(string frameLocator); 18 public static Func< IWebDriver, bool > InvisibilityOfElementLocated(By locator); 19 public static Func< IWebDriver, bool > InvisibilityOfElementWithText(By locator, string text); 20 public static Func< IWebDriver, ReadOnlyCollection <IWebElement >> PresenceOfAllElementsLocatedBy(By locator); 21 public static Func< IWebDriver, bool > StalenessOf(IWebElement element); 22 public static Func< IWebDriver, bool > TextToBePresentInElement(IWebElement element, string text); 23 public static Func< IWebDriver, bool > TextToBePresentInElementLocated(By locator, string text); 24 public static Func< IWebDriver, bool > TextToBePresentInElementValue(By locator, string text); 25 public static Func< IWebDriver, bool > TextToBePresentInElementValue(IWebElement element, string text); 26 public static Func< IWebDriver, bool > TitleContains(string title); 27 public static Func< IWebDriver, bool > TitleIs(string title); 28 public static Func< IWebDriver, bool > UrlContains(string fraction); 29 public static Func< IWebDriver, bool > UrlMatches(string regex); 30 public static Func< IWebDriver, bool > UrlToBe(string url); 31 public static Func< IWebDriver, ReadOnlyCollection <IWebElement >> VisibilityOfAllElementsLocatedBy(By locator); 32 public static Func< IWebDriver, ReadOnlyCollection <IWebElement >> VisibilityOfAllElementsLocatedBy(ReadOnlyCollection <IWebElement > elements); 33 }
最後這一部分,實際上是大多自動化測試的使用人員不會用到的部分。但對於構建測試框架的架構師而言,這一部分則是必不可少的。實際的應用場景中總會有針對本身程序特定的等待需求。好比:待測試的應用程序在全部的頁面異步加載的時候,都會出現一個等待的進度條,也就是說咱們的操做須要等待這個進度條消失以後才能進行。那麼,對於這部分功能的處理就不該該有上層的測試框架使用人員來完成,框架開發人員應當開發一個簡單的接口供上層測試人員使用。關於接口的開發規範,開發方式的討論已經超出了本系列的範圍,這裏我只是簡單的展現一下如何實現功能,在測試框架開發的相關文章中,我會詳細的描述這部分的內容。
首先,咱們看看如何作一個簡單的擴展,剛剛提到了,Func是一個委託的簡化定義,so... ... 咱們能夠用以下方式來擴展:
1 /// <summary> 2 /// demo3 : 擴展等待 3 /// </summary> 4 [Fact(DisplayName = "Cnblogs.SeleniumAPI.Demo3")] 5 public void SeleniumAPI_Demo3() 6 { 7 IWebDriver driver = new FirefoxDriver(); 8 //省略操做代碼.... 9 WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10)); 10 wait.Until<bool>( 11 delegate(IWebDriver dir) 12 { 13 var element = dir.FindElement(By.XPath(".//div[@id='divProcessBar']")); 14 return !element.Displayed; 15 } 16 ); 17 }
上面的Code,定義了匿名委託實現了以前描述的針對進度條消失的等待。可是這樣的實現方式是須要全部的測試框架使用這都瞭解委託的概念的,通常來講測試用例編寫人員都是來自QA Team的,這樣的要求會直接提升對框架使用者的技術要求,一樣這樣的寫法也違背了不少面向對象程序設計的基本原則。更推薦的寫法是單獨定義一個靜態類(這個靜態類由框架開發人員維護):
1 /// <summary> 2 /// 自定義的擴展條件 3 /// </summary> 4 public class ExpectedConditionsExtension 5 { 6 /// <summary> 7 /// 等待進度條消失 8 /// </summary> 9 /// <param name="dir">WebDriver對象</param> 10 /// <returns>操做Func對象</returns> 11 public static Func<IWebDriver, bool> ProcessBarDisappears() 12 { 13 return delegate(IWebDriver driver) 14 { 15 IWebElement element = null; 16 try 17 { 18 element = driver.FindElement(By.XPath(".//div[@id='divProcessBar']")); 19 } 20 catch (NoSuchElementException) { return true; } 21 return !element.Displayed; 22 }; 23 } 24 }
如今對於以前上層消費代碼的調用就變成了下面的樣子:
1 /// <summary> 2 /// demo4 : 擴展等待 升級版 3 /// </summary> 4 [Fact(DisplayName = "Cnblogs.TestFlowControl.Demo4")] 5 public void TestFlowControl_Demo4() 6 { 7 IWebDriver driver = new FirefoxDriver(); 8 //省略操做代碼.... 9 WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10)); 10 wait.Until(ExpectedConditionsExtension.ProcessBarDisappears()); 11 }
須要說明的是,這不是簡單的語法糖(即僅僅簡化代碼),它帶來更多的好處是下降了對框架的使用人員的技術要求。因爲這裏咱們不須要使用框架的人懂得C#的委託(固然懂了更好),因此就下降了框架入門的成本,試想一下若是你是一個測試框架的架構師。你的用戶就是大家團隊的QA。面對上述的兩種方式,我相信後一種調用方式會更容易讓他們接受和正確的使用。另外一方面,因爲作了這樣的封裝,若是開發人員修改了進度條的結構咱們的可控的(只需修改擴展類中的定位)。固然,你也能夠要求全部的QA熟練的掌握C#的各類高級語法知識。但若是是那樣的話,咱們開發測試框架的意義何在?開發測試框架的一個重要目的不就是爲了屏蔽複雜的技術實現,下降學習成本嗎? 這個思想,也將會是後續關於測試框架設計的主導思想(畢竟測試框架的用戶是QA,請不要默認他們具備高級開發級別的能力,這裏絕沒有說QA的能力不如開發,我只是想說明從設計一個測試框架的角度來說易用性是一個很重要的指標)。
《Selenium For C#》的相關文章:Click here.
說明:Demo地址:https://github.com/DemoCnblogs/Selenium