1

My HTML looks like below

<div class="container">
    <div class="jumbotron">
        <h1 id="survey-title" class="display-5 text-capitalize">Your favourite candidate</h1>
        <hr class="my-4">

        <div class="card-body">
            <h5 id="question-title" class="card-title">What is your name?</h5>

            <form id="question_form" action="/survey/takesurvey/1/2" method="post" class="form">
                <input type="hidden" name="csrfmiddlewaretoken" value="ELBWYGZRxRuI7CoZ2xkmgCJ3f9JweFMM4Ew1pQbgSE3BLb38VStPJEbHvsyiBEFg">
                <div class="form-group"><input type="text" name="response" value="asdfasdfas" maxlength="400" class="form-control" title="" id="id_response"></div>
            </form>
        </div>

        <a id="btn-previous" href="javascript:history.back()" class="btn btn-primary ">Previous</a>
        <a id="btn-next" href="/survey/takesurvey/finish/1" class="btn btn-primary">Next</a>
    </div>
</div>

Notice the href on previous button

<a id="btn-previous" href="javascript:history.back()" class="btn btn-primary ">Previous</a>

Clicking next will submit form and go to "next" page, again clicking "previous" should come back and preserve the input value in previous page. I am using following code to test this behavior, The following code is working only when I put sleep(1)line in between finding the element and clicking. Else the page is not going back (url not changing)

  • Why just waiting for url change before clicking the button not working
  • I suspect it has to do with javascript event handler (which is inline in html) not being loaded.
  • I is there any way to check if page is ready for the click event
cur_url = self.browser.current_url
self.browser.find_element_by_id('btn-next').click()

wait(lambda: self.assertNotEqual(self.browser.current_url, cur_url))
btn_previous = self.browser.find_element_by_id('btn-previous')

## This code is not working without the sleep below. Which I suspect 
## has to do with javascript event handler (which is inline in html) not being loaded.
## Is there better way to check that the button is ready to be clicked
sleep(1)

btn_previous.click()
Dev Khadka
  • 5,142
  • 4
  • 19
  • 33

3 Answers3

1

You can use WebDriverWait with element_to_be_clickable condition. Check here also.

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

WebDriverWait(self.browser, 5).until(EC.element_to_be_clickable((By.ID, "btn-previous"))).click()

You can try wait for JavaScript:

WebDriverWait(self.browser, 20).until(lambda d: d.execute_script(
        'return (document.readyState == "complete" || document.readyState == "interactive")'))

# or without interactive 
WebDriverWait(self.browser, 20).until(lambda d: d.execute_script(
        'return document.readyState == "complete"'))
Sers
  • 12,047
  • 2
  • 12
  • 31
  • thank you for your solution but it has same result as my previous code. The code goes past the line for browser url doesn't change. It works only when using sleep(1) before the line – Dev Khadka Jan 12 '20 at 11:17
  • Check answer update with waiting for JS to complete – Sers Jan 12 '20 at 11:32
  • thanks, yes solution of checking readyState worked when putting it after clicking button – Dev Khadka Jan 12 '20 at 11:55
0

This is not the desired solution but a workaround the worked for me for now.

My functions for wait

from time import sleep
from functools import wraps
from selenium.common.exceptions import NoSuchElementException

def wrap_in_wait(func, retires=5):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return wait(func, *args, retires=retires, **kwargs)
    return wrapper

def wait(func, *args, retires=10, exceptions=[AssertionError, NoSuchElementException], **kwargs):
    for i in range(retires):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            print('retry', i)
            if type(e) not in exceptions:
                raise e

            if i == retires-1:
                raise e

            sleep(0.5 + i*.2)


Test function with Work around solution

def test_question_form_is_loaded_with_existing_answer(self):
        '''Test question form pre-loads'''
        self.browser.get(self.live_server_url + self.url)
        inp = self.browser.find_element_by_css_selector("input#id_response")
        inp.send_keys("This is my answer")

        self.browser.find_element_by_id('btn-next').click()

        @wrap_in_wait
        def click_previous_button():
            btn_previous = self.browser.find_element_by_id('btn-previous')
            btn_previous.click()
            inp = self.browser.find_element_by_css_selector("input#id_response")
            self.assertEqual(inp.get_attribute('value'), "This is my answer")

        click_previous_button()

This solution retries click_previous_button for 10 times if it raises AssertionError or NoSuchElementException exception. It is working on 2nd try.

But I am still not sure why javascript event is not triggered even when the button is clickable and if there is direct way to test when javascript is ready

Dev Khadka
  • 5,142
  • 4
  • 19
  • 33
0

I still had this issue in Selenium 4.x

My solution was (.NET):

Actions action = new Actions(Driver);
action.MoveToElement(elementToClick).Click().Perform();
Denis Koreyba
  • 3,144
  • 1
  • 31
  • 49