【问题标题】:Selenium Chrome Screenshot - html2canvasSelenium Chrome 屏幕截图 - html2canvas
【发布时间】:2015-07-22 12:18:16
【问题描述】:

我使用 html2canvas.js 库与 selenium 一起拍摄全页屏幕截图。

public class ChromeCanvasCapture {

    private static final String APP_URL = "http://www.flipkart.com";

    public static String getFileContent(String filePath)
        throws FileNotFoundException, IOException {
        FileInputStream inputStream = new FileInputStream(filePath);
        String fileContent = IOUtils.toString(inputStream);
        inputStream.close();
        return fileContent;
    }

    public static void main(String[] args) {

        WebDriver webDriver = null;

        try {
            System.setProperty("webdriver.chrome.driver",
                "D:\\Drivers\\chromedriver.exe");
            webDriver = new ChromeDriver();
            webDriver.get(APP_URL);
            webDriver.manage().window().maximize();

            if (webDriver instanceof JavascriptExecutor) {
                // scroll to the bottom of the page
                ((JavascriptExecutor) webDriver)                        .executeScript("window.scrollTo(0,document.body.scrollHeight)");
                // //scroll to the top of the page
                ((JavascriptExecutor) webDriver)
                    .executeScript("window.scrollTo(0,0)");
            }

            String jsFile = getFileContent("html2canvas.js");
            jsFile = jsFile
                + " var webDriverCallback = arguments[arguments.length - 1]; html2canvas(document.body, {onrendered: function(canvas) {var img = canvas.toDataURL('image/png').replace('data:image/png;base64,', '');; webDriverCallback(img); }";
            System.out.println(jsFile);
            webDriver.manage().timeouts().setScriptTimeout(5, TimeUnit.SECONDS);
            if (webDriver instanceof JavascriptExecutor) {
                JavascriptExecutor executor = (JavascriptExecutor) webDriver;
                Object result = executor.executeAsyncScript(jsFile);
                String imageString = String.valueOf(result);
                byte[] imageData = Base64.decodeBase64(imageString);
                OutputStream outputStream = new FileOutputStream(
                    "C:\\Captures\\canvas_captured.png");
                outputStream.write(imageData);
                outputStream.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // webDriver.quit();
        }
    }
}

我将 html2canvas.js 文件保存在我的 java 项目的类路径中。我用来截屏的java脚本代码是:

var webDriverCallback = arguments[arguments.length - 1];

html2canvas(document.body, {
    onrendered: function(canvas) {
        var img = canvas.toDataURL('image/png').replace('data:image/png;base64,', '');;
        webDriverCallback(img);
    }
});

我能够捕捉到 Flipkart 页面的全页屏幕截图,但其中没有任何图像。

我无法使用 Chrome 中的 TakeScreenshot 实用程序,因为它不允许使用 chrome 浏览器拍摄全页屏幕截图。

【问题讨论】:

    标签: java google-chrome selenium selenium-webdriver html2canvas


    【解决方案1】:

    您可以使用以下内容。
    但是您必须等待一段时间,直到所有图像都加载完毕。
    它可以选择跳过重复的元素,因为 Flipkart 的标题搜索横幅是重复的,我将其作为要隐藏的元素传递。

        driver.get("http://www.flipkart.com");
        WebDriverWait wait = new WebDriverWait(driver, 60);
        wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//div[contains(@class,'fk-ui-ccarousel-supercontainer requirecss-HPS')]")));
    
        WebElement header = driver.findElement(By.id("fk-header"));
        //As header is repeating we are giving it as repetitive element so that it will remove it while taking screenshot
        Files.copy(Utils.makeFullScreenshot(driver, header), new File("D:\\fsile.png"));
    

    Utils.Java
    是对GalenFramework的FullPageScreenShot的修改

