今天給大家介紹一個比較新的UI自動化測試工具-- Selenide。確實是比較新的,國內應該還沒有多少人用它。在百度和google上你只能搜到一個中文帖子簡單介紹了一下。如果你想用這個工具,不可避免的你要去閱讀英文文檔了。不過這年頭寫代碼的有幾個看不懂英文的。所以這都不是問題。
簡單介紹
Selenide的團隊自詡它是一個測試工具而不是一個測試框架。因為它只是webdriver的一個封裝,只是他們封裝了更好用的API,更穩定的控件搜索機制,更好的異常處理機制等等。底層的實現還是webdriver。所以他們認為並沒有偉大到開發了一個測試框架,而僅僅是個測試工具(很謙虛的說~)。所以一切webdriver能做的selenide都能做。webdriver 做不到的。。 嘿嘿。。你也別指望它能做到。 就像團隊人員說的:selenide is just a wrapper. 如果你不想使用selenide了,或者selenide滿足不了你的需要。你大可以通過以下方式獲取一個webdriver,直接操作webdriver的api。 順便一說,現在只有java版本。
driver = WebDriverRunner.getWebDriver();
所以如果有小伙伴擔心這個新興的工具沒有強大到滿足你的項目需求怎么辦。你們可以放心了,最壞的情況也就是你直接用webdriver去做就好了。
Quick start
廢話不多說直接搞。使用maven管理的話直接引入下面的依賴
<dependency>
<groupId>com.codeborne</groupId>
<artifactId>selenide</artifactId>
<version>3.0</version>
<scope>test</scope>
</dependency>
然后我們直接寫一個腳本,就拿官方文檔的例子吧
@Test public void userCanLoginByUsername() { open("/login"); $(By.name("username")).setValue("johny"); $("#submit").click(); $(".success-message").shouldHave(text("Hello, Johny!")); }
Ok,我們的demo結束了。你問啟動和關閉瀏覽器的代碼哪去了?答案是木有啊。前面說的selenide致力於封裝更簡單好用的API,所以這些東西它都幫你做好了。它默認使用Firefox瀏覽器。如果你要使用其他瀏覽器可以手動設置一下。如下:
Configuration.browser="chrome"; // 在環境變量中設置chrome 瀏覽器的路徑 System.setProperty("webdriver.chrome.driver", Constant.getChromeDriverPath());
然后我們就可以不用關心瀏覽器的啟動和銷毀的問題了。是不是簡化了一些了
webdriver和selenide對比
我們一定很關心為什么我們不用webdriver而用selenide呢,我們下面就來一步一步對比一下。
創建webdriver的方式:
webdriver:
DesiredCapabilities desiredCapabilities = DesiredCapabilities.htmlUnit(); desiredCapabilities.setCapability(HtmlUnitDriver.INVALIDSELECTIONERROR, true); desiredCapabilities.setCapability(HtmlUnitDriver.INVALIDXPATHERROR, false); desiredCapabilities.setJavascriptEnabled(true); WebDriver driver = new HtmlUnitDriver(desiredCapabilities);
selenide:
open("/my-application/login");
就像剛才的demo里一樣。selenide不需要這些代碼。直接在open這個api里就啟動瀏覽器了,當測試結束的時候,自然就會關閉瀏覽器
查詢頁面元素
webdriver:
WebElement customer = driver.findElement(By.id("customerContainer"));
selenide:
WebElement customer = $(By.id("customerContainer"));
這方面倒沒有什么特別簡化的,但是selenide有其他更靈活的方式搜尋控件,例如byText,byValue等等等等。這些在webdriver中除非用xpath,否則你是做不到的。下面看看例子。
selenide:
WebElement customer = $(byText("Customer profile"));
WebElement temp = $(byValue("不使用")).click();
WebElement temp1 = $(byAttribute("data-name",“test name”)).click();
再看看下面一個,如果返回多個元素,取其中一個的例子
webdriver:
driver.findElements(By.tagName("li")).get(5);
selenide:
$("li", 5);
$$("#multirowTable tr").filterBy(text("Norris"))
上面第一行代碼是所有標簽為li的元素中的第5個
第二行代碼是取特定的集合后再去搜尋text為期望值的元素。
注:
$開頭的是取一個元素
$$開頭的是取一個集合
selenide默認支持css selector,所以代碼看上去簡潔了很多。
截圖
關於失敗截圖我們都知道webdriver有個api可以做。但是selenide可以自動截圖,默認保存在build/report里面
斷言控件
webdriver:
assertEquals("Customer profile", driver.findElement(By.id("customerContainer")).getText());
selenide:
$("#customerContainer").shouldHave(text("Customer profile"));
selenide提供一系列should標簽幫我們做斷言的工作,而且有一批text()的這種選擇器來幫助我們斷言各種類型。上面的例子就是斷言控件是否有期望的text。selenide專門有一個condition包,里面有各種各樣的condition,這些condition就是should標簽的參數。上面的例子text就是一個condition。其他的還有id,value,attribute,readonly等等。
等待控件
有些時候為了增加穩定性,我們需要增加等待一個控件出現的機制。因為控件可能不是立刻出現的。或者說等待一個控件的某個屬性變成一個特定值
webdriver:
FluentWait<By> fluentWait = new FluentWait<By>(By.tagName("TEXTAREA")); fluentWait.pollingEvery(100, TimeUnit.MILLISECONDS); fluentWait.withTimeout(1000, TimeUnit.MILLISECONDS); fluentWait.until(new Predicate<By>() { public boolean apply(By by) { try { return browser.findElement(by).isDisplayed(); } catch (NoSuchElementException ex) { return false; } } }); assertEquals("John", browser.findElement(By.tagName("TEXTAREA")).getAttribute("value"));
selenide:
$("TEXTAREA").shouldHave(value("John"));
可以看到selenide還是一個should 的api搞定了。 它默認4s超時。4s內會循環check控件的value是否變成了期望值。同樣的還有text,attribute等選擇器。
正則表達式
webdriver:
WebElement element = driver.findElement(By.id("customerContainer"));
assertTrue(Pattern.compile(".*profile.*", DOTALL).matcher(element.getText()).matches());
selenide:
$("#customerContainer").should(matchText("profile"));
關於alert
webdriver:
try { Alert alert = checkAlertMessage(expectedConfirmationText); alert.accept(); } catch (UnsupportedOperationException alertIsNotSupportedInHtmlUnit) { return; } Thread.sleep(200); // sometimes it will fail
selenide:
confirm("Are you sure to delete your profile?");
或者
dismiss("Are you sure to delete your profile?");
關於log
webdriver:
WebElement element = driver.findElement(By.id("customerContainer"));
System.out.println("tag: " + element.getTag());
System.out.println("text: " + element.getText());
System.out.println("id: " + element.getAttribute("id"));
System.out.println("name: " + element.getAttribute("name"));
System.out.println("class: " + element.getAttribute("class"));
System.out.println("value: " + element.getAttribute("value"));
System.out.println("visible: " + element.isDisplayed());
selenide:
System.out.println($("#customerContainer"));
// 輸出的信息有點像這樣: "<option value=livemail.ru checked=true selected:true>@livemail.ru</option>". 把元素的所有信息顯示出來
還有很多不同,我就不一一列舉了。 大家自己感受一下
selenide的API
其實從上面的一節我們已經認識了不少的api了,但是其實還有很多很好用的api。更詳細的內容,大家看一下官方文檔:selenideAPI
題外話
UI測試框架的選型
為什么我沒用robot framework, macaca這些大家常用的測試框架?很多同學一定會比較這些框架。例如robot framework 是關鍵字驅動,接口,UI都可以。使用起來很方便,學習成本低。 macaca支持各種平台封裝的非常好等等。為啥你要用這么個冷門貨。我告訴大家其實一開始我就想用原生的webdriver了。為啥呢?因為靈活,我用這么個原始的玩意我可以自己寫好多自己的東西。那我為啥要寫那么多自己的東西呢?
答案是我們這個蛋疼的UI。來來來,我給大家show一下我們高大上的UI。
我們的主要功能就是這個DAG(有向無環圖)管理的一系列任務。所有的任務都是異步執行並都在這個DAG中管理,我們前端的框架是facebook的react框架,我們前端的開發自己開發了特定組件。也就是說我們的UI上充斥了很多自定義控件,強大的xpath都識別不了的控件。而且很多控件無ID,無name,無text,除了一個tag name 基本啥也沒有。看到圖里的連接各個node的那小圓點了么?那就是這么個3無控件。你想自動化它就只能這樣
public PlanPage linkSlot(int from, int to){ actions().dragAndDrop($(By.tagName("circle"),from), $(By.tagName("circle"),to)).perform(); return this; }
對,為了能自動化連接這倆小圓點,我只能數數了。還有任務是異步執行的對吧。你怎么等待任務成功或者失敗呢?因為執行成功或者失敗在UI上反應的就只有個對號或者錯號。而這倆圖片也特么的是那3無控件。除了image的name也幾乎啥也沒有,但是image的name還特么的是隨機生成的。。。 而且一個DAG里這么多任務,要驗證多少個對號錯號?所以沒辦法逼的我只能寫個函數等待數據庫的狀態變化和yarn(hadoop資源管理服務)上面的job的變化。例如:
public PlanPage waitForjob(AppFinalStatus status){ YarnMonitor monitor = new YarnMonitor(); App app = monitor.monitorAPPOnYarn(jobBeginTime); assertThat(app.getFinalStatus()).as("任務執行結果有誤,請查看集群log,appID為:"+app.getId()).isEqualTo(AppFinalStatus.SUCCEEDED.getValue()); return this; } public PlanPage waitRunStatus(String projectName, RunStatus expectedStatus, int timeout){ RunStatus status = WaitDBStatus.waitRun(projectName,timeout); assertThat(status).as("任務的狀態錯誤,當前任務的狀態為:"+status).isEqualTo(expectedStatus); return this; }
大家知道我的心酸了吧~~ 就連我們前端開發的leader都跟我說:實在太委屈你們了~。 這么蛋疼的UI一開始我都不想UI自動化了。但是后來覺得還是不行,因為我們是一個很重UI的產品,前端代碼很多很復雜。你不自動化靠手動得要多少人。。。。好歹我得自動化回歸個主流程吧。。由於配置復雜,傳遞給后台的json大的離譜,除了在UI上執行,根本沒有一個好的辦法生成那個json。所以接口測試的方式去調用任務也是不靠譜的。因為你搞不出那個大json來。只能硬着頭皮在UI上搞。所以碰到這種UI。robot framework和macaca都不行了。
也許有同學會問為啥控件里沒id沒name呢? 因為開發用的框架不需要那玩意,react框架不需要id和name來幫開發定位,人家有自己的一套機制。所以人家當然也就不加了。。我也就是在有些控件實在沒招的情況下才露胳膊挽袖子的跟開發說趕緊給我加個id或者name了。
關於測試框架
我總結的一條經驗就是:切記不作死就不會死,裝逼是作的表現會死的很慘很慘。有開源項目就用開源的千萬別裝逼自己寫,你可以2次封裝一下以適應你的項目但是千萬別天真的以為你寫的比人家的好。我學會rest-assured以后立馬把以前封裝的http client代碼給扔了,學會assertJ以后我立馬把以前封裝的斷言庫扔了一大半就剩下那責任鏈了,學會selenide以后我立馬把以前封裝的webdriver組件給扔了,等到testng把自己的dataloading做完善了做牛逼了沒准我寫的數據驅動組件也得扔了,記住你自己寫的代碼就是用來仍的。對於我們這些非大神的人來說什么是框架?框架是就是把各種開源工具拼到一塊然后自己寫點代碼封裝一下,沒開源框架的自己趕緊快速寫一套對付用等找到成熟的框架就加進來把以前的代碼仍進垃圾箱,這年頭誰等你吭哧癟肚的憋個把月的寫個框架出來。等你把框架寫完了沒准項目都黃了。
總結
我這人就這么啰嗦大家見諒哈。
如果對軟件測試、接口測試、自動化測試、性能測試、LR腳本開發、面試經驗交流。感興趣可以747981058,群內會有不定期的發放免費的資料鏈接,這些資料都是從各個技術網站搜集、整理出來的,如果你有好的學習資料可以私聊發我,我會注明出處之后分享給大家。

