背景:項目需要自動報告生成,其中主要的複雜點及是報表的生成及截圖寫入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安裝的瀏覽器和瀏覽器驅動版本不一致