action_chains.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. # Licensed to the Software Freedom Conservancy (SFC) under one
  2. # or more contributor license agreements. See the NOTICE file
  3. # distributed with this work for additional information
  4. # regarding copyright ownership. The SFC licenses this file
  5. # to you under the Apache License, Version 2.0 (the
  6. # "License"); you may not use this file except in compliance
  7. # with the License. You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing,
  12. # software distributed under the License is distributed on an
  13. # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  14. # KIND, either express or implied. See the License for the
  15. # specific language governing permissions and limitations
  16. # under the License.
  17. """
  18. The ActionChains implementation,
  19. """
  20. from selenium.webdriver.remote.command import Command
  21. from .utils import keys_to_typing
  22. from .actions.action_builder import ActionBuilder
  23. class ActionChains(object):
  24. """
  25. ActionChains are a way to automate low level interactions such as
  26. mouse movements, mouse button actions, key press, and context menu interactions.
  27. This is useful for doing more complex actions like hover over and drag and drop.
  28. Generate user actions.
  29. When you call methods for actions on the ActionChains object,
  30. the actions are stored in a queue in the ActionChains object.
  31. When you call perform(), the events are fired in the order they
  32. are queued up.
  33. ActionChains can be used in a chain pattern::
  34. menu = driver.find_element_by_css_selector(".nav")
  35. hidden_submenu = driver.find_element_by_css_selector(".nav #submenu1")
  36. ActionChains(driver).move_to_element(menu).click(hidden_submenu).perform()
  37. Or actions can be queued up one by one, then performed.::
  38. menu = driver.find_element_by_css_selector(".nav")
  39. hidden_submenu = driver.find_element_by_css_selector(".nav #submenu1")
  40. actions = ActionChains(driver)
  41. actions.move_to_element(menu)
  42. actions.click(hidden_submenu)
  43. actions.perform()
  44. Either way, the actions are performed in the order they are called, one after
  45. another.
  46. """
  47. def __init__(self, driver):
  48. """
  49. Creates a new ActionChains.
  50. :Args:
  51. - driver: The WebDriver instance which performs user actions.
  52. """
  53. self._driver = driver
  54. self._actions = []
  55. if self._driver.w3c:
  56. self.w3c_actions = ActionBuilder(driver)
  57. def perform(self):
  58. """
  59. Performs all stored actions.
  60. """
  61. if self._driver.w3c:
  62. self.w3c_actions.perform()
  63. else:
  64. for action in self._actions:
  65. action()
  66. def reset_actions(self):
  67. """
  68. Clears actions that are already stored on the remote end.
  69. """
  70. if self._driver.w3c:
  71. self._driver.execute(Command.W3C_CLEAR_ACTIONS)
  72. else:
  73. self._actions = []
  74. def click(self, on_element=None):
  75. """
  76. Clicks an element.
  77. :Args:
  78. - on_element: The element to click.
  79. If None, clicks on current mouse position.
  80. """
  81. if self._driver.w3c:
  82. self.w3c_actions.pointer_action.click(on_element)
  83. self.w3c_actions.key_action.pause()
  84. self.w3c_actions.key_action.pause()
  85. else:
  86. if on_element:
  87. self.move_to_element(on_element)
  88. self._actions.append(lambda: self._driver.execute(
  89. Command.CLICK, {'button': 0}))
  90. return self
  91. def click_and_hold(self, on_element=None):
  92. """
  93. Holds down the left mouse button on an element.
  94. :Args:
  95. - on_element: The element to mouse down.
  96. If None, clicks on current mouse position.
  97. """
  98. if self._driver.w3c:
  99. self.w3c_actions.pointer_action.click_and_hold(on_element)
  100. self.w3c_actions.key_action.pause()
  101. if on_element:
  102. self.w3c_actions.key_action.pause()
  103. else:
  104. if on_element:
  105. self.move_to_element(on_element)
  106. self._actions.append(lambda: self._driver.execute(
  107. Command.MOUSE_DOWN, {}))
  108. return self
  109. def context_click(self, on_element=None):
  110. """
  111. Performs a context-click (right click) on an element.
  112. :Args:
  113. - on_element: The element to context-click.
  114. If None, clicks on current mouse position.
  115. """
  116. if self._driver.w3c:
  117. self.w3c_actions.pointer_action.context_click(on_element)
  118. self.w3c_actions.key_action.pause()
  119. else:
  120. if on_element:
  121. self.move_to_element(on_element)
  122. self._actions.append(lambda: self._driver.execute(
  123. Command.CLICK, {'button': 2}))
  124. return self
  125. def double_click(self, on_element=None):
  126. """
  127. Double-clicks an element.
  128. :Args:
  129. - on_element: The element to double-click.
  130. If None, clicks on current mouse position.
  131. """
  132. if self._driver.w3c:
  133. self.w3c_actions.pointer_action.double_click(on_element)
  134. for _ in range(4):
  135. self.w3c_actions.key_action.pause()
  136. else:
  137. if on_element:
  138. self.move_to_element(on_element)
  139. self._actions.append(lambda: self._driver.execute(
  140. Command.DOUBLE_CLICK, {}))
  141. return self
  142. def drag_and_drop(self, source, target):
  143. """
  144. Holds down the left mouse button on the source element,
  145. then moves to the target element and releases the mouse button.
  146. :Args:
  147. - source: The element to mouse down.
  148. - target: The element to mouse up.
  149. """
  150. if self._driver.w3c:
  151. self.w3c_actions.pointer_action.click_and_hold(source) \
  152. .move_to(target) \
  153. .release()
  154. for _ in range(3):
  155. self.w3c_actions.key_action.pause()
  156. else:
  157. self.click_and_hold(source)
  158. self.release(target)
  159. return self
  160. def drag_and_drop_by_offset(self, source, xoffset, yoffset):
  161. """
  162. Holds down the left mouse button on the source element,
  163. then moves to the target offset and releases the mouse button.
  164. :Args:
  165. - source: The element to mouse down.
  166. - xoffset: X offset to move to.
  167. - yoffset: Y offset to move to.
  168. """
  169. if self._driver.w3c:
  170. self.w3c_actions.pointer_action.click_and_hold(source) \
  171. .move_to_location(xoffset, yoffset) \
  172. .release()
  173. for _ in range(3):
  174. self.w3c_actions.key_action.pause()
  175. else:
  176. self.click_and_hold(source)
  177. self.move_by_offset(xoffset, yoffset)
  178. self.release()
  179. return self
  180. def key_down(self, value, element=None):
  181. """
  182. Sends a key press only, without releasing it.
  183. Should only be used with modifier keys (Control, Alt and Shift).
  184. :Args:
  185. - value: The modifier key to send. Values are defined in `Keys` class.
  186. - element: The element to send keys.
  187. If None, sends a key to current focused element.
  188. Example, pressing ctrl+c::
  189. ActionChains(driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
  190. """
  191. if element:
  192. self.click(element)
  193. if self._driver.w3c:
  194. self.w3c_actions.key_action.key_down(value)
  195. self.w3c_actions.pointer_action.pause()
  196. else:
  197. self._actions.append(lambda: self._driver.execute(
  198. Command.SEND_KEYS_TO_ACTIVE_ELEMENT,
  199. {"value": keys_to_typing(value)}))
  200. return self
  201. def key_up(self, value, element=None):
  202. """
  203. Releases a modifier key.
  204. :Args:
  205. - value: The modifier key to send. Values are defined in Keys class.
  206. - element: The element to send keys.
  207. If None, sends a key to current focused element.
  208. Example, pressing ctrl+c::
  209. ActionChains(driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
  210. """
  211. if element:
  212. self.click(element)
  213. if self._driver.w3c:
  214. self.w3c_actions.key_action.key_up(value)
  215. self.w3c_actions.pointer_action.pause()
  216. else:
  217. self._actions.append(lambda: self._driver.execute(
  218. Command.SEND_KEYS_TO_ACTIVE_ELEMENT,
  219. {"value": keys_to_typing(value)}))
  220. return self
  221. def move_by_offset(self, xoffset, yoffset):
  222. """
  223. Moving the mouse to an offset from current mouse position.
  224. :Args:
  225. - xoffset: X offset to move to, as a positive or negative integer.
  226. - yoffset: Y offset to move to, as a positive or negative integer.
  227. """
  228. if self._driver.w3c:
  229. self.w3c_actions.pointer_action.move_by(xoffset, yoffset)
  230. self.w3c_actions.key_action.pause()
  231. else:
  232. self._actions.append(lambda: self._driver.execute(
  233. Command.MOVE_TO, {
  234. 'xoffset': int(xoffset),
  235. 'yoffset': int(yoffset)}))
  236. return self
  237. def move_to_element(self, to_element):
  238. """
  239. Moving the mouse to the middle of an element.
  240. :Args:
  241. - to_element: The WebElement to move to.
  242. """
  243. if self._driver.w3c:
  244. self.w3c_actions.pointer_action.move_to(to_element)
  245. self.w3c_actions.key_action.pause()
  246. else:
  247. self._actions.append(lambda: self._driver.execute(
  248. Command.MOVE_TO, {'element': to_element.id}))
  249. return self
  250. def move_to_element_with_offset(self, to_element, xoffset, yoffset):
  251. """
  252. Move the mouse by an offset of the specified element.
  253. Offsets are relative to the top-left corner of the element.
  254. :Args:
  255. - to_element: The WebElement to move to.
  256. - xoffset: X offset to move to.
  257. - yoffset: Y offset to move to.
  258. """
  259. if self._driver.w3c:
  260. self.w3c_actions.pointer_action.move_to(to_element, xoffset, yoffset)
  261. self.w3c_actions.key_action.pause()
  262. else:
  263. self._actions.append(
  264. lambda: self._driver.execute(Command.MOVE_TO, {
  265. 'element': to_element.id,
  266. 'xoffset': int(xoffset),
  267. 'yoffset': int(yoffset)}))
  268. return self
  269. def release(self, on_element=None):
  270. """
  271. Releasing a held mouse button on an element.
  272. :Args:
  273. - on_element: The element to mouse up.
  274. If None, releases on current mouse position.
  275. """
  276. if self._driver.w3c:
  277. self.w3c_actions.pointer_action.release()
  278. self.w3c_actions.key_action.pause()
  279. else:
  280. if on_element:
  281. self.move_to_element(on_element)
  282. self._actions.append(lambda: self._driver.execute(Command.MOUSE_UP, {}))
  283. return self
  284. def send_keys(self, *keys_to_send):
  285. """
  286. Sends keys to current focused element.
  287. :Args:
  288. - keys_to_send: The keys to send. Modifier keys constants can be found in the
  289. 'Keys' class.
  290. """
  291. if self._driver.w3c:
  292. self.w3c_actions.key_action.send_keys(keys_to_send)
  293. else:
  294. self._actions.append(lambda: self._driver.execute(
  295. Command.SEND_KEYS_TO_ACTIVE_ELEMENT, {'value': keys_to_typing(keys_to_send)}))
  296. return self
  297. def send_keys_to_element(self, element, *keys_to_send):
  298. """
  299. Sends keys to an element.
  300. :Args:
  301. - element: The element to send keys.
  302. - keys_to_send: The keys to send. Modifier keys constants can be found in the
  303. 'Keys' class.
  304. """
  305. if self._driver.w3c:
  306. self.w3c_actions.key_action.send_keys(keys_to_send, element=element)
  307. else:
  308. self._actions.append(lambda: element.send_keys(*keys_to_send))
  309. return self
  310. # Context manager so ActionChains can be used in a 'with .. as' statements.
  311. def __enter__(self):
  312. return self # Return created instance of self.
  313. def __exit__(self, _type, _value, _traceback):
  314. pass # Do nothing, does not require additional cleanup.