当前位置: 首页 > 知识库问答 >
问题:

如何用Xvfb和硒拍摄视网膜截图

公宜春
2023-03-14

我想把我的混合应用程序的一些截图itunes自动连接。我正在运行Ubuntu 14.04. chromeDrive2.15.322448

使用Selenium和Xvfb自动截图很容易。但是要得到视网膜截图并不容易。

我用更高的dpi启动了我的Xvfb:

/usr/bin/Xvfb :99 -screen 0 2000x2000x24 -dpi 200

当我检查显示信息时,一切似乎都是正确的:

xdpyinfo -display :99

...
screen #0:
  dimensions:    2000x2000 pixels (254x254 millimeters)
  resolution:    200x200 dots per inch
  depths (6):    24, 1, 4, 8, 16, 32
...

然后我像这样启动我的chromedriver

private WebDriver getChromeDriver ( Phone phone )
{
    Map<String, Object> deviceMetrics = new HashMap<String, Object>();
    deviceMetrics.put("width", 320);
    deviceMetrics.put("height", 460);
    deviceMetrics.put("pixelRatio", 2);
    Map<String, Object> mobileEmulation = new HashMap<String, Object>();
    mobileEmulation.put("deviceMetrics", deviceMetrics);
    mobileEmulation.put("userAgent", "iphone4");

    ChromeDriverService cds = new ChromeDriverService.Builder().withEnvironment(ImmutableMap.of("DISPLAY", ":99")).build();

    Map<String, Object> chromeOptions = new HashMap<String, Object>();
    chromeOptions.put("mobileEmulation", mobileEmulation);
    DesiredCapabilities capabilities = DesiredCapabilities.chrome();
    capabilities.setCapability(ChromeOptions.CAPABILITY, chromeOptions);
    WebDriver driver = new ChromeDriver(cds, capabilities);
    return driver;
}

在完成了一些其他无聊的代码后,我截图:

 File srcFile = ( (TakesScreenshot) driver ).getScreenshotAs(OutputType.FILE);

这不工作。屏幕截图是在普通的dpi。所以拍摄的网站图像只有320x460,而不是应有的640x960。

我在截图之前设置了一个断点,并像这样转储帧缓冲区:

export DISPLAY=:99 
xwd -root -silent | xwdtopnm |pnmtojpeg > screen.jpg

如您所见,标题栏是根据更高的dpi呈现的,但浏览器窗口的其余部分没有。

那么,我如何运行一个具有更多dpi的Chrome驱动程序来拍摄视网膜截图呢?有可能吗?

共有3个答案

孟晨朗
2023-03-14

我切换到Firefox,它通过以下代码为我工作。但目前情况并非如此,因为selenium在我的Firefox版本47上运行不正常,请参阅https://github.com/SeleniumHQ/selenium/issues/2257所以我现在无法测试这段代码,但上次我能够用它获得视网膜截图:

package de.kicktipp.screenshots.stackoverflow;

import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

import javax.imageio.ImageIO;

import org.openqa.selenium.Dimension;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxBinary;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxProfile;

public class ScreenshotMaker
{
    private PhoneList           phoneList   = new PhoneList();

    private static final String HOST        = "https://m.kicktipp.de";
    private static final String PATH        = "/";

    private File                resultDirectory;
    private int                 filenumber  = 0;

    public static void main ( String[] args ) throws Exception
    {
        ScreenshotMaker screenshotMaker = new ScreenshotMaker();
        screenshotMaker.run();
    }

    public WebDriver getDriver ( Phone phone, Display display )
    {
        FirefoxProfile profile = new FirefoxProfile();
        // profile.setPreference("layout.css.devPixelsPerPx", "2.0");
        // Ansonsten erscheint ein hässliches Popup welches Reader Funktion
        // anbietet
        profile.setPreference("reader.parse-on-load.enabled", false);
        profile.setPreference("xpinstall.signatures.required", false);
        FirefoxBinary firefoxBinary = new FirefoxBinary();
        firefoxBinary.setEnvironmentProperty("DISPLAY", display.getDisplayNumberString());
        FirefoxDriver firefoxDriver = new FirefoxDriver(firefoxBinary, profile);
        firefoxDriver.manage().window().setSize(new Dimension(phone.getWidth(), display.getHeight()));
        return firefoxDriver;
    }

