Skip to content

Commit 6bb2608

Browse files
authored
Merge pull request #397 from seleniumbase/advanced-javascript-clicking
Advanced JavaScript Clicking
2 parents 707a021 + 22ede64 commit 6bb2608

File tree

6 files changed

+73
-23
lines changed

6 files changed

+73
-23
lines changed

README.md

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,16 @@ All-in-one framework for fast & simple browser automation and end-to-end testing
66

77
SeleniumBase uses [pytest](https://github.com/pytest-dev/pytest) for running tests, while using [Selenium WebDriver](https://www.seleniumhq.org/) for controlling web browsers.<br />
88

9-
Watch [my_first_test.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/my_first_test.py) from [examples/](https://github.com/seleniumbase/SeleniumBase/tree/master/examples) running in demo mode:<br />
10-
11-
<img src="https://cdn2.hubspot.net/hubfs/100006/images/my_first_test_mov4.gif" title="SeleniumBase" />
12-
9+
Run [my_first_test.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/my_first_test.py) in Demo Mode to see test assertions:
1310
```bash
14-
pytest my_first_test.py --demo_mode
11+
pytest my_first_test.py --demo
1512
```
1613

17-
Here's the code of [my_first_test.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/my_first_test.py):
14+
<img src="https://cdn2.hubspot.net/hubfs/100006/images/my_first_test_mov4.gif" title="SeleniumBase" />
1815

19-
<img src="https://cdn2.hubspot.net/hubfs/100006/images/my_first_test_4.png" title="SeleniumBase" />
16+
<img src="https://cdn2.hubspot.net/hubfs/100006/images/my_first_test_7.png" title="SeleniumBase" />
2017

21-
SeleniumBase includes additional tools for automated [visual testing](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/visual_testing/ReadMe.md), assisted-QA with [MasterQA](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/master_qa/ReadMe.md), and creating [website tours](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/tour_examples/ReadMe.md).
18+
SeleniumBase includes tools for automated [visual testing](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/visual_testing/ReadMe.md), assisted-QA with [MasterQA](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/master_qa/ReadMe.md), and creating [website tours](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/tour_examples/ReadMe.md).
2219

2320
## <img src="https://cdn2.hubspot.net/hubfs/100006/images/super_square_logo_3a.png" title="SeleniumBase" height="32"> Get Started:
2421

@@ -162,10 +159,10 @@ With Pytest, a green dot means a test passed. An "F" means a test failed.
162159

163160
<a id="seleniumbase_demo_mode"></a> **Use Demo Mode to help you see what tests are asserting.**
164161

165-
If the example test is moving too fast for your eyes, you can run it in **Demo Mode** by adding ``--demo_mode`` on the command-line, which pauses the browser briefly between actions, highlights page elements being acted on, and lets you know what test assertions are happening in real time:
162+
If the example test is moving too fast for your eyes, you can run it in **Demo Mode** by adding ``--demo`` on the command-line, which pauses the browser briefly between actions, highlights page elements being acted on, and lets you know what test assertions are happening in real time:
166163

167164
```bash
168-
pytest my_first_test.py --demo_mode
165+
pytest my_first_test.py --demo
169166
```
170167

171168
**Pytest** includes test discovery. If you don't specify a specific file or folder to run from, ``pytest`` will search all subdirectories automatically for tests to run based on the following matching criteria:
@@ -221,7 +218,7 @@ SeleniumBase provides additional Pytest command-line options for tests:
221218
--start_page=URL # (The starting URL for the web browser when tests begin.)
222219
--log_path=LOG_PATH # (The directory where log files get saved to.)
223220
--archive_logs # (Archive old log files instead of deleting them.)
224-
--demo_mode # (The option to visually see test actions as they occur.)
221+
--demo # (The option to visually see test actions as they occur.)
225222
--demo_sleep=SECONDS # (The option to wait longer after Demo Mode actions.)
226223
--highlights=NUM # (Number of highlight animations for Demo Mode actions.)
227224
--message_duration=SECONDS # (The time length for Messenger alerts.)

examples/my_first_test.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,15 @@ def test_basic(self):
4949
# If you don't specify a `timeout`, a default timeout is used.
5050
# Default timeouts are configured in seleniumbase/config/settings.py
5151
#
52-
# 3. There's usually more than one way to do the same thing. Ex:
52+
# 3. SeleniumBase methods are very versatile. For example,
53+
# self.update_text(SELECTOR, TEXT) does the following:
54+
# * Waits for the element to be visible
55+
# * Waits for the element to be interactive
56+
# * Clears the text field
57+
# * Types in the new text
58+
# * Hits Enter/Submit (if the text ends in "\n")
59+
#
60+
# 4. There's usually more than one way to do the same thing. Ex:
5361
# [
5462
# self.assert_text("xkcd: volume 0", "h3")
5563
# ]
@@ -74,17 +82,17 @@ def test_basic(self):
7482
# title = element.get_attribute("title")
7583
# ]
7684
#
77-
# 4. self.assert_exact_text(TEXT) ignores leading and trailing
85+
# 5. self.assert_exact_text(TEXT) ignores leading and trailing
7886
# whitespace in the TEXT assertion.
7987
# So, self.assert_exact_text("Some Text") will find [" Some Text "].
8088
#
81-
# 5. For backwards-compatibilty, some SeleniumBase methods that do the
89+
# 6. For backwards-compatibilty, some SeleniumBase methods that do the
8290
# same thing have multiple names, kept on from previous versions.
8391
# Ex: wait_for_element_visible() is the same as find_element().
8492
# Both search for and return the element, and raise an exception if
8593
# the element does not appear on the page within the timeout limit.
8694
# And assert_element() also does this (minus returning the element).
8795
#
88-
# 6. For the full method list, see one of the following:
96+
# 7. For the full method list, see one of the following:
8997
# * SeleniumBase/seleniumbase/fixtures/base_case.py
9098
# * SeleniumBase/help_docs/method_summary.md

help_docs/method_summary.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,10 +184,14 @@ self.slow_scroll_to(selector, by=By.CSS_SELECTOR)
184184

185185
self.click_xpath(xpath)
186186

187-
self.js_click(selector, by=By.CSS_SELECTOR)
187+
self.js_click(selector, by=By.CSS_SELECTOR, all_matches=False)
188+
189+
self.js_click_all(selector, by=By.CSS_SELECTOR)
188190

189191
self.jquery_click(selector, by=By.CSS_SELECTOR)
190192

193+
self.jquery_click_all(selector, by=By.CSS_SELECTOR)
194+
191195
self.hide_element(selector, by=By.CSS_SELECTOR)
192196

193197
self.hide_elements(selector, by=By.CSS_SELECTOR)

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ colorama>=0.4.1
3333
pymysql>=0.9.3
3434
pyotp>=2.3.0
3535
boto>=2.49.0
36-
cffi>=1.13.0
36+
cffi>=1.13.1
3737
tqdm>=4.36.1
3838
flake8>=3.7.8
3939
certifi>=2019.9.11

seleniumbase/fixtures/base_case.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1545,8 +1545,9 @@ def click_xpath(self, xpath):
15451545
# so self.click_xpath() is just a longer name for the same action.
15461546
self.click(xpath, by=By.XPATH)
15471547

1548-
def js_click(self, selector, by=By.CSS_SELECTOR):
1549-
""" Clicks an element using pure JS. Does not use jQuery. """
1548+
def js_click(self, selector, by=By.CSS_SELECTOR, all_matches=False):
1549+
""" Clicks an element using pure JS. Does not use jQuery.
1550+
If "all_matches" is False, only the first match is clicked. """
15501551
selector, by = self.__recalculate_selector(selector, by)
15511552
if by == By.LINK_TEXT:
15521553
message = (
@@ -1566,9 +1567,16 @@ def js_click(self, selector, by=By.CSS_SELECTOR):
15661567
css_selector = self.convert_to_css_selector(selector, by=by)
15671568
css_selector = re.escape(css_selector)
15681569
css_selector = self.__escape_quotes_if_needed(css_selector)
1569-
self.__js_click(selector, by=by) # The real "magic" happens here
1570+
if not all_matches:
1571+
self.__js_click(selector, by=by) # The real "magic" happens
1572+
else:
1573+
self.__js_click_all(selector, by=by) # The real "magic" happens
15701574
self.__demo_mode_pause_if_active()
15711575

1576+
def js_click_all(self, selector, by=By.CSS_SELECTOR):
1577+
""" Clicks all matching elements using pure JS. (No jQuery) """
1578+
self.js_click(selector, by=By.CSS_SELECTOR, all_matches=True)
1579+
15721580
def jquery_click(self, selector, by=By.CSS_SELECTOR):
15731581
""" Clicks an element using jQuery. Different from using pure JS. """
15741582
selector, by = self.__recalculate_selector(selector, by)
@@ -1582,6 +1590,18 @@ def jquery_click(self, selector, by=By.CSS_SELECTOR):
15821590
self.safe_execute_script(click_script)
15831591
self.__demo_mode_pause_if_active()
15841592

1593+
def jquery_click_all(self, selector, by=By.CSS_SELECTOR):
1594+
""" Clicks all matching elements using jQuery. """
1595+
selector, by = self.__recalculate_selector(selector, by)
1596+
self.wait_for_element_present(
1597+
selector, by=by, timeout=settings.SMALL_TIMEOUT)
1598+
if self.is_element_visible(selector, by=by):
1599+
self.__demo_mode_highlight_if_active(selector, by)
1600+
selector = self.convert_to_css_selector(selector, by=by)
1601+
click_script = """jQuery('%s').click()""" % selector
1602+
self.safe_execute_script(click_script)
1603+
self.__demo_mode_pause_if_active()
1604+
15851605
def hide_element(self, selector, by=By.CSS_SELECTOR):
15861606
""" Hide the first element on the page that matches the selector. """
15871607
selector, by = self.__recalculate_selector(selector, by)
@@ -1728,7 +1748,7 @@ def choose_file(self, selector, file_path, by=By.CSS_SELECTOR,
17281748
A relative file_path will get converted into an absolute file_path.
17291749
17301750
Example usage:
1731-
self.choose_file('input[type="file"], "my_dir/my_file.txt")
1751+
self.choose_file('input[type="file"]', "my_dir/my_file.txt")
17321752
"""
17331753
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
17341754
timeout = self.__get_new_timeout(timeout)
@@ -3273,6 +3293,27 @@ def __js_click(self, selector, by=By.CSS_SELECTOR):
32733293
% css_selector)
32743294
self.execute_script(script)
32753295

3296+
def __js_click_all(self, selector, by=By.CSS_SELECTOR):
3297+
""" Clicks all matching elements using pure JS. (No jQuery) """
3298+
selector, by = self.__recalculate_selector(selector, by)
3299+
css_selector = self.convert_to_css_selector(selector, by=by)
3300+
css_selector = re.escape(css_selector)
3301+
css_selector = self.__escape_quotes_if_needed(css_selector)
3302+
script = ("""var simulateClick = function (elem) {
3303+
var evt = new MouseEvent('click', {
3304+
bubbles: true,
3305+
cancelable: true,
3306+
view: window
3307+
});
3308+
var canceled = !elem.dispatchEvent(evt);
3309+
};
3310+
var $elements = document.querySelectorAll('%s');
3311+
var index = 0, length = $elements.length;
3312+
for(; index < length; index++){
3313+
simulateClick($elements[index]);}"""
3314+
% css_selector)
3315+
self.execute_script(script)
3316+
32763317
def __jquery_click(self, selector, by=By.CSS_SELECTOR):
32773318
""" Clicks an element using jQuery. Different from using pure JS. """
32783319
selector, by = self.__recalculate_selector(selector, by)

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545

4646
setup(
4747
name='seleniumbase',
48-
version='1.32.14',
48+
version='1.32.15',
4949
description='Fast, Easy, and Reliable Browser Automation & Testing.',
5050
long_description=long_description,
5151
long_description_content_type='text/markdown',
@@ -116,7 +116,7 @@
116116
'pymysql>=0.9.3',
117117
'pyotp>=2.3.0',
118118
'boto>=2.49.0',
119-
'cffi>=1.13.0',
119+
'cffi>=1.13.1',
120120
'tqdm>=4.36.1',
121121
'flake8>=3.7.8',
122122
'certifi>=2019.9.11',

0 commit comments

Comments
 (0)