Ticket #2879: 2879.selenium-support.diff
File 2879.selenium-support.diff, 21.5 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..a86a16e 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 socket 10 import threading 8 11 9 12 from django.conf import settings 10 13 from django.core import mail 11 14 from django.core.exceptions import ValidationError 15 from django.core.handlers.wsgi import WSGIHandler 12 16 from django.core.management import call_command 13 17 from django.core.signals import request_started 18 from django.core.servers.basehttp import (WSGIRequestHandler, WSGIServer, 19 AdminMediaHandler) 14 20 from django.core.urlresolvers import clear_url_caches 15 21 from django.core.validators import EMPTY_VALUES 16 22 from django.db import (transaction, connection, connections, DEFAULT_DB_ALIAS, … … from django.test.utils import (get_warnings_state, restore_warnings_state, 23 29 override_settings) 24 30 from django.utils import simplejson, unittest as ut2 25 31 from django.utils.encoding import smart_str 32 from django.utils.unittest.case import skipUnless 26 33 27 34 __all__ = ('DocTestRunner', 'OutputChecker', 'TestCase', 'TransactionTestCase', 28 35 'SimpleTestCase', 'skipIfDBFeature', 'skipUnlessDBFeature') … … def skipUnlessDBFeature(feature): 732 739 """ 733 740 return _deferredSkip(lambda: not getattr(connection.features, feature), 734 741 "Database doesn't support feature %s" % feature) 742 743 744 745 746 747 748 class QuietWSGIRequestHandler(WSGIRequestHandler): 749 """ 750 Just a regular WSGIRequestHandler except it doesn't log to the standard 751 output any of the requests received, so as to not clutter the output for 752 the tests' results. 753 """ 754 def log_message(*args): 755 pass 756 757 758 class LiveServerThread(threading.Thread): 759 """ 760 Thread for running a live http server while the tests are running. 761 """ 762 763 def __init__(self, address, port, fixtures): 764 self.address = address 765 self.port = port 766 self.fixtures = fixtures 767 self.please_stop = threading.Event() 768 self.is_ready = threading.Event() 769 super(LiveServerThread, self).__init__() 770 771 def run(self): 772 """ 773 Sets up live server and database and loops over handling http requests. 774 """ 775 # Instantiate the server and prepare it so also serve the admin media 776 httpd = WSGIServer((self.address, self.port), QuietWSGIRequestHandler) 777 handler = AdminMediaHandler(WSGIHandler()) 778 httpd.set_app(handler) 779 780 # If the database is in memory we must reload the data in this new 781 # thread. 782 if (settings.DATABASES['default']['ENGINE'] == 'django.db.backends.sqlite3' or 783 settings.DATABASES['default']['TEST_NAME']): 784 connection.creation.create_test_db(0) 785 # Import the fixtures into the test database 786 if hasattr(self, 'fixtures'): 787 call_command('loaddata', *self.fixtures, **{'verbosity': 0}) 788 789 # Serve requests 790 self.is_ready.set() 791 while not self.please_stop.isSet(): 792 httpd.handle_request() 793 httpd.server_close() 794 795 class LiveServerTestCase(TestCase): 796 """ 797 Does basically the same as TestCase but also launches a live http server in 798 a separate thread so that the tests may use another testing framework, such 799 as Selenium for example, instead of the built-in dummy client. 800 """ 801 802 django_server_address = '127.0.0.1' #TODO: Should those be settings instead 803 django_server_port = 8000 # of class attributes? 804 fixtures = [] 805 806 def setUp(self): 807 # Launch the Django live server's thread 808 self.server_thread = LiveServerThread(self.django_server_address, 809 self.django_server_port, fixtures=self.fixtures) 810 self.server_thread.start() 811 super(LiveServerTestCase, self).setUp() 812 813 def tearDown(self): 814 # Kindly ask the Django live server to stop 815 self.server_thread.please_stop.set() 816 # Send one last dummy request to unlock the live server thread 817 conn = httplib.HTTPConnection(self.django_server_address, 818 self.django_server_port) 819 conn.request("GET", '/', '', {}) 820 self.server_thread.join() 821 super(LiveServerTestCase, self).tearDown() 822 823 824 825 826 try: 827 # Check if the 'selenium' package is installed 828 from selenium import selenium 829 selenium_installed = True 830 except ImportError: 831 selenium_installed = False 832 833 # Check if the Selenium server is running 834 try: 835 conn = httplib.HTTPConnection(settings.SELENIUM_SERVER_ADDRESS, 836 settings.SELENIUM_SERVER_PORT) 837 try: 838 conn.request("GET", "/selenium-server/driver/", '', {}) 839 finally: 840 conn.close() 841 selenium_server_running = True 842 except socket.error: 843 selenium_server_running = False 844 845 class SeleniumTestCase(LiveServerTestCase): 846 """ 847 Does basically the same as TestServerTestCase but also connects to the 848 Selenium server. The selenium client is then available with 849 'self.selenium'. The requirements are to have the 'selenium' installed in 850 the python path and to have the Selenium server running. If those 851 requirements are not filled then the tests will be skipped. 852 """ 853 854 def setUp(self): 855 super(SeleniumTestCase, self).setUp() 856 # Launch the Selenium server 857 selenium_browser_url = 'http://%s:%s' % ( 858 self.django_server_address, self.django_server_port) 859 self.selenium = selenium( 860 settings.SELENIUM_SERVER_ADDRESS, settings.SELENIUM_SERVER_PORT, 861 settings.SELENIUM_BROWSER, selenium_browser_url) 862 self.selenium.start() 863 # Wait for the Django server to be ready 864 self.server_thread.is_ready.wait(timeout=5) 865 866 def tearDown(self): 867 self.selenium.stop() 868 super(SeleniumTestCase, self).tearDown() 869 870 SeleniumTestCase = skipUnless(selenium_installed, 871 'The \'selenium\' package isn\'t installed')(SeleniumTestCase) 872 SeleniumTestCase = skipUnless(selenium_server_running, 873 'Can\'t connect to the Selenium server using address %s and port %s' % ( 874 settings.SELENIUM_SERVER_ADDRESS, settings.SELENIUM_SERVER_PORT) 875 )(SeleniumTestCase) 876 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