web-dev-qa-db-ja.com

ループで分度器を使用する

ループインデックス(i)は、ループ内で分度器を使用するときに期待するものではありません。

症状:

失敗:インデックスは範囲外です。 index: 'x'の要素にアクセスしようとしましたが、 'x'要素しかありません

または

インデックスは静的で、常に最後の値に等しい

私のコード

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

例えば:

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

または

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

または

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"
      }
    })
  }
})
27
hankduan

これが起こっている理由は、分度器が約束を使用しているためです。

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

Promise(つまり、element(by...)element.all(by...))は、基になる値が準備完了になると、then関数を実行します。これが意味することは、すべてのプロミスが最初にスケジュールされ、結果の準備ができたときにthen関数が実行されることです。

このようなものを実行すると:

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

起こるのは、getPromise().then(function() {...})がすぐに戻り、promiseの準備が整う前に、then内で関数を実行しないことです。したがって、最初にループが3回実行され、すべてのgetPromise()呼び出しがスケジュールされます。次に、promiseが解決されると、対応するthensが実行されます。

コンソールは次のようになります。

_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.
_

では、分度器をループでどのように実行しますか?一般的な解決策は閉鎖です。 ループ内のJavaScriptクロージャ-簡単で実用的な例 を参照してください

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

しかし、これは読みやすいものではありません。幸いなことに、分度器関数filter(fn)get(i)first()last()、およびexpectこれに対処するために、約束を守るためにパッチが適用されます。

前述の例に戻ります。最初の例は次のように書き換えることができます。

_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.
}
_

2番目と3番目の例は、次のように書き換えることができます。

_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. 
_

つまり、分度器には要素iを反復またはアクセスする多くの方法があるため、forループとiを使用する必要はありません。ただし、forループとiを使用する必要がある場合は、クロージャーソリューションを使用できます。

37
hankduan

ハンクはこれに答えて素晴らしい仕事をしました。
。 promiseのものを外部関数に移動して、インデックスを渡すだけです。

たとえば、ページ上のすべてのリストアイテムをそれぞれのインデックス(ElementArrayFinderから)に記録する場合は、次のようにします。

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

これは、自分で使用するためにいくつかの高速デバッグと簡単な調整が必要な場合に便利です。

3
willko747

私は、上記で議論したはるかに学んだ人々の論理や知恵について議論していません。非同期として宣言された関数内のProtractorの現在のバージョンでは、以下のようなforループ(これはTypeScriptで作成し、@ hetznercloud/protractor-test-helperからflowLogを組み込んでいますが、コンソールと考えていますが、ここでもlogは機能します)は、単純に期待するような動作をします。

let inputFields = await element.all(by.tagName('input'));
let i: number;
flowLog('count = '+ inputFields.length);
for (i=0; i < inputFields.length; i++){
  flowLog(i+' '+await inputFields[i].getAttribute('id')+' '+await inputFields[i].getAttribute('value'));
}

のような出力を生成する

    count = 44
0 7f7ac149-749f-47fd-a871-e989a5bd378e 1
1 7f7ac149-749f-47fd-a871-e989a5bd3781 2
2 7f7ac149-749f-47fd-a871-e989a5bd3782 3
3 7f7ac149-749f-47fd-a871-e989a5bd3783 4
4 7f7ac149-749f-47fd-a871-e989a5bd3784 5
5 7f7ac149-749f-47fd-a871-e989a5bd3785 6

...

42 7f7ac149-749f-47fd-a871-e989a5bd376a 1
43 7f7ac149-749f-47fd-a871-e989a5bd376b 2

私が理解しているように、ここではawaitがキーであり、配列を前もって解決することを強制します(したがって、カウントは正しいです)。ループ内のawaitsは、iになる前に各プロミスを解決しますインクリメントできます。

ここでの私の意図は、読者に選択肢を与えることであり、上記に疑問を投げかけることではありません。

0
Jeremy Kahan