selenium.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import sys
  2. import unittest
  3. from contextlib import contextmanager
  4. from django.test import LiveServerTestCase, tag
  5. from django.utils.decorators import classproperty
  6. from django.utils.module_loading import import_string
  7. from django.utils.text import capfirst
  8. class SeleniumTestCaseBase(type(LiveServerTestCase)):
  9. # List of browsers to dynamically create test classes for.
  10. browsers = []
  11. # A selenium hub URL to test against.
  12. selenium_hub = None
  13. # The external host Selenium Hub can reach.
  14. external_host = None
  15. # Sentinel value to differentiate browser-specific instances.
  16. browser = None
  17. # Run browsers in headless mode.
  18. headless = False
  19. def __new__(cls, name, bases, attrs):
  20. """
  21. Dynamically create new classes and add them to the test module when
  22. multiple browsers specs are provided (e.g. --selenium=firefox,chrome).
  23. """
  24. test_class = super().__new__(cls, name, bases, attrs)
  25. # If the test class is either browser-specific or a test base, return it.
  26. if test_class.browser or not any(name.startswith('test') and callable(value) for name, value in attrs.items()):
  27. return test_class
  28. elif test_class.browsers:
  29. # Reuse the created test class to make it browser-specific.
  30. # We can't rename it to include the browser name or create a
  31. # subclass like we do with the remaining browsers as it would
  32. # either duplicate tests or prevent pickling of its instances.
  33. first_browser = test_class.browsers[0]
  34. test_class.browser = first_browser
  35. # Listen on an external interface if using a selenium hub.
  36. host = test_class.host if not test_class.selenium_hub else '0.0.0.0'
  37. test_class.host = host
  38. test_class.external_host = cls.external_host
  39. # Create subclasses for each of the remaining browsers and expose
  40. # them through the test's module namespace.
  41. module = sys.modules[test_class.__module__]
  42. for browser in test_class.browsers[1:]:
  43. browser_test_class = cls.__new__(
  44. cls,
  45. "%s%s" % (capfirst(browser), name),
  46. (test_class,),
  47. {
  48. 'browser': browser,
  49. 'host': host,
  50. 'external_host': cls.external_host,
  51. '__module__': test_class.__module__,
  52. }
  53. )
  54. setattr(module, browser_test_class.__name__, browser_test_class)
  55. return test_class
  56. # If no browsers were specified, skip this class (it'll still be discovered).
  57. return unittest.skip('No browsers specified.')(test_class)
  58. @classmethod
  59. def import_webdriver(cls, browser):
  60. return import_string("selenium.webdriver.%s.webdriver.WebDriver" % browser)
  61. @classmethod
  62. def import_options(cls, browser):
  63. return import_string('selenium.webdriver.%s.options.Options' % browser)
  64. @classmethod
  65. def get_capability(cls, browser):
  66. from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
  67. return getattr(DesiredCapabilities, browser.upper())
  68. def create_options(self):
  69. options = self.import_options(self.browser)()
  70. if self.headless:
  71. try:
  72. options.headless = True
  73. except AttributeError:
  74. pass # Only Chrome and Firefox support the headless mode.
  75. return options
  76. def create_webdriver(self):
  77. if self.selenium_hub:
  78. from selenium import webdriver
  79. return webdriver.Remote(
  80. command_executor=self.selenium_hub,
  81. desired_capabilities=self.get_capability(self.browser),
  82. )
  83. return self.import_webdriver(self.browser)(options=self.create_options())
  84. @tag('selenium')
  85. class SeleniumTestCase(LiveServerTestCase, metaclass=SeleniumTestCaseBase):
  86. implicit_wait = 10
  87. external_host = None
  88. @classproperty
  89. def live_server_url(cls):
  90. return 'http://%s:%s' % (cls.external_host or cls.host, cls.server_thread.port)
  91. @classproperty
  92. def allowed_host(cls):
  93. return cls.external_host or cls.host
  94. @classmethod
  95. def setUpClass(cls):
  96. cls.selenium = cls.create_webdriver()
  97. cls.selenium.implicitly_wait(cls.implicit_wait)
  98. super().setUpClass()
  99. @classmethod
  100. def _tearDownClassInternal(cls):
  101. # quit() the WebDriver before attempting to terminate and join the
  102. # single-threaded LiveServerThread to avoid a dead lock if the browser
  103. # kept a connection alive.
  104. if hasattr(cls, 'selenium'):
  105. cls.selenium.quit()
  106. super()._tearDownClassInternal()
  107. @contextmanager
  108. def disable_implicit_wait(self):
  109. """Disable the default implicit wait."""
  110. self.selenium.implicitly_wait(0)
  111. try:
  112. yield
  113. finally:
  114. self.selenium.implicitly_wait(self.implicit_wait)