前言

  在做web自动化的过程中,我们经常需要用到截图功能,具体是体现在用例失败的时候,截取当前页面图片,有助于问题定位,那么具体的截图功能怎么实现呢?下面就简单介绍一下吧。

一、TakesScreenshot截图

  如果初始化的driver对象是WebDriver类型的,那么它是没有getScreenshotAs()方法的,如果driver是ChromeDriver类型,那么我们很明显能看到getScreenshotAs()方法的存在。

java 截图网页 web截图_System

java 截图网页 web截图_xml_02

  鼠标停留在ChromeDriver,通过ctrl+H打开类的继承体系,我们可以看到ChromeDriver是继承RemoteWebDriver类,查看ChromeDriver类,发现其实现了LocationContext,WebStorage,HasTouchScreen,NetworkConnection等接口,这些接口看上去和截图并没有关系。

java 截图网页 web截图_xml_03

  继续查看RemoteWebDriver类,可以看到它实现了WebDriver,JavascriptExecutor,FindsById,FindsByClassName,FindsByLinkText,FindsByName,FindsByCssSelector,FindsByTagName,FindsByXPath,HasInputDevices,HasCapabilities,Interactive,TakesScreenshot等接口,而这个TakesScreenshot就是我们需要的截图功能接口的实现。

java 截图网页 web截图_System_04

  如果我们定义的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报告,报告里面可以看到用例失败的截图信息

java 截图网页 web截图_System_05