How to init a WebElement with a wrapped element - selenium

I have followed a tutorial on how to close random opened popups before doing any action :
https://www.vinsguru.com/selenium-webdriver-how-to-handle-annoying-random-popup-alerts/
The idea is to ccreate an ElementProxy class which implements the interface InvocationHandler. so, the proxy’s invoke method would be called first before the actual method is invoked.
So we call checkForPopupAndKill before invoking any action on a WebElement.
Then we wrap our regular WebElement with this proxy object. We basically need a class which has a method to accept a WebElement and returns the WebElement with some wrapper.
In the tutorial they init elements of the page object with wrapper elements, like this:
//first init elements
PageFactory.initElements(driver, pageobject);
//then access all the WebElements and create a wrapper
for(Field f:pageobject.getClass().getDeclaredFields()){
if(f.getType().equals(WebElement.class)){
boolean accessible = f.isAccessible();
f.setAccessible(true);
//reset the webelement with proxy object
f.set(pageobject, ElementGuard.guard((WebElement) f.get(pageobject)));
f.setAccessible(accessible);
}
}
That's because they have declared fileds with the #FindBy annotation in the page object, but in my framework I'm declaring elements as following :
WebElement elt = getSmartElement(By.cssSelector("#my_id"));
My question is, how can I init my element with with the wrapper element ?
Thanks in advance.

You can directly use the ElementGuard as shown here which wraps the actual WebElement with proxy.
WebElement elt = ElementGuard.guard(getSmartElement(By.cssSelector("#my_id")));

Related

How can I avoid having multiple xpath variables in Selenium?

