Diff for "Windmill"

Not logged in - Log In / Register

Differences between revisions 10 and 46 (spanning 36 versions)
Revision 10 as of 2009-05-05 16:23:02
Size: 3087
Editor: mars
Comment:
Revision 46 as of 2011-09-23 13:49:05
Size: 8140
Editor: gary
Comment:
Deletions are marked like this. Additions are marked like this.
Line 1: Line 1:
||<tablestyle="width: 100%;" style="border: solid red 6px; font-weight: bold;"> {X} ''HISTORICAL''. <<BR>> 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. ||

||<tablestyle="width: 100%;" colspan=3 style="background: #2a2929; font-weight: bold; color: #f6bc05;">This page provides information about writing JavaScript integration tests. ||

||<tablestyle="float:right; font-size: 0.9em; width:30%; background:#F1F1ED; margin: 0 0 1em 1em;" style="padding:0.5em;"><<TableOfContents>>||
Line 12: Line 18:
Windmill is included in the Launchpad source tree. Windmill is included in the Launchpad source tree.  Its dependencies are installed by the `launchpad-developer-dependencies` ubuntu package.
Line 17: Line 23:
There is a script called `lp-windmill.py` in the top-level directory. This is
a wrapper around the windmill main script which starts a Launchpad server on
port 8085 locally with a fresh database (including all of the standard tests
sample data).
To run all Windmill tests:
Line 22: Line 25:
The windmill process is then fired off with the command line argument.     {{{
    bin/test --layer=WindmillLayer
    }}}
Line 24: Line 29:
So if you want to run all the tests you'd typically use: To run all Windmill tests for a single application (e.g. the Bugs application):
Line 26: Line 31:
    {{{
    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:
Line 27: Line 49:
$ ./lp-windmill.py test=lib/canonical/launchpad/windmill/tests firefox http://launchpad.dev:8085    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/<application>/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')
Line 30: Line 97:
   '''Note that it is not currently possible to run all the tests like that because of a bug/limitation in Windmill.'''
   You need to run each tests starting at the correct vhost. So for bugs tests, you'd use:
When hiding or revealing elements using POJS, use the `unseen` class to
control this behavior. It's easy to test for too. E.g.
Line 33: Line 100:
   {{{
$ ./lp-windmill.py test=lib/canonical/launchpad/windmill/tests/test_bugs firefox http://bugs.launchpad.dev:8085
{{{#! javascript
    // Hide this element.
    element1.addClass('unseen');
    // Reveal this element.
    element2.removeClass('unseen');
Line 37: Line 107:
   and for registry, you'd use: {{{#! python
    # Test for visibility.
    client.asserts.assertNotProperty(
        id=u'search-results',
        validator='className|unseen')
Line 39: Line 113:
   {{{
$ ./lp-windmill.py test=lib/canonical/launchpad/windmill/tests/test_registry firefox http://launchpad.dev:8085
    # Test for invisibility.
    client.asserts.assertProperty(
        id=u'search-results',
        validator='className|unseen')
Line 43: Line 119:
For interactive test running and development, it's usually more convenient to
run the Launchpad server separately:
For writing test containing conditions based on the page content you can use the ''execJS'' method.
Line 46: Line 121:
    {{{
$ ./lp-windmill.py --server-only
}}}

The Windmill driver script, `windmill.py`, is located under the `utilities/` directory. Using it, you can start an interactive shell to run tests without setting up and tearing down Launchpad repeatedly:

    {{{
$ ./utilities/windmill.py shell firefox http://launchpad.dev:8085
{{{#! 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()
Line 57: Line 131:
See the
[[http://trac.getwindmill.com/wiki/BookChapter-2-RunningWindmill#TheShellEnvironment|help page]] on the shell environment.
== Known problems ==
Line 60: Line 133:
==== How are the tests organized ==== === Logging in before opening a page might not really log you in ===
Line 62: Line 135:
Tests written in
[[http://trac.getwindmill.com/wiki/BookChapter-4-4-PythonTests|python ]] are
rooted at `lib/canonical/launchpad/windmill/tests`.
If you log in using ensure_login() before you open a page, you might not actually get logged in.
Line 66: Line 137:
Tests are divided by applications and then subdivided by workflows. '''Workaround''': Open the page you want to test before logging in.
Line 68: Line 139:
Tests using the
[[http://trac.getwindmill.com/wiki/BookChapter-4-3-JavascriptTests|JS API]]
are in `lib/canonical/launchpad/windmill/jstests`.
'''Bug''': [[https://launchpad.net/bugs/487657|#487657]]
Line 73: Line 142:
== Writing Tests == === Asserting that a node exists directly after waiting for it sometimes fails ===
Line 75: Line 144:
 * Test the Happy Path, and one or two error paths
 * Edge cases are best pushed into the unit tests
 * Setup and teardown are expensive, so there will be a trend towards testing more per test
 * Prefer element ids to XPath for locating page elements
   * XPath makes your tests brittle and dependent on page structure
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]]
Line 82: Line 156:
== External resources == = External resources =
Line 84: Line 158:
 * [[http://trac.getwindmill.com/wiki/WindmillBook|Windmill Book]]  * [[http://wiki.github.com/windmill/windmill/|Windmill Wiki]]
Line 87: Line 161:
= Troubleshooting =
Line 88: Line 163:
 * 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')

}}}

{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 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/<application>/windmill/tests.

Tests using the JS API are in lib/canonical/launchpad/windmill/jstests.

Both are described in the Windmill Wiki chapter on 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 DSL

  • Use the 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.

    # 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.

    // Hide this element.
    element1.addClass('unseen');
    // Reveal this element.
    element2.removeClass('unseen');

    # 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.

        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: #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: #487666

External resources

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'} ) 

    • 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

Windmill (last edited 2011-09-23 13:49:05 by gary)