Alguma sugestão para testar o código extjs em um navegador, de preferência com selênio?

92

Temos usado o selênio com grande sucesso para lidar com testes de sites de alto nível (além de extensos doctests em Python em nível de módulo). No entanto, agora estamos usando extjs para muitas páginas e está sendo difícil incorporar testes Selenium para componentes complexos como grades.

Alguém teve sucesso ao escrever testes automatizados para páginas da web baseadas em extjs? Muitas pesquisas no Google encontram pessoas com problemas semelhantes, mas poucas respostas. Obrigado!

mm2001
fonte
O pessoal da Ext JS teve a gentileza de postar sobre esse assunto em seu blog aqui . Eu espero que isso ajude.
NBRed5 de

Respostas:

173

O maior obstáculo em testar ExtJS com Selenium é que ExtJS não renderiza elementos HTML padrão e o Selenium IDE irá ingenuamente (e corretamente) gerar comandos direcionados a elementos que atuam apenas como decoração - elementos supérfluos que ajudam ExtJS com toda a área de trabalho- aparência e sensação. Aqui estão algumas dicas e truques que reuni enquanto escrevia um teste Selenium automatizado em um aplicativo ExtJS.

Dicas Gerais

Localizando Elementos

Ao gerar casos de teste Selenium gravando as ações do usuário com Selenium IDE no Firefox, o Selenium baseará as ações registradas nos ids dos elementos HTML. No entanto, para a maioria dos elementos clicáveis, ExtJS usa ids gerados como "ext-gen-345" que provavelmente mudarão em uma visita subsequente à mesma página, mesmo se nenhuma alteração de código tiver sido feita. Depois de registrar as ações do usuário para um teste, é necessário um esforço manual para passar por todas as ações que dependem dos IDs gerados e substituí-los. Existem dois tipos de substituições que podem ser feitas:

Substituindo um Localizador de Id por um Localizador CSS ou XPath

Os localizadores CSS começam com "css =" e os localizadores XPath começam com "//" (o prefixo "xpath =" é opcional). Os localizadores CSS são menos detalhados e mais fáceis de ler e devem ser preferidos aos localizadores XPath. No entanto, pode haver casos em que os localizadores XPath precisam ser usados ​​porque um localizador CSS simplesmente não pode cortá-lo.

Executando JavaScript

Alguns elementos requerem mais do que simples interações mouse / teclado devido à complexa renderização realizada pelo ExtJS. Por exemplo, um Ext.form.CombBox não é realmente um <select>elemento, mas uma entrada de texto com uma lista suspensa separada que está em algum lugar na parte inferior da árvore do documento. Para simular adequadamente uma seleção de ComboBox, é possível primeiro simular um clique na seta suspensa e depois clicar na lista que aparece. No entanto, localizar esses elementos por meio de localizadores CSS ou XPath pode ser complicado. Uma alternativa é localizar o próprio componente ComoBox e chamar métodos nele para simular a seleção:

var combo = Ext.getCmp('genderComboBox'); // returns the ComboBox components
combo.setValue('female'); // set the value
combo.fireEvent('select'); // because setValue() doesn't trigger the event

No Selenium, o runScriptcomando pode ser usado para realizar a operação acima de uma forma mais concisa:

with (Ext.getCmp('genderComboBox')) { setValue('female'); fireEvent('select'); }

Lidando com AJAX e renderização lenta

Selenium tem sabores "* AndWait" para todos os comandos para aguardar o carregamento da página quando uma ação do usuário resulta em transições de página ou recarregamentos. No entanto, como as buscas AJAX não envolvem carregamentos de página reais, esses comandos não podem ser usados ​​para sincronização. A solução é fazer uso de dicas visuais como a presença / ausência de um indicador de progresso AJAX ou a aparência de linhas em uma grade, componentes adicionais, links etc. Por exemplo:

Command: waitForElementNotPresent
Target: css=div:contains('Loading...')

Às vezes, um elemento aparecerá somente após um determinado período de tempo, dependendo da rapidez com que ExtJS renderiza os componentes após uma ação do usuário resultar em uma mudança de visualização. Em vez de usar atrasos arbitrários com o pausecomando, o método ideal é esperar até que o elemento de interesse esteja ao nosso alcance. Por exemplo, para clicar em um item depois de esperar que ele apareça:

Command: waitForElementPresent
Target: css=span:contains('Do the funky thing')
Command: click
Target: css=span:contains('Do the funky thing')

Depender de pausas arbitrárias não é uma boa ideia, pois as diferenças de tempo que resultam da execução dos testes em navegadores diferentes ou em máquinas diferentes tornarão os casos de teste instáveis.

Itens não clicáveis

Alguns elementos não podem ser acionados pelo clickcomando. É porque o ouvinte de evento está realmente no contêiner, observando os eventos do mouse em seus elementos filho, que eventualmente borbulham para o pai. O controle da guia é um exemplo. Para clicar em uma guia, você deve simular um mouseDownevento no rótulo da guia:

Command: mouseDownAt
Target: css=.x-tab-strip-text:contains('Options')
Value: 0,0

Validação de Campo

Os campos do formulário (componentes Ext.form. *) Que possuem expressões regulares ou vtypes associados para validação irão acionar a validação com um certo atraso (veja a validationDelaypropriedade que é definida como 250ms por padrão), após o usuário inserir o texto ou imediatamente quando o campo perder foco - ou desfoca (veja a validateOnDelaypropriedade). Para acionar a validação de campo após emitir o comando type Selenium para inserir algum texto dentro de um campo, você deve fazer o seguinte:

  • Validação com atraso de disparo

    ExtJS dispara o cronômetro de atraso de validação quando o campo recebe eventos de ativação. Para acionar esse temporizador, simplesmente emita um evento de keyup fictício (não importa qual tecla você usa, pois ExtJS o ignora), seguido por uma pequena pausa que é mais longa do que o validationDelay:

    Command: keyUp
    Target: someTextArea
    Value: x
    Command: pause
    Target: 500
  • Validação Imediata de Gatilho

    Você pode injetar um evento de desfoque no campo para acionar a validação imediata:

    Command: runScript
    Target: someComponent.nameTextField.fireEvent("blur")

Verificando os resultados da validação

Após a validação, você pode verificar a presença ou ausência de um campo de erro:

Command: verifyElementNotPresent   
Target: //*[@id="nameTextField"]/../*[@class="x-form-invalid-msg" and not(contains(@style, "display: none"))]

Command: verifyElementPresent   
Target: //*[@id="nameTextField"]/../*[@class="x-form-invalid-msg" and not(contains(@style, "display: none"))]

Observe que a verificação "display: none" é necessária porque uma vez que um campo de erro é mostrado e precisa ser escondido, ExtJS irá simplesmente ocultar o campo de erro em vez de removê-lo totalmente da árvore DOM.

Dicas para elementos específicos

Clicar em um Ext.form.Button

  • Opção 1

    Comando: clique em Destino: css = botão: contém ('Salvar')

    Seleciona o botão pela legenda

  • opção 2

    Comando: clique em Destino: css = # botão salvar-opções

    Seleciona o botão por seu id

Selecionando um valor de um Ext.form.ComboBox

Command: runScript
Target: with (Ext.getCmp('genderComboBox')) { setValue('female'); fireEvent('select'); }

Primeiro define o valor e, em seguida, dispara explicitamente o evento select caso haja observadores.

Ates Goral
fonte
3
Aqui estão algumas dicas do manual: rkapse.blogspot.nl/2009/06/selenium-ide-issues-with-extjs.html
dan-klasson
5

Esse blog me ajudou muito. Ele escreveu muito sobre o assunto e parece que ainda está ativo. O cara também parece apreciar um bom design.

Ele basicamente fala sobre como enviar javascript para fazer consultas e usar o método Ext.ComponentQuery.query para recuperar coisas da mesma maneira que você faz em seu aplicativo ext internamente. Dessa forma, você pode usar xtypes e itemIds e não precisa se preocupar em tentar analisar qualquer uma das coisas malucas geradas automaticamente.

Achei este artigo em particular muito útil.

Posso postar algo um pouco mais detalhado aqui em breve - ainda estou tentando entender como fazer isso corretamente

JonnyRaa
fonte
4

Tenho testado meu aplicativo da web ExtJs com selênio. Um dos maiores problemas era selecionar um item na grade para fazer algo com ele.