    private void run ( ) throws Exception
    {
        mkdir();
        for (Phone phone : phoneList)
        {
            WebDriver driver = null;
            Display display = null;
            try
            {
                display = new Display(phone.getDpiFaktor());
                driver = getDriver(phone, display);
                System.out.println(phone.getName());
                filenumber = 0;
                System.out.println("");
                System.out.println("Generating Screenshots for " + phone.getName());
                System.out.println("-----------------------------------------------------------------------------");
                driver.get(HOST + "/");
                shot(display, driver, PATH, phone);
            }
            finally
            {
                if (driver != null)
                {
                    driver.quit();
                }
                if (display != null)
                {
                    display.shutdown();
                }
            }
        }
        System.out.println("");
        System.out.println("-----------------------------------------------------------------------------");
        System.out.println("Finished.");

    }

    private void mkdir ( ) throws IOException
    {
        File targetDir = targetDir();
        resultDirectory = new File(targetDir, "results");
        resultDirectory.mkdir();
        System.out.println("Writing screenshots to " + resultDirectory.getCanonicalPath());
    }

    public File targetDir ( )
    {
        String relPath = getClass().getProtectionDomain().getCodeSource().getLocation().getFile();
        File targetDir = new File(relPath + "../..");
        if (!targetDir.exists())
        {
            targetDir.mkdir();
        }
        return targetDir;
    }

    private void shot ( Display display, WebDriver driver, String path, Phone phoneSpec ) throws Exception
    {
        String url = getUrl(path);
        driver.get(url);
        scrollToRemoveScrollbars(driver);
        // Selenium screenshot doesn't work, we are dumping the framebuffer
        // directly
        File srcFile = display.captureScreenshot();
        moveFile(srcFile, phoneSpec);
    }

    private void scrollToRemoveScrollbars ( WebDriver driver ) throws Exception
    {
        JavascriptExecutor js = (JavascriptExecutor) driver;
        js.executeScript("window.scrollTo(0,20);");
        js.executeScript("window.scrollTo(0,0);");
        Thread.sleep(800);
    }

    private String getUrl ( String path )
    {
        StringBuffer url = new StringBuffer(HOST);
        url.append(path);
        return url.toString();
    }

    private void moveFile ( File srcFile, Phone phone ) throws Exception
    {
        String filename = phone.getFilename(filenumber);
        File file = new File(resultDirectory, filename);
        if (file.exists())
        {
            file.delete();
        }
        crop(srcFile, file, phone);
        System.out.println(filename);
    }

    private void crop ( File srcFile, File targetFile, Phone phone ) throws Exception
    {
        int width = phone.getPixelWidth();
        int height = phone.getPixelHeight();
        int yStart = 71 * phone.getDpiFaktor();
        Image orig = ImageIO.read(srcFile);
        BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        bi.getGraphics().drawImage(orig, 0, 0, width, height, 0, yStart, width, height + yStart, null);
        ImageIO.write(bi, "png", targetFile);
    }
}

class PhoneList extends ArrayList<Phone>
{
    private static final Phone  IPHONE_4        = new Phone(320, 460, "iphone4", 2);
    private static final Phone  IPHONE_5        = new Phone(320, 548, "iphone5", 2);
    private static final Phone  IPHONE_6        = new Phone(375, 667, "iphone6", 2);
    private static final Phone  IPAD            = new Phone(1024, 748, "ipad", 2);
    private static final Phone  IPHONE_6_PLUS   = new Phone(414, 736, "iphone6plus", 3);
    private static final Phone  AMAZON          = new Phone(480, 800, "amazon", 1);

    public PhoneList ()
    {
        add(AMAZON);
        add(IPHONE_4);
        add(IPHONE_5);
        add(IPHONE_6);
        add(IPAD);
        add(IPHONE_6_PLUS);
    }
}

class Phone
{
    private int     width       = 0;
    private int     height      = 0;
    private String  name        = "";
    private int     dpiFaktor   = 2;

    public Phone ( int width, int height, String name, int dpiFaktor )
    {
        this.width = width;
        this.height = height;
        this.name = name;
        this.dpiFaktor = dpiFaktor;
    }

    public int getWidth ( )
    {
        return width;
    }

    public int getHeight ( )
    {
        return height;
    }

    public int getPixelWidth ( )
    {
        return width * dpiFaktor;
    }

    public int getPixelHeight ( )
    {
        return height * dpiFaktor;
    }

    public int getDpiFaktor ( )
    {
        return dpiFaktor;
    }

    public String getName ( )
    {
        return name;
    }

    public Dimension getDimension ( )
    {
        return new Dimension(width, height);
    }

    public String getFilename ( int number )
    {
        String dimension = getPixelWidth() + "x" + getPixelHeight();
        return name + "-" + dimension + "-" + number + ".png";
    }
}

class Display
{
    private static final int    HEIGHT                  = 5000;
    private static final int    WIDTH                   = 5000;
    private static String       XVFB                    = "/usr/bin/Xvfb";
    private static String       DISPLAY_NUMBER_STRING   = ":99";
    private static String       SCREEN_SIZE             = " -screen 0 " + WIDTH + "x" + HEIGHT + "x24";
    private static String       XVFB_COMMAND            = XVFB + " " + DISPLAY_NUMBER_STRING + SCREEN_SIZE + " -dpi ";
    private static int          baseDpi                 = 100;
    private Process             p;

    public Display ( int dpiFaktor ) throws IOException, InterruptedException
    {
        checkExecutable();
        int dpi = baseDpi * dpiFaktor;
        String cmd = XVFB_COMMAND + dpi;
        p = Runtime.getRuntime().exec(cmd);
        Thread.sleep(1000);
        try
        {
            int exitValue = p.exitValue();
            String msgTemplate = "ERROR: Exit Value: %s. Display konnte nicht gestartet werden. Läuft ein Display noch auf %s ?";
            String msg = String.format(msgTemplate, exitValue, DISPLAY_NUMBER_STRING);
            throw new IllegalStateException(msg);
        }
        catch (IllegalThreadStateException e)
        {
            // Das ist gut, der Prozess ist noch nicht beendet.
            System.out.println("Switched on display at " + dpi + "dpi with command " + cmd);
            return;
        }
    }

    private void checkExecutable ( )
    {
        File file = new File(XVFB);
        if (!file.canExecute())
        {
            System.err.println("Xvfb is not installed at " + XVFB);
            System.err.println("Install Xvfb by runing");
            System.err.println("apt-get install xvfb");
        }
    }

    public File captureScreenshot ( ) throws IOException, InterruptedException
    {
        File tempFile = File.createTempFile("screenshots", ".png");
        String absolutePath = tempFile.getAbsolutePath();
        String cmd = "import -window root " + absolutePath;
        String[] env = new String[] { "DISPLAY=" + DISPLAY_NUMBER_STRING };
        Process exec = Runtime.getRuntime().exec(cmd, env);
        exec.waitFor();
        return tempFile;
    }

    public void shutdown ( ) throws IOException, InterruptedException
    {
        p.destroy();
        try
        {
            Thread.sleep(1000);
            int exitValue = p.exitValue();
            System.out.println("Display was switched off. ExitValue: " + exitValue);
        }
        catch (IllegalThreadStateException e)
        {
            // Das ist nicht gut, der Prozess sollte beendet sein.
            // Kill it:
            p = Runtime.getRuntime().exec("pkill Xvfb");
        }
    }

    public String getDisplayNumberString ( )
    {
        return DISPLAY_NUMBER_STRING;
    }

    public int getHeight ( )
    {
        return HEIGHT;
    }
}

