Skip to content

Commit e817860

Browse files
committed
Add a js_click() method and improve JavaScript code
1 parent aa741cf commit e817860

File tree

1 file changed

+93
-18
lines changed

1 file changed

+93
-18
lines changed

seleniumbase/fixtures/base_case.py

Lines changed: 93 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,17 @@ def click(self, selector, by=By.CSS_SELECTOR,
122122
element = page_actions.wait_for_element_visible(
123123
self.driver, selector, by, timeout=timeout)
124124
element.click()
125+
except WebDriverException:
126+
self.wait_for_ready_state_complete()
127+
if not by == By.LINK_TEXT:
128+
# Only use a JavaScript click if not clicking by Link Text
129+
self.__js_click(selector, by=by)
130+
else:
131+
# One more attempt to click on the element
132+
element = page_actions.wait_for_element_visible(
133+
self.driver, selector, by, timeout=timeout)
134+
element.click()
135+
125136
if settings.WAIT_FOR_RSC_ON_CLICKS:
126137
self.wait_for_ready_state_complete()
127138
if self.demo_mode:
@@ -512,7 +523,7 @@ def update_text_value(self, selector, new_value, by=By.CSS_SELECTOR,
512523
new_value - the new value for setting the text field
513524
by - the type of selector to search by (Default: CSS)
514525
timeout - how long to wait for the selector to be visible
515-
retry - if True, use jquery if the selenium text update fails
526+
retry - if True, use JS if the selenium text update fails
516527
"""
517528
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
518529
timeout = self._get_new_timeout(timeout)
@@ -556,10 +567,23 @@ def update_text_value(self, selector, new_value, by=By.CSS_SELECTOR,
556567
element.send_keys(Keys.RETURN)
557568
if settings.WAIT_FOR_RSC_ON_PAGE_LOADS:
558569
self.wait_for_ready_state_complete()
570+
except Exception:
571+
exc_message = self._get_exception_message()
572+
update = ("Your version of ChromeDriver may be out-of-date! "
573+
"Please go to "
574+
"https://sites.google.com/a/chromium.org/chromedriver/ "
575+
"and download the latest version to your system PATH! "
576+
"Original Exception Message: %s" % exc_message)
577+
using_old_chromedriver = False
578+
if "unknown error: call function result missing" in exc_message:
579+
using_old_chromedriver = True
580+
if self.browser == 'chrome' and using_old_chromedriver:
581+
raise Exception(update)
582+
else:
583+
raise Exception(exc_message)
559584
if (retry and element.get_attribute('value') != new_value and (
560585
not new_value.endswith('\n'))):
561-
logging.debug('update_text_value is falling back to jQuery!')
562-
selector = re.escape(selector)
586+
logging.debug('update_text() is falling back to JavaScript!')
563587
self.set_value(selector, new_value, by=by)
564588
if self.demo_mode:
565589
if self.driver.current_url != pre_action_url:
@@ -772,7 +796,7 @@ def bring_to_front(self, selector, by=By.CSS_SELECTOR):
772796

773797
def highlight(self, selector, by=By.CSS_SELECTOR,
774798
loops=settings.HIGHLIGHTS, scroll=True):
775-
""" This method uses fancy javascript to highlight an element.
799+
""" This method uses fancy JavaScript to highlight an element.
776800
Used during demo_mode.
777801
@Params
778802
selector - the selector of the element to find
@@ -930,13 +954,37 @@ def scroll_click(self, selector, by=By.CSS_SELECTOR):
930954
def click_xpath(self, xpath):
931955
self.click(xpath, by=By.XPATH)
932956

957+
def js_click(self, selector, by=By.CSS_SELECTOR):
958+
""" Clicks an element using pure JS. Does not use jQuery. """
959+
selector, by = self._recalculate_selector(selector, by)
960+
if by == By.LINK_TEXT:
961+
message = (
962+
"Pure JavaScript doesn't support clicking by Link Text. "
963+
"You may want to use self.jquery_click() instead, which "
964+
"allows this with :contains(), assuming jQuery isn't blocked. "
965+
"For now, self.js_click() will use a regular WebDriver click.")
966+
logging.debug(message)
967+
self.click(selector, by=by)
968+
return
969+
element = self.wait_for_element_present(
970+
selector, by=by, timeout=settings.SMALL_TIMEOUT)
971+
if self.is_element_visible(selector, by=by):
972+
self._demo_mode_highlight_if_active(selector, by)
973+
if not self.demo_mode:
974+
self._scroll_to_element(element)
975+
css_selector = self.convert_to_css_selector(selector, by=by)
976+
css_selector = re.escape(css_selector)
977+
self.__js_click(selector, by=by) # The real "magic" happens here
978+
self._demo_mode_pause_if_active()
979+
933980
def jquery_click(self, selector, by=By.CSS_SELECTOR):
981+
""" Clicks an element using jQuery. Different from using pure JS. """
934982
selector, by = self._recalculate_selector(selector, by)
935-
selector = self.convert_to_css_selector(selector, by=by)
936983
self.wait_for_element_present(
937984
selector, by=by, timeout=settings.SMALL_TIMEOUT)
938985
if self.is_element_visible(selector, by=by):
939986
self._demo_mode_highlight_if_active(selector, by)
987+
selector = self.convert_to_css_selector(selector, by=by)
940988
selector = self._make_css_match_first_element_only(selector)
941989
click_script = """jQuery('%s')[0].click()""" % selector
942990
self.safe_execute_script(click_script)
@@ -1056,7 +1104,8 @@ def convert_xpath_to_css(self, xpath):
10561104
def convert_to_css_selector(self, selector, by):
10571105
""" This method converts a selector to a CSS_SELECTOR.
10581106
jQuery commands require a CSS_SELECTOR for finding elements.
1059-
This method should only be used for jQuery actions. """
1107+
This method should only be used for jQuery/JavaScript actions.
1108+
Pure JavaScript doesn't support using a:contains("LINK_TEXT"). """
10601109
if by == By.CSS_SELECTOR:
10611110
return selector
10621111
elif by == By.ID:
@@ -1080,21 +1129,21 @@ def convert_to_css_selector(self, selector, by):
10801129

10811130
def set_value(self, selector, new_value, by=By.CSS_SELECTOR,
10821131
timeout=settings.LARGE_TIMEOUT):
1083-
""" This method uses jQuery to update a text field.
1084-
Similar to jquery_update_text_value(), but the element
1085-
doesn't need to be officially visible to work. """
1132+
""" This method uses JavaScript to update a text field. """
10861133
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
10871134
timeout = self._get_new_timeout(timeout)
10881135
if page_utils.is_xpath_selector(selector):
10891136
by = By.XPATH
10901137
orginal_selector = selector
1091-
selector = self.convert_to_css_selector(selector, by=by)
1092-
self._demo_mode_highlight_if_active(selector, by)
1093-
self.scroll_to(selector, by=by, timeout=timeout)
1094-
value = json.dumps(new_value)
1095-
selector = self._make_css_match_first_element_only(selector)
1096-
set_value_script = """jQuery('%s').val(%s)""" % (selector, value)
1097-
self.safe_execute_script(set_value_script)
1138+
css_selector = self.convert_to_css_selector(selector, by=by)
1139+
self._demo_mode_highlight_if_active(orginal_selector, by)
1140+
if not self.demo_mode:
1141+
self.scroll_to(orginal_selector, by=by, timeout=timeout)
1142+
value = re.escape(new_value)
1143+
css_selector = re.escape(css_selector)
1144+
script = ("""document.querySelector('%s').value='%s';"""
1145+
% (css_selector, value))
1146+
self.execute_script(script)
10981147
if new_value.endswith('\n'):
10991148
element = self.wait_for_element_present(
11001149
orginal_selector, by=by, timeout=timeout)
@@ -1103,12 +1152,20 @@ def set_value(self, selector, new_value, by=By.CSS_SELECTOR,
11031152
self.wait_for_ready_state_complete()
11041153
self._demo_mode_pause_if_active()
11051154

1155+
def js_update_text(self, selector, new_value, by=By.CSS_SELECTOR,
1156+
timeout=settings.LARGE_TIMEOUT):
1157+
""" Same as self.set_value() """
1158+
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
1159+
timeout = self._get_new_timeout(timeout)
1160+
self.set_value(
1161+
selector, new_value, by=by, timeout=timeout)
1162+
11061163
def jquery_update_text_value(self, selector, new_value, by=By.CSS_SELECTOR,
11071164
timeout=settings.LARGE_TIMEOUT):
11081165
""" This method uses jQuery to update a text field.
11091166
If the new_value string ends with the newline character,
11101167
WebDriver will finish the call, which simulates pressing
1111-
{Enter/Return} after the text is entered. """
1168+
{Enter/Return} after the text is entered. """
11121169
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
11131170
timeout = self._get_new_timeout(timeout)
11141171
if page_utils.is_xpath_selector(selector):
@@ -1128,7 +1185,7 @@ def jquery_update_text_value(self, selector, new_value, by=By.CSS_SELECTOR,
11281185

11291186
def jquery_update_text(self, selector, new_value, by=By.CSS_SELECTOR,
11301187
timeout=settings.LARGE_TIMEOUT):
1131-
""" The shorter version of jquery_update_text_value()
1188+
""" The shorter version of self.jquery_update_text_value()
11321189
(The longer version remains for backwards compatibility.) """
11331190
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
11341191
timeout = self._get_new_timeout(timeout)
@@ -1740,6 +1797,24 @@ def process_checks(self, print_only=False):
17401797

17411798
############
17421799

1800+
def __js_click(self, selector, by=By.CSS_SELECTOR):
1801+
""" Clicks an element using pure JS. Does not use jQuery. """
1802+
selector, by = self._recalculate_selector(selector, by)
1803+
css_selector = self.convert_to_css_selector(selector, by=by)
1804+
css_selector = re.escape(css_selector)
1805+
script = ("""var simulateClick = function (elem) {
1806+
var evt = new MouseEvent('click', {
1807+
bubbles: true,
1808+
cancelable: true,
1809+
view: window
1810+
});
1811+
var canceled = !elem.dispatchEvent(evt);
1812+
};
1813+
var someLink = document.querySelector('%s');
1814+
simulateClick(someLink);"""
1815+
% css_selector)
1816+
self.execute_script(script)
1817+
17431818
def _get_href_from_link_text(self, link_text, hard_fail=True):
17441819
href = self.get_link_attribute(link_text, "href", hard_fail)
17451820
if not href:

0 commit comments

Comments
 (0)