简体   繁体   中英

selenium button click not triggering inline javascript event handler

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()

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"'))

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM