Maroxtn

Untitled

Jun 16th, 2021
66
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 44.73 KB | None | 0 0
  1. from copy import copy
  2. from helium._impl.match_type import PREFIX_IGNORE_CASE
  3. from helium._impl.selenium_wrappers import WebElementWrapper, \
  4. WebDriverWrapper, FrameIterator, FramesChangedWhileIterating
  5. from helium._impl.util.dictionary import inverse
  6. from helium._impl.util.os_ import make_executable
  7. from helium._impl.util.system import is_windows, get_canonical_os_name
  8. from helium._impl.util.xpath import lower, predicate, predicate_or
  9. from inspect import getfullargspec, ismethod, isfunction
  10. from os import access, X_OK
  11. from os.path import exists, join, dirname
  12. from selenium.common.exceptions import UnexpectedAlertPresentException, \
  13. ElementNotVisibleException, MoveTargetOutOfBoundsException, \
  14. WebDriverException, StaleElementReferenceException, \
  15. NoAlertPresentException, NoSuchWindowException
  16. from selenium.webdriver.remote.webelement import WebElement
  17. from selenium.webdriver.support.wait import WebDriverWait
  18. from selenium.webdriver.support.ui import Select
  19. from selenium.webdriver import Chrome, ChromeOptions, Firefox, FirefoxOptions
  20. from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
  21.  
  22. from time import sleep, time
  23.  
  24. import atexit
  25. import re
  26.  
  27. def might_spawn_window(f):
  28. def f_decorated(self, *args, **kwargs):
  29. driver = self.require_driver()
  30. if driver.is_ie() and AlertImpl(driver).exists():
  31. # Accessing .window_handles in IE when an alert is present raises an
  32. # UnexpectedAlertPresentException. When DesiredCapability
  33. # 'unexpectedAlertBehaviour' is not 'ignore' (the default is
  34. # 'dismiss'), this leads to the alert being closed. Since we don't
  35. # want to unintentionally close alert dialogs, we therefore do not
  36. # access .window_handles in IE when an alert is present.
  37. return f(self, *args, **kwargs)
  38. window_handles_before = driver.window_handles[:]
  39. result = f(self, *args, **kwargs)
  40. # As above, don't access .window_handles in IE if an alert is present:
  41. if not (driver.is_ie() and AlertImpl(driver).exists()):
  42. if driver.is_firefox():
  43. # Unlike Chrome, Firefox does not wait for new windows to open.
  44. # Give it a little time to do so:
  45. sleep(.2)
  46. new_window_handles = [
  47. h for h in driver.window_handles
  48. if h not in window_handles_before
  49. ]
  50. if new_window_handles:
  51. driver.switch_to.window(new_window_handles[0])
  52. return result
  53. return f_decorated
  54.  
  55. def handle_unexpected_alert(f):
  56. def f_decorated(*args, **kwargs):
  57. try:
  58. return f(*args, **kwargs)
  59. except UnexpectedAlertPresentException:
  60. raise UnexpectedAlertPresentException(
  61. "This command is not supported when an alert is present. To "
  62. "accept the alert (this usually corresponds to clicking 'OK') "
  63. "use `Alert().accept()`. To dismiss the alert (ie. 'cancel' "
  64. "it), use `Alert().dismiss()`. If the alert contains a text "
  65. "field, you can use write(...) to set its value. "
  66. "Eg.: `write('hi there!')`."
  67. )
  68. return f_decorated
  69.  
  70. class APIImpl:
  71. DRIVER_REQUIRED_MESSAGE = \
  72. "This operation requires a browser window. Please call one of " \
  73. "the following functions first:\n" \
  74. " * start_chrome()\n" \
  75. " * start_firefox()\n" \
  76. " * set_driver(...)"
  77. def __init__(self):
  78. self.driver = None
  79. def start_firefox_impl(self, url=None, headless=False, options=None):
  80. firefox_driver = self._start_firefox_driver(headless, options)
  81. return self._start(firefox_driver, url)
  82. def _start_firefox_driver(self, headless, options):
  83. firefox_options = FirefoxOptions() if options is None else options
  84. if headless:
  85. firefox_options.headless = True
  86. kwargs = {
  87. 'options': firefox_options,
  88. 'service_log_path': 'nul' if is_windows() else '/dev/null'
  89. }
  90. try:
  91. result = Firefox(**kwargs)
  92. except WebDriverException:
  93. # This usually happens when geckodriver is not on the PATH.
  94. driver_path = self._use_included_web_driver('geckodriver')
  95. result = Firefox(executable_path=driver_path, **kwargs)
  96. atexit.register(self._kill_service, result.service)
  97. return result
  98. def start_chrome_impl(self, url=None, headless=False, options=None):
  99. chrome_driver = self._start_chrome_driver(headless, options)
  100. return self._start(chrome_driver, url)
  101. def _start_chrome_driver(self, headless, options):
  102. chrome_options = self._get_chrome_options(headless, options)
  103. capabilities = DesiredCapabilities.CHROME
  104. capabilities["goog:loggingPrefs"] = {"performance": "ALL"} # chromedriver 75+
  105.  
  106. try:
  107. result = Chrome(options=chrome_options, desired_capabilities=capabilities)
  108. except WebDriverException:
  109. # This usually happens when chromedriver is not on the PATH.
  110. driver_path = self._use_included_web_driver('chromedriver')
  111. result = Chrome(options=chrome_options, executable_path=driver_path)
  112. atexit.register(self._kill_service, result.service)
  113. return result
  114. def _get_chrome_options(self, headless, options):
  115. result = ChromeOptions() if options is None else options
  116. # Prevent Chrome's debug logs from appearing in our console window:
  117. result.add_experimental_option('excludeSwitches', ['enable-logging'])
  118. if headless:
  119. result.add_argument('--headless')
  120. return result
  121. def _use_included_web_driver(self, driver_name):
  122. if is_windows():
  123. driver_name += '.exe'
  124. driver_path = join(
  125. dirname(__file__), 'webdrivers', get_canonical_os_name(),
  126. driver_name
  127. )
  128. if not access(driver_path, X_OK):
  129. try:
  130. make_executable(driver_path)
  131. except Exception:
  132. raise RuntimeError(
  133. "The driver located at %s is not executable." % driver_path
  134. ) from None
  135. return driver_path
  136. def _kill_service(self, service):
  137. old = service.send_remote_shutdown_command
  138. service.send_remote_shutdown_command = lambda: None
  139. try:
  140. service.stop()
  141. finally:
  142. service.send_remote_shutdown_command = old
  143. def _start(self, browser, url=None):
  144. self.set_driver_impl(browser)
  145. if url is not None:
  146. self.go_to_impl(url)
  147. return self.get_driver_impl()
  148. @might_spawn_window
  149. @handle_unexpected_alert
  150. def go_to_impl(self, url):
  151. if '://' not in url:
  152. url = 'http://' + url
  153. self.require_driver().get(url)
  154. def set_driver_impl(self, driver):
  155. self.driver = WebDriverWrapper(driver)
  156. def get_driver_impl(self):
  157. if self.driver is not None:
  158. return self.driver.unwrap()
  159. @might_spawn_window
  160. @handle_unexpected_alert
  161. def write_impl(self, text, into=None):
  162. if into is not None:
  163. from helium import GUIElement
  164. if isinstance(into, GUIElement):
  165. into = into._impl
  166. self._handle_alerts(
  167. self._write_no_alert, self._write_with_alert, text, into=into
  168. )
  169. def _write_no_alert(self, text, into=None):
  170. if into:
  171. if isinstance(into, str):
  172. into = TextFieldImpl(self.require_driver(), into)
  173. def _write(elt):
  174. if hasattr(elt, 'clear') and callable(elt.clear):
  175. elt.clear()
  176. elt.send_keys(text)
  177. self._manipulate(into, _write)
  178. else:
  179. self.require_driver().switch_to.active_element.send_keys(text)
  180. def _write_with_alert(self, text, into=None):
  181. if into is None:
  182. into = AlertImpl(self.require_driver())
  183. if not isinstance(into, AlertImpl):
  184. raise UnexpectedAlertPresentException(
  185. "into=%r is not allowed when an alert is present." % into
  186. )
  187. into._write(text)
  188. def _handle_alerts(self, no_alert, with_alert, *args, **kwargs):
  189. driver = self.require_driver()
  190. if not AlertImpl(driver).exists():
  191. return no_alert(*args, **kwargs)
  192. return with_alert(*args, **kwargs)
  193. @might_spawn_window
  194. @handle_unexpected_alert
  195. def press_impl(self, key):
  196. self.require_driver().switch_to.active_element.send_keys(key)
  197. def click_impl(self, element):
  198. self._perform_mouse_action(element, self._click)
  199. def doubleclick_impl(self, element):
  200. self._perform_mouse_action(element, self._doubleclick)
  201. def hover_impl(self, element):
  202. self._perform_mouse_action(element, self._hover)
  203. def rightclick_impl(self, element):
  204. self._perform_mouse_action(element, self._rightclick)
  205. def press_mouse_on_impl(self, element):
  206. self._perform_mouse_action(element, self._press_mouse_on)
  207. def release_mouse_over_impl(self, element):
  208. self._perform_mouse_action(element, self._release_mouse_over)
  209. def _click(self, selenium_elt, offset):
  210. self._move_to_element(selenium_elt, offset).click().perform()
  211. def _doubleclick(self, selenium_elt, offset):
  212. self._move_to_element(selenium_elt, offset).double_click().perform()
  213. def _hover(self, selenium_elt, offset):
  214. self._move_to_element(selenium_elt, offset).perform()
  215. def _rightclick(self, selenium_elt, offset):
  216. self._move_to_element(selenium_elt, offset).context_click().perform()
  217. def _press_mouse_on(self, selenium_elt, offset):
  218. self._move_to_element(selenium_elt, offset).click_and_hold().perform()
  219. def _release_mouse_over(self, selenium_elt, offset):
  220. self._move_to_element(selenium_elt, offset).release().perform()
  221. def _move_to_element(self, element, offset):
  222. result = self.require_driver().action()
  223. if offset is not None:
  224. result.move_to_element_with_offset(element, *offset)
  225. else:
  226. result.move_to_element(element)
  227. return result
  228. def drag_impl(self, element, to):
  229. with DragHelper(self) as drag_helper:
  230. self._perform_mouse_action(element, drag_helper.start_dragging)
  231. self._perform_mouse_action(to, drag_helper.drop_on_target)
  232. @might_spawn_window
  233. @handle_unexpected_alert
  234. def _perform_mouse_action(self, element, action):
  235. element, offset = self._unwrap_clickable_element(element)
  236. self._manipulate(element, lambda wew: action(wew.unwrap(), offset))
  237. def _unwrap_clickable_element(self, elt):
  238. from helium import HTMLElement, Point
  239. offset = None
  240. if isinstance(elt, str):
  241. elt = ClickableText(self.require_driver(), elt)
  242. elif isinstance(elt, HTMLElement):
  243. elt = elt._impl
  244. elif isinstance(elt, Point):
  245. elt, offset = self._point_to_element_and_offset(elt)
  246. return elt, offset
  247. def _point_to_element_and_offset(self, point):
  248. driver = self.require_driver()
  249. element = WebElementWrapper(driver.execute_script(
  250. 'return document.elementFromPoint(%r, %r);' % (point.x, point.y)
  251. ))
  252. offset = point - (element.location.left, element.location.top)
  253. if offset == (0, 0) and driver.is_firefox():
  254. # In some CSS settings (eg. test_point.html), the (0, 0) point of
  255. # buttons in Firefox is not clickable! The reason for this is that
  256. # Firefox styles buttons to not be perfect squares, but have an
  257. # indent in the corners. This workaround makes `click(btn.top_left)`
  258. # work even when this happens:
  259. offset = (1, 1)
  260. return element, offset
  261. @handle_unexpected_alert
  262. def find_all_impl(self, predicate):
  263. return [
  264. predicate.with_impl(bound_gui_elt_impl)
  265. for bound_gui_elt_impl in predicate._impl.find_all()
  266. ]
  267. def scroll_down_impl(self, num_pixels):
  268. self._scroll_by(0, num_pixels)
  269. def scroll_up_impl(self, num_pixels):
  270. self._scroll_by(0, -num_pixels)
  271. def scroll_right_impl(self, num_pixels):
  272. self._scroll_by(num_pixels, 0)
  273. def scroll_left_impl(self, num_pixels):
  274. self._scroll_by(-num_pixels, 0)
  275. @handle_unexpected_alert
  276. def _scroll_by(self, dx_pixels, dy_pixels):
  277. self.require_driver().execute_script(
  278. 'window.scrollBy(arguments[0], arguments[1]);', dx_pixels, dy_pixels
  279. )
  280. @might_spawn_window
  281. @handle_unexpected_alert
  282. def select_impl(self, combo_box, value):
  283. from helium import ComboBox
  284. if isinstance(combo_box, str):
  285. combo_box = ComboBoxImpl(self.require_driver(), combo_box)
  286. elif isinstance(combo_box, ComboBox):
  287. combo_box = combo_box._impl
  288. def _select(web_element):
  289. if isinstance(web_element, WebElementWrapper):
  290. web_element = web_element.unwrap()
  291. Select(web_element).select_by_visible_text(value)
  292. self._manipulate(combo_box, _select)
  293. def _manipulate(self, gui_or_web_elt, action):
  294. driver = self.require_driver()
  295. if hasattr(gui_or_web_elt, 'perform') \
  296. and callable(gui_or_web_elt.perform):
  297. driver.last_manipulated_element = gui_or_web_elt.perform(action)
  298. else:
  299. if isinstance(gui_or_web_elt, WebElement):
  300. gui_or_web_elt = WebElementWrapper(gui_or_web_elt)
  301. action(gui_or_web_elt)
  302. driver.last_manipulated_element = gui_or_web_elt
  303. @handle_unexpected_alert
  304. def drag_file_impl(self, file_path, to):
  305. to, _ = self._unwrap_clickable_element(to)
  306. drag_and_drop = DragAndDropFile(self.require_driver(), file_path)
  307. drag_and_drop.begin()
  308. try:
  309. # Some web apps (Gmail in particular) only register for the 'drop'
  310. # event when user has dragged the file over the document. We
  311. # therefore simulate this dragging over the document first:
  312. drag_and_drop.drag_over_document()
  313. self._manipulate(to, lambda elt: drag_and_drop.drop_on(elt))
  314. finally:
  315. drag_and_drop.end()
  316. @might_spawn_window
  317. @handle_unexpected_alert
  318. def attach_file_impl(self, file_path, to=None):
  319. from helium import Point
  320. driver = self.require_driver()
  321. if to is None:
  322. to = FileInput(driver)
  323. elif isinstance(to, str):
  324. to = FileInput(driver, to)
  325. elif isinstance(to, Point):
  326. to, _ = self._point_to_element_and_offset(to)
  327. self._manipulate(to, lambda elt: elt.send_keys(file_path))
  328. def refresh_impl(self):
  329. self._handle_alerts(
  330. self._refresh_no_alert, self._refresh_with_alert
  331. )
  332. def _refresh_no_alert(self):
  333. self.require_driver().refresh()
  334. def _refresh_with_alert(self):
  335. AlertImpl(self.require_driver()).accept()
  336. self._refresh_no_alert()
  337. def wait_until_impl(self, condition_fn, timeout_secs=10, interval_secs=0.5):
  338. if ismethod(condition_fn):
  339. is_bound = condition_fn.__self__ is not None
  340. args_spec = getfullargspec(condition_fn).args
  341. unfilled_args = len(args_spec) - (1 if is_bound else 0)
  342. else:
  343. if not isfunction(condition_fn):
  344. condition_fn = condition_fn.__call__
  345. args_spec = getfullargspec(condition_fn).args
  346. unfilled_args = len(args_spec)
  347. condition = \
  348. condition_fn if unfilled_args else lambda driver: condition_fn()
  349. wait = WebDriverWait(
  350. self.require_driver().unwrap(), timeout_secs,
  351. poll_frequency=interval_secs
  352. )
  353. wait.until(condition)
  354. @handle_unexpected_alert
  355. def switch_to_impl(self, window):
  356. driver = self.require_driver()
  357. from helium import Window
  358. if isinstance(window, str):
  359. window = WindowImpl(driver, window)
  360. elif isinstance(window, Window):
  361. window = window._impl
  362. driver.switch_to.window(window.handle)
  363. def kill_browser_impl(self):
  364. self.require_driver().quit()
  365. self.driver = None
  366. @handle_unexpected_alert
  367. def highlight_impl(self, element):
  368. driver = self.require_driver()
  369. from helium import HTMLElement, Text
  370. if isinstance(element, str):
  371. element = Text(element)
  372. if isinstance(element, HTMLElement):
  373. element = element._impl
  374. try:
  375. element = element.first_occurrence
  376. except AttributeError:
  377. pass
  378. previous_style = element.get_attribute("style")
  379. if isinstance(element, WebElementWrapper):
  380. element = element.unwrap()
  381. driver.execute_script(
  382. "arguments[0].setAttribute("
  383. "'style', 'border: 2px solid red; font-weight: bold;'"
  384. ");", element
  385. )
  386. driver.execute_script(
  387. "var target = arguments[0];"
  388. "var previousStyle = arguments[1];"
  389. "setTimeout("
  390. "function() {"
  391. "target.setAttribute('style', previousStyle);"
  392. "}, 2000"
  393. ");", element, previous_style
  394. )
  395. def require_driver(self):
  396. if not self.driver:
  397. raise RuntimeError(self.DRIVER_REQUIRED_MESSAGE)
  398. return self.driver
  399.  
  400. class DragHelper:
  401. def __init__(self, api_impl):
  402. self.api_impl = api_impl
  403. self.is_html_5_drag = None
  404. def __enter__(self):
  405. self._execute_script(
  406. "window.helium = {};"
  407. "window.helium.dragHelper = {"
  408. " createEvent: function(type) {"
  409. " var event = document.createEvent('CustomEvent');"
  410. " event.initCustomEvent(type, true, true, null);"
  411. " event.dataTransfer = {"
  412. " data: {},"
  413. " setData: function(type, val) {"
  414. " this.data[type] = val;"
  415. " },"
  416. " getData: function(type) {"
  417. " return this.data[type];"
  418. " }"
  419. " };"
  420. " return event;"
  421. " }"
  422. "};"
  423. )
  424. return self
  425. def start_dragging(self, element, offset):
  426. if self._attempt_html_5_drag(element):
  427. self.is_html_5_drag = True
  428. else:
  429. self.api_impl._press_mouse_on(element, offset)
  430. def drop_on_target(self, target, offset):
  431. if self.is_html_5_drag:
  432. self._complete_html_5_drag(target)
  433. else:
  434. self.api_impl._release_mouse_over(target, offset)
  435. def _attempt_html_5_drag(self, element_to_drag):
  436. return self._execute_script(
  437. "var source = arguments[0];"
  438. "function getDraggableParent(element) {"
  439. " var previousParent = null;"
  440. " while (element != null && element != previousParent) {"
  441. " previousParent = element;"
  442. " if ('draggable' in element) {"
  443. " var draggable = element.draggable;"
  444. " if (draggable === true)"
  445. " return element;"
  446. " if (typeof draggable == 'string' "
  447. " || draggable instanceof String)"
  448. " if (draggable.toLowerCase() == 'true')"
  449. " return element;"
  450. " }"
  451. " element = element.parentNode;"
  452. " }"
  453. " return null;"
  454. "}"
  455. "var draggableParent = getDraggableParent(source);"
  456. "if (draggableParent == null)"
  457. " return false;"
  458. "window.helium.dragHelper.draggedElement = draggableParent;"
  459. "var dragStart = window.helium.dragHelper.createEvent('dragstart');"
  460. "source.dispatchEvent(dragStart);"
  461. "window.helium.dragHelper.dataTransfer = dragStart.dataTransfer;"
  462. "return true;",
  463. element_to_drag
  464. )
  465. def _complete_html_5_drag(self, on):
  466. self._execute_script(
  467. "var target = arguments[0];"
  468. "var drop = window.helium.dragHelper.createEvent('drop');"
  469. "drop.dataTransfer = window.helium.dragHelper.dataTransfer;"
  470. "target.dispatchEvent(drop);"
  471. "var dragEnd = window.helium.dragHelper.createEvent('dragend');"
  472. "dragEnd.dataTransfer = window.helium.dragHelper.dataTransfer;"
  473. "window.helium.dragHelper.draggedElement.dispatchEvent(dragEnd);",
  474. on
  475. )
  476. def __exit__(self, *_):
  477. self._execute_script("delete window.helium;")
  478. def _execute_script(self, script, *args):
  479. return self.api_impl.require_driver().execute_script(script, *args)
  480.  
  481. class DragAndDropFile:
  482. def __init__(self, driver, file_path):
  483. self.driver = driver
  484. self.file_path = file_path
  485. self.file_input_element = None
  486. self.dragover_event = None
  487. def begin(self):
  488. self._create_file_input_element()
  489. try:
  490. self.file_input_element.send_keys(self.file_path)
  491. except:
  492. self.end()
  493. raise
  494. def _create_file_input_element(self):
  495. # The input needs to be visible to Selenium to allow sending keys to it
  496. # in Firefox and IE.
  497. # According to http://stackoverflow.com/questions/6101461/
  498. # Selenium criteria whether an element is visible or not are the
  499. # following:
  500. # - visibility != hidden
  501. # - display != none (is also checked against every parent element)
  502. # - opacity != 0
  503. # - height and width are both > 0
  504. # - for an input, the attribute type != hidden
  505. # So let's make sure its all good!
  506. self.file_input_element = self.driver.execute_script(
  507. "var input = document.createElement('input');"
  508. "input.type = 'file';"
  509. "input.style.display = 'block';"
  510. "input.style.opacity = '1';"
  511. "input.style.visibility = 'visible';"
  512. "input.style.height = '1px';"
  513. "input.style.width = '1px';"
  514. "if (document.body.childElementCount > 0) { "
  515. " document.body.insertBefore(input, document.body.childNodes[0]);"
  516. "} else { "
  517. " document.body.appendChild(input);"
  518. "}"
  519. "return input;"
  520. )
  521. def drag_over_document(self):
  522. # According to the HTML5 spec, we need to dispatch the dragenter event
  523. # once, and then the dragover event continuously, every 350+-200ms:
  524. # http://www.w3.org/html/wg/drafts/html/master/editing.html#current-drag
  525. # -operation
  526. # Especially IE implements this spec very tightly, and considers the
  527. # dragging to be over if no dragover event occurs for more than ~1sec.
  528. # We thus need to ensure that we keep dispatching the dragover event.
  529.  
  530. # This line used to read `_dispatch_event(..., to='document')`. However,
  531. # this doesn't work when adding a photo to a tweet on Twitter.
  532. # Dispatching the event to document.body fixes this, and also works for
  533. # Gmail:
  534. self._dispatch_event('dragenter', to='document.body')
  535. self.dragover_event = self._prepare_continuous_event(
  536. 'dragover', 'document', interval_msecs=300
  537. )
  538. self.dragover_event.start()
  539. def _dispatch_event(self, event_name, to):
  540. script, args = self._prepare_dispatch_event(event_name, to)
  541. self.driver.execute_script(script, *args)
  542. def _prepare_continuous_event(self, event_name, to, interval_msecs):
  543. script, args = self._prepare_dispatch_event(event_name, to)
  544. return JavaScriptInterval(self.driver, script, args, interval_msecs)
  545. def _prepare_dispatch_event(self, event_name, to):
  546. script = \
  547. "var files = arguments[0].files;" \
  548. "var items = [];" \
  549. "var types = [];" \
  550. "for (var i = 0; i < files.length; i++) {" \
  551. " items[i] = {kind: 'file', type: files[i].type};" \
  552. " types[i] = 'Files';" \
  553. "}" \
  554. "var event = document.createEvent('CustomEvent');" \
  555. "event.initCustomEvent(arguments[1], true, true, 0);" \
  556. "event.dataTransfer = {" \
  557. " files: files," \
  558. " items: items," \
  559. " types: types" \
  560. "};" \
  561. "arguments[2].dispatchEvent(event);"
  562. if isinstance(to, str):
  563. script = script.replace('arguments[2]', to)
  564. args = self.file_input_element, event_name,
  565. else:
  566. args = self.file_input_element, event_name, to.unwrap()
  567. return script, args
  568. def drop_on(self, target):
  569. self.dragover_event.stop()
  570. self._dispatch_event('drop', to=target)
  571. def end(self):
  572. if self.file_input_element is not None:
  573. self.driver.execute_script(
  574. "arguments[0].parentNode.removeChild(arguments[0]);",
  575. self.file_input_element
  576. )
  577. self.file_input_element = None
  578.  
  579. class JavaScriptInterval:
  580. def __init__(self, driver, script, args, interval_msecs):
  581. self.driver = driver
  582. self.script = script
  583. self.args = args
  584. self.interval_msecs = interval_msecs
  585. self._interval_id = None
  586. def start(self):
  587. setinterval_script = (
  588. "var originalArguments = arguments;"
  589. "return setInterval(function() {"
  590. " arguments = originalArguments;"
  591. " %s"
  592. "}, %d);"
  593. ) % (self.script, self.interval_msecs)
  594. self._interval_id = \
  595. self.driver.execute_script(setinterval_script, *self.args)
  596. def stop(self):
  597. self.driver.execute_script(
  598. "clearInterval(arguments[0]);", self._interval_id
  599. )
  600. self._interval_id = None
  601.  
  602. class GUIElementImpl:
  603. def __init__(self, driver):
  604. self._bound_occurrence = None
  605. self._driver = driver
  606. def find_all(self):
  607. if self._is_bound():
  608. yield self
  609. else:
  610. for occurrence in self.find_all_occurrences():
  611. yield self.bound_to_occurrence(occurrence)
  612. def _is_bound(self):
  613. return self._bound_occurrence is not None
  614. def find_all_occurrences(self):
  615. raise NotImplementedError()
  616. def bound_to_occurrence(self, occurrence):
  617. result = copy(self)
  618. result._bound_occurrence = occurrence
  619. return result
  620. def exists(self):
  621. try:
  622. next(self.find_all())
  623. except StopIteration:
  624. return False
  625. else:
  626. return True
  627. @property
  628. def first_occurrence(self):
  629. if not self._is_bound():
  630. self._bind_to_first_occurrence()
  631. return self._bound_occurrence
  632. def _bind_to_first_occurrence(self):
  633. self.perform(lambda _: None)
  634. # _perform_no_wait(...) below now sets _bound_occurrence.
  635. def perform(self, action):
  636. from helium import Config
  637. end_time = time() + Config.implicit_wait_secs
  638. # Try to perform `action` at least once:
  639. result = self._perform_no_wait(action)
  640. while result is None and time() < end_time:
  641. result = self._perform_no_wait(action)
  642. if result is not None:
  643. return result
  644. raise LookupError()
  645. def _perform_no_wait(self, action):
  646. for bound_gui_elt_impl in self.find_all():
  647. occurrence = bound_gui_elt_impl.first_occurrence
  648. try:
  649. action(occurrence)
  650. except Exception as e:
  651. if self.should_ignore_exception(e):
  652. continue
  653. else:
  654. raise
  655. else:
  656. self._bound_occurrence = occurrence
  657. return occurrence
  658. def should_ignore_exception(self, exception):
  659. if isinstance(exception, ElementNotVisibleException):
  660. return True
  661. if isinstance(exception, MoveTargetOutOfBoundsException):
  662. return True
  663. if isinstance(exception, WebDriverException):
  664. msg = exception.msg
  665. if 'is not clickable at point' in msg \
  666. and 'Other element would receive the click' in msg:
  667. # This can happen when the element has moved.
  668. return True
  669. return False
  670.  
  671. class HTMLElementImpl(GUIElementImpl):
  672. def __init__(
  673. self, driver, below=None, to_right_of=None, above=None,
  674. to_left_of=None
  675. ):
  676. super(HTMLElementImpl, self).__init__(driver)
  677. self.below = self._unwrap_element(below)
  678. self.to_right_of = self._unwrap_element(to_right_of)
  679. self.above = self._unwrap_element(above)
  680. self.to_left_of = self._unwrap_element(to_left_of)
  681. self.matches = PREFIX_IGNORE_CASE()
  682. def _unwrap_element(self, element):
  683. if isinstance(element, str):
  684. return TextImpl(self._driver, element)
  685. from helium import HTMLElement
  686. if isinstance(element, HTMLElement):
  687. return element._impl
  688. return element
  689. @property
  690. def width(self):
  691. return self.first_occurrence.location.width
  692. @property
  693. def height(self):
  694. return self.first_occurrence.location.height
  695. @property
  696. def x(self):
  697. return self.first_occurrence.location.left
  698. @property
  699. def y(self):
  700. return self.first_occurrence.location.top
  701. @property
  702. def top_left(self):
  703. from helium import Point
  704. return Point(self.x, self.y)
  705. @property
  706. def web_element(self):
  707. return self.first_occurrence.unwrap()
  708. def find_all_occurrences(self):
  709. self._handle_closed_window()
  710. self._driver.switch_to.default_content()
  711. try:
  712. for frame_index in FrameIterator(self._driver):
  713. search_regions = self._get_search_regions_in_curr_frame()
  714. for occurrence in self.find_all_in_curr_frame():
  715. if self._should_yield(occurrence, search_regions):
  716. occurrence.frame_index = frame_index
  717. yield occurrence
  718. except FramesChangedWhileIterating:
  719. # Abort this search.
  720. pass
  721. def _handle_closed_window(self):
  722. window_handles = self._driver.window_handles
  723. try:
  724. curr_window_handle = self._driver.current_window_handle
  725. except NoSuchWindowException:
  726. window_has_been_closed = True
  727. else:
  728. window_has_been_closed = curr_window_handle not in window_handles
  729. if window_has_been_closed:
  730. self._driver.switch_to.window(window_handles[0])
  731. def _get_search_regions_in_curr_frame(self):
  732. result = []
  733. if self.below:
  734. result.append([
  735. elt.location.is_above
  736. for elt in self.below.find_all_in_curr_frame()
  737. ])
  738. if self.to_right_of:
  739. result.append([
  740. elt.location.is_to_left_of
  741. for elt in self.to_right_of.find_all_in_curr_frame()
  742. ])
  743. if self.above:
  744. result.append([
  745. elt.location.is_below
  746. for elt in self.above.find_all_in_curr_frame()
  747. ])
  748. if self.to_left_of:
  749. result.append([
  750. elt.location.is_to_right_of
  751. for elt in self.to_left_of.find_all_in_curr_frame()
  752. ])
  753. return result
  754. def _should_yield(self, occurrence, search_regions):
  755. return occurrence.is_displayed() and \
  756. self._is_in_any_search_region(occurrence, search_regions)
  757. def _is_in_any_search_region(self, element, search_regions):
  758. for direction in search_regions:
  759. found = False
  760. for search_region in direction:
  761. if search_region(element.location):
  762. found = True
  763. break
  764. if not found:
  765. return False
  766. return True
  767. def find_all_in_curr_frame(self):
  768. raise NotImplementedError()
  769. def _is_enabled(self):
  770. """
  771. Useful for subclasses.
  772. """
  773. return self.first_occurrence.get_attribute('disabled') is None
  774.  
  775. class SImpl(HTMLElementImpl):
  776. def __init__(self, driver, selector, **kwargs):
  777. super(SImpl, self).__init__(driver, **kwargs)
  778. self.selector = selector
  779. def find_all_in_curr_frame(self):
  780. wrap = lambda web_elements: list(map(WebElementWrapper, web_elements))
  781. if self.selector.startswith('@'):
  782. return wrap(self._driver.find_elements_by_name(self.selector[1:]))
  783. if self.selector.startswith('//'):
  784. return wrap(self._driver.find_elements_by_xpath(self.selector))
  785. return wrap(self._driver.find_elements_by_css_selector(self.selector))
  786.  
  787. class HTMLElementIdentifiedByXPath(HTMLElementImpl):
  788. def find_all_in_curr_frame(self):
  789. x_path = self.get_xpath()
  790. return self._sort_search_result(
  791. list(map(
  792. WebElementWrapper, self._driver.find_elements_by_xpath(x_path)
  793. ))
  794. )
  795. def _sort_search_result(self, search_result):
  796. keys_to_result_items = []
  797. for web_elt in search_result:
  798. try:
  799. key = self.get_sort_index(web_elt)
  800. except StaleElementReferenceException:
  801. pass
  802. else:
  803. keys_to_result_items.append((key, web_elt))
  804. sort_key = lambda tpl: tpl[0]
  805. keys_to_result_items.sort(key=sort_key)
  806. result_item = lambda tpl: tpl[1]
  807. return list(map(result_item, keys_to_result_items))
  808. def get_xpath(self):
  809. raise NotImplementedError()
  810. def get_sort_index(self, web_element):
  811. return self._driver.get_distance_to_last_manipulated(web_element) + 1
  812.  
  813. class HTMLElementContainingText(HTMLElementIdentifiedByXPath):
  814. def __init__(self, driver, text=None, **kwargs):
  815. super(HTMLElementContainingText, self).__init__(driver, **kwargs)
  816. self.search_text = text
  817. def get_xpath(self):
  818. xpath_base = "//" + self.get_xpath_node_selector() + \
  819. predicate(self.matches.xpath('.', self.search_text))
  820. return '%s[not(self::script)][not(.%s)]' % (xpath_base, xpath_base)
  821. def get_xpath_node_selector(self):
  822. return '*'
  823.  
  824. class TextImpl(HTMLElementContainingText):
  825. def __init__(self, driver, text=None, include_free_text=True, **kwargs):
  826. super(TextImpl, self).__init__(driver, text, **kwargs)
  827. self.include_free_text = include_free_text
  828. @property
  829. def value(self):
  830. return self.first_occurrence.text
  831. def get_xpath(self):
  832. button_impl = ButtonImpl(self._driver, self.search_text)
  833. link_impl = LinkImpl(self._driver, self.search_text)
  834. components = [
  835. self._get_search_text_xpath(),
  836. button_impl.get_input_button_xpath(),
  837. link_impl.get_xpath()
  838. ]
  839. if self.search_text and self.include_free_text:
  840. components.append(
  841. FreeText(self._driver, self.search_text).get_xpath()
  842. )
  843. return ' | '.join(components)
  844. def _get_search_text_xpath(self):
  845. if self.search_text:
  846. result = super(TextImpl, self).get_xpath()
  847. else:
  848. no_descendant_with_same_text = \
  849. "not(.//*[normalize-space(.)=normalize-space(self::*)])"
  850. result = '//*[text() and %s]' % no_descendant_with_same_text
  851. return result + "[not(self::option)]" + \
  852. ("" if self.include_free_text else "[count(*) <= 1]")
  853.  
  854. class FreeText(HTMLElementContainingText):
  855. def get_xpath_node_selector(self):
  856. return 'text()'
  857. def get_xpath(self):
  858. return super(FreeText, self).get_xpath() + '/..'
  859.  
  860. class LinkImpl(HTMLElementContainingText):
  861. def get_xpath_node_selector(self):
  862. return 'a'
  863. def get_xpath(self):
  864. return super(LinkImpl, self).get_xpath() + ' | ' + \
  865. "//a" + \
  866. predicate(self.matches.xpath('@title', self.search_text)) + \
  867. ' | ' + "//*[@role='link']" + \
  868. predicate(self.matches.xpath('.', self.search_text))
  869. @property
  870. def href(self):
  871. return self.web_element.get_attribute('href')
  872.  
  873. class ListItemImpl(HTMLElementContainingText):
  874. def get_xpath_node_selector(self):
  875. return 'li'
  876.  
  877. class ButtonImpl(HTMLElementContainingText):
  878. def get_xpath_node_selector(self):
  879. return 'button'
  880. def is_enabled(self):
  881. aria_disabled = self.first_occurrence.get_attribute('aria-disabled')
  882. return self._is_enabled() \
  883. and (not aria_disabled or aria_disabled.lower() == 'false')
  884. def get_xpath(self):
  885. has_aria_label = self.matches.xpath('@aria-label', self.search_text)
  886. has_text = self.matches.xpath('.', self.search_text)
  887. has_text_or_aria_label = predicate_or(has_aria_label, has_text)
  888. return ' | '.join([
  889. super(ButtonImpl, self).get_xpath(), self.get_input_button_xpath(),
  890. "//*[@role='button']" + has_text_or_aria_label,
  891. "//button" + predicate(has_aria_label)
  892. ])
  893. def get_input_button_xpath(self):
  894. if self.search_text:
  895. has_value = self.matches.xpath('@value', self.search_text)
  896. has_label = self.matches.xpath('@label', self.search_text)
  897. has_aria_label = self.matches.xpath('@aria-label', self.search_text)
  898. has_title = self.matches.xpath('@title', self.search_text)
  899. has_text = \
  900. predicate_or(has_value, has_label, has_aria_label, has_title)
  901. else:
  902. has_text = ''
  903. return "//input[@type='submit' or @type='button']" + has_text
  904.  
  905. class ImageImpl(HTMLElementIdentifiedByXPath):
  906. def __init__(self, driver, alt, **kwargs):
  907. super(ImageImpl, self).__init__(driver, **kwargs)
  908. self.alt = alt
  909. def get_xpath(self):
  910. return "//img" + predicate(self.matches.xpath('@alt', self.alt))
  911.  
  912. class LabelledElement(HTMLElementImpl):
  913. SECONDARY_SEARCH_DIMENSION_PENALTY_FACTOR = 1.5
  914. def __init__(self, driver, label=None, **kwargs):
  915. super(LabelledElement, self).__init__(driver, **kwargs)
  916. self.label = label
  917. def find_all_in_curr_frame(self):
  918. if not self.label:
  919. result = self._find_elts()
  920. else:
  921. labels = TextImpl(
  922. self._driver, self.label, include_free_text=False
  923. ).find_all_in_curr_frame()
  924. if labels:
  925. result = list(self._filter_elts_belonging_to_labels(
  926. self._find_elts(), labels
  927. ))
  928. else:
  929. result = self._find_elts_by_free_text()
  930. return sorted(result, key=self._driver.get_distance_to_last_manipulated)
  931. def _find_elts(self, xpath=None):
  932. if xpath is None:
  933. xpath = self.get_xpath()
  934. return list(map(
  935. WebElementWrapper, self._driver.find_elements_by_xpath(xpath)
  936. ))
  937. def _find_elts_by_free_text(self):
  938. elt_types = [
  939. xpath.strip().lstrip('/') for xpath in self.get_xpath().split('|')
  940. ]
  941. labels = '//text()' + predicate(self.matches.xpath('.', self.label))
  942. xpath = ' | '.join(
  943. [(labels + '/%s::' + elt_type + '[1]')
  944. % ('preceding-sibling'
  945. if 'checkbox' in elt_type or 'radio' in elt_type
  946. else 'following')
  947. for elt_type in elt_types]
  948. )
  949. return self._find_elts(xpath)
  950. def get_xpath(self):
  951. raise NotImplementedError()
  952. def get_primary_search_direction(self):
  953. return 'to_right_of'
  954. def get_secondary_search_direction(self):
  955. return 'below'
  956. def _filter_elts_belonging_to_labels(self, all_elts, labels):
  957. for label, elt in self._get_labels_with_explicit_elts(all_elts, labels):
  958. yield elt
  959. labels.remove(label)
  960. all_elts.remove(elt)
  961. labels_to_elts = self._get_related_elts(all_elts, labels)
  962. labels_to_elts = self._ensure_at_most_one_label_per_elt(labels_to_elts)
  963. self._retain_closest(labels_to_elts)
  964. for elts_for_label in list(labels_to_elts.values()):
  965. assert len(elts_for_label) <= 1
  966. if elts_for_label:
  967. yield next(iter(elts_for_label))
  968. def _get_labels_with_explicit_elts(self, all_elts, labels):
  969. for label in labels:
  970. if label.tag_name == 'label':
  971. label_target = label.get_attribute('for')
  972. if label_target:
  973. for elt in all_elts:
  974. elt_id = elt.get_attribute('id')
  975. if elt_id.lower() == label_target.lower():
  976. yield label, elt
  977. def _get_related_elts(self, all_elts, labels):
  978. result = {}
  979. for label in labels:
  980. for elt in all_elts:
  981. if self._are_related(elt, label):
  982. if label not in result:
  983. result[label] = set()
  984. result[label].add(elt)
  985. return result
  986. def _are_related(self, elt, label):
  987. if elt.location.intersects(label.location):
  988. return True
  989. prim_search_dir = self.get_primary_search_direction()
  990. sec_search_dir = self.get_secondary_search_direction()
  991. return label.location.distance_to(elt.location) <= 150 and (
  992. elt.location.is_in_direction(prim_search_dir, label.location) or
  993. elt.location.is_in_direction(sec_search_dir, label.location)
  994. )
  995. def _ensure_at_most_one_label_per_elt(self, labels_to_elts):
  996. elts_to_labels = inverse(labels_to_elts)
  997. self._retain_closest(elts_to_labels)
  998. return inverse(elts_to_labels)
  999. def _retain_closest(self, pivots_to_elts):
  1000. for pivot, elts in list(pivots_to_elts.items()):
  1001. if elts:
  1002. # Would like to use a set literal {...} here, but this is not
  1003. # supported in Python 2.6. Thus we need to use set([...]).
  1004. pivots_to_elts[pivot] = set([self._find_closest(pivot, elts)])
  1005. def _find_closest(self, to_pivot, among_elts):
  1006. remaining_elts = iter(among_elts)
  1007. result = next(remaining_elts)
  1008. result_distance = self._compute_distance(result, to_pivot)
  1009. for element in remaining_elts:
  1010. element_distance = self._compute_distance(element, to_pivot)
  1011. if element_distance < result_distance:
  1012. result = element
  1013. result_distance = element_distance
  1014. return result
  1015. def _compute_distance(self, elt_1, elt_2):
  1016. loc_1 = elt_1.location
  1017. loc_2 = elt_2.location
  1018. if loc_1.is_in_direction(self.get_secondary_search_direction(), loc_2):
  1019. factor = self.SECONDARY_SEARCH_DIMENSION_PENALTY_FACTOR
  1020. else:
  1021. factor = 1
  1022. return factor * loc_1.distance_to(loc_2)
  1023.  
  1024. class CompositeElement(HTMLElementImpl):
  1025. def __init__(self, driver, *args, **kwargs):
  1026. super(CompositeElement, self).__init__(driver, **kwargs)
  1027. self.args = [driver] + list(args)
  1028. self.kwargs = kwargs
  1029. self._first_element = None
  1030. @property
  1031. def first_element(self):
  1032. if self._first_element is None:
  1033. self._bind_to_first_occurrence()
  1034. # find_all_in_curr_frame() below now sets _first_element
  1035. return self._first_element
  1036. def find_all_in_curr_frame(self):
  1037. already_yielded = []
  1038. for element in self.get_elements():
  1039. for bound_gui_elt_impl in element.find_all_in_curr_frame():
  1040. if self._first_element is None:
  1041. self._first_element = element
  1042. if bound_gui_elt_impl not in already_yielded:
  1043. yield bound_gui_elt_impl
  1044. already_yielded.append(bound_gui_elt_impl)
  1045. def get_elements(self):
  1046. for element_type in self.get_element_types():
  1047. yield element_type(*self.args, **self.kwargs)
  1048. def get_element_types(self):
  1049. raise NotImplementedError()
  1050.  
  1051. class ClickableText(CompositeElement):
  1052. def __init__(self, driver, text, **kwargs):
  1053. super(ClickableText, self).__init__(driver, text, **kwargs)
  1054. def get_element_types(self):
  1055. return [ButtonImpl, TextImpl, ImageImpl]
  1056.  
  1057. class TextFieldImpl(CompositeElement):
  1058. def __init__(self, driver, label=None, **kwargs):
  1059. super(TextFieldImpl, self).__init__(driver, label, **kwargs)
  1060. def get_element_types(self):
  1061. return [
  1062. StandardTextFieldWithPlaceholder, StandardTextFieldWithLabel,
  1063. AriaTextFieldWithLabel
  1064. ]
  1065. @property
  1066. def value(self):
  1067. return self.first_element.value
  1068. def is_enabled(self):
  1069. return self.first_element.is_enabled()
  1070. def is_editable(self):
  1071. return self.first_element.is_editable()
  1072.  
  1073. class StandardTextFieldWithLabel(LabelledElement):
  1074. @property
  1075. def value(self):
  1076. return self.first_occurrence.get_attribute('value') or ''
  1077. def is_enabled(self):
  1078. return self._is_enabled()
  1079. def is_editable(self):
  1080. return self.first_occurrence.get_attribute('readOnly') is None
  1081. def get_xpath(self):
  1082. return \
  1083. "//input[%s='text' or %s='email' or %s='password' or %s='number' " \
  1084. "or %s='tel' or string-length(@type)=0]" % ((lower('@type'), ) * 5)\
  1085. + " | //textarea | //*[@contenteditable='true']"
  1086.  
  1087. class AriaTextFieldWithLabel(LabelledElement):
  1088. @property
  1089. def value(self):
  1090. return self.first_occurrence.text
  1091. def is_enabled(self):
  1092. return self._is_enabled()
  1093. def is_editable(self):
  1094. return self.first_occurrence.get_attribute('readOnly') is None
  1095. def get_xpath(self):
  1096. return "//*[@role='textbox']"
  1097.  
  1098. class StandardTextFieldWithPlaceholder(HTMLElementIdentifiedByXPath):
  1099. def __init__(self, driver, label, **kwargs):
  1100. super(StandardTextFieldWithPlaceholder, self).__init__(driver, **kwargs)
  1101. self.label = label
  1102. @property
  1103. def value(self):
  1104. return self.first_occurrence.get_attribute('value') or ''
  1105. def is_enabled(self):
  1106. return self._is_enabled()
  1107. def is_editable(self):
  1108. return self.first_occurrence.get_attribute('readOnly') is None
  1109. def get_xpath(self):
  1110. return "(%s)%s" % (
  1111. StandardTextFieldWithLabel(self.label).get_xpath(),
  1112. predicate(self.matches.xpath('@placeholder', self.label))
  1113. )
  1114.  
  1115. class FileInput(LabelledElement):
  1116. def get_xpath(self):
  1117. return "//input[@type='file']"
  1118.  
  1119. class ComboBoxImpl(CompositeElement):
  1120. def __init__(self, driver, label=None, **kwargs):
  1121. super(ComboBoxImpl, self).__init__(driver, label, **kwargs)
  1122. def get_element_types(self):
  1123. return [ComboBoxIdentifiedByDisplayedValue, ComboBoxIdentifiedByLabel]
  1124. def is_editable(self):
  1125. return self.first_occurrence.tag_name != 'select'
  1126. @property
  1127. def value(self):
  1128. selected_value = self._select_driver.first_selected_option
  1129. if selected_value:
  1130. return selected_value.text
  1131. return None
  1132. @property
  1133. def options(self):
  1134. return [option.text for option in self._select_driver.options]
  1135. @property
  1136. def _select_driver(self):
  1137. return Select(self.web_element)
  1138.  
  1139. class ComboBoxIdentifiedByLabel(LabelledElement):
  1140. def get_xpath(self):
  1141. return "//select | //input[@list]"
  1142.  
  1143. class ComboBoxIdentifiedByDisplayedValue(HTMLElementContainingText):
  1144. def get_xpath_node_selector(self):
  1145. return 'option'
  1146. def get_xpath(self):
  1147. option_xpath = \
  1148. super(ComboBoxIdentifiedByDisplayedValue, self).get_xpath()
  1149. return option_xpath + '/ancestor::select[1]'
  1150. def find_all_in_curr_frame(self):
  1151. all_cbs_with_a_matching_value = super(
  1152. ComboBoxIdentifiedByDisplayedValue, self
  1153. ).find_all_in_curr_frame()
  1154. result = []
  1155. for cb in all_cbs_with_a_matching_value:
  1156. for selected_option in Select(cb.unwrap()).all_selected_options:
  1157. if self.matches.text(selected_option.text, self.search_text):
  1158. result.append(cb)
  1159. break
  1160. return result
  1161.  
  1162. class CheckBoxImpl(LabelledElement):
  1163. def is_enabled(self):
  1164. return self._is_enabled()
  1165. def is_checked(self):
  1166. return self.first_occurrence.get_attribute('checked') is not None
  1167. def get_xpath(self):
  1168. return "//input[@type='checkbox']"
  1169. def get_primary_search_direction(self):
  1170. return 'to_left_of'
  1171. def get_secondary_search_direction(self):
  1172. return 'to_right_of'
  1173.  
  1174. class RadioButtonImpl(LabelledElement):
  1175. def is_selected(self):
  1176. return self.first_occurrence.get_attribute('checked') is not None
  1177. def get_xpath(self):
  1178. return "//input[@type='radio']"
  1179. def get_primary_search_direction(self):
  1180. return 'to_left_of'
  1181. def get_secondary_search_direction(self):
  1182. return 'to_right_of'
  1183.  
  1184. class WindowImpl(GUIElementImpl):
  1185. def __init__(self, driver, title=None):
  1186. super(WindowImpl, self).__init__(driver)
  1187. self.search_title = title
  1188. def find_all_occurrences(self):
  1189. result_scores = []
  1190. for handle in self._driver.window_handles:
  1191. window = WindowImpl.SeleniumWindow(self._driver, handle)
  1192. if self.search_title is None:
  1193. result_scores.append((0, window))
  1194. else:
  1195. title = window.title
  1196. if title.startswith(self.search_title):
  1197. score = len(title) - len(self.search_title)
  1198. result_scores.append((score, window))
  1199. score = lambda tpl: tpl[0]
  1200. result_scores.sort(key=score)
  1201. for score, window in result_scores:
  1202. yield window
  1203. @property
  1204. def title(self):
  1205. return self.first_occurrence.title
  1206. @property
  1207. def handle(self):
  1208. return self.first_occurrence.handle
  1209. class SeleniumWindow:
  1210. def __init__(self, driver, handle):
  1211. self.driver = driver
  1212. self.handle = handle
  1213. self._window_handle_before = None
  1214. @property
  1215. def title(self):
  1216. with self:
  1217. return self.driver.title
  1218. def __enter__(self):
  1219. try:
  1220. self._window_handle_before = self.driver.current_window_handle
  1221. except NoSuchWindowException as window_closed:
  1222. do_switch = True
  1223. else:
  1224. do_switch = self._window_handle_before != self.handle
  1225. if do_switch:
  1226. self.driver.switch_to.window(self.handle)
  1227. def __exit__(self, *_):
  1228. if self._window_handle_before and \
  1229. self.driver.current_window_handle != self._window_handle_before:
  1230. self.driver.switch_to.window(self._window_handle_before)
  1231.  
  1232. class AlertImpl(GUIElementImpl):
  1233. def __init__(self, driver, search_text=None):
  1234. super(AlertImpl, self).__init__(driver)
  1235. self.search_text = search_text
  1236. def find_all_occurrences(self):
  1237. try:
  1238. result = self._driver.switch_to.alert
  1239. text = result.text
  1240. if self.search_text is None or text.startswith(self.search_text):
  1241. yield result
  1242. except NoAlertPresentException:
  1243. pass
  1244. @property
  1245. def text(self):
  1246. return self.first_occurrence.text
  1247. def accept(self):
  1248. first_occurrence = self.first_occurrence
  1249. try:
  1250. first_occurrence.accept()
  1251. except WebDriverException as e:
  1252. # Attempt to work around Selenium issue 3544:
  1253. # https://code.google.com/p/selenium/issues/detail?id=3544
  1254. msg = e.msg
  1255. if msg and re.match(
  1256. r"a\.document\.getElementsByTagName\([^\)]*\)\[0\] is "
  1257. r"undefined", msg
  1258. ):
  1259. sleep(0.25)
  1260. first_occurrence.accept()
  1261. else:
  1262. raise
  1263. def dismiss(self):
  1264. self.first_occurrence.dismiss()
  1265. def _write(self, text):
  1266. self.first_occurrence.send_keys(text)
  1267.  
Advertisement
Add Comment
Please, Sign In to add comment