Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架

这篇文章介绍框架(frame/iframe)的操作。框架分两种,一种是frame,另一种是iframe。

在说frame的操作之前有必要先了解下frame的一些特点,打开示例网页frames.html,frame就长这样:

Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第1张图片

里面包含了三个部分。我们鼠标右键查看源代码,发现它很短,结构是这样的:

Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第2张图片

里面有和两个关键标签。翻译过来就是“框架集合”,它有一个叫cols或rows的属性很关键。cols是英文columns的缩写,代表分割列;rows就是rows,单词本身已经够短的了,没法缩写,代表分割行。一个大的框架集合里面包围着小的框架集合或是框架。所以你看,最外边的那个里面还嵌套着一个小的和一个,小的里面还有。框架遵循的就是这种嵌套结构。接着,最外边的最大,包含一切,所以第一行代码的意思就是把整个网页分成两列,左边占40%,里面放一个;右边占60%,里面放一个小的。第三行的小里面rows=”50%, 50%”就是把右边的部分再分成两行,上边占50%,下边占50%,每个里边放一个。

这么看起来整个框架是不是像一个拥有很多城市的国家?就相当于国家,就是里面的城市。在古代,国家里面还可能有诸侯国,所以里面还可以包含另外一个小的:

Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第3张图片

再继续看frames.html网页,不管是大的还是小的,满打满算总共有三个。每个里面都有一个叫src的属性,指向的都是某个HTML页面,你看webelements.html,formlogin.html和captcha.html是不是就是对应的那三个部分?frame就是通过这个属性把HTML页面包含在里面的。像我们之前讲的那些网页控件一样,每个还有id或name属性,当然这个不属于必要属性,可有可无。总之,代码写到这里才生成了我们最后看到的完整页面。

说完结构,框架是干什么的就清楚了吧?说白了其实就是为了让不同页面同时显示而设计的东东。有了刚才的知识做基础,现在就可以讲frame的操作啦。写自动化测试代码时,我们更多的是跟frame打交道。所以你不用太在乎frame在哪儿,属于哪个,只需要关心数量,以及有没有id或name属性即可。一会儿演示的时候就明白了。

我们新建一个项目吧,新建一个叫SeleniumFrame的java项目 -> 包com.test -> Test.java,添加selenium jar包,把driver声明配置好,把frames.html网页全屏打开:

Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第4张图片

比如现在我们在frames.html页面右上部分的用户名textbox输入”marco”(之前讲按钮操作那节咱做过这步操作),如果按照原来的方法写,那就应该是:

Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第5张图片

结果我们发现抛异常了,说找不到该元素。有人说没毛病呀,怎么会定位不到?然后觉得定位器有毛病,换个别的,用xpath。告诉你们,别费力气了,你用哪个也找不到。为什么呢?记得上篇讲Alert的时候说的吗?你要是想操作Alert必须先用driver连续调用switchTo().alert()把焦点转到Alert上才行,否则操作会失败。框架也是一样的道理,要找的这个元素在frame上,不在当前的网页里,所以也要先转换焦点才行。

有人说了,这没道理啊,框架不也是网页的一部分吗?你一个控件在框架上,那不就是在网页上吗?其实这个说法不完全。事实上是,框架属于当前网页的一部分,但又和网页彼此独立:

Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第6张图片

第一层级是网页,第二层级是独立的框架。我们还是用国家和诸侯国关系这么理解:在古代,国家是由中央政权统治,属于第一等级;国家又是由一个个诸侯国组成的,它们服从中央政府,属于第二等级。但偏偏现在有个诸侯国的国君想起兵造反,想搞独立搞分裂。这时天子如果不用武力镇压能管用么?就凭下道诏书安慰一下?扯淡。所以,诸侯国力量壮大后其实是不归附中央的,是独立的。框架也一样,它们组成了网页,但又和网页彼此独立。这么理解就容易点了吧?

而且,正因为彼此独立,一个网页控件如果在框架上,那就一定会和当前网页分隔开,直接定位是找不到的,一定要先把焦点转换到相对应的框架上。这就好比你是诸侯国的一位小公民,想要找你得先到你所在的诸侯国才行。

搞清楚关系后,我们继续。因为一定要包含在中,根据这个特点基本上frame框架类型就是网页层和一个框架层,两层。我们把这1个网页和n个frame统称为对象。两层还是简单了点,一会儿我们会讨论iframe框架,它可以让框架套框架实现嵌套多层。

Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第7张图片

终于该转换焦点了。和下拉列表的操作有点像,转换焦点也可以通过三种方式实现。它们分别是索引,定位器,以及frame本身:

Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第8张图片

连续调用后把焦点从当前对象转移到下一层的目标frame上。注意再看一遍,这句话很短,但有两个重点:是从当前对象转移!并且是移到下一层!一会儿你就能体会到为什么要强调了。一开始默认的当前对象当然就是网页本身喽,这个时候写driver.switchTo().frame()会把焦点转换到它下面的frame层。用例子来讲,frames.html中网页层下面有三个frame,driver.switchTo().frame()会把焦点转换到它们中的一个。

先说索引(index)。索引指的是在当前对象的下一层出现的位置,是一个整型参数,按出现的先后顺序从0递增。不要纠结,过滤掉,不考虑。这三个frame按照出现的先后顺序索引号就是下面这个样子:

Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第9张图片

有人说你这不是按顺序一个一个看的么?要是5个frame呢?10个frame呢?对,没错,就是一个一个数的。继续刚才的测试,被操作的textbox属于1号frame,所以先将焦点转换到1号frame,然后再定位。代码更改如下:

Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第10张图片

再运行一次,控件被成功定位并且输入”marco”,焦点转换后运行成功。

第二种是用id或name定位器。比如这个1号frame还有一个id属性,等于”f0002”。我就可以用它来替换索引:

Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第11张图片

第三种方法是用frame对象本身,先看怎么写的:

Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第12张图片

有人说你都有id或name了直接用第二种方法就好了,干嘛还那么麻烦用第三种方法呢?确实,第三种看上去是有点吹毛求疵。本来嘛,放着好好的定位器不直接用,你还声明出来一个对象,这不是脱裤子放p么?其实这么设计是有意图的,我们一会儿就说,现在先不管它。

现在的焦点在1号frame。那问题来了,如果我现在想点击0号frame的确定按钮呢?我可以直接写driver.switchTo().frame(0)转换吗?可以么?不可以,答案是否定的!记得刚才强调什么了么?从当前对象!而且是向下一层转!现在的焦点已经在1号frame上,如果直接写driver.switchTo().frame(0)意味着把焦点从1号frame上转换到它下面的0号frame,可人家1号frame明明下面没有层级了呀?哪儿来的0号frame呀?结果肯定就是报错。以前有些朋友跟我说怎么转换不了呀?有可能就是出了这个错误,一定要记清。

那怎么解决呢?由于0号frame和1号frame在同一个层级上,正确的方法就是跳回到上一级,再找0号不就完了?跳回去的方法是driver.switchTo().parentFrame(),里面不加任何参数。然后转到0号:

Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第13张图片

按照该图用索引的方法代码如下:

Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第14张图片

为了验证焦点确实已经在0号frame上了,我还点了一下webelements.html的确定按钮。

现在请大家思考一个场景:假设我要点击某个frame里的某个按钮,按钮我用firebug轻松找到了,正当我高高兴兴地想要看看它属于哪个frame时,突然一脸懵b,我发现当前页面有很多很多个frame,你别问多少个,因为我也不知道。总之一时半会儿你不可能把索引index数清楚。更缺德的是,没有一个frame有id或name,要多惨有多惨。怎么破?这个问题肯定之前有人想到过。

打开网页frames2.html,这个例子就和我描述的差不多。当然,这里边frame的个数还是能数清楚的,不过我们假装它数不清:

Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第15张图片

遇到这类复杂问题时别慌,第一步,我们根据这个按钮,先用firebug看一下整个网页的结构,并确定层数:

Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第16张图片

按钮在一个frame里,同一层级有许多frame,但再往上就是网页层了,证明它是个标准的frame二层结构。确定结构层数非常关键,后面讲iframe时就能体会到。

第二步,怎么找出按钮所在的frame?没别的办法了,我们只能一个一个地遍历所有的frame,看哪个frame有按钮。既然要遍历,你就得知道整个网页有多少个frame,要知道有多少个frame就得用driver.findElements()配合size()来找。先摆上程序:

Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第17张图片
Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第18张图片

一句一句讲。所有的frame都有标签,所以参数用的是tagName。返回的是一个List接口对象,里面装了所有的frame,而这些frame本身是WebElement类型的,所以用泛型指明。这块知识以前都讨论过了,就不详细说了。接着声明了一个整型变量num_frames来存frame的数量。

接着我选择用for循环来遍历,初值是0,终值当然是刚求的num_frames。然后每次用List中拿出一个frame,把焦点从网页层转到该frame上。注意,之前有人说第三种用frame对象本身转移焦点这种方法看起来没什么用,现在知道有必要了吧?在数不清楚,没有类似id或name这样好用的定位器时,我们就应该想到用frame对象本身。

