Skip to content

Commit fb2c6a3

Browse files
authored
Merge pull request #1241 from seleniumbase/add-click-with-offset
Add "click_with_offset()" method
2 parents 0985608 + 0eb58c0 commit fb2c6a3

File tree

7 files changed

+119
-20
lines changed

7 files changed

+119
-20
lines changed

examples/test_canvas.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from seleniumbase import BaseCase
2+
3+
4+
class CanvasTests(BaseCase):
5+
def get_pixel_colors(self):
6+
# Return the RGB colors of the canvas element's top left pixel
7+
x = 0
8+
y = 0
9+
if self.browser == "safari":
10+
x = 1
11+
y = 1
12+
color = self.execute_script(
13+
"return document.querySelector('canvas').getContext('2d')"
14+
".getImageData(%s,%s,1,1).data;" % (x, y)
15+
)
16+
if self.is_chromium():
17+
return [color[0], color[1], color[2]]
18+
else:
19+
return [color['0'], color['1'], color['2']]
20+
21+
def test_canvas_actions(self):
22+
self.open("https://seleniumbase.io/canvas/")
23+
self.highlight("canvas")
24+
rgb = self.get_pixel_colors()
25+
self.assert_equal(rgb, [221, 242, 231]) # Looks greenish
26+
self.click_with_offset("canvas", 500, 350)
27+
self.highlight("canvas")
28+
rgb = self.get_pixel_colors()
29+
self.assert_equal(rgb, [39, 42, 56]) # Blue by hamburger
30+
31+
def test_canvas_click(self):
32+
self.open("https://seleniumbase.io/other/canvas")
33+
self.click_with_offset("canvas", 300, 200)
34+
self.sleep(1) # Not needed (Lets you see the alert pop up)
35+
alert = self.switch_to_alert()
36+
self.assert_equal(alert.text, "You clicked on the square!")
37+
self.accept_alert()
38+
self.sleep(1) # Not needed (Lets you see the alert go away)

help_docs/method_summary.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ self.click_if_visible(selector, by=By.CSS_SELECTOR)
130130

131131
self.click_active_element()
132132

133+
self.click_with_offset(selector, x, y, by=By.CSS_SELECTOR, mark=False, timeout=None)
134+
133135
self.is_selected(selector, by=By.CSS_SELECTOR, timeout=None)
134136
# Duplicates: self.is_checked(selector, by=By.CSS_SELECTOR, timeout=None)
135137

mkdocs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ edit_uri: ""
1010
site_dir: "site"
1111
docs_dir: "mkdocs_build"
1212
# Copyright
13-
copyright: Copyright © 2014 - 2021 Michael Mintz
13+
copyright: Copyright © 2014 - 2022 Michael Mintz
1414
# Extensions
1515
markdown_extensions:
1616
- admonition

