這篇文章介紹框架(frame/iframe)的操作。框架分兩種,一種是frame,另一種是iframe。
在說frame的操作之前有必要先了解下frame的一些特點,打開示例網頁frames.html,frame就長這樣:
里面包含了三個部分。我們鼠標右鍵查看源代碼,發(fā)現(xiàn)它很短,結構是這樣的:
里面有<frameset>和<frame>兩個關鍵標簽。<frameset>翻譯過來就是“框架集合”,它有一個叫cols或rows的屬性很關鍵。cols是英文columns的縮寫,代表分割列;rows就是rows,單詞本身已經夠短的了,沒法縮寫,代表分割行。一個大的框架集合里面包圍著小的框架集合或是框架。所以你看,最外邊的那個<frameset>里面還嵌套著一個小的<frameset>和一個<frame>,小的<frameset>里面還有<frame>??蚣茏裱木褪沁@種嵌套結構。接著,最外邊的<frameset>最大,包含一切,所以第一行代碼<frameset cols=”40%, 60%”>的意思就是把整個網頁分成兩列,左邊占40%,里面放一個<frame name=”frame1” src=”webelements.html” nosize />;右邊占60%,里面放一個小的<frameset>。第三行的小<frameset>里面rows=”50%, 50%”就是把右邊的部分再分成兩行,上邊占50%,下邊占50%,每個里邊放一個<frame>。
這么看起來整個框架是不是像一個擁有很多城市的國家?<frameset>就相當于國家,<frame>就是里面的城市。在古代,國家里面還可能有諸侯國,所以<frameset>里面還可以包含另外一個小的<frameset>:
再繼續(xù)看frames.html網頁,不管是大的還是小的<frameset>,滿打滿算總共有三個<frame>。每個<frame>里面都有一個叫src的屬性,指向的都是某個HTML頁面,你看webelements.html,formlogin.html和captcha.html是不是就是對應的那三個部分?frame就是通過這個屬性把HTML頁面包含在里面的。像我們之前講的那些網頁控件一樣,每個<frame>還有id或name屬性,當然這個不屬于必要屬性,可有可無。總之,代碼寫到這里才生成了我們最后看到的完整頁面。
說完結構,框架是干什么的就清楚了吧?說白了其實就是為了讓不同頁面同時顯示而設計的東東。有了剛才的知識做基礎,現(xiàn)在就可以講frame的操作啦。寫自動化測試代碼時,我們更多的是跟frame打交道。所以你不用太在乎frame在哪兒,屬于哪個<frameset>,只需要關心數(shù)量,以及有沒有id或name屬性即可。一會兒演示的時候就明白了。
我們新建一個項目吧,新建一個叫SeleniumFrame的java項目 -> 包com.test -> Test.java,添加selenium jar包,把driver聲明配置好,把frames.html網頁全屏打開:
比如現(xiàn)在我們在frames.html頁面右上部分的用戶名textbox輸入”marco”(之前講按鈕操作那節(jié)咱做過這步操作),如果按照原來的方法寫,那就應該是:
結果我們發(fā)現(xiàn)拋異常了,說找不到該元素。有人說沒毛病呀,怎么會定位不到?然后覺得定位器有毛病,換個別的,用xpath。告訴你們,別費力氣了,你用哪個也找不到。為什么呢?記得上篇講Alert的時候說的嗎?你要是想操作Alert必須先用driver連續(xù)調用switchTo().alert()把焦點轉到Alert上才行,否則操作會失敗??蚣芤彩且粯拥牡览?,要找的這個元素在frame上,不在當前的網頁里,所以也要先轉換焦點才行。
有人說了,這沒道理啊,框架不也是網頁的一部分嗎?你一個控件在框架上,那不就是在網頁上嗎?其實這個說法不完全。事實上是,框架屬于當前網頁的一部分,但又和網頁彼此獨立:
第一層級是網頁,第二層級是獨立的框架。我們還是用國家和諸侯國關系這么理解:在古代,國家是由中央政權統(tǒng)治,屬于第一等級;國家又是由一個個諸侯國組成的,它們服從中央政府,屬于第二等級。但偏偏現(xiàn)在有個諸侯國的國君想起兵造反,想搞獨立搞分裂。這時天子如果不用武力鎮(zhèn)壓能管用么?就憑下道詔書安慰一下?扯淡。所以,諸侯國力量壯大后其實是不歸附中央的,是獨立的??蚣芤惨粯?,它們組成了網頁,但又和網頁彼此獨立。這么理解就容易點了吧?
而且,正因為彼此獨立,一個網頁控件如果在框架上,那就一定會和當前網頁分隔開,直接定位是找不到的,一定要先把焦點轉換到相對應的框架上。這就好比你是諸侯國的一位小公民,想要找你得先到你所在的諸侯國才行。
搞清楚關系后,我們繼續(xù)。因為<frame>一定要包含在<frameset>中,根據這個特點基本上frame框架類型就是網頁層和一個框架層,兩層。我們把這1個網頁和n個frame統(tǒng)稱為對象。兩層還是簡單了點,一會兒我們會討論iframe框架,它可以讓框架套框架實現(xiàn)嵌套多層。
終于該轉換焦點了。和下拉列表的操作有點像,轉換焦點也可以通過三種方式實現(xiàn)。它們分別是索引,定位器,以及frame本身:
連續(xù)調用后把焦點從當前對象轉移到下一層的目標frame上。注意再看一遍,這句話很短,但有兩個重點:是從當前對象轉移!并且是移到下一層!一會兒你就能體會到為什么要強調了。一開始默認的當前對象當然就是網頁本身嘍,這個時候寫driver.switchTo().frame()會把焦點轉換到它下面的frame層。用例子來講,frames.html中網頁層下面有三個frame,driver.switchTo().frame()會把焦點轉換到它們中的一個。
先說索引(index)。索引指的是<frame>在當前對象的下一層出現(xiàn)的位置,是一個整型參數(shù),按出現(xiàn)的先后順序從0遞增。不要糾結<frameset>,過濾掉,不考慮。這三個frame按照出現(xiàn)的先后順序索引號就是下面這個樣子:
有人說你這不是按順序一個一個看的么?要是5個frame呢?10個frame呢?對,沒錯,就是一個一個數(shù)的。繼續(xù)剛才的測試,被操作的textbox屬于1號frame,所以先將焦點轉換到1號frame,然后再定位。代碼更改如下:
再運行一次,控件被成功定位并且輸入”marco”,焦點轉換后運行成功。
第二種是用id或name定位器。比如這個1號frame還有一個id屬性,等于”f0002”。我就可以用它來替換索引:
第三種方法是用frame對象本身,先看怎么寫的:
有人說你都有id或name了直接用第二種方法就好了,干嘛還那么麻煩用第三種方法呢?確實,第三種看上去是有點吹毛求疵。本來嘛,放著好好的定位器不直接用,你還聲明出來一個對象,這不是脫褲子放p么?其實這么設計是有意圖的,我們一會兒就說,現(xiàn)在先不管它。
現(xiàn)在的焦點在1號frame。那問題來了,如果我現(xiàn)在想點擊0號frame的確定按鈕呢?我可以直接寫driver.switchTo().frame(0)轉換嗎?可以么?不可以,答案是否定的!記得剛才強調什么了么?從當前對象!而且是向下一層轉!現(xiàn)在的焦點已經在1號frame上,如果直接寫driver.switchTo().frame(0)意味著把焦點從1號frame上轉換到它下面的0號frame,可人家1號frame明明下面沒有層級了呀?哪兒來的0號frame呀?結果肯定就是報錯。以前有些朋友跟我說怎么轉換不了呀?有可能就是出了這個錯誤,一定要記清。
那怎么解決呢?由于0號frame和1號frame在同一個層級上,正確的方法就是跳回到上一級,再找0號不就完了?跳回去的方法是driver.switchTo().parentFrame(),里面不加任何參數(shù)。然后轉到0號:
按照該圖用索引的方法代碼如下:
為了驗證焦點確實已經在0號frame上了,我還點了一下webelements.html的確定按鈕。
現(xiàn)在請大家思考一個場景:假設我要點擊某個frame里的某個按鈕,按鈕我用firebug輕松找到了,正當我高高興興地想要看看它屬于哪個frame時,突然一臉懵b,我發(fā)現(xiàn)當前頁面有很多很多個frame,你別問多少個,因為我也不知道??傊粫r半會兒你不可能把索引index數(shù)清楚。更缺德的是,沒有一個frame有id或name,要多慘有多慘。怎么破?這個問題肯定之前有人想到過。
打開網頁frames2.html,這個例子就和我描述的差不多。當然,這里邊f(xié)rame的個數(shù)還是能數(shù)清楚的,不過我們假裝它數(shù)不清:
遇到這類復雜問題時別慌,第一步,我們根據這個按鈕,先用firebug看一下整個網頁的結構,并確定層數(shù):
按鈕在一個frame里,同一層級有許多frame,但再往上就是網頁層了,證明它是個標準的frame二層結構。確定結構層數(shù)非常關鍵,后面講iframe時就能體會到。
第二步,怎么找出按鈕所在的frame?沒別的辦法了,我們只能一個一個地遍歷所有的frame,看哪個frame有按鈕。既然要遍歷,你就得知道整個網頁有多少個frame,要知道有多少個frame就得用driver.findElements()配合size()來找。先擺上程序:
一句一句講。所有的frame都有<frame>標簽,所以參數(shù)用的是tagName。返回的是一個List接口對象,里面裝了所有的frame,而這些frame本身是WebElement類型的,所以用泛型指明。這塊知識以前都討論過了,就不詳細說了。接著聲明了一個整型變量num_frames來存frame的數(shù)量。
接著我選擇用for循環(huán)來遍歷,初值是0,終值當然是剛求的num_frames。然后每次用List中拿出一個frame,把焦點從網頁層轉到該frame上。注意,之前有人說第三種用frame對象本身轉移焦點這種方法看起來沒什么用,現(xiàn)在知道有必要了吧?在數(shù)不清楚,沒有類似id或name這樣好用的定位器時,我們就應該想到用frame對象本身。
焦點轉移到某一個frame后,我們就要判斷它是否有按鈕。如果有,點擊;沒有,繼續(xù)轉移到下一個frame。判斷方法還是用driver.findElements()配合size()求數(shù)量,不是0就證明有。這個例子中只有一個按鈕,所以要么得1要么得0。注意,千萬別用driver.findElement()去判斷,因為找到了還好,找不到會直接拋異常。以前也強調過,某個控件只有存在的時候用findElement()才不會拋異常。返回的按鈕數(shù)量用num_btn表示,如果大于0證明找到,點擊按鈕,否則繼續(xù)。
還沒完,我們掃描完一個frame后如果它沒有按鈕,別忘了焦點還要退回到網頁層,否則下一次循環(huán)還是從當前的frame繼續(xù)往下一層找。這點剛才也講過了,唉,沒辦法,處處是陷阱。
運行程序,按鈕被點擊,彈出對話框,大功告成:
frame的操作大體就是這些,再說說iframe。iframe和frame很像,遇到的時候也很多,并且常用的操作其實是一樣的,都有driver.switchTo().frame()和driver.switchTo().parentFrame()。值得注意的是兩種框架的結構不同。第一,對于frame來說,它只能存在于frameset中,而iframe則沒關系。第二,frame不能放在網頁的<body></body>標簽中,但iframe就可以。第三,frame是可以包含iframe的,而且iframe有時候會嵌套在另一個iframe里面。比如打開網頁frames3.html我們看看:
查看一下網頁元素源代碼,又來了,又是找按鈕游戲,又是沒id沒name,而且還有iframe。沒關系,這個例子中總共有8個iframe,f1包括f3和f4,f2包括f5和f6,f5包括f7和f8。按鈕所在位置是f7,先畫圖確定層數(shù),別懶,畫出來就清楚多了。大概它的結構畫出來應該是:
因為iframe可以嵌套,所以有可能形成多層結構,畫完了圖相信你也體會到了(這就是確定層數(shù)的重要性)。結構其實還好,雖然有4層,但框架的數(shù)量不是很多。順藤摸瓜知道按鈕是沿著右半區(qū)f2,f5最后落在f7那個框架里。直接上代碼,有了之前的經驗,理解起來也就不難了:
或是用剛才List對象的方法:
f2是第二層的第二個frame,所以索引號為1,這點沒什么疑問。接下來轉f5時我用了driver.findElements()和tagName去右半邊找,位置0代表f2里的第一個frame,也就是f5。轉到第四層的方法和第三層完全一樣,最后找到按鈕。這里面我就創(chuàng)建了一個List對象iframes去存每一層的iframe,沒必要為每一層的iframe都創(chuàng)建一個新的,浪費資源。
運行結果:
如果要是回到上一層還是用driver.switchTo().parentFrame(),如果要回到第一層網頁層那需要寫三遍。selenium提供了另一種方法能直接回到網頁層:driver.switchTo().defaultContent(),一步到位,大家可以去試試。
這就是對于frame和iframe的操作,篇幅稍微有點長,不過理解起來也不難。不管是frame還是iframe,以后遇到類似沒有id/name而且框架數(shù)量龐大時我們先確定層數(shù),必要的時候畫個圖看下結構。實際情況下一張網頁也不會有很多很多個frame,全是我YY的,目的就是告訴大家分析復雜情況的方法。值得注意的是,這幾個例子里的框架都是有邊框的,現(xiàn)實中為了網頁美觀開發(fā)人員一般都把邊框去掉了,尤其是iframe。網頁是好看了,但測起來就難了,要是不看源代碼根本看不出來有沒有框架,還一個勁兒地定位控件,殊不知人家壓根不在網頁層里。所以,以后遇到一直定位不上+自信沒寫錯的情況下馬上就要反應到是不是有frame或iframe存在。
這篇文章的源代碼在SeleniumFrame和SeleniumIframe這兩個項目里。
本篇知識點及注意事項:
1.框架和提示框一樣,需要改變焦點。連續(xù)調用switchTo()和frame()會把焦點從當前對象轉移到下一層的目標frame上。
2.訪問同層frame的正確的方法就是跳回到上一級,再由上一級向下訪問。
3.在數(shù)不清楚,沒有類似id或name這樣好用的定位器時,我們就應該想到用frame對象本身。
4.遇到復雜frame/iframe問題時先確定層數(shù),必要時畫圖。
5.frame是不能放在html標簽里的,必須獨立存在。但iframe不能獨立存在,要么放在html標簽里,要么放在frame里。