简体   繁体   中英

How can font anti aliasing be disabled on phantomjs with webdriver (Java)?

I am trying to write Selenium tests that check layout issues. For this I am using Selenium Webdriver on the Java side and phantomjs as the "browser". I want to use phantomjs because it is able to make screenshots of the actually rendered components.

By default phantomjs renders text using anti aliasing, and that makes it hard to scan texts (to find text baselines and to do simple OCR).

How can I tell phantomJS to not use anti aliasing?

I used the following dirty trick to disable phantomjs anti aliasing on Linux. PhantomJS is built using fontconfig, and this library looks for a file "fonts.conf" in several locations: see https://www.freedesktop.org/software/fontconfig/fontconfig-user.html .

By creating the following fonts.conf we can disable anti aliasing for fontconfig:

<match target="font">
  <edit mode="assign" name="antialias">
    <bool>false</bool>
  </edit>
</match>

One of the locations is defined by an environment variable, according to the specs: $XDG_CONFIG_HOME/fontconfig/fonts.conf. So by creating a temp file like /tmp/phantomjs-config/fontconfig/fonts.conf with the above content, and then setting XDG_CONFIG_HOME to /tmp/phantomjs-config we tell fontconfig to read that file.

There is one problem though: the PhantomJSDriver class, by default, does not allow environment variables to be set. Which is sad, because the underlying worker code, PhantomWebDriverService, does allow this.

To solve this I created a small helper class in which I copied some protected methods from 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];
    }
}

With this new code I can now create a PhantomJSDriver as follows:

        //-- 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);

Issues with the code

The most important issue with this code is that it might fail if there are fonts.conf files (like $HOME/.fonts.conf) that define anti aliasing. But for my test case this works OK.

The same trick also works for Headless Chrome ;)

To disable anti aliasing on headless chrome use the same code above to generate the fonts.conf file, then allocate a Chrome webdriver as follows:

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);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM