I am facing a rare kind of issue while Automating through Selenium/Python while trying to fill out two fields on a website. My script fills out the first field i.e. ORIGIN CITY pretty fine. I have induced WebDriverWait for the second field DELIVERY ADDRESS.
My guess the DELIVERY ADDRESS field is pretty much clickable even before the waiter is induced.
But the ORIGIN CITY field have a JavaScript associated through onchange
event as follows :
onchange="javascript:setTimeout('__doPostBack(\'DrpCity\',\'\')', 0)"
ORIGIN CITY HTML :
<li>
<div class="form-group">
<select name="DrpCity" onchange="javascript:setTimeout('__doPostBack(\'DrpCity\',\'\')', 0)" id="DrpCity" class="inputStyle">
<option selected="selected" value="0">Origin City</option>
<option value="3">Bangalore</option>
<option value="6">Chennai</option>
<option value="8">Delhi - NCR</option>
<option value="10">Hyderabad</option>
<option value="7">Kochi</option>
<option value="12">Kolkata</option>
<option value="13">Mumbai</option>
<option value="15">Pune</option>
</select>
<span id="ReqCity" style="color:Red;visibility:hidden;">Select your city !</span>
</div>
</li>
DELIVERY ADDRESS HTML :
<li>
<div class="form-group">
<div class="" id="div_AddPopup" style="display: none;">
*Cars will not be delivered at Metro Stations, Malls or Public Place.
</div>
<input name="txtPickUp" type="text" id="txtPickUp" class="inputStyle locMark" placeholder="Delivery Address" onfocus="showOnKeyPress(); return true;" onblur="hideOnKeyPress(); return true;">
<span id="ReqPickUp" style="color:Red;visibility:hidden;">Enter Delivery Address !</span>
</div>
</li>
Once the JavaScript finishes it clears up the text from the DELIVERY ADDRESS field.
I did had a look at the Java Client's ExpectedConditions as jsReturnsValue which is not there for the Selenium Python Client.
Website : https://www.avis.co.in/
My Code :
from selenium import webdriver
from selenium.webdriver.support.ui import Select
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Firefox()
driver.get("https://www.avis.co.in")
mySelect = Select(driver.find_element_by_id("DrpCity"))
mySelect.select_by_visible_text("Pune")
WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.XPATH,"//input[@id='txtPickUp']"))).send_keys("XYZ")
Any suggestions will be helpful.
I have found, the easiest way to deal with these JS postbacks
, is to wait for the element that is affected by the load to go stale or not be found.
Here is an example function:
def waitForElementRemoved(Element, WaitCount, WaitTime):
ElementRemoved = False
WaitTry = 0
while not ElementRemoved:
try:
if WaitTry > WaitCount:
raise Exception("Element not removed from page in alloted time")
Test = Element.text
WaitTry += 1
time.sleep(WaitTime)
except (NoSuchElementException, StaleElementReferenceException):
ElementRemoved = True
And then I would pick an element that is affected by this postback load
and pass it to the function along with some timing arguments.
such as:
driver = webdriver.Firefox()
driver.get("https://www.avis.co.in")
removedElement = driver.find_element_by_id("DrpCity")
mySelect = Select(driver.find_element_by_id("DrpCity"))
mySelect.select_by_visible_text("Pune")
waitForElementRemoved(removedElement, 10, .5)
WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.XPATH, "//input[@id='txtPickUp']"))).send_keys("XYZ")
I don't know if this is the best way to deal with JavaScript onchange load
events, but in my case, it has been very effective.
To add on to this answer: I have found that waiting until element goes stale does not always work when there are multiple load events, I have found that using that method along with using the below code works much more effectively to make sure these dynamic load events are complete: (I adapted the below code from HERE)
# Wait for AJAX (Jquery or JS) dynamic page load events
class DynamicLoadState:
def __call__(self, driver):
LoadComplete = False
JQueryLoadComplete = False
JSLoadComplete = False
try:
if driver.execute_script("return jQuery.active") == 0: JQueryLoadComplete = True
except Exception:
# JQuery is not present on page
JQueryLoadComplete = True
if driver.execute_script("return document.readyState") == 'complete': JSLoadComplete = True
if JQueryLoadComplete and JSLoadComplete: LoadComplete = True
return LoadComplete
def WaitForDynamicLoad(driver, WaitTime):
WebDriverWait(driver, WaitTime).until(DynamicLoadState())
# Use the first method of waiting for the element to go stale
# then run this to make sure all loading is completed
WaitForDynamicLoad(driver, Counts.WaitTime)
Hope this helps keep someone from using time.sleep()
in the future for page load!
@PixelEinstein 's Answer is the working and accepted Answer as his idea to wait for the element that is affected by the load to go stale or not be found did the trick. Finally I have arrived to a solution through a much simpler way then it was expected. I had to simply wait for the DELIVER ADDRESS field to go stale and then invoke 'send_keys("XYZ")' again on DELIVER ADDRESS field as follows :
driver.get("https://www.avis.co.in")
mySelect = Select(driver.find_element_by_id("DrpCity"))
mySelect.select_by_visible_text("Pune")
WebDriverWait(driver, 10).until(EC.staleness_of(driver.find_element_by_xpath("//input[@id='txtPickUp']")))
driver.find_element_by_xpath("//input[@id='txtPickUp']").send_keys("XYZ")
The discussion How to wait on the __doPostBack method to complete in javascript? was helpful to understand __doPostBack()
tl;dr
来源:https://stackoverflow.com/questions/49561611/how-do-i-wait-for-a-javascript-dopostback-call-through-selenium-and-webdriver