背景:项目需要自动报告生成,其中主要的复杂点及是报表的生成及截图写入doc文档,重点在报表的生成和截图
于是引入了echarts(别人封装好的组件)+seleniumhq(自动化测试工具)
引入POM依赖
<!-- https://mvnrepository.com/artifact/com.codeborne/phantomjsdriver -->
<dependency>
<groupId>com.codeborne</groupId>
<artifactId>phantomjsdriver</artifactId>
<version>1.4.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>3.141.59</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-chrome-driver -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-chrome-driver</artifactId>
<version>3.141.59</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-firefox-driver -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-firefox-driver</artifactId>
<version>3.141.59</version>
</dependency>
<dependency>
<groupId>com.github.abel533</groupId>
<artifactId>ECharts</artifactId>
<version>3.0.0.5</version>
<exclusions>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
DriverConfig
package com.milla.report.config;
import com.milla.report.constant.ReportConstant;
import com.milla.report.enumeration.DriverEnum;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.phantomjs.PhantomJSDriver;
import org.openqa.selenium.phantomjs.PhantomJSDriverService;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.logging.Level;
/**
* @Package: com.aimsphm.nuclear.report.config
* @Description: <浏览器驱动配置类>
* @Author: MILLA
* @CreateDate: 2020/4/26 13:53
* @UpdateUser: MILLA
* @UpdateDate: 2020/4/26 13:53
* @UpdateRemark: <>
* @Version: 1.0
*/
@Configuration
public class WebDriverConfig {
@Bean("phantomJSDriver")
public WebDriver phantomJSDriver() throws IOException {
// //设置必要参数
DesiredCapabilities options = new DesiredCapabilities();
//ssl证书支持
options.setCapability("acceptSslCerts", true);
//截屏支持
options.setCapability("takesScreenshot", true);
//css搜索支持
options.setCapability("cssSelectorsEnabled", true);
//js支持
options.setJavascriptEnabled(true);
//驱动支持
initPhantomJSEnvironment(options);
//创建无界面浏览器对象
PhantomJSDriver driver = new PhantomJSDriver(options);
// driver.setLogLevel(Level.OFF);//关闭日志
driver.manage().window().maximize();
driver.setLogLevel(Level.ALL);
return driver;
}
// @Bean("chromeDriver")//目前linux执行存在问题
public WebDriver chromeDriver() throws IOException {
initLoadByOsName(DriverEnum.ChromeDriver);
ChromeOptions options = new ChromeOptions();
//设置 chrome 的无头模式
options.setHeadless(Boolean.TRUE);
//启动一个 chrome 实例
return new ChromeDriver(options);
}
// @Bean("firefoxDriver")//目前linux执行存在问题
public WebDriver firefoxDriver() throws IOException {
initLoadByOsName(DriverEnum.FirefoxDriver);
FirefoxOptions options = new FirefoxOptions();
//设置 chrome 的无头模式
options.setHeadless(Boolean.TRUE);
//启动一个 chrome 实例
return new FirefoxDriver(options);
}
/**
* 根据系统名称进行初始化
*
* @param driver
* @throws IOException
*/
private void initLoadByOsName(DriverEnum driver) throws IOException {
ClassLoader classLoader = WebDriverConfig.class.getClassLoader();
//获取操作系统的名字
String osName = System.getProperty(ReportConstant.SYSTEM_CONSTANT_OS_NAME, ReportConstant.BLANK);
if (osName.startsWith(ReportConstant.OS_NAME_PRE_MAC)) {//苹果的打开方式
File file = decompressionDriver2TempPath(classLoader, driver.getDriverNameMac(), false);
System.setProperty(driver.getBinPath(), file.getAbsolutePath());
} else if (osName.startsWith(ReportConstant.OS_NAME_PRE_WINDOWS)) {//windows的打开方式
File file = decompressionDriver2TempPath(classLoader, driver.getDriverNameWin(), false);
System.setProperty(driver.getBinPath(), file.getAbsolutePath());
} else {//unix,linux
File file = decompressionDriver2TempPath(classLoader, driver.getDriverNameLinux(), true);
System.setProperty(driver.getBinPath(), file.getAbsolutePath());
}
}
/**
* 解压文件到指定的工作路径
*
* @param classLoader 类加载器
* @param driverName 需要运行的文件名称
* @param isCmd 是否将文件设置程可执行文件
* @return
* @throws IOException
*/
private File decompressionDriver2TempPath(ClassLoader classLoader, String driverName, boolean isCmd) throws IOException {
//获取临时目录
URL resource = classLoader.getResource(ReportConstant.PROJECT_DRIVER_ROOT_DIR + driverName);
InputStream inputStream = resource.openStream();
File file = new File(ReportConstant.SYSTEM_CONSTANT_OS_TEMP_DIR + File.separator + driverName);
if (!file.exists()) {
FileUtils.copyInputStreamToFile(inputStream, file);
if (isCmd) {//将文件变成可执行状态
Runtime r = Runtime.getRuntime();
r.exec(ReportConstant.LINUX_EXECUTABLE_CMD_PRE + file.getAbsolutePath());
}
}
return file;
}
/**
* 初始化运行环境
*
* @param options 配置项
* @throws IOException
*/
private void initPhantomJSEnvironment(DesiredCapabilities options) throws IOException {
ClassLoader classLoader = WebDriverConfig.class.getClassLoader();
String phantomJSPath;
//获取操作系统的名字
String osName = System.getProperty(ReportConstant.SYSTEM_CONSTANT_OS_NAME, ReportConstant.BLANK);
if (osName.startsWith(ReportConstant.OS_NAME_PRE_MAC)) {
File file = decompressionDriver2TempPath(classLoader, DriverEnum.PhantomJSDriver.getDriverNameMac(), false);
phantomJSPath = file.getAbsolutePath();
} else if (osName.startsWith(ReportConstant.OS_NAME_PRE_WINDOWS)) {//windows的打开方式
File file = decompressionDriver2TempPath(classLoader, DriverEnum.PhantomJSDriver.getDriverNameWin(), false);
phantomJSPath = file.getAbsolutePath();
} else {//unix,linux
File file = decompressionDriver2TempPath(classLoader, DriverEnum.PhantomJSDriver.getDriverNameLinux(), true);
phantomJSPath = file.getAbsolutePath();
}
options.setCapability(PhantomJSDriverService.PHANTOMJS_EXECUTABLE_PATH_PROPERTY, phantomJSPath);
}
}
PS:Driver不用每次都生成,可以做成单例的,这样生成图片的速率会大大提升 ,如果是并发比较大的话可以做一个Driver的线程池来进行图片的生成(下次再实现吧)
常量类
package com.milla.report.constant;
import java.io.File;
/**
* @Package: com.aimsphm.nuclear.report.constant
* @Description: <常量类>
* @Author: MILLA
* @CreateDate: 2020/4/27 18:09
* @UpdateUser: MILLA
* @UpdateDate: 2020/4/27 18:09
* @UpdateRemark: <>
* @Version: 1.0
*/
public class ReportConstant {
/**
* Window系统前缀
*/
public static final String OS_NAME_PRE_WINDOWS = "Windows";
/**
* Mac系统前缀
*/
public static final String OS_NAME_PRE_MAC = "Mac OS";
/**
* Linux系统前缀
*/
public static final String OS_NAME_PRE_LINUX = "Linux";
/**
* 变量中系统的key
*/
public static final String SYSTEM_CONSTANT_OS_NAME = "os.name";
/**
* 驱动运行临时目录
*/
public static final String SYSTEM_CONSTANT_OS_TEMP_DIR = File.separator + "usr" + File.separator + "share" + File.separator + "locale";
/**
* 驱动在项目中的根路径
*/
public static final String PROJECT_DRIVER_ROOT_DIR = File.separator + "driver" + File.separator;
/**
* linux将文件变成可执行文件命令前缀
*/
public static final String LINUX_EXECUTABLE_CMD_PRE = "chmod +x ";
/**
* 空字符串
*/
public static final String BLANK = "";
/**
* 本地浏览器打开文件前缀
*/
public static final String BROWSER_LOCAL_OPEN_PRE = "file:///";
/**
*echarts 图片tag名称
*/
public static final String ECHARTS_CANVAS = "canvas";
}
枚举类
package com.milla.report.enumeration;
/**
* @Package: com.aimsphm.nuclear.report.enumeration
* @Description: <驱动枚举类[可设置多版本]>
* @Author: MILLA
* @CreateDate: 2020/4/27 18:15
* @UpdateUser: MILLA
* @UpdateDate: 2020/4/27 18:15
* @UpdateRemark: <>
* @Version: 1.0
*/
public enum DriverEnum {
ChromeDriver("webdriver.chrome.driver", "chromedriver-win32.exe", "chromedriver-mac", "chromedriver-linux64"),//谷歌驱动
FirefoxDriver("webdriver.gecko.driver", "geckodriver-win64.exe", "geckodriver-mac", "geckodriver-linux64"),//火狐
PhantomJSDriver("phantomjs.binary.path", "phantomjs-win.exe", "phantomjs-mac", "phantomjs-linux");
DriverEnum(String binPath, String driverNameWin, String driverNameMac, String driverNameLinux) {
this.binPath = binPath;
this.driverNameWin = driverNameWin;
this.driverNameMac = driverNameMac;
this.driverNameLinux = driverNameLinux;
}
//驱动名称
private String binPath;
//window名称
private String driverNameWin;
//mac 名称
private String driverNameMac;
//linux名称
private String driverNameLinux;
public String getBinPath() {
return binPath;
}
public String getDriverNameWin() {
return driverNameWin;
}
public String getDriverNameMac() {
return driverNameMac;
}
public String getDriverNameLinux() {
return driverNameLinux;
}
}
ps: 可以分不同的系统,分不同的浏览器版本,其中phantomjs是不需要浏览器支持的,可无浏览器操作截图
截图工具类
package com.milla.report.util;
import com.milla.report.constant.ReportConstant;
import org.openqa.selenium.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
* @Package: com.aimsphm.nuclear.report.util
* @Description: <截图工具类>
* @Author: MILLA
* @CreateDate: 2020/4/28 9:32
* @UpdateUser: MILLA
* @UpdateDate: 2020/4/28 9:32
* @UpdateRemark: <>
* @Version: 1.0
*/
@Component
public class ScreenshotUtils {
@Autowired
@Qualifier("phantomJSDriver")
private WebDriver driver;
/**
* 直接通过driver截图 不延时
*
* @param htmlPath html文件路径
* @param outputType 输出类型
* @param <X> 具体类型
* @return
* @throws WebDriverException
*/
public <X> X getScreenshotAsByDriver(String htmlPath, OutputType<X> outputType) throws WebDriverException, InterruptedException {
driver.get(ReportConstant.BROWSER_LOCAL_OPEN_PRE + htmlPath);
return getScreenshotAs(htmlPath, outputType, null);
}
/**
* 直接通过driver截图 延时
*
* @param htmlPath html文件路径
* @param outputType 输出类型
* @param sleepTime 延时时间
* @param <X> 具体类型
* @return
* @throws WebDriverException
* @throws InterruptedException
*/
public <X> X getScreenshotAsByDriver(String htmlPath, OutputType<X> outputType, Long sleepTime) throws WebDriverException, InterruptedException {
//是否有html
if (Objects.nonNull(htmlPath) && htmlPath.length() > 0) {
driver.get(ReportConstant.BROWSER_LOCAL_OPEN_PRE + htmlPath);
}
//是否延时处理
if (Objects.nonNull(sleepTime)) {
Thread.sleep(sleepTime);
}
return ((TakesScreenshot) driver).getScreenshotAs(outputType);
}
/**
* @param htmlPath html文件路径
* @param outputType 输出文件类型
* @param sleepTime 延时时间
* @param <X> 具体类型
* @return
* @throws WebDriverException
* @throws InterruptedException
*/
public <X> X getScreenshotAs(String htmlPath, OutputType<X> outputType, Long sleepTime) throws WebDriverException, InterruptedException {
return getScreenshotAs(htmlPath, null, outputType, sleepTime);
}
/**
* @param htmlPath html文件路径
* @param tagName html中tag名称
* @param outputType 输出类型
* @param sleepTime 延时时间
* @param <X> 具体类型
* @return
* @throws WebDriverException
* @throws InterruptedException
*/
public <X> X getScreenshotAs(String htmlPath, String tagName, OutputType<X> outputType, Long sleepTime) throws WebDriverException, InterruptedException {
//是否有html
if (Objects.nonNull(htmlPath) && htmlPath.length() > 0) {
driver.get(ReportConstant.BROWSER_LOCAL_OPEN_PRE + htmlPath);
}
//是否延时处理
if (Objects.nonNull(sleepTime)) {
Thread.sleep(sleepTime);
}
//如果有tagName
if (Objects.nonNull(tagName) && tagName.length() > 0) {
return getScreenshotAs(outputType, tagName);
}
return getScreenshotAs(outputType, ReportConstant.ECHARTS_CANVAS);
}
/**
* 默认tag不延迟
*
* @param htmlPath html文件路径
* @param outputType 输出类型
* @param <X> 具体类型
* @return
* @throws WebDriverException
* @throws InterruptedException
*/
public <X> X getScreenshotAs(String htmlPath, OutputType<X> outputType) throws WebDriverException, InterruptedException {
return getScreenshotAs(htmlPath, outputType, null);
}
/**
* @param outputType 输出类型
* @param tagName html中元素名称
* @param <X> 具体类型
* @return
* @throws WebDriverException
*/
public <X> X getScreenshotAs(OutputType<X> outputType, String tagName) throws WebDriverException {
//tag名称为空直接采用driver的截图方式
if (Objects.isNull(tagName) && tagName.length() == 0) {
return ((TakesScreenshot) driver).getScreenshotAs(outputType);
}
WebElement element;
try {
//获取不到指定tag的话使用driver进行截图
element = driver.findElement(By.tagName(tagName));
} catch (Exception e) {
return ((TakesScreenshot) driver).getScreenshotAs(outputType);
}
WebDriver.Window window = driver.manage().window();
Dimension size = element.getSize();
window.setSize(new Dimension(size.width, size.getHeight()));
return element.getScreenshotAs(outputType);
}
}
ps:在截图的时候需要对需要截图的tag进行大小设置,同时浏览器渲染的时候需要时间,这里设置了手动的延时时间,也可采用selenium中延时的API,这里就比较暴力的直接采用JavaApi实现了。
生成Html文件
public void scatter() throws IOException, InterruptedException {
long currentTimeMillis = System.currentTimeMillis();
GsonOption option = new GsonOption();
//地址:http://echarts.baidu.com/doc/example/pie6.html
option.tooltip(new Tooltip()
.trigger(Trigger.axis)
.showDelay(0)
.axisPointer(new AxisPointer().type(PointerType.cross)
.lineStyle(new LineStyle()
.type(LineType.dashed).width(1))));
option.legend("scatter1");
option.toolbox().show(true).feature(Tool.mark, Tool.dataZoom, Tool.dataView, Tool.restore, Tool.saveAsImage);
ValueAxis valueAxis = new ValueAxis().power(1).splitNumber(4).scale(true);
option.xAxis(valueAxis);
option.yAxis(valueAxis);
//注:这里的结果是一种圆形一种方形,是因为默认不设置形状时,会循环形状数组
option.series(
new Scatter("scatter1").symbolSize("20").data(randomDataArray())
);
option.view();
String exportToHtml = option.exportToHtml("/tmp/echarts/", "scatter2.html");
//这里是测试,真实项目中需要得到文件流或二进制文件
File srcFile = screenshot.getScreenshotAs(exportToHtml, OutputType.FILE, 300L);
FileUtils.copyFile(srcFile, new File("/tmp/echarts/image" + System.currentTimeMillis() + ".png"));
System.out.println("共计用时:" + (System.currentTimeMillis() - currentTimeMillis));
}
PS:此处是为测试,在项目中使用的是文件流的方式
重要的模板 (template)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Html2Image</title>
<style>*{ margin:0} html,body{ height:50%} .wrapper{ min-height:100%; height:auto !important; height:100%; margin:0 auto -155px} .footer,.push{ height:155px} table.gridtable{ font-family:verdana,arial,sans-serif; font-size:11px; color:#333; border-width:1px; border-color:#666; border-collapse:collapse; margin:5px auto} table.gridtable th{ border-width:1px; padding:8px; border-style:solid; border-color:#666; background-color:#dedede} table.gridtable td{ border-width:1px; padding:8px; border-style:solid; border-color:#666; background-color:#fff} .middle{ text-align:center; margin:0 auto; width:90%; height:auto} .info{ font-size:12px; text-align:center; line-height:20px; padding:40px} .info a{ margin:0 10px; text-decoration:none; color:green}</style>
</head>
<body >
<div class="wrapper">
<div class="middle">
<!-- <h1 style="padding: 70px 0 20px;">ECharts效果</h1> -->
<!-- 为ECharts准备一个具备大小(宽高)的Dom -->
<div id="main" style="height:400px"></div>
</div>
</div>
</body>
<!-- ECharts单文件引入 -->
<script src="http://echarts.baidu.com/gallery/vendors/echarts/echarts-all-3.js"></script>
<script type="text/javascript">
// 基于准备好的dom,初始化echarts图表
var myChart = echarts.init(document.getElementById('main'));
var option = ##option##;
// 为echarts对象加载数据
myChart.setOption(option);
</script>
</html>
PS:echarts组件中的模板是采用OptionUtil加载文件的方式,插件已经停止更新,所以模板的名称是固定的template(或改其源码也可以)
下面就可以愉快的玩耍了,哈哈
划重点了:
1、为了防止跨平台需要额外的运维,可将可执行文件都放在Project中,产生的问题是,打包之后的Project中是没有办法将可执行文件放在系统变量汇总的,所以需要通过JavaAPI方式将可执行文件导入到服务器的工作目录,并将其变成可执行文件,方可执行2、Java8以后可以通过JavaAPI方式执行Js文件
ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName( "JavaScript" ); System.out.println( engine.getClass().getName() ); System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );
3、在linux中执行的时候,截图出来的图片始终是乱码的,大部分因为字体不支持的原因
//在/usr/share/fonts 目录下创建micro文件夹 [root@elastic-slave ~]# mkdir /usr/share/fonts/micro //安装依赖 [root@elastic-slave ~]# yum install bitmap-fonts bitmap-fonts-cjk //然后将windows中的fonts文件夹直接全部导过去,重新运行程序即可
4、使用chrom和firefox在window上时,并无任何问题,但是在linux上还是不能执行,原因猜测:linux安装的浏览器和浏览器驱动版本不一致