I'm using CasperJS and CoffeeScript for integration testing of a hobby project. Page Objects seem to be a useful abstraction. I had two major requirements:
- Support for node.js style callbacks - mainly because tests can then be written using async.waterfall
- Support extending (subclassing) to encapsulate common functionality, like a sidebar or a navbar
Here's the code, followed by a brief example of how I use it. It's a bit long, but that's about the shortest meaningful example I can give.
class PO # Base PO
@casper: null
# name: The name of the page object. Used to access it as PO[name], which is kind of like a forward declaration
# selector: The page represented by this PO is considered to be ready for usage once this is visible
# mixin: An object; properties of this will be mixed into the new instance
constructor: (@name, @selector, mixin) ->
if PO[@name] isnt undefined
throw "Failed creating PO #{@name}: a PO instance or class method with this name already exists"
PO[@name] = this
@__defineGetter__ 'casper', -> PO.casper
this[key] = val for key, val of mixin
waitUntilVisible: (cb) -> @casper.waitUntilVisible @selector, cb
# Switch to po, and call the callback cb with err and the po as parameters
next: (cb, po, err=null) ->
t0 = Date.now()
msg = "Switching to PO #{po.name}"
@casper.log msg, 'debug'
po.waitUntilVisible =>
@casper.log "#{msg}: done in #{Date.now() - t0}ms", 'debug'
cb err, po
po
new PO 'navbar', '',
tabs: {}
logout: (cb) ->
@casper.click '#logout'
@casper.waitFor (=> not @haveLogin()), (=> cb null, PO['login'])
haveLogin: (cb) ->
result = nick == @casper.evaluate -> $('#current-user-nick').text()
cb? result
result
addTab: ({name, switchSelector, readySelector, po}) ->
@tabs[name] = arguments[0]
toTab: (name, cb) ->
cb "Navbar tab #{name} not known", null unless name of @tabs
tab = @tabs[name]
@casper.click tab.switchSelector
@casper.waitUntilVisible tab.readySelector, => cb null, PO[tab.po]
class PON extends PO # PO with navbar
constructor: (name, selector, mixin) ->
super name, selector, mixin
@navbar = PO['navbar']
new PON 'login', '.login-form',
# ...
module.exports = (casper) ->
PO.casper = casper
PO['login']