Qualche suggerimento per testare il codice extjs in un browser, preferibilmente con selenio?


92

Abbiamo utilizzato il selenio con grande successo per gestire test di siti Web di alto livello (oltre a numerosi doctest Python a livello di modulo). Tuttavia ora stiamo usando extjs per molte pagine e si sta dimostrando difficile incorporare i test di selenio per i componenti complessi come le griglie.

Qualcuno ha avuto successo nella scrittura di test automatizzati per pagine web basate su extjs? Un sacco di googling trova persone con problemi simili, ma poche risposte. Grazie!


Le brave persone di Ext JS sono state così gentili da postare su questo argomento sul loro blog qui . Spero che aiuti.
NBRed5

Risposte:


173

Il più grande ostacolo nel testare ExtJS con Selenium è che ExtJS non esegue il rendering di elementi HTML standard e l'IDE Selenium genererà ingenuamente (e giustamente) comandi mirati a elementi che agiscono solo come decorazioni - elementi superflui che aiutano ExtJS con l'intero desktop- guardare e sentire. Ecco alcuni suggerimenti e trucchi che ho raccolto durante la scrittura del test automatico del selenio su un'app ExtJS.

Suggerimenti generali

Individuazione degli elementi

Quando si generano casi di test Selenium registrando le azioni dell'utente con Selenium IDE su Firefox, Selenium baserà le azioni registrate sugli ID degli elementi HTML. Tuttavia, per la maggior parte degli elementi cliccabili, ExtJS utilizza ID generati come "ext-gen-345" che probabilmente cambieranno in una visita successiva alla stessa pagina, anche se non sono state apportate modifiche al codice. Dopo aver registrato le azioni dell'utente per un test, è necessario uno sforzo manuale per eseguire tutte queste azioni che dipendono dagli ID generati e per sostituirli. Esistono due tipi di sostituzioni che possono essere effettuate:

Sostituzione di un localizzatore di ID con un localizzatore di CSS o XPath

I localizzatori CSS iniziano con "css =" e i localizzatori XPath iniziano con "//" (il prefisso "xpath =" è facoltativo). I localizzatori CSS sono meno dettagliati e più facili da leggere e dovrebbero essere preferiti rispetto ai localizzatori XPath. Tuttavia, ci possono essere casi in cui è necessario utilizzare i localizzatori XPath perché un localizzatore CSS semplicemente non può tagliarlo.

Esecuzione di JavaScript

Alcuni elementi richiedono più delle semplici interazioni mouse / tastiera a causa del complesso rendering eseguito da ExtJS. Ad esempio, un Ext.form.CombBox non è realmente un <select>elemento ma un input di testo con un elenco a discesa separato che si trova da qualche parte nella parte inferiore dell'albero del documento. Per simulare correttamente una selezione ComboBox, è possibile simulare prima un clic sulla freccia del menu a tendina e poi fare clic sull'elenco che compare. Tuttavia, localizzare questi elementi tramite CSS o XPath locator può essere complicato. Un'alternativa è individuare il componente ComoBox stesso e chiamare i metodi su di esso per simulare la selezione:

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

In Selenium il runScriptcomando può essere utilizzato per eseguire l'operazione di cui sopra in una forma più concisa:

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

Affrontare AJAX e rendering lento

Selenium ha versioni "* AndWait" per tutti i comandi per l'attesa del caricamento della pagina quando un'azione dell'utente si traduce in transizioni o ricariche della pagina. Tuttavia, poiché i recuperi AJAX non comportano caricamenti di pagine effettivi, questi comandi non possono essere utilizzati per la sincronizzazione. La soluzione consiste nell'utilizzare indizi visivi come la presenza / assenza di un indicatore di avanzamento AJAX o l'aspetto di righe in una griglia, componenti aggiuntivi, collegamenti ecc. Ad esempio:

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

A volte un elemento apparirà solo dopo un certo periodo di tempo, a seconda della velocità con cui ExtJS esegue il rendering dei componenti dopo che un'azione dell'utente si traduce in una modifica della vista. Invece di usare ritardi arbitrari con il pausecomando, il metodo ideale è quella di aspettare fino a quando l'elemento di interesse viene alla nostra portata. Ad esempio, per fare clic su un elemento dopo aver atteso che venga visualizzato:

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

