How to test if an element has class using Protractor?

Problem

I'm trying out Protractor to e2e test Angular app and haven't figured out how to detect if an element has a specific class or not.

In my case, the test clicks on submit button and now I want to know if form[name="getoffer"] has class .ngDirty. What may be the solutions?

describe('Contact form', function() {
    beforeEach(function(){
        browser.get('http://localhost:9000');
        element(by.linkText('Contact me')).click();
    });

    it('should fail form validation, all fields pristine', function() {
        element(by.css('.form[name="getoffer"] input[type="submit"]')).click();
        expect(element(by.name('getoffer'))).toHaveClass('ngDirty'); // <-- This line
    });
});
Problem courtesy of: Allan Tatter

Solution

One gotcha you have to look out for with using toMatch(), as in the accepted answer, is partial matches. For instance, let's say you have an element that might have the classes correct and incorrect, and you want to test that it has the class correct. If you were to use expect(element.getAttribute('class')).toMatch('correct'), that will return true even if the element has the incorrect class.

My suggestion:

If you want to only accept exact matches, you can create a helper method for it:

var hasClass = function (element, cls) {
    return element.getAttribute('class').then(function (classes) {
        return classes.split(' ').indexOf(cls) !== -1;
    });
};

You can use it like this (taking advantage of the fact that expect automatically resolves promises in Protractor):

expect(hasClass(element(by.name('getoffer')), 'ngDirty')).toBe(true);
Solution courtesy of: Sergey K

Discussion

I made this matcher, I had to wrap it in a promise and use 2 returns

this.addMatchers({
    toHaveClass: function(a) {
        return this.actual.getAttribute('class').then(function(cls){
            var patt = new RegExp('(^|\\s)' + a + '(\\s|$)');
            return patt.test(cls);
        });
    }
});

in my test i can now do stuf like this:

   var myDivs = element.all(by.css('div.myClass'));
   expect(myDivs.count()).toBe(3);

   // test for class
   expect(myDivs.get(0)).not.toHaveClass('active');

this also works when an element has multiple classes or when an element has no class attribute at all.

Discussion courtesy of: Michiel

If you're using Protractor with Jasmine, you could use toMatch to match as a regular expression...

expect(element(by.name('getoffer')).getAttribute('class')).toMatch('ngDirty');

Also, note that toContain will match list items, if you need that.

Discussion courtesy of: ryan.l

One way to achieve this would be to use xpath and use contains()

Example:

var expectElementToHaveClass = function (className) {
    var path = by.xpath("//div[contains(@class,'"+ className +"')]");
    expect(element.all(path).count()).to.eventually.be.eq(1);
};
Discussion courtesy of: Chanthu
function checkHasClass (selector, class_name) {
    // custom function returns true/false depending if selector has class name

    // split classes for selector into a list
    return $(selector).getAttribute('class').then(function(classes){
        var classes = classes.split(' ');
        if (classes.indexOf(class_name) > -1) return true;
        return false;
    });
}

This is how I do it at least, without the need to use the expect function. This function simply returns true if the class is inside the element and false if not. This also uses promises so you would use it like:

checkHasClass('#your-element', 'your-class').then(function(class_found){
    if (class_found) console.log("Your element has that class");
});

Edit: I just realized this is essentially the same as the top answer

Discussion courtesy of: Sir Neuman

Based on the answer from Sergey K, you could also add a custom matcher to do this:

(coffeescript)

  beforeEach(()->
    this.addMatchers({
      toHaveClass: (expected)->
        @message = ()->
          "Expected #{@actual.locator_.value} to have class '#{expected}'"

        @actual.getAttribute('class').then((classes)->
          classes.split(' ').indexOf(expected) isnt -1
        )
    })
  )

Then you can use it in tests like this:

expect($('div#ugly')).toHaveClass('beautiful')

And you'll get the following error if it doesn't:

 Message:
   Expected div#ugly to have class beautiful
 Stacktrace:
   Error: Expected div#ugly to have class 'beautiful'
Discussion courtesy of: Ed Hinchliffe

Have you tried...

var el = element(by.name('getoffer'));
expect(e.getAttribute('class')).toBe('ngDirty')

or a variation of the above...

Discussion courtesy of: James Dykes

Simplest is:

expect(element.getAttribute('class')).toContain("active");
Discussion courtesy of: Fidel Gonzo

Here a Jasmine 1.3.x custom toHaveClass matcher with negation .not support plus wait up to 5 seconds (or whatever you specify).

Find the full custom matcher to be added on your onPrepare block in this gist

Sample usage:

it('test the class finder custom matcher', function() {
    // These guys should pass OK given your user input
    // element starts with an ng-invalid class:
    expect($('#user_name')).toHaveClass('ng-invalid');
    expect($('#user_name')).not.toHaveClass('ZZZ');
    expect($('#user_name')).toNotHaveClass('ZZZ');
    expect($('#user_name')).not.toNotHaveClass('ng-invalid');
    // These guys should each fail:
    expect($('#user_name')).toHaveClass('ZZZ');
    expect($('#user_name')).not.toHaveClass('ng-invalid');
    expect($('#user_name')).toNotHaveClass('ng-invalid');
    expect($('#user_name')).not.toNotHaveClass('ZZZ');
});
Discussion courtesy of: Leo Gallucci

This recipe can be found in it's original form on Stack Over Flow.