3

I'm using selenium to locate and select a HTML element using find_element_by_id. I have had no issues with this until yesterday, when the web developers changed the ID of the element to a dynamically string.

Here is an example of what the ID looks like: input_205.

Before, I used browser.find_elements_by_xpath('//*[@id="input_205"]').send_keys(viewId) to locate the element and this worked perfectly, but now the number value in that string dynamically changes.

The only pattern I've identified is that it is a three digit number every time the page loads.

I'm wondering if there's a way I can set my xpath to some sort of "begins with" or "contains" so that I can still target the ID regardless of the three digit numbers after input_. Thank you in advance!

undetected Selenium
  • 183,867
  • 41
  • 278
  • 352

2 Answers2

6

Try below XPath:

//*[starts-with(@id, 'input_')]
Andersson
  • 51,635
  • 17
  • 77
  • 129
dangi13
  • 1,275
  • 1
  • 8
  • 11
  • When I test that, terminal throws "SyntaxError: invalid syntax" This is what I have written: `browser.find_element_by_xpath('//*[starts-with(@id, 'input_')]')` Am I using this correctly? – Jessica Feliciano Jul 20 '18 at 19:59
  • @JessicaFeliciano , That's because of inconsistency quotes usage. try `browser.find_element_by_xpath("//*[starts-with(@id, 'input_')]")` – Andersson Jul 20 '18 at 20:02
  • Update: I've modified my code like this - `browser.find_element_by_xpath("//*[starts-with(@id, 'input_')]")` When I made this modification, I get the following error: "Message: element not visible" – Jessica Feliciano Jul 20 '18 at 20:03
  • You can refer this doc for waiting for visibility of element :http://selenium-python.readthedocs.io/waits.html , on the very first example it is given that how to wait until an element is displayed. Also check if you are not in an iframe. – dangi13 Jul 20 '18 at 20:07
  • 1
    @dangi13 , FYI `ElementNotVisibleException` will not be raised in case element located inside iframe, but `NoSuchElementException` – Andersson Jul 20 '18 at 20:11
  • @Andersson I was able to get the CSS version of the xpath to work. Not sure why but it worked. The only other issue I'm having now is send_keys(). For some reason when I execute send_keys() method, I get this: "ElementNotVisibleException: Message: element not visible." Any thoughts on this? – Jessica Feliciano Jul 20 '18 at 22:03
  • 1
    @JessicaFeliciano , as was already suggested, try to apply ExplicitWait as `WebDriverWait(browser, 10).until(EC.visibility_of_element_located((By.CSS_SELECTOR, "[id^='input_']"))).send_keys("foo")`. If it's not working check if there is only one element matched by your selector: `print(len(browser.find_elements_by_css_selector("[id^='input_']")))` – Andersson Jul 21 '18 at 06:03
0

As you mentioned in your question that you had no issues with this until yesterday to locate the element through:

browser.find_elements_by_xpath('//*[@id="input_205"]').send_keys(viewId)

That's because the desired element was having a static id assigned.

But now the element id is dynamic and getting assigned through some JavaScript/AjaxCalls. So to identify the element you have to construct a dynamic Locator Strategy. But your code trials gives us the idea about the id attribute only. To construct a dymanic locator you can use multiple items from the below mentioned items:

  • The <tagName> tag e.g. <input>
  • The class attribute e.g. class="input-full"
  • Partial id attribute e.g. input_ from id="input_205"
  • Angular attributes e.g. ng-click="navigate('export',$event)"

Most importantly, as the element is a dynamic element to invoke send_keys() you have to induce WebDriverWait in-conjuction with expected_conditions method set as element_to_be_clickable(locator).

As an example, if the element is represented in the HTML DOM as follows:

<div class="form-group clearfix">
        <input id="input_205" class="input-full" type="text" ng-click="navigate('run',$event)">
</div>

Your effective locator will be:

  • CSS_SELECTOR:

    WebDriverWait(driver, 20).until(EC.element_to_be_clickable((By.CSS_SELECTOR, "input.input-full[id^='input_']"))).send_keys(viewId)
    
  • XPATH:

    WebDriverWait(driver, 20).until(EC.element_to_be_clickable((By.XPATH, "//input[@class='input-full' and starts-with(@id, 'input_')]"))).send_keys(viewId)
    

Note A : Reference of the wildcards used with cssSelectors:

  • ^ : To indicate an attribute value starts with
  • * : To indicate an attribute value contains
  • $ : To indicate an attribute value ends with

Note B : You have to add the following imports :

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
undetected Selenium
  • 183,867
  • 41
  • 278
  • 352