How to test if an element has class using Protractor?

AngularjsProtractor

Angularjs Problem Overview


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
    });
});

Angularjs Solutions


Solution 1 - Angularjs

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 2 - Angularjs

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.

Solution 3 - Angularjs

Simplest is:

expect(element.getAttribute('class')).toContain("active");

Solution 4 - Angularjs

Have you tried this?

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

or a variation of the above...

Solution 5 - Angularjs

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'

Solution 6 - Angularjs

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.

Solution 7 - Angularjs

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

Solution 8 - Angularjs

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');
});

Solution 9 - Angularjs

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);
};

Solution 10 - Angularjs

You can use the CSS parser to handle this by checking if an element with the given class exists:

expect(element(by.css('.form[name="getoffer"].ngDirty')).isPresent()).toBe(true);

Solution 11 - Angularjs

Essentially, you're solving a few problems:

  1. how to get class. class is an html attribute and thus can be retrieved by this command (await is the recommended way these days)
let class = await element.getAttribute('class')
  1. Once you got the value of a class, you want to assert it
// for exact value
expect(class).toBe("active");

// for partial match
expect(class).toContain("active");
// or
expect(class.includes("active")).toBe(true);

// BUT, keep in mind
expect('male').toContain('male');
expect('female').toContain('male');
// BOTH pass

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAllan TatterView Question on Stackoverflow
Solution 1 - AngularjsSergey KView Answer on Stackoverflow
Solution 2 - Angularjsryan.lView Answer on Stackoverflow
Solution 3 - AngularjsFidel GonzoView Answer on Stackoverflow
Solution 4 - AngularjsJames DykesView Answer on Stackoverflow
Solution 5 - AngularjsEd_View Answer on Stackoverflow
Solution 6 - AngularjsMichielView Answer on Stackoverflow
Solution 7 - AngularjsSir NeumanView Answer on Stackoverflow
Solution 8 - AngularjsLeo GallucciView Answer on Stackoverflow
Solution 9 - AngularjsChanthuView Answer on Stackoverflow
Solution 10 - AngularjssplintorView Answer on Stackoverflow
Solution 11 - AngularjsSergey PleshakovView Answer on Stackoverflow