下面以DojoWidget和Textbox兩個(gè)類(lèi)為例講解Widget的封裝。
function DojoWidget($self) {
this.getLabel = function () {
var $widId = getAttribute($self, "widgetid")
_set($labelText, getLabelTextByFor($widId))
return $labelText
}
this.hasError = function () {
var $class = getAttribute($self, "class")
return $class.indexOf("dijitError") == -1 ? false : true
}
}
var $DojoTextbox = function Textbox($elem) {
var $self = findEnclosingWidget($elem, "dijitValidationTextBox")
DojoWidget.call(this, $self)
var $textbox = _textbox("dijitReset dijitInputInner", _in($self))
this.setValue = function ($value) {
_setValue($textbox, $value)
var $current = this.getValue()
_assertEqual($value, $current)
}
this.getValue = function () {
return _getValue($textbox)
}
this.blur = function () {
_blur($textbox)
}
}
core.sah中定義了所有的Dojo widget類(lèi)。所有的Dojo widget類(lèi)都繼承DojoWidget。DojoWidget定義了一些widget通用的函數(shù),例如getLabel和hasError。$self變量通過(guò)函數(shù)findEnclosingWidget獲得,這個(gè)變量代表了Dojo widget外層的元素。此函數(shù)通過(guò)檢查父節(jié)點(diǎn)中是否有widgetid屬性,并且檢查class屬性的值是否包含指定的標(biāo)示widget類(lèi)型的字符串(例如,DojoTextbox的類(lèi)型字符串是dijitValidationTextBox)來(lái)識(shí)別widget的外層元素。Widget的繼承通過(guò)call函數(shù)實(shí)現(xiàn),它將$self傳給DojoWidget類(lèi)的構(gòu)造器。$textbox的識(shí)別使用了_in函數(shù),這種方法保證了元素識(shí)別的準(zhǔn)確性。事實(shí)上,無(wú)論一個(gè)widget本身有多復(fù)雜,通過(guò)_in函數(shù)可以將內(nèi)部元素查找與外界隔離。大家或許注意到this.setValue函數(shù)中有個(gè)比較奇怪的地方,this.getValue()的返回值是先賦值給$current變量然后進(jìn)行斷言判斷的。為什么不寫(xiě)成“_assertEqual($value,this.getValue())”呢?這是因?yàn)槟壳癝ahi不支持這樣的語(yǔ)句,或許將來(lái)會(huì)支持。
findByLabel的實(shí)現(xiàn)
function findByLabel($labelText, $className) {
var $label = _label($labelText)
var $wid = getAttribute($label, "for")
_set($id, findIdByWID($wid))
var $div = _byId($id)
return new $className($div)
}
function findIdByWID($wid) {
var $widget = dojo.query("[widgetid='" + $wid + "']")[0]
return $widget.getAttribute("id")
}
通過(guò)元素label標(biāo)識(shí)元素的原則通過(guò)findByLabel函數(shù)實(shí)現(xiàn)。它有兩個(gè)參數(shù),第一個(gè)是label的文本內(nèi)容,第二個(gè)是目標(biāo)widget的實(shí)現(xiàn)類(lèi)。實(shí)現(xiàn)原理很簡(jiǎn)單。label元素的for屬性值是widget的widgetid的值。因此,我們通過(guò)widgetid可以找到widget元素。但事實(shí)上,從下面大家看出來(lái)我們是先利用的dojo.query找到了元素對(duì)應(yīng)的id,然后通過(guò)_byId獲得widget元素。為什么用這種迂回的方法呢?根據(jù)Sahi的文檔,理論上我們是可以通過(guò)修改concat.js文件增加widgetid查找屬性(具體參見(jiàn)http://sahi.co.in/w/tweaking-sahi-apis)可以實(shí)現(xiàn)利用_div,_table或者_(dá)span等函數(shù)直接獲得widget元素。不幸的是,當(dāng)前版本中存在的一個(gè)bug導(dǎo)致自定義屬性不能被識(shí)別。所以,目前只能先通過(guò)widgetid找id的方法迂回解決。另外,值得一提的是因?yàn)閒indIdByWID函數(shù)用到了dojo的庫(kù)函數(shù),因此它被定義在browser tag中。
數(shù)據(jù)驅(qū)動(dòng)
Sahi自帶對(duì)CSV,Excel以及數(shù)據(jù)庫(kù)訪(fǎng)問(wèn)的函數(shù)。示例代碼示范了如何使用CSV進(jìn)行數(shù)據(jù)驅(qū)動(dòng)測(cè)試。讓我們一起來(lái)看看JobAppFormTests.sah中的testSimple函數(shù)。被注釋掉的部分是一般的定義測(cè)試數(shù)據(jù)的方法。_readCSVFile函數(shù)加載testdata.csv到$data變量,它事實(shí)上一個(gè)兩維數(shù)組。_dataDrive函數(shù)能夠自動(dòng)遍歷數(shù)組數(shù)據(jù)調(diào)用fillForm函數(shù)。
function testSimple() {
/*
var $eduValue="masters"
var $nameValue="my name"
var $addressValue="Shanghai"
var $stateValue="California"
fillForm($nameValue,$eduValue,$addressValue,$stateValue)
*/
var $data = _readCSVFile("./testdata.csv")
_dataDrive(fillForm, $data)
}
testdata.csv內(nèi)容:
Tom, high school, Address1, Alaska
Mike, masters, Address2, Florida
John, PhD, Address3, Hawaii
其他
另外,Sahi提供了類(lèi)似于JUnit的測(cè)試框架。所有以test開(kāi)頭的函數(shù)都被認(rèn)為是測(cè)試用例,如果有setUp和tearDown函數(shù),它們會(huì)分別在每個(gè)測(cè)試用例運(yùn)行前后執(zhí)行。并且所有測(cè)試文件還是可以組織到一個(gè).suite文件中作為一套測(cè)試用例運(yùn)行。更詳細(xì)的介紹,請(qǐng)大家參考Sahi的官方文檔。Sahi也能支持拖放,大家可以參考示例代碼中Slider widget的實(shí)現(xiàn)。文件上傳是很多Web自動(dòng)化測(cè)試的局限,不過(guò),Sahi得益于它Proxy的架構(gòu)也實(shí)現(xiàn)了文件上傳功能。
三.結(jié)束語(yǔ)
總的來(lái)說(shuō),Sahi是一款不錯(cuò)的Web自動(dòng)化測(cè)試工具,尤其是它對(duì)元素關(guān)聯(lián)查找的支持以及頁(yè)面隱式等待的機(jī)制對(duì)Web2.0應(yīng)用的測(cè)試是很有幫助的。希望讀者閱讀完本文能有所收獲。如果,想了解更多關(guān)于Sahi的信息,請(qǐng)?jiān)L問(wèn)Sahi的官方網(wǎng)站(http://sahi.co.in/w/) 并且可以通過(guò)訪(fǎng)問(wèn)http://www.slideshare.net/narayanraman觀看Sahi的推廣演示文檔。如果對(duì)Sahi與Selenium的比較感興趣,可以訪(fǎng)問(wèn)http://blog.sahi.co.in/2010/04/sahi-vs-selenium.html 。