Java+seleniumhq+phantomjsdriver+selenium-chrome-driver+selenium-firefox-driver截圖

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章