I created test framework with Selenium and setup ExtentReports for test report. Used page object model and #FindBy annotation for fields to create own store of WebElements for each page. Now I would like to create custom annotation #Name
#Name(description = "google main page")
#FindBy(linkText = "Gmail")
private WebElement gmail;
And implementation for it, to be able to use description of each WebElement later in my report. I have my own implementation of click() method
public static void click(WebElement element) {
try{
element.click();
TestReport.addLog(LogStatus.INFO, "Element "+NameImpl.getDescription(element)+" clicked");
} catch (NoSuchElementException e) {
TestReport.addLog(LogStatus.ERROR, "Element "+NameImpl.getDescription(element)+" not found");
}
}
I'm able to get description of all elements annotated in class with reflection like here
Is it possible to read the value of a annotation in java?
but cannot get description of specific element used in my click method.
Any ideas how to achieve that?
Just from the parameter passed to the click method, there's no way the get annotations. This reason is the annotation are on the gmail field, not on the WebElement class. So the only way to get the #Name annotation is to first get the Field representing your gmail field, and that will have to be done through the declaring class:
ClassWithGmailField.class.getField("gmail").getAnnotation(Name.class).description()
Just from the parameter of the click method, you could only reach annotations defined on the WebElement class itself e.g.:
#SomeAnnotation
public class WebElement {...}
but this is not useful for anything in your case.
To achieve something similar to what you want, you could potentially:
Reflectively analyze the class, extract all #Name'd fields and collect the meta together with the field values, perhaps into some kind of wrapper e.g. NamedElement that would have the description from #Name and the WebElement itself
Reflectively call the click method providing it with the meta it needs (the description in your case). But for this you'd need to somehow know which method to invoke for each field (e.g. by yet another annotation), making your logic encoded external to your actual code. Might make sense in some cases but probably a bad idea in general.
A quick (uncompiled, untested) code example of the first idea:
public class NamedElement extends WebElement {
public String description;
public WebElement element;
public NamedElement(String description, WebElement element) {
this.description = description;
this.element = element;
}
}
public class NamedElementExtractor {
public static Collection<NamedElement> getNamedElements(Object instanceWithWebElements) {
//instanceWithElements in your case would be an instance of the class that has the "gmail" field, i.e. the one I referred to as ClassWithGmailField above
Collection<NamedElement> namedElements = new List<NamedElement>();
for (Field field : instanceWithWebElements.getClass().getDeclaredFields()) {
field.setAccessible(true);
//maybe first check field.isAnnotationPresent(Name.class)
String desc = field.getAnnotation(Name.class).description();
WebElement element = field.getValue(instanceWithWebElements);
namedElements.add(new NamedElement(desc, element));
}
}
}
...
for (NamedElement namedElement : NamedElementExtractor.getNamedElements(instanceWithWebElements))) {
Click.click(namedElement);
}
...
public static void click(NamedElement namedElement) {
try{
namedElement.element.click();
TestReport.addLog(LogStatus.INFO, "Element "+ namedElement.description +" clicked");
} catch (NoSuchElementException e) {
TestReport.addLog(LogStatus.ERROR, "Element "+ namedElement.description +" not found");
}
}
No idea if this is appropriate/usable in your case, but it's food for thought.
Related
I am struggling with a piece of code in C# that I am not sure how to change to Java:
This is the calling method:
Assert.Istrue(DashboardPage.IsAt, "Failed to login);"
This is the Dashboard class with the functionality. This is the C# that I want to convert to Java:
public class DashboardPage {
public static bool isAt {
get {
var h2 = Driver.driver().FindElements(By.TagName("h2"));
if (h2.Count>0)
return h2[0].Text =="Dashboard";
return false;
}
}
}
Because I was not able to create the above functionality in Java, I did an alternative as below.But as you can see this checks the whole page source and not for particular tag.
public static boolean isTextPresent(String text) {
try {
boolean b = Driver.driver().getPageSource().contains(text);
return b;
}
catch (Exception e) {
return false;
}
}
I guess your code should be somewhat like this:
public static boolean isTextPresent(WebDriver driver,String text){
try{
boolean b = driver.findElement(By.tagName("h2")).getText().contains(text);
return b;
}
catch(Exception e){
return false;
}
}
Above example will find the first available h2 tag in the DOM.
If you want check your text across all h2 tags, you should use findElements():
List<WebElement> tags = driver.findElements(By.tagName("h2"));
for(WebElement e:tags){
if(e.getText().contains(text))
return true;
}
Note that, here I'm passing WebDriver as an additional argument and assuming that you have declared and defined WebDriver already in the class from which you are calling (something like WebDriver driver=new FirefoxDriver()). This will allow the function to use the same instance.
Also, I have used contains() here , this will check the substring. If you want the exact match use equals() instead.
My question is regarding dynamic By Locators.
My Page classes usually looks like that:
public class MyPage {
private WebDriver driver;
private By myFixedLocator = By.xpath(".......");
private String myDynamicLoactor = "//div[#id = 'someId']" +
"//div[contains( #class, '<className>')]";
public MyPage(WebDriver driver) {this.driver = driver;}
public AnotherSuperPage getAnotherPage(String className) {
By tmpBy = By.xpath(myDynamicLocator.replace("<className>", className));
driver.findElement(tmpBy);
return new AnotherSuperPage(driver);
}
//for example here: childOne and Two are sub classes of AnotherSuperClass
public AnotherChild1Page getChildOne() {return getAnotherPage("childOne");}
public AnotherChild1Page getChildTwo() {return getAnotherPage("childTwo")}
}
Locators like myDynamicLocator represents elements, the all have similar xpath structure except of the one String part.
Is there any better way to do this? As far as I understood, the By locators are final and immutible.
This is also why I don't use Page Factory, since the #FindBy annotation I can use flexible locator as in the example above.
And when I have a By locator, can I get the text inside in a smooth way? because By.toString() gives me the whole information, including "xpath"....
You could have also achieved it by simply doing it as:
public AnotherSuperPage getAnotherPage(String className) {
driver.findElement(By.xpath("//div[#id = 'someId']//div[contains( #class, '" + className +"')]"));
return new AnotherSuperPage(driver);
}
instead of creating a string object then replacing it and storing it in another variable, though anyhow this is what happens internally, but less coding. Hope am understanding you right...
For some reason, when I call method Spage.editExButton(int ID), I get an error saying that WebElement first is null. Why is it null? I have defined it using the #FindBy annotation. I have to explicitly define the element using findElement(By.id("xxx")) in order to be able to click on it. But why am I unable to call it using the #FindBy notation?
public class SPage extends GPage<SPage> {
public SPage() {
super();
}
public SPage(String pageType) {
super(pageType);
}
#FindBy(id = "xxx")
WebElement first;
public WebElement eButton(int ID) {
first.click();
String tmp = ID + "-Edit";
WebElement edit = getDriver().findElement(By.id(tmp));
return edit;
}
public EPage cEdit(int ID) {
eButton(ID).click();
return new EPage(getBasePageType()).openPage(EPage.class);
}
}
I am calling the method like this:
static EPage epage;
static SPage spage;
#Test
public void edit_exception() {
epage = spage.cEdit(IDbefore);
}
You need to call this (preferably in your constructors):
PageFactory.initElements(getDriver(), this);
More information: https://code.google.com/p/selenium/wiki/PageFactory
As all other answer mentions that we have to initialize webElements using PageFactory.initElements(),
There are two ways of doing it or rather two places where it can be done :
1) inside class SPage :
As there are two constructors in SPage class we have to add following code to initailize all the elements declared in this class, it should be done in both class because we don't know that which constructor will be used to initialize SPage:
We will be passing driver instance to SPage class constructors, which will look like below :
public SPage(WebDriver driver) {
super();
PageFactory.initElements(driver, this);
}
public SPage(String pageType, WebDriver driver) {
super(pageType);
PageFactory.initElements(driver, this);
}
2) inside Other classes where SPage WebElements are to be used :
in the above example elementsCan be initialzed in the class where edit_exception() method is written, in short before anyplace we want to use elements/actions of the class SPage , Now the code will look like below:
#Test
public void edit_exception() {
spage = PageFactory.initElements(driver, SPage.class); //we are not passing driver instance to SPage class
epage = spage.cEdit(IDbefore);
}
In my test class I added the following line of Code
WebMainMenu mainmenu = PageFactory.initElements(driver, WebMainMenu.class);
mainmenu.doStuff(driver, 5);
etc
I agree as per above that you need to instantiate the page object.
WebElement first;
is null because element is not initialize when we are instantiating our page class. So initialize all elements in page class
PageFactory.initElements(driver, this);
I have a function for a Selenium Test that looks like this.
public static WebElement getElmObject (String locinfo, String loctype) {
try{
return driver.findElement(By.loctype(locinfo));
} catch (Throwable t){
return null;
}
The function is supposed to take in the info string and the type (the name of the method to call in the BY class - like xpath, cssselector, tagname etc.) How do I get Java to evaluate the value of "loctype"?
I come from a ColdFusion background and this is easy to do with CF but I am having a hard time trying to do this in Java. I just get a "cannot resolve method" issue and it won't compile. Is it even possible to do?
You can do this using Reflection.
public static WebElement getElmObject(String locinfo, String loctype) {
try {
Method method = By.class.getMethod(loctype, String.class);
By by = (By) method.invoke(By.class, locinfo);
return driver.findElement(by);
} catch (Throwable t) {
return null;
}
}
However I find this strange and I would recommend using different methods (getElmObjectById, getElmObjectByCss, etc.) or to use an enum (ID, CSS, XPATH, etc.) as parameter instead of the method name. Using the method name as parameter, it makes your caller dependent of the Selenium implementation. If they change the name of a method, your code will not work anymore and you will even not notice this at compile time!
we can also do it with enum like this
other than creating seperate methods for each and every locator like getElmObjectById as LaurentG said we can also achieve it as shown below
public enum avilableLocators
{
CLASS_NAME, CSS_SELECTOR, XPATH
}
and have a method with switch case or if-else if which will have a return type of By
public By locinfo(String locinfo)
{
String locatorValue=null;
switch (locType(locinfo))
{
case XPATH:
locatorValue=locinfo.split(",")[1]/*assuming that you are passing locinfo,locvalue*/
return By.xpath(locator);
}
}
public final avilableLocators locType(String loctype) {
if (loctype.contains("xpath"))
{
return avilableLocators.XPATH;
}
}
so the final usage can be like this
String locDetails="xpath,//*[#id='ComScorePingFile']"
locinfo(locDetails);
Not sure if this is a decent question or not but here it goes. We are trying to implement a UI testing framework (selenium web-driver) and want to use a Page driven design for example
class HomePage {
#FindBy(how = How.Id, id="myPageHeaderID")
private String pageHeader
In the simple example above I need to hard-code the "myPageHeaderID" string literal. One of the requirements proposed is that we be able to pull in the "myPageHeaderID" from a property for both maintenance reasons (no code deploy if something changes) and for internationalization reasons. I have been searching around and probably not doing a proper search but is there any way of doing what I am asking above?
I briefly went down this route, but due to our application it wasn't quite achievable (pages aren't always displayed in the same order once you've visited a page).
public class PageElement implements WebElementAdapter, Locatable {
private How how;
private String using;
private boolean required;
#FindBy(how = How.ID_OR_NAME, using = DEFAULT_LOCATION_STRATEGY)
private WebElement backingElement;
public PageElement(How how, String using using) {
this.how = how;
this.using = using;
this.required = true;
}
/**
* This is how the overriding of the element location is done. I then injected
* these values in a spring configured bean file.
*
* This is needed on your config file:
* default-lazy-init="true" default-init-method="initialize">
*/
public final void initElement() {
if (backingElement == null || isStale() {
backingElement = getDriver().findElement(getLocationStrategy());
}
}
public By getLocationStrategy() {
By by = new ByIdOrName(using.replace(DEFAULT_LOCATION_STRATEGY, using));
switch(how) {
case CLASS_NAME:
by = By.className(using.replace(DEFAULT_LOCATION_STRATEGY, using));
break;
//Do for others
}
return by;
}
public WebElement getBackingElement() {
return backingElement;
}
}
public interface WebElementAdapter {
WebElement getBackingElement();
}
public interface Locatable {
By getLocationStrategy();
}
I then created common widgets in POJOs, and injected these into page objects which were a collection of these widgets.
From there I had a simple test harness which was responsible for taking in strings (which were then executed. Basically it allowed for test cases to be written in SpEL and act on the beans which were injected.
It was what I thought a pretty neat project, but I had to shelf it to get some other things done.
Annotations are essentially metadata. Taking database metadata for example, it would be weird if Oracle database would turn into MySQL, right? Here is the article about Annotation Transformers in TestNG. Didn't try it myself, but I think it could be implemented in some way or another.
AFAIK, you can call a method from the Annotation.
#FindBy(how = How.Id, id=getProp())
private String pageHeader;
private String getProp()
{
String prop = //whatever way you want to get the value
return prop;
}
Doesn't that work?