我想为我的混合应用程序拍摄一些截图,以便自动连接itunes。我正在运行Ubuntu 14.04。 chromedriver 2.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的chromedriver来拍摄视网膜截图?有可能吗?
答案 0 :(得分:3)
如果您只想拍摄一些屏幕主机,可以使用Google Chrome无头工具。例如,获取视网膜截图就像
一样简单$ google-chrome --headless --hide-scrollbars --disable-gpu \
--screenshot --force-device-scale-factor=2 \
--window-size=750,1334 https://www.kicktipp.de/
答案 1 :(得分:1)
我面临同样的问题并且仍然存在,但以下内容可能会有用。它允许我通过将一个VNC连接附加到xvfb帧缓冲区来排除xvfb或chrome。
log:requests
在VNC进入后,可以从终端加载#!/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
GUI。导航到网页确认Chrome正在使用正确的DPI呈现页面。屏幕截图http://i.stack.imgur.com/iEjo0.jpg
我真的很想让这个也工作,所以如果你有任何新的发展,请联系我们。我使用了https://registry.hub.docker.com/u/selenium/standalone-chrome-debug/ BTW。
答案 2 :(得分:0)
我切换到Firefox,它使用以下代码为我工作。但目前它没有因为我的Firefox版本47没有使用selenium,请参阅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>