Тестирование содержимого временного элемента с помощью транспортира

Я пытаюсь проверить страницу входа на моем сайте с помощью транспортира.

Если вы входите в систему неправильно, на сайте отображается сообщение "тост", которое появляется в течение 5 секунд, а затем исчезает (используя $timeout).

Я использую следующий тест:

  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")
      )
    )
  )

Соответствующая разметка (нефрит) находится ниже:

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

Проблема заключается в отсутствии теста toastMessage (он не может найти элемент). Кажется, он ждет, когда тост исчезнет, ​​а затем запустит тест.

Я также попытался поставить тест toastMessage вне обратного вызова then() (я думаю, что это в значительной степени избыточно), но я получаю то же самое поведение.

Моя лучшая догадка заключается в том, что транспортир видит, что там работает $timeout, и ждет его завершения до запуска следующего теста (ref поток управления транспортиром). Как мне обойти это и убедиться, что тест выполняется во время таймаута?

Update:

Следуя приведенному ниже предложению, я использовал browser.wait(), чтобы ждать, пока тост станет видимым, а затем попытался запустить тест, когда обещание было разрешено. Это не сработало.

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")
)

Операторы console.log позволяют мне видеть, что происходит. Это серия событий, [] - это то, что я вижу в браузере.

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

Для дополнительной ясности в том, что происходит с тостером: у меня есть служба, которая по существу содержит массив сообщений. Тост-директива всегда находится на странице (шаблон - нефрит выше) и следит за сообщениями в службе тостов. Если появилось новое сообщение, он запускает следующий код:

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

Это подталкивает сообщение в массив сообщений в области 5 секунд, что и делает тост (через ng-show="messages.length").

Почему переносчик ждет, пока истечет срок тоста $timeout, прежде чем перейти к испытаниям?

Ответ 1

Оказывается, что это известное поведение для транспортира. Я думаю, что это будет ошибка, но на данный момент проблема закрыта.

Обходной путь заключается в использовании $interval вместо $timeout, установив третий аргумент в 1, чтобы он вызывался только один раз.

Ответ 2

Я взломал это, используя нижеследующий блок кода. У меня была панель уведомлений от стороннего пакета node (ng-notifications-bar), который использовал $timeout вместо $interval, но нужно было ожидать, что текст ошибки будет определенным значением. Я использую короткий сон(), чтобы появилась анимация панели уведомлений, переключилась ignoreSynchronization на true, поэтому Protractor не дождался окончания $timeout, установил my expect() и переключил ignoreSynchronization обратно на false, поэтому Protractor может продолжите тест в пределах регулярной качательности AngularJS. Я знаю, что сон не идеальны, но они очень короткие.

browser.sleep(500);
browser.ignoreSynchronization = true;
expect(page.notification.getText()).toContain('The card was declined.');
browser.sleep(500);
browser.ignoreSynchronization = false;

Ответ 3

you should wait for your toast displayed then do other steps

browser.wait(function() {
    return $('.toaster').isDisplayed();
}, 20000);

Ответ 4

Если кому-то все еще интересно, этот код работает для меня без хаков до $timeout или $interval или Toast. Идея состоит в том, чтобы использовать promises щелчка() и wait() для включения и выключения синхронизации. Нажмите все, чтобы перейти на страницу с помощью сообщения тоста, и немедленно отключите синхронизацию, дождитесь сообщения с тостом, затем отпустите его, а затем снова включите синхронизацию (ВСТАВЬТЕ обещание).

element(by.id('createFoo')).click().then(function () {
    browser.wait(EC.stalenessOf(element(by.id('createFoo'))), TIMEOUT);
    browser.ignoreSynchronization = true;
    browser.wait(EC.visibilityOf(element(by.id('toastClose'))), TIMEOUT).then(function () {
      element(by.id('toastClose')).click();
      browser.ignoreSynchronization = false;
   })
});

Ответ 5

Надеюсь, это может помочь тем, у кого есть проблемы с транспортиром, жасмином, angular и ngToast.

Я создаю CommonPage для обработки Toast на всех страницах без дубликата кода. Например:

var CommonPage = require('./pages/common-page');
var commonPage = new CommonPage();
decribe('Test toast', function(){
    it('should add new product', function () {
            browser.setLocation("/products/new").then(function () {

                element(by.model("product.name")).sendKeys("Some name");    
                var btnSave = element(by.css("div.head a.btn-save"));
                browser.wait(EC.elementToBeClickable(btnSave, 5000));
                btnSave.click().then(function () {

                    // this function use a callback to notify
                    // me when Toast appears
                    commonPage.successAlert(function (toast) {
                        expect(toast.isDisplayed()).toBe(true);
                    });

                });
            });
        })
});

И это мой CommonPage:

var _toastAlert = function (type, cb) {
    var toast = null;

    switch (type) {
        case "success":
            toast = $('ul.ng-toast__list div.alert-success');
            break;
        case "danger":
            toast = $('ul.ng-toast__list div.alert-danger');
            break;
    }

    if (!toast) {
        throw new Error("Unable to determine the correct toast type");
    }

    browser.ignoreSynchronization = true;
    browser.sleep(500);
    browser.wait(EC.presenceOf(toast), 10000).then(function () {
        cb(toast);
        toast.click();
        browser.ignoreSynchronization = false;
    })


}

var CommonPage = function () {    
    this.successAlert = function (cb) {
        _toastAlert("success", cb);
    };
    this.dangerAlert = function(cb) {
        _toastAlert("danger", cb);
    }
}

module.exports = CommonPage;