selenium.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import sys
  2. import unittest
  3. from contextlib import contextmanager
  4. from django.test import LiveServerTestCase, tag
  5. from django.utils.functional 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 (
  67. DesiredCapabilities,
  68. )
  69. return getattr(DesiredCapabilities, browser.upper())
  70. def create_options(self):
  71. options = self.import_options(self.browser)()
  72. if self.headless:
  73. try:
  74. options.headless = True
  75. except AttributeError:
  76. pass # Only Chrome and Firefox support the headless mode.
  77. return options
  78. def create_webdriver(self):
  79. if self.selenium_hub:
  80. from selenium import webdriver
  81. return webdriver.Remote(
  82. command_executor=self.selenium_hub,
  83. desired_capabilities=self.get_capability(self.browser),
  84. )
  85. return self.import_webdriver(self.browser)(options=self.create_options())
  86. @tag('selenium')
  87. class SeleniumTestCase(LiveServerTestCase, metaclass=SeleniumTestCaseBase):
  88. implicit_wait = 10
  89. external_host = None
  90. @classproperty
  91. def live_server_url(cls):
  92. return 'http://%s:%s' % (cls.external_host or cls.host, cls.server_thread.port)
  93. @classproperty
  94. def allowed_host(cls):
  95. return cls.external_host or cls.host
  96. @classmethod
  97. def setUpClass(cls):
  98. cls.selenium = cls.create_webdriver()
  99. cls.selenium.implicitly_wait(cls.implicit_wait)
  100. super().setUpClass()
  101. @classmethod
  102. def _tearDownClassInternal(cls):
  103. # quit() the WebDriver before attempting to terminate and join the
  104. # single-threaded LiveServerThread to avoid a dead lock if the browser
  105. # kept a connection alive.
  106. if hasattr(cls, 'selenium'):
  107. cls.selenium.quit()
  108. super()._tearDownClassInternal()
  109. @contextmanager
  110. def disable_implicit_wait(self):
  111. """Disable the default implicit wait."""
  112. self.selenium.implicitly_wait(0)
  113. try:
  114. yield
  115. finally:
  116. self.selenium.implicitly_wait(self.implicit_wait)