2

I was recently tasked with implementing some automated testing for an AngularJS application developed by my company. After doing some research into the different testing frameworks available, I decided to use Protractor as it seems to provide all of the functionality that we will need, and appears to be well documented & maintained.

When I started designing & writing the tests, I noticed that most of the HTML elements in the code had not been given ID attributes- so I started assigning ID attributes to the various elements that would be used in the tests, so that I could access them using functions such as element(by.id('abc')); to assign them to variables for use within the test scripts.

However, I have just been informed that not giving the HTML elements ID attributes was a deliberate design choice in order to make the source code easier to compress for distribution to our customers. So this means that I now need to re-write my tests so that they no longer use ID attributes to get hold of the elements, but I'm not sure quite how to do this...

When I inspect those menu item in the browser, it shows the following HTML structure:

<ul id="nav" class="nav" data-slim-scroll ... >
    <li data-ng-mouseenter="setMenuTop($event)" ...
        <a href="#/pages" id="pagesMenuBtn" ... >
            ...
        <a href="#/alarms" id="alarmsMenuBtn" ... >
            ...
        ...
    </li>
    ...
</ul>

The goal is to be able to remove the id attributes from the tags above, but still be able to access those HTML elements to run tests on them.

For example, where I previously had:

describe('myApp', function() {
    var alarmsMenuBtn = element(by.id('alarmsMenuBtn'));
    var chartsMenuBtn = element(by.id('chartsMenuBtn'));
    ...
    it('should navigate to the Charts page', function(){
        browser.waitForAngularEnabled(false);
        browser.actions().mouseMove(chartsMenuBtn).perform();
        chartsMenuBtn.click();
        browser.waitForAngularEnabled(true);
        browser.call(closeDlg).then(function(){
            expect(browser.getCurrentUrl()).toBe('chartsURL');
    });

I now have:

describe('myApp', function() {
    var menuItems = $(".slimScrollDiv").$(".nav").$$("li");
    var alarmsMenuBtn = menuItems.get(1).$("a");
    var chartsMenuBtn = menuItems.get(2).$("a");

But when I run this with the same tests, my tests now fail, giving me messages such as:

Failures: 1) App should navigate to the Alarms page Message: Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL. Stack: Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL. at ontimeout (timers.js:386:11) at tryOnTimeout (timers.js:250:5) at Timer.listOnTimeout (timers.js:214:5) Message: Failed: element not visible (Session info: chrome=61.0.3163.100) (Driver info: chromedriver=2.33.506120 (e3e53437346286c0bc2d2dc9aa4915ba81d9023f),platform=Windows NT 10.0.15063 x86_64) Stack: ...

and:

2) App should navigate to the Charts page Message: Failed: element not visible (Session info: chrome=61.0.3163.100) (Driver info: chromedriver=2.33.506120 (e3e53437346286c0bc2d2dc9aa4915ba81d9023f),platform=Windows NT 10.0.15063 x86_64)

The HTML for these elements is:

<li><a href="#/alarms" id="alarmsMenuBtn" target="_self"><i class="ti-alert"></i><span data-i18n="Alarms"></span></a></li>
<li><a href="#/charts" id="chartsMenuBtn" target="_self"><i class="ti-bar-chart"></i><span data-i18n="Charts"></span></a></li>

So my question is, how can I access these (or any other) HTML elements using something other than ID attributes to get them?

5
  • First of all, you likely have a syntax error in your code: browser.actions().mouuseMove(chartsMenuBtn).perform(); (mouuse!). That said, either use by.xpath (some browsers like chrome supports getting a query selector by right clicking an element), or just get deeper in protractor. I could go on for a while there, but two great sources that helped me are the following: luxiyalu.com/protractor-locators-selectors and blog.ng-book.com/10-protractor-tips-for-better-e2e-tests . In any case, are elements meant to be somewhat static or are they meant to change over time? Commented Oct 31, 2017 at 11:34
  • 1
    About xpath on chrome -> prntscr.com/h4bfl8 (example). Result, in this case, is //*[@id="comment-81014907"]/td[2]/div/span[1] (which is UNIQUE for this specific case though). About element being not visible, check this: stackoverflow.com/questions/37809915/… . Moreover, I would suggest you to run protractor on some specific environments, there are some great starters on yeoman that gives you a starter kit and debugging in visual studio code. That's very helpful, sometimes. Commented Oct 31, 2017 at 11:35
  • Apologies- that was a copy-paste typo somehow... Commented Oct 31, 2017 at 11:39
  • The issue you have is visibility of the element. Visibility means that the element is not only displayed but also has a height and width that is greater than 0. Are there any menuItems that are presented to the DOM but aren't visible? Commented Oct 31, 2017 at 12:23
  • 1
    No, all of the menu items are visible. Commented Oct 31, 2017 at 13:07

2 Answers 2

2

The Protractor API documentation provides a full list of locators you may use in addition to ID to locate DOM elements. The documentation also provides sample code to demonstrate how each locator can be implemented in practice.

Here is the full list as of Protractor version 5.2.0:

ProtractorBy

The Protractor Locators. These provide ways of finding elements in Angular applications by binding, model, etc.

addLocator - Add a locator to this instance of ProtractorBy.

binding - Find an element by text binding.

exactBinding - Find an element by exact binding.

model - Find an element by ng-model expression.

buttonText - Find a button by text.

partialButtonText - Find a button by partial text.

repeater - Find elements inside an ng-repeat.

exactRepeater - Find an element by exact repeater.

cssContainingText - Find elements by CSS which contain a certain string.

options - Find an element by ng-options expression.

deepCss - Find an element by css selector within the Shadow DOM.

Extends webdriver.By

className - Locates elements that have a specific class name.

css - Locates elements using a CSS selector.

id - Locates an element by its ID.

linkText - Locates link elements whose visible text matches the given string.

js - Locates an elements by evaluating a JavaScript expression, which may be either a function or a string.

name - Locates elements whose name attribute has the given value.

partialLinkText - Locates link elements whose visible text contains the given substring.

tagName - Locates elements with a given tag name.

xpath - Locates elements matching a XPath selector.

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks for your answer. I did look into this, but the issue that I thought using something like className or some other 'non-unique' attribute might give me was that it might return a number of elements when I am looking for just one particular one (hence wanting to use the ID as a unique value)... I just gave by.className(...) a go, and sure enough, I got a load of messages stating W/element - more than one element found for locator By(css selector, .ti-bar-chart)- the first result will be used (for example).
The test then also fails, because it's trying to perform a click on the wrong element...
I am already using the repeater to find elements inside ng-repeat elements, and this works correctly. The particular elements I don't seem to be able to get hold of without IDs are <a></a> elements nested inside <li></li> elements.
Just gave by.linkText() a go, passing it the value given to the data-i18n attribute of the HTML tag, and that appears to have worked- thanks a lot!
I have a follow up query: since doing this, when I now run my tests, I seem to keep getting a FA Jasmine spec timed out. Resetting the WebDriver Control Flow. error, which is always thrown at the same point- the point where my closeDlg() function is called to close a dialog that is opened automatically when browsing to a specific page. I tried adding a browser.waitForAngularEnabled(true); call within that function (before assigning the 'Cancel' button to a variable), but this doesn't seem to have made a difference...
-1

following by id is bad idea.. please use data-test-hooks... and you already have documentation about protractor. http://www.protractortest.org/#/api

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.