Python Selenium for loop on multiple input fields with same ID - python

I'm using Python and Selenium to fill out a web form. On one of the pages, it has multiple input fields with the same ID. Sometimes it's 1, 2, 3 and 4. Each ticket is different.
Here's my attempt:
how_many_meter_options = len(browser.find_elements_by_xpath("//span[contains(#class,'meterdisplay')]"))
browser.implicitly_wait(30)
print("There are ")
print(how_many_meter_options)
print(" Meter Options")
thecountofmeters = str(how_many_meter_options)
for row_of_meters in thecountofmeters:
browser.find_element_by_xpath("//span[contains(#class,'meterdisplay')]").click()
thelastmeter = browser.find_element_by_id("meteredit-lastreading-display").text
print(thelastmeter)
browser.implicitly_wait(30)
browser.find_element_by_id('meteredit-display').clear()
browser.find_element_by_id('meteredit-display').send_keys(thelastmeter)
browser.implicitly_wait(30)
browser.find_element_by_name('ok').click()
browser.implicitly_wait(30)
This only fills out the first input field. I need it to do all.
Here's the html
<input rt-autofocus="" type="number" id="meteredit-display" name="display" data-ng-model="meter.MeterDisplay" min="0" data-ng-disabled="!canEditMeter()" class="ng-pristine ng-valid ng-valid-number ng-valid-min ng-valid-pattern">
Here's my attempt:

The function find_element_by_id() will only find the first element with a matching id that you put in the arguments. To my knowledge there is no function to find multiple using just the id as an argument, however you might be able to use find_elements_by_xpath("//input[id='meteredit-display']") which will return a group of elements you can iterate through and apply your commands on.
Something like this:
input_elements = browser.find_elements_by_xpath("//input[id='meteredit-display']")
for element in input_elements:
element.clear()
element.send_keys(thelastmeter)
Let me know if you try this and how it works.
Edit: Also I should add that calling browser.implictly_wait() multiple times does nothing. The functions tells the webdriver to wait up to the specified amount of time when trying to find something before it moves on. It is something that just needs to be set once and then you don't have to call it again unless you want to change the amount of time that is waited.

Like Nathan Roberts suggested above, you can look for the xpath, however, having multiple elements with the same id is not considered valid HTML. If you have any say in this I'd recommend requesting the change.
Another option would be to use a regular expression on the raw html.

Related

Finding an element whose Xpath is unique to each login

I am coding a python web automation selenium script.
In the script, I use driver.find_element_by_xpath('xpath') to find elements on Binary.com. This means I would have to preload Binary.com and copy xpaths of the elements I need to find. For most elements the method works but for a few I realised that the Xpath is unique to each login.
For example, if I login in now and try to copy the xpath of a certain element it will be //*[#id="tp1602844250562"] but if the page is reloaded or I try to login on a different window the xpath would have then changed to //*[#id="tp1602844157070"]. Please note they are not the same id numbers. This means I cannot use one xpath on a separate page login
The desired element has an HTML code:
<input type="text" class="time hasTimepicker" tab-index="-1" value="00:00" readonly="" id="tp1602844157070">
Refer to the supplied image for clear html code
You can try below with class instead of id as the id is getting pulled from DB i guess-
//div[#class='date-time']//input[contains(#class,'time')]
Why don't you use the class instead of the id? Try this xpath:
driver.find_element_by_xpath('//input[#class = "time hasTimepicker"]')
Try changing your xpath expression to
//input[starts-with(#id,"tp")]
or, if it's not always input
//*[starts-with(#id,"tp")]
To find the input element with that class use.
driver.find_element_by_css_selector("input.time.hasTimepicker")

How to enter numeric value in input field using Python Selenium?

I've got a script writing values into a web page, and all values write except for one field that keeps throwing up the following error:
(Screenshot provided b/c in other similar questions many comments said this is impossible to happen on a web page.)
"Please enter a numeric value."
Here's my code:
workcenter_to_add = {}
workcenter_to_add['BatchCycle'] = str(2.78)
# driver = my_chrome_webpage
WebDriverWait(driver, wait_time).until(EC.presence_of_element_located((By.XPATH, "//input[#id='BatchSize']"))).send_keys(workcenter_to_add['BatchCycle'])
As everyone knows, if I do not input the 2.78 value in as a string WebDriver throws an error. But my page demands a numeric value. I'm stuck.
I've Googled around and not found a usable answer to this. It seems if you're using Java there's a setAttribute method you can use, but if you're using Pythonyou've got to figure something out.
For example, the question here looked promising but I could not find the String or how to import it to get it to work. There's a couple of other much older questions that talk about executing java but I have had no luck getting them to work.
I've got the page-source HTML here:
https://drive.google.com/open?id=1xRNPfc5E65dbif_44BQ_z_4fMYVJNPcs
I am sure though you are passing the value .send_keys('2.78'), still the value will be numeric. So, ideally you should not get this issue.
Here is the sample html and script to confirm the same.
<html><head>
<script>
function validateOnClick(evt) {
var theEvent = evt || window.event;
// Handle paste
if (theEvent.type === 'click') {
key = document.querySelector('input').value.toString();
} else {
// Handle key press
var key = theEvent.keyCode || theEvent.which;
key = String.fromCharCode(key);
}
var regex = /[0-9]|\./;
console.log(key);
if( !regex.test(key) ) {
alert("Please enter numeric value");
theEvent.returnValue = false;
if(theEvent.preventDefault) theEvent.preventDefault();
}
}
</script>
</head>
<body>
<input placeholder='check'></input>
<button type='submit' onClick='validateOnClick(event)'>Submit</button>
</body></html>
Script to check:
driver.get(url)
# check with string (not integer)
driver.find_element_by_tag_name('input').send_keys('Hello')
driver.find_element_by_tag_name('button').click()
print(driver.switch_to.alert.text)
driver.switch_to.alert.dismiss()
# now check with integer
driver.find_element_by_tag_name('input').clear()
driver.find_element_by_tag_name('input').send_keys(workcenter_to_add['BatchCycle'])
driver.find_element_by_tag_name('button').click()
Screenshot:
So, We have to check what's the js/method implemented to validate the value entered in the field. As you can see passing integer with in quotes from python script does not make any difference to the field and it's data type.
I'm sure this is going to be an unpopular answer, but this is how I got it work.
The field in this question and another field on another page in the same ERP system were throwing the same error. send_keys() would not work no matter what I tried.
That's when I put on my thinking cap and starting trying other ways.
I tried entering the information into another field on the page that would accept numbers via send_keys() and then cutting and pasting the values into the field that would not accept the value had I used send_keys(). It worked!
Here's a code snippet I used on the different page with the same issue:
elem1 = driver.find_element_by_id('txtNote')
elem1.send_keys(rm['txtGross_Weight'])
elem1.send_keys(Keys.CONTROL, 'a') #highlight all in box
elem1.send_keys(Keys.CONTROL, 'x') #cut
elem2 = driver.find_element_by_id('txtGross_Weight')
elem2.send_keys(Keys.CONTROL, 'v') #paste
I was looking for a high tech answer when a low tech work around sufficed.
Is it code or methodology I'd write on a job resume? Probably not. Did it work and can I live with it? Yes. Thank you for the people who tried to answer.
It looks a lot like a comma vs dot problem?
But I could be wrong.
It depends on the browser locale of the machine that your selenium is running on.
So a simple test could be to enter the text '2,78' or '2.78' into the field.
Python converts the number to a string, and that is not a localized number.
When it is sent as keys, it is sent as four characters '2' '.' '7' '8'.
It then ends in the Javascript scope of your browser, that depending on the OS and Language settings will be either using comma or dot as a decimal separator.
The dialog box with the notification
possibly is the outcome of Constraint API's element.setCustomValidity() method.
I had been through the page-source HTML which you have shared. But as per your code trials:
By.XPATH, "//input[#id='BatchSize']"
I didn't find any <input> tag within the pagesource. The text based relevant HTML would have helped us to construct an answer in a better way. However, you need to consider a few things as follows:
As you are dealing with an <input> tag, instead of presence_of_element_located() you should be using element_to_be_clickable().
You haven't told us about the error WebDriver throws if you do not input the 2.78 value as a string. Still, as str(2.78) works so you can stick to it.
Effectively your line of code will be:
workcenter_to_add = {}
workcenter_to_add['BatchCycle'] = str(2.78)
WebDriverWait(driver, wait_time).until(EC.element_to_be_clickable((By.XPATH, "//input[#id='BatchSize']"))).send_keys(workcenter_to_add['BatchCycle'])
References
You can find a couple of relevant discussions on Constraint_validation in:
How to handle html5 constraint validation pop-up using Selenium?
How can I extract the text of HTML5 Constraint validation in https://www.phptravels.net/ website using Selenium and Java?

How to fix a changing xPath in search box (doesn't contain text)

Basically I want to input invoices in Xero software for my job. The process is very simple, I have some values that I need to input in some slots. I have a big problem however. The xpath is dynamic (changes every time you refresh).
Basically it changes from something like this:
//*[#id="PaidToName_12ddc347c7bc4f5aa84c452f55660690_value"]
To something like this:
//*[#id="PaidToName_4fea44e4f8a844b4b630b4bf149490d8_value"]
So the numbers keep on changing.
I have tried a starts-with function however I am pretty sure that there are two XPATHs that starts with PaidToName or end with value, therefore this doesn't seem like a solution as I get this error message:
selenium.common.exceptions.ElementNotInteractableException: Message: element not interactable
The other thing to note is that I see many elements that have the "input type hidden" in the HTML code which I am pretty sure play a role with that. Please let me know if there is anything I can do to help.
This is the code I have tried that doesn't work.
button = driver.find_element_by_xpath("//*[starts-with(#id,'PaidToName')]")
button.send_keys('lol')
This is the HTML code I am trying to retrieve
<input type="text" size="24" autocomplete="off" id="PaidToName_4fea44e4f8a844b4b630b4bf149490d8_value" name="PaidToName_4fea44e4f8a844b4b630b4bf149490d8_value" class="x-form-text x-form-field autocompleter x-form-focus" tabindex="10" style="width: 129px;">
You can use xpath with id and class combination, try this :
button = driver.find_element_by_xpath("//*[contains(#id,'PaidToName') and contains(#class,'x-form-text')]")
button.send_keys('lol')
Try below given locator.
driver.find_element_by_xpath("//input[contains(#id,'PaidToName') AND contains(#class,'x-form-text')]")

How to match multiple element tags with single XPath

I want to create function that will allow to fill registration, authorization and other text forms. Something like:
def fill_form(my_list):
forms = driver.find_elements_by_xpath(common_xpath)
for element in forms:
element.send_keys(my_list[forms.index(element)])
It should get as arguments a list of text values to send into <input type="text">, <input type="password"> and <textarea> elements.
So far I have:
common_xpath ='//input[#type="text" or #type="password"]'
but I can't understand how to add textarea element to this XPath to match it also
A simpler and more future-proof strategy would be separate the XPath expression into 2/3 distinct expressions that search for required WebElements, but if you really need to use a single XPath expression I imagine you could use the | operator to devise something along the lines of:
//input[#type="text" or #type="password"] | //textarea
Not sure if this is going to do what you look for, but, from what I can see this Xpath could get the job done:
common_xpath = "//*[self::input[#type='text'] or self::input[#type='password'] or self::textarea]"
I don't program in Python, but tried that one in Chrome's console (using $x("...")), and it seems to do what you want. You should consider calling that XPath inside the form (path/to/your/form//*...), to make it more specific.
TIL that you could select different tags in Xpath :)
Check this related answer for more info.
Finally, as a personal note, I'm not that experience with Selenium, but I would suggest you to consider using the PageObject model design pattern, to make the tests easier to maintain. Hope it works for you.
I am not aware of Python, but this is what I would do in a JAVa
String [] commanXpath= {"text", "password"};
String xpathVar= "//input[#type='"+commanXpath[index]+"']";
System.out.println(xpathVar);
By common_xpath= By.xpath(xpathVar);
See if you can implement similar logic in Python. Can you also update original post with exact html tags.

Need xpath locators for visible elements

I'm trying to make a test for my site. Having troubles on some user form. The trick is, that the number of text fields in the form varies depending in user options (disabled ones are present in the code, but have a style <displayed: none;> tag), so I'm trying to find more flexible approach than locating every element one-by-one and filling the forms with try/except blocks.
I'm using an xpath locator
text_fields = driver.find_elements_by_xpath("//div[#class='form-line']/div[#class='form-inputs']/input[#type='text' and not(ancestor::div[#style='display: none;'])]")
The trouble is that firebug locates only needed elements, but when I use it my selenium script, printing the list of text_fields gives me all the elements, even without a <displayed: none;> tag
How can I get only visible elements?
PS Sorry for my bad English ^_^
You can get all the form elements the usual way, then iterate on the list and remove those elements that do not return true on is_displayed().
Try the contains() method:
text_fields = driver.find_elements_by_xpath(
"//div[#class='form-line']/div[#class='form-inputs']/input[#type='text' and
not(ancestor::div[contains(#style, 'display: none;')])]")
The important part is:
div[contains(#style, 'display: none;')]
Note, that if the style contains the string display:none; or display:none, the selector won't match.
I use the following and it works great.
self.assertTrue(driver.find_element_by_xpath("//div[#id='game_icons']/div/div[2]/div/a/img"))
This is for Selenium and Python of course.

Categories