From d207ac1568bb4dee305f6692ed7ddee8a1ff8b99 Mon Sep 17 00:00:00 2001 From: Tom Forbes Date: Thu, 25 Oct 2018 18:52:29 +0100 Subject: [PATCH] Fixed #29883 -- Added selenium hub support to runtests.py. --- django/test/selenium.py | 37 ++++++++++++++++++++++++++++++++++++- django/test/testcases.py | 6 +++++- tests/runtests.py | 19 +++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/django/test/selenium.py b/django/test/selenium.py index 9bf1e1038de..976f6ab06e0 100644 --- a/django/test/selenium.py +++ b/django/test/selenium.py @@ -2,7 +2,11 @@ import sys import unittest from contextlib import contextmanager +from selenium import webdriver +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities + from django.test import LiveServerTestCase, tag +from django.utils.decorators import classproperty from django.utils.module_loading import import_string from django.utils.text import capfirst @@ -10,6 +14,10 @@ from django.utils.text import capfirst class SeleniumTestCaseBase(type(LiveServerTestCase)): # List of browsers to dynamically create test classes for. 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. browser = None @@ -29,6 +37,10 @@ class SeleniumTestCaseBase(type(LiveServerTestCase)): # either duplicate tests or prevent pickling of its instances. first_browser = test_class.browsers[0] 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 # them through the test's module namespace. module = sys.modules[test_class.__module__] @@ -37,7 +49,12 @@ class SeleniumTestCaseBase(type(LiveServerTestCase)): cls, "%s%s" % (capfirst(browser), name), (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) return test_class @@ -48,13 +65,31 @@ class SeleniumTestCaseBase(type(LiveServerTestCase)): def import_webdriver(cls, 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): + 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)() @tag('selenium') class SeleniumTestCase(LiveServerTestCase, metaclass=SeleniumTestCaseBase): 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 def setUpClass(cls): diff --git a/django/test/testcases.py b/django/test/testcases.py index f327e535610..2c358c01af0 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -1303,6 +1303,10 @@ class LiveServerTestCase(TransactionTestCase): def live_server_url(cls): return 'http://%s:%s' % (cls.host, cls.server_thread.port) + @classproperty + def allowed_host(cls): + return cls.host + @classmethod def setUpClass(cls): super().setUpClass() @@ -1316,7 +1320,7 @@ class LiveServerTestCase(TransactionTestCase): connections_override[conn.alias] = conn 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.server_thread = cls._create_server_thread(connections_override) diff --git a/tests/runtests.py b/tests/runtests.py index 88971f43a11..a4107772030 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -4,6 +4,7 @@ import atexit import copy import os import shutil +import socket import subprocess import sys import tempfile @@ -436,6 +437,15 @@ if __name__ == "__main__": '--selenium', action=ActionSelenium, metavar='BROWSERS', 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( '--debug-sql', action='store_true', help='Turn on the SQL query logger within tests.', @@ -456,6 +466,12 @@ if __name__ == "__main__": 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 options.modules = [os.path.normpath(labels) for labels in options.modules] @@ -470,6 +486,9 @@ if __name__ == "__main__": options.tags = ['selenium'] elif 'selenium' not in options.tags: options.tags.append('selenium') + if options.selenium_hub: + SeleniumTestCaseBase.selenium_hub = options.selenium_hub + SeleniumTestCaseBase.external_host = options.external_host SeleniumTestCaseBase.browsers = options.selenium if options.bisect: