1

So I have a class that wraps a WebElement and adds functionality to make it more useful in my page objects. I now need to set the text of a text input, and the only way I can see to do this is to inject javascript to find the element and set its value attribute.

For simple cases where I have an id to go off, this is fairly simple, however where there are dynamically generated forms there might not be a useful id to go off - some of these are found by finding the enclosing form and then looking for a css selector within it, where there are other inputs matching the same css selector within other forms.

I can see that I could solve this by finding everything by xpath, however there must be a less brittle/disruptive solution. I know that you can get a WebElement from a Javascript executor, what I'm wondering is if it's possible to inject a javascript object reference to a given WebElement, and if not, a way to get a unique xpath at runtime from an existing WebElement.

I'm using python, but solutions in other languages would probably be transferrable.


Edit

I know that there is a webdriver element cache somewhere, and that I can get an element's id in that cache:

enter image description here

Is there any way of using this in the injected javascript?

theheadofabroom
  • 20,639
  • 5
  • 33
  • 65
  • 1
    "to get a unique xpath at runtime from an existing WebElement" is not possible - there is no unique xpath for a given element, they surely are far more than one and they probably will match more than one element. – furins Jan 21 '14 at 14:57
  • 1
    @furins surely if all else fails, at runtime you could give the full path from the document root element giving an index number in the case of any element with multiple children of the same element type? That xpath would be unique to that web element at that time. I don't care if two seconds later the DOM changes, I'm only interested in being able to accurately act on an element at the precise moment that I'm looking for it. – theheadofabroom Jan 21 '14 at 15:20
  • maybe I've not understood your question very well: if you have a class that wraps WebElement, why do you not simply use `send_keys` from that class (docs: http://selenium.googlecode.com/git/docs/api/py/webdriver_remote/selenium.webdriver.remote.webelement.html#selenium.webdriver.remote.webelement.WebElement.send_keys)? – furins Jan 21 '14 at 15:23
  • @furins because the input is disabled, and would usually be set by a file select dialogue, however that takes focus from the page and interupts the test – theheadofabroom Jan 21 '14 at 15:37
  • Now I understand it better. I've not idea how to achieve your desired behavior then, except identifying it uniquelly basing on its (default) value, its state, eventually checking the attributes of next sibling elements and finally using *n-th element selectors* inside a loop in javascript – furins Jan 21 '14 at 15:51
  • The ID given to that element is by the internal Selenium code only. It is not any reference to *anything* on the DOM or the web page. – Arran Jan 22 '14 at 11:01
  • @Arran damn I was hoping that as an implementation detail of webdriver there might be some way of looking it up in javascript – theheadofabroom Jan 22 '14 at 12:06

1 Answers1

4

If I understand your question correctly, you are asking how to pass a Selenium WebElement object to your JavaScript. If the page under test is using jQuery, it might look like this:

# select the element in python
form1 = driver.find_element_by_id('id_of_the_form')

# execute javascript which references the object
execute_script("$(arguments[0]).find('input.childOfInterest').val('newValue')", form1)

Because execute_script() takes a String (JS to execute) and an optional second parameter (object reference), you then refer to the object (a WebElement in this case) using arguments[0]. In this example, I passed in the parent form element, and then used jQuery to select the form, making it a jQuery object, and then called jQuery's .find() off of that, passing in a css selector to locate the child element, and then called jQuery's .val() off of that to update the element's value.


You also asked how to generate an xpath at runtime. Here's an example of this (complete answer here):

// javascript
function getPathTo(element) {
    if (element.id!=='')
        return 'id("'+element.id+'")';
    if (element===document.body)
        return element.tagName;

    var ix= 0;
    var siblings= element.parentNode.childNodes;
    for (var i= 0; i<siblings.length; i++) {
        var sibling= siblings[i];
        if (sibling===element)
            return getPathTo(element.parentNode)+'/'+element.tagName+'['+(ix+1)+']';
        if (sibling.nodeType===1 && sibling.tagName===element.tagName)
            ix++;
    }
}

Here are steps to implement it in your Selenium

# define xpath generation JS in python
xpathJS = "function getPathTo(element){ if(element....."

# add new function to the page
execute_script(xpathJS)

# select an element with SE
element1 = driver.find_element_by_id('id_of_element')

# get the xpath of the element
xpath1 = execute_script("return getPathTo(arguments[0])", element1)
Community
  • 1
  • 1
Dingredient
  • 2,191
  • 22
  • 47