Nightwatch: Better way than `.pause(1000)` to avoid brittle tests?

Peter V. Mørch picture Peter V. Mørch · Oct 19, 2015 · Viewed 20.4k times · Source

Is .pause(1000) really the best-practice there is to wait for form submission? I'm looking for a way to reliably submit a form without having to know details about the page appearing as a result of the form submission.

The example from the home page uses .pause(1000) to wait for form submissions, and ironically doesn't work any longer, but this version with a modified css-selector version does:

module.exports = {
  'Demo test Google' : function (client) {
    client
      .url('http://www.google.com')
      .waitForElementVisible('body', 1000)
      .assert.title('Google')
      .assert.visible('input[type=text]')
      .setValue('input[type=text]', 'rembrandt van rijn')
      .waitForElementVisible('button[name=btnG]', 1000)
      .click('button[name=btnG]')
      .pause(1000)
      // This selector is different from the home page's - this one
      // works...
      .assert.containsText('ol#rso div.g:first-of-type',
        'Rembrandt - Wikipedia')
  }
};

The problem with .pause(1000) to ensure the form gets submitted is how to determine the timeout. It is either going to make our tests slow if the timeout is too long or make them brittle if the timeout is too short. Slow hardware, other processes on the server, moon alignment, you name it can influence what "good" timeout values should be.

Is there a better way to say: "Wait for the form to be submitted before continuing"?

We've experimented with .waitForElementVisible('body', VERY_LONG_TIMEOUT) instead, and it seems to work and not take longer than necessary, but I'm guessing this also isn't reliable. That it only works because the "current" page has disappeared (this time) and so we're waiting for the "new" page's body to appear. And that tomorrow some oddity will occur and it'll be faster than normal and .waitForElementVisible('body') will return immediately because the old page is still there. == also brittle. Is that correct?

If so, is there some less brittle way than .pause(1000) or .waitForElementVisible('body')? Especially if we don't know much about the page returned after the submission, so we can't .waitForElementVisible('.element-only-on-new-page')?

The reason I'm asking is that our tests actually looked more like:

module.exports = {
  'Test1 - submit form' : function (client) {
    client
      .url('http://some/url')
      .waitForElementVisible('body', 1000)
      .assert.title('MyTitle')
      .setValue('input[name="widget"]', 'value')
      // Click to submit the form to change some internal state
      .click('button[name="postForm"]')

      // Form got submitted fine in chromium 42 every single time. chromium
      // 45 needs additionally:
      //
      // .pause(1000)
      // or
      // .waitForElementVisible('body', 1000)
  }
  'Test2 - continue using new value' : function (client) {
    client
      .url('http://some/other/url')
      .waitForElementVisible('body', 1000)
      .assert.title('MyOtherTitle')
      .setValue('input[name="widget2"]', 'value2')
      .waitForElementVisible('.bla-bla', 1000)
  }
};

This broke because the form at 'http://some/url' no longer gets submitted in chromium 45 :-( We'd like find a good solution, not just one that seems to work under today's conditions...

Answer

Stuart Brock picture Stuart Brock · Aug 22, 2016

Have you tried chaining waitForElementNotVisible with waitForElementVisible for the body html? This should only wait for the appropriate time at each step. I'd do some testing to make sure it isn't brittle though. We're using this to monitor a "simulated page transition" in a Single Page Application.

e.g.

module.exports = {
  'Test1 - submit form' : function (client) {
    client
      .url('http://some/url')
      .waitForElementVisible('body', 1000)
      .assert.title('MyTitle')
      .setValue('input[name="widget"]', 'value')
      // Click to submit the form to change some internal state
      .click('button[name="postForm"]')
      .waitForElementNotVisible('body', 5000)
      .waitForElementVisible('body', 10000)
  }
};