8

I want to write my own locator to access the elements. WebDriver’s API offers currently eight locators allowing to retrieve elements by id, name attribute, tag name, complete or partial link text, XPath, class name, and css selector. However those default locators not enough for me now because I have to access the elements through a new attribute. Let me give an xample so that you can understand what I really want here.

Example: Choose your username:

Now I want to write a code so that I can access the username button using the myLocator locator like:

*driver.findElement(By.myLocator("username")).*

It would be very helpful if anybody can give us some good idea how could I rewrite the BY class to add my own locator.

Thank you in advance for your help.

Prasad
  • 472
  • 5
  • 15
BlueSky
  • 101
  • 1
  • 1
  • 4
  • It looks like you have to write some custom component. May be this links can help you out. `1.` [How to make Custom Component in java](http://www.javaworld.com/javaworld/jw-04-1997/jw-04-step.html), `2.` [Web-Driver API](http://selenium.googlecode.com/svn/trunk/docs/api/java/org/openqa/selenium/WebDriver.html) – Smit Jan 10 '13 at 17:36
  • I have read the Web Driver API before and my understanding is, I have to create a new class for my new locator which will inherited from org.openqa.selenium.By class. However, I didn't find good knowledge base article which explains this implementation. Thanks for your comment. – BlueSky Jan 10 '13 at 19:19
  • Its not impossible but kinda lot of work. You are thinking in correct direction. However have you taken look at selenium javadoc. You can take reference from those .class files and implement your own won method. – Smit Jan 10 '13 at 19:43
  • I downloaded the selenium project and going through the By class and other classes inherited from By class. Wish me good luck, thanks. – BlueSky Jan 10 '13 at 19:48

5 Answers5

7

using c#

using OpenQA.Selenium;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

public class ImageBy : By
{
    public ImageBy(string imageByString)
    {
        FindElementMethod = (ISearchContext context) =>
        {
            IWebElement mockElement = context.FindElement(By.XPath("//img[@src='" + imageByString + "']"));
            return mockElement;
        };

        FindElementsMethod = (ISearchContext context) =>
        {
            ReadOnlyCollection<IWebElement> mockElements = context.FindElements(By.XPath("//img[@src='" + imageByString + "']"));
            return mockElements;
        };
    }
}

and the usage would be as follows

[FindsBy(How = How.Custom, Using = @"/path/to/img", CustomFinderType = typeof(ImageBy) )]
private IWebElement MenuStartButton = null;

Using Java

import java.util.List;

import org.openqa.selenium.By;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.WebElement;

public class ByImageSrc extends By 
{
    private final String imageByString;
    public ByImageSrc(String imageByString)
    {
        this.imageByString = imageByString;
    }

    @Override
    public List<WebElement> findElements(SearchContext context) 
    {
         List<WebElement> mockElements = context.findElements(By.xpath("//img[@src='" + imageByString + "']"));
         return mockElements;
    }
}

usage :

WebElement element = driver.findElement(new ByImageSrc("/path/to/image"));
Sridhar
  • 733
  • 9
  • 13
1

You would need to subclass the By class and provide an implementation for findElement and findElements methods, since this is where the 'meat' of the actual element finding occurs.

You should then be able to use it with the normal driver.FindElement then.

Arran
  • 24,648
  • 6
  • 68
  • 78
0

I know this doesn't really answer your question, but why not just use Xpath? It seems like it would be a lot of extra work to build out a new locator instead of just crawling the DOM.

For example: driver.findElement(By.Xpath("//div[@username='YourUsername']")

I could give a better example with some more detail about the attribute and page you're working with.

Soner Gönül
  • 97,193
  • 102
  • 206
  • 364
Michiel
  • 1
  • 1
  • Thank you for your quick answer. Actually in our current environment, xpath does not work most of the time since the internal structure changes frequently and in some scenario that is not possible. That’s why designers decided to add a new attribute for automation purpose and they don’t want us to use the id or name of an element, because they also change time to time according to their requirements. Thanks again. – BlueSky Jan 10 '13 at 19:05
0

Much like Michiel answered I feel you can achieve what you want with what Selenium has already provided. If it is a maintenance overhead you are wanting to avoid due to developers altering ids and element names you can create a separate file that keeps track of the elements you need to locate.

//EG. (not Java I know :))
string usernameXPath = "//div[@username='YourUsername']";

Then if there is a change you can maintain this. You could even go a step further and implement a class for each 'other type' of element and just put an XPath around it in the constructor. XPath is very flexible as it offers functions such as 'contains' and 'parent::div'. Maybe look at the W3schools page for some more XPath reading.

EDIT: Also worth noting is that the C# bindings latest release says the following:

.Net: Introduces the Updating the CustomFinderType property to the .NET FindsByAttribute. This allows use of custom By subclasses in the PageFactory. The custom finder must be a subclass of By, and it must expose a public constructor that takes a string argument.

Hopefull this is in the next Java release for you too :)

Nashibukasan
  • 2,028
  • 23
  • 37
  • Thanks for your reply. Our current project is very very big and long term project. At the beginning we also thought to create a separate file to keep the change, however we found that would be more painful through out the project life time. Then we decided to implement new locator to avoid any future problem. – BlueSky Jan 11 '13 at 17:08
  • I am currently working on a project that has been running for over 15 months and we just implemented our own 'Element' object that takes in a type and a string. From here the locating implementation is hidden in another layer. I guess this is much the same as what you are trying to achieve. Good luck! – Nashibukasan Jan 13 '13 at 22:11
0

You can do something like this:

private final String myLocator = "//*[contains(@id,'username') and contains (@type,'text') and contains (text(),'exactly what I want')]";

So you can write up the locator however you wish, depending on the attributes that what you're mapping has, no need to have 5 rows for a click or select.

Also, you can use a wildcard in that locator and just replace whatever you want to use as a parameter with "%s", like this:

private final String myLocator = "//*[contains(@id,'%s') and contains (@type,'text') and contains (text(),'exactly what I want')]";

Then have a dynamic element created from that like:

private WebElement usernameSelectElement(String text) {
    return driver.findElement(By.xpath(String.format(myLocator, text)));
}

And the usage would be something like:

public void clickMyElement(text){
usernameSelectElement(text).click();
}

For the example that you had, it's just overly complicated in my view.