selenium_helpers_base.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. #! /usr/bin/env python
  2. # ex:ts=4:sw=4:sts=4:et
  3. # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
  4. #
  5. # BitBake Toaster Implementation
  6. #
  7. # Copyright (C) 2013-2016 Intel Corporation
  8. #
  9. # SPDX-License-Identifier: GPL-2.0-only
  10. #
  11. # The Wait class and some of SeleniumDriverHelper and SeleniumTestCase are
  12. # modified from Patchwork, released under the same licence terms as Toaster:
  13. # https://github.com/dlespiau/patchwork/blob/master/patchwork/tests.browser.py
  14. """
  15. Helper methods for creating Toaster Selenium tests which run within
  16. the context of Django unit tests.
  17. """
  18. import os
  19. import time
  20. import unittest
  21. from django.contrib.staticfiles.testing import StaticLiveServerTestCase
  22. from selenium import webdriver
  23. from selenium.webdriver.support.ui import WebDriverWait
  24. from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
  25. from selenium.common.exceptions import NoSuchElementException, \
  26. StaleElementReferenceException, TimeoutException
  27. def create_selenium_driver(cls,browser='chrome'):
  28. # set default browser string based on env (if available)
  29. env_browser = os.environ.get('TOASTER_TESTS_BROWSER')
  30. if env_browser:
  31. browser = env_browser
  32. if browser == 'chrome':
  33. return webdriver.Chrome(
  34. service_args=["--verbose", "--log-path=selenium.log"]
  35. )
  36. elif browser == 'firefox':
  37. return webdriver.Firefox()
  38. elif browser == 'marionette':
  39. capabilities = DesiredCapabilities.FIREFOX
  40. capabilities['marionette'] = True
  41. return webdriver.Firefox(capabilities=capabilities)
  42. elif browser == 'ie':
  43. return webdriver.Ie()
  44. elif browser == 'phantomjs':
  45. return webdriver.PhantomJS()
  46. elif browser == 'remote':
  47. # if we were to add yet another env variable like TOASTER_REMOTE_BROWSER
  48. # we could let people pick firefox or chrome, left for later
  49. remote_hub= os.environ.get('TOASTER_REMOTE_HUB')
  50. driver = webdriver.Remote(remote_hub,
  51. webdriver.DesiredCapabilities.FIREFOX.copy())
  52. driver.get("http://%s:%s"%(cls.server_thread.host,cls.server_thread.port))
  53. return driver
  54. else:
  55. msg = 'Selenium driver for browser %s is not available' % browser
  56. raise RuntimeError(msg)
  57. class Wait(WebDriverWait):
  58. """
  59. Subclass of WebDriverWait with predetermined timeout and poll
  60. frequency. Also deals with a wider variety of exceptions.
  61. """
  62. _TIMEOUT = 10
  63. _POLL_FREQUENCY = 0.5
  64. def __init__(self, driver):
  65. super(Wait, self).__init__(driver, self._TIMEOUT, self._POLL_FREQUENCY)
  66. def until(self, method, message=''):
  67. """
  68. Calls the method provided with the driver as an argument until the
  69. return value is not False.
  70. """
  71. end_time = time.time() + self._timeout
  72. while True:
  73. try:
  74. value = method(self._driver)
  75. if value:
  76. return value
  77. except NoSuchElementException:
  78. pass
  79. except StaleElementReferenceException:
  80. pass
  81. time.sleep(self._poll)
  82. if time.time() > end_time:
  83. break
  84. raise TimeoutException(message)
  85. def until_not(self, method, message=''):
  86. """
  87. Calls the method provided with the driver as an argument until the
  88. return value is False.
  89. """
  90. end_time = time.time() + self._timeout
  91. while True:
  92. try:
  93. value = method(self._driver)
  94. if not value:
  95. return value
  96. except NoSuchElementException:
  97. return True
  98. except StaleElementReferenceException:
  99. pass
  100. time.sleep(self._poll)
  101. if time.time() > end_time:
  102. break
  103. raise TimeoutException(message)
  104. class SeleniumTestCaseBase(unittest.TestCase):
  105. """
  106. NB StaticLiveServerTestCase is used as the base test case so that
  107. static files are served correctly in a Selenium test run context; see
  108. https://docs.djangoproject.com/en/1.9/ref/contrib/staticfiles/#specialized-test-case-to-support-live-testing
  109. """
  110. @classmethod
  111. def setUpClass(cls):
  112. """ Create a webdriver driver at the class level """
  113. super(SeleniumTestCaseBase, cls).setUpClass()
  114. # instantiate the Selenium webdriver once for all the test methods
  115. # in this test case
  116. cls.driver = create_selenium_driver(cls)
  117. cls.driver.maximize_window()
  118. @classmethod
  119. def tearDownClass(cls):
  120. """ Clean up webdriver driver """
  121. cls.driver.quit()
  122. super(SeleniumTestCaseBase, cls).tearDownClass()
  123. def get(self, url):
  124. """
  125. Selenium requires absolute URLs, so convert Django URLs returned
  126. by resolve() or similar to absolute ones and get using the
  127. webdriver instance.
  128. url: a relative URL
  129. """
  130. abs_url = '%s%s' % (self.live_server_url, url)
  131. self.driver.get(abs_url)
  132. def find(self, selector):
  133. """ Find single element by CSS selector """
  134. return self.driver.find_element_by_css_selector(selector)
  135. def find_all(self, selector):
  136. """ Find all elements matching CSS selector """
  137. return self.driver.find_elements_by_css_selector(selector)
  138. def element_exists(self, selector):
  139. """
  140. Return True if one element matching selector exists,
  141. False otherwise
  142. """
  143. return len(self.find_all(selector)) == 1
  144. def focused_element(self):
  145. """ Return the element which currently has focus on the page """
  146. return self.driver.switch_to.active_element
  147. def wait_until_present(self, selector):
  148. """ Wait until element matching CSS selector is on the page """
  149. is_present = lambda driver: self.find(selector)
  150. msg = 'An element matching "%s" should be on the page' % selector
  151. element = Wait(self.driver).until(is_present, msg)
  152. return element
  153. def wait_until_visible(self, selector):
  154. """ Wait until element matching CSS selector is visible on the page """
  155. is_visible = lambda driver: self.find(selector).is_displayed()
  156. msg = 'An element matching "%s" should be visible' % selector
  157. Wait(self.driver).until(is_visible, msg)
  158. return self.find(selector)
  159. def wait_until_focused(self, selector):
  160. """ Wait until element matching CSS selector has focus """
  161. is_focused = \
  162. lambda driver: self.find(selector) == self.focused_element()
  163. msg = 'An element matching "%s" should be focused' % selector
  164. Wait(self.driver).until(is_focused, msg)
  165. return self.find(selector)
  166. def enter_text(self, selector, value):
  167. """ Insert text into element matching selector """
  168. # note that keyup events don't occur until the element is clicked
  169. # (in the case of <input type="text"...>, for example), so simulate
  170. # user clicking the element before inserting text into it
  171. field = self.click(selector)
  172. field.send_keys(value)
  173. return field
  174. def click(self, selector):
  175. """ Click on element which matches CSS selector """
  176. element = self.wait_until_visible(selector)
  177. element.click()
  178. return element
  179. def get_page_source(self):
  180. """ Get raw HTML for the current page """
  181. return self.driver.page_source