    import java.awt.Graphics2D;
    import java.awt.image.BufferedImage;
    import java.io.ByteArrayInputStream;
    import java.io.File;
    import java.io.IOException;
    import javax.imageio.ImageIO;
    import org.openqa.selenium.JavascriptExecutor;
    import org.openqa.selenium.OutputType;
    import org.openqa.selenium.TakesScreenshot;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    
    public class Utils {
    
    private static final String JS_RETRIEVE_DEVICE_PIXEL_RATIO = "var pr = window.devicePixelRatio; if (pr != undefined && pr != null)return pr; else return 1.0;";
    
    private static void hideScroll(WebDriver driver) {
        ((JavascriptExecutor) driver).executeScript("document.documentElement.style.overflow = 'hidden';");
    }
    
    private static void showScroll(WebDriver driver) {
        ((JavascriptExecutor) driver).executeScript("document.documentElement.style.overflow = 'visible';");
    }
    
    private static void showHideElements(WebDriver driver, Boolean hide, WebElement... skipElements) {
        String display;
        if (hide) {
            display = "none";
        } else {
            display = "block";
        }
        if (skipElements != null) {
            for (WebElement skipElement : skipElements) {
                ((JavascriptExecutor) driver).executeScript("arguments[0].style.display = '" + display + "';", skipElement);
            }
        }
    }
    
    private static byte[] getScreenShot(WebDriver driver) {
        return ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
    }
    
    //The code that does the job
    public static File makeFullScreenshot(WebDriver driver, WebElement... skipElements) throws IOException, InterruptedException {
        //Scroll to bottom to make sure all elements loaded correctly
        // scrollVerticallyTo(driver, (int) longScrollHeight);
    
        // scroll up first to start taking screenshots
        scrollVerticallyTo(driver, 0);
        hideScroll(driver);
        //No need to hide elements for first attempt
        byte[] bytes = getScreenShot(driver);
    
        showHideElements(driver, true, skipElements);
        long longScrollHeight = (Long) ((JavascriptExecutor) driver).executeScript("return Math.max("
                + "document.body.scrollHeight, document.documentElement.scrollHeight,"
                + "document.body.offsetHeight, document.documentElement.offsetHeight,"
                + "document.body.clientHeight, document.documentElement.clientHeight);"
        );
    
        BufferedImage image = ImageIO.read(new ByteArrayInputStream(bytes));
        int capturedWidth = image.getWidth();
        int capturedHeight = image.getHeight();
    
        Double devicePixelRatio = ((Number) ((JavascriptExecutor) driver).executeScript(JS_RETRIEVE_DEVICE_PIXEL_RATIO)).doubleValue();
    
        int scrollHeight = (int) longScrollHeight;
    
        File file = File.createTempFile("screenshot", ".png");
    
        int adaptedCapturedHeight = (int) (((double) capturedHeight) / devicePixelRatio);
    
        BufferedImage resultingImage;
    
        if (Math.abs(adaptedCapturedHeight - scrollHeight) > 40) {
            int scrollOffset = adaptedCapturedHeight;
    
            int times = scrollHeight / adaptedCapturedHeight;
            int leftover = scrollHeight % adaptedCapturedHeight;
    
            final BufferedImage tiledImage = new BufferedImage(capturedWidth, (int) (((double) scrollHeight) * devicePixelRatio), BufferedImage.TYPE_INT_RGB);
            Graphics2D g2dTile = tiledImage.createGraphics();
            g2dTile.drawImage(image, 0, 0, null);
    
            int scroll = 0;
            for (int i = 0; i < times - 1; i++) {
                scroll += scrollOffset;
                scrollVerticallyTo(driver, scroll);
                BufferedImage nextImage = ImageIO.read(new ByteArrayInputStream(getScreenShot(driver)));
                g2dTile.drawImage(nextImage, 0, (i + 1) * capturedHeight, null);
            }
            if (leftover > 0) {
                scroll += scrollOffset;
                scrollVerticallyTo(driver, scroll);
                BufferedImage nextImage = ImageIO.read(new ByteArrayInputStream(getScreenShot(driver)));
                BufferedImage lastPart = nextImage.getSubimage(0, nextImage.getHeight() - (int) (((double) leftover) * devicePixelRatio), nextImage.getWidth(), leftover);
                g2dTile.drawImage(lastPart, 0, times * capturedHeight, null);
            }
    
            scrollVerticallyTo(driver, 0);
    
            resultingImage = tiledImage;
        } else {
            resultingImage = image;
        }
        showScroll(driver);
        showHideElements(driver, false, skipElements);
        ImageIO.write(resultingImage, "png", file);
        return file;
    }
    
