Start PhantomJS a single time with Arquillian and Drone


Arquillian, Drone, PhantomJS are awesome tools to write UI tests but by default scope of PhantomJS is linked to JUnit scope (class or test). When having a big suite it means Drone will fork a bunch of processes and loose a lot of time starting/stopping PhantomJS…for nothing.

This should be addressed with drone 2 but here a small workaround changing the life.

First define a single fork PhantomJS driver factory (ie start a process lazily then kill it with the (test) JVM:

import org.jboss.arquillian.drone.spi.Configurator;
import org.jboss.arquillian.drone.spi.Destructor;
import org.jboss.arquillian.drone.spi.Instantiator;
import org.jboss.arquillian.drone.webdriver.configuration.WebDriverConfiguration;
import org.jboss.arquillian.drone.webdriver.factory.PhantomJSDriverFactory;
import org.openqa.selenium.phantomjs.PhantomJSDriver;

import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;

public class SingleForkPhantomJSDriverFactory extends PhantomJSDriverFactory
        // useless but Drone gets generics brutally so we need it
        implements Configurator, Instantiator, Destructor {

    private static final Logger LOGGER = Logger.getLogger(SingleForkPhantomJSDriverFactory.class.getName());
    private static final AtomicReference DRIVER = new AtomicReference();

    @Override
    public void destroyInstance(final PhantomJSDriver instance) {
        // no-op: done by a shutdown hook
    }

    @Override
    public PhantomJSDriver createInstance(final WebDriverConfiguration configuration) {
        PhantomJSDriver driver = DRIVER.get();
        if (driver == null) {
            final PhantomJSDriver instance = super.createInstance(configuration);
            if (!DRIVER.compareAndSet(null, instance)) {
                destroyInstance(instance);
                driver = instance;
                LOGGER.info("Created PhantomJSDriver");
            } else {
                driver = DRIVER.get();

                final PhantomJSDriver theDriver = driver;
                Runtime.getRuntime().addShutdownHook(new Thread() {
                    @Override
                    public void run() {
                        SingleForkPhantomJSDriverFactory.super.destroyInstance(theDriver);
                        LOGGER.info("Destroyed PhantomJSDriver");
                    }
                });
            }
        }
        return driver;
    }
}

Then we need to override default PhantomJSDriverFactory (we could define another one but we really want to override it here). To do so just define an Arquillian extension:

public class OverridePhandtomJSDriverLifecycleExtension implements LoadableExtension {
  @Override
  public void register(final ExtensionBuilder builder) {
    // to stay compliant with default drone behavior if used
    builder.override(Configurator.class, PhantomJSDriverFactory.class, SingleForkPhantomJSDriverFactory.class);
    builder.override(Instantiator.class, PhantomJSDriverFactory.class, SingleForkPhantomJSDriverFactory.class);
    builder.override(Destructor.class, PhantomJSDriverFactory.class, SingleForkPhantomJSDriverFactory.class);
  }
}

Then register it through a META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension file (just put the qualified name of OverridePhandtomJSDriverLifecycleExtension inside).

And that’s it! now you’ll get a single PhantomJS fork for all your tests.

Tested and approved with TomEE remote adapter of course :).

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s