【问题标题】:How to take Retina Screenshots with Xvfb and Selenium如何使用 Xvfb 和 Selenium 拍摄 Retina 屏幕截图
【发布时间】:2015-07-14 07:46:13
【问题描述】:

我想为我的 iTunes 自动连接混合应用截取一些屏幕截图。我正在运行 Ubuntu 14.04。 chromedriver 2.15.322448

使用 Selenium 和 Xvfb 可以轻松自动截屏。但是要获得retina屏幕截图并不容易。

我以更高的 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 的 chromedriver 来获取视网膜屏幕截图?有可能吗?

【问题讨论】:

  • 您找到解决方案了吗?我遇到了完全相同的问题,如果您分享您对 xvfb 和视网膜分辨率屏幕截图的经验,将不胜感激
  • 我在下面发布了一个答案。我希望它有所帮助。

标签: selenium screenshot retina-display selenium-chromedriver xvfb


【解决方案1】:

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

一样简单
$ google-chrome --headless --hide-scrollbars --disable-gpu \
                --screenshot --force-device-scale-factor=2 \
                --window-size=750,1334 https://www.kicktipp.de/

【讨论】:

  • --force-device-scale-factor=2 帮我解决了这个问题,非常感谢!
【解决方案2】:

我面临同样的问题,但仍然卡住,但以下内容可能有用。它允许我通过将 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 进入后,google-chrome GUI 可以从终端加载。导航到网页确认 Chrome 正在使用正确的 DPI 呈现页面。截图http://i.stack.imgur.com/iEjo0.jpg

我也真的很想实现这个功能,所以如果您有任何新的进展,请与我们联系。我用https://registry.hub.docker.com/u/selenium/standalone-chrome-debug/顺便说一句。

【讨论】:

    【解决方案3】:

    我切换到 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>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-08-07
      • 2014-01-12
      • 1970-01-01
      • 2014-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多