Para isso, escrevi o método auxiliar (na classe SeleniumExtJsUtils, que é uma coleção de métodos úteis para facilitar a interação com ExtJs):

/**
 * Javascript needed to execute in order to select row in the grid
 * 
 * @param gridId Grid id
 * @param rowIndex Index of the row to select
 * @return Javascript to select row
 */
public static String selectGridRow(String gridId, int rowIndex) {
    return "Ext.getCmp('" + gridId + "').getSelectionModel().selectRow(" + rowIndex + ", true)";
}

e quando eu precisasse selecionar uma linha, eu apenas chamaria:

selenium.runScript( SeleniumExtJsUtils.selectGridRow("<myGridId>", 5) );

Para que isso funcione, preciso definir meu id na grade e não deixar ExtJs gerar o seu próprio.

Marko Dumic
fonte
você deve ser capaz de encontrar a grade usando seu xtype em Ext.ComponentQuery.query (assumindo que você estendeu da grade e definiu o xtype para sua própria grade) Eu coloquei algumas coisas sobre isso mais abaixo. Também descobri que você pode clicar nas coisas usando xpath para encontrar o td que contém um valor de identificação para uma linha
JonnyRaa
4

Para detectar que o elemento está visível, você usa a cláusula: not(contains(@style, "display: none")

É melhor usar isto:

visible_clause = "not(ancestor::*[contains(@style,'display: none')" +
    " or contains(@style, 'visibility: hidden') " + 
    " or contains(@class,'x-hide-display')])"

hidden_clause = "parent::*[contains(@style,'display: none')" + 
    " or contains(@style, 'visibility: hidden')" + 
    " or contains(@class,'x-hide-display')]"
Master-Test
fonte
3

Você pode fornecer mais informações sobre os tipos de problemas que está tendo com o teste extjs?

Uma extensão do Selenium que considero útil é waitForCondition . Se o seu problema parece ser com os eventos Ajax, você pode usar waitForCondition para esperar que os eventos aconteçam.

Ryan Convidado
fonte
3

As páginas da web Ext JS podem ser complicadas de testar, por causa do HTML complicado que acabam gerando como nas grades Ext JS.

O robô HTML5 lida com isso usando uma série de práticas recomendadas para pesquisar e interagir com segurança com componentes com base em atributos e condições que não são dinâmicos. Em seguida, ele fornece atalhos para fazer isso com todos os componentes HTML, Ext JS e Sencha Touch com os quais você precisaria interagir. Ele vem em 2 sabores:

  1. Java - API familiar baseada em Selenium e JUnit que possui suporte a driver da web integrado para todos os navegadores modernos.
  2. Gwen - Uma linguagem de estilo humano para criar e manter testes de navegador de forma rápida e fácil, que vem com seu próprio ambiente de desenvolvimento integrado. Tudo baseado na API Java.

Por exemplo, se você deseja encontrar a linha da grade Ext JS contendo o texto "Foo", pode fazer o seguinte em Java:

findExtJsGridRow("Foo");

... e você poderia fazer o seguinte em Gwen:

extjsgridrow by text "Foo"

Há muita documentação para Java e Gwen sobre como trabalhar com componentes específicos do Ext JS. A documentação também detalha o HTML resultante para todos esses componentes Ext JS, que também podem ser úteis.

John V
fonte
2

Dicas úteis para buscar grade via Id de grade na página: Acho que você pode estender a função mais útil desta API.

   sub get_grid_row {
        my ($browser, $grid, $row)  = @_;


        my $script = "var doc = this.browserbot.getCurrentWindow().document;\n" .
            "var grid = doc.getElementById('$grid');\n" .
            "var table = grid.getElementsByTagName('table');\n" .
            "var result = '';\n" .
            "var row = 0;\n" . 
            "for (var i = 0; i < table.length; i++) {\n" .
            "   if (table[i].className == 'x-grid3-row-table') {\n".
            "       row++;\n" . 
            "       if (row == $row) {\n" .
            "           var cols_len = table[i].rows[0].cells.length;\n" .
            "           for (var j = 0; j < cols_len; j++) {\n" .
            "               var cell = table[i].rows[0].cells[j];\n" .
            "               if (result.length == 0) {\n" .
            "                   result = getText(cell);\n" .
            "               } else { \n" .
            "                   result += '|' + getText(cell);\n" .
            "               }\n" .
            "           }\n" .
            "       }\n" .
            "   }\n" .
            "}\n" .
            "result;\n";

        my $result = $browser->get_eval($script);
        my @res = split('\|', $result);
        return @res;
    }

fonte
2

Teste mais fácil por meio de atributos de dados HTML personalizados

Da documentação do Sencha :

Um itemId pode ser usado como uma forma alternativa de obter uma referência a um componente quando nenhuma referência de objeto estiver disponível. Em vez de usar um id com Ext.getCmp, use itemId com Ext.container.Container.getComponent que irá recuperar itemId's ou id's. Como os itemId's são um índice para o MixedCollection interno do contêiner, o itemId tem como escopo local o contêiner - evitando conflitos potenciais com Ext.ComponentManager que requer um id único.

Substituindo o Ext.AbstractComponent's onBoxReadymétodo, eu definir um atributo de dados personalizado (cujo nome vem do meu costume testIdAttrpropriedade de cada componente) para o componente itemIdde valor, se ele existir. Adicione a Testing.overrides.AbstractComponentclasse ao array application.jsdo seu arquivo requires.

/**
 * Overrides the Ext.AbstracComponent's onBoxReady
 * method to add custom data attributes to the
 * component's dom structure.
 *
 * @author Brian Wendt
 */
Ext.define('Testing.overrides.AbstractComponent', {
  override: 'Ext.AbstractComponent',


  onBoxReady: function () {
    var me = this,
      el = me.getEl();


    if (el && el.dom && me.itemId) {
      el.dom.setAttribute(me.testIdAttr || 'data-selenium-id', me.itemId);
    }


    me.callOverridden(arguments);
  }
});

Este método fornece aos desenvolvedores uma maneira de reutilizar um identificador descritivo em seu código e ter esses identificadores disponíveis cada vez que a página é renderizada. Chega de pesquisar ids não descritivos e gerados dinamicamente.

Brian Wendt
fonte
2

Estamos desenvolvendo uma estrutura de teste que usa selênio e encontramos problemas com extjs (já que é a renderização do lado do cliente). Acho útil procurar um elemento assim que o DOM estiver pronto.

public static boolean waitUntilDOMIsReady(WebDriver driver) {
    def maxSeconds = DEFAULT_WAIT_SECONDS * 10
    for (count in 1..maxSeconds) {
        Thread.sleep(100)
        def ready = isDOMReady(driver);
        if (ready) {
            break;
        }
    }
}

public static boolean isDOMReady(WebDriver driver){
    return driver.executeScript("return document.readyState");
}
Bertanasco
fonte
1

Para UI complexa que não é HTML formal, xPath é sempre algo com que você pode contar, mas um pouco complexo quando se trata de implementação de UI diferente usando ExtJs.

Você pode usar Firebug e Firexpath como extensões do firefox para testar o xpath de um determinado elemento e simplesmente passar o xpath completo como parâmetro para o selênio.

Por exemplo em código java:

String fullXpath = "xpath=//div[@id='mainDiv']//div[contains(@class,'x-grid-row')]//table/tbody/tr[1]/td[1]//button"

selenium.click(fullXpath);
Chun
fonte
1

Quando eu estava testando o aplicativo ExtJS usando o WebDriver, usei a próxima abordagem: procurei o texto do campo por rótulo e obtive o @foratributo do rótulo. Por exemplo, temos um rótulo

<label id="dynamic_id_label" class="TextboxLabel" for="textField_which_I_am_lloking_for">
Name Of Needed Label
<label/>

E precisamos apontar algumas informações para o WebDriver: //input[@id=(//label[contains(text(),'Name Of Needed Label')]/@for)] .

Então, ele pegará o id do @foratributo e o usará posteriormente. Este é provavelmente o caso mais simples, mas fornece a maneira de localizar o elemento. É muito mais difícil quando você não tem rótulo, mas então você precisa encontrar algum elemento e escrever seu xpath procurando por irmãos, elementos descendentes / ascendentes.

olyv
fonte