【问题标题】:Using protractor with loops使用带循环的量角器
【发布时间】:2015-03-10 17:04:56
【问题描述】:

当我在循环中使用量角器时,循环索引 (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"
      }
    })
  }
})

【问题讨论】:

  • 感谢您的努力 - 但这是经典的闭环问题。
  • @BenjaminGruenbaum 是的,这是经典的闭环问题,我在答案中引用了stackoverflow.com/questions/750486/…。但是,我打开它有两个原因。 1)许多人没有意识到两者之间的相关性,因为有些人不了解 elementFinders 返回的承诺和 2)关闭不是量角器的最佳解决方案,因为对此有量角器特定的解决方案 - 请参阅答案
  • 悬念要了我的命!哪两个原因?
  • 抱歉输入太快了。编辑了第一个回复。
  • 在数组上使用 .filter(或 .map 或 .forEach)实际上是我在 JS 中通常会这样做(假设没有“let”)。所以我不会称它为量角器特定的。人们不知道这两者之间存在相关性这一事实正是为什么通常不删除重复项的原因——因此他们可以使用相关关键字找到这个问题,然后找到一般问题。我感谢您在这些方面付出的努力(上帝知道我们可以在 promise 标记中使用更多规范),但我不确定这是否合适,因为有类似的规范。如果您愿意,我们可以在 meta 中询问。你怎么看?

标签: angularjs selenium-webdriver promise protractor


【解决方案1】:

发生这种情况的原因是量角器使用承诺。

阅读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 closure inside loops – simple practical example

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

第二个和第三个例子可以改写为:

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,可以使用闭包解决方案。

【讨论】:

  • 这个问题已经看到好几次了,谢谢你的解惑!现在我们可以参考这篇文章了。
  • 是的,在过去的一周里,我已经两次看到这个确切的问题以及许多其他与承诺相关的问题。希望它能帮助人们更多地理解 Promise。
  • “循环计数器”问题并非特定于 Promise。在 for 循环中定义的 任何 函数都将成为计数器终端值的牺牲品,无论它是否是 Promise 回调。例如,考虑一个事件处理程序 - 相同的交易。
  • @Roamer-1888 是的,你是对的,但在 Protractor 的上下文中,承诺是新人绊倒的最大障碍之一。虽然这可以通过使用任何其他回调的循环来发生,但它与量角器的相关性并不高,因为它不像这种用例那么常见。
  • @hankduan:有没有办法在满足条件时打破循环?我不能使用“过滤器”,因为我需要迭代具有相似内容的多个页面(而不是在单个页面上出现相同的页面)。诸如 DONE() 函数之类的东西或满足条件的东西!@hankduan:有没有办法在满足条件时从循环中中断?我不能使用“过滤器”,因为我需要迭代具有相似内容的多个页面
【解决方案2】:

汉克在回答这个问题上做得很好。
我还想指出另一种快速而肮脏的方法来处理这个问题。只需将 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】:

    我并不是在与上面讨论的更有学问的人的逻辑或智慧争论。我写信指出,在声明为 async 的函数中的当前版本的 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 导致每个承诺在我被允许之前被解析递增。

    我在这里的目的是给读者提供选择,而不是质疑上述内容。

    【讨论】:

      【解决方案4】:

      现在更容易做到这一点

      it('test case', async () => {
        let elems = element.all(selector)
      
        for (let i=0; i < await elems.count(); i++) {
          console.log(await elems.get(i).getText())
        }
      });
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-12-27
        • 1970-01-01
        • 2017-02-21
        • 1970-01-01
        • 2023-03-07
        相关资源
        最近更新 更多