4

I am using Selenium with Firefox Webdriver to work with elements on a page that has unique CSS IDs (on every page load) but the IDs change every time so I am unable to use them to locate an element. This is because the page is a web application built with ExtJS.

I am trying to use Firebug to get the element information.

I need to find a unique xPath or selector so I can select each element individually with Selenium.

When I use Firebug to copy the xPath I get a value like this:

//*[@id="ext-gen1302"]

However, the next time the page is loaded it looks like this:

//*[@id="ext-gen1595"]

On that page every element has this ID format, so the CSS ID can not be used to find the element.

I want to get the xPath that is in terms of its position in the DOM, but Firebug will only return the ID xPath since it is unique for that instance of the page.

/html/body/div[4]/div[3]/div[4]/div/div/div/span[2]/span

How can I get Firebug (or another tool that would work with similar speed) to give me a unique selector that can be used to find the element with Selenium even after the ext-gen ID changes?

Yi Zeng
  • 32,020
  • 13
  • 97
  • 125
AdnanEK
  • 198
  • 1
  • 4
  • 13
  • I wonder how http://selectorgadget.com/ would work for you – paul trmbrth Aug 02 '13 at 18:01
  • Just tried it, the tool ends up getting the same value as Firebug's copy xPath. – AdnanEK Aug 02 '13 at 18:17
  • @AdnanEK: I wouldn't recommend using the long xpath in terms of the position in the DOM; they turn out to be unreliable if anything else in the html structure changes. There's probably a better way of identifying the element; can you post some of your html, to let people offer suggestions? – Vince Bowdren Aug 05 '13 at 11:26

3 Answers3

5

Another victim of ExtJS UI automation testing, here are my tips specifically for testing ExtJS. (However, this won't answer the question described in your title)

Tip 1: Don't ever use unreadable XPath like /div[4]/div[3]/div[4]/div/div/div/span[2]/span. One tiny change of source code may lead to DOM structure change. This will cause huge maintenance costs.

Tip 2: Take advantage of meaningful auto-generated partial ids and class names.

For example, this ExtJS grid example: By.cssSelector(".x-grid-view .x-grid-table") would be handy. If there are multiple of grids, try index them or locate the identifiable ancestor, like By.cssSelector("#something-meaningful .x-grid-view .x-grid-table").

Tip 3: Create meaningful class names in the source code. ExtJS provides cls and tdCls for custom class names, so you can add cls:'testing-btn-cancel' in your source code, and get it by By.cssSelector(".testing-btn-cancel").

Tip3 is the best and the final one. If you don't have access the source code, talk to your manager, Selenium UI automation should really be a developer job for someone who can modify the source code, rather than a end-user-like tester.

Yi Zeng
  • 32,020
  • 13
  • 97
  • 125
  • Tip 2 was useful. To give more information my use of Selenium is more to automate use of control panels on websites, so its not quite a testing use. Thank you! – AdnanEK Aug 05 '13 at 17:45
1

I would recommend using CSS in this instance by doing By.cssSelector("span[id^='ext-gen'])

The above statement means "select a span element with an id that starts with ext-gen". (If it needs to be more specific, you can reply, and I'll see if I can help you).

Alternatively, if you want to use XPath, look at this answer: Xpath for selecting html id including random number

Community
  • 1
  • 1
Nathan Merrill
  • 7,648
  • 5
  • 37
  • 56
  • The problem is is that every element on the page has that ext-gen-#### format for ID, so I am forced to use another means of finding it. – AdnanEK Aug 02 '13 at 17:52
  • the above selector will select everything that starts with ext-gen. It doesn't matter what follows it, as long as it starts with ext-gen – Nathan Merrill Aug 02 '13 at 17:53
  • I need to be able to select single elements. – AdnanEK Aug 02 '13 at 17:57
1

Although it is not desired in some cases as mentioned above, you can parse through the elements and generate xpath ids.

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

public class XPATHDriverWrapper {
Map xpathIDToWebElementMap = new LinkedHashMap();
Map webElementToXPATHIDMap = new LinkedHashMap();
public XPATHDriverWrapper(WebDriver driver){
    WebElement htmlElement = driver.findElement(By.xpath("/html"));
    iterateThroughChildren(htmlElement, "/html");
}

private void iterateThroughChildren(WebElement parentElement, String parentXPATH) {
    Map siblingCountMap = new LinkedHashMap();

    List childrenElements = parentElement.findElements(By.xpath(parentXPATH+"/*"));
    for(int i=0;i<childrenElements.size(); i++) {
        WebElement childElement = childrenElements.get(i);
        String childTag = childElement.getTagName();
        String childXPATH = constructXPATH(parentXPATH, siblingCountMap, childTag);
        xpathIDToWebElementMap.put(childXPATH, childElement);
        webElementToXPATHIDMap.put(childElement, childXPATH);
        iterateThroughChildren(childElement, childXPATH);
//          System.out.println("childXPATH:"+childXPATH);
    }
}

public WebElement findWebElementFromXPATHID(String xpathID) {
    return xpathIDToWebElementMap.get(xpathID);
}

public String findXPATHIDFromWebElement(WebElement webElement) {
    return webElementToXPATHIDMap.get(webElement);
}

private String constructXPATH(String parentXPATH,
        Map siblingCountMap, String childTag) {
    Integer count = siblingCountMap.get(childTag);
    if(count == null) {
        count = 1;
    } else {
        count = count + 1;
    }
    siblingCountMap.put(childTag, count);
    String childXPATH = parentXPATH + "/" + childTag + "[" + count + "]";
    return childXPATH;
}
}

Another wrapper to generate ids from Document is posted at: http://scottizu.wordpress.com/2014/05/12/generating-unique-ids-for-webelements-via-xpath/

Scott Izu
  • 2,229
  • 25
  • 12