Going headless
Going headless seems to be all the rage these days, so let's have a look at how we can add support a headless browser to our burgeoning framework.
It's actually a relatively simple change; first, we are going to use this code to modify our POM to add a <headless>
property (we are going to set it to true
because you are always going to want to start of running things in headless mode, right?):
<properties> <project.build.sourceEncoding>UTF- 8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF- 8</project.reporting.outputEncoding> <java.version>1.8</java.version> <!-- Dependency versions --> <selenium.version>3.12.0</selenium.version> <testng.version>6.14.3</testng.version> <!-- Plugin versions --> <driver-binary-downloader-maven-plugin.version>1.0.17 </driver-binary-downloader-maven-plugin.version> <maven-compiler-plugin.version>3.7.0 </maven-compiler-plugin.version> <maven-failsafe-plugin.version>2.21.0 </maven-failsafe-plugin.version> <!-- Configurable variables --> <threads>1</threads> <browser>firefox</browser> <overwrite.binaries>false</overwrite.binaries> <headless>true</headless> </properties>
Then, we need to pass that in through maven-failsafe-plugin
:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>${maven-failsafe-plugin.version}</version> <configuration> <parallel>methods</parallel> <threadCount>${threads}</threadCount> <systemPropertyVariables> <browser>${browser}</browser> <headless>${headless}</headless> <!--Set properties passed in by the driver binary downloader--> <webdriver.chrome.driver>${webdriver.chrome.driver} </webdriver.chrome.driver> <webdriver.ie.driver>${webdriver.ie.driver} </webdriver.ie.driver> <webdriver.opera.driver>${webdriver.opera.driver} </webdriver.opera.driver> <webdriver.gecko.driver>${webdriver.gecko.driver} </webdriver.gecko.driver> <webdriver.edge.driver>${webdriver.edge.driver} </webdriver.edge.driver> </systemPropertyVariables> </configuration> <executions> <execution> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> </plugin>
Finally, we will use this code to update our DriverType
enum to read in the new headless system property and apply it to the CHROME
and FIREFOX
entries:
package com.masteringselenium.config; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeOptions; import org.openqa.selenium.edge.EdgeDriver; import org.openqa.selenium.edge.EdgeOptions; import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.firefox.FirefoxOptions; import org.openqa.selenium.ie.InternetExplorerDriver; import org.openqa.selenium.ie.InternetExplorerOptions; import org.openqa.selenium.opera.OperaDriver; import org.openqa.selenium.opera.OperaOptions; import org.openqa.selenium.remote.CapabilityType; import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.safari.SafariDriver; import org.openqa.selenium.safari.SafariOptions; import java.util.HashMap; public enum DriverType implements DriverSetup { FIREFOX { public RemoteWebDriver getWebDriverObject (DesiredCapabilities capabilities) { FirefoxOptions options = new FirefoxOptions(); options.merge(capabilities); options.setHeadless(HEADLESS); return new FirefoxDriver(options); } }, CHROME { public RemoteWebDriver getWebDriverObject (DesiredCapabilities capabilities) { HashMap<String, Object> chromePreferences = new HashMap<>(); chromePreferences.put("profile.password_manager_enabled" , false); ChromeOptions options = new ChromeOptions(); options.merge(capabilities); options.setHeadless(HEADLESS); options.addArguments("--no-default-browser-check"); options.setExperimentalOption("prefs", chromePreferences); return new ChromeDriver(options); } }, IE { public RemoteWebDriver getWebDriverObject (DesiredCapabilities capabilities) { InternetExplorerOptions options = new InternetExplorerOptions(); options.merge(capabilities); options.setCapability(CapabilityType.ForSeleniumServer. ENSURING_CLEAN_SESSION, true); options.setCapability(InternetExplorerDriver. ENABLE_PERSISTENT_HOVERING, true); options.setCapability(InternetExplorerDriver. REQUIRE_WINDOW_FOCUS, true); return new InternetExplorerDriver(options); } }, EDGE { public RemoteWebDriver getWebDriverObject(DesiredCapabilities capabilities) { EdgeOptions options = new EdgeOptions(); options.merge(capabilities); return new EdgeDriver(options); } }, SAFARI { public RemoteWebDriver getWebDriverObject (DesiredCapabilities capabilities) { SafariOptions options = new SafariOptions(); options.merge(capabilities); return new SafariDriver(options); } }, OPERA { public RemoteWebDriver getWebDriverObject (DesiredCapabilities capabilities) { OperaOptions options = new OperaOptions(); options.merge(capabilities); return new OperaDriver(options); } }; public final static boolean HEADLESS = Boolean.getBoolean("headless"); }
We can now run our project again exactly the same way as before:
mvn clean verify -Dthreads=2
You will see the test start up again, but this time you won't see a browser window pop up. Everything should work exactly as before and the tests should pass as expected. If you want to see the browser window pop up again, you can just set headless to false
:
mvn clean verify -Dthreads=2 -Dheadless=false
What happened to GhostDriver?
You may have noticed that I haven't mentioned GhostDriver or PhantomJS at all in the headless section. That's because PhatomJS is no longer under active development and GhostDriver no longer has a core maintainer. PhantomJS is still available and it's possible to get GhostDriver up and running. However, you are then testing with these issues:
- An out-of-date rendering engine (an old version of QTWebkit)
- A JavaScript engine that is not used in any of the major browsers
- A tool that is not completely thread safe
With the release of headless modes from ChromeDriver and FirefoxDriver, it just doesn't make sense to keep using PhantomJS. It was great in its heyday, but it just not a useful tool to use with Selenium any more.