焦点转移到某一个frame后,我们就要判断它是否有按钮。如果有,点击;没有,继续转移到下一个frame。判断方法还是用driver.findElements()配合size()求数量,不是0就证明有。这个例子中只有一个按钮,所以要么得1要么得0。注意,千万别用driver.findElement()去判断,因为找到了还好,找不到会直接抛异常。以前也强调过,某个控件只有存在的时候用findElement()才不会抛异常。返回的按钮数量用num_btn表示,如果大于0证明找到,点击按钮,否则继续。

还没完,我们扫描完一个frame后如果它没有按钮,别忘了焦点还要退回到网页层,否则下一次循环还是从当前的frame继续往下一层找。这点刚才也讲过了,唉,没办法,处处是陷阱。

运行程序,按钮被点击,弹出对话框,大功告成:

Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第19张图片

frame的操作大体就是这些,再说说iframe。iframe和frame很像,遇到的时候也很多,并且常用的操作其实是一样的,都有driver.switchTo().frame()和driver.switchTo().parentFrame()。值得注意的是两种框架的结构不同。第一,对于frame来说,它只能存在于frameset中,而iframe则没关系。第二,frame不能放在网页的标签中,但iframe就可以。第三,frame是可以包含iframe的,而且iframe有时候会嵌套在另一个iframe里面。比如打开网页frames3.html我们看看:

Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第20张图片

查看一下网页元素源代码,又来了,又是找按钮游戏,又是没id没name,而且还有iframe。没关系,这个例子中总共有8个iframe,f1包括f3和f4,f2包括f5和f6,f5包括f7和f8。按钮所在位置是f7,先画图确定层数,别懒,画出来就清楚多了。大概它的结构画出来应该是:

Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第21张图片

因为iframe可以嵌套,所以有可能形成多层结构,画完了图相信你也体会到了(这就是确定层数的重要性)。结构其实还好,虽然有4层,但框架的数量不是很多。顺藤摸瓜知道按钮是沿着右半区f2,f5最后落在f7那个框架里。直接上代码,有了之前的经验,理解起来也就不难了:

Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第22张图片

或是用刚才List对象的方法:

Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第23张图片

f2是第二层的第二个frame,所以索引号为1,这点没什么疑问。接下来转f5时我用了driver.findElements()和tagName去右半边找,位置0代表f2里的第一个frame,也就是f5。转到第四层的方法和第三层完全一样,最后找到按钮。这里面我就创建了一个List对象iframes去存每一层的iframe,没必要为每一层的iframe都创建一个新的,浪费资源。

运行结果:

Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架_第24张图片

如果要是回到上一层还是用driver.switchTo().parentFrame(),如果要回到第一层网页层那需要写三遍。selenium提供了另一种方法能直接回到网页层:driver.switchTo().defaultContent(),一步到位,大家可以去试试。

这就是对于frame和iframe的操作,篇幅稍微有点长,不过理解起来也不难。不管是frame还是iframe,以后遇到类似没有id/name而且框架数量庞大时我们先确定层数,必要的时候画个图看下结构。实际情况下一张网页也不会有很多很多个frame,全是我YY的,目的就是告诉大家分析复杂情况的方法。值得注意的是,这几个例子里的框架都是有边框的,现实中为了网页美观开发人员一般都把边框去掉了,尤其是iframe。网页是好看了,但测起来就难了,要是不看源代码根本看不出来有没有框架,还一个劲儿地定位控件,殊不知人家压根不在网页层里。所以,以后遇到一直定位不上+自信没写错的情况下马上就要反应到是不是有frame或iframe存在。

这篇文章的源代码在SeleniumFrame和SeleniumIframe这两个项目里。

本篇知识点及注意事项:
1.框架和提示框一样,需要改变焦点。连续调用switchTo()和frame()会把焦点从当前对象转移到下一层的目标frame上。
2.访问同层frame的正确的方法就是跳回到上一级,再由上一级向下访问。
3.在数不清楚,没有类似id或name这样好用的定位器时,我们就应该想到用frame对象本身。
4.遇到复杂frame/iframe问题时先确定层数,必要时画图。
5.frame是不能放在html标签里的,必须独立存在。但iframe不能独立存在,要么放在html标签里,要么放在frame里。

你可能感兴趣的:(Selenium Web Driver自动化测试(java版)系列下半部分(19) - 元素操作 - 框架)