djangofan
djangofan

Reputation: 29669

Creating a Selenium getText() method for an element?

I am trying to create a Selenium getText() method that gets either the node text OR gets the node+child text of an element. By default, the Selenium behavior seems to use the Xpath .//string() method of getting text and including the text of immediate children. I want to take advantage of XPaths power to enable me to get the text in a more targeted way. My question is: am I misunderstanding this or is there a better way to accomplish this?

public String getText(By locationOfText, boolean childText)
{
  By locator = null;
  if ( childText)
  {
    locator = ByChained( locationOfText, By.xpath(".//string()"));
  } else {
    locator = ByChained( locationOfText, By.xpath(".//text()"));
  }
  JavascriptExecutor jse = (JavascriptExecutor)driver;
  String elementText = jse.executeScript("document.evaluate(locator, document.body, null, 
     XPathResult.STRING_TYPE, null);");

  return elementText;
} 

Here is an HTML snippet:

<h5 class="class-name clearfix">Inner Text
   <a class="info-link class-description" href="#">i</a>
</h5>

The problem is that I get the text Inner Texti when I use Selenium to do a text call like this:

driver.findElement(".//h5").getText();

My expectation was to retrieve the value Inner Text . By creating the method above, I hope to call it like so:

String text = elementHelper.getText(By.xpath(".//h5"),false);

Upvotes: 3

Views: 3015

Answers (1)

Louis
Louis

Reputation: 151401

string() is an XPath 2.0 construct but most browsers (if not all), only support XPath 1.0. Moreover, I'm not that fond of rushing to XPath for querying the DOM tree. XPath evaluation has a significant performance overhead. So adapting my answer here, I would suggest:

public String getText(By locationOfText, boolean childText)
{
  WebElement el = driver.findElement(locationOfText);
  if (childText)
  {
    return el.getText();
  }

  JavascriptExecutor jse = (JavascriptExecutor) driver;
  return jse.executeScript(
    "var parent = arguments[0]; "+
    "var child = parent.firstChild; "+
    "var ret = ""; "+
    "while(child) { "+
    "    if (child.nodeType === Node.TEXT_NODE) "+
    "        ret += child.textContent; "+
    "    child = child.nextSibling; "+
    "} "+
    "return ret;", el);
}

The locationOfText parameter could be any By method that Selenium supports.

In your code you use ByChained for location, which presumably you'd want to pass to executeScript but forgot to do. I cannot see how this would work, even if you added location to your executeScript call (and fixed the script to grab arguments[0]). ByChained would support things like mixing a CSS selector with an XPath, etc. Selenium can presumably resolve the combination by performing multiple searches but there is no way the XPath engine of a browser will accept some sort of combination of CSS and XPath.

Upvotes: 3

Related Questions