requirements.txt

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ PyYAML>=6.0;python_version>="3.6"
1717
traitlets>=4.3.3;python_version<"3.7"
1818
traitlets>=5.1.1;python_version>="3.7"
1919
certifi>=2021.10.8
20+
filelock>=3.2.1;python_version<"3.6"
21+
filelock>=3.4.1;python_version>="3.6" and python_version<"3.7"
22+
filelock>=3.6.0;python_version>="3.7"
23+
platformdirs>=2.0.2;python_version<"3.6"
24+
platformdirs>=2.4.0;python_version>="3.6" and python_version<"3.7"
25+
platformdirs>=2.5.1;python_version>="3.7"
2026
six==1.16.0
2127
ipdb==0.13.4;python_version<"3.5"
2228
ipdb==0.13.9;python_version>="3.5"
@@ -39,17 +45,14 @@ h11==0.13.0;python_version>="3.7"
3945
trio==0.20.0;python_version>="3.7"
4046
trio-websocket==0.9.2;python_version>="3.7"
4147
pyopenssl==22.0.0;python_version>="3.7"
42-
wsproto==1.0.0;python_version>="3.7"
48+
wsproto==1.1.0;python_version>="3.7"
4349
selenium==3.141.0;python_version<"3.7"
4450
selenium==4.1.2;python_version>="3.7"
4551
msedge-selenium-tools==3.141.3;python_version<"3.7"
4652
more-itertools==5.0.0;python_version<"3.5"
4753
more-itertools==8.12.0;python_version>="3.5"
4854
cssselect==1.1.0
4955
sortedcontainers==2.4.0
50-
filelock==3.2.1;python_version<"3.6"
51-
filelock==3.4.1;python_version>="3.6" and python_version<"3.7"
52-
filelock==3.6.0;python_version>="3.7"
5356
fasteners==0.16;python_version<"3.5"
5457
fasteners==0.16.3;python_version>="3.5" and python_version<"3.6"
5558
fasteners==0.17.3;python_version>="3.6"
@@ -97,13 +100,8 @@ ipython==7.16.1;python_version>="3.6" and python_version<"3.7"
97100
ipython==7.32.0;python_version>="3.7"
98101
matplotlib-inline==0.1.3;python_version>="3.7"
99102
colorama==0.4.4
100-
platformdirs==2.0.2;python_version<"3.6"
101-
platformdirs==2.4.0;python_version>="3.6" and python_version<"3.7"
102-
platformdirs==2.5.1;python_version>="3.7"
103-
pathlib2==2.3.6;python_version<"3.5"
104103
importlib-metadata==2.1.3;python_version<"3.6"
105104
importlib-metadata==4.2.0;python_version>="3.6" and python_version<"3.8"
106-
virtualenv>=20.13.2
107105
pycparser==2.21
108106
pymysql==0.10.1;python_version<"3.6"
109107
pymysql==1.0.2;python_version>="3.6"

seleniumbase/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# seleniumbase package
2-
__version__ = "2.4.15"
2+
__version__ = "2.4.16"

seleniumbase/fixtures/base_case.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1733,6 +1733,69 @@ def click_active_element(self):
17331733
elif self.slow_mode:
17341734
self.__slow_mode_pause_if_active()
17351735

