Protractor e2e tests with MEANJS navigation

767 views Asked by At

I'm still in learning mode with most of this.

I have installed MeanJS and am using the navigation that came out of the box. I am starting to implement end-to-end tests using Protractor and Jasmine.

I want my protractor tests to interact with the navigation component. In full page it is wide. In mobile size, it is a hamburger. Any general information on good ways to write / think about testing this would be much appreciated.

Here are some of the items I'm stuck on at the moment:

Simulating a menu item selection

I want to simulate the user clicking a menu item using Protractor. The HTML for the full-screen width presentation...

<li data-ng-repeat="item in menu.items | orderBy: 'position'" 
    data-ng-if="item.shouldRender(authentication.user);"  
    ng-switch="item.menuItemType" ui-route="/admin" class="dropdown"  
    ng-class="{active: ($uiRoute)}"  
    dropdown="item.menuItemType === 'dropdown'">  

    <!-- THIS NEEDS TO BE CLICKED TO MAKE THE CHILD LINKS VISIBLE -->
    <a ng-switch-when="dropdown" class="dropdown-toggle ng-scope"  
        dropdown-toggle="" aria-haspopup="true" aria-expanded="false"> 
        <span data-ng-bind="item.title" class="ng-binding">Admin</span> 
        <b class="caret"></b> 
    </a> 
    <ul ng-switch-when="dropdown" class="dropdown-menu ng-scope"> 
        <li data-ng-repeat="subitem in item.items | orderBy: 'position'"  
           data-ng-if="subitem.shouldRender(authentication.user);"  
           ui-route="/admin/users" ng-class="{active: $uiRoute}" class="ng-scope">  

            <!-- THIS IS WHAT I WANT TO CLICK -->
            <a href="/#!/admin/users" data-ng-bind="subitem.title" class="ng-binding"> 
               Administer Users 
            </a> 
        </li> 
    </ul>

I think what I need to do is simulate the click of the "Admin" link to expose the child menu (Noted with the first comment above). Then simulate the click of the "Administer Users" link to trigger the navigation

My specific questions:

  1. Am I correct in my strategy for simulating navigation link clicks?
  2. What magic element(by.?) statement should I use to find the "Admin" link
  3. Assuming I have the "Admin" link, what is the best way to find the "Administer Users" link to simulate that click?

How should I handle the collapsed / hamburger mode

The navigation paradigm changes when the in mobile layout. I've not studied the layout yet, but there are different clicks needed for the same links.

What are the best practices here? Any advice on resources I can use to help discover what to do?

I should also note...

I do want to find the link that is a child of the menu... as opposed to just finding any link with that URL in it.

There are more menu items than what is shown... I just reduced the html sample for clarity.

1

There are 1 answers

0
Doug On BEST ANSWER

There and Back Again... (self answer)

I understand much more about protractor, JS, etc. than I did when I started. I think this is a pretty decent solution. This would have saved be enormous amounts of time. Hope it helps someone...

First, use the Page Object pattern.

Next, create a function to click the navbar links, including click to expand/expose the children. It needs to work intelligently based on the responsive nature of the MEANJS nav (e.g. to hamburger or not to hamburger):

commonPOs.clickNavBar = function(mainTab, linkUrl) {
  var deferred = protractor.promise.defer();

  var hamburger = element(by.id('nav-hamburger'));

  hamburger.isDisplayed().then(function(result) {
    if ( result ) {
      hamburger.click().then(function() {
        var navBar = hamburger
          .element(by.xpath('../..'))
          .element(by.id('myapp-navbar'));
          return clickItNow(mainTab, linkUrl, navBar, deferred);
      });
    } else {
      return clickItNow(mainTab, linkUrl, element(by.id('myapp-navbar')), deferred);
    }
  });

  return deferred.promise;
};

function clickItNow(mainTab, linkUrl, navBar, deferred) {
  var targetLink;
  var linkCssExpression = 'a[href*="' + linkUrl + '"]';

  expect(navBar.waitReady()).toBeTruthy();

  if(mainTab) {
    // if mainTab was  passed, neet to
    // click the parent first to expose the link
    var parentTabLink;

    if (mainTab == 'ACCTADMIN') {
      parentTabLink = navBar.element(by.id('account-admin-menu'));
    }
    else {
      parentTabLink = navBar.element(by.id('main-menu-' + mainTab.split(' ').join('')));
    }
    // expect(parentTabLink.isDisplayed()).toBeTruthy();
    expect(parentTabLink.waitReady()).toBeTruthy();
    parentTabLink.click();
    targetLink = parentTabLink.element(by.xpath('..')).element(by.css(linkCssExpression));
  }
  else {
    targetLink = navBar.element(by.css(linkCssExpression));
  }

  expect(targetLink.waitReady()).toBeTruthy();
  targetLink.click().then(function() {
    return deferred.fulfill();
  });
}

I reference the waitReady() function in the code above. You can find that here.

The navigation function above will require some html markup changes to protractor can find the navigation elements a little easier:

/public/modules/core/views/header.client.view.html Tag these elements with the IDs:

  • 'nav-hamburger'
  • 'myapp-navbar'
  • 'main-menu-{{item.title.split(' ').join('')}}' <<< lets spaces exist in the title
  • 'account-admin-menu'

/public/modules/core/views/header.client.view.html

<div class="container" data-ng-controller="HeaderController">
    <div class="navbar-header">
        <button id="nav-hamburger" class="navbar-toggle" type="button" data-ng-click="toggleCollapsibleMenu()">  . . . .
        </button>
    </div>
    <nav id="myapp-navbar" class="collapse navbar-collapse" collapse="!isCollapsed" role="navigation">
        <ul class="nav navbar-nav" data-ng-if="menu.shouldRender(authentication.user);">
            <li data-ng-repeat="item in menu.items | orderBy: 'position'" data-ng-if="item.shouldRender(authentication.user);" ng-switch="item.menuItemType" ui-route="{{item.uiRoute}}" class="{{item.menuItemClass}}" ng-class="{active: ($uiRoute)}" dropdown="item.menuItemType === 'dropdown'">
                <a id="main-menu-{{item.title.split(' ').join('')}}" ng-switch-when="dropdown" class="dropdown-toggle" dropdown-toggle>

        <ul class="nav navbar-nav navbar-right" data-ng-show="authentication.user">
            <li class="dropdown" dropdown>
                <a id="account-admin-menu" href="#" class="dropdown-toggle" data-toggle="dropdown" dropdown-toggle>

I have wrapped the navigation to specific elements with generic page object commands:

commonPOs.navToSigninPage = function() {
  var deferred = protractor.promise.defer();

  this.clickNavBar(null, 'signin').then(function() {
    expect(something).toBeTruthy();
    deferred.fulfill(new SigninPage());
  });

  return deferred.promise;
};

Finally, use it in your specs like so:

commonPOs.navToSigninPage().then(function(signInPage){
  signInPage.loginWithCredentials(username, password);
});