Setup Protractor E2E Tests with Aurelia CLI Apps

Recently I've taken a fresh look at the aurelia-protractor-plugin, which is based on the original plugin I wrote for the Aurelia Skeleton and described in one of the first official Aurelia Blog posts. A lot of things are still valid, but many things have changed since then. Lately, great contributions from Core Team member Bazyli Brzóska have first moved out the plugin into a custom repo and further more made it a good fit for WebPack based Aurelia apps. Besides that with the broader acceptance of async/await due to native support in Node made writing Protractor based tests even easier. What hasn't been addressed yet though is how to setup Protractor E2E tests with a freshly scaffolded Aurelia CLI app.

How to Get Started

For the purpose of this article lets start with a new Aurelia CLI app, which will be TypeScript based.
As usual, you'll run au new [yourprojectname] and select TypeScript as your language of choice.
I've picked TypeScript on purpose for this example since it requires much more dependencies and setup ceremony. Making it work with VanillaJS should be all in all much easier.

Installing Protractor Dependencies

Unlike the skeletons, a CLI generated app is pretty much barebones and requires you to install additional features by yourself. This is good because you're maybe not into testing at all because all your code always works right?

If you're like me though and don't trust yourself, setting up Protractor is an npm install --save-dev protractor away.

Starting from Node >= 8 and NPM >= 5 you can skip --save as this will be the default but still need to add --save-dev in order to install it as a devDependency, which Protractor in our case is.

Additionally install the aurelia-protractor-plugin with npm install --save-dev aurelia-protractor-plugin.

Once that is done, create a folder e2e located under the test folder. This will contain all our test specifications. Since we're on a TypeScript based starter, we'd also like to write our test cases using TS. In order to have Protractor understand these on the fly, we can leverage the package ts-node by installing it via npm install --save-dev ts-node.

Setup Protractor Configurations

Next create a file protractor.conf.js in the root of your app with the following configuration:

exports.config = {  
  directConnect: true,

  // Capabilities to be passed to the webdriver instance.
  capabilities: {
    'browserName': 'chrome'
  },

  specs: ['test/e2e/**/*.ts'],

  plugins: [{
    package: 'aurelia-protractor-plugin'
  }],

  onPrepare: function() {
    require('ts-node')
      .register({
        compilerOptions: { module: 'commonjs' },
        disableWarnings: true,
        fast: true
      });
  },

  // Options to be passed to Jasmine-node.
  jasmineNodeOpts: {
    showColors: true,
    defaultTimeoutInterval: 30000
  }
};

A few things to note here. First, we'll setup chrome as the default browser defined in the capabilities section and directly connect to the ChromeDriver without needing a dedicated SeleniumServer. In order to obtain the driver, we can run node_modules/.bin/webdriver-manager update which is a sub-dependency of Protractor. Next, we told Protractor that test cases can be found in the e2e folder using the specs property. The property plugins takes an array of objects where we pass in the aurelia-protractor-plugin. And last but not least, since we're on TypeScript we need to tell Protractor that during preparation (onPrepare) it should transpile the given sources, targeting the commonjs module format.

Creating your first Test

With that done let's create our first test by creating the file test/e2e/demo.spec.ts. The name pattern is free at your disposal and can be defined in the specs section of the Protractor configuration, as seen right before.

The content of the script should look like this:

import {browser, element, by, ExpectedConditions} from 'aurelia-protractor-plugin/protractor';

describe('demo', () => {  
  beforeEach(async () => {
    await browser.loadAndWaitForAureliaPage("http://localhost:9000");
    /* await browser.wait(
      ExpectedConditions.presenceOf(element(by.css("h1"))), 2000
    ); */
  });

  it('should load the page and greet the user', async () => {
    await expect(element(by.tagName("H1")).getText()).toBe("Hello World!");
  });
});

First, we're importing all the necessary parts from the aurelia-protractor-plugin provided type definition. Next, we define a scenario demo using the describe function. Now before each test runs, we want to make sure the page is loaded, Aurelia rendered and the page is ready-for-interaction. Since this would be quite repetitive, we can put that in our beforeEach section.
Note the use of the async keyword to indicate that the following anonymous function is going to await the plugin loadAndWaitForAureliaPage method.

If, for whatever reason, you do not want to use the aurelia-protractor-plugin an alternative method to start the test once the page is loaded, would be to wait for the presence of the default h1 element scaffolded in our fresh app, with a fallback timeout of 2 seconds. You do that using the ExpectedConditions API. The relevant code can be found under comments in the beforeEach section of the previous example.

Now the test itself merely checks that the H1's text equals Hello world!. The cool thing about protractor is that we do not need to use a jasmine done callback to indicate that the test has finished but can continue using async/await which clearly simplifies the code written.

Updating Jasmine Type Definitions

Now the last thing that stands out are those pesky squiggle lines as seen in the next screenshot inside the IDE of your choice.

Process of how components interact with the store Visual Studio Code missing type definition error indicator

The TypeScript compiler argues that you can't provide a string to the function toBe since it would expect an Expected<Promise<string>> type. The reason is that with a recent update of Jasmine the type-safety for expectations was enhanced and does not support the patched methods introduced by Protractor. In order to get rid of those simply run npm install npm install --save-dev @types/jasminewd2 and after revisiting your file the error should be gone.

Conclusion

I hope you've seen that it doesn't take a lot to get your E2E tests up and running with an Aurelia CLI app. Aurelias Protractor Plugin provides the old-known framework-specific helpers and taming the asynchronous nature with async/await is a real dream compared to Promises.

As usual, if there are any questions and or tips just leave a comment below.

photo credit: geralt: Test blackboard via Pixabay (license)