Noble-Surfer
Noble-Surfer

Reputation: 3172

Angular JS- testing with Protractor: how to get an element when element doesn't have a tag-name?

I am developing a test suite for my AngularJS app using Protractor as my testing framework.

The app's menu is displayed at a fixed location across all pages of the site- when the user hovers the cursor over any of the buttons on the menu, the cursor changes to a pointer, to indicate that that button can be clicked. Each button in the menu represents a particular page of the app- if there are sub-pages belonging to a page when the user hovers the cursor over the menu button for that page, a sub-menu is displayed, with text-buttons for each of the sub-pages.

I am currently working on a test that will test the functionality of the menu- hovering the cursor over one of the buttons on the menu, checking that the sub-menu is displayed, and then hidden again when the cursor is no longer hovering over that menu button, checking that clicking any of the menu buttons/ sub-menu buttons take you to the right page, etc.

The HTML for the menu is written as follows:

<ul id="nav"
    class="nav"
    data-slim-scroll
    data-collapse-nav
    data-highlight-active>
    <li data-ng-mouseenter="setMenuTop($event)" ng-show="menu.pages.length > 0 || menu.upages.length > 0 || canCreatePage">
        <a href="#/pages" id="pagesMenuBtn" target="_self"><i class="ti-layers"></i><span data-i18n="Pages"></span></a>
        <ul class="text-capitalize">
            <li ng-repeat="page in menu.pages"><a href="#/{{page.location}}" target="_self"><i class="ti-angle-right"></i><span data-i18n="{{page.title}}"></span></a></li>
            <li ng-repeat="upage in menu.upages">
                <div class="nav-menu-divider" data-ng-if="$index === 0"></div>
                <a href="#/{{upage.location}}" target="_self">
                    <i class="ti-angle-right"></i><span data-i18n="{{upage.title}}"></span>
                </a>
            </li>
            <li data-ng-show="canCreatePage">
                <div class="nav-menu-divider"></div>
                <a href="#/pages/create" target="_self">
                    <i class="ti-angle-right"></i><span data-i18n="Create Page"></span>
                </a>
            </li>
        </ul>
    </li>
    <li>...</li>
    <li>...</li>
    <li>...</li>
    <li>...</li>
    <li data-ng-mouseenter="setMenuTop($event)">
        <a href="#/config" target="_self"><i class="ti-settings"></i><span data-i18n="Config"></span></a>
        <ul>
            <li ng-repeat="page in menu.config"><a href="#/{{page.location}}" target="_self"><i class="ti-angle-right"></i><span data-i18n="{{page.title}}"></span></a></li>
            <li ng-show="platform._id"><a ng-href="#/config/platform/{{platform._id}}" target="_self"><i class="ti-angle-right"></i><span data-i18n="Platform"></span></a></li>
            <li ng-show="system._id"><a ng-href="#/config/system/{{system._id}}" target="_self"><i class="ti-angle-right"></i><span data-i18n="System"></span></a></li>
            <li><a href="#/config/user" target="_self"><i class="ti-angle-right"></i><span data-i18n="Users"></span></a></li>
            <li><a href="#/config/backup" target="_self"><i class="ti-angle-right"></i><span data-i18n="Backup / Restore"></span></a></li>
            <li ng-show="systemActions.dateTime"><a href="#/config/systemtime" target="_self"><i class="ti-angle-right"></i><span data-i18n="Date / Time"></span></a></li>
        </ul>
    </li>
    <li class="nav-footer">
        <span>{{currentTime.localTDZFormat}}</span>
    </li>
</ul>

As shown, there are two menu items which have 'sub-menus' associated with them.

I want to test that when the user hovers their cursor over one of the menu-items that have an associated sub-menu, the sub-menu is displayed correctly, and when the cursor leaves the menu-item, the sub-menu is no longer displayed: I have tried to do this with the following test:

it('should display the Pages menu', function() {
    browser.waitForAngularEnabled(false);
    browser.actions().mouseMove(pagesMenuBtn).perform();
    expect(pageVarBrowserBtn.isDisplayed()).toBeTruthy();
    browser.actions().mouseMove(userCircle).perform();
    expect(pageVarBrowserBtn.isDisplayed()).toBeFalsy();
    browser.waitForAngularEnabled(true);
});

The variables used in this test are defined with:

var pagesMenuBtn = element(by.id('pagesMenuBtn'));
var pageVarBrowserBtn = element.all(by.id('menu.pages')).get(0);
var userCircle = element(by.id('icon-user-circle'));

The pageVarBrowserBtn is the first menu-item in the sub-menu displayed when the user hovers their cursor over the pagesMenuBtn.

Currently, when I run the test, the browser opens, navigates to the root page of my app, and hovers the cursor over the pagesMenuBtn element, so that the sub-menu is displayed (I can see it displayed in the browser window that was opened up by the test).

But, I then get an error in the console that says:

Failed: Index out of bound. Trying to access element at index: 0, but there are only 0 elements that match locator By(CSS selector, *[id="menu.pages"])

I don't understand why I'm getting this error...? How can I get a single element from the sub-menu that's being displayed, in order to check that it is the correct item and that clicking it takes the user to the correct location? I can't specify the exact element's ID because it is being created by the ng-repeat, so that's why I'm trying to get it by its position/ array index - 0.

Upvotes: 0

Views: 337

Answers (2)

Sudharsan Selvaraj
Sudharsan Selvaraj

Reputation: 4832

First of all, There may be two reasons why you are getting the above error.

1. Either the locator you are using is wrong.
2. Your script is not waiting until the element is displayed in DOM.

In your case, you have mentioned a wrong locator as element.all(by.id('menu.pages')) because there is no element with id menu.pages is present in your HTML.

Instead of using element.all(by.id('menu.pages')), change it to element.all(by.repeater('page in menu.pages')).get(0) to get the 1st element from the menu list. Also kindly refer Element locators in Protractor to find the list of all available locating strategies that is supported by protractor.

Hope this might help solve your problem!

Upvotes: 1

Venu Duggireddy
Venu Duggireddy

Reputation: 806

Did you try using element.all(locator).first() to get the first element form the selector

Upvotes: 0

Related Questions