Search code examples
angularjstimeoutprotractor

Testing the contents of a temporary element with protractor


I'm trying to test the login page on my site using protractor.

If you log in incorrectly, the site displays a "toast" message that pops up for 5 seconds, then disappears (using $timeout).

I'm using the following test:

  describe('[login]', ()->
    it('should show a toast with an error if the password is wrong', ()->

      username = element(select.model("user.username"))
      password = element(select.model("user.password"))
      loginButton = $('button[type=\'submit\']')
      toast = $('.toaster')

      # Verify that the toast isn't visible yet
      expect(toast.isDisplayed()).toBe(false)

      username.sendKeys("admin")
      password.sendKeys("wrongpassword")
      loginButton.click().then(()->
        # Verify that toast appears and contains an error
        toastMessage = $('.toast-message')
        expect(toast.isDisplayed()).toBe(true)
        expect(toastMessage.getText()).toBe("Invalid password")
      )
    )
  )

The relevant markup (jade) is below:

.toaster(ng-show="messages.length")
  .toast-message(ng-repeat="message in messages") {{message.body}}

The problem is the toastMessage test is failing (it can't find the element). It seems to be waiting for the toast to disappear and then running the test.

I've also tried putting the toastMessage test outside the then() callback (I think this is pretty much redundant anyway), but I get the exact same behaviour.

My best guess is that protractor sees that there's a $timeout running, and waits for it to finish before running the next test (ref protractor control flow). How would I get around this and make sure the test runs during the timeout?

Update:

Following the suggestion below, I used browser.wait() to wait for the toast to be visible, then tried to run the test when the promise resolved. It didn't work.

console.log "clicking button"
loginButton.click()

browser.wait((()-> toast.isDisplayed()),20000, "never visible").then(()->
  console.log "looking for message"
  toastMessage = $('.toaster')
  expect(toastMessage.getText()).toBe("Invalid password")
)

The console.log statements let me see what's going on. This is the series of events, the [] are what I see happening in the browser.

clicking button
[toast appears]
[5 sec pass]
[toast disappears]
looking for message
[test fails]

For added clarity on what is going on with the toaster: I have a service which essentially holds an array of messages. The toast directive is always on the page (template is the jade above), and watches the messages in the toast service. If there is a new message, it runs the following code:

scope.messages.push(newMessage)
# set a timeout to remove it afterwards.
$timeout(
()-> 
  scope.messages.splice(0,1) 
, 
  5000
)

This pushes the message into the messages array on the scope for 5 seconds, which is what makes the toast appear (via ng-show="messages.length").

Why is protractor waiting for the toast's $timeout to expire before moving on to the tests?


Solution

  • It turns out that this is known behaviour for protractor. I think it should be a bug, but at the moment the issue is closed.

    The workaround is to use $interval instead of $timeout, setting the third argument to 1 so it only gets called once.