Fixed #29883 -- Added selenium hub support to runtests.py.

This commit is contained in:
Tom Forbes 2018-10-25 18:52:29 +01:00 committed by Tim Graham
parent 5a2dd5ec53
commit d207ac1568
3 changed files with 60 additions and 2 deletions

View File

@ -2,7 +2,11 @@ import sys
import unittest import unittest
from contextlib import contextmanager from contextlib import contextmanager
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from django.test import LiveServerTestCase, tag from django.test import LiveServerTestCase, tag
from django.utils.decorators import classproperty
from django.utils.module_loading import import_string from django.utils.module_loading import import_string
from django.utils.text import capfirst from django.utils.text import capfirst
@ -10,6 +14,10 @@ from django.utils.text import capfirst
class SeleniumTestCaseBase(type(LiveServerTestCase)): class SeleniumTestCaseBase(type(LiveServerTestCase)):
# List of browsers to dynamically create test classes for. # List of browsers to dynamically create test classes for.
browsers = [] browsers = []
# A selenium hub URL to test against.
selenium_hub = None
# The external host Selenium Hub can reach.
external_host = None
# Sentinel value to differentiate browser-specific instances. # Sentinel value to differentiate browser-specific instances.
browser = None browser = None
@ -29,6 +37,10 @@ class SeleniumTestCaseBase(type(LiveServerTestCase)):
# either duplicate tests or prevent pickling of its instances. # either duplicate tests or prevent pickling of its instances.
first_browser = test_class.browsers[0] first_browser = test_class.browsers[0]
test_class.browser = first_browser test_class.browser = first_browser
# Listen on an external interface if using a selenium hub.
host = test_class.host if not test_class.selenium_hub else '0.0.0.0'
test_class.host = host
test_class.external_host = cls.external_host
# Create subclasses for each of the remaining browsers and expose # Create subclasses for each of the remaining browsers and expose
# them through the test's module namespace. # them through the test's module namespace.
module = sys.modules[test_class.__module__] module = sys.modules[test_class.__module__]
@ -37,7 +49,12 @@ class SeleniumTestCaseBase(type(LiveServerTestCase)):
cls, cls,
"%s%s" % (capfirst(browser), name), "%s%s" % (capfirst(browser), name),
(test_class,), (test_class,),
{'browser': browser, '__module__': test_class.__module__} {
'browser': browser,
'host': host,
'external_host': cls.external_host,
'__module__': test_class.__module__,
}
) )
setattr(module, browser_test_class.__name__, browser_test_class) setattr(module, browser_test_class.__name__, browser_test_class)
return test_class return test_class
@ -48,13 +65,31 @@ class SeleniumTestCaseBase(type(LiveServerTestCase)):
def import_webdriver(cls, browser): def import_webdriver(cls, browser):
return import_string("selenium.webdriver.%s.webdriver.WebDriver" % browser) return import_string("selenium.webdriver.%s.webdriver.WebDriver" % browser)
@classmethod
def get_capability(cls, browser):
return getattr(DesiredCapabilities, browser.upper())
def create_webdriver(self): def create_webdriver(self):
if self.selenium_hub:
return webdriver.Remote(
command_executor=self.selenium_hub,
desired_capabilities=self.get_capability(self.browser),
)
return self.import_webdriver(self.browser)() return self.import_webdriver(self.browser)()
@tag('selenium') @tag('selenium')
class SeleniumTestCase(LiveServerTestCase, metaclass=SeleniumTestCaseBase): class SeleniumTestCase(LiveServerTestCase, metaclass=SeleniumTestCaseBase):
implicit_wait = 10 implicit_wait = 10
external_host = None
@classproperty
def live_server_url(cls):
return 'http://%s:%s' % (cls.external_host or cls.host, cls.server_thread.port)
@classproperty
def allowed_host(cls):
return cls.external_host or cls.host
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):

View File

@ -1303,6 +1303,10 @@ class LiveServerTestCase(TransactionTestCase):
def live_server_url(cls): def live_server_url(cls):
return 'http://%s:%s' % (cls.host, cls.server_thread.port) return 'http://%s:%s' % (cls.host, cls.server_thread.port)
@classproperty
def allowed_host(cls):
return cls.host
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super().setUpClass() super().setUpClass()
@ -1316,7 +1320,7 @@ class LiveServerTestCase(TransactionTestCase):
connections_override[conn.alias] = conn connections_override[conn.alias] = conn
cls._live_server_modified_settings = modify_settings( cls._live_server_modified_settings = modify_settings(
ALLOWED_HOSTS={'append': cls.host}, ALLOWED_HOSTS={'append': cls.allowed_host},
) )
cls._live_server_modified_settings.enable() cls._live_server_modified_settings.enable()
cls.server_thread = cls._create_server_thread(connections_override) cls.server_thread = cls._create_server_thread(connections_override)

View File

@ -4,6 +4,7 @@ import atexit
import copy import copy
import os import os
import shutil import shutil
import socket
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
@ -436,6 +437,15 @@ if __name__ == "__main__":
'--selenium', action=ActionSelenium, metavar='BROWSERS', '--selenium', action=ActionSelenium, metavar='BROWSERS',
help='A comma-separated list of browsers to run the Selenium tests against.', help='A comma-separated list of browsers to run the Selenium tests against.',
) )
parser.add_argument(
'--selenium-hub',
help='A URL for a selenium hub instance to use in combination with --selenium.',
)
parser.add_argument(
'--external-host', default=socket.gethostname(),
help='The external host that can be reached by the selenium hub instance when running Selenium '
'tests via Selenium Hub.',
)
parser.add_argument( parser.add_argument(
'--debug-sql', action='store_true', '--debug-sql', action='store_true',
help='Turn on the SQL query logger within tests.', help='Turn on the SQL query logger within tests.',
@ -456,6 +466,12 @@ if __name__ == "__main__":
options = parser.parse_args() options = parser.parse_args()
using_selenium_hub = options.selenium and options.selenium_hub
if options.selenium_hub and not options.selenium:
parser.error('--selenium-hub and --external-host require --selenium to be used.')
if using_selenium_hub and not options.external_host:
parser.error('--selenium-hub and --external-host must be used together.')
# Allow including a trailing slash on app_labels for tab completion convenience # Allow including a trailing slash on app_labels for tab completion convenience
options.modules = [os.path.normpath(labels) for labels in options.modules] options.modules = [os.path.normpath(labels) for labels in options.modules]
@ -470,6 +486,9 @@ if __name__ == "__main__":
options.tags = ['selenium'] options.tags = ['selenium']
elif 'selenium' not in options.tags: elif 'selenium' not in options.tags:
options.tags.append('selenium') options.tags.append('selenium')
if options.selenium_hub:
SeleniumTestCaseBase.selenium_hub = options.selenium_hub
SeleniumTestCaseBase.external_host = options.external_host
SeleniumTestCaseBase.browsers = options.selenium SeleniumTestCaseBase.browsers = options.selenium
if options.bisect: if options.bisect: