|| {X} ''HISTORICAL''. <
> We no longer use windmill at all. For Javascript XHR tests, see standard_yuixhr_test_template.js and standard_yuixhr_test_template.py in the root of the Launchpad tree. || ||This page provides information about writing JavaScript integration tests. || ||<>|| = Windmill for JS Integration Tests = For integration testing that covers JS workflows, our tool of choice is [[http://www.getwindmill.com/|Windmill]]. <> == Using Windmill to Test Launchpad == === Setting up Windmill === Windmill is included in the Launchpad source tree. Its dependencies are installed by the `launchpad-developer-dependencies` ubuntu package. === Running the Launchpad Windmill test suite === To run all Windmill tests: {{{ bin/test --layer=WindmillLayer }}} To run all Windmill tests for a single application (e.g. the Bugs application): {{{ bin/test --layer=BugsWindmillLayer }}} To run a single Windmill test (e.g. test_bug_commenting() in the Bugs application): {{{ bin/test --layer=WindmillLayer --test=test_bug_commenting }}} To run Windmill test in ''headless mode'' (without opening a Firefox window) {{{ xvfb-run -s '-screen 0 1024x768x24' ./bin/test -vv --layer=WindmillLayer -t test_bug_commenting }}} Here is a handy bash alias you can use to run tests from anywhere in the tree: {{{ alias wmt='xvfb-run -s "-screen 0 1024x768x24" `bzr root`/bin/test --layer=WindmillLayer -vvt ' example usage: wmt test_bug_commenting }}} The Windmill tests are integrated into our normal test suite, and the tests has a layered named `FooWindmillLayer`, where `Foo` is the name of the application (e.g. `BugsWindmillLayer`. The Windmill layers are excluded by default, so when running a Windmill tests you always have to specify the `--layer` option, otherwise the test runner won't find it. === How are the tests organized === Tests written in python are placed in `lib/lp//windmill/tests`. Tests using the JS API are in `lib/canonical/launchpad/windmill/jstests`. Both are described in the Windmill Wiki chapter on [[http://trac.getwindmill.com:/wiki/authoringtests|Authoring Tests]]. = Test Writing Tips = * Test the Happy Path, and one or two error paths * Edge cases are best pushed into the unit tests * Prefer element ids to XPath for locating page elements * XPath makes your tests brittle and dependent on page structure * Try to pull test actions out into their own functions, creating a Python Windmill testing [[http://en.wikipedia.org/wiki/Domain-specific_programming_language|DSL]] * Use the [[http://code.google.com/p/webdriver/wiki/PageObjects|PageObject pattern]] to encapsulate the actions available through specific pages * You can find some windmill python helpers in '''lib/canonical/launchpad/windmill/testing''' * To keep the Windmill GUI running to debug a test failure, pass `-D` to `bin/test` to make it stop on the first failure, or insert a `import pdb; pdb.set_trace()` at the place you want to poke around in the GUI. When using `open()`, make sure to use `waits.forPageLoad()` before any `waits.forElement()` (unless you are absolutely sure that you can't be on a page that already contains that element). When using the lazr-js slide-in/slide-out effects, use the following assertions to test whether the element is visible or not. {{{#! python # Test for visibility. client.asserts.assertProperty( id=u'search-results', validator='className|lazr-opened') # Test for invisibility. client.asserts.assertProperty( id=u'search-results', validator='className|lazr-closed') }}} When hiding or revealing elements using POJS, use the `unseen` class to control this behavior. It's easy to test for too. E.g. {{{#! javascript // Hide this element. element1.addClass('unseen'); // Reveal this element. element2.removeClass('unseen'); }}} {{{#! python # Test for visibility. client.asserts.assertNotProperty( id=u'search-results', validator='className|unseen') # Test for invisibility. client.asserts.assertProperty( id=u'search-results', validator='className|unseen') }}} For writing test containing conditions based on the page content you can use the ''execJS'' method. {{{#! python code = ( "lookupNode({id: '%s'}).text == 'Working in reviewer mode.'" % self.current_working_mode) current_is_reviewer = self.client.execJS(js=code)['output'] if current_is_reviewer: do_something() }}} == Known problems == === Logging in before opening a page might not really log you in === If you log in using ensure_login() before you open a page, you might not actually get logged in. '''Workaround''': Open the page you want to test before logging in. '''Bug''': [[https://launchpad.net/bugs/487657|#487657]] === Asserting that a node exists directly after waiting for it sometimes fails === If you wait for a node, and then directly assert that it's there, you sometimes get a failure when doing the assert, claiming the node isn't there. Example code: {{{ client.waits.forElement(xpath=ADD_COMMENT_BUTTON) client.asserts.assertNode (xpath=ADD_COMMENT_BUTTON) }}} '''Workaround''': Don't use assertNode() after forElement(). Waiting for the element already asserts that the node is there, and there's no need checking it again. '''Bug''': [[https://launchpad.net/bugs/487666|#487666]] = External resources = * [[http://wiki.github.com/windmill/windmill/|Windmill Wiki]] * [[http://www.slideshare.net/alexchaffee/fullstack-webapp-testing-with-selenium-and-rails|Full-stack webapp testing with Selenium and Rails]] - has a number of tips that apply to any automated testing framework = Troubleshooting = * If you are running on a slow computer or inside a VM, you may randomly trigger timeout errors. One way around these errors is to run each test individually: {{{ $ bin/test --layer=WindmillLayer --list-tests | sed -e '1d' > tests.list $ cat tests.list | xargs -i bin/test --layer=WindmillLayer --test='{}' >> test.log }}} * In the Windmill IDE window, you can test windmill's ability to find objects by going to the Tools menu (not the Firefox Tools menu) and clicking on "Firebug Lite". In Firebug Lite, you will be able to call the lookupNode() function with the same locator parameters that windmill uses. For example: * {{{ lookupNode( {classname: 'yui-picker'} ) }}} * {{{ lookupNode( {xpath: '//table[@id=foo]//button'} ) }}} * [[http://trac.getwindmill.com/wiki/ControllerApi|Locator Docs]] * Run `make schema` if you're having an issue that ends in something like: {{{ File "/usr/lib/python2.4/urllib.py", line 82, in urlopen return opener.open(url) File "/usr/lib/python2.4/urllib.py", line 190, in open return getattr(self, name)(url) File "/usr/lib/python2.4/urllib.py", line 316, in open_http errcode, errmsg, headers = h.getreply() File "/usr/lib/python2.4/httplib.py", line 1137, in getreply response = self._conn.getresponse() File "/usr/lib/python2.4/httplib.py", line 866, in getresponse response.begin() File "/usr/lib/python2.4/httplib.py", line 336, in begin version, status, reason = self._read_status() File "/usr/lib/python2.4/httplib.py", line 294, in _read_status line = self.fp.readline() File "/usr/lib/python2.4/socket.py", line 317, in readline data = recv(1) IOError: [Errno socket error] (104, 'Connection reset by peer') }}} ---- CategoryJavaScript CategoryTesting