There is an interface WebElement.
I want to make a wrapper UIElement, that implements WebElement.
I've overridden all methods.
But faced one problem with findElementS method that returns List<WebElement>. I've tried to change return type to List<UIElement>, but code returns an error: "attempting to use incompatible return type". And it's weird for me, because it was not a problem to override findElement method and specify UIElement as return type.
Here is my method that IDE doesn't give to use with error from above:
List<UIElement> list = new ArrayList<>();
for (WebElement el : this.element.findElements(by)) {
UIElement uiElement = new UIElement(this.driver, el);
list.add(uiElement);
}
return list;
Here is UIElement class and constructors:
public class UIElement implements WebElement {
private final WebDriver driver;
private final WebElement element;
private final Actions actions;
private final JavascriptExecutor jsExecutor;
private final Waiter waiter;
public UIElement(WebDriver driver, By by) {
this.driver = driver;
this.element = this.driver.findElement(by);
this.actions = new Actions(this.driver);
this.jsExecutor = (JavascriptExecutor) this.driver;
this.waiter = new Waiter(this.driver);
}
public UIElement(WebDriver driver, WebElement element) {
this.driver = driver;
this.element = element;
this.actions = new Actions(this.driver);
this.jsExecutor = (JavascriptExecutor) this.driver;
this.waiter = new Waiter(this.driver);
}
}
It is not possible to change the "List of Webelement" to "List of UIElement" in the overrided method.
You cant change generic type of List to subtype of that generic type. Problem is that reference to any instance can be subtype of that instance
Refer this link for more details- Stackoverflow
I use Selenium.
I have Page Object like this:
public class Portal extends Utils{
private WebDriver driver;
private final By getReason= By.xpath("//a[contains(text(),'Get Sol')]");
public Portal(WebDriver driver) {
super(driver);
this.driver = driver;
}
public VisitReason clickCurrentReason() {
clickIn(getReason);
return new VisitReason(driver);
}
}
I would like this method: clickCurrentReason() return new Object extend Utils class and passed it parameter: driver.
How to do it?
I know I have to use generics. I found part of solution:
public<T extends Utils> T clickCurrentReason(){
clickIn(getReason);
return (T)(driver);
}
But how passed in return: "return new Object(driver)"
#Test method:
public void Test() {
TestingEnv testingEnv = new TestingEnv(driver);
Portal portal = testingEnv.openPage();
VisitReason visitReason = portal.clickCurrentReason();
//sometimes instead of the last line it will be: VisitInSpot visitInSpot = portal.clickCurrentReason();
//sometimes instead of the last line it will be: VisitBack visitBack = portal.clickCurrentReason();
}
package com.objects;
import java.util.ArrayList;
import java.util.List;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.How;
import org.openqa.selenium.support.PageFactory;
import com.tests.BaseClass;
public class LinkedInHomePage extends BaseClass {
public LinkedInHomePage(WebDriver driver) {
PageFactory.initElements(driver, this);
}
#FindBy(how = How.ID, using = "firstName-coldRegistrationForm")
public WebElement newFirstNameTexthBox;
#FindBy(how = How.NAME, using = "lastName")
public WebElement newLastNameTexthBox;
#FindBy(how = How.ID, using = "email-coldRegistrationForm")
public WebElement newEmailTexthBox;
#FindBy(how = How.ID, using = "password-coldRegistrationForm")
public WebElement newPasswordTexthBox;
#FindBy(how = How.ID, using = "btn-submit")
public WebElement signUpButton;
public void EnterNewFirstName(String inComingNewFirstName) {
newFirstNameTexthBox.clear();
newFirstNameTexthBox.sendKeys(inComingNewFirstName);
}
public void EnterNewLastName(String inComingNewLastName) {
newLastNameTexthBox.clear();
newLastNameTexthBox.sendKeys(inComingNewLastName);
}
public void EnterNewEmail(String inComingNewEmail) {
newEmailTexthBox.clear();
newEmailTexthBox.sendKeys(inComingNewEmail);
}
public void EnterNewPassword(String inComingNewPassword) {
newPasswordTexthBox.clear();
newPasswordTexthBox.sendKeys(inComingNewPassword);
}
public void ClickSignUp() {
signUpButton.click();
}
public void JoinNow(String FName, String LName, String Email,
String Password) {
EnterNewFirstName(FName);
EnterNewLastName(LName);
EnterNewEmail(Email);
EnterNewPassword(Password);
ClickSignUp();
}
}
The JoinNow() function above as you can see takes multiple parameters and it works perfectly fine. I would like use array or list to reduce
the number of arguments and then use loop to fill the text boxes. I want to accomplish something similar as below, but since I am using Page Object Model Design, can't use findElement.
#SuppressWarnings("unchecked")
protected void JoinNow(String... var) {
List<MyElements> inputElements = new ArrayList<MyElements>();
inputElements.add((MyElements) driver.findElement(By
.id("firstName-coldRegistrationForm")));
inputElements.add((MyElements) driver.findElement(By.id("lastName")));
inputElements.add((MyElements) driver.findElement(By
.id("email-coldRegistrationForm")));
inputElements.add((MyElements) driver.findElement(By
.id("password-coldRegistrationForm")));
for (int i = 0; i < var.length; i++) {
((WebElement) inputElements.get(i)).sendKeys(var[i]);
}
}
Why not send a Map to JoinNow method. I suggest you keep your PageObject pattern as is. I believe this is more readable and more maintainable long term
Map<String,String> data = new HashMap<String,String>();
data.put("firstname", "George");
data.put("lastname", "Clooney");
data.put("email", "George#xyz.com");
data.put("password", "Dontguess");
JoinNow(data);
protected void JoinNow(Map<String,String> data) {
firstNameElement.sendKeys(data.get("firstname"));
lastNameElement.sendKeys(data.get("lastname"));
emailElement.sendKeys(data.get("email"));
passwordElement.sendKeys(data.get("password"));
}
There are couple of items you need to consider while doing page object model. Based on how you have done it (by using #FindBy), sometimes you will end up with stale data. For better results you should be defining your ids like below and use them to get you Webelemment. This is how i would design it. Define all your By.id and put it into a list and use the function below to simplify it.
Definition of by:
static final By FNAME_REGFORM_BY_ID = By.id("firstName-coldRegistrationForm");
public void fillTextBoxes(List<By> bys, List<String> valuesToPopulate) {
List<WebElement> webelements = new ChromeDriver().findElements(By.cssSelector("input[type=text]"));
for (int i = 0; i < bys.size(); i++) {
new ChromeDriver().findElement(bys.get(i)).sendKeys(valuesToPopulate.get(i));
}
}
Edit: For better understanding, fillTextBoxes was equivalent of joinNow.
List<String> values=new LiskedList<String>();
Values.add("emailid");
values.add("firstname");
List<By> bys=new LinkedList<By>();
bys.add(FNAME_REGFORM_BY_ID);
bys.add(EMAIL_ID_By_ID);
public void joinNow(List<By> bys, List<String> values) {
List<WebElement> webelements = new ChromeDriver().findElements(By.cssSelector("input[type=text]"));
for (int i = 0; i < bys.size(); i++) {
new ChromeDriver().findElement(bys.get(i)).sendKeys(valuesToPopulate.get(i));
}
}
Don't use Annotated #FindBy(how = How.ID, using = "email-coldRegistrationForm") etc, as this slow down execution as well as gives you stale data. FindBy tends to slow down as the entire page object becomes completely initialized only after all elements are available. Try using Static By like i mentioned. We ran into perf issues and started switching from annotated Findby.
Based on request, here is a sample implementation. You might have to tweak based on your project requirements, but this should get started.
Your Page Class (renamed as SO for my sake).
public class SO {
WebDriver driver;
static final By FNAME_REGFORM_BY_ID = By.id("firstName-coldRegistrationForm");
static final By LNAME_REGFORM_BY_NAME = By.className("lastName");
static final By EMAIL_REGFORM_BY_ID = By.id("email-coldRegistrationForm");
static final By PWD_REGFORM_BY_ID = By.id("password-coldRegistrationForm");
static final By SUBMIT_REGFORM_BY_ID = By.id("btn-submit");
public SO(WebDriver driver) {
this.driver = driver;
}
public List<By> getAllInput() {
List<By> bys = new LinkedList<By>();
bys.add(FNAME_REGFORM_BY_ID);
bys.add(LNAME_REGFORM_BY_NAME);
bys.add(LNAME_REGFORM_BY_NAME);
bys.add(LNAME_REGFORM_BY_NAME);
bys.add(LNAME_REGFORM_BY_NAME);
return bys;
}
public void joinNow(List<String> values) {
PageFactory.initElements(this.driver, HotelDetailsPage.class);
List<By> bys = this.getAllInput();
List<WebElement> webElements = this.driver.findElements(By.cssSelector("input[type=text]"));
for (int i = 0; i < bys.size(); i++) {
new ChromeDriver().findElement(bys.get(i)).sendKeys(values.get(i));
}
}
public SO open(final String url) {
this.driver.get(url);
return this;
}
}
Test Method:
#Test
public void testForm() {
WebDriver driver = new FirefoxDriver();
SO so = new SO(driver);
SO soPage = so.open("your app url");
List<String> values = new LinkedList<String>();
values.add("firstname");
values.add("lastName");
values.add("emailId");
values.add("password");
values.add("id");
soPage.joinNow(values);
}
Following is the another way:
HashMap<WebElement, String> d = new HashMap<WebElement, String>();
d.put(newFirstNameTexthBox, "Michael");
d.put(newLastNameTexthBox, "Johnson");
d.put(newEmailTexthBox, "a#b.com");
d.put(newPasswordTexthBox, "Michael123$");
joinNow(d);
protected void JoinNow(Map<WebElement,String> data) {
for (Entry<WebElement, String> objValue : data.entrySet())
objValue.getKey().sendKeys(objValue.getValue());
}
I created 4 classes also after I decided to convert my project into this design pattern. I moved my codes inside the related classes into the methods. While compiling I'm facing failures and I don't know why.
The main class
GidiyorTest.java
public class GidiyorTest {
protected WebDriver driver;
protected String baseUrl;
private boolean acceptNextAlert = true;
private StringBuffer verificationErrors = new StringBuffer();
static GidiyorTest gittiGidiyor = new GidiyorTest();
static String generatedMail = gittiGidiyor.generateString();
static String generatedUsername = gittiGidiyor.generateString();
static RegisterPage registerPage = new RegisterPage();
static LoginPage loginPage = new LoginPage();
static SearchPage searchPage = new SearchPage();
static DiscountsPage discountsPage = new DiscountsPage();
public String generateString(){
char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
StringBuilder sb = new StringBuilder();
Random random = new Random();
for (int i = 0; i < 7; i++) {
char c = chars[random.nextInt(chars.length)];
sb.append(c);
}
String output = sb.toString();
return output;
}
#Before
public void setUp() throws Exception {
driver = new FirefoxDriver();
baseUrl = "https://www.gittigidiyor.com/";
driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
}
#Test
public void testGidiyor() throws Exception {
registerPage.Register();
loginPage.Login();
searchPage.Search();
discountsPage.Discount();
}
#After
....
RegisterPage.java (One of the four new classes for instance sharing just one)
public class RegisterPage extends GidiyorTest {
public void Register() throws InterruptedException {
driver.get(baseUrl + "/kayit-ol");
driver.findElement(By.name("name")).clear();
driver.findElement(By.name("name")).sendKeys("murat");
driver.findElement(By.name("surname")).clear();
driver.findElement(By.name("surname")).sendKeys("yilmaz");
Thread.sleep(300);
driver.findElement(By.id("suggestion_email_input_verifier")).clear();
driver.findElement(By.id("suggestion_email_input_verifier")).sendKeys(
generatedMail + "#gmail.com");
driver.findElement(By.id("nickname")).clear();
driver.findElement(By.id("nickname")).sendKeys(generatedUsername);
Thread.sleep(300);
driver.findElement(By.name("passwd")).clear();
driver.findElement(By.name("passwd")).sendKeys("123456abc");
driver.findElement(By.name("passwd2")).clear();
driver.findElement(By.name("passwd2")).sendKeys("123456abc");
Thread.sleep(300);
driver.findElement(By.id("cepgsm")).clear();
driver.findElement(By.id("cepgsm")).sendKeys("531");
driver.findElement(By.id("cep")).clear();
driver.findElement(By.id("cep")).sendKeys("600 29 79");
Thread.sleep(1000);
driver.findElement(By.id("SubmitForm")).click();
}
}
And the error is beginning at registerPage.Register(); line. One another is java.lang.NullPointerException.
Hope you can help.
The way you're creating your PageObject is not correct. You should not extend the test, one of the main points is that the PageObject should not know anything about the test, rather just expose the services offered by the page.
On the other hand your test should hold the assertions and other test related logic.
The second wrong thing is that you should use PageFactory to instantiate your page object, so that you can take advantage of the lazy binding mechanism. So change to something like this
public class RegisterPage {
private WebDriver driver;
public RegisterPage(WebDriver driver) {
this.driver = driver;
}
// The rest of your class
}
and instantiate inside the test using PageFactory
PageFactory.initElements(driver, RegisterPage.class);
also to ease up maintainence and benefit from lazy element binding you can think about adding your elements as fields, and mark them via annotation, so they get populated by PageFactory as well e.g.
public class RegisterPage {
private WebDriver driver;
public RegisterPage(WebDriver driver) {
this.driver = driver;
}
#FindBy(name = "name")
private WebElement name;
...
}
}
For example, if I were to test Google search, what is the benefit of the Page Object model returning new Google Search Page Object?
E.g.
public class SearchPage {
private final WebDriver driver;
public SearchPage(WebDriver driver) {
this.driver = driver;
}
public SearchPage search(String query) {
WebElement e = driver.findElement(By.name("q")).sendKeys(query);
e.submit();
return new SearchPage(driver);
}
}
vs
public class SearchPage {
private final WebDriver driver;
public SearchPage(WebDriver driver) {
this.driver = driver;
}
public void search(String query) {
WebElement e = driver.findElement(By.name("q")).sendKeys(query);
e.submit();
}
}
Thanks for the help!
One thing that comes to my mind is chaining the methods from SearchPage class. When you would have lets say some higher level class that is responsible for running the tests you could use sth like this:
String actualText = searchPage.search("q").openFirstResult().selectItemFromCombo().checkName().getNameText()
etc. etc.
This makes reading your code very easy, looks almost like a sentence and it is understandable for othe people.