Ionic e2e tests – scroll to your goal

Another slightly niche tip on end-to-end testing with the latest Ionic – 4.x stable as of this post. Maybe it’s obvious to more seasoned JS testers, but I struggled to find all the info in one place to successfully implement a test that needs to scroll content in an Ionic app. (I would assume a similar approach will also work when using Protractor with any other modern Angular app.)

The specific scenario? You’ve got a ‘thing’ your test needs to click, but it’s a scroll / drag away. It might technically be in the viewport, but below something else, like Ionic’s tab bar. Seems like a relatively common occurrence, and one that should be simple to deal with in your test.

I was surprised to find this so fiddly to do, and for a while I was simply making the window so big that it didn’t need scrolling as a workaround. Not great, since huge windows are exactly the opposite of what you need to accurately simulate the experience of those on mobile phones.

Here’s what I did in the end to make those tests better for Webful PasswordMaker:

  1. Used Protractor’s browser.executeScript() to scroll the viewport to the button I needed to click, as suggested here. This requires the browser running your tests to support scrollIntoView(), but if you use the latest Chrome as is the Ionic default you should be all set.
  2. Used browser.sleep() to explicitly give the browser 500ms to catch up. I have seen advice against this, but when the preceding action has to happen outside of events Protractor handles natively, it’s not always easy to see what expected conditions you could wait for instead – and this does work. (Alternatives and pull requests always welcome!)
  3. Made the test helper method that performs the button click in my app.po.ts return a Promise, resolving to boolean true only after the click()‘s promise resolved. This can only happen after the sleep() above.
  4. Made the actual tests in app.e2e-spec.ts verify the value to be true with an expect() call. I think this is the cleanest way to ensure the tests wait exactly as long as necessary for the button click to have been fully processed, which is when our promise resolves.

Here’s what my save() helper method now looks like:

public save(): Promise {
  const ionicSaveButton = element(by.css(`ion-button[name="save"]`));

  return new Promise(resolve => {
    // Scroll to the bottom of the content, so we don't have to make the browser viewport
    // huge to avoid the tab bar stealing focus when clicking Save. https://stackoverflow.com/a/47580259/2803757
    const ionicSaveButtonWebElement = ionicSaveButton.getWebElement();
    browser.executeScript(
      `arguments[0].scrollIntoView({behavior: "smooth", block: "end"});`,
      ionicSaveButtonWebElement,
    );

    // As the scrolling happens with custom JS and 'outside' the normal E2E event flow, a
    // short explicit sleep seems to be needed for the scroll to complete before we try to
    // click() the button. Waiting for `executeScript()`'s promise resolution wasn't sufficient.
    browser.sleep(500);

    ionicSaveButton.click().then(() => resolve(true));
  });
}

And its invocations in tests:

...
expect(page.save()).toEqual(true);
...

Found a better way to do this? Let me know!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.