Affidarsi a pause arbitrarie non è una buona idea poiché le differenze di temporizzazione che derivano dall'esecuzione dei test in browser diversi o su macchine diverse renderanno i casi di test instabili.

Elementi non cliccabili

Alcuni elementi non possono essere attivati ​​dal clickcomando. È perché il listener di eventi si trova effettivamente sul contenitore, a guardare gli eventi del mouse sui suoi elementi figli, che alla fine si espandono fino al genitore. Il controllo struttura a schede è un esempio. Per fare clic su una scheda, è necessario simulare un mouseDownevento sull'etichetta della scheda:

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

Validazione sul campo

I campi modulo (componenti Ext.form. *) Che hanno associato espressioni regolari o tipi v per la convalida attiveranno la convalida con un certo ritardo (vedere la validationDelayproprietà che è impostata su 250 ms per impostazione predefinita), dopo che l'utente ha inserito il testo o immediatamente quando il campo perde focus - o sfoca (vedere la validateOnDelayproprietà). Per attivare la convalida del campo dopo aver emesso il comando di tipo Selenium per inserire del testo all'interno di un campo, è necessario eseguire una delle seguenti operazioni:

  • Attivazione della convalida ritardata

    ExtJS attiva il timer del ritardo di convalida quando il campo riceve eventi di keyup. Per attivare questo timer, è sufficiente emettere un evento keyup fittizio (non importa quale chiave usi poiché ExtJS lo ignora), seguito da una breve pausa che è più lunga del validationDelay:

    Command: keyUp
    Target: someTextArea
    Value: x
    Command: pause
    Target: 500
  • Attivazione della convalida immediata

    Puoi iniettare un evento di sfocatura nel campo per attivare la convalida immediata:

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

Verifica dei risultati della convalida

Dopo la convalida, è possibile verificare la presenza o l'assenza di un campo di errore:

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"))]

Notare che il controllo "display: none" è necessario perché una volta che un campo di errore è mostrato e quindi deve essere nascosto, ExtJS nasconderà semplicemente il campo di errore invece di rimuoverlo completamente dall'albero DOM.

Suggerimenti specifici per elemento

Fare clic su un pulsante Ext. Form

  • opzione 1

    Comando: click Target: css = button: contains ('Save')

    Seleziona il pulsante in base alla sua didascalia

  • opzione 2

    Comando: fai clic su Target: css = # pulsante save-options

    Seleziona il pulsante in base al suo ID

Selezione di un valore da un Ext.form.ComboBox

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

Per prima cosa imposta il valore e quindi attiva in modo esplicito l'evento select nel caso in cui siano presenti osservatori.



5

Questo blog mi ha aiutato molto. Ha scritto parecchio sull'argomento e sembra che sia ancora attivo. Il ragazzo sembra anche apprezzare il buon design.

Fondamentalmente parla dell'utilizzo dell'invio di javascript per eseguire query e dell'utilizzo del metodo Ext.ComponentQuery.query per recuperare cose nello stesso modo in cui si fa internamente nell'app ext. In questo modo puoi usare xtypes e itemIds e non devi preoccuparti di provare ad analizzare nessuna delle cose pazze generate automaticamente.

Ho trovato questo articolo in particolare molto utile.

Potrei postare qualcosa di un po 'più dettagliato qui presto - sto ancora cercando di capire come farlo correttamente


4

Ho testato la mia applicazione web ExtJs con selenio. Uno dei problemi più grandi è stato selezionare un elemento nella griglia per fare qualcosa con esso.