1736+
def click_with_offset(
1737+
self, selector, x, y, by=By.CSS_SELECTOR, mark=False, timeout=None
1738+
):
1739+
"""
1740+
Click an element at an {X,Y}-offset location.
1741+
{0,0} is the top-left corner of the element.
1742+
If mark==True, will draw a dot at location. (Useful for debugging)
1743+
"""
1744+
from selenium.webdriver.common.action_chains import ActionChains
1745+
1746+
self.__check_scope()
1747+
if not timeout:
1748+
timeout = settings.SMALL_TIMEOUT
1749+
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
1750+
timeout = self.__get_new_timeout(timeout)
1751+
selector, by = self.__recalculate_selector(selector, by)
1752+
element = page_actions.wait_for_element_visible(
1753+
self.driver, selector, by, timeout
1754+
)
1755+
self.__demo_mode_highlight_if_active(selector, by)
1756+
if mark:
1757+
selector = self.convert_to_css_selector(selector, by=by)
1758+
selector = re.escape(selector)
1759+
selector = self.__escape_quotes_if_needed(selector)
1760+
px = x - 2
1761+
if px < 0:
1762+
px = 0
1763+
py = y - 2
1764+
if py < 0:
1765+
py = 0
1766+
script = (
1767+
"var canvas = document.querySelector('%s');"
1768+
"var ctx = canvas.getContext('2d');"
1769+
"ctx.fillStyle = '#F80808';"
1770+
"ctx.fillRect(%s, %s, 5, 5);"
1771+
% (selector, px, py)
1772+
)
1773+
self.execute_script(script)
1774+
try:
1775+
element_location = element.location["y"]
1776+
element_location = element_location - 130 + y
1777+
if element_location < 0:
1778+
element_location = 0
1779+
scroll_script = "window.scrollTo(0, %s);" % element_location
1780+
self.driver.execute_script(scroll_script)
1781+
self.sleep(0.1)
1782+
except Exception:
1783+
pass
1784+
try:
1785+
action_chains = ActionChains(self.driver)
1786+
action_chains.move_to_element_with_offset(element, x, y)
1787+
action_chains.click().perform()
1788+
except MoveTargetOutOfBoundsException:
1789+
message = (
1790+
"Target coordinates for click are out-of-bounds!\n"
1791+
"The offset must stay inside the target element!"
1792+
)
1793+
raise Exception(message)
1794+
if self.demo_mode:
1795+
self.__demo_mode_pause_if_active()
1796+
elif self.slow_mode:
1797+
self.__slow_mode_pause_if_active()
1798+
17361799
def is_checked(self, selector, by=By.CSS_SELECTOR, timeout=None):
17371800
"""Determines if a checkbox or a radio button element is checked.
17381801
Returns True if the element is checked.

setup.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@
142142
'traitlets>=4.3.3;python_version<"3.7"',
143143
'traitlets>=5.1.1;python_version>="3.7"',
144144
"certifi>=2021.10.8",
145+
'filelock>=3.2.1;python_version<"3.6"',
146+
'filelock>=3.4.1;python_version>="3.6" and python_version<"3.7"',
147+
'filelock>=3.6.0;python_version>="3.7"',
148+
'platformdirs>=2.0.2;python_version<"3.6"',
149+
'platformdirs>=2.4.0;python_version>="3.6" and python_version<"3.7"',
150+
'platformdirs>=2.5.1;python_version>="3.7"',
145151
"six==1.16.0",
146152
'ipdb==0.13.4;python_version<"3.5"',
147153
'ipdb==0.13.9;python_version>="3.5"',
@@ -164,17 +170,14 @@
164170
'trio==0.20.0;python_version>="3.7"',
165171
'trio-websocket==0.9.2;python_version>="3.7"',
166172
'pyopenssl==22.0.0;python_version>="3.7"',
167-
'wsproto==1.0.0;python_version>="3.7"',
173+
'wsproto==1.1.0;python_version>="3.7"',
168174
'selenium==3.141.0;python_version<"3.7"',
169175
'selenium==4.1.2;python_version>="3.7"',
170176
'msedge-selenium-tools==3.141.3;python_version<"3.7"',
171177
'more-itertools==5.0.0;python_version<"3.5"',
172178
'more-itertools==8.12.0;python_version>="3.5"',
173179
"cssselect==1.1.0",
174180
"sortedcontainers==2.4.0",
175-
'filelock==3.2.1;python_version<"3.6"',
176-
'filelock==3.4.1;python_version>="3.6" and python_version<"3.7"',
177-
'filelock==3.6.0;python_version>="3.7"',
178181
'fasteners==0.16;python_version<"3.5"',
179182
'fasteners==0.16.3;python_version>="3.5" and python_version<"3.6"',
180183
'fasteners==0.17.3;python_version>="3.6"',
@@ -222,13 +225,8 @@
222225
'ipython==7.32.0;python_version>="3.7"', # Requires matplotlib-inline
223226
'matplotlib-inline==0.1.3;python_version>="3.7"', # ipython needs this
224227
"colorama==0.4.4",
225-
'platformdirs==2.0.2;python_version<"3.6"',
226-
'platformdirs==2.4.0;python_version>="3.6" and python_version<"3.7"',
227-
'platformdirs==2.5.1;python_version>="3.7"',
228-
'pathlib2==2.3.6;python_version<"3.5"', # Sync with "virtualenv"
229228
'importlib-metadata==2.1.3;python_version<"3.6"',
230229
'importlib-metadata==4.2.0;python_version>="3.6" and python_version<"3.8"', # noqa: E501
231-
"virtualenv>=20.13.2", # Sync with importlib-metadata and pathlib2
232230
"pycparser==2.21",
233231
'pymysql==0.10.1;python_version<"3.6"',
234232
'pymysql==1.0.2;python_version>="3.6"',

0 commit comments

Comments
 (0)