Issue
So I inherited an automation project (JAVA11 based using Selenium/TestNG) and they want it refactored to utilize Fluent Design for the code base so that it is easier for non-coders to start a test build. Makes Sense...however...they are using Nested JAVA Classes throughout their Page Map Factory and would like to still keep it that way.
I'm good with C# but not so much JAVA and Nested Classes. Below is an example of what I'm talking about. They basically want the Fluent design so if they need to access a nested class they can call it within the using event of the Parent Class this way it does not affect the chain.
I've looked around for examples of this with Selenium but having trouble finding some. Any assistance is appreciated.
\\Code Base
public class UserAdminPage
{
@SuppressWarnings("unused")
private static WebElement element = null;
public UserAdminPage(RemoteWebDriver driver)
{
PageFactory.initElements(driver, this);
}
public static UserAdminPage using(RemoteWebDriver driver)
{
return new UserAdminPage(driver);
}
public static class AddEditUserInformation
{
@FindBy(name = "firstName")
public WebElement FirstName_txtbx;
public UserAdminPage FirstName_txtbx(String firstname)
{
this.FirstName_txtbx.sendKeys(firstname);
return this;
}
@FindBy(name = "lastName")
public WebElement LastName_txtbx;
public AddEditUserInformation LastName_txtbx(String lastname)
{
this.LastName_txtbx.sendKeys(lastname);
return this;
}
}
@FindBy(id = "AccountsList")
public WebElement AccountName_dropdwn;
public void AccountName_dropdwn()
{
this.AccountName_dropdwn.click();
}
@FindBy(id = "Save_button")
public WebElement Save_btn;
public void Save_btn()
{
this.Save_btn.click();
}
}
\\Test Event
UserAdminPage.using(driver).AccountName_dropdwn()
.AddEditUserInformation.FirstName_txtbx("John")
.AddEditUserInformation.LastName_txtbx("Doe")
.Save_btn()
Solution
As Zag pointed out in his answer, the "nested classes" you are speaking about are effectively "regular classes" (since they are static), the only unusual thing here is that they are placed in the same file as other classes.
You cannot have a good "fluent design" with classes that have nothing in common (no common interface, no use of generics) unless you are willing to make some sacrifices. The code below shows a compromise using "on" methods: there is a chain but you will manually need to move the chain from using one class to the other (this is typically something that a good "fluent design" will hide and automtically do for you so you do not have to think about it).
Note that the code below compiles and can be executed (via the "main" method) - this is how your code should be presented in your question(s) if at all possible.
package so;
public class UserAdminPage {
public static void main(String[] args) {
// test code
System.out.println("Start test.");
UserAdminPage.using(new RemoteWebDriver())
.AccountName_dropdwn()
.onAddEditUserInformation()
.FirstName_txtbx("John")
.LastName_txtbx("Doe")
.onUserAdminPage()
.Save_btn();
System.out.println("Finished test.");
}
public static UserAdminPage using(RemoteWebDriver driver) {
return new UserAdminPage(driver);
}
private WebElement AccountName_dropdown = new WebElement();
private WebElement Save_btn = new WebElement();
public UserAdminPage(RemoteWebDriver driver) {
PageFactory.initElements(driver, this);
}
public UserAdminPage AccountName_dropdwn() {
AccountName_dropdown.click();
return this;
}
public UserAdminPage Save_btn() {
Save_btn.click();
return this;
}
public AddEditUserInformation onAddEditUserInformation() {
return new AddEditUserInformation(this);
}
// nested class
public static class AddEditUserInformation {
private UserAdminPage page;
public WebElement FirstName_txtbx = new WebElement();
public WebElement LastName_txtbx = new WebElement();
public AddEditUserInformation(UserAdminPage page) {
this.page = page;
}
public AddEditUserInformation FirstName_txtbx(String firstname) {
this.FirstName_txtbx.sendKeys(firstname);
return this;
}
public AddEditUserInformation LastName_txtbx(String lastname) {
this.LastName_txtbx.sendKeys(lastname);
return this;
}
public UserAdminPage onUserAdminPage() {
return page;
}
}
// external classes
public static class WebElement {
public void sendKeys(String keys) {}
public void click() {}
}
public static class RemoteWebDriver {}
public static class PageFactory {
public static void initElements(RemoteWebDriver driver, UserAdminPage page) {}
}
}
Answered By - vanOekel
Answer Checked By - Terry (JavaFixing Volunteer)