前言
在做web自动化的过程中,我们经常需要用到截图功能,具体是体现在用例失败的时候,截取当前页面图片,有助于问题定位,那么具体的截图功能怎么实现呢?下面就简单介绍一下吧。
一、TakesScreenshot截图
如果初始化的driver对象是WebDriver类型的,那么它是没有getScreenshotAs()方法的,如果driver是ChromeDriver类型,那么我们很明显能看到getScreenshotAs()方法的存在。
鼠标停留在ChromeDriver,通过ctrl+H打开类的继承体系,我们可以看到ChromeDriver是继承RemoteWebDriver类,查看ChromeDriver类,发现其实现了LocationContext,WebStorage,HasTouchScreen,NetworkConnection等接口,这些接口看上去和截图并没有关系。
继续查看RemoteWebDriver类,可以看到它实现了WebDriver,JavascriptExecutor,FindsById,FindsByClassName,FindsByLinkText,FindsByName,FindsByCssSelector,FindsByTagName,FindsByXPath,HasInputDevices,HasCapabilities,Interactive,TakesScreenshot等接口,而这个TakesScreenshot就是我们需要的截图功能接口的实现。
如果我们定义的driver是WebDriver类型,但是我们也想要使用截图功能,应该怎么办呢?其实是可以通过强制类型转换实现,代码如下:
WebDriver driver = new ChromeDriver();
TakesScreenshot takesScreenshot = (TakesScreenshot) driver;
此时通过takesScreenshot对象就可以调用到getScreenshotAs()方法去截图。
具体Demo实现:
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.apache.commons.io.FileUtils;
import org.testng.annotations.Test;
import java.io.File;
import java.io.IOException;
/**
* @Author: boge
* @Email: 1209879943@qq.com
*/
public class GetScreenShot {
public static void main(String[] args) throws IOException {
WebDriver driver = new ChromeDriver();
driver.get("https://www.baidu.com");
driver.manage().window().maximize();
TakesScreenshot takesScreenshot = (TakesScreenshot) driver;
// 通过File对象实现截图
File srcFile = takesScreenshot.getScreenshotAs(OutputType.FILE);
String path = "D:\\testIO" + File.separator +
String.valueOf(System.currentTimeMillis()) + ".png";
File destFile = new File(path);
FileUtils.copyFile(srcFile, destFile);
driver.quit();
}
}
二、Allure报表嵌入截图
IHookable注解作用:动态替换每一个被@Test注解标注的方法
未针对用例做处理
testng.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd" >
<suite name="测试套件">
<listeners>
<listener class-name="com.boge.listener.TestFailListener"></listener>
</listeners>
<test name="test">
<classes>
<class name="com.boge.testcases.TestDemo"/>
</classes>
</test>
</suite>
TestFailListener监听器:
package com.boge.listener;
import com.boge.base.BaseTest;
import io.qameta.allure.Attachment;
import org.testng.IHookCallBack;
import org.testng.IHookable;
import org.testng.ITestResult;
/**
* @Author: boge
* @Email: 1209879943@qq.com
*/
public class TestFailListener implements IHookable {
@Override
public void run(IHookCallBack callBack, ITestResult testResult) {
System.out.println("当前是IHookable接口的run方法执行");
}
}
TestDemo:
package com.boge.testcases;
import org.apache.log4j.Logger;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
/**
* @Author: boge
* @Email: 1209879943@qq.com
*/
public class TestDemo {
private Logger logger = Logger.getLogger(TestDemo.class);
@BeforeMethod
public void setUp() {
logger.info("execute setUp method");
}
@Test
public void test() {
ChromeDriver driver = new ChromeDriver();
String url = "https://www.baidu.com";
driver.get(url);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String currentUrl = driver.getCurrentUrl();
Assert.assertEquals(currentUrl, url);
logger.info("断言成功");
}
@AfterMethod
public void tearDown() {
logger.info("execute tearDown method");
}
}
此时执行testng.xml时,控制台打印结果如下:
[2022-04-25 21:28:56] [INFO ] [TestDemo:19] - execute setUp method
当前是IHookable接口的run方法执行
[2022-04-25 21:28:56] [INFO ] [TestDemo:39] - execute tearDown method
===============================================
测试套件
Total tests run: 1, Passes: 1, Failures: 0, Skips: 0
===============================================
Process finished with exit code 0
此时可以看到,TestDemo里面用@Test标记的用例并未执行,而是直接执行的TestFailListener里面的run方法。
针对用例做处理
testng.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd" >
<suite name="测试套件">
<listeners>
<listener class-name="com.boge.listener.TestFailListener"></listener>
</listeners>
<test name="test">
<classes>
<class name="com.boge.testcases.TestDemo"/>
</classes>
</test>
</suite>
TestFailListener监听器:
import com.boge.base.BaseTest;
import io.qameta.allure.Attachment;
import org.testng.IHookCallBack;
import org.testng.IHookable;
import org.testng.ITestResult;
/**
* @Author: boge
* @Email: 1209879943@qq.com
*/
public class TestFailListener implements IHookable {
@Override
public void run(IHookCallBack callBack, ITestResult testResult) {
System.out.println("当前是IHookable接口的run方法执行");
//一定要保证测试主体能够去正常执行
//testResult接收测试执行的结果
callBack.runTestMethod(testResult);
//监听到用例执行失败的情况
if (testResult.getThrowable() != null) {
//BaseTest.getScreenshotAsFile("D:\\testIO\\"+System.currentTimeMillis()+".png");
//生成字节数组格式的截图
byte[] screenshot = BaseTest.getScreenshotAsByte();
//嵌入到Allure报表中
saveScreenshotToAllure(screenshot);
}
}
@Attachment
public byte[] saveScreenshotToAllure(byte[] data) {
return data;
}
}
Attachment注解可以把我们需要的信息给添加到allure的附件中去
BastTest:
package com.boge.base;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* @Author: boge
* @Email: 1209879943@qq.com
*/
public class BaseTest {
public static Logger logger = Logger.getLogger(BaseTest.class);
//定义全局static driver
public static WebDriver driver;
public BaseTest() {
}
/**
* 统一浏览器封装
*
* @param browserName 指定打开浏览器名
*/
public void openBrowser(String browserName) {
if ("chrome".equalsIgnoreCase(browserName)) {
//执行打开chrome的代码
//System.setProperty("webdriver.chrome.driver", "src\\test\\resources\\chromedriver.exe");
logger.info("打开【chrome】浏览器");
driver = new ChromeDriver();
//return driver;
} else if ("firefox".equalsIgnoreCase(browserName)) {
//执行打开firefox的代码
System.setProperty("webdriver.gecko.driver", "src\\test\\resources\\geckodriver.exe");
logger.info("打开【firefox】浏览器");
driver = new FirefoxDriver();
//return driver;
} else if ("ie".equalsIgnoreCase(browserName)) {
//执行打开ie的代码
//取消IE安全设置(忽略IE的Protected Mode的设置)
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability(InternetExplorerDriver.INTRODUCE_FLAKINESS_BY_IGNORING_SECURITY_DOMAINS, true);
//忽略掉浏览器缩放设置问题
capabilities.setCapability(InternetExplorerDriver.IGNORE_ZOOM_SETTING, true);
//表示让我们脚本使用对应的驱动程序iedriver.exe
System.setProperty("webdriver.ie.driver", "src\\test\\resources\\IEDriverServer.exe");
logger.info("打开【IE】浏览器");
driver = new InternetExplorerDriver(capabilities);
//return driver;
} else {
logger.info("浏览器不支持,请确认你的浏览器名是否正确");
//return null;
}
}
/**
* 访问网址的二次封装
*
* @param url
*/
public void toUrl(String url) {
driver.get(url);
logger.info("打开【" + url + "】网址");
}
/**
* 隐式等待二次封装
*
* @param timeOut
*/
public void setImplicitlyWait(int timeOut) {
driver.manage().timeouts().implicitlyWait(timeOut, TimeUnit.SECONDS);
logger.info("设置全局隐式等待时间【" + timeOut + "】");
}
/**
* 封装等待时间,单位秒
*
* @param second
*/
public void sleep(long second) {
try {
Thread.sleep(second * 1000);
} catch (InterruptedException e) {
logger.error("等待异常");
logger.error(e);
}
logger.info("等待时间: " + second + "秒");
}
/**
* 获取当前页面的URL地址
*
* @return
*/
public String getUrl() {
String url = driver.getCurrentUrl();
logger.info("获取当前页面的URL地址【" + url + "】");
return url;
}
/**
* 访问指定url地址
* @param url
*/
public void turnToUrl(String url){
driver.navigate().to(url);
logger.info("访问指定页面的URL地址【" + url + "】");
}
/**
* 关闭浏览器
*/
public void closeBrowser() {
driver.quit();
logger.info("关闭测试浏览器");
logger.info("==================================================");
}
/**
* 最大化浏览器
*/
public void maxBrowser() {
driver.manage().window().maximize();
logger.info("浏览器最大化");
}
/**
* 刷新页面
*/
public void refreshBrowser() {
driver.navigate().refresh();
logger.info("刷新页面");
}
/**
* 页面回退
*/
public void backBrowser() {
driver.navigate().back();
logger.info("浏览器页面回退");
}
/**
* 页面前进
*/
public void forwardBrowser() {
driver.navigate().forward();
logger.info("浏览器页面前进");
}
/**
* 生成File文件截图的封装
*
* @param picPath 截图文件需要保存的路径
*/
public static void getScreenshotAsFile(String picPath) {
TakesScreenshot takesScreenshot = (TakesScreenshot) driver;
// file对象
File srcFile = takesScreenshot.getScreenshotAs(OutputType.FILE);
File descFile = new File(picPath);
try {
FileUtils.copyFile(srcFile, descFile);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 生成字节数组截图的封装
*/
public static byte[] getScreenshotAsByte() {
TakesScreenshot takesScreenshot = (TakesScreenshot) driver;
// 字节数组
byte[] screenshot = takesScreenshot.getScreenshotAs(OutputType.BYTES);
return screenshot;
}
}
TestDemo:
package com.boge.testcases;
import com.boge.base.BaseTest;
import org.apache.log4j.Logger;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
/**
* @Author: boge
* @Email: 1209879943@qq.com
*/
public class TestDemo extends BaseTest {
private Logger logger = Logger.getLogger(TestDemo.class);
@BeforeMethod
public void setUp() {
logger.info("execute setUp method");
}
@Test
public void test() {
BaseTest baseTest = new BaseTest();
baseTest.openBrowser("chrome");
String url = "https://www.baidu.com";
baseTest.toUrl(url);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String currentUrl = BaseTest.driver.getCurrentUrl();
logger.info("currentUrl: " + currentUrl);
Assert.assertEquals(currentUrl, url);
logger.info("断言成功");
}
@AfterMethod
public void tearDown() {
logger.info("execute tearDown method");
}
}
此时执行testng.xml时,控制台打印结果如下:
[2022-04-25 22:09:01] [INFO ] [TestDemo:33] - currentUrl: https://www.baidu.com/
java.lang.AssertionError:
Expected :https://www.baidu.com
Actual :https://www.baidu.com/
<Click to see difference>
at org.testng.Assert.fail(Assert.java:97)
at org.testng.Assert.assertEqualsImpl(Assert.java:136)
at org.testng.Assert.assertEquals(Assert.java:118)
at org.testng.Assert.assertEquals(Assert.java:575)
at org.testng.Assert.assertEquals(Assert.java:585)
at com.boge.testcases.TestDemo.test(TestDemo.java:34)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:133)
at org.testng.internal.TestInvoker.invokeMethod(TestInvoker.java:584)
at org.testng.internal.TestInvoker.invokeTestMethod(TestInvoker.java:172)
at org.testng.internal.MethodRunner.runInSequence(MethodRunner.java:46)
at org.testng.internal.TestInvoker$MethodInvocationAgent.invoke(TestInvoker.java:804)
at org.testng.internal.TestInvoker.invokeTestMethods(TestInvoker.java:145)
at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:146)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:128)
at java.util.ArrayList.forEach(ArrayList.java:1257)
at org.testng.TestRunner.privateRun(TestRunner.java:770)
at org.testng.TestRunner.run(TestRunner.java:591)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:402)
at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:396)
at org.testng.SuiteRunner.privateRun(SuiteRunner.java:355)
at org.testng.SuiteRunner.run(SuiteRunner.java:304)
at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:53)
at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:96)
at org.testng.TestNG.runSuitesSequentially(TestNG.java:1180)
at org.testng.TestNG.runSuitesLocally(TestNG.java:1102)
at org.testng.TestNG.runSuites(TestNG.java:1032)
at org.testng.TestNG.run(TestNG.java:1000)
at com.intellij.rt.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java:66)
at com.intellij.rt.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:109)
[2022-04-25 22:09:01] [INFO ] [TestDemo:40] - execute tearDown method
===============================================
Default Suite
Total tests run: 1, Passes: 0, Failures: 1, Skips: 0
===============================================
Process finished with exit code 0
在编辑器的Terminal下执行mvn clean test,可以运行用例,用例执行完成后,执行mvn io.qameta.allure:allure-maven:serve命令,可以生成Allure报告,报告里面可以看到用例失败的截图信息