Skip to content

Commit

Permalink
Introduce classindex dependency and auto-discover WebDriverFactory im…
Browse files Browse the repository at this point in the history
…plementations.
  • Loading branch information
Toilal committed Aug 22, 2016
1 parent 8357c20 commit 0c22cc5
Show file tree
Hide file tree
Showing 11 changed files with 138 additions and 37 deletions.
4 changes: 4 additions & 0 deletions README.md
Expand Up @@ -925,6 +925,10 @@ public class BrowserStackWebDriverFactory implements WebDriverFactory {
} }
``` ```


This implementation will bediscovered with [classindex](https://github.com/atteo/classindex), a
compile-time alternative to run-time classpath scanning. It requires your IDE to have Annotation Processing
enabled in the Java Compiler configuration.

Instead of implementing a new ```WebDriverFactory``` class, you may also override ```newWebDriver()``` in the Test Instead of implementing a new ```WebDriverFactory``` class, you may also override ```newWebDriver()``` in the Test
class, but doing so will ignore any value defined in ```webDriver``` configuration property. class, but doing so will ignore any value defined in ```webDriver``` configuration property.


Expand Down
4 changes: 4 additions & 0 deletions fluentlenium-core/pom.xml
Expand Up @@ -23,6 +23,10 @@
<groupId>javax.inject</groupId> <groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId> <artifactId>javax.inject</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.atteo.classindex</groupId>
<artifactId>classindex</artifactId>
</dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
Expand Down
@@ -0,0 +1,16 @@
package org.fluentlenium.configuration;

/**
* Add alternative names to an object.
*
* {@link WebDriverFactory} implementations can also implement this interface to be registered in {@link WebDrivers}
* registry with those alternative names.
*/
public interface AlternativeNames {
/**
* Get the alternative names.
*
* @return
*/
String[] getAlternativeNames();
}
@@ -0,0 +1,28 @@
package org.fluentlenium.configuration;

public class DefaultWebDriverFactories {
public static class FirefoxWebDriverFactory extends ReflectiveWebDriverFactory {
public FirefoxWebDriverFactory() {
super("firefox", "org.openqa.selenium.firefox.FirefoxDriver");
}
}

public static class ChromeWebDriverFactory extends ReflectiveWebDriverFactory {
public ChromeWebDriverFactory() {
super("chrome", "org.openqa.selenium.chrome.ChromeDriver");
}
}

public static class InternetExplorerWebDriverFactory extends ReflectiveWebDriverFactory {
public InternetExplorerWebDriverFactory() {
super("ie", "org.openqa.selenium.ie.InternetExplorerDriver");
}
}

public static class HtmlUnitWebDriverFactory extends ReflectiveWebDriverFactory {
public HtmlUnitWebDriverFactory() {
super("htmlunit", "org.openqa.selenium.htmlunit.HtmlUnitDriver");
}
}

}
Expand Up @@ -9,13 +9,15 @@
/** /**
* A simple {@link WebDriverFactory} that create {@link WebDriver} instances using reflection. * A simple {@link WebDriverFactory} that create {@link WebDriver} instances using reflection.
*/ */
public class ReflectiveWebDriverFactory implements WebDriverFactory { public class ReflectiveWebDriverFactory implements WebDriverFactory, AlternativeNames {
private String name;
private Object[] args; private Object[] args;
private String webDriverClassName; private String webDriverClassName;
private Class<? extends WebDriver> webDriverClass; private Class<? extends WebDriver> webDriverClass;
private boolean available; private boolean available;


public ReflectiveWebDriverFactory(String webDriverClassName, Object... args) { public ReflectiveWebDriverFactory(String name, String webDriverClassName, Object... args) {
this.name = name;
this.webDriverClassName = webDriverClassName; this.webDriverClassName = webDriverClassName;
this.args = args; this.args = args;
try { try {
Expand All @@ -26,7 +28,8 @@ public ReflectiveWebDriverFactory(String webDriverClassName, Object... args) {
} }
} }


public ReflectiveWebDriverFactory(Class<? extends WebDriver> webDriverClass, Object... args) { public ReflectiveWebDriverFactory(String name, Class<? extends WebDriver> webDriverClass, Object... args) {
this.name = name;
this.webDriverClass = webDriverClass; this.webDriverClass = webDriverClass;
this.args = args; this.args = args;
this.webDriverClassName = webDriverClass.getName(); this.webDriverClassName = webDriverClass.getName();
Expand Down Expand Up @@ -55,7 +58,12 @@ public WebDriver newWebDriver() {
} }


@Override @Override
public String[] getNames() { public String getName() {
return name;
}

@Override
public String[] getAlternativeNames() {
if (webDriverClass != null) { if (webDriverClass != null) {
return new String[]{webDriverClass.getName(), webDriverClass.getSimpleName()}; return new String[]{webDriverClass.getName(), webDriverClass.getSimpleName()};
} }
Expand Down
@@ -1,10 +1,14 @@
package org.fluentlenium.configuration; package org.fluentlenium.configuration;


import org.atteo.classindex.IndexSubclasses;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;


import javax.inject.Named;

/** /**
* Factory of {@link WebDriver}, that can be registered in {@link WebDrivers} registry. * Factory of {@link WebDriver}, that can be registered in {@link WebDrivers} registry.
*/ */
@IndexSubclasses
public interface WebDriverFactory { public interface WebDriverFactory {
/** /**
* Creates a new instance of {@link WebDriver}. * Creates a new instance of {@link WebDriver}.
Expand All @@ -14,9 +18,11 @@ public interface WebDriverFactory {
WebDriver newWebDriver(); WebDriver newWebDriver();


/** /**
* Names of this factory. * Primary name of this factory.
* *
* @return * To register it with alternative name, use {@link AlternativeNames}.
*
* @return Primary name.
*/ */
String[] getNames(); String getName();
} }
@@ -0,0 +1,9 @@
package org.fluentlenium.configuration;

import org.atteo.classindex.processor.ClassIndexProcessor;

public class WebDriverFactoryProcessor extends ClassIndexProcessor {
public WebDriverFactoryProcessor() {
indexSubclasses(WebDriverFactory.class);
}
}
@@ -1,15 +1,18 @@
package org.fluentlenium.configuration; package org.fluentlenium.configuration;


import lombok.experimental.Delegate; import lombok.experimental.Delegate;
import org.atteo.classindex.ClassIndex;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;


import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;


/** /**
* A registry of {@link WebDriverFactory}. * A registry of {@link WebDriverFactory}.
* <p> * <p>
* Supported drivers are "firefox", "htmlunit". * Supported drivers are "firefox", "chrome", "ie", "htmlunit", or any class name implementing {@link WebDriver}.
*/ */
public enum WebDrivers { public enum WebDrivers {
INSTANCE; INSTANCE;
Expand All @@ -19,10 +22,21 @@ public enum WebDrivers {


static class Impl { static class Impl {
Impl() { Impl() {
register("firefox", new ReflectiveWebDriverFactory("org.openqa.selenium.firefox.FirefoxDriver")); Iterable<Class<? extends WebDriverFactory>> factoryClasses = ClassIndex.getSubclasses(WebDriverFactory.class);
register("chrome", new ReflectiveWebDriverFactory("org.openqa.selenium.chrome.ChromeDriver")); for (Class<? extends WebDriverFactory> factoryClass : factoryClasses) {
register("ie", new ReflectiveWebDriverFactory("org.openqa.selenium.ie.InternetExplorerDriver")); if (factoryClass == ReflectiveWebDriverFactory.class) continue;
register("htmlunit", new ReflectiveWebDriverFactory("org.openqa.selenium.htmlunit.HtmlUnitDriver")); if (Modifier.isAbstract(factoryClass.getModifiers())) continue;
if (!Modifier.isPublic(factoryClass.getModifiers())) continue;
WebDriverFactory factory;
try {
factory = factoryClass.getConstructor().newInstance();
} catch (NoSuchMethodException e) {
throw new ConfigurationException(factoryClass + " should have a public default constructor.", e);
} catch (Exception e) {
throw new ConfigurationException(factoryClass + " can't be instantiated.", e);
}
register(factory);
}
} }


private Map<String, WebDriverFactory> factories = new HashMap<>(); private Map<String, WebDriverFactory> factories = new HashMap<>();
Expand All @@ -36,7 +50,7 @@ static class Impl {
public synchronized WebDriverFactory get(String name) { public synchronized WebDriverFactory get(String name) {
WebDriverFactory factory = factories.get(name); WebDriverFactory factory = factories.get(name);
if (factory == null) { if (factory == null) {
ReflectiveWebDriverFactory reflectiveFactory = new ReflectiveWebDriverFactory(name); ReflectiveWebDriverFactory reflectiveFactory = new ReflectiveWebDriverFactory(name, name);
if (reflectiveFactory.isAvailable()) { if (reflectiveFactory.isAvailable()) {
factories.put(name, reflectiveFactory); factories.put(name, reflectiveFactory);
factory = reflectiveFactory; factory = reflectiveFactory;
Expand All @@ -58,21 +72,29 @@ public synchronized WebDriver newWebDriver(String name) {
} }


/** /**
* Register a new {@link WebDriver} factory with the given name. * Register a new {@link WebDriver} factory.
* *
* It will also register the factory under names returned by {@link WebDriverFactory#getNames()} * It will also register the factory under names returned by {@link AlternativeNames#getAlternativeNames()} if
* it implements {@link AlternativeNames}.
* *
* @param name name to register
* @param factory {@link WebDriver} factory to register * @param factory {@link WebDriver} factory to register
*/ */
public synchronized void register(String name, WebDriverFactory factory) { public synchronized void register(WebDriverFactory factory) {
if (factories.containsKey(name)) { if (factories.containsKey(factory.getName())) {
throw new ConfigurationException("A factory is already registered with this name: " + name + " (" + factories.get(name) + ")"); throw new ConfigurationException("A factory is already registered with this name: " +
factory.getName() + " (" + factories.get(factory.getName()) + ")");
} }
factories.put(name, factory); factories.put(factory.getName(), factory);
for (String alternativeName : factory.getNames()) { if (factory instanceof AlternativeNames) {
factories.put(alternativeName, factory); for (String alternativeName : ((AlternativeNames) factory).getAlternativeNames()) {
if (factories.containsKey(alternativeName)) {
throw new ConfigurationException("A factory is already registered with this name: " +
alternativeName + " (" + factories.get(alternativeName) + ")");
}
factories.put(alternativeName, factory);
}
} }

} }
} }


Expand Down
Expand Up @@ -34,7 +34,7 @@ public FailingDriver() {


@Test @Test
public void testInexistantClass() { public void testInexistantClass() {
final ReflectiveWebDriverFactory webDriverFactory = new ReflectiveWebDriverFactory("org.fluentlenium.ThisClassDoesntExists"); final ReflectiveWebDriverFactory webDriverFactory = new ReflectiveWebDriverFactory("doesnt-exists", "org.fluentlenium.ThisClassDoesntExists");
assertThat(webDriverFactory.isAvailable()).isFalse(); assertThat(webDriverFactory.isAvailable()).isFalse();


assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { assertThatThrownBy(new ThrowableAssert.ThrowingCallable() {
Expand All @@ -45,13 +45,11 @@ public void call() throws Throwable {
}).isExactlyInstanceOf(ConfigurationException.class); }).isExactlyInstanceOf(ConfigurationException.class);


assertThat(webDriverFactory.getWebDriverClass()).isNull(); assertThat(webDriverFactory.getWebDriverClass()).isNull();

assertThat(webDriverFactory.getNames()).isEmpty();
} }


@Test @Test
public void testNonWebDriverClass() { public void testNonWebDriverClass() {
final ReflectiveWebDriverFactory webDriverFactory = new ReflectiveWebDriverFactory(getClass().getName()); final ReflectiveWebDriverFactory webDriverFactory = new ReflectiveWebDriverFactory("test-class", getClass().getName());
assertThat(webDriverFactory.isAvailable()).isFalse(); assertThat(webDriverFactory.isAvailable()).isFalse();


assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { assertThatThrownBy(new ThrowableAssert.ThrowingCallable() {
Expand All @@ -66,7 +64,7 @@ public void call() throws Throwable {


@Test @Test
public void testAbstractClass() { public void testAbstractClass() {
final ReflectiveWebDriverFactory webDriverFactory = new ReflectiveWebDriverFactory(AbstractDriver.class); final ReflectiveWebDriverFactory webDriverFactory = new ReflectiveWebDriverFactory("abstract", AbstractDriver.class);
assertThat(webDriverFactory.isAvailable()).isTrue(); assertThat(webDriverFactory.isAvailable()).isTrue();


assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { assertThatThrownBy(new ThrowableAssert.ThrowingCallable() {
Expand All @@ -79,7 +77,7 @@ public void call() throws Throwable {


@Test @Test
public void testNoConstructorClass() { public void testNoConstructorClass() {
final ReflectiveWebDriverFactory webDriverFactory = new ReflectiveWebDriverFactory(NoConstructorDriver.class); final ReflectiveWebDriverFactory webDriverFactory = new ReflectiveWebDriverFactory("no-constructor", NoConstructorDriver.class);
assertThat(webDriverFactory.isAvailable()).isTrue(); assertThat(webDriverFactory.isAvailable()).isTrue();


WebDriver webDriver = webDriverFactory.newWebDriver(); WebDriver webDriver = webDriverFactory.newWebDriver();
Expand All @@ -92,7 +90,7 @@ public void testNoConstructorClass() {


@Test @Test
public void testFailingDriverClass() { public void testFailingDriverClass() {
final ReflectiveWebDriverFactory webDriverFactory = new ReflectiveWebDriverFactory(FailingDriver.class); final ReflectiveWebDriverFactory webDriverFactory = new ReflectiveWebDriverFactory("failing", FailingDriver.class);
assertThat(webDriverFactory.isAvailable()).isTrue(); assertThat(webDriverFactory.isAvailable()).isTrue();


assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { assertThatThrownBy(new ThrowableAssert.ThrowingCallable() {
Expand All @@ -105,7 +103,7 @@ public void call() throws Throwable {


@Test @Test
public void testCustomConstructorClassInvalidArguments() { public void testCustomConstructorClassInvalidArguments() {
final ReflectiveWebDriverFactory webDriverFactory = new ReflectiveWebDriverFactory(CustomConstructorDriver.class); final ReflectiveWebDriverFactory webDriverFactory = new ReflectiveWebDriverFactory("custom_constructor", CustomConstructorDriver.class);
assertThat(webDriverFactory.isAvailable()).isTrue(); assertThat(webDriverFactory.isAvailable()).isTrue();


assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { assertThatThrownBy(new ThrowableAssert.ThrowingCallable() {
Expand All @@ -118,7 +116,7 @@ public void call() throws Throwable {


@Test @Test
public void testCustomConstructorClass() { public void testCustomConstructorClass() {
final ReflectiveWebDriverFactory webDriverFactory = new ReflectiveWebDriverFactory(CustomConstructorDriver.class, true); final ReflectiveWebDriverFactory webDriverFactory = new ReflectiveWebDriverFactory("custom_constructor", CustomConstructorDriver.class, true);
assertThat(webDriverFactory.isAvailable()).isTrue(); assertThat(webDriverFactory.isAvailable()).isTrue();


WebDriver webDriver = webDriverFactory.newWebDriver(); WebDriver webDriver = webDriverFactory.newWebDriver();
Expand All @@ -131,11 +129,12 @@ public void testCustomConstructorClass() {


@Test @Test
public void testHtmlUnitWebDriver() { public void testHtmlUnitWebDriver() {
ReflectiveWebDriverFactory webDriverFactory = new ReflectiveWebDriverFactory(HtmlUnitDriver.class.getName()); ReflectiveWebDriverFactory webDriverFactory = new ReflectiveWebDriverFactory("htmlunit", HtmlUnitDriver.class.getName());
assertThat(webDriverFactory.isAvailable()).isTrue(); assertThat(webDriverFactory.isAvailable()).isTrue();
assertThat(webDriverFactory.getWebDriverClass()).isSameAs(HtmlUnitDriver.class); assertThat(webDriverFactory.getWebDriverClass()).isSameAs(HtmlUnitDriver.class);


assertThat(webDriverFactory.getNames()).containsExactly(HtmlUnitDriver.class.getName(), HtmlUnitDriver.class.getSimpleName()); assertThat(webDriverFactory.getName()).contains("htmlunit");
assertThat(webDriverFactory.getAlternativeNames()).containsExactly(HtmlUnitDriver.class.getName(), HtmlUnitDriver.class.getSimpleName());


WebDriver webDriver = webDriverFactory.newWebDriver(); WebDriver webDriver = webDriverFactory.newWebDriver();
try { try {
Expand Down
Expand Up @@ -22,8 +22,8 @@ public WebDriver newWebDriver() {
} }


@Override @Override
public String[] getNames() { public String getName() {
return new String[]{"custom-factory"}; return "custom";
} }
} }


Expand All @@ -35,15 +35,15 @@ public void before() {
@Test @Test
public void testFirefox() { public void testFirefox() {
WebDriverFactory firefox = webDrivers.get("firefox"); WebDriverFactory firefox = webDrivers.get("firefox");
assertThat(firefox).isExactlyInstanceOf(ReflectiveWebDriverFactory.class); assertThat(firefox).isExactlyInstanceOf(DefaultWebDriverFactories.FirefoxWebDriverFactory.class);


Class<? extends WebDriver> webDriverClass = ((ReflectiveWebDriverFactory) firefox).getWebDriverClass(); Class<? extends WebDriver> webDriverClass = ((ReflectiveWebDriverFactory) firefox).getWebDriverClass();
assertThat(webDriverClass).isSameAs(FirefoxDriver.class); assertThat(webDriverClass).isSameAs(FirefoxDriver.class);
} }


@Test(expected = ConfigurationException.class) @Test(expected = ConfigurationException.class)
public void testRegisterFirefox() { public void testRegisterFirefox() {
webDrivers.register("firefox", new CustomWebDriverFactory()); webDrivers.register(new CustomWebDriverFactory());
} }


@Test @Test
Expand Down
5 changes: 5 additions & 0 deletions pom.xml
Expand Up @@ -144,6 +144,11 @@
<artifactId>javabean-tester</artifactId> <artifactId>javabean-tester</artifactId>
<version>1.4.1</version> <version>1.4.1</version>
</dependency> </dependency>
<dependency>
<groupId>org.atteo.classindex</groupId>
<artifactId>classindex</artifactId>
<version>3.4</version>
</dependency>
<dependency> <dependency>
<groupId>org.powermock</groupId> <groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId> <artifactId>powermock-module-junit4</artifactId>
Expand Down

0 comments on commit 0c22cc5

Please sign in to comment.