如何使用 webdriver (Java) 在 phantomjs 上禁用字体抗锯齿?
Posted
技术标签:
【中文标题】如何使用 webdriver (Java) 在 phantomjs 上禁用字体抗锯齿?【英文标题】:How can font anti aliasing be disabled on phantomjs with webdriver (Java)? 【发布时间】:2017-09-02 15:26:21 【问题描述】:我正在尝试编写检查布局问题的 Selenium 测试。为此,我在 Java 端使用 Selenium Webdriver,并将 phantomjs 作为“浏览器”。我想使用 phantomjs,因为它能够对实际渲染的组件进行截图。
默认情况下,phantomjs 使用抗锯齿呈现文本,这使得扫描文本变得困难(查找文本基线和进行简单的 OCR)。
如何告诉 phantomJS 不使用抗锯齿功能?
【问题讨论】:
【参考方案1】:我使用以下肮脏的技巧在 Linux 上禁用 phantomjs 抗锯齿。 PhantomJS 是使用 fontconfig 构建的,该库在多个位置查找文件“fonts.conf”:参见https://www.freedesktop.org/software/fontconfig/fontconfig-user.html。
通过创建以下 fonts.conf,我们可以禁用 fontconfig 的抗锯齿:
<match target="font">
<edit mode="assign" name="antialias">
<bool>false</bool>
</edit>
</match>
根据规范,其中一个位置由环境变量定义:$XDG_CONFIG_HOME/fontconfig/fonts.conf。因此,通过使用上述内容创建一个像 /tmp/phantomjs-config/fontconfig/fonts.conf 这样的临时文件,然后将 XDG_CONFIG_HOME 设置为 /tmp/phantomjs-config,我们告诉 fontconfig 读取该文件。
但是有一个问题:默认情况下,PhantomJSDriver 类不允许设置环境变量。这很可悲,因为底层工作代码 PhantomWebDriverService 确实允许这样做。
为了解决这个问题,我创建了一个小的帮助类,在其中我从 PhantomJSDriverService 复制了一些受保护的方法:
public class MyPhantomDriverService
public static PhantomJSDriverService createDefaultService(Capabilities desiredCapabilities, Map<String, String> env)
Proxy proxy = null;
if (desiredCapabilities != null)
proxy = Proxy.extractFrom(desiredCapabilities);
File phantomjsfile = findPhantomJS(desiredCapabilities, "https://github.com/ariya/phantomjs/wiki", "http://phantomjs.org/download.html");
File ghostDriverfile = findGhostDriver(desiredCapabilities, "https://github.com/detro/ghostdriver/blob/master/README.md", "https://github.com/detro/ghostdriver/downloads");
Builder builder = new Builder();
builder.usingPhantomJSExecutable(phantomjsfile)
.usingGhostDriver(ghostDriverfile)
.usingAnyFreePort()
.withProxy(proxy)
.withLogFile(new File("phantomjsdriver.log"))
.usingCommandLineArguments(findCLIArgumentsFromCaps(desiredCapabilities, "phantomjs.cli.args"))
.usingGhostDriverCommandLineArguments(findCLIArgumentsFromCaps(desiredCapabilities, "phantomjs.ghostdriver.cli.args"));
if(null != env)
builder.withEnvironment(env);
return builder.build();
public static File findPhantomJS(Capabilities desiredCapabilities, String docsLink, String downloadLink)
String phantomjspath;
if (desiredCapabilities != null && desiredCapabilities.getCapability("phantomjs.binary.path") != null)
phantomjspath = (String)desiredCapabilities.getCapability("phantomjs.binary.path");
else
phantomjspath = (new ExecutableFinder()).find("phantomjs");
phantomjspath = System.getProperty("phantomjs.binary.path", phantomjspath);
Preconditions.checkState(phantomjspath != null, "The path to the driver executable must be set by the %s capability/system property/PATH variable; for more information, see %s. The latest version can be downloaded from %s", "phantomjs.binary.path", docsLink, downloadLink);
File phantomjs = new File(phantomjspath);
checkExecutable(phantomjs);
return phantomjs;
protected static File findGhostDriver(Capabilities desiredCapabilities, String docsLink, String downloadLink)
String ghostdriverpath;
if (desiredCapabilities != null && desiredCapabilities.getCapability("phantomjs.ghostdriver.path") != null)
ghostdriverpath = (String)desiredCapabilities.getCapability("phantomjs.ghostdriver.path");
else
ghostdriverpath = System.getProperty("phantomjs.ghostdriver.path");
if (ghostdriverpath != null)
File ghostdriver = new File(ghostdriverpath);
Preconditions.checkState(ghostdriver.exists(), "The GhostDriver does not exist: %s", ghostdriver.getAbsolutePath());
Preconditions.checkState(ghostdriver.isFile(), "The GhostDriver is a directory: %s", ghostdriver.getAbsolutePath());
Preconditions.checkState(ghostdriver.canRead(), "The GhostDriver is not a readable file: %s", ghostdriver.getAbsolutePath());
return ghostdriver;
else
return null;
protected static void checkExecutable(File exe)
Preconditions.checkState(exe.exists(), "The driver executable does not exist: %s", exe.getAbsolutePath());
Preconditions.checkState(!exe.isDirectory(), "The driver executable is a directory: %s", exe.getAbsolutePath());
Preconditions.checkState(exe.canExecute(), "The driver is not executable: %s", exe.getAbsolutePath());
private static String[] findCLIArgumentsFromCaps(Capabilities desiredCapabilities, String capabilityName)
if (desiredCapabilities != null)
Object cap = desiredCapabilities.getCapability(capabilityName);
if (cap != null)
if (cap instanceof String[])
return (String[])((String[])cap);
if (cap instanceof Collection)
try
Collection<String> capCollection = (Collection<String>)cap;
return (String[])capCollection.toArray(new String[capCollection.size()]);
catch (Exception var4)
System.err.println(String.format("Unable to set Capability '%s' as it was neither a String[] or a Collection<String>", capabilityName));
return new String[0];
有了这个新代码,我现在可以创建一个 PhantomJSDriver,如下所示:
//-- 1. Make a temp directory which will contain our fonts.conf
String tmp = System.getProperty("java.io.tmpdir");
if(tmp == null)
tmp = "/tmp";
File dir = new File(tmp + File.separator + "/_phantomjs-config/fontconfig");
dir.mkdirs();
if(! dir.exists())
throw new IOException("Can't create fontconfig directory to override phantomjs font settings at " + dir);
File conf = new File(dir, "fonts.conf");
String text = "<match target=\"font\">\n"
+ "<edit mode=\"assign\" name=\"antialias\">\n"
+ "<bool>false</bool>\n"
+ "</edit>\n"
+ "</match>";
try(FileOutputStream fos = new FileOutputStream(conf))
fos.write(text.getBytes("UTF-8"));
//-- Set the XDG_CONFIG_HOME envvar; this is used by fontconfig as one of its locations
Map<String, String> env = new HashMap<>();
env.put("XDG_CONFIG_HOME", dir.getParentFile().getAbsolutePath());
PhantomJSDriverService service = MyPhantomDriverService.createDefaultService(capabilities, env);
wd = new PhantomJSDriver(service, capabilities);
代码问题
此代码最重要的问题是,如果存在定义抗锯齿的 fonts.conf 文件(如 $HOME/.fonts.conf),它可能会失败。但是对于我的测试用例,这可以正常工作。
同样的技巧也适用于 Headless Chrome ;)
要在 headless chrome 上禁用抗锯齿,请使用上面相同的代码生成 fonts.conf 文件,然后按如下方式分配 Chrome webdriver:
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeDriverService;
import org.openqa.selenium.chrome.ChromeDriverService.Builder;
...
Map<String, String> env = new HashMap<>();
env.put("XDG_CONFIG_HOME", dir.getParentFile().getAbsolutePath());
Builder builder = new Builder();
builder.usingAnyFreePort();
builder.withEnvironment(env);
ChromeDriverService service = builder.build();
return new ChromeDriver(service, dc);
【讨论】:
我建议你开始远离它。因为它现在不再支持。 groups.google.com/forum/#!topic/phantomjs/9aI5d-LDuNE 我知道,但仍有人在使用它。过渡到其他东西并没有那么快;) 实际上,同样的技巧也适用于无头 Chrome ;) 我也会用它来更新答案。以上是关于如何使用 webdriver (Java) 在 phantomjs 上禁用字体抗锯齿?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 webdriver (Java) 在 phantomjs 上禁用字体抗锯齿?
如何在 Selenium WebDriver Java 中使用 JavaScript
如何使用 Java 在 Selenium WebDriver 的隐藏字段中键入一些文本
如何在 Java 中使用 Selenium WebDriver 单击按钮?
如何在 Java 中使用 Selenium WebDriver 上传文件
如何使用适用于 Chrome Windows 10 的 WebDriver 在 Eclipse (Java) 上安装 Selenium