Como testar se um elemento tem classe usando Protractor?

90

Estou testando o Protractor para testar o aplicativo Angular do e2e e não descobri como detectar se um elemento tem uma classe específica ou não.

No meu caso, o teste clica no botão enviar e agora quero saber se form [name = "getoffer"] tem a classe .ngDirty. Quais podem ser as soluções?

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
    });
});
Allan Tatter
fonte

Respostas:

110

Uma pegadinha que você deve procurar usar toMatch(), como na resposta aceita, são as correspondências parciais. Por exemplo, digamos que você tenha um elemento que pode ter as classes correcte incorrect, e deseja testar se ele tem a classe correct. Se você fosse usar expect(element.getAttribute('class')).toMatch('correct'), isso retornará verdadeiro mesmo se o elemento tiver a incorrectclasse.

Minha sugestão:

Se você deseja aceitar apenas correspondências exatas, pode criar um método auxiliar para isso:

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

Você pode usá-lo assim (aproveitando o fato de que expectresolve automaticamente as promessas no Protractor):

expect(hasClass(element(by.name('getoffer')), 'ngDirty')).toBe(true);
Sergey K
fonte
1
Isso funcionou para mim, com o mod que o nome da classe tinha que ser formato de hífen e não caixa de camelo, ou sejaexpect(hasClass(element(by.name('getoffer')), 'ng-dirty')).toBe(true);
Ackroydd
Não estou certo sobre o que a função tem classe está fazendo, particularmente onde ou quais classes estão ao ser passada para a função. Então, alguém poderia me esclarecer?
ErikAGriffin
@ErikAGriffin: A função hasClass recebe dois argumentos - um elemento html e o nome de uma classe a ser verificada. A função então obtém o atributo "class" (as classes) que o elemento possui. Ele divide a string de classe para obter uma matriz de classes. Em seguida, verifica se a classe que você está procurando está em algum índice positivo da matriz. Presumo que seja uma forma de verificar a existência.
VSO de
Reescrevi sua função de quatro linhas neste sexy ES6 de uma linha: hasClass = (element, className) => element.getAttribute ('class'). Then ((classes) => classes.split ('') .indexOf ( className)! == -1);
Laurens Mäkel,
Em TypeScript: função assíncrona hasClass (elm: ElementFinder, className: string): Promise <boolean> {const classNamesStr: string = await elm.getAttribute ('class'); const classNamesArr: string [] = classNamesStr.split (''); return classNamesArr.includes (className); }
tedw
56

Se você estiver usando o Transferidor com Jasmine, poderá usar toMatchpara corresponder como uma expressão regular ...

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

Além disso, observe que toContaincorresponderá aos itens da lista, se necessário.

ryan.l
fonte
1
Não tenho certeza se essa é a maneira ideal de fazer isso, mas pelo menos funciona como esperado :)
Allan Tatter
1
Não, eu diria que provavelmente não. Eu esperaria elementretornar um objeto com um hasClassmétodo, que você poderia envolver dentro de uma expectchamada ...
ryan.l
2
toContainpode ser uma escolha melhor do que toMatchneste caso.
TrueWill de
Se você quiser se proteger contra partidas parciais, tente algo como /(^|\s)ngDirty($|\s)/.
Andrew Myers
você provavelmente pode usar .not.toContainpara verificar se não tem a classe em particular ou .toContainpara verificar se ela existe
Jack
18

O mais simples é:

expect(element.getAttribute('class')).toContain("active");
Fidel Gonzo
fonte
Isso retorna um erro java.util.ArrayList cannot be cast to java.lang.Stringno Microsoft Edge
Manolis
@Manolis como você está obtendo exceções de java no console da web para angularjs ??
vasia
4

Com base na resposta de Sergey K, você também pode adicionar um matcher personalizado para fazer isso:

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

Então você pode usá-lo em testes como este:

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

E você obterá o seguinte erro se isso não acontecer:

 Message:
   Expected div#ugly to have class beautiful
 Stacktrace:
   Error: Expected div#ugly to have class 'beautiful'
Ed Hinchliffe
fonte
3

Você tentou...

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

ou uma variação do acima ...

James Dykes
fonte
4
O problema é que o formulário tem mais de uma classe associada.
Allan Tatter
2

Fiz este matcher, tive que embrulhar em uma promessa e usar 2 retornos

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

em meu teste agora posso fazer coisas como esta:

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

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

isso também funciona quando um elemento tem várias classes ou quando um elemento não tem nenhum atributo de classe.

Michiel
fonte
1

Aqui, um toHaveClassmatcher personalizado Jasmine 1.3.x com .notsuporte de negação e aguarde até 5 segundos (ou o que você especificar).

Encontre o matcher personalizado completo a ser adicionado ao seu bloco onPrepare nesta essência

Uso de amostra:

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');
});
Leo Gallucci
fonte
1
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;
    });
}

É assim que faço pelo menos, sem a necessidade de usar a função expect. Esta função simplesmente retorna true se a classe estiver dentro do elemento e false se não estiver. Isso também usa promessas, então você pode usá-lo como:

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

Edit: acabei de perceber que isso é essencialmente o mesmo que a resposta principal

Sir Neuman
fonte
1

Uma maneira de conseguir isso seria usar xpath e usar contains()

Exemplo:

var expectElementToHaveClass = function (className) {
    var path = by.xpath("//div[contains(@class,'"+ className +"')]");
    expect(element.all(path).count()).to.eventually.be.eq(1);
};
Chanthu
fonte
0

Você pode usar o analisador CSS para lidar com isso, verificando se existe um elemento com a classe fornecida:

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