这是我的pom:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>de.kicktipp</groupId>
    <artifactId>screenshots</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>screenshots</name>
    <properties>
        <jdk.version>1.7</jdk.version>
        <maven.version>3.0</maven.version>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <selenium-java.version>2.53.1</selenium-java.version>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>${jdk.version}</source>
                    <target>${jdk.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>${selenium-java.version}</version>
        </dependency>
    </dependencies>
</project>
危璞
2023-03-14

我面临着同样的问题,但仍然无法解决,但下面的内容可能会有用。它允许我通过将VNC连接连接到xvfb帧缓冲区来排除xvfb或chrome。

#!/bin/bash
export GEOMETRY="$SCREEN_WIDTH""x""$SCREEN_HEIGHT""x""$SCREEN_DEPTH"

function shutdown {
  kill -s SIGTERM $NODE_PID
  wait $NODE_PID
}

sudo -E -i -u seluser \
  DISPLAY=$DISPLAY \
  xvfb-run --server-args="$DISPLAY -screen 0 $GEOMETRY -dpi 300 -ac +extension RANDR" \
  java -jar /opt/selenium/selenium-server-standalone.jar &
NODE_PID=$!

trap shutdown SIGTERM SIGINT
for i in $(seq 1 10)
do
  xdpyinfo -display $DISPLAY >/dev/null 2>&1
  if [ $? -eq 0 ]; then
    break
  fi
  echo Waiting xvfb...
  sleep 0.5
done

fluxbox -display $DISPLAY &

x11vnc -forever -usepw -shared -rfbport 5900 -display $DISPLAY &

wait $NODE_PID

输入VNC'ing后,可以从终端加载googlechromeGUI。网页导航确认Chrome使用正确的DPI呈现网页。截图http://i.stack.imgur.com/iEjo0.jpg

我真的很想让这个工作,所以如果你有任何新的发展,请联系。我用https://registry.hub.docker.com/u/selenium/standalone-chrome-debug/BTW。

邢献
2023-03-14

如果你只想拍摄一些屏幕主机,你可以使用谷歌chrome无头工具。例如,获取视网膜屏幕截图就像

$ google-chrome --headless --hide-scrollbars --disable-gpu \
                --screenshot --force-device-scale-factor=2 \
                --window-size=750,1334 https://www.kicktipp.de/
 类似资料:
  • 我需要使用Sikuli的自动化,但我无法采取截图。问题是,当我点击“截屏”或“创建区域”时,IDE会被隐藏,但选择区域选项不会出现....

  • 问题内容: 我要实现的是从python中的任何网站获取网站截图。 环境:Linux 问题答案: 在Mac上,有webkit2png,在Linux+KDE上,可以使用khtml2png。我已经尝试了前者,并且效果很好,并且听说后者已投入使用。 我最近遇到了QtWebKit,它声称是跨平台的(我猜Qt将WebKit卷入了他们的库中)。但是我从未尝试过,所以我无法告诉您更多信息。 QtWebKit链接显

  • 可在游戏中等拍摄并保存任何场景。同时按下PS键和START键。 可从(照片)查看已保存的信息。 若要拍摄画面截图,需事先将PS Vita专用存储卡插入主机。 因画面或应用程序的状态,可能无法拍摄画面截图。

  • 问题内容: 我正在构建一个称为“ HTML测验”的东西。它完全在JavaScript上运行,非常酷。 最后,弹出一个结果框,显示“您的结果:”,它显示了他们花费了多少时间,他们得到了多少百分比以及他们从10个问题中得出了多少个问题。我想有一个按钮,上面写着“捕获结果”,并使其以某种方式截取屏幕截图或div,然后仅在页面上显示捕获的图像即可在其中单击鼠标右键并“将图像另存为”。 我真的很想这样做,以

  • 问题内容: 我正在尝试使用WebdriverIO截取整个页面的屏幕截图。 我读过,最好的方法是使用WebdriverCSS增强我的WebdriverIO流。WebdriverCSS自动截图整个页面? 问题是WebdriverCSS对我不起作用。我认为是因为它尚未与兼容。 有什么办法可以使它起作用,或者可以使用其他解决方案? 我的代码: ( 这是生产什么,但在回调中未定义的值 ) !EDIT: 这是

  • 嗨,我如何在Windows电脑上使用任何浏览器拍摄网页的完整屏幕截图? 每当我用PrintScreen截图时,它只截图显示页面,但我希望截图的高度为整页。我们能拍完整的截图吗,包括隐藏的八张? 抱歉我英语不好。 我有firefox和chrome。