14

According to the documentation, to get a single attribute by name you can use .getAttribute() on a WebElement:

var myElement = element(by.id('myId'));
expect(myElement.getAttribute('myAttr')).toEqual('myValue');

But how can I get all of the attributes that an element has?

There is no information about this use case/functionality in the Protractor API.

Dr1Ku
  • 2,875
  • 3
  • 47
  • 56
alecxe
  • 462,703
  • 120
  • 1,088
  • 1,195
  • Take a look at [`getOuterHtml()`](http://angular.github.io/protractor/#/api?view=webdriver.WebElement.prototype.getOuterHtml). – J0e3gan Dec 29 '14 at 19:48
  • @J0e3gan thank you, but this would give me an HTML representation of an element. The desired output would be an object, key:value of all attributes of an element. – alecxe Dec 29 '14 at 20:22
  • Just beware that frameworks like Angular add more attributes, which might make your tests vulnerable. – Dmitri Zaitsev May 16 '15 at 13:46
  • @DmitriZaitsev totally agree and it makes perfect sense. Thanks. – alecxe May 16 '15 at 19:18

4 Answers4

10

You can expand javascript's Element type and add getAttributes() function:

Element.prototype.getAttributes = function() {
    return (function (node) {
        var attrs = {};
        for (var i=0;i<node.length;i++) {
            attrs[node.item(i).name] = node.item(i).value;
        }
        return attrs;
    })(this.attributes);
};

demo

then you can test integrity of attributes using the same method you use for one attribute:

var myElement = element(by.id('myId'));
expect(myElement.getAttributes()).toEqual({'attr1': 'value1', 'attr1': 'value1', ... });
neo
  • 579
  • 1
  • 3
  • 14
  • This is an awesome idea - now I can easily reuse `getAttributes()` throughout the project. Thank you! – alecxe Jan 08 '15 at 01:09
  • 1
    Before you go too far, consider: [*What's wrong with extending the DOM*](http://perfectionkills.com/whats-wrong-with-extending-the-dom/). – RobG Jan 08 '15 at 12:47
  • @RobG I shall quote one of the comments from post: "From the perspective of the Javascript, being a prototyping language, there is nothing technically wrong with doing this. Extending prototypes is exactly what Javascript programmers should be doing". The truth is no matter how you develop your code there is always drawbacks and pitfalls but it doesn't always mean what your doing is wrong and sometimes you just need to be more careful and maybe take stronger measures. tnx for the post though, it was really helpful – neo Jan 08 '15 at 13:43
  • How many libraries extend the DOM via prototypes? Perhaps the authors know something. Even Prototype.js doesn't do that anymore. Just because you *can* do something doesn't mean it's a good idea to do it—I wrote a fun library to add iterators to DOM elements but would never use it in anger. You might like to research Juriy "kangax" Zaytsev so you can appropriately weight his opinion when considering it with that of Marc K. – RobG Jan 08 '15 at 14:22
  • @RobG It is rather unfair to use "Who use it" clause, to try and prove me wrong! I don't know Zaytsev but I'm sure he is a rather brilliant old chap like your self, and I have no intention to imply that he excellency is wrong, rather as I said before he is right about risk of this type of js'ing but that doesn't mean it's the wrong way to go, it just mean you have to be more careful. Please don't get offended and angry, this is not a personal matter, if i may ask of you and also please give this a read too: https://github.com/nbubna/mind-hacking/blob/gh-pages/extending-the-dom.md, cheers mate – neo Jan 08 '15 at 15:43
  • 1
    I'm not saying your wrong, only that anyone thinking about extending DOM objects should take Juriy's advice into consideration. There are plenty of other well respected javascript developers who think the same way (e.g. jQuery,YUI, ext don't extend DOM objects and Prototype.js was re–written to stop doing that), just that Juriy wrote an excellent article about it. In the context of the OP, maybe it works spectacularly well with certain testing frameworks, I'm willing to be convinced. ;-) – RobG Jan 08 '15 at 23:08
1

Use executeScript() to execute a script that forms a list of attributes reading them from element.attributes (js part inside is taken from here):

var elm = element(by.id('runButton')).getWebElement();
browser.executeScript(
    'var items = {}; \
     for (index = 0; index < arguments[0].attributes.length; ++index) { \
         items[arguments[0].attributes[index].name] = arguments[0].attributes[index].value \
     }; \
     return items;', elm).then(function (attrs) {
        console.log(attrs);
    });

Here attrs would contain a dictionary/object of element attributes with keys as attribute names and values as attribute values.

Demo (using angularjs.org tutorial page, getting all attributes for a header):

$ node node_modules/protractor/bin/elementexplorer.js https://docs.angularjs.org/tutorial
Getting page at: https://docs.angularjs.org/tutorial
> var elm = element(by.tagName('header')).getWebElement();
> browser.executeScript('var items = {}; for (index = 0; index < arguments[0].attributes.length; ++index) { items[arguments[0].attributes[index].name] = arguments[0].attributes[index].value }; return items;', elm).then(function (attrs) {
...     console.log(attrs);
... });
{ class: 'header header-fixed', 'scroll-y-offset-element': '' }

Not really beautiful and compact, but works for me. Would be happy to see better alternatives.


UPDATE (an improvement to the approach above):

It would also work if I would define a regular function and pass it in:

function getAllAttributes (arguments) {
    var items = {}; 
    for (index = 0; index < arguments[0].attributes.length; ++index) { 
        items[arguments[0].attributes[index].name] = arguments[0].attributes[index].value; 
    }
    return items;
}

browser.executeScript(getAllAttributes, elm).then(function (attrs) {
    console.log(attrs);
});
Community
  • 1
  • 1
alecxe
  • 462,703
  • 120
  • 1,088
  • 1,195
  • FYI, to run elementexplorer.js, we need to start selenium server first. `java -jar node_modules/protractor/selenium/selenium-server-standalone-2.44.0.jar -Dwebdriver.chrome.driver=node_modules/protractor/selenium/chromedriver` – allenhwkim Jan 02 '15 at 17:39
  • @allenhwkim yup, thanks for the note. Do you think all element attributes can be retrieved in an easier manner? Thanks. – alecxe Jan 03 '15 at 01:51
  • @alexe no, I could not find better solution that you have. As long as `executeScript` returns deep copy of all recursive properties, it's not easy to do it. I think WebDriver should add a method for this, `getAttributes()` – allenhwkim Jan 04 '15 at 16:28
1

If your attributes that you need are prefixed with data you should be able to use the dataset for the element which will shrink your execute script by a bit:

browser.executeScript('return arguments[0].dataset;', elm).then(function (attrs) {
    console.log(attrs);
});
Mathew Berg
  • 28,625
  • 11
  • 69
  • 90
1

You have to use browser.executeScript() function call instead of protractor API since Element.attributes is out of protractor API implementation:

var elem = element(by.id('runButton'));
browser.executeScript("return arguments[0].attributes", elem.getWebElement())
    .then(function (attrs) {
        console.log(attrs.length);    // outputs numbers of attributes.
        // access collection of Attr objects
        console.log(attrs[0].isId);   // outputs `true`
        console.log(attrs[0].name);   // outputs `id`
        console.log(attrs[0].value);  // outputs `runButton`
    });

Remember that when saying attributes, it means a named map structure instead an array in the context of DOM model. Which means you have to use the NamedNodeMap to access collection of Attr objects.

It works as the same way as that in @alecxe's answer without the iteration part.

shawnzhu
  • 7,233
  • 4
  • 35
  • 51