Ember CLI page objects are extrememly powerful and my preferred way of testing my ember cli applications. I think page objects are an excellent abstraction for front-end testing, and I'll share my experiences with a simple example.

Step 1: Make a component

Let's say you're building the next disruptive, amazing application in ember cli. This application is going to need plenty of tests to make sure its working in every possible way.

Let's say this disruptive app has a simple component with a button. When clicking on this button a user is presented with some information. Let's say you're component looks a lot like this:


Question: What is the best football franchise ever?

{{#if displayInformation}}

The Patriots, you idiot!

{{/if}}

This is a very simple component that displays information when the displayInformation boolean is toggled on the components javascript side. (trust me, it works...)

Step 2: Write a test


import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';

moduleForComponent('button-with-information', 'Integration | Component | buttonwith information', {
  integration: true
});

test('clicking the button displays an img', function(assert) {
  this.render(hbs`{{button-with-information}}`);
  this.$('button.answer').click();
  assert.equal(this.$('img').length, 1);
});

these look OK. they're certainly acceptable. But there is something, just, lacking here. The syntax for testing that an image is displayed on the page is awkward -- like my years in high school. Asserting that an element's length is equal to 1 to prove that it's displayed? Yuck! I want something a bit more readable than this. Enter ember-cli-page-object.

Step 3: Realize Page Objects are Awesome

Let's create a new page object for integration test with ember g page-object components/button-with-information. Lets edit our newly created page-object to look something like this


// tests/pages/components/button-with-information.js
import {
  create,
  clickable,
  isVisible
} from 'ember-cli-page-object';

export default create({
  getAnswer: clickable('button.answer'),
  imageIsDisplayed: isVisible('img.tommy-the-goat')
});

The ember-cli-page-object documentation is awesome and explains all these options very well. In this file, we are mapping some attributes to built-in page-object functions. Essentially we're building a DSL to test our component.

Now your test can look like this:


import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import page from 'page-object-example/tests/pages/components/button-with-information';

moduleForComponent('button-with-information', 'Integration | Component | button with information', {
  integration: true,
  beforeEach() {
    page.setContext(this);
  },
  afterEach() {
    page.removeContext();
  }
});

test('clicking the button displays an img', function(assert) {
  this.render(hbs`{{button-with-information}}`);
  page.getAnswer();
  assert.ok(page.imageIsDisplayed);
});

Look at how readable this code is! You are literally testing if an imageIsDisplayed after clicking a button. It's awesome! When using a page object in an integration test with a component, its imperative to set the context of your pageObject. We set the context in our beforeEach and remove it in our afterEach callbacks above.

Now the real power lies in the portability of these page objects. Let's say you had an accpetance test. And this acceptance test is supposed to render your component that you created this page object for. Wouldn't it be nice to make sure the component renderend correctly within this page and the button can still be pressed? I'll bet you'd think that'd be a nice thing, becuase you seem like a nice person.


import { test } from 'qunit';
import moduleForAcceptance from 'page-object-example/tests/helpers/modulefor-acceptance';
import tomBradyButton from 'page-objectexample/tests/pages/components/button-with-information';

moduleForAcceptance('Acceptance | application');

test('you can see the tom brady button.', function(assert) {
  visit('/');

  andThen(function() {
    assert.ok(tomBradyButton.isVisible);
  });

  tomBradyButton.getAnswer();

  andThen(function() {
    assert.ok(tomBradyButton.imageIsDisplayed);
  });
});

This is actually a very powerful concept. With our new page object, we are able to test our component at a lower level -- with our integration test. Then acceptance test the component at an even higher level -- by testing the page object in our acceptance test.

There are a million ways you can use ember cli page objects, and this just happens to be my favorite. It allows me to test my components at a lower level, then re-affirm those tests at the behavioral level (with acceptance tests). I hope you enjoyed this quick trip down the rabbit hole of page objects.