计算网页中某个元素的位置

由于项目的需要,测试中需要对网页元素进行截图,以确保它看上去没有问题。之前我写过一篇文章介绍过一种方法,先使用 WebDriver 进行全屏截图,然后根据目标元素(DOM Element)所在的位置,再对截下来的图片进行剪裁,保留我们需要的位置即可。

那段代码一直都工作得很好,直到我知道了一个东西:iframe。iframe(普通的 frame 也是一样的,不过 frame 现在不太常见,这里只用 iframe 举例)中的内容被视为一个独立的网页,连 Window 对象也是和它的父级网页分开的。而 WebDriver 中的 WebElement.getLocation()方法只能返回这个 WebElement 和它所在的 Window 的位置关系,它的实现没什么问题,但全屏截图不仅包含了 iframe 的内容,可能也包含了它的父级页面的内容,剪裁的时候需要知道目标元素在截图中的位置。那么问题来了,挖掘机技术哪家强?如何计算一个元素相对于截图的位置?

这个问题还要分类讨论,原因是:Chrome 和 Firefox 中截图的行为是不一样的。Chrome 的截图是当前可见(viewport)的网页内容,比方说,当网页的实际大小超过 Chrome 窗口大小时,根据滚动条的位置不同,窗口中显示的内容不同,Chrome 的截图就是显示出来的内容。于是我们要计算目标元素相对于当前可见内容的位置。而 Firefox 用了一个方法,可以截到整个网页的内容,无视当前窗口大小。于是对于 Firefox 我们要计算元素的绝对位置(Absolute Position)。

获得一个元素的位置,需要用到一个方法:Element.getBoundingClientRect()。这个方法返回这个元素相对于它所处的 Windows 在当前可见内容的位置,用 top、left、right、bottom 四个值来表示。我们只关心其中的 top 和 left,至于剪裁的尺寸,我们可以通过元素本身的长和宽来得到,不需要计算。要计算目标元素对于顶级 Window的位置,我们只需要依次加上它的父级 Window的 top 和 left 即可。代码如下:

以上代码适用于 Chrome ,而在 Firefox 中,我们还需要计算元素的绝对位置。这里需要用到 Window.pageXOffset。pageXOffset,或者 scrollX,表示当前 Window 的横向滚动条滚动的位置,把这个值和上述的 left 相加,即可得到目标元素的横向绝对位置。当然,iframe 也可以特殊处理的:

由于 IE8 不支持 pageXOffset 和 scrollX,于是在 IE8 中需要一些特殊处理,即代码中标注“IE8”的部分。把这两段 Javascript 代码,替换之前文中的 WebElement.getLocation(),即可实现在 iframe 中对特定元素截图。

在WebDriver测试中处理警告对话框

虽然警告对话框(alert)在现代网页设计中已经很少用到了,但总有一些奇葩的设计师(比如Google Calendar)会想去用它。警告对话框奇葩的地方在于,它不是网页的一部分,而是一个模态窗口。警告对话框不仅会使当前网页不能点击,还会造成阻塞对的Tab访问。

在使用WebDriver测试的时候,如果碰到有警告对话框,不要着急,WebDriver已经考虑到这一点了。参考以下代码:

关键部分就是webDriver.switchTo().alert(),它会控制WebDriver切换到警告对话框,如果当前没有警告对话框,WebDriver会扔出一个NoAlertPresentException。Alert还有其它方法,参见官方文档

WebDriver测试中设定浏览器窗口的大小

WebDriver是一个网页测试的框架,它可以启动不同的浏览器来测试同一个网页,有助于开发人员检测网站在不同的浏览器中的表现。

除了不同类型和版本的浏览器之外,浏览器窗口的大小也是测试的关键因素之一,因为网页在大小不同的窗口中表现可能不一样。于是除了测试不同的浏览器之外,在运行测试之前,我们还要调整窗口的大小。

如果你的测试是用Java实现的,且比较复杂,推荐使用JUnit的Rule来实现这一操作,如下:

TestWatcher.starting()会在每个测试用例之前执行一遍。代码中把窗口大小统一设置成了1366*768,这里只是抛砖引玉一下,你也可以使用参数来控制,实现每个测试用例有不同的设置。另外这段代码中使用了Guice来注于一个WebDriver实例,对Guice不熟的同学请移步这里

在WebDriver中执行Javascript

WebDriver是一个很强大的UI测试的工具,它提供了很多和浏览器交互的API,比如getText(),click()等。

但还有一些常用的功能它没有提供,比如setAttribute。对于一个WebElement,测试中只能通过getAttribute()来得到它的某些属性,比如CSS,但不能改变它的属性。估计这是因为WebDriver最初设计的时候,动态地改动属性还是比较危险或者说不确定的操作,当然现在已经不是这样了。如果你想在测试中更改WebElement属性,比如给一个Input设置内容,可以通过下面的方法:

是的,从代码中可以看出,WebElement和String对象都可以直接做为参数传入,很方便。在Javascript中只要使用arguments[]数组就可以引用到了。

调用的时候,大致是这样的:

当然,对于设置Input的内容,也可以使用WebElement.sendKeys()方法。但显然JavascriptExecutor的功能更强大,这里只是抛砖引玉一下。

使用WebDriver对特定的WebElement截图

用过 WebDriver 的同学都知道,WebDriver 可以对浏览器中的页面进行截图。例如:

这样产生的图片是整个网页。但有时候我们并不需要整个网页,只需要某些特定的 WebElement,以避免一些干扰。

虽然 WebDriver 本身没有提供这样的 API,不过我们可以自己来,就是在全屏的截图中再进行裁剪,如下:

其中 ImageIO 和 BufferedImage 分别来自 javax.imageio 和 java.awt.image。

这种方法可以很好的削减截图的大小,也避免了一些不确定因素(比如日期时间等),是测试中保存截图的很好的方式。

注:如果测试的网页中含有 iframe,则需要使用更复杂的方式计算 WebElement 的位置,详见:计算网页中某个元素的位置