    private static void scrollVerticallyTo(WebDriver driver, int scroll) {
        ((JavascriptExecutor) driver).executeScript("window.scrollTo(0, " + scroll + ");");
        try {
            waitUntilItIsScrolledToPosition(driver, scroll);
        } catch (InterruptedException e) {
           // LOG.trace("Interrupt error during scrolling occurred.", e);
        }
    }
    
    private static void waitUntilItIsScrolledToPosition(WebDriver driver, int scrollPosition) throws InterruptedException {
        int hardTime = 0;//SCREENSHOT_FULLPAGE_SCROLLWAIT
        if (hardTime > 0) {
            Thread.sleep(hardTime);
        }
        int time = 250;//SCREENSHOT_FULLPAGE_SCROLLTIMEOUT
        boolean isScrolledToPosition = false;
        while (time >= 0 && !isScrolledToPosition) {
            Thread.sleep(50);
            time -= 50;
            isScrolledToPosition = Math.abs(obtainVerticalScrollPosition(driver) - scrollPosition) < 3;
        }
    }
    
    private static int obtainVerticalScrollPosition(WebDriver driver) {
        Long scrollLong = (Long) ((JavascriptExecutor) driver).executeScript("return (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;");
        return scrollLong.intValue();
    }
    }
    

    【讨论】:

      【解决方案2】:

      html2canvas 脚本通过遍历页面的 DOM 来工作它收集那里所有元素的信息,然后它使用这些信息来构建页面的表示,这意味着它实际上不截取屏幕截图页面,但通过读取 DOM 构建表示,即)整个图像是在客户端浏览器上创建的,因此它也不会规避任何浏览器内容策略限制

      在同源策略下,Web 浏览器允许网页中包含的脚本访问另一个网页的数据,前提是两个网页具有相同的来源。因此脚本使用的所有图像都需要位于同一来源下才能读取它们。但是在您的情况下,图像是从不同域http://img5a.flixcart.com加载到flipkart.com的(您可以从 src 属性中验证这一点Flipkart 中的图像)

      您的另一个选择是使用代理,但目前没有用于 java 的 html2canvas proxy。如果您绝对需要,您可以构建一个。

      如果您有兴趣,其他解决方案

      伪代码

      document.documentElement.scrollHeight //gives total page height
      
      document.documentElement.clientHeight //gives current screen height
      
      //You can write ur logic ex : if scroll height is greater than client height scroll
      if(document.documentElement.scrollHeight>document.documentElement.clientHeight)
      //take screenshot and then scroll(Do this till the end of the page)
      }
      

      最后将所有屏幕截图合并到一个图像文件中

      希望这对您有所帮助...如果您有任何疑问,请回复

      【讨论】:

      • 我尝试了您建议的解决方案。但在 Flipkart 的情况下它会失败。由于标题是静态的,它会出现在所有屏幕截图中。
      • @PriyankThakkar 是的,这是这种方法的缺点之一......您必须在第一次滚动 document.getElementById("fk-header").remove() 后使用 javascript 手动删除主菜单;
      猜你喜欢
      • 1970-01-01
      • 2022-01-13
      • 1970-01-01
      • 1970-01-01
      • 2016-01-14
      • 2022-01-26
      • 2017-12-25
      • 2018-01-25
      • 2014-01-03
      相关资源
      最近更新 更多