i am defining 2 variables in Selenium like this:
#FindBy(xpath = "//span[contains(text(),'€11')]")
private WebElement singleTicket;
#FindBy(xpath = "//span[contains(text(),'€19')]")
private WebElement returnTicket;`
i need to pass different values in these variables for different tests e.g. "$12" but i dont want to create more variables in my page object with different prices. what is a good solution for this?
There is no way to pass parameter to #FindBy annotation. But you can write a custom function to get webelement based on specific value and tag too. so you don't need to create separate element everytime. Please have a look on below function.
public WebElement getWebElementForSpecificText(String tagName, String text) {
String formXpath= ".//"+tagName+"[contains(text(),'"+text+"')]";
return driver.findElement(By.xpath(formXpath));
}
Yu can call this function as mentioned below:
getWebElementForSpecificText("span", "€11");
getWebElementForSpecificText("span", "€12");
getWebElementForSpecificText("span", "€19");

Pros and Cons of using Own Selectors in selenium

I would like to implement my Own By Class in order to have custom selectors. What are the pros and cons of using Custom Selectors
DEV code
<button class="btn js-AddNuts" type="button" testid="addbutton">
Here Selector is testid
Reason: We are planning to have test specific selectors and design Names for all the elements in DEV Code so that if they change anything in DEV it doesn't impact test.
There is no need to create a custom locator in this case (or any case that I have run across or can think of). You can simply use the following code that uses a CSS Selector.
By.cssSelector("button[testid='addbutton']")
I would suggest that you spend some time reading and learning CSS Selectors. They are very powerful.
CSS Selector Reference
CSS Selector Tips
As you know there is no problem with creating your own locator, Selenium provides the functionality to create custom locator. Basically selenium internally uses to locate element using xPath when you are going to find element By.id, By.name etc. So you could create simply your own locator by extending By class.
If you want to create a custom locator which locate element using testid, you should create as below (Assuming you are using java) :-
import java.util.List;
import org.openqa.selenium.By;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.WebElement;
class ByTestId extends By {
private final String testId;
public ByTestId(String testId) {
this.testId = testId;
}
#Override
public List<WebElement> findElements(SearchContext context) {
return context.findElements(By
.xpath(".//*[#testid = '"+testId+"']"));
}
#Override
public WebElement findElement(SearchContext context) {
return context.findElement(By
.xpath("//*[#testid = '"+testId+"']"));
}
}
Now you can easily locate element by using testid as below :-
//for single webelement
WebElement element = driver.findElement(new ByTestId("addbutton"));
//for list of webelements
List<WebElement> elements = driver.findElements(new ByTestId("addbutton"));
Note : You can also locate the element in your custom By class using By.cssSelector as : By.cssSelector("*[testid = '"+testId+"']")
Hope it helps..:)

Where to cause initElements method using FindBy annotation

I'm trying to use page object model but I'm not sure how to init elements correctly.
Please see an example of my test class:
public class TestSuiteSubscriber extends TestInitializer {
Menu menuPage = new Menu(driver);
SubscribersSearchForm searchForm = new SubscribersSearchForm(driver);
#Test (priority = 1)
public void findByOldNumber(){
menuPage = PageFactory.initElements(driver, Menu.class);
menuPage.openSubscribers();
searchForm = PageFactory.initElements(driver, SubscribersSearchForm.class);
searchForm.subscriberNumber.sendKeys("100001");
}
If I cause .initElements in constructors of page classes, I get the NullPointerException. As I understand, this is because they had been initialized before the page is loaded. Is there a proper way to avoid causing the .initElements method directly in the test method to make tests more readable?
A very common practice of instantiating the elements using PageObject is to use a BaseClass and inherit that throughout the other page classes. So the initElements will be instantiated every time the PageObject is instantiated. I have GitHub where I implemented the concept with TestNG. See if that would help you.
In your case TestInitializer should have the initElements I believe. This line should reside in TestInitializer constructor
PageFactory.initElements(driver, this);

when do FindBy attributes trigger a driver.FindElement?

My question is: do webelements decorated with findby attributes call the findelement function upon each reference to them? If not, when?
And what is the procedure with List< webelement > which is also decorated? Does it trigger when you reference the list, or when you reference an element inside that list?
I'm asking because I have some situations where I'm getting stale element exceptions and I want to know how to deal with them.
WebElements are evaluated lazily. That is, if you never use a WebElement field in a PageObject, there will never be a call to "findElement" for it. Reference.
If don't want WebDriver to query the element each time, you have to use the #CacheLookup annotation.
What about the list part of my question?
The findElements is triggered when you query from the list. Say you have:
#FindBy(xpath = "//div[#class=\"langlist langlist-large\"]//a")
private List<WebElement> list;
Following code samples all trigger the findElements :
list.isEmpty();
WebElement element = list.get(0);
Where as
List<WebElement> newList = new ArrayList<WebElement>();
newList = list;
does not trigger the findElements().
Please check the LocatingElementListHandler class. I suggest diving into the source for answers.
You may find this code comment from PageFactory class helpful:
/**
* Instantiate an instance of the given class, and set a lazy proxy for each of the WebElement
* and List<WebElement> fields that have been declared, assuming that the field name is also
* the HTML element's "id" or "name". This means that for the class:
*
* <code>
* public class Page {
* private WebElement submit;
* }
* </code>
*
* there will be an element that can be located using the xpath expression "//*[#id='submit']" or
* "//*[#name='submit']"
*
* By default, the element or the list is looked up each and every time a method is called upon it.
* To change this behaviour, simply annotate the field with the {#link CacheLookup}.
* To change how the element is located, use the {#link FindBy} annotation.
*
* This method will attempt to instantiate the class given to it, preferably using a constructor
* which takes a WebDriver instance as its only argument or falling back on a no-arg constructor.
* An exception will be thrown if the class cannot be instantiated.
*
* #see FindBy
* #see CacheLookup
* #param driver The driver that will be used to look up the elements
* #param pageClassToProxy A class which will be initialised.
* #return An instantiated instance of the class with WebElement and List<WebElement> fields proxied
*/
For Question 1) The concept in Page Factory pattern is to identify WebElements only when they are used in any operation.
For Question 2) Whenever you try to access the Page class variables (WebElement or List) #FindBy triggers FindElement or FindElements based on the variable type.
class LoginPage{
.....
#FindBy(id, "uname")
WebElement username;// no trigger
#FindBy(xpath, "//table/tr")
List<WebElement> pdtTable; // no trigger
.....
.....
public void enterUserame(String text){
uname.sendKeys(text);
}
.....
.....
}
.....
.....
LoginPage loginPage = PageFactory
.initElements(driver, LoginPage.class); // creates WebElement variables but not triggers
if(loginPage.uname.isDisplayed()){// Trigger happens
loginPage.enterUserame("example");// Trigger happens
}
int count=pdtTable.size();// Trigger happens for FindElements
Additional Info : PageFactory annotation #CacheLookup is used to mark the WebElements once located so that the same instance in the DOM can always be used. This annotation, when applied over a WebElement, instructs Selenium to keep a cache of the WebElement instead of searching for the WebElement every time from the WebPage. This helps us save a lot of time.

getting a 'The IWebDriver object must implement or wrap a driver that implements IHasInputDevices.' error

im a newbie in c# and selenium.
Im trying to create an element extension to mouseover an element.
I've got the following:
public static void mouseoverElement(this IWebElement element, IWebDriver driver)
{
Actions actions = new Actions(driver);
actions.MoveToElement(element).Perform();
}
And this would be called from another class
public MLinks mouseOverCandidate()
{
candidateMenu.mouseoverElement(driver);
return this;
}
And this is where i will call from the test:
new HomePage(driver, server)
.MainLinks.mouseOverCandidate();
I will always get this which i don't quite understand. I've already got a driver set. Anyone can help me out on this? thanks
System.ArgumentException : The IWebDriver object must implement or wrap a driver that implements IHasInputDevices.
The method mouseoverElement is taking an IWebDriver, which is an interface.
The concrete driver class implements this interface, as well as the other interface IHasInputDevices.
So you need to pass in the concrete class, so that it can expose IHasInputDevices, as well as IWebDriver
Note that you will also get this error if the WebDriver you pass in is null.