在循环中使用量角器
Using protractor with loops
循环索引 (i
) 不是我在循环中使用 Protractor 时所期望的。
症状:
Failed: Index out of bound. Trying to access element at index:'x', but there are only 'x' elements
或
Index is static and always equal to the last value
我的代码
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"
}
})
}
})
发生这种情况的原因是量角器使用了 promises。
Read https://github.com/angular/protractor/blob/master/docs/control-flow.md
Promise(即 element(by...)
、element.all(by...)
)在基础值准备就绪时执行它们的 then
函数。这意味着所有的 promises 首先被安排,然后 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() {...})
returns 立即发生,在 promise 准备就绪之前并且没有执行 then
中的函数。所以首先循环 运行s 通过 3 次,调度所有 getPromise()
调用。然后,随着承诺的解决,相应的 then
是 运行。
控制台看起来像这样:
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
被修补为 take promises,以处理这个。
回到前面提供的示例。第一个例子可以重写为:
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
,可以使用闭包方案
Hank 很好地回答了这个问题。
我还想指出另一种快速而肮脏的方法来处理这个问题。只需将 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
});
});
当您需要进行一些快速调试并且易于调整以供您自己使用时,这会派上用场。
我不是在争论上面讨论的更有学问的人的逻辑或智慧。我写信指出,在 Protractor 的当前版本中,在一个声明为异步的函数中,有一个如下所示的 for 循环(我是用 typeScript 编写的,合并了来自 @hetznercloud/protractor-test-helper 的 flowLog,尽管我相信 console.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
是这里的关键,强制预先解析数组(因此计数是正确的)并且循环中的 await
s 导致每个 promise 是在允许递增 i 之前解决。
我的目的是给读者选择,而不是质疑上面的内容。
最近更简单的方法
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())
}
});
循环索引 (i
) 不是我在循环中使用 Protractor 时所期望的。
症状:
Failed: Index out of bound. Trying to access element at index:'x', but there are only 'x' elements
或
Index is static and always equal to the last value
我的代码
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"
}
})
}
})
发生这种情况的原因是量角器使用了 promises。
Read https://github.com/angular/protractor/blob/master/docs/control-flow.md
Promise(即 element(by...)
、element.all(by...)
)在基础值准备就绪时执行它们的 then
函数。这意味着所有的 promises 首先被安排,然后 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() {...})
returns 立即发生,在 promise 准备就绪之前并且没有执行 then
中的函数。所以首先循环 运行s 通过 3 次,调度所有 getPromise()
调用。然后,随着承诺的解决,相应的 then
是 运行。
控制台看起来像这样:
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
被修补为 take promises,以处理这个。
回到前面提供的示例。第一个例子可以重写为:
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
,可以使用闭包方案
Hank 很好地回答了这个问题。
我还想指出另一种快速而肮脏的方法来处理这个问题。只需将 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
});
});
当您需要进行一些快速调试并且易于调整以供您自己使用时,这会派上用场。
我不是在争论上面讨论的更有学问的人的逻辑或智慧。我写信指出,在 Protractor 的当前版本中,在一个声明为异步的函数中,有一个如下所示的 for 循环(我是用 typeScript 编写的,合并了来自 @hetznercloud/protractor-test-helper 的 flowLog,尽管我相信 console.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
是这里的关键,强制预先解析数组(因此计数是正确的)并且循环中的 await
s 导致每个 promise 是在允许递增 i 之前解决。
我的目的是给读者选择,而不是质疑上面的内容。
最近更简单的方法
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())
}
});