Dealing with Timing Issues in Ionic E2E Tests

Dealing with Timing Issues in Ionic E2E Tests

Follow Josh Morony on

When creating End-to-End (E2E) tests, we are basically programming the browser to behave like a human. We tell the browser what pages to go to, what input to send, what buttons to click, and so on.

In a typical E2E test, we will send multiple commands to the browser. We might tell it to click on one button, and then add some input somewhere, click on another button, and any number of other actions. As a human, we know when we can click a button – we would wait until the page has changed or until a loading overlay disappears – but when we are programming the browser to perform actions for us, sometimes it will perform the actions too quickly.

In this video tutorial, we walk through how to use Protractor’s ExpectedConditions to tell the browser to wait until something specific has happened before performing the next step in the test.

UPDATE: I would still recommend watching the video below for context on how ExpectedConditions work, but the example used is a little outdated and won’t work as is. Keep reading this article after the video for a more current example of using ExpectedConditions in an Ionic/Angular application.

Here’s the video:

Now, let’s take a look at another example. We are going to use Ionic’s conference starter template which is basically a “kitchen sink” style example application. To create this application yourself, you can just create a new Ionic application:

ionic start my-e2e-test --type=angular

and choose the conference starter application when prompted. Included in this application is some Google Maps functionality. If you go to the “Map” page, it will load in and display a Google Map. Loading in the Google Maps JavaScript SDK and displaying a map is not an instantaneous operation, it is going to take some time (probably a few seconds), so this is a good candiate for waiting on ExpectedConditions in our E2E tests.

Without ExpectedConditions we might try to do something like this:

import { AppPage } from "./app.po";
import { browser, protractor } from "protractor";

describe("new App", () => {
  let page: AppPage;

  beforeEach(() => {
    page = new AppPage();
  });

  it("google maps should be displayed within 3 seconds", () => {
    page.navigateTo();
    expect(page.getMap().isPresent()).toBeTruthy();
  });
});

For reference, here is the page object file that the above code is making use of:

import { browser, by, element } from "protractor";

export class AppPage {
  navigateTo() {
    return browser.get("/");
  }

  getMap() {
    return element(by.css(".show-map"));
  }
}

The basic idea is that when the map is visible its container will have a CSS class of show-map added. In our test, we navigate to the maps page (I have modified the starter application so that it navigates directly to the map page to simplify this example) and then we check that the element with a CSS class of show-map is present in the DOM.

However, if we try to execute this test we will get the following failure:

1) new App google maps should be displayed within 3 seconds
  - Expected false to be truthy.

Currently in the test, we are navigating to the page and immediately checking to see if the map is loaded. However, it hasn’t had enough time to load at this point. Instead of waiting an arbitrary amount of time and hoping that the maps have finished loading, this is a situation where we can make use of ExpectedConditions. Instead, we could do something like this:

import { AppPage } from "./app.po";
import { browser, protractor } from "protractor";

describe("new App", () => {
  let page: AppPage;

  beforeEach(() => {
    page = new AppPage();
  });

  it("google maps should be displayed within 3 seconds", () => {
    browser.waitForAngularEnabled(false);

    page.navigateTo();
    browser.wait(
      protractor.ExpectedConditions.presenceOf(page.getMap()),
      3000,
      "Google maps did not display"
    );

    expect(page.getMap().isPresent()).toBeTruthy();
  });
});

Now we are waiting for this condition to be fulfilled:

protractor.ExpectedConditions.presenceOf(page.getMap()),

Once protractor detects that the element with a CSS class of show-map is in the DOM, it will automatically proceed with the test. If this does not occur within 3000ms (3 seconds) then the test will fail with the error message we provided. Let’s check that out by purposesly breaking the map functionality and then running the E2E tests:

1) new App google maps should be displayed within 3 seconds
  - Failed: Google maps did not display
  Wait timed out after 3003ms

Since we specified a timeout of 3000 it waits for this amount of time but then automatically fails. Since I broke the functionality by removing the code that adds the show-map class, the ExpectedCondition will never be fulfilled and so it times out after the 3 seconds. If we fix that functionality and try running the test again you will see that it succeeds:

  new App
    ✓ google maps should be displayed within 3 seconds

Executed 1 of 1 spec SUCCESS in 2 secs.

Summary

Using ExpectedConditions to wait for certain events to occur in your application is much more reliable than just manually waiting for a set amount of time. It will ensure that the application is ready to be tested when you run your expect statements, and it will also make sure that your tests are running as fast as possible. You will save time writing your tests and running them.

If you’d like a much more in-depth introduction to Unit and E2E testing for Ionic/Angular applications, you might be interested in checking out my Elite Ionic (Angular) course.

Check out my latest videos: