So far, so good. But the existing code deserves to be improved.

Use coarse-grained methods mapped to a business case. For example, instead of having a whole bunch of selectTitle() , fillFirstName() , fillLastName() , submitRegistration() , etc. methods for each registration field, have a single register() method that inputs and submits the data. Again, this isolates possible breaking changes in the page class.

Use id attributes on elements used for selection. This makes it less likely to break the test by changing the structure of the DOM.

There are a couple of good practices there - suggested by colleagues and from my personal experience:

Mixing selectors and tests into the same class makes tests brittle, especially in the early stage of the project when the GUI changes a lot. Isolating selectors into a dedicated class let us buffer changes into that class only.

If you’ve read the step definition class above, you might have noticed that there’s no Selenium dependency anywhere in the code. All of it has been hidden in a class that represents the page:

I cheated a little for this one as it was already implemented in the first TestNG MVP but let’s pretend otherwise.

Improved design

The Page needs to select components through the Selenium API, thus it needs a reference to a WebDriver . This is a problem when a single feature contains several scenarios as this reference needs to be shared among all scenarios. Possible solutions to this include:

A single scenario per feature. Every scenario will have to define its starting point. For our e-commerce checkout scenario, this defeats the purpose of testing itself. A single scenario containing steps of all scenarios. In this case, all scenarios will be merged into a very long one. That makes for a hard-to-read scenario and an even harder to read (and maintain) class. To be able to have multiple scenarios per feature while putting methods into their relevant step definitions, one needs to share the same driver instance among all step definitions. This can be achieved by applying the Singleton pattern to a dedicated class. DI ! Actually, Cucumber The last alternative is to use…​! Actually, Cucumber integrates quite nicely to integrate with some commons DI frameworks, including Weld and Spring.

This is great news, as it’s possible to use the libraries we already use in development in the tests. Regular readers know me as a Spring proponent, so I naturally used it as the DI framework. Those are dependencies that are required for that in the POM :

<!-- Cucumber Spring integration --> <dependency> <groupId> info.cukes </groupId> <artifactId> cucumber-spring </artifactId> <version> ${cucumber.version} </version> <scope> test </scope> </dependency> <!-- Spring --> <dependency> <groupId> org.springframework </groupId> <artifactId> spring-core </artifactId> <version> ${spring.version} </version> <scope> test </scope> </dependency> <dependency> <groupId> org.springframework </groupId> <artifactId> spring-beans </artifactId> <version> ${spring.version} </version> <scope> test </scope> </dependency> <dependency> <groupId> org.springframework </groupId> <artifactId> spring-context </artifactId> <version> ${spring.version} </version> <scope> test </scope> </dependency> <!-- Spring test - mandatory but not available via transitivity --> <dependency> <groupId> org.springframework </groupId> <artifactId> spring-test </artifactId> <version> ${spring.version} </version> <scope> test </scope> </dependency>

At this point, it’s quite easy to create a standard Spring configuration file to generate the driver, as well as the page objects and their necessaries dependencies:

@Configuration open class AppConfiguration { @Bean open fun driver () = ChromeDriver () @Bean open fun contextConfigurator () = ContextConfigurator ( properties ) @Bean open fun homePage ( contextConfigurator : ContextConfigurator ) = HomePage ( driver (), contextConfigurator ) // Other page beans }

In thie configuration, the driver bean is a singleton managed by Spring and the single instance can be shared among all page beans.