Chai as promised eventually not waiting long enough in Protractor e2e tests against an Angular app

545 Views Asked by At

I'm using Protractor with Chai-as-promised for my newest Angular 4 app. I have only 2 e2e tests for now. One logging in an existant user, another signing up a new one. My signup logic will also login the newly created user if signup succeeds.

Here's html the code:

<ul class="navbar-nav my-2 my-lg-0">
  <li *ngIf="!isAuthenticated()" class="nav-item">
    <button id="loginLink" class="btn btn-link nav-link" style="line-height: inherit" type="button" role="button" (click)="openLoginModal()">Login</button>
  </li>
  <li *ngIf="!isAuthenticated() && signupAllowed()" class="nav-item">
    <button id="signupLink" class="btn btn-link nav-link" style="line-height: inherit" type="button" role="button" (click)="openSignupModal()">Sign up</button>
  </li>
  <li *ngIf="isAuthenticated()" class="nav-item">
    <a id="logoutLink" class="nav-link" role="button" (click)="logout()">Logout</a>
  </li>
</ul>

And here's the e2e tests code:

import { browser, by, element } from "protractor";

const expect = global[ 'chai' ].expect;

describe('Auth', () => {
  describe('login/logout', () => {
    it('should login successfully with valid username and password tuple', () => {
      // arrange
      browser.get('/');
      expect(element(by.id('loginLink')).isPresent()).to.eventually.be.true;
      element(by.id('loginLink')).click();

      // act
      element(by.id('usernameInput')).sendKeys('jeep87c');
      element(by.id('passwordInput')).sendKeys('1111');
      element(by.id('loginBtn')).click();

      // assert
      expect(element(by.id('logoutLink')).isPresent()).to.eventually.be.true;
      element(by.id('logoutLink')).click();
      expect(element(by.id('loginLink')).isPresent().to.eventually.be.true;
    });
  });

  describe('signup/logout', () => {
    it('should succeeds with unused username and matching passwords and login', () => {
      // arrange
      browser.get('/');
      expect(element(by.id('signupLink')).isPresent()).to.eventually.be.true;
      element(by.id('signupLink')).click();

      // act
      element(by.id('usernameInput')).sendKeys('test');
      element(by.id('passwordInput')).sendKeys('1111');
      element(by.id('confirmPasswordInput')).sendKeys('1111');
      element(by.id('signupBtn')).click();

      // assert
      expect(element(by.id('logoutLink')).isPresent()).to.eventually.be.true;
      element(by.id('logoutLink')).click();
    });
  });
});

As you can see, both test are pretty similar. But, the signup/logout fails on expect(element(by.id('logoutLink')).isPresent()).to.eventually.be.true;, which is also present in the login/logout test, which succeeds.

What am I missing? It feels like it doesn't wait at all. But it works for the login/logout(meaning the test succeeds).

Both login & signup requests to the API are taking around 200ms to complete each. Important to note that when a user submit the signup form, it will trigger first a signup request to the api and if it succeeds, then a login request. So it will take around 400ms to complete. Which, I'm guessing, is above what eventuallywill poll/wait for.

EDIT: Some expect(...).to.eventually were missing in the code above, which I fixed. As a FYI, here's the assertion failure:

Auth signup/logout should succeeds with unused username and matching passwords and login:

  AssertionError: expected false to be true

It really feels like the promise resolve before the element is actually present in the dom and fail the assertion. Instead of polling for it again until it's true or timeout.

EDIT #2: Just tried the exact same code but, waiting 5 seconds before executing the last two lines and it passed successfully...

setTimeout(function() {
  expect(element(by.id('logoutLink')).isPresent()).to.eventually.be.true;
  element(by.id('logoutLink')).click();
}, 5000);

So it's definitely expect(element(by.id('logoutLink')).isPresent()).to.eventually.be.true; promise resolving to fast to false and failing the test. How do I fix this?

Any help will be appreciated.

1

There are 1 best solutions below

0
On

I'm not sure it was the issue causing this but, once I change my signup code from :

signup() {
  this.auth.signup(signupData)
      .subscribe({
                   error: (err: any) => {},
                   complete: () => {
                     this.auth.login(new LoginData(signupData.username, signupData.password))
                         .subscribe({
                                      error: (err: any) => {},
                                      complete: () => {
                                        this.activeModal.close();
                                        this.router.navigateByUrl('/');
                                      }
                                    });
                   }
                 });
}

To:

signup() {
  this.auth.signup(signupData)
      .subscribe({
                   next: (response) => this.auth.setToken(response.json().token),
                   error: (err: any) => {}, // TODO-jeep87c: Handle 403, 401 with proper UI alert and 500/404 in common handler (use username.setErrors)
                   complete: () => {
                     this.activeModal.close();
                     this.router.navigateByUrl('/');
                   }
                 });
}

The test succeeded. I'm using ng2-ui-auth being a Angular 2+ port of original Satellizer for AngularJs.

It could be related to how I used the Obsersables returned by those auth service's methods.

If anybody could explain, I would be happy to truly understand what happened here.