Darn it. It's
http://github.com/pinhook/funcunit (not pinkook).
By tomorrow, we should have a release that you can play with. We had one yesterday, but I decided that it would be best to not have a custom qunit.
Here's how it's shaping up to work / look.
We'd like to provide 3 things:
- A qunit.envjs.js script that enables qunit to report and run in envjs.
- A envjs shell script that loads rhino, selenium, envjs, a settings and opens a page.
- A funcunit.js script that enables qunit to drive selenium.
The first 2 are nothing new and have been done by other people. This would let people take an existing qunit test and do something like:
./envjs mytest.html
The settings file is so that EnvJS can make Ajax requests as if it was not running from the filesystem.
But the funcunit.js part is very special. It allows your tests to open another page (via the browser or via selenium) and perform the clicks and keypresses a user would do. It's got a sweet syntax. Here's what an auto-suggest looks like:
- S('#auto_suggest').click().type("qUnit")
- S('.result').exists().size(function(size){
- equal(size, 5,"there are 5 results")
- })
This clicks and types in an auto suggest button, waits for a result to exists, and then checks the number of results.
You might be asking yourself, why the callback for size? The reason is that functional tests do lots of asynchronous behavior and very little checking. Our previous version was practically unusable b/c we were always having to callback multiple functions. This new syntax allows you to call $.wait and still keep your test code very linear (no nested functions).
This was for an ftp app. I'd often have to create a file, do something to it, and test the result. This is why you'll see the createFile function. It's a helper. Notice all the timeoutCallbacks that move to another function and have to pass the original callback. Gross! This could be done with something like:
- Helpers = {
//takes a filename and returns a selector - convert : function(fileName){ .... },
- createFile : function(fileName){
- S(this.convert(fileName.replace(/\/[^\/]+$/,"") )).leftClick()
- S("#contextmenu .new_file").visible().click()
- S("#input.newEntry").exists().type(fileName+"\n")
- S(this.convert(fileName)).exists()
- },
- ....
And in my qunit test I could make a few folders as easily as:
- test("create a file and move it to another folder", function(){
- Helpers.createFile("foo/bar.txt")
- var selector = Helpers.convert("foo/bar.txt")
- S( selector ).dragTo( Helpers.convert("adifferentFolder") )
- S(Helpers.convert("adifferentFolder/bar.txt")).exists(function(){
ok(true,"Drag a file to another works")
}) - Helpers.removeFile("adifferentFolder/bar.txt")
- })
Testing solved?
I think I solved a bunch of problems by combining all approaches to testing and adding an 'asynchronous' aware syntax.
qUnit - Can't be automated. Can't do functional tests.
EnvJS - Not the 'real' browser.
Selenium - Very difficult to write and debug tests. With FuncUnit, all these tests run in the browser. You can debug them w/ firebug. Also, the API is nicer in my opinion.
How it works
funcunit.js has 2 modes - rhino and browser. In the browser, funcunit basically wraps:
Synthetic uses mostly feature detection to enable clicks and kepresses on dom objects (including their side effects).
If the page is running in envjs/rhino, it issues these commands to selenium. We have a custom version of selenium that uses synthetic (which fixes numerous bugs).
What we need
If you like this idea a lot ... I'd be happy to move it into the qUnit project when it's ready.