Ticket #2879: 2879.selenium-support.2.diff
File 2879.selenium-support.2.diff, 24.0 KB (added by , 13 years ago) |
---|
-
django/conf/global_settings.py
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 6b09be2..51f97cf 100644
a b DEFAULT_EXCEPTION_REPORTER_FILTER = 'django.views.debug.SafeExceptionReporterFil 560 560 # The name of the class to use to run the test suite 561 561 TEST_RUNNER = 'django.test.simple.DjangoTestSuiteRunner' 562 562 563 # Selenium settings 564 SELENIUM_SERVER_ADDRESS = 'localhost' 565 SELENIUM_SERVER_PORT = 4444 566 SELENIUM_BROWSER = '*firefox' 567 563 568 ############ 564 569 # FIXTURES # 565 570 ############ -
django/test/testcases.py
diff --git a/django/test/testcases.py b/django/test/testcases.py index ee22ac2..871b7de 100644
a b 1 1 from __future__ import with_statement 2 2 3 import httplib 3 4 import re 4 5 import sys 5 6 from functools import wraps 6 7 from urlparse import urlsplit, urlunsplit 7 8 from xml.dom.minidom import parseString, Node 9 import select 10 import socket 11 import threading 8 12 9 13 from django.conf import settings 14 from django.contrib.staticfiles.handlers import StaticFilesHandler 10 15 from django.core import mail 11 16 from django.core.exceptions import ValidationError 17 from django.core.handlers.wsgi import WSGIHandler 12 18 from django.core.management import call_command 13 19 from django.core.signals import request_started 20 from django.core.servers.basehttp import (WSGIRequestHandler, WSGIServer, 21 WSGIServerException) 14 22 from django.core.urlresolvers import clear_url_caches 15 23 from django.core.validators import EMPTY_VALUES 16 24 from django.db import (transaction, connection, connections, DEFAULT_DB_ALIAS, … … from django.test.utils import (get_warnings_state, restore_warnings_state, 23 31 override_settings) 24 32 from django.utils import simplejson, unittest as ut2 25 33 from django.utils.encoding import smart_str 34 from django.utils.unittest.case import skipUnless 26 35 27 36 __all__ = ('DocTestRunner', 'OutputChecker', 'TestCase', 'TransactionTestCase', 28 37 'SimpleTestCase', 'skipIfDBFeature', 'skipUnlessDBFeature') … … def skipUnlessDBFeature(feature): 732 741 """ 733 742 return _deferredSkip(lambda: not getattr(connection.features, feature), 734 743 "Database doesn't support feature %s" % feature) 744 745 class QuietWSGIRequestHandler(WSGIRequestHandler): 746 """ 747 Just a regular WSGIRequestHandler except it doesn't log to the standard 748 output any of the requests received, so as to not clutter the output for 749 the tests' results. 750 """ 751 def log_message(*args): 752 pass 753 754 class NonBlockingWSGIServer(WSGIServer): 755 """ 756 The code in this class is borrowed from the `SocketServer.BaseServer` class 757 in Python 2.6. The important functionality here is that the server is non- 758 blocking and that it can be shut down at any moment. This is made possible 759 by the server regularly polling the socket and checking if it has been 760 asked to stop. 761 Note for the future: Once Django stops supporting Python 2.5, this class 762 can be removed as `WSGIServer` will have this ability to shutdown on 763 demand. 764 """ 765 766 def __init__(self, *args, **kwargs): 767 super(NonBlockingWSGIServer, self).__init__(*args, **kwargs) 768 self.__is_shut_down = threading.Event() 769 self.__serving = False 770 771 def serve_forever(self, poll_interval=0.5): 772 """Handle one request at a time until shutdown. 773 774 Polls for shutdown every poll_interval seconds. 775 """ 776 self.__serving = True 777 self.__is_shut_down.clear() 778 while self.__serving: 779 r, w, e = select.select([self], [], [], poll_interval) 780 if r: 781 self._handle_request_noblock() 782 self.__is_shut_down.set() 783 784 def shutdown(self): 785 """Stops the serve_forever loop. 786 787 Blocks until the loop has finished. This must be called while 788 serve_forever() is running in another thread, or it will 789 deadlock. 790 """ 791 self.__serving = False 792 self.__is_shut_down.wait() 793 794 def handle_request(self): 795 """Handle one request, possibly blocking. 796 """ 797 fd_sets = select.select([self], [], [], None) 798 if not fd_sets[0]: 799 return 800 self._handle_request_noblock() 801 802 def _handle_request_noblock(self): 803 """Handle one request, without blocking. 804 805 I assume that select.select has returned that the socket is 806 readable before this function was called, so there should be 807 no risk of blocking in get_request(). 808 """ 809 try: 810 request, client_address = self.get_request() 811 except socket.error: 812 return 813 if self.verify_request(request, client_address): 814 try: 815 self.process_request(request, client_address) 816 except Exception: 817 self.handle_error(request, client_address) 818 self.close_request(request) 819 820 class LiveServerThread(threading.Thread): 821 """ 822 Thread for running a live http server while the tests are running. 823 """ 824 825 def __init__(self, address, port, fixtures): 826 self.address = address 827 self.port = port 828 self.fixtures = fixtures 829 self.is_ready = threading.Event() 830 self.error = None 831 super(LiveServerThread, self).__init__() 832 833 def run(self): 834 """ 835 Sets up live server and database and loops over handling http requests. 836 """ 837 try: 838 # Instantiate and start the server 839 self.httpd = NonBlockingWSGIServer( 840 (self.address, self.port), QuietWSGIRequestHandler) 841 handler = StaticFilesHandler(WSGIHandler()) 842 self.httpd.set_app(handler) 843 844 # If the database is in memory we must reload the data in this new 845 # thread. 846 if (settings.DATABASES['default']['ENGINE'] == 'django.db.backends.sqlite3' or 847 settings.DATABASES['default']['TEST_NAME']): 848 connection.creation.create_test_db(0) 849 # Import the fixtures into the test database 850 if hasattr(self, 'fixtures'): 851 call_command('loaddata', *self.fixtures, 852 **{'verbosity': 0}) 853 self.is_ready.set() 854 self.httpd.serve_forever() 855 except WSGIServerException, e: 856 self.error = e 857 self.is_ready.set() 858 859 def join(self, timeout=None): 860 self.httpd.shutdown() 861 super(LiveServerThread, self).join(timeout) 862 863 class LiveServerTestCase(TestCase): 864 """ 865 Does basically the same as TestCase but also launches a live http server in 866 a separate thread so that the tests may use another testing framework, such 867 as Selenium for example, instead of the built-in dummy client. 868 """ 869 870 django_server_address = '127.0.0.1' # TODO: Should those be settings instead 871 django_server_port = 8000 # of class attributes? 872 fixtures = [] 873 874 def setUp(self): 875 # Launch the Django live server's thread 876 self.server_thread = LiveServerThread(self.django_server_address, 877 int(self.django_server_port), fixtures=self.fixtures) 878 self.server_thread.start() 879 super(LiveServerTestCase, self).setUp() 880 881 def tearDown(self): 882 # Terminate the Django server's thread 883 self.server_thread.join() 884 super(LiveServerTestCase, self).tearDown() 885 886 try: 887 # Check if the 'selenium' package is installed 888 from selenium import selenium 889 selenium_installed = True 890 except ImportError: 891 selenium_installed = False 892 893 # Check if the Selenium server is running 894 try: 895 conn = httplib.HTTPConnection(settings.SELENIUM_SERVER_ADDRESS, 896 settings.SELENIUM_SERVER_PORT) 897 try: 898 conn.request("GET", "/selenium-server/driver/", '', {}) 899 finally: 900 conn.close() 901 selenium_server_running = True 902 except socket.error: 903 selenium_server_running = False 904 905 class SeleniumTestCase(LiveServerTestCase): 906 """ 907 Does basically the same as TestServerTestCase but also connects to the 908 Selenium server. The selenium client is then available with 909 'self.selenium'. The requirements are to have the 'selenium' installed in 910 the python path and to have the Selenium server running. If those 911 requirements are not filled then the tests will be skipped. 912 """ 913 914 def setUp(self): 915 super(SeleniumTestCase, self).setUp() 916 # Launch the Selenium server 917 selenium_browser_url = 'http://%s:%s' % ( 918 self.django_server_address, self.django_server_port) 919 self.selenium = selenium( 920 settings.SELENIUM_SERVER_ADDRESS, 921 int(settings.SELENIUM_SERVER_PORT), 922 settings.SELENIUM_BROWSER, 923 selenium_browser_url) 924 self.selenium.start() 925 # Wait for the Django server to be ready 926 self.server_thread.is_ready.wait() 927 if self.server_thread.error: 928 raise self.server_thread.error 929 930 def tearDown(self): 931 self.selenium.stop() 932 super(SeleniumTestCase, self).tearDown() 933 934 SeleniumTestCase = skipUnless(selenium_installed, 935 'The \'selenium\' package isn\'t installed')(SeleniumTestCase) 936 SeleniumTestCase = skipUnless(selenium_server_running, 937 'Can\'t connect to the Selenium server using address %s and port %s' % ( 938 settings.SELENIUM_SERVER_ADDRESS, settings.SELENIUM_SERVER_PORT) 939 )(SeleniumTestCase) 940 No newline at end of file -
docs/internals/contributing/writing-code/unit-tests.txt
diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt index 5ec09fe..4a50a77 100644
a b Going beyond that, you can specify an individual test method like this: 122 122 123 123 ./runtests.py --settings=path.to.settings i18n.TranslationTests.test_lazy_objects 124 124 125 Running the Selenium tests 126 ~~~~~~~~~~~~~~~~~~~~~~~~~~ 127 128 Some admin tests require Selenium to work via a real Web browser. To allow 129 those tests to run and not be skipped, you must install the selenium_ package 130 into the Python path and download the `Selenium server (>2.11.0)`_. The 131 Selenium server must then be started with the following command: 132 133 .. code-block:: bash 134 135 java -jar selenium-server-standalone-2.11.0.jar 136 137 You may then run the tests normally, for example: 138 139 .. code-block:: bash 140 141 ./runtests.py --settings=test_sqlite admin_inlines 142 125 143 Running all the tests 126 144 ~~~~~~~~~~~~~~~~~~~~~ 127 145 … … dependencies: 135 153 * setuptools_ 136 154 * memcached_, plus a :ref:`supported Python binding <memcached>` 137 155 * gettext_ (:ref:`gettext_on_windows`) 156 * selenium_ plus the `Selenium server (>2.11.0)`_ 138 157 139 158 If you want to test the memcached cache backend, you'll also need to define 140 159 a :setting:`CACHES` setting that points at your memcached instance. … … associated tests will be skipped. 149 168 .. _setuptools: http://pypi.python.org/pypi/setuptools/ 150 169 .. _memcached: http://www.danga.com/memcached/ 151 170 .. _gettext: http://www.gnu.org/software/gettext/manual/gettext.html 171 .. _selenium: http://pypi.python.org/pypi/selenium 172 .. _Selenium server (>2.11.0): http://seleniumhq.org/download/ 152 173 153 174 .. _contrib-apps: 154 175 -
docs/ref/settings.txt
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 20366e3..b7151a6 100644
a b default port. Not used with SQLite. 498 498 499 499 .. setting:: USER 500 500 501 SELENIUM_SERVER_ADDRESS 502 ~~~~~~~~~~~~~~~~~~~~~~~ 503 504 Default: ``localhost`` 505 506 Address where the Selenium server can be accessed. 507 508 SELENIUM_SERVER_PORT 509 ~~~~~~~~~~~~~~~~~~~~ 510 511 Default: ``4444`` 512 513 Port where the Selenium server can be accessed. 514 515 SELENIUM_BROWSER 516 ~~~~~~~~~~~~~~~~ 517 518 Default: ``'*firefox'`` 519 520 Browser to be used when running Selenium tests. Note that the prefixing star 521 ('``*``') is required. Possible values include: 522 523 * ``'*firefox'`` 524 * ``'*googlechrome'`` 525 * ``'*safari'`` 526 * ``'*mock'`` 527 * ``'*firefoxproxy'`` 528 * ``'*pifirefox'`` 529 * ``'*chrome'`` 530 * ``'*iexploreproxy'`` 531 * ``'*iexplore'`` 532 * ``'*safariproxy'`` 533 * ``'*konqueror'`` 534 * ``'*firefox2'`` 535 * ``'*firefox3'`` 536 * ``'*firefoxchrome'`` 537 * ``'*piiexplore'`` 538 * ``'*opera'`` 539 * ``'*iehta'`` 540 * ``'*custom'`` 541 501 542 USER 502 543 ~~~~ 503 544 -
docs/topics/testing.txt
diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index dc5bf7e..47638f3 100644
a b Some of the things you can do with the test client are: 580 580 * Test that a given request is rendered by a given Django template, with 581 581 a template context that contains certain values. 582 582 583 Note that the test client is not intended to be a replacement for Twill_,583 Note that the test client is not intended to be a replacement for Windmill_, 584 584 Selenium_, or other "in-browser" frameworks. Django's test client has 585 585 a different focus. In short: 586 586 587 587 * Use Django's test client to establish that the correct view is being 588 588 called and that the view is collecting the correct context data. 589 589 590 * Use in-browser frameworks such as Twill and Selenium to test *rendered* 591 HTML and the *behavior* of Web pages, namely JavaScript functionality. 590 * Use in-browser frameworks such as Windmill_ and Selenium_ to test *rendered* 591 HTML and the *behavior* of Web pages, namely JavaScript functionality. Django 592 also provides special support for those frameworks; see the sections on 593 :class:`~django.test.testcases.LiveServerTestCase` and 594 :class:`~django.test.testcases.SeleniumTestCase`. 592 595 593 596 A comprehensive test suite should use a combination of both test types. 594 597 595 .. _Twill: http://twill.idyll.org/596 .. _Selenium: http://seleniumhq.org/597 598 598 Overview and a quick example 599 599 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 600 600 … … set up, execute and tear down the test suite. 1828 1828 those options will be added to the list of command-line options that 1829 1829 the :djadmin:`test` command can use. 1830 1830 1831 Live test server 1832 ---------------- 1833 1834 .. currentmodule:: django.test.testcases 1835 1836 .. versionadded::1.4 1837 1838 .. class:: LiveServerTestCase() 1839 1840 **This is a stub** 1841 1842 ``LiveServerTestCase`` does basically the same as ``TestCase`` but also 1843 launches a live http server in a separate thread so that the tests may use 1844 another testing framework, such as Selenium_ or Windmill_ for example, instead 1845 of the built-in :ref:`dummy client <test-client>`. 1846 1847 Selenium 1848 -------- 1849 1850 .. versionadded::1.4 1851 1852 .. class:: SeleniumTestCase() 1853 1854 **This is a stub** 1855 1856 Django provides out-of-the box support for Selenium_, as Django itself uses it 1857 in its own test suite for the admin. 1858 1859 TODO: 1860 1861 * settings 1862 * requirements 1863 * code sample 1864 1865 .. _Windmill: http://www.getwindmill.com/ 1866 .. _Selenium: http://seleniumhq.org/ 1831 1867 1832 1868 Attributes 1833 1869 ~~~~~~~~~~ -
tests/regressiontests/admin_inlines/tests.py
diff --git a/tests/regressiontests/admin_inlines/tests.py b/tests/regressiontests/admin_inlines/tests.py index c2e3bbc..629e2c3 100644
a b from django.test import TestCase 7 7 8 8 # local test models 9 9 from .admin import InnerInline 10 from django.test.testcases import SeleniumTestCase 10 11 from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person, 11 12 OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book) 12 13 … … class TestInlinePermissions(TestCase): 380 381 self.assertContains(response, 'value="4" id="id_inner2_set-TOTAL_FORMS"') 381 382 self.assertContains(response, '<input type="hidden" name="inner2_set-0-id" value="%i"' % self.inner2_id) 382 383 self.assertContains(response, 'id="id_inner2_set-0-DELETE"') 384 385 386 class AdminInlinesSeleniumTests(SeleniumTestCase): 387 fixtures = ['admin-views-users.xml'] 388 urls = "regressiontests.admin_inlines.urls" 389 390 def admin_login(self, username, password): 391 """ 392 Helper function to log into the admin. 393 """ 394 self.selenium.open('/admin/') 395 self.selenium.type('username', username) 396 self.selenium.type('password', password) 397 self.selenium.click("//input[@value='Log in']") 398 self.selenium.wait_for_page_to_load(3000) 399 400 def test_add_inlines(self): 401 """ 402 Ensure that the "Add another XXX" link correctly adds items to the 403 inline form. 404 """ 405 self.admin_login(username='super', password='secret') 406 self.selenium.open('/admin/admin_inlines/titlecollection/add/') 407 408 # Check that there's only one inline to start with and that it has the 409 # correct ID. 410 self.failUnlessEqual(self.selenium.get_css_count( 411 'css=#title_set-group table tr.dynamic-title_set'), 1) 412 self.failUnless(self.selenium.get_attribute( 413 'css=.dynamic-title_set:nth-of-type(1)@id'), 'title_set-0') 414 self.failUnless(self.selenium.is_element_present( 415 'css=form#titlecollection_form tr.dynamic-title_set#title_set-0 input[name=title_set-0-title1]')) 416 self.failUnless(self.selenium.is_element_present( 417 'css=form#titlecollection_form tr.dynamic-title_set#title_set-0 input[name=title_set-0-title2]')) 418 419 # Add an inline 420 self.selenium.click("link=Add another Title") 421 422 # Check that the inline has been added, that it has the right id, and 423 # that it contains the right fields. 424 self.failUnlessEqual(self.selenium.get_css_count( 425 'css=#title_set-group table tr.dynamic-title_set'), 2) 426 self.failUnless(self.selenium.get_attribute( 427 'css=.dynamic-title_set:nth-of-type(2)@id'), 'title_set-1') 428 self.failUnless(self.selenium.is_element_present( 429 'css=form#titlecollection_form tr.dynamic-title_set#title_set-1 input[name=title_set-1-title1]')) 430 self.failUnless(self.selenium.is_element_present( 431 'css=form#titlecollection_form tr.dynamic-title_set#title_set-1 input[name=title_set-1-title2]')) 432 433 # Let's add another one to be sure 434 self.selenium.click("link=Add another Title") 435 self.failUnlessEqual(self.selenium.get_css_count( 436 'css=#title_set-group table tr.dynamic-title_set'), 3) 437 self.failUnless(self.selenium.get_attribute( 438 'css=.dynamic-title_set:nth-of-type(3)@id'), 'title_set-2') 439 self.failUnless(self.selenium.is_element_present( 440 'css=form#titlecollection_form tr.dynamic-title_set#title_set-2 input[name=title_set-2-title1]')) 441 self.failUnless(self.selenium.is_element_present( 442 'css=form#titlecollection_form tr.dynamic-title_set#title_set-2 input[name=title_set-2-title2]')) 443 444 def test_delete_inlines(self): 445 self.admin_login(username='super', password='secret') 446 self.selenium.open('/admin/admin_inlines/titlecollection/add/') 447 448 # Add a few inlines 449 self.selenium.click("link=Add another Title") 450 self.selenium.click("link=Add another Title") 451 self.selenium.click("link=Add another Title") 452 self.selenium.click("link=Add another Title") 453 self.failUnlessEqual(self.selenium.get_css_count( 454 'css=#title_set-group table tr.dynamic-title_set'), 5) 455 self.failUnless(self.selenium.is_element_present( 456 'css=form#titlecollection_form tr.dynamic-title_set#title_set-0')) 457 self.failUnless(self.selenium.is_element_present( 458 'css=form#titlecollection_form tr.dynamic-title_set#title_set-1')) 459 self.failUnless(self.selenium.is_element_present( 460 'css=form#titlecollection_form tr.dynamic-title_set#title_set-2')) 461 self.failUnless(self.selenium.is_element_present( 462 'css=form#titlecollection_form tr.dynamic-title_set#title_set-3')) 463 self.failUnless(self.selenium.is_element_present( 464 'css=form#titlecollection_form tr.dynamic-title_set#title_set-4')) 465 466 # Click on a few delete buttons 467 self.selenium.click( 468 'css=form#titlecollection_form tr.dynamic-title_set#title_set-1 td.delete a') 469 self.selenium.click( 470 'css=form#titlecollection_form tr.dynamic-title_set#title_set-2 td.delete a') 471 # Verify that they're gone and that the IDs have been re-sequenced 472 self.failUnlessEqual(self.selenium.get_css_count( 473 'css=#title_set-group table tr.dynamic-title_set'), 3) 474 self.failUnless(self.selenium.is_element_present( 475 'css=form#titlecollection_form tr.dynamic-title_set#title_set-0')) 476 self.failUnless(self.selenium.is_element_present( 477 'css=form#titlecollection_form tr.dynamic-title_set#title_set-1')) 478 self.failUnless(self.selenium.is_element_present( 479 'css=form#titlecollection_form tr.dynamic-title_set#title_set-2')) 480 No newline at end of file -
tests/regressiontests/admin_widgets/tests.py
diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py index 08a1a59..f163dc0 100644
a b from django.core.files.storage import default_storage 11 11 from django.core.files.uploadedfile import SimpleUploadedFile 12 12 from django.db.models import DateField 13 13 from django.test import TestCase as DjangoTestCase 14 from django.test.testcases import SeleniumTestCase 14 15 from django.utils import translation 15 16 from django.utils.html import conditional_escape 16 17 from django.utils.unittest import TestCase … … class RelatedFieldWidgetWrapperTests(DjangoTestCase): 372 373 # Used to fail with a name error. 373 374 w = widgets.RelatedFieldWidgetWrapper(w, rel, widget_admin_site) 374 375 self.assertFalse(w.can_add_related) 376 377 378 class AdminWidgetsSeleniumTests(SeleniumTestCase): 379 fixtures = ['admin-widgets-users.xml'] 380 urls = "regressiontests.admin_widgets.urls" 381 382 def admin_login(self, username, password): 383 """ 384 Helper function to log into the admin. 385 """ 386 self.selenium.open('/') 387 self.selenium.type('username', username) 388 self.selenium.type('password', password) 389 self.selenium.click("//input[@value='Log in']") 390 self.selenium.wait_for_page_to_load(3000) 391 392 def get_css_value(self, selector, attribute): 393 """ 394 Helper function that returns the value for the CSS attribute of an 395 DOM element specified by the given selector. 396 """ 397 return self.selenium.get_eval( 398 'selenium.browserbot.getCurrentWindow().django' 399 '.jQuery("%s").css("%s")' % (selector, attribute)) 400 401 def test_show_hide_date_time_picker_widgets(self): 402 """ 403 Ensure that pressing the ESC key closes the date and time picker 404 widgets. 405 Refs #17064. 406 """ 407 self.admin_login(username='super', password='secret') 408 # Open a page that has a date and time picker widgets 409 self.selenium.open('/admin_widgets/member/add/') 410 411 # First, with the date picker widget --------------------------------- 412 # Check that the date picker is hidden 413 self.assertEqual( 414 self.get_css_value('#calendarbox0', 'display'), 'none') 415 # Click the calendar icon 416 self.selenium.click('id=calendarlink0') 417 # Check that the date picker is visible 418 self.assertEqual( 419 self.get_css_value('#calendarbox0', 'display'), 'block') 420 # Press the ESC key 421 self.selenium.key_up('css=html', '27') 422 # Check that the date picker is hidden again 423 self.assertEqual( 424 self.get_css_value('#calendarbox0', 'display'), 'none') 425 426 # Then, with the time picker widget ---------------------------------- 427 # Check that the time picker is hidden 428 self.assertEqual( 429 self.get_css_value('#clockbox0', 'display'), 'none') 430 # Click the time icon 431 self.selenium.click('id=clocklink0') 432 # Check that the time picker is visible 433 self.assertEqual( 434 self.get_css_value('#clockbox0', 'display'), 'block') 435 # Press the ESC key 436 self.selenium.key_up('css=html', '27') 437 # Check that the time picker is hidden again 438 self.assertEqual( 439 self.get_css_value('#clockbox0', 'display'), 'none') 440 No newline at end of file