How to get an element by pressing TAB in selenium (Python)? - python

I want to fill out some personal data on a website. The first input element can be accessed by find_element_by_id but the id of the next text field has a different id every time I access the website. In a Browser, I can simply press the TAB key two times to get to the desired text input field. Is there a way to achieve similar behavior with selenium?
i tried the following:
input1 = browser.find_element_by_id('id_email')
input1.send_keys("email#something.com")
input2 = browser.send_keys(Keys.TAB).send_keys(Keys.TAB)
input2.send_keys("Something else")
But Line 3 gives me:
AttributeError: 'WebDriver' object has no attribute 'send_keys'

You cannot send keys against the browser object.
This line:
input2 = browser.send_keys(Keys.TAB).send_keys(Keys.TAB)
is not valid. It even tells you as such: 'WebDriver' object has no attribute 'send_keys'
That says your webdriver object (which you called "browser") does not have a attribute ( a method/ a function) called Send_Keys.
Top tip to avoid this sort of problem is to use a good IDE with intellisense. That will tell you the methods you can use.
In vscode, you get this:
As you type, it tells you valid commands - and you can see the driver has no send keys!
What you need to do is use .sendKeys(..) against a web element.
In your code you already have input1 - that is a web element. You can send keys against that.
Something like this:
input1 = browser.find_element_by_id('id_email')
input1.send_keys("email#something.com")
input1.send_keys(Keys.Tab)
If you want to do multiple tabs from the same object, you can just add multiple. This will tab 3 times
.send_keys(Keys.TAB + Keys.TAB + Keys.TAB)
When i run a sample script on google.com,
I tab 3 times i go to the googlesearch button. (first tab is the clear, second tab is the microphone, 3rd tab goes to the button):
Finally, using tabs to navigate is a LAST RESORT. They can be flaky and inconsistent. You REALLY should get an identifier for your object.
If you can share your URL or the page DOM then I'm help you identify a working identifier. I know you say there is no ID but there many ways to access objects.

welcome!
i can only guess, since i'm not able to test the code out. But seems like you are not getting the right element with the 2 Tabs, since it starts from the start of the html page and not from the input1 field. You'd better getting the password by its id or parents id, rather then with Key.TAB.

Related

Python Selenium for loop on multiple input fields with same ID

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.

Getting span text(value) every time it changes in selenium python

I am trying to print the value of a span every time it changes. To print the value of the span is quite easy:
popup = wait.until(EC.visibility_of_element_located((By.XPATH, '//*[#id="spot"]')))
Print(popup.text)
This will print the value at that moment, the problem is that the value will change every 2 seconds. I tried using:
# wait for the first popup to appear
popup = wait.until(EC.visibility_of_element_located((By.XPATH, '//*[#id="spot"]')))
# print the text
print(popup.text)
# wait for the first popup to disappear
wait.until(EC.staleness_of(popup))
# wait for the second popup to appear
popup = wait.until(EC.visibility_of_element_located((By.XPATH, '//*[#id="spot"]')))
# print the text
print(popup.text)
# wait for the second popup to disappear
wait.until(EC.staleness_of(popup))
No matter how long my wait value is, 10 or 20 or even 30 seconds, the process always times out. I do not know much about coding but I think this method does not work because the span as a whole does not change only the span value(text). One method that I tried was to loop the Print(popup) command and it partially worked. it printed the same value 489 times until it changed and printed the other one 489 times again.I have since tried this code:
popup = wait.until(EC.text_to_be_present_in_element_value((By.XPATH, '//*[#id="spot"]')))
print(popup.text)
but it returns:
TypeError: __init__() missing 1 required positional argument: 'text_'
.
Please help what it is I need to add or what method I need to use to get the changing value.
HTML code inspection
Please I beg you, please beware Im not trying to print the text of the span, I already know how to do that, I want print it everytime it changes
Assuming that the element does disappear and reappear again:
You can just go back and forth between waiting for the element being located and being located.
Assuming that the elements content changes, but doesn't disappear:
I don't know of any explicit way to wait for the change of the content of an element, so as far as I am concerned you would need to compare the change yourself. You might want to add an absolute wait of < 2 seconds to limit the amount of unnecessary comparisons you make.
# Init a list to contain the values later on
values = []
# Wait for the element to be loaded in the first place
popup = wait.until(EC.visibility_of_element_located((By.XPATH, '//*[#id="spot"]')))
values.append(popup.text)
while True:
# possibly wait here
new_value = driver.find_element(By.XPATH, '//*[#id="spot"]')
# look up if the value has changed based on the values you know and add the new value
if values[-1] != new_value:
values.append(new_value)
# add an exit condition unless you actually want to do it forever
Please be aware: This will only work if the value actually changes each and every time or if you don't need duplicates that follow one another.
If you need every value, you can leave out the comparison and add one value every ca. 2 seconds.
For your example:
The page on binary.com you provided uses websocket in order to refresh the content. This is a protocol that allows the server to send data to the client and the other way around.
So it's a different approach to the http protocol you are used to (you send a request, the server replies - let's say you ask for the webpage, then the server will just send it).
This protocol opens a connection and keeps it alive. There will hardly be a wait to anticipated this change. But: In your browser (assuming Chrome here) you can go into your developer tools, go into the "Network" Tab and filter for the WS (websocket). You'll see a connection with v3?app_id=1 (you might need to refresh the page to have output in the Network-Tab).
Click on that connection and you'll see the messages your client sent annd the ones you received. Naturally you only need those received so filter for those.
As those are quite a few steps have a look on that screenshots, it shows the correct settings:
Every message is in json format and you click on it to see its content. Under "tick" you'll see the ask and bid data.
In case that suffices, you can just leave the page open for as long as you need, then copy the output, save it as a file and read it with python for analysis.
It seems you can also automate this with selenium as demostrated here:
http://www.amitrawat.tech/post/capturing-websocket-messages-using-selenium/
Basically they do the same thing, they set the capability to record the log, then filter through it to get the data they need. Note that they use Java to do so - but it wont be hard to translate to python.

Get text from span (Whatsapp Web)

I am learning Python with Selenium and I would like to capture the username from the last message in a whatsapp group conversation. I tried it in several ways but I couldn't.
Ex:
I would like to get the text "Nay"from this part and store it in a variable.
<span dir="auto" class="FMlAw FdF4z _3Whw5">Nay</span>
Print Screen
I tried:
texto1 = post[ultimo].find_element_by_xpath("//span[#class_name = 'FMlAw FdF4z _3Whw5']").text
But i get an error AttributeError: 'NoneType' object has no attribute 'text'
I achieved relative success with the code below but in addition to copying the user's name, this also copied the messages
texto1 = post[ultimo].find_element_by_class_name("_3Whw5").text
Sorry, I'm using Google Translate.
Thanks.
#get all messsages as a list
messages = driver.find_elements_by_xpath("//div[contains(#class,'message-in focusable-list-item')]/following::span[contains(#class,'selectable-text')]/span")
#reverse the list so you can get easier the last element
messages.reverse()
#last element becomes the first - now you can use it, I printed for the purpose of testing
print(messages[0].text)

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?

Find number of check-boxes on webpage, when page source contains 'type' as a string also, using selenium python

This is my code which finds number of check-boxes on the current webpage
checkboxes=driver.find_elements(By.XPATH("html/body/div[2]/div/section[8]/div/div/div[3]/div/div[2]/table/tbody/tr[1]/td[1]/input[#type='checkbox']"))
but it gives following error:
'str' object is not callable
As HTML DOM contains string "checkbox" in it.
Thus I am unable to find a way to calculate total number of check-boxes.
You should send two arguments to find_elements, like checkboxes=driver.find_elements(By.XPATH, xpath) while there is only one argument in your code- checkboxes=driver.find_elements(By.XPATH(xpath)). So you should use:
checkboxes=driver.find_elements(By.XPATH, "html/body/div[2]/div/section[8]/div/div/div[3]/div/div[2]/table/tbody/tr[1]/td[1]/input[#type='checkbox']")
UPDATE
Regarding OP comment: If you want to get only checkboxes visible on page, use following code:
checkboxes_list = [check for check in driver_find_elements_by_xpath('//input[#type="checkbox"]') if check.is_displayed()]

Categories