If you save the code structure that you mentioned in the question, sooner or later, saving it will be a nightmare. Try to adhere to the rule: the same test code (once) for all browsers (environments).
This condition will force you to solve two problems:
1) how to run tests for all selected browsers
2) how to apply certain browser workarounds without polluting the test code
Actually, this seems to be your question.
This is how I solved the first problem. First, I defined all the environments that I am going to test. I call the "environment" all the conditions in which I want to run my tests: browser name, version number, OS, etc. So, apart from the test code, I created an enumeration as follows:
public enum Environments { FF_18_WIN7("firefox", "18", Platform.WINDOWS), CHR_24_WIN7("chrome", "24", Platform.WINDOWS), IE_9_WIN7("internet explorer", "9", Platform.WINDOWS) ; private final DesiredCapabilities capabilities; private final String browserName; private final String version; private final Platform platform; Environments(final String browserName, final String version, final Platform platform) { this.browserName = browserName; this.version = version; this.platform = platform; capabilities = new DesiredCapabilities(); } public DesiredCapabilities capabilities() { capabilities.setBrowserName(browserName); capabilities.setVersion(version); capabilities.setPlatform(platform); return this.capabilities; } public String browserName() { return browserName; } }
Easily modify and add environments when you need to. As you can see, I use this to create and get DesiredCapabilities, which will later be used to create a specific WebDriver.
In order for tests to run for all specific environments, I used JUnit (4.10 in my case) org.junit.experimental.theories :
@RunWith(MyRunnerForSeleniumTests.class) public class MyWebComponentTestClassIT { @Rule public MySeleniumRule selenium = new MySeleniumRule(); @DataPoints public static Environments[] enviroments = Environments.values(); @Theory public void sample_test(final Environments environment) { Page initialPage = LoginPage.login(selenium.driverFor(environment), selenium.getUserName(), selenium.getUserPassword());
Tests are annotated as @Theory (not as @Test , as in regular JUnit tests) and the parameter is passed. Each test will then be performed for all defined values โโof this parameter, which should be an array of values โโannotated as @DataPoints . In addition, you should use a runner that extends from org.junit.experimental.theories.Theories . I use org.junit.rules to prepare my tests by putting all the necessary plumbing there. As you can see, I also get a driver of certain features through the rule. Although you can use the following code directly in your test:
RemoteWebDriver driver = new RemoteWebDriver(new URL(some_url_string), environment.capabilities());
The fact is that, having it in the rule, you write the code once and use it for all your tests. As for the page class, this is the class into which I put all the code that uses the driver functionality (find an element, navigate, etc.). Thus, again, the test code remains neat and understandable, and, again, you write it once and use it in all of your tests. So this is the solution to the first problem. (I know you can do a similar thing with TestNG, but I have not tried.)
To solve the second problem, I created a special package in which I save all the possible ways to bypass the browser. It consists of an abstract class, for example. BrowserSpecific, which contains generic code that is different (or has an error) in another browser. In the same package, I have classes specific to each browser used in the tests, and each of them extends BrowserSpecific.
Here's how it works for the Chrome driver error you mention. I create a clickOnButton method in BrowserSpecific with common code for the behavior affected:
public abstract class BrowserSpecific { protected final RemoteWebDriver driver; protected BrowserSpecific(final RemoteWebDriver driver) { this.driver = driver; } public static BrowserSpecific aBrowserSpecificFor(final RemoteWebDriver driver) { BrowserSpecific browserSpecific = null; if (Environments.FF_18_WIN7.browserName().contains(driver.getCapabilities().getBrowserName())) { browserSpecific = new FireFoxSpecific(driver); } if (Environments.CHR_24_WIN7.browserName().contains(driver.getCapabilities().getBrowserName())) { browserSpecific = new ChromeSpecific(driver); } if (Environments.IE_9_WIN7.browserName().contains(driver.getCapabilities().getBrowserName())) { browserSpecific = new InternetExplorerSpecific(driver); } return browserSpecific; } public void clickOnButton(final WebElement button) { button.click(); } }
and then I override this method in a specific class, for example. ChromeSpecific where I post the workaround:
public class ChromeSpecific extends BrowserSpecific { ChromeSpecific(final RemoteWebDriver driver) { super(driver); } @Override public void clickOnButton(final WebElement button) {
When I have to consider the specific behavior of a browser, I do the following:
aBrowserSpecificFor(driver).clickOnButton(logoutButton);
instead:
button.click();
That way, in my general code, I can easily determine where the workaround was applied, and I keep the workarounds isolated from the general code. I find it easy to maintain, since errors are usually resolved and workarounds can or should be changed or fixed.
Last word on test execution. Since you are going to use Selenium Grid, you will want to use the ability to run tests in parallel, so be sure to configure this feature for your JUnit tests (available since version 4.7).