Per questo, ho scritto il metodo helper (nella classe SeleniumExtJsUtils che è una raccolta di metodi utili per una più facile interazione con 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 avevo bisogno di selezionare una riga, chiamavo semplicemente:

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

Perché funzioni, devo impostare il mio ID sulla griglia e non lasciare che ExtJs generi il proprio.


dovresti essere in grado di trovare la griglia usando il suo xtype in Ext.ComponentQuery.query (supponendo che tu abbia esteso dalla griglia e definito l'xtype per la tua griglia) Ho messo alcune cose su questo più in basso. Ho anche scoperto che puoi fare clic su qualcosa usando xpath per trovare il td che contiene un valore identificativo per una riga
JonnyRaa

4

Per rilevare che l'elemento è visibile si utilizza la clausola: not(contains(@style, "display: none")

È meglio usare questo:

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')]"

3

Puoi fornire maggiori informazioni sui tipi di problemi che stai riscontrando con i test extjs?

Un'estensione Selenium che trovo utile è waitForCondition . Se il tuo problema sembra essere un problema con gli eventi Ajax, puoi usare waitForCondition per aspettare che si verifichino eventi.


3

Le pagine web Ext JS possono essere difficili da testare, a causa del complicato HTML che finiscono per generare come con le griglie Ext JS.

HTML5 Robot si occupa di questo utilizzando una serie di best practice su come cercare e interagire in modo affidabile con i componenti in base ad attributi e condizioni che non sono dinamici. Fornisce quindi scorciatoie per farlo con tutti i componenti HTML, Ext JS e Sencha Touch con cui dovresti interagire. È disponibile in 2 gusti:

  1. Java - Familiarità API basata su Selenium e JUnit con supporto per driver web integrato per tutti i browser moderni.
  2. Gwen - Un linguaggio in stile umano per creare e mantenere rapidamente e facilmente i test del browser, che viene fornito con il proprio ambiente di sviluppo integrato. Tutto si basa sull'API Java.

Ad esempio, se desideri trovare la riga della griglia Ext JS contenente il testo "Foo", puoi eseguire le seguenti operazioni in Java:

findExtJsGridRow("Foo");

... e potresti fare quanto segue in Gwen:

extjsgridrow by text "Foo"

C'è molta documentazione sia per Java che per Gwen su come lavorare con componenti specifici di Ext JS. La documentazione descrive anche il codice HTML risultante per tutti questi componenti Ext JS, che potresti trovare utile.


2

Suggerimenti utili per recuperare la griglia tramite l'ID della griglia sulla pagina: Penso che tu possa estendere una funzione più utile da questa 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;
    }

2

Test più semplici tramite attributi di dati HTML personalizzati

Dalla documentazione di Sencha :

Un itemId può essere utilizzato come metodo alternativo per ottenere un riferimento a un componente quando non è disponibile alcun riferimento all'oggetto. Invece di usare un id con Ext.getCmp, usa itemId con Ext.container.Container.getComponent che recupererà gli itemId o gli id. Poiché gli itemId sono un indice della MixedCollection interna del contenitore, itemId ha l'ambito locale del contenitore, evitando potenziali conflitti con Ext.ComponentManager che richiede un ID univoco.

Override del Ext.AbstractComponent's onBoxReadymetodo, ho impostato un attributo di dati personalizzato (il cui nome deriva dalla mia abitudine testIdAttrdi proprietà di ogni componente) per la componente itemIddi valore, se esiste. Aggiungi la Testing.overrides.AbstractComponentclasse al tuo application.jsfilerequires all'array .

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

Questo metodo fornisce agli sviluppatori un modo per riutilizzare un identificatore descrittivo all'interno del loro codice e per avere quegli identificatori disponibili ogni volta che la pagina viene visualizzata. Niente più ricerche attraverso ID non descrittivi e generati dinamicamente.


2

Stiamo sviluppando un framework di test che utilizza il selenio e abbiamo riscontrato problemi con extjs (poiché è il rendering lato client). Trovo utile cercare un elemento una volta che il DOM è 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");
}

1

Per un'interfaccia utente complessa che non è HTML formale, xPath è sempre qualcosa su cui puoi contare, ma un po 'complesso quando si tratta di un'implementazione dell'interfaccia utente diversa utilizzando ExtJs.

Puoi utilizzare Firebug e Firexpath come estensioni per Firefox per testare l'xpath di un determinato elemento e passare semplicemente xpath completo come parametro al selenio.

Ad esempio in codice java:

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

selenium.click(fullXpath);

1

Quando stavo testando l'applicazione ExtJS utilizzando WebDriver, ho utilizzato l'approccio successivo: ho cercato il campo in base al testo dell'etichetta e ho ottenuto l' @forattributo dall'etichetta. Ad esempio, abbiamo un'etichetta

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

E dobbiamo puntare WebDriver alcuni input: //input[@id=(//label[contains(text(),'Name Of Needed Label')]/@for)].

Quindi, sceglierà l'id dall'attributo @fore lo utilizzerà ulteriormente. Questo è probabilmente il caso più semplice, ma ti dà il modo di individuare l'elemento. È molto più difficile quando non hai un'etichetta, ma devi trovare un elemento e scrivere il tuo xpath alla ricerca di fratelli, elementi di discesa / salita.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.