普通截图
如果只是生成当前浏览器可视范围内的截图,则 Selenium 提供了统一的 API 实现——TakeScreenshot.getScreenshotAs()
。
1
| File image = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
|
Driver 类型与强转
虽然所有 WebDriver
实现类也都实现了 TakeScreenshot
接口,但是如果是严格面向接口编程,则所有 Driver 都是通过 WebDriver
接口变量持有的。因此,通常需要强转。
值得注意的是,生成的截图只包括浏览器可视范围,换句话说,如果存在滚动条,则截取的并非完整页面。
完整页面截图
怎样截取完整页面截图呢?我们先分浏览器种类讨论。
Firefox 最简单,FirefoxDriver
自带截图的方法:
1
| File image = ((FirefoxDriver)driver).getFullPageScreenshotAs(OutputType.FILE);
|
ChromeDrvier
和 ChromiumDriver
都没有实现该方法。
但是,熟悉 DevTools 的小伙伴应该知道,我们可以通过执行“Capture full size screenshot”命令截取完整页面截图。所以,只要通过 CDP 调用该命令即可。
【扩展说明】
DevTools 中使用快捷键 Ctrl
+ Shift
+ P
执行命令。
查看 Chrome DevTools Protocol 文档(详见这里),可以看到只有一个 Page.captureScreenshot
命令——默认行为就是“普通截图”。
不过,我们可以利用这个命令进行完整页面截图。下面说说找到的两种方案。
Chrome 完整页面截图方案一
具体思路是:先将设备屏幕大小设置为页面相同大小,就可以直接截取完整页面截图了。
【扩展说明】
据查看了 Chrome 源码的小伙伴说,“Capture full size screenshot”命令底层就是使用的这种方案。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| var height = ((JavascriptExecutor) chromeDriver) .executeScript("return document.body.scrollHeight");
var deviceParams = new HashMap<String, Object>(); deviceParams.put("mobile", false);
deviceParams.put("width", 0); deviceParams.put("height", height); deviceParams.put("deviceScaleFactor", 1); chromeDriver.executeCdpCommand("Emulation.setDeviceMetricsOverride", deviceParams);
var resource = chromeDriver.executeCdpCommand("Page.captureScreenshot", new HashMap<>()); String data = (String) resource.get("data"); byte[] bytes = Base64.getDecoder().decode(data); Files.write(Paths.get("E:/full.png"), bytes);
chromeDriver.executeCdpCommand("Emulation.clearDeviceMetricsOverride", new HashMap<>());
|
Chrome 完整页面截图方案二
思路:配置截图命令的实验属性,改变其默认行为,使其可以超出视窗截图。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| Map<String, Object> rect = (Map<String, Object>) (driver.executeCdpCommand("Page.getLayoutMetrics", new HashMap<>())) .get("contentSize"); var width = rect.get("width"); var height = rect.get("height"); var clipParams = new HashMap<String, Object>(); clipParams.put("width", width); clipParams.put("height", height); clipParams.put("x", 0); clipParams.put("y", 0); clipParams.put("scale", 1); var captureParams = new HashMap<String, Object>();
captureParams.put("clip", clipParams); captureParams.put("captureBeyondViewport", true); captureParams.put("fromSurface", true); var resource = driver.executeCdpCommand("Page.captureScreenshot", captureParams);
String data = (String) resource.get("data"); byte[] bytes = Base64.getDecoder().decode(data); Files.write(Paths.get("E:/full.png"), bytes);
|
【对比】
方案一有更好的兼容性(毕竟是浏览器底层实现思路),新旧版本 Chrome 上没有发现什么差异。
方案二兼容性更差。因为使用了实验属性 captureBeyondViewport
,老版本 Chrome(如笔者用的 80 版)可能不生效(100 版下是有效的)。
其他通用思路
有没有通用方案?答案是:可能算有。
可以使用 Ashot 库进行完整页面截图,它的原理是滚动截图并拼接。
从原理上说,任意浏览器都是通用的,但是默认情况下,它只适合处理比较简单的可以滚动截屏的页面。
从效果来看,对一些比较复杂的场景处理起来并没有想像中那样完美,比如:
- 最后页非整页滚动,拼接可能存在问题
- 对于页眉页脚拼接可能会重复显示
- 固定悬浮工具栏或菜单一类元素可能会重复显示
可能通过一些自定义截取方式可以消除这些问题,但可能不会太简单,有兴趣可以自行尝试。
参考
Chrome DevTools Protocol
GitHub - ashot
ashot API doc