Utilizzo del protractor con anelli

L’indice di loop ( i ) non è quello che mi aspetto quando utilizzo il protractor all’interno di un ciclo.

Sintomi:

Non riuscito: indice fuori limite. Cercando di accedere all’elemento all’indice: ‘x’, ma ci sono solo elementi ‘x’

o

L’indice è statico e sempre uguale all’ultimo valore

Il mio codice

 for (var i = 0; i < MAX; ++i) { getPromise().then(function() { someArray[i] // 'i' always takes the value of 'MAX' }) } 

Per esempio:

 var expected = ['expect1', 'expect2', 'expect3']; var els = element.all(by.css('selector')); for (var i = 0; i < expected.length; ++i) { els.get(i).getText().then(function(text) { expect(text).toEqual(expected[i]); // Error: `i` is always 3. }) } 

o

 var els = element.all(by.css('selector')); for (var i = 0; i < 3; ++i) { els.get(i).getText().then(function(text) { if (text === 'should click') { els.get(i).click(); // fails with "Failed: Index out of bound. Trying to access element at index:3, but there are only 3 elements" } }) } 

o

 var els = element.all(by.css('selector')); els.then(function(rawelements) { for (var i = 0; i < rawelements.length; ++i) { rawelements[i].getText().then(function(text) { if (text === 'should click') { rawelements[i].click(); // fails with "Failed: Index out of bound. Trying to access element at index:'rawelements.length', but there are only 'rawelements.length' elements" } }) } }) 

La ragione per cui questo sta accadendo è perché il protractor usa le promesse.

Leggi https://github.com/angular/protractor/blob/master/docs/control-flow.md

Promises (cioè element(by...) , element.all(by...) ) esegue le loro then funzioni quando il valore sottostante diventa pronto. Ciò significa che tutte le promesse vengono pianificate per prime e quindi le funzioni di then vengono eseguite man mano che i risultati diventano pronti.

Quando si esegue qualcosa del genere:

 for (var i = 0; i < 3; ++i) { console.log('1) i is: ', i); getPromise().then(function() { console.log('2) i is: ', i); someArray[i] // 'i' always takes the value of 3 }) } console.log('* finished looping. i is: ', i); 

Quello che succede è che getPromise().then(function() {...}) ritorna immediatamente, prima che la promise sia pronta e senza eseguire la funzione all'interno di then . Quindi, prima il ciclo viene eseguito 3 volte, pianificando tutte le chiamate getPromise() . Quindi, mentre le promesse si risolvono, vengono eseguite le corrispondenti s.

La console sarebbe simile a questa:

 1) i is: 0 // schedules first `getPromise()` 1) i is: 1 // schedules second `getPromise()` 1) i is: 2 // schedules third `getPromise()` * finished looping. i is: 3 2) i is: 3 // first `then` function runs, but i is already 3 now. 2) i is: 3 // second `then` function runs, but i is already 3 now. 2) i is: 3 // third `then` function runs, but i is already 3 now. 

Quindi, come si esegue il protractor nei cicli? La soluzione generale è la chiusura. Vedi la chiusura JavaScript all'interno di loop - semplice esempio pratico

 for (var i = 0; i < 3; ++i) { console.log('1) i is: ', i); var func = (function() { var j = i; return function() { console.log('2) j is: ', j); someArray[j] // 'j' takes the values of 0..2 } })(); getPromise().then(func); } console.log('* finished looping. i is: ', i); 

Ma questo non è bello da leggere. Fortunatamente, puoi anche usare il filter(fn) funzioni del protractor filter(fn) , get(i) , first() , last() , e il fatto che si expect patch per prendere promesse, per affrontare questo.

Tornando agli esempi forniti in precedenza. Il primo esempio può essere riscritto come:

 var expected = ['expect1', 'expect2', 'expect3']; var els = element.all(by.css('selector')); for (var i = 0; i < expected.length; ++i) { expect(els.get(i).getText()).toEqual(expected[i]); // note, the i is no longer in a `then` function and take the correct values. } 

Il secondo e il terzo esempio possono essere riscritti come:

 var els = element.all(by.css('selector')); els.filter(function(elem) { return elem.getText().then(function(text) { return text === 'should click'; }); }).click(); // note here we first used a 'filter' to select the appropriate elements, and used the fact that actions like `click` can act on an array to click all matching elements. The result is that we can stop using a for loop altogether. 

In altre parole, il protractor ha molti modi per iterare o accedere all'elemento i modo che non sia necessario utilizzare per cicli e i . Ma se devi usare loop e i , puoi usare la soluzione di chiusura.

Hank ha fatto un ottimo lavoro nel rispondere a questo.
Volevo anche notare un altro modo rapido e sporco per gestire questo. Basta spostare la roba di promise su qualche funzione esterna e passarla all’indice.

Ad esempio, se si desidera registrare tutti gli elementi della lista sulla pagina al loro indice (da ElementArrayFinder) si potrebbe fare qualcosa del genere:

  var log_at_index = function (matcher, index) { return $$(matcher).get(index).getText().then(function (item_txt) { return console.log('item[' + index + '] = ' + item_txt); }); }; var css_match = 'li'; it('should log all items found with their index and displayed text', function () { $$(css_match).count().then(function (total) { for(var i = 0; i < total; i++) log_at_index(css_match, i); // move promises to external function }); }); 

Questo è utile quando è necessario eseguire un rapido debug e facile da modificare per il proprio uso.