Marijke
Marijke

Reputation: 323

Protractor: how to get the text of ngb-alert

I want to test if adding a data source works. Normally I give in the info in a modal, I click confirm, the modal disappears and my list of data sources is now longer. (I check the length before and after adding the data source so I expect the list to be longer after editing it). But when the data source does not exist or was already added, a bootstrap alert pops up with an error message and the modal stays put. Example of error message What I want: check if an error message occurs so I can display that error message instead of the count of the data sources. That way I know quicker why my test failed.

I tried a lot of things, I tried waiting for the browser and without waiting for the browser. But every time it just doesn't want to see the alert.

This post from SO seemed to be exactly my problem: protractor: testing bootstrap alerts but did also not solve my problem.

This is the html code that pops up:

<dm-alert>
 <ngb-alert>
    <div role="alert" class="alert alert-danger alert-dismissible">
      <button aria-label="Close" class="close" type="button">
            <span aria-hidden="true">×</span>
      </button>
      The description '4106-5002-5005-5006-02' has to be unique
    </div>
 </ngb-alert>
</dm-alert>

I use async/await to be sure that the program waits, so all the functions the following code is in, are async functions. So I tried reaching it like this (also without the catch and with an extra function for when timeout exceeds). This was a method inside a page object, that is why it returns values:

(script 1)

import { $, browser, by, element, ExpectedConditions as until } from 'protractor';
await browser.wait(until.presenceOf(element(by.xpath('//ngb-alert/div[contains(@class, alert-danger)]'))), waitTimeForErrorInSeconds * 1000).then((isPresent) => {
      return isPresent;
    }).catch(() => {
      console.log('no error occured')
      return false;
    })
    return false;

And like this, this one was just inside the test: (script 2)

await browser.wait(until.presenceOf(element(by.xpath('//ngb-alert/div[contains(@class, alert)]'))), 10 * 1000)
     .then((isPresent) => {
         console.log('is present (true):  ' + isPresent);
         let text = element(by.xpath('//ngb-alert/div[contains(@class, alert)]')).getText();
         console.log(text);
      }, (isPresent) => {
          console.log('is present (false):  ' + isPresent)
      }).catch((error) => {
          console.log('browser.wait.catch: ' + error)
      })

here the test always goes into the 'is present (false)' when the alert appears or not. I like that it doesn't fail my test for this, but I'd like it to notice when the alert pops up.

And like this (also inside the test): (script 3)

expect(element(by.xpath('//ngb-alert/div[contains(@class, alert)]'))
     .isDisplayed())
     .toBeFalsy();

isDisplayed always gives an error because it can't find the element

(script 4)

expect(element(by.xpath('//ngb-alert/div[contains(@class, alert)]'))
     .isPresent())
     .toBeFalsy();

isPresent works well when no error is present, but when there is, it won't notice.

I checked the XPath by searching it in the dev tools in chrome when it is present and it always finds it immediately, so that shouldn't be the problem. Before I tried all these things, I made it work. So searching it by that XPath has worked before.

EDIT:

So I noticed that my script 2 was already good, but for some reason until.presenceOf didn't work but if I did .isPresent() on the element, it did seem to find the element. But why?

This is the code for the alert:

<ngb-alert [type]="notification.category" (close)="close(notification)">
    {{ notification.message }}
</ngb-alert>

So I thought one of these should work in script 2 (line 5) to get the text of the notification:

element(by.xpath('//ngb-alert')).getText();
element(by.xpath('//ngb-alert/div')).getText();
element(by.xpath('//ngb-alert/div[contains(@class, alert)]')).getText();
element(by.binding('notification.message')).getText();
element(by.binding('notification.message')).$('ngb-alert').getText();

But they don't...

So, another try with this: (script 5)

await browser.wait((element(by.xpath('//ngb-alert/div[contains(@class, alert-danger)]')).isPresent()), 3 * 1000)
    .then((isPresent) => {
       console.log('browser.wait.then true: ' + isPresent)
       element(by.binding('notification.message')).$('ngb-alert').getText()
                .then((text) => {
                    console.log(text);
                });
     }, (isPresents) => {
        console.log('browser.wait.then false: ' + isPresents)
     }).catch((error) => {
         // if fails
         console.log('browser.wait.catch: ' + error)

     })

This returns
browser.wait.then true: false
browser.wait.catch: JavascriptError: javascript error: angular is not defined

Another try...

Ok, so here I use until.presenceOf (until = ExpectedConditions) and no xpath:

        await browser.wait(until.presenceOf($('.alert-danger')), 3 * 1000)
            .then((isPresent) => {
                console.log('browser.wait.then true: ' + isPresent);
                $('ngb-alert').element(by.binding('notification.message')).getText().then((text) => {
                    console.log(text);
                });
            }, (isPresents) => {
                console.log('browser.wait.then false: ' + isPresents)
            }).catch((error) => {
                console.log('browser.wait.catch: ' + error)
            })

What happens here is that when no alert shows, it gives a response I expect:

browser.wait.then false: TimeoutError: Wait timed out after 3013ms  

But when an alert shows, it will wait until the alert disappears and then tell me this:

browser.wait.then false: TimeoutError: Wait timed out after 7664ms 

So why does it suddenly wait longer than the timeout I gave (which was 3 seconds).

Upvotes: 0

Views: 952

Answers (1)

Marijke
Marijke

Reputation: 323

I found the answer here:

...the alert’s internal dismiss functionality has been implemented with Angular’s $timeout service which increments $browser’s outstanding request count and protractor with ignoreSynchronization=false waits correctly until there is any outstanding requests. This means if you do not disable the synchronization, you cannot see the opened alert, because protractor suspends execution until it is visible...

But ignoreSynchronization is deprecated so you need to use await browser.waitForAngularEnabled(false). This makes sure it does not wait until the alert is gone to check what the alert is about.

So this is the code that worked for me to get the text of an ngb-alert if an alert occurs (for my code, I don't want an error to occur, but when it does, I want to be able to know what error it was):

await browser.waitForAngularEnabled(false);
await browser.wait(until.presenceOf(element(by.css('.alert-danger'))), 5 * 1000)
    .then((isPresent) => {
        console.log('presence of error: ' + isPresent);
        $('ngb-alert').getText().then((errorAlert) => {
          expect('no error').toBe('displayed', 
          'This error showed up when adding data source: ' + errorAlert)
        });
        }, (isNotPresent) => {
            console.log('no presence of error: ' + isNotPresent)
        }).catch((error) => {
            console.log('checking presence of error caused an error: ' + error)
        })
await browser.waitForAngularEnabled(true);

P.S.: Getting the element by binding is not something you can do with angular 5, the following error will occur:

"JavascriptError: javascript error: angular is not defined".

Upvotes: 1

Related Questions