本文首先探讨了编写 Selenium 脚本时的两种最简单、常用的截图方法,接着重点介绍了在难以利用普通截图方法获取到截图时,如何利用 Selenium 获取到的元素分析网页内容,进而绘制出所需要的图像的方法。
翻译验证测试 (Translation Verification Test) 是全球化测试的重要组成部分。在翻译验证测试准备阶段,翻译验证测试技术支持人员经常需要截取大量的图片给各个国家的测试人员。为了节省时间,实现自动化截图已是大势所趋。一般情况下,我们直接使用 Selenium 的 getScreenshotAs() 方法就可以满足多数情况下的需要。然而,该方法对很多悬浮框和下拉菜单的处理却不尽人意。接下来让我们一起来看看在实际应用中我们都可以选择哪些方法。
一、利用 Selenium 的 getScreenshotAs() 方法
利用 TakesScreenshot 接口提供的 getScreenshotAs() 方法捕捉屏幕,是我们在开发 Selenium 脚本时最常用的方法。getScreenshotAs() 函数可以返回 BASE64、BYTES 或 FILE 类型的数据。当指定返回类型为 BASE64 的时候 ,会返回一个 base64 编码字符串;当指定返回类型为 BYTES 的时候,会返回一个 bytes 数组; 当指定返回类型为 FILE 的时候,会将获取到的截图存放到一个临时文件中,以供复制到指定的文件。该方法可以满足最基本的需求。如清单 1 所示, captureScreen() 方法会将整个屏幕截下来,并将图片保存为 jpg 文件。
清单 1. 利用 Selenium 自带的方法截图
public static void captureScreen(WebDriver driver,String screenName) throws IOException{ //判断是否存在screenpath目录 if(!(new File(screenpath).isDirectory())){ new File(screenpath).mkdir(); } File screenShotFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE); FileUtils.copyFile(screenShotFile, new File(screenpath+screenName+".jpg")); }
Selenium 的 getScreenshotAs() 方法并不能处理一些更为复杂的问题,如对某些悬浮框、下拉菜单选项进行截图时,该方法返回的图片中并不包含悬浮框、下拉菜单部分。此时,可以选择采用 Robot 类的 createScreenCapture() 方法。
二、利用 Robot 类的 createScreenCapture() 方法
利用 Java 的 Robot 类可以完成一些与操作系统有关的底层输入,通过 Robot 类的 createScreenCapture() 方法可以截取到和 PrtSc 按键相同效果的图片。该方法简单易用,且功能强大,基本可以满足大部分需求。
清单 2. 利用 Robot 截图
public static void captureScreen(String screenName, int x, int y, int width, int height) { try { BufferedImage capture = null; Rectangle area = new Rectangle(x, y, width, height); Robot robot = new Robot(); capture = robot.createScreenCapture(area); String fn = screenpath + screenName + ".jpg"; FileOutputStream out = new FileOutputStream(fn); JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out); encoder.encode(capture); out.flush(); out.close(); } catch (Exception e) { e.printStackTrace(); } }
对于某些气泡悬浮框,如 title 属性值,需要将鼠标悬浮在元素上才可以显示出来。采用 Selenium 的 Actions 类去模拟鼠标操作,可以解决一些通过 javascript、css 控制的鼠标移动、悬浮操作 ,但是对于 title 这样的气泡悬浮框却无能为力,因为 Actions 方法只是模拟鼠标操作,而 title 属性值需要在真实的鼠标悬浮在元素上时才会显示出来。此时,可以先使用 Robot 类的 mouseMove() 方法将鼠标移到指定屏幕位置,再进行截图。
清单 3. 在 Robot 类的 mouseMove() 方法中应用 Selenium 获取到的点坐标
Robot robot = newRobot(); Point point = element.getLocation(); robot.mouseMove(point.x+55, point.y+90);
mouseMove() 中的点坐标为 Selenium 获取到的元素的屏幕坐标,而通过 getLocation() 方法获得的元素坐标为元素相对于浏览器内的 document 对象的坐标,所以需要进行微调。
由于 Robot 类操作的是一些操作系统事件,所以在某些系统(如 X-Window 系统)中如果 Robot 类访问鼠标、键盘的操作需要特定权限,那么可能会抛出 AWTException。然而,在多平台上实现截图并不是我们在本文中需要考虑的内容。利用 Robot 类的截图方法可以截取指定区域的屏幕,相比第一种方法更加灵活,功能也更加强大。
三、解析网页页面,动态生成图片
对于页面中某些气泡悬浮框(如图 1 中的“到百度首页”字符串),如 title 和 alt 属性值,虽然使用 Robot 可以截取到,但是需要调节坐标位置。在我们实际应用中,由于我们一般都是通过 Remote Desktop Connection Manager 连接多台远程虚拟机,屏幕的大小有时会发生改变,因而元素的坐标位置也经常需要调节。
图 1. 气泡悬浮框示例
![图 1. 气泡悬浮框示例](http://www.ithov.com/uploads/allimg/140128/1505142A7-0.png)
实现思路
所以对于此类情况,我们可以通过另外一种方式解决,即解析网页页面,取出 title 属性值,并根据 title 信息绘制成图片,最后将绘制好的图片添加到原来的图中并返回整张图片。从理论上讲,所有的页面元素,我们都可以通过此方法实现截图。实现方法如清单 4 所示。
清单 4. 抓取完整的屏幕截图
public static void captureFlyover(WebDriver driver,String xpath,String screenName) throws Exception{ BufferedImage img; BufferedImage img1; //img1为抓取的不包含tooltip的屏幕截图 img1 = gsscScreenshotImage(driver); //img为将悬浮框添加到img1后返回的屏幕截图 img = gsscAddTooltip(driver.findElement(By.xpath(xpath)), img1); ImageIO.write(img, "png", new File(screenpath+screenName+".jpg")); }
其中, gsscScreenshotImage() 方法调用的是第一种方式中的 getScreenshotAs() 方法。接着,我们来看一下 gsscAddTooltip() 的实现方式。我们首先在 gsscCreateTooltip() 创建了 tooltip 图像。根据传入的 Selenium 获取到的 element 对象,我们可以获取到元素的 title 和 font-family 等属性值以及元素的坐标位置。根据这些信息,我们创建出一个 img 图像。我们将图像的背景色设置为 #F5FCDE。但为了更加逼真,我们还可以通过 getCssValue() 方法获取到更多和元素有关的信息,如 background、border、padding 等等。
清单 5. 创建悬浮框
public static Point text_location; //获取title属性值,创建tooltip public static BufferedImage gsscCreateTooltip (WebElement element, int size) throws Exception{ String text = element.getAttribute("title"); String text_font = element.getCssValue("font-family"); text_location = element.getLocation(); int tooltip_h = size+7; BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); Graphics2D tooltip = img.createGraphics(); //设置悬浮框的宽度为文本的宽度加上7像素 int tooltip_w = tooltip.getFontMetrics().stringWidth(text)+7; img = new BufferedImage(tooltip_w, tooltip_h, BufferedImage.TYPE_INT_ARGB); tooltip = img.createGraphics(); //将创建图像的文字字体设置为从页面元素中获取到的文字字体 tooltip.setFont(new Font(text_font, Font.PLAIN, size)); tooltip.setPaint(Color.decode("#F5FCDE")); tooltip.fillRect(0, 0, tooltip_w, tooltip_h); tooltip.setPaint(Color.black); tooltip.draw3DRect(0, 0, tooltip_w-1, tooltip_h-1, true); //将文本绘制到图像上,drawString()并不支持文本的自动换行,当需打印的文 //本太长时,需要自己实现自动换行 tooltip.drawString(text,3,tooltip_h-5); tooltip.dispose(); return img; }
然后,我们在 gsscAddTooltip() 中进行判断,如果我们只想得到仅包含 tooltip 的图片,那么我们直接利用 tooltip 这个 BufferedImage 对象绘出图像并返回。否则将
然后,我们在 gsscAddTooltip() 中进行判断,如果我们只想得到仅包含 tooltip 的图片,那么我们直接利用 tooltip 这个 BufferedImage 对象绘出图像并返回。否则将 tooltip 绘制在 img 中指定元素的坐标位置附近(以元素中心为坐标,向 x 和 y 方向分别偏移 20px)。
清单 6. 将悬浮框添加到 Selenium 截取的图片中
public static BufferedImage gsscAddTooltip (WebElement element, BufferedImage img) throws Exception{ // 创建tooltip BufferedImage tooltip = gsscCreateTooltip(element, 12); int tooltip_w = tooltip.getWidth(); int tooltip_h = tooltip.getHeight(); BufferedImage buffImg; Graphics2D graphic; if (img != null){ // 将tooltip添加到img中 int img_w = img.getWidth(); int img_h = img.getHeight(); if (img_w >= (tooltip_w+text_location.getX())){ buffImg = new BufferedImage(img_w, img_h+tooltip_h+2, BufferedImage.TYPE_INT_ARGB); }else{ buffImg = new BufferedImage(tooltip_w+text_location.getX()+10, img_h+tooltip_h+2, BufferedImage.TYPE_INT_ARGB); } graphic = buffImg.createGraphics(); graphic.drawImage(img, 0, 0, null); graphic.drawImage(tooltip, text_location.x+20, text_location.y+20, null); }else{ buffImg = new BufferedImage(tooltip_w+3, tooltip_h+2, BufferedImage.TYPE_INT_ARGB); graphic = buffImg.createGraphics(); graphic.drawImage(tooltip, 3, 1, null); } graphic.dispose(); return buffImg; }
图 2. 利用第三种方式截到的图(仅显示部分)
![图 2. 利用第三种方式截到的图(仅显示部分)](http://www.ithov.com/uploads/allimg/140128/1505141620-1.png)
需要注意的问题
利 用该方法截取百度页面(http://www.baidu.com/s?wd=mytest&rsv_bp=0&ch=& tn=baidu&bar=&rsv_spt=3&ie=utf-8&rsv_sug3=1& inputT=7063)中的百度 logo 的 title“到百度首页”这个字符串的时候,截到的却是乱码。利用 FireBug 查看该网页的源代码才发现,原来该元素的字体被设置成了 arial。当页面文字被设置成某些西方字体如 arial 这样的字体时,调用 drawString() 方法打印中文字符会出现乱码。这时,只要将字体改为某些支持的字体就可以了,如:tooltip.setFont(newFont("宋体", Font.PLAIN, size));
图 3. 百度搜索结果页面的字体
![图 3. 百度搜索结果页面的字体](http://www.ithov.com/uploads/allimg/140128/1505143D1-2.png)
图 4. 修改字体前后截图对比(仅显示部分)
![图 4. 修改字体前后截图对比(仅显示部分)](http://www.ithov.com/uploads/allimg/140128/1505145916-3.png)
对动态生成图片方法的改进
在 captureFlyover() 函数中,页面指定元素是通过 xpath 来获取的,该实例只返回了一个页面元素。如有需要,可以根据 xpath 表达式获取到所有符合条件的页面元素,然后将这些页面元素逐一添加到截图中,从而实现在同一个页面中获取到多个悬浮框的效果。但这种方法面临的另一个问题就是,多个悬浮框有可能会重叠,需要注意每个悬浮框在截图中显示的具体位置。
清单 7. 利用 xpath 获取多个页面元素
//driver.findElements(By.xpath(xpath)会返回符合xpath表达式的所有元素,而 //driver.findElement(By.xpath(xpath)会返回符合xpath表达式的第一个元素 img = gsscAddTooltips(driver.findElements(By.xpath(xpath)), img1);
通过解析网页动态生成图片,需要编写比较多的代码,实现难度相对比较大,一般只有在需要大量的截取某同类型的图片且无法通过 getScreenshotAs() 方法实现时才会选择。
结束语
本文通过示例代码,介绍了在用 Selenium 进行自动化测试时可以选用的三种网页截图方法。三种方法,各有优劣,可以适用在不同场景。读者也可以利用 JNI,通过调用第三方组件的方式来实现网页截图。但由于我们对性能并没有太高的要求,所以在实际应用中并未采用这种做法,这里也不再多述。