Requiring All Page Objects for Angular Protractor Specs

You’ve got an Angular app and an accompanying test suite. You’ve followed the recommendations about using Page Objects, but it’s cumbersome to require each of them you’d like to use in each spec. Wouldn’t it be nice to automate that?

Page What Now?

If you happen to be writing “E2E” tests for your Angular app with Protractor but you aren’t using Page Objects yet, here’s a quick intro:

Once you’re convinced of their benefit and you’ve seen some nice examples of using them, you go and build a few. And then your app grows… and grows. Now you have a mess of page objects that have to be manually required in each spec that uses them.

Step 1: Corral Your Pages

My first solution to this problem was a few simple prerequisites:

  • Export each Page class from its own separate file.
  • Collect those files into a common subdirectory.

For a small-to-medium-sized app, a single “pages” directory under test/e2e is probably fine. As your app grows past 10 pages, consider putting a separate pages directory into each feature-specific directory under test/e2e, e.g.:

    test/
        e2e/
            onboarding/
                pages/
            edit_document/
                pages/
            manage_users/
                pages/

Step 2: Require Them in Bulk

(Examples are in CoffeeScript because that’s how I roll.)

Instead of requiring each page object file everywhere you need them:

    ManageUsersPage = require 'pages/manage_users'
    AwesomePage = require 'pages/awesome'
    #...
 
    describe 'Awesome Amaze Feature', ->
      it 'still requires mucking with users', ->
        ManageUsersPage.createUser 'Bob'
        AwesomePage.makeWonderfulThings()
        #...

Prefer to bulk import the pages once, relying on them being in a separate subdirectory all to their own:

    Pages = require '../pages'
 
    describe 'Awesome Amaze Feature', ->
      it 'still requires mucking with users', ->
        Pages.ManageUsers.createUser 'Bob'
        Pages.AwesomePage.makeWonderfulThings()
        #...

This can be achieved by adding an index.coffee (or index.js of course) in your pages directory, with something like this inside:

>
    for page in require("fs").readdirSync(__dirname) when not page.match /^index/
      mod = require "./#{page}"
      exports[mod.name] = mod

(More details on this approach are given in a StackOverflow thread about this.)

This works great! Much better than scattering those requires everywhere.

Step 3: Always Be Automating

After a while I felt I’d outgrown my lone pages directory, so I thought about adding one for each feature subdirectory within my E2E suite. Then it started to get to me that I’d need an index file in each pages directory…

How could this approach be improved? Well, that same StackOverflow thread had some hints: there are a number of “require all the things” packages for Node, so why not put one to use?

  • require-all
  • requireall
  • include-all
  • require-directory
  • require-dir
  • recursive-require
  • recquire

It’s no surprise that npm has a package overlap problem :)

This first one I tried seemed to do exactly what I wanted: node-require-all. I after a quick check though, I noticed a problem. This package (and its fork, include-all) assumes you’d like the exported module name to match the filenames you’re requiring. That could work fine for you, but I prefer to have more control and use the given module name, similarly to my index approach above.

So I just made one I liked! I forked include-all, added an option to require with exported function names, and installed it:

npm install --save-dev karlin/include-all

Now I can put a single helper somewhere with this:

    requirePages = (dir) ->
      require('include-all')(
        dirname: "#{dir}/pages"
        useModuleName: true
      )
 
    module.exports = requirePages

and then use that in each spec like this:

    Pages = require('../helpers/require_pages')(__dirname)
 
    describe 'Awesome Amaze Feature', ->
      beforeEach ->
        @manageUsers = new Pages.ManageUsers()
        #...

That’s it! Now you can focus more on writing features and specs and less on wrangling page classes on the filesystem!