如何使用webdriver(Java)在phantomjs上禁用字体消除锯齿?

问题描述:

我正在尝试编写检查布局问题的Selenium测试。为此,我使用Java端的Selenium Webdriver和phantomjs作为“浏览器”。我想使用phantomjs,因为它可以制作实际渲染组件的屏幕截图。如何使用webdriver(Java)在phantomjs上禁用字体消除锯齿?

默认情况下,phantomjs使用反锯齿呈现文本,这使得很难扫描文本(以查找文本基线并执行简单的OCR)。

我该如何告诉phantomJS不要使用抗锯齿?

我使用了下面的诡计来禁用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),它可能会失败在定义反锯齿。但对于我的测试用例来说,这工作正常。

相同的技巧也适用于无头铬)

要禁用无头铬抗锯齿使用上述相同的代码来生成fonts.conf文件,然后如下分配一个镀铬的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); 
+0

我建议你开始远离它。现在它不再支持。 https://groups.google.com/forum/#!topic/phantomjs/9aI5d-LDuNE –

+0

我知道,但人们仍在使用它。过渡到别的不是很快;) – fjalvingh

+0

实际上,同样的技巧也适用于无头Chrome;)我也会更新答案